1. 程式人生 > >(六) 區塊鏈資料結構 – 金鑰對(公鑰和私鑰)

(六) 區塊鏈資料結構 – 金鑰對(公鑰和私鑰)

金鑰是構建比特幣信任網路的核心要素。金鑰通常包括私鑰和公鑰兩部分。其中私鑰用於生成簽名、公鑰用於生成地址。

金鑰生成曲線

比特幣的金鑰採用橢圓曲線演算法 SECP256k1來生成。SECP256K1曲線的大致形狀如下:

SECP256k1

該曲線的數學表達是為:y^2 \ \% \ p=(x^3+7) \ \%\ py2%p=(x3+7)%p,其中

p=2^{256} - 2^{32} - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1p=225623229282726241

在生成祕鑰時,回先選取一個基點G。然後生成一個256位的隨機數k,該隨機數即為私鑰。然後通過橢圓隨機曲線乘法,得出曲線上的一個點K,K即為公鑰。其中K=k*G

K=kG,注意該公式,以目前的算力幾乎是不可逆的。因此可以通過私鑰計算公鑰,但是目前無法通過公鑰反推出私鑰。具體的祕鑰生成過程,參見[1] 橢圓曲線演算法
因為公鑰為橢圓曲線的上的點,因此可以用其座標<x,y>來標識。因為y可以通過x計算出來,因此通常只儲存和傳輸橫座標x。因為曲線相對x軸對稱,因此通過x計算出的y值會包含正負兩項,因此通常在公鑰資料前,加一個標識位,來標識是否儲存了y值或儲存的y值為整數還是負數。

公鑰的資料格式如下:

公鑰儲存格式

其中標誌位如果為04,代表公鑰採用未壓縮的格式儲存,如果標誌位為02或03,則代表公鑰採用壓縮格式儲存,02代表y的為正數,03代表y為負數。

核心程式碼

核心變數定義

   //比特幣使用的secp256k1橢圓曲線引數
    private static final X9ECParameters CURVE_PARAMS = CustomNamedCurves.getByName("secp256k1");

    /** The parameters of the secp256k1 curve that Bitcoin uses. */
    public static final ECDomainParameters CURVE;

    /**
     * Equal to CURVE.getN().shiftRight(1), used for canonicalising the S value of a signature. If you aren't
     * sure what this is about, you can ignore it.
     * 等於曲線最大值域右移一位,作為簽名S值的基數
     */
public static final BigInteger HALF_CURVE_ORDER; private static final SecureRandom secureRandom; static { // Init proper random number generator, as some old Android installations have bugs that make it unsecure. if (Utils.isAndroidRuntime()) new LinuxSecureRandom(); // Tell Bouncy Castle to precompute data that's needed during secp256k1 calculations. Increasing the width // number makes calculations faster, but at a cost of extra memory usage and with decreasing returns. 12 was // picked after consulting with the BC team. FixedPointUtil.precompute(CURVE_PARAMS.getG(), 12); //設定金鑰生成使用的橢圓曲線引數 CURVE = new ECDomainParameters(CURVE_PARAMS.getCurve(), CURVE_PARAMS.getG(), CURVE_PARAMS.getN(), CURVE_PARAMS.getH()); //設定簽名S值生成的基數標準 HALF_CURVE_ORDER = CURVE_PARAMS.getN().shiftRight(1); //例項化隨機數生成物件 secureRandom = new SecureRandom(); } // The two parts of the key. If "priv" is set, "pub" can always be calculated. If "pub" is set but not "priv", we // can only verify signatures not make them. // 金鑰的兩部分:公鑰和私鑰。通過私鑰可以計算公鑰,通過公鑰無法反退出私鑰。 // 當只設置了公鑰而未設定私鑰時,該金鑰智慧用於簽名驗證,不能用於簽名生成 protected final BigInteger priv; // A field element. protected final LazyECPoint pub;

生成祕鑰物件

    /**
     * Generates an entirely new keypair with the given {@link SecureRandom} object. Point compression is used so the
     * resulting public key will be 33 bytes (32 for the co-ordinate and 1 byte to represent the y bit).
     * 通過提供的隨機數生成器,生成完整的金鑰對。
     * 生成的公鑰包含33個位元組,其中x座標佔用32額位元組,y座標佔用1個位元組(因為y值可以通過x只計算出來,因此這個位元組用於標識正負)
     */
    public ECKey(SecureRandom secureRandom) {
        //例項化金鑰對生成器
        ECKeyPairGenerator generator = new ECKeyPairGenerator();
        //設定金鑰對生成器的相關引數,包括曲線型別和隨機數生成器
        ECKeyGenerationParameters keygenParams = new ECKeyGenerationParameters(CURVE, secureRandom);
        //初始化金鑰生成器相關引數
        generator.init(keygenParams);
        //生成金鑰對
        AsymmetricCipherKeyPair keypair = generator.generateKeyPair();

        //獲取公鑰和私鑰引數物件
        ECPrivateKeyParameters privParams = (ECPrivateKeyParameters) keypair.getPrivate();
        ECPublicKeyParameters pubParams = (ECPublicKeyParameters) keypair.getPublic();
        //獲取私鑰和公鑰值
        priv = privParams.getD();
        pub = new LazyECPoint(CURVE.getCurve(), pubParams.getQ().getEncoded(true));
        //設定金鑰對生成時間
        creationTimeSeconds = Utils.currentTimeSeconds();
    }

生成公鑰和私鑰

    /**
     * Given the domain parameters this routine generates an EC key
     * pair in accordance with X9.62 section 5.2.1 pages 26, 27.
     */
    public AsymmetricCipherKeyPair generateKeyPair()
    {
        BigInteger n = params.getN();
        int nBitLength = n.bitLength();
        int minWeight = nBitLength >>> 2;

        BigInteger d;
        for (;