首先獲得 ECDSA private key, 再獲得 ECDSA public key,,而後再計算出錢包地址。ECDSA是Elliptic Curve Digital Signature Algorithm的縮寫, 即橢圓曲線數字簽名算法。java
一、ECDSA private key:執行openssl命令, bitcoin要用到secp256k1git
openssl ecparam -name secp256k1 -genkey > priv.pem # DER格式 openssl ec -in priv.pem -outform DER | tail -c +8 | head -c 32 | xxd -p -c 32 # 輸出 read EC key writing EC key ccea9c5a20e2b78c2e0fbdd8ae2d2b67e6b1894ccb7a55fc1de08bd53994ea64
獲得祕鑰文件priv.pem, 輸出DER格式, 長度是 64
ccea9c5a20e2b78c2e0fbdd8ae2d2b67e6b1894ccb7a55fc1de08bd53994ea64
算法
二、ECDSA public key: priv.pem 生成 pub_keybash
openssl ec -in priv.pem -pubout -outform DER | tail -c 65 | xxd -p -c 65 # 輸出 read EC key writing EC key 04d061e9c5891f579fd548cfd22ff29f5c642714cc7e7a9215f0071ef5a5723f691757b28e31be71f09f24673eed52348e58d53bcfd26f4d96ec6bf1489eab429d
一樣輸出DER格式, 長度是130
pub_key = 04d061e9c5891f579fd548cfd22ff29f5c642714cc7e7a9215f0071ef5a5723f691757b28e31be71f09f24673eed52348e58d53bcfd26f4d96ec6bf1489eab429d
編碼
三、第2步結果進行hash160運算:hash160運算就是先進行SHA256, 再進行RMD160code
bytes = [pub_key].pack("H*") # 轉爲16進制 hash160_val = Digest::RMD160.hexdigest(Digest::SHA256.digest(bytes) )
hash160_val = 2b6f3b9e337cedbb7c40839523fb1100709c12f7
orm
四、第3步結果加上前綴符blog
前綴符通常是00
, 會生成普通的主網地址
bitcoin address 前綴符有好幾種, 具體看https://en.bitcoin.it/wiki/List_of_address_prefixesip
'00'+ '2b6f3b9e337cedbb7c40839523fb1100709c12f7'
result_04 = 002b6f3b9e337cedbb7c40839523fb1100709c12f7
ssl
bitcoin地址的前綴列表列出了幾種錢包地址的類型
十進制 | 16進制 | 做用 | 首字母 | 例子 |
---|---|---|---|---|
0 | 00 | P2PKH address | 1 | 17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhem |
5 | 05 | P2SH address | 3 | 3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX |
111 | 6F | Testnet pub key | m or n | mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn |
五、第4步結果, 執行2次SHA256, 取前8位做爲校驗和
hex_str = [result_04].pack("H*") checksum = Digest::SHA256.hexdigest(Digest::SHA256.digest(hex_str) )[0...8]
checksum = 86b2e90c
六、第4步結果 跟 第5步結果合併
'002b6f3b9e337cedbb7c40839523fb1100709c12f7' + '86b2e90c' # result_04 + checksum
result_06 = 002b6f3b9e337cedbb7c40839523fb1100709c12f786b2e90c
七、第6步結果進行base58編碼
Base58是一種獨特的編碼方式, 是Base64的變形, 主要用於Bitcoin的錢包地址.
相比Base64, Base58去掉了數字0
, 大寫字母O
, 大寫字母I
, 小寫字母l
, +
和/
, 避免引發視覺混淆。
result_06 = "002b6f3b9e337cedbb7c40839523fb1100709c12f786b2e90c" leading_zero_bytes = (step_06.match(/^([0]+)/) ? $1 : '').size / 2 # leading_zero_bytes的做用是字母填充, 待研究下 address = ("1" * leading_zero_bytes) + encode_base58(step_06.to_i(16) )
獲得 14xfJr1DArtYR156XBs28FoYk6sQqirT2s
, 這就是了一個標準的bitcoin地址。
在bitcoin系統中,私鑰能得公鑰, 公鑰能獲得錢包地址,
私鑰=>公鑰=>錢包地址, 而反向是不可能的。
Java Base58算法
public class Base58 { public static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); private static final char ENCODED_ZERO; private static final int[] INDEXES; public Base58() { } public static String encode(byte[] input) { if (input.length == 0) { return ""; } else { int zeros; for(zeros = 0; zeros < input.length && input[zeros] == 0; ++zeros) { ; } input = Arrays.copyOf(input, input.length); char[] encoded = new char[input.length * 2]; int outputStart = encoded.length; int inputStart = zeros; while(inputStart < input.length) { --outputStart; encoded[outputStart] = ALPHABET[divmod(input, inputStart, 256, 58)]; if (input[inputStart] == 0) { ++inputStart; } } while(outputStart < encoded.length && encoded[outputStart] == ENCODED_ZERO) { ++outputStart; } while(true) { --zeros; if (zeros < 0) { return new String(encoded, outputStart, encoded.length - outputStart); } --outputStart; encoded[outputStart] = ENCODED_ZERO; } } } public static byte[] decode(String input) throws AddressFormatException { if (input.length() == 0) { return new byte[0]; } else { byte[] input58 = new byte[input.length()]; int zeros; int outputStart; for(zeros = 0; zeros < input.length(); ++zeros) { char c = input.charAt(zeros); outputStart = c < 128 ? INDEXES[c] : -1; if (outputStart < 0) { throw new AddressFormatException("Illegal character " + c + " at position " + zeros); } input58[zeros] = (byte)outputStart; } for(zeros = 0; zeros < input58.length && input58[zeros] == 0; ++zeros) { ; } byte[] decoded = new byte[input.length()]; outputStart = decoded.length; int inputStart = zeros; while(inputStart < input58.length) { --outputStart; decoded[outputStart] = divmod(input58, inputStart, 58, 256); if (input58[inputStart] == 0) { ++inputStart; } } while(outputStart < decoded.length && decoded[outputStart] == 0) { ++outputStart; } return Arrays.copyOfRange(decoded, outputStart - zeros, decoded.length); } } public static BigInteger decodeToBigInteger(String input) throws AddressFormatException { return new BigInteger(1, decode(input)); } public static byte[] decodeChecked(String input) throws AddressFormatException { byte[] decoded = decode(input); if (decoded.length < 4) { throw new AddressFormatException("Input too short"); } else { byte[] data = Arrays.copyOfRange(decoded, 0, decoded.length - 4); byte[] checksum = Arrays.copyOfRange(decoded, decoded.length - 4, decoded.length); byte[] actualChecksum = Arrays.copyOfRange(Sha256Hash.hashTwice(data), 0, 4); if (!Arrays.equals(checksum, actualChecksum)) { throw new AddressFormatException("Checksum does not validate"); } else { return data; } } } private static byte divmod(byte[] number, int firstDigit, int base, int divisor) { int remainder = 0; for(int i = firstDigit; i < number.length; ++i) { int digit = number[i] & 255; int temp = remainder * base + digit; number[i] = (byte)(temp / divisor); remainder = temp % divisor; } return (byte)remainder; } static { ENCODED_ZERO = ALPHABET[0]; INDEXES = new int[128]; Arrays.fill(INDEXES, -1); for(int i = 0; i < ALPHABET.length; INDEXES[ALPHABET[i]] = i++) { ; } } }