對于比特幣而言,公鑰對應著btc地址,私鑰相當于密碼,擁有私鑰意味著掌握了這個地址,所以了解私鑰是非常重要的。
我希望通過代碼實現(xiàn)比特幣私鑰、地址的生成來學習了解這方面的知識。本文的代碼很多來自開源項目bitcoinj,實現(xiàn)的語言為java。
一、私鑰與公鑰的關系
通過橢圓曲線算法,我們可以得到一個密鑰對,一個為私鑰,一個為公鑰。私鑰和公鑰都是256位32個字節(jié)的byte數(shù)組。
下面通過代碼來實現(xiàn):
ECKey key = new ECKey();
byte[] privKeyBytes = key.getPrivKeyBytes();
byte[] pubKeyBytes = key.getPubKey();
ECKey封裝了橢圓曲線算法,初始化ECKey,即可獲得一個密鑰對。
二、公鑰轉換為btc地址
由于數(shù)組不便于使用,所以公鑰需要進行格式化,格式化后的結果即為btc地址。
先說下原理:
下面通過代碼實現(xiàn):
/**
* ripemd160(sha256(in))
* 雙hash,計算比特幣地址時使用
*/
public static byte[] sha256hash160(byte[] input) {
try {
byte[] sha256 = MessageDigest.getInstance("SHA-256").digest(input);
RIPEMD160Digest digest = new RIPEMD160Digest();
digest.update(sha256, 0, sha256.length);
byte[] out = new byte[20];
digest.doFinal(out, 0);
return out;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e); // Cannot happen.
}
}
/**
* 從公鑰計算出地址
* @param pubKeyBytes
* @return
*/
public static String getBtcAddress (byte[] pubKeyBytes) {
byte[] hash160 = sha256hash160(pubKeyBytes);
byte version = 0x00;
return Base58Check.encode(version, hash160);
}
三、base58及base58check編碼
base58介紹:
base58在base64的基礎上,去除了幾個看起來會產(chǎn)生歧義的字符,如 0 (零), O (大寫字母O), I (大寫的字母i) and l (小寫的字母L) ,和幾個影響雙擊選擇的字符,如/, +。結果字符集正好58個字符(包括9個數(shù)字,24個大寫字母,25個小寫字母)。
base58check介紹:
Base58 導出的字符串沒有校驗機制,這樣,在傳播過程中,如果漏寫了幾個字符,會檢測不出來。所以使用了改進版的算法 Base58Check。
實現(xiàn)是:在encode前,在輸入流尾部加入輸入內容的hash值(4個字節(jié))。然后再對輸入流進行 Base58Encode。
參考代碼:
info.block123.btc.kit.Base58
info.block123.btc.kit.Base58Check
四、私鑰格式化
為了方便存儲和使用,私鑰必須要進行格式化輸出。
btc私鑰格式介紹:
種類版本描述
Hex? ?16進制byte數(shù)組16進制byte數(shù)組
WIF? ? 5開頭Base58Check編碼
WIF-compressed? ?K or L開頭Base58Check編碼前,在byte數(shù)組后加0x01字節(jié)
代碼實現(xiàn):
/**
* 未加工的密鑰格式化
*/
@Override
public String format(byte[] keyBytes) {
if ( PRIV_KEY_HEX.equals(keyType)) {
return BtcKit.toHexString(keyBytes);
}
if ( PRIV_KEY_WIF.equals(keyType)) {
byte version = (byte)0x80;
return Base58Check.encode(version, keyBytes);
}
if ( PRIV_KEY_WIFC.equals(keyType)) {
byte[] cBytes = new byte[ keyBytes.length + 1];
System.arraycopy(keyBytes, 0, cBytes, 0, keyBytes.length);
cBytes[ cBytes.length - 1] = 0x01;
byte version = (byte)0x80;
return Base58Check.encode(version, cBytes);
}
return null;
}
@Override
public byte[] parse(String src) {
if ( PRIV_KEY_HEX.equals(keyType)) {
return BtcKit.hexStringToByte(src);
}
if ( PRIV_KEY_WIF.equals(keyType)) {
return Base58Check.decode(src);
}
if (PRIV_KEY_WIFC.equals(keyType)) {
byte[] rawBytes = Base58Check.decode(src);
byte[] result = new byte[rawBytes.length-1];
System.arraycopy(rawBytes, 0, result, 0, result.length);
return result;
}
return null;
}
五、私鑰推算出比特幣地址
比特幣地址可以由私鑰推算出來。下面為代碼實現(xiàn):
@Test
public void testPrivKeyToAddress() {
? ? String formatPrivKey = "5JG9hT3beGTJuUAmCQEmNaxAuMacCTfXuw1R3FCXig23RQHMr4K";
? ? KeyFormat format = new PrivKeyFormat(KeyFormat.PRIV_KEY_WIF);
? ?byte[] privKeyBytes = format.parse(formatPrivKey);
? ?byte[] pubKeyBytes = ECKey.publicKeyFromPrivate(BtcKit.byte32toBigInteger(privKeyBytes), false);
? ? String address = BtcKit.getBtcAddress(pubKeyBytes);
? ? System.out.println("計算后的地址:" + address);
Assert.assertEquals(address, "1thMirt546nngXqyPEz532S8fLwbozud8");
}
PS:如果覺得對你有幫助,歡迎打賞
btc地址:1BYgnJ1Xv561L3qFrFJasVzEGs1JC93QV1