序
??? 這幾個(gè)月一直在忙自己的終身大事戴卜,好久不寫文章了;靜下來(lái)分析寫作中斷的原因琢岩,發(fā)現(xiàn)一半是工作太忙投剥,一半是自己太懶,堅(jiān)持很多年做一件事情真得挺難的担孔。從今天開始江锨,我要拾起寫作計(jì)劃,寫作可以梳理自己的知識(shí)框架糕篇,同時(shí)分享給大家一起學(xué)習(xí)啄育,歡迎大家提出建議。
??? 之前的寫作內(nèi)容都是區(qū)塊鏈入門級(jí)別的拌消,出于個(gè)人愛好挑豌,主要圍繞ethereum展開,但是工作中基本不涉及ethereum的內(nèi)容墩崩,一直沒有深入下去氓英;由于工作圍繞BitCoin展開,以后的文章我先轉(zhuǎn)移到BTC領(lǐng)域鹦筹。
??? 今天先從BTC的基本概念入手铝阐,整理一下私鑰、公鑰铐拐、地址的概念饰迹,這些概念在網(wǎng)絡(luò)上已經(jīng)泛濫很久了,實(shí)在是太枯燥余舶,還是自己coding來(lái)得爽啊鸭,今天就和大家分享如何用Java生成BTC私鑰、公鑰和地址匿值。理論方面先分享幾個(gè)鏈接:
??? https://en.bitcoin.it/wiki/Private_key
??? 比特幣密鑰生成規(guī)則及 Go 實(shí)現(xiàn) (特別推薦赠制,這篇文章質(zhì)量很高)
??? 這里強(qiáng)調(diào)一下理論的重要性,只有徹底理解了BTC私鑰、公鑰钟些、地址的關(guān)系烟号,才能coding出來(lái),所有的代碼只是思想的體現(xiàn)政恍。
Java 代碼實(shí)現(xiàn)
?代碼部分只貼出了核心片段汪拥,稍微加工一下即可運(yùn)行。
// 主要使用了java lang 有關(guān)橢圓曲線算法的package, bouncycastle lib(bcprov-jdk15on-160.jar)以及bitcoinj的Base58類
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECPoint;
import java.security.PublicKey;
import java.security.PrivateKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.MessageDigest;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.io.UnsupportedEncodingException;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bitcoinj.core.Base58;
// 定義Key類篙耗,用來(lái)封裝公私鑰對(duì)和地址
class Key {
? ? private String privkey;
? ? private String pubkey;
? ? private String address;
? ? public Key() {
? ? ? ? Reset();
? ? }
? ? public Key(String privkey, String pubkey, String address) {
? ? ? ? this.privkey = privkey;
? ? ? ? this.pubkey? = pubkey;
? ? ? ? this.address = address;
? ? }
? ? public void Reset() {
? ? ? ? this.privkey = null;
? ? ? ? this.pubkey? = null;
? ? ? ? this.address = null;
? ? }
? ? public void SetPrivKey(String privkey) {
? ? ? ? this.privkey = privkey;
? ? }
? ? public void SetPubKey(String pubkey) {
? ? ? ? this.pubkey = pubkey;
? ? }
? ? public void SetAddress(String address) {
? ? ? ? this.address = address;
? ? }
? ? public String ToString() {
? ? ? ? return "{\n"
? ? ? ? ? ? + "\t privkey:" + this.privkey + "\n"
? ? ? ? ? ? + "\t pubkey :" + this.pubkey? + "\n"
? ? ? ? ? ? + "\t address:" + this.address + "\n"
? ? ? ? ? ? + "}\n";
? ? }
}
public class KeyGenerator {
?? // Base58 encode prefix迫筑,不同的prefix可以定制地址的首字母
? ? static final byte PubKeyPrefix = 65;
? ? static final byte PrivKeyPrefix = -128;
? ? static final String PrivKeyPrefixStr = "80";
? ? static final byte PrivKeySuffix = 0x01;
? ? static int keyGeneratedCount = 1;
? ? static boolean debug = true;
? ? static KeyPairGenerator sKeyGen;
? ? static ECGenParameterSpec sEcSpec;
? ? static {
? ? ? ? ? ? Security.addProvider(new BouncyCastleProvider());
? ? }
? ? private static boolean ParseArguments(String []argv) {
? ? ? ? for (int i = 0; i < argv.length - 1; i++) {
? ? ? ? ? ? if ("-n".equals(argv[i])) {
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? keyGeneratedCount = Integer.parseInt(argv[i + 1]);
? ? ? ? ? ? ? ? ? ? i = i + 1;
? ? ? ? ? ? ? ? ? ? continue;
? ? ? ? ? ? ? ? } catch (NumberFormatException e) {
? ? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? ? ? ? return false;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? } else if ("-debug".equals(argv[i])) {
? ? ? ? ? ? ? ? debug = true;
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? System.out.println(argv[i] + " not supported...");
? ? ? ? ? ? ? ? return false;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return keyGeneratedCount > 0;
? ? }
? ? public static void main(String args[]) {
? ? ? ? if (args.length > 1) {
? ? ? ? ? if (!ParseArguments(args)) {
? ? ? ? ? ? ? System.out.println("Arguments error, please check...");
? ? ? ? ? ? ? System.exit(-1);
? ? ? ? ? }
? ? ? ? }
? ? ? ? Key key = new Key();
? ? ? ? key.Reset();
? ? ? ? KeyGenerator generator = new KeyGenerator();
? ? ? ? for (int i = 0; i < keyGeneratedCount; i++) {
? ? ? ? ? ? key.Reset();
? ? ? ? ? ? if (generator.GenerateKey(key)) {
? ? ? ? ? ? ? ? System.out.println(key.ToString());
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? System.out.println("Generate key error...");
? ? ? ? ? ? ? ? System.exit(-1);
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ? public KeyGenerator() {
? ? ? ? Init();
? ? }
? ? private void Init() {
? ? ? ? // Initialize key generator
? ? ? ? // The specific elliptic curve used is the secp256k1.
? ? ? ? try {
? ? ? ? ? ? sKeyGen = KeyPairGenerator.getInstance("EC");
? ? ? ? ? ? sEcSpec = new ECGenParameterSpec("secp256k1");
? ? ? ? ? ? if (sKeyGen == null) {
? ? ? ? ? ? ? ? System.out.println("Error: no ec algorithm");
? ? ? ? ? ? ? ? System.exit(-1);
? ? ? ? ? ? }
? ? ? ? ? ? sKeyGen.initialize(sEcSpec); // 采用secp256K1標(biāo)準(zhǔn)的橢圓曲線加密算法
? ? ? ? } catch (InvalidAlgorithmParameterException e) {
? ? ? ? ? ? System.out.println("Error:" + e);
? ? ? ? ? ? System.exit(-1);
? ? ? ? } catch (NoSuchAlgorithmException e) {
? ? ? ? ? ? System.out.println("Error:" + e);
? ? ? ? ? ? System.exit(-1);
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? System.out.println("Error:" + e);
? ? ? ? ? ? System.exit(-1);
? ? ? ? }
? ? }
? ? public boolean GenerateKey(Key key) {
? ? ? ? key.Reset();
? ? ? ? // Generate key pair,依據(jù)橢圓曲線算法產(chǎn)生公私鑰對(duì)
? ? ? ? KeyPair kp = sKeyGen.generateKeyPair();
? ? ? ? PublicKey pub = kp.getPublic();
? ? ? ? PrivateKey pvt = kp.getPrivate();
? ? ? ? ECPrivateKey epvt = (ECPrivateKey)pvt;
? ? ? ? String sepvt = Utils.AdjustTo64(epvt.getS().toString(16)).toUpperCase(); // 私鑰16進(jìn)制字符串
? ? ? ? if (debug) {
? ? ? ? ? ? System.out.println("Privkey[" + sepvt.length() + "]: " + sepvt);
? ? ? ? }
????? // 獲取X宗弯,Y坐標(biāo)點(diǎn)脯燃,“04” + sx + sy即可獲得完整的公鑰,但是這里我們需要壓縮的公鑰
? ? ? ? ECPublicKey epub = (ECPublicKey)pub;
? ? ? ? ECPoint pt = epub.getW();
? ? ? ? String sx = Utils.AdjustTo64(pt.getAffineX().toString(16)).toUpperCase();
? ? ? ? String sy = Utils.AdjustTo64(pt.getAffineY().toString(16)).toUpperCase();
? ? ? ? String bcPub = "04" + sx + sy;
? ? ? ? if (debug) {
? ? ? ? ? ? System.out.println("Pubkey[" + bcPub.length() + "]: " + bcPub);
? ? ? ? }
? ? ? ? // Here we get compressed pubkey
?????? // 獲取壓縮公鑰的方法:Y坐標(biāo)最后一個(gè)字節(jié)是偶數(shù)蒙保,則 "02" + sx辕棚,否則 "03" + sx
? ? ? ? byte[] by = Utils.HexStringToByteArray(sy);
? ? ? ? byte lastByte = by[by.length - 1];
? ? ? ? String compressedPk;
? ? ? ? if ((int)(lastByte) % 2 == 0) {
? ? ? ? ? ? compressedPk = "02" + sx;
? ? ? ? } else {
? ? ? ? ? ? compressedPk = "03" + sx;
? ? ? ? }
? ? ? ? if (debug) {
? ? ? ? ? ? System.out.println("compressed pubkey: " + compressedPk);
? ? ? ? }
? ? ? ? key.SetPubKey(compressedPk);
? ? ? ? // We now need to perform a SHA-256 digest on the public key,
? ? ? ? // followed by a RIPEMD-160 digest.
?????? // 對(duì)壓縮的公鑰做SHA256摘要
? ? ? ? byte[] s1 = null;
? ? ? ? MessageDigest sha = null;
? ? ? ? try {
? ? ? ? ? ? sha = MessageDigest.getInstance("SHA-256");
? ? ? ? ? ? s1 = sha.digest(Utils.HexStringToByteArray(compressedPk));
? ? ? ? ? ? if (debug) {
? ? ? ? ? ? ? ? System.out.println("sha: " + Utils.BytesToHex(s1).toUpperCase());
? ? ? ? ? ? }
? ? ? ? } catch (NoSuchAlgorithmException e) {
? ? ? ? ? ? System.out.println("Error:" + e);
? ? ? ? ? ? return false;
? ? ? ? }
? ? ? ? // We use the Bouncy Castle provider for performing the RIPEMD-160 digest
? ? ? ? // since JCE does not implement this algorithm.
??????? // SHA256摘要之后做RIPEMD-160,這里調(diào)用Bouncy Castle的庫(kù)邓厕,不知道的同學(xué)百度搜一下就懂了
? ? ? ? byte[] r1 = null;
? ? ? ? byte[] r2 = null;
? ? ? ? try {
? ? ? ? ? ? MessageDigest rmd = MessageDigest.getInstance("RipeMD160", "BC");
? ? ? ? ? ? if (rmd == null || s1 == null) {
? ? ? ? ? ? ? ? System.out.println("can't get ripemd160 or sha result is null");
? ? ? ? ? ? ? ? return false;
? ? ? ? ? ? }
? ? ? ? ? ? r1 = rmd.digest(s1);
? ? ? ? ? ? r2 = new byte[r1.length + 1];
? ? ? ? ? ? r2[0] = PubKeyPrefix; // RipeMD160 摘要之后加上公鑰前綴
? ? ? ? ? ? for (int i = 0; i < r1.length; i++)
? ? ? ? ? ? ? ? r2[i + 1] = r1[i]; // 寫的有點(diǎn)low逝嚎,大家采用System.arraycopy自行修改吧
? ? ? ? ? ? if (debug) {
? ? ? ? ? ? ? ? System.out.println("rmd: " + Utils.BytesToHex(r2).toUpperCase());
? ? ? ? ? ? }
? ? ? ? } catch (NoSuchAlgorithmException e) {
? ? ? ? ? ? System.out.println("Error:" + e);
? ? ? ? ? ? return false;
? ? ? ? } catch (NoSuchProviderException e) {
? ? ? ? ? ? System.out.println("Error:" + e);
? ? ? ? ? ? return false;
? ? ? ? }
? ? ? ? byte[] s2 = null; // 加上前綴之后做兩次SHA256
? ? ? ? if (sha != null && r2 != null) {
? ? ? ? ? ? sha.reset();
? ? ? ? ? ? s2 = sha.digest(r2);
? ? ? ? ? ? if (debug) {
? ? ? ? ? ? ? ? System.out.println("sha: " + Utils.BytesToHex(s2).toUpperCase());
? ? ? ? ? ? }
? ? ? ? } else {
? ? ? ? ? ? System.out.println("cant't do sha-256 after ripemd160");
? ? ? ? ? ? return false;
? ? ? ? }
? ? ? ? byte[] s3 = null;
? ? ? ? if (sha != null && s2 != null) {
? ? ? ? ? ? sha.reset();
? ? ? ? ? ? s3 = sha.digest(s2);
? ? ? ? ? ? if (debug) {
? ? ? ? ? ? ? ? System.out.println("sha: " + Utils.BytesToHex(s3).toUpperCase());
? ? ? ? ? ? }
? ? ? ? } else {
? ? ? ? ? ? System.out.println("cant't do sha-256 after sha-256");
? ? ? ? ? ? return false;
? ? ? ? }
??????? // 讀懂下面內(nèi)容,大家仔細(xì)閱讀《比特幣密鑰生成規(guī)則及 Go 實(shí)現(xiàn)》
? ? ? ? byte[] a1 = new byte[r2.length + 4];
? ? ? ? for (int i = 0 ; i < r2.length ; i++) a1[i] = r2[i];
? ? ? ? for (int i = 0 ; i < 4 ; i++) a1[r2.length + i] = s3[i];
? ? ? ? if (debug) {
? ? ? ? ? ? System.out.println("before base58: " + Utils.BytesToHex(a1).toUpperCase());
? ? ? ? }
? ? ? ? key.SetAddress(Base58.encode(a1)); // 到此详恼,可以獲取WIF格式的地址
? ? ? ? if (debug) {
? ? ? ? ? ? System.out.println("addr: " + Base58.encode(a1));
? ? ? ? }
? ? ? ? // Lastly, we get compressed privkey 最后獲取壓縮的私鑰
? ? ? ? byte[] pkBytes = null;
? ? ? ? pkBytes = Utils.HexStringToByteArray("80" + sepvt + "01");//sepvt.getBytes("UTF-8");
? ? ? ? if (debug) {
? ? ? ? ? ? ? ? System.out.println("raw compressed privkey: " + Utils.BytesToHex(pkBytes).toUpperCase());
? ? ? ? ? ? }
? ? ? ? try {
? ? ? ? ? ? sha = MessageDigest.getInstance("SHA-256");
? ? ? ? } catch (NoSuchAlgorithmException e) {
? ? ? ? ? ? System.out.println("Error:" + e);
? ? ? ? ? ? return false;
? ? ? ? }
? ? ? ? sha.reset();
? ? ? ? byte[] shafirst? = sha.digest(pkBytes);
? ? ? ? sha.reset();
? ? ? ? byte[] shasecond = sha.digest(shafirst);
? ? ? ? byte[] compressedPrivKey = new byte[pkBytes.length + 4];
? ? ? ? for (int i = 0; i < pkBytes.length; i++) {
? ? ? ? ? ? compressedPrivKey[i] = pkBytes[i];
? ? ? ? }
? ? ? ? for (int j = 0; j < 4; j++) {
? ? ? ? ? ? compressedPrivKey[j + pkBytes.length] = shasecond[j];
? ? ? ? }
? ? ? ? //compressedPrivKey[compressedPrivKey.length - 1] = PrivKeySuffix;
? ? ? ? key.SetPrivKey(Base58.encode(compressedPrivKey));
? ? ? ? if (debug) {
? ? ? ? ? ? System.out.println("compressed private key: " + Base58.encode(compressedPrivKey));
? ? ? ? }
? ? ? ? return true;
? ? }
}
// 附上Utils中的靜態(tài)方法补君,都很簡(jiǎn)單
public class Utils {
? ? public static String AdjustTo64(String s) {
? ? ? ? switch(s.length()) {
? ? ? ? ? ? case 62: return "00" + s;
? ? ? ? ? ? case 63: return "0" + s;
? ? ? ? ? ? case 64: return s;
? ? ? ? ? ? default:
? ? ? ? ? ? ? ? throw new IllegalArgumentException("not a valid key: " + s);
? ? ? ? }
? ? }
? ? public static String BytesToHex(byte[] src) {
? ? ? ? StringBuilder stringBuilder = new StringBuilder("");
? ? ? ? if (src == null || src.length <= 0) {
? ? ? ? ? ? return null;
? ? ? ? }
? ? ? ? for (int i = 0; i < src.length; i++) {
? ? ? ? ? ? int v = src[i] & 0xFF;
? ? ? ? ? ? String hv = Integer.toHexString(v);
? ? ? ? ? ? if (hv.length() < 2) {
? ? ? ? ? ? ? ? stringBuilder.append(0);
? ? ? ? ? ? }
? ? ? ? ? ? stringBuilder.append(hv);
? ? ? ? }
? ? ? ? return stringBuilder.toString();
? ? }
? ? public static byte[] HexStringToByteArray(String s) {
? ? ? ? int len = s.length();
? ? ? ? byte[] data = new byte[len / 2];
? ? ? ? for (int i = 0; i < len; i += 2) {
? ? ? ? ? ? data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
? ? ? ? ? ? ? ? + Character.digit(s.charAt(i+1), 16));
? ? ? ? }
? ? ? ? return data;
? ? }
}
總結(jié)
??? 如果以上代碼仍有疑問,請(qǐng)仔細(xì)閱讀上面推薦的兩篇文章单雾,請(qǐng)時(shí)刻記鬃:code只是理論的體現(xiàn)。
這里總結(jié)一下BitCoin 生成私鑰硅堆、壓縮格式私鑰屿储、公鑰、壓縮格式公鑰渐逃、WIF錢包地址的過程:
a. secp256K1標(biāo)準(zhǔn)的EC算法生成公私鑰對(duì):(privkey, pubkey)够掠;
b. privkey 生成壓縮格式私鑰:假設(shè)privkey1 = 私鑰前綴0x80+privkey+私鑰后綴x01,
??? result1 = sha256(sha256(privkey1)),對(duì) privkey1做兩次SHA256摘要茄菊, result1前4個(gè)字節(jié)添加到privkey1疯潭, privkey2 = privkey1 + result1[0:3],壓縮私鑰compPrivkey = base58(privkey2)面殖;
c. pubkey 生成完整的公鑰和壓縮格式的公鑰:pubkey對(duì)應(yīng)一個(gè)坐標(biāo)點(diǎn)(X竖哩,Y),由X可以推算出Y脊僚,
??? 0x04 + X + Y就是完整的公鑰相叁;設(shè)Y的最后一個(gè)字節(jié)為b,則:
??? b為偶數(shù),壓縮格式的公鑰compPubkey = 0x02 + x,
??? b為奇數(shù)增淹,壓縮格式的公鑰compPubkey = 0x03 + x椿访。
d. 壓縮的公鑰compPubkey生成WIF格式的地址address:
??? 假設(shè) r1 =? RIPEMD160(SHA256(compPubkey)),壓縮公鑰先做SHA256虑润,在做RIPEMD160摘要;
??? 假設(shè) r2 = PubkeyPrefix(這里為10進(jìn)制65) + r1;
??? 假設(shè) s3 = SHA256(SHA256(r2))成玫,r2兩次SHA256摘要,s3的前4個(gè)字節(jié)為s3[0:3];
??? 假設(shè) a = r2 + s3[0:3]拳喻,WIF address = base58(a)哭当。
??? 以上就是簡(jiǎn)單的總結(jié),比較繁瑣舞蔽,至于為什么這么做荣病,那是bitcoin設(shè)計(jì)師設(shè)計(jì)的码撰,請(qǐng)大家查看官方資料渗柿。
??? 最后留給大家一個(gè)問題:compressed privkey如何得到完整的私鑰匙?脖岛?別看補(bǔ)充內(nèi)容朵栖,自己先想想!
補(bǔ)充(壓縮私鑰匙轉(zhuǎn)為完整私鑰):
神奇的事情是這樣發(fā)生的柴梆,對(duì)compressed privkey做base58 decode陨溅,結(jié)果為38個(gè)字節(jié),結(jié)構(gòu)為:
1字節(jié)前綴(0x80) + 32 字節(jié)私鑰 + 1字節(jié)后綴(0x01) + 4 字節(jié)(這四個(gè)字節(jié)就是上面result1頭4個(gè)字節(jié))绍在。
神奇吧门扇?So amazing! 最后送給小伙伴們壓縮私鑰轉(zhuǎn)換為原始私鑰的code:
public static String convertWIFPrivkeyIntoPrivkey(String wifPrivKey) throws AddressFormatException {
? ? ? ? if (wifPrivKey == null || "".equals(wifPrivKey)) {
? ? ? ? ? ? throw new AddressFormatException("Invalid WIF private key");
? ? ? ? }
? ? ? ? byte[] base58Decode = null;
? ? ? ? try {
? ? ? ? ? ? base58Decode = Base58.decode(wifPrivKey);
? ? ? ? } catch (AddressFormatException e) {
? ? ? ? ? ? throw e;
? ? ? ? }
? ? ? ? String decodeStr = Utils.bytesToHexString(base58Decode);
? ? ? ? if (decodeStr.length() != 76) {
? ? ? ? ? ? throw new AddressFormatException("Invalid WIF private key");
? ? ? ? }
? ? ? ? String version = decodeStr.substring(0, 2);
? ? ? ? String suffix = decodeStr.substring(66, 68);
? ? ? ? if (!"80".equals(version) || !"01".equals(suffix)) {
? ? ? ? ? ? throw new AddressFormatException("Invalid WIF private key");
? ? ? ? }
? ? ? ? String privKeyStr = decodeStr.substring(2, 66);
? ? ? ? return privKeyStr;
? ? }