随机生成私钥
私钥是 256 位的二进制数,以 64 位 hex 显示,例如
bef05ca99c4bb9d17f9f164a5bffd48ee2f99f866a3621dd9a4be62412c28148
public static byte[] privateKey() {
byte[] privateKey = new byte[32];
random.nextBytes(privateKey);
return privateKey;
}
从私钥到公钥
{K = k * G} secp256k1 标准的椭圆曲线,以私钥 k 为起点,将曲线上已定义的生成点 G 相乘获得另一点,也就是公钥 K = (x, y)。
K = bef05ca99c4bb9d17f9f164a5bffd48ee2f99f866a3621dd9a4be62412c28148 * G
x = c2a0eef93156029532c9b6d33dfd4d09abc3fa0454bc1580230682c9d197f974
y = 0ccafcd456bbac903010082d251b83f8d10013d49e75b1c681c2189c92955f35
公钥有两种格式,压缩与未压缩。坐标 x 对应两个 y 值,分别为奇数和偶数,因此可以将 y 压缩为一个字节:02 表示偶数,03 表示奇数。
未压缩的公钥:04 + x + y
已压缩的公钥:02 + x 或 03 + x
(bitcoinj 里面地址是由压缩格式公钥计算出来的。)
<dependency>
<groupId>com.madgag.spongycastle</groupId>
<artifactId>core</artifactId>
<version>1.58.0.0</version>
</dependency>
private static final ECDomainParameters CURVE;
private static final X9ECParameters CURVE_PARAMS = CustomNamedCurves.getByName("secp256k1");
static {
FixedPointUtil.precompute(CURVE_PARAMS.getG(), 12);
CURVE = new ECDomainParameters(CURVE_PARAMS.getCurve(), CURVE_PARAMS.getG(), CURVE_PARAMS.getN(),
CURVE_PARAMS.getH());
}
private static byte[] fromPrivate(byte[] privKeyBytes) {
BigInteger privKey = new BigInteger(1, privKeyBytes);
if (privKey.bitLength() > CURVE.getN().bitLength()) {
privKey = privKey.mod(CURVE.getN());
}
ECPoint multiply = new FixedPointCombMultiplier().multiply(CURVE.getG(), privKey);
byte[] uncompressed = multiply.getEncoded(false);
byte[] compressed = multiply.getEncoded(true);
return compressed;
}
从公钥 K 到比特币地址
- ripemd160(sha256(K)) 结果长 20 字节
- 对 1 添加版本前缀,例如比特币地址前缀为 0x00
- 对 2 做两次 sha256 并取前 4 字节
- 对 2 + 3 做 base58 编码
base58check: 2,3,4
base58 即 [0-9a-zA-Z] - [0OIl] 数字 0、大写字母 O I、小写字母 l
byte[] hashedPubKey = Utils.ripeMD160(Utils.sha256(pubBytes));
base58Check(hashedPubKey);
private static String base58Check(byte[] input) throws Exception {
byte[] data = new byte[1 + input.length];
data[0] = 0;
System.arraycopy(input, 0, data, 1, input.length);
byte[] checksum = Utils.sha256(data);
checksum = Utils.sha256(checksum);
byte[] address = new byte[data.length + 4];
System.arraycopy(data, 0, address, 0, data.length);
System.arraycopy(checksum, 0, address, data.length, 4);
return Base58.encode(address);
}