(六) 區塊鏈資料結構 – 金鑰對(公鑰和私鑰)
阿新 • • 發佈:2019-02-05
金鑰是構建比特幣信任網路的核心要素。金鑰通常包括私鑰和公鑰兩部分。其中私鑰用於生成簽名、公鑰用於生成地址。
金鑰生成曲線
比特幣的金鑰採用橢圓曲線演算法 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=2256−232−29−28−27−26−24−1
在生成祕鑰時,回先選取一個基點G。然後生成一個256位的隨機數k,該隨機數即為私鑰。然後通過橢圓隨機曲線乘法,得出曲線上的一個點K,K即為公鑰。其中K=k*G K=k∗G,注意該公式,以目前的算力幾乎是不可逆的。因此可以通過私鑰計算公鑰,但是目前無法通過公鑰反推出私鑰。具體的祕鑰生成過程,參見[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 (;