比特幣、區塊鏈基礎技術簡介(含C#原始碼)(一) —— 橢圓曲線密碼學之secp256k1的C#簡易實現

深圳峰哥發表於2018-08-13

轉載請在文章首尾註明原始出處CSDN
前言

原本這個文章的標題是《比特幣、區塊鏈基礎技術學習筆記》,想做個學習筆記,供有相同興趣的同學參考;但發到朋友圈後部分朋友反饋說看不懂,於是把這個筆記補充了簡介這一章節內容(其餘章節也有少量調整或改動),再把標題改成了《比特幣、區塊鏈基礎技術簡介》

簡介

文章的副標題是《橢圓曲線密碼學之secp256k1的C#簡易實現》,目的在於基於C#語言在兩百行左右的程式碼簡易實現了橢圓曲線密碼學secp256k1的根據私鑰計算公鑰的演算法。
那麼從《筆記》改為《簡介》之後,首先就要回答以下幾個問題:什麼是橢圓曲線密碼學?有什麼用?在比特幣、區塊鏈中又發揮了什麼作用?
橢圓曲線密碼學,是非對稱加密技術的一種。那麼什麼又是非對稱加密技術呢?又有什麼用呢?
簡單來說,非對稱加密技術基本上是用來解決類似於“證明我媽是我媽”這一類問題的。在比特幣系統中,就用來“證明我的比特幣就是我的比特幣(我才能花出去)”;而比特幣及大部分割槽塊鏈系統,都採用了橢圓曲線密碼學,並且通常用的是secp256k1。
順帶說一句,大家平常用的網銀的UKey,其中也用到了非對稱加密技術,作用也就是為了在銀行系統“證明我就是我”。
通常非對稱加密技術會給不同的人生成不同的私鑰,再根據私鑰生成公鑰;公鑰會公佈給大眾,而私鑰則需要妥善保管。
那麼非對稱加密技術怎麼來證明我的比特幣就是我的比特幣呢?其中的道理在於:用私鑰加密的資料,用公鑰才能解開;換句話說,用公鑰能解讀出正確的資料,就能證明你才是私鑰的擁有者,那麼你就是這些比特幣(或者銀行賬戶)的擁有者,你就能順理成章地支配這些比特幣。
目前常用的非對稱加密技術有RSA和橢圓曲線密碼學(ECC)。RSA目前普遍用於銀行、證書等系統;而比特幣、區塊鏈系統則基本採用了橢圓曲線密碼學。
在比特幣系統中,所有賬本是公開的;即使沒有私鑰,你也能清楚的看到,每個比特幣從哪裡來,到了哪裡去;比特幣錢包就是你的私鑰合集,如果不幸遺失了私鑰或錢包,那麼私鑰對應的比特幣將可能會被私鑰的拾獲者進行支配;如果確定遺失並沒有其他人拾獲,那麼那些比特幣,還是你的,只是你再也無法支付出去了(花錢需要私鑰)。
簡單地說,在比特幣系統中:收錢只需要公鑰,花錢需要私鑰;而橢圓曲線密碼學,則是這一切的基礎。

橢圓曲線密碼學之secp256k1的C#簡易實現

本文的內容偏向於C#
的簡易實現(兩百行程式碼左右),所以關於橢圓曲線密碼學的介紹只止步於能實現即可,並不完整。同時限於該知識是從搜尋引擎中來,加之個人理解能力有限,所以可能有錯漏之處,希望懂的朋友能留言指正。

橢圓曲線密碼學

對於橢圓曲線,我們用到的方程式是:y2=x3+ax+b

y^{2} = x^{3} + ax + b
;對於給定的a
a
b
b
,該方程在笛卡爾座標系確定了一條曲線。
橢圓曲線密碼學,取某個大質數P
P
,對上述方程進行同餘處理,得到:
y2x3+ax+b(modP)
y^{2} ≡x^{3} + ax + b (mod P)

當給定整數a
a
b
b
、質數P
P
時,上述方程的整數解及虛點Zero
Zero
構成了點集p
p
。(這裡的Zero
Zero
就是零元素,在簡易C#程式碼中將被實現為null值)
取點集p
p
中單位點G(xg,yg)
G(x_{g}, y_{g})
,定義G=(xg,yg)
-G=(x_{g},-y_{g})

定義加法M(x1,y1)+N(x2,y2)=Q(x3,y3)
M(x_{1},y_{1})+N(x_{2},y_{2})=Q(x_{3},y_{3})
如下:
x3k2x1x2(modP)
x_{3} ≡ k^{2} − x_{1} − x_{2} (mod P)

y3k(x1x3)y1(modP)
y_{3} ≡ k(x_{1} − x_{3}) − y_{1}(mod P)

其中,k(y2y1)/(x2x1)(modP,x1!=x2)
k ≡ (y_{2} − y_{1})/ (x_{2} − x_{1}) (mod P,x1!=x2時)
k(3x12+a)/2y1(modP,x1==x2)
k ≡ (3x_{1}^{2}+ a)/2y_{1}(mod P,x1==x2時)

定義G+(G)=Zero
G+(-G)=Zero
,定義Zero+Q=Q+Zero=Q(Q)
Zero+Q=Q+Zero= Q(Q為任意點)

根據上述定義的加法,G+G=2G
G + G = 2G
2G+G=G+2G=3G
2G+G=G+2G=3G…
也在點集p
p
內。
對於給定P
P
a
a
b
b
G
G
,存在整數N
N
,使得NG=Zero
NG=Zero
。其中滿足NG=Zero
NG=Zero
的最小正整數n
n
稱為G
G
的階。

secp256k1

在上述理論中取以下值即可:
P

P
= FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F
=225623229282726241
= 2^{256} − 2^{32} − 2^{9} − 2^{8} − 2^{7} − 2^{6} − 2^{4} – 1

a=0,b=7
a = 0, b = 7

單位點G=
G =

(0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8)
n
n
= FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141

應用

在 1 ~ n1

n-1
之間,隨機取一個整數 privateKey
privateKey
,計算 publicKey=privateKeyG
publicKey = privateKey * G

橢圓曲線密碼學在數學上,有這樣的特性:從privateKey
privateKey
計算出 publicKey
publicKey
,相對容易(根據上述公式計算即可),計算機只需要毫秒級別的時間即可;但是要從publicKey
publicKey
計算出 privateKey
privateKey
,那就相當困難了,即使目前最快的計算機也可能要算上個千年萬載的。
其中 privateKey
privateKey
就是私鑰,如果不幸被別人獲取了,這個私鑰能支配的比特幣就可能會立即被轉移至其他賬戶。publicKey
publicKey
就是公鑰,在經過一系列處理後,就可以公開成為你的比特幣收款地址。

C#簡易實現

根據私鑰來計算公鑰,實際上就是實現點G的標量乘法,從演算法到程式碼,這裡有兩個難點:一、需要一個大整數類:這一點還好,通過搜尋引擎,找到了C#自身已有的BigInteger類的完美實現,只需要引用 System.Numerics.dll就好了;二、計算公式中的斜率 k

k
值,用到了費爾瑪小定理來實現,不太瞭解的同學可自行通過搜尋引擎進行學習。
上簡易程式碼(兩個檔案共兩百行左右):

BitcoinTools.cs

using System.Text;
using System.Globalization;
using System.Numerics;

public class BitcoinTools
{
    public class ECPoint
    {
        public BigInteger x;
        public BigInteger y;
    }

    public static readonly BigInteger P = BigInteger.Parse("0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", NumberStyles.HexNumber);
    public static readonly BigInteger N = BigInteger.Parse("0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", NumberStyles.HexNumber);
    public static readonly ECPoint G = new ECPoint()
    {
        x = BigInteger.Parse("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", NumberStyles.HexNumber),
        y = BigInteger.Parse("483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", NumberStyles.HexNumber)
    };

    /// <summary>
    /// N - 1 
    /// </summary>
    private static readonly BigInteger N_1 = BigInteger.Parse("0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140", NumberStyles.HexNumber);

    /// <summary>
    /// P - 2
    /// </summary>
    private static readonly BigInteger P_2 = BigInteger.Parse("0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2D", NumberStyles.HexNumber);
    private static readonly int MaxBit = 256;
    private static readonly ECPoint[] Power_G = new ECPoint[MaxBit];

    /// <summary>
    /// 為了提高運算效率,將G與2的n次方的積進行快取
    /// </summary>
    static BitcoinTools()
    {
        for (int i = 0; i < MaxBit; i++)
        {
            if (i == 0)
            {
                Power_G[0] = G;
            }
            else
            {
                Power_G[i] = Addition(Power_G[i - 1], Power_G[i - 1]);
            }
        }
    }

    /// <summary>
    /// 判斷某點是否在橢圓曲線 secp256k1 上(mod P)
    /// </summary>
    public static bool IsOnCurve(ECPoint point)
    {
        BigInteger leftSide = BigInteger.Pow(point.y, 2) % P;
        BigInteger rightSide = (BigInteger.Pow(point.x, 3) + 7) % P;
        return leftSide == rightSide;
    }

    /// <summary>
    /// 取得x的倒數(mod P)
    /// 根據費爾瑪小定理
    /// </summary>
    private static BigInteger GetReciprocalModP(BigInteger x)
    {
        BigInteger[] array = new BigInteger[MaxBit];
        BigInteger ret = 1;
        BigInteger temp = P_2;
        for (int i = 0; i < MaxBit; i++)
        {
            if (i == 0)
            {
                array[0] = x;
            }
            else
            {
                array[i] = BigInteger.Pow(array[i - 1], 2) % P;
            }

            if (!temp.IsEven)
            {
                ret *= array[i];
                ret %= P;
            }

            temp >>= 1;

            if (temp.IsZero)
                break;
        }
        return ret;
    }

    /// <summary>
    /// 兩個點相加
    /// </summary>
    public static ECPoint Addition(ECPoint a, ECPoint b)
    {
        if (a == null)
            return b;
        if (b == null)
            return a;

        BigInteger k;
        if (a.x == b.x)
        {
            if ((a.y + b.y) % P == 0)
            {
                return null;
            }
            k = 3 * BigInteger.Pow(a.x, 2);
            k *= GetReciprocalModP(2 * a.y);
            k %= P;
        }
        else
        {
            k = (b.y + P - a.y) % P;
            k *= GetReciprocalModP((b.x + P - a.x) % P);
            k %= P;
        }
        ECPoint ret = new ECPoint();
        ret.x = (k * k + P - a.x + P - b.x) % P;
        ret.y = (k * (P + a.x - ret.x) + P - a.y) % P;
        return ret;
    }

    /// <summary>
    /// 對點G的標量乘法
    /// </summary>
    public static ECPoint Multiplication(BigInteger pri)
    {
        ECPoint ret = null;
        for (int i = 0; i < MaxBit; i++)
        {
            if (!pri.IsEven)
            {
                if (ret == null)
                {
                    ret = Power_G[i];
                }
                else
                {
                    ret = Addition(ret, Power_G[i]);
                }
            }

            pri >>= 1;

            if (pri.IsZero)
                break;
        }
        return ret;
    }

    /// <summary>
    /// 將位元組陣列輸出為字串
    /// </summary>
    public static string BytesToString(byte[] bytes)
    {
        StringBuilder builder = new StringBuilder();
        for (int i = bytes.Length - 1; i >= 0; i--)
        {
            builder.Append(bytes[i].ToString("X2"));
        }
        return builder.ToString();
    }
}

Program.cs

using System;
using System.Numerics;

class Program
{
    static void Main(string[] args)
    {
        for (int k = 1; k <= 20; k++)
        {
            var publicKey = BitcoinTools.Multiplication(k);
            Console.WriteLine(" k = {0}", k);
            Console.WriteLine(" x = {0}", BitcoinTools.BytesToString(publicKey.x.ToByteArray()));
            Console.WriteLine(" y = {0}", BitcoinTools.BytesToString(publicKey.y.ToByteArray()));
            Console.WriteLine();
        }

        string[] kArray =
            { "112233445566778899"
            , "112233445566778899112233445566778899"
            , "28948022309329048855892746252171976963209391069768726095651290785379540373584"
            , "57896044618658097711785492504343953926418782139537452191302581570759080747168"
            , "86844066927987146567678238756515930889628173209306178286953872356138621120752"
            };

        foreach (string k in kArray)
        {
            var publicKey = BitcoinTools.Multiplication(BigInteger.Parse(k));
            Console.WriteLine(" k = {0}", k);
            Console.WriteLine(" x = {0}", BitcoinTools.BytesToString(publicKey.x.ToByteArray()));
            Console.WriteLine(" y = {0}", BitcoinTools.BytesToString(publicKey.y.ToByteArray()));
            Console.WriteLine();
        }

        BigInteger kStart = BigInteger.Parse("115792089237316195423570985008687907852837564279074904382605163141518161494317");
        BigInteger kEnd = BigInteger.Parse("115792089237316195423570985008687907852837564279074904382605163141518161494336");
        for (BigInteger k = kStart; k <= kEnd; k++)
        {
            var publicKey = BitcoinTools.Multiplication(k);
            Console.WriteLine(" k = {0}", k);
            Console.WriteLine(" x = {0}", BitcoinTools.BytesToString(publicKey.x.ToByteArray()));
            Console.WriteLine(" y = {0}", BitcoinTools.BytesToString(publicKey.y.ToByteArray()));
            Console.WriteLine();
        }
    }
}

執行結果

執行結果和這個網址的內容能匹配上:
https://crypto.stackexchange.com/questions/784/are-there-any-secp256k1-ecdsa-test-examples-available
暫未發現Bug。結果的略有不同之處是在部分大整數(最高bit為1的大整數)之前額外多了兩個十六進位制的0。是因為C#實現的大整數,如果最高位(bit)是1的話,則表示是負數;因此正數且最高bit為1的話,會在之前再補充一個位元組0。

後記及下節內容預告

橢圓曲線密碼學,相信是部分初入門朋友的難點;我也不例外,為這點內容通過搜尋引擎搜了n久,然後寫了這些程式碼,整理成文,為的是讓別的初學者能快速、初步瞭解相關的技術。下次的內容是《比特幣私鑰、公鑰及錢包地址的C#簡易實現》

第一次在網路上發表文章,排版及錯漏之處在所難免,大家請多多見諒。
轉載請在文章首尾註明原始出處CSDN。

相關文章