我试图为使用bitcoinj库实现的HD钱包密钥生成以太坊地址,但我感到困惑:
DeterministicSeed seed = new DeterministicSeed("some seed code here", null, "", 1409478661L);
DeterministicKeyChain chain = DeterministicKeyChain.builder().seed(seed).build();
DeterministicKey addrKey = chain.getKeyByPath(HDUtils.parsePath("M/44H/60H/0H/0/0"), true);
System.out.println("address from pub=" + Keys.getAddress(Sign.publicKeyFromPrivate(addrKey.getPrivKey())));
该代码相应地打印正确的以太坊地址https://iancoleman.io/bip39/.这里一切都很好。
但是,当我试图避免使用私钥并仅使用公钥生成非强化密钥时,我会得到不同的结果,即调用返回另一个结果:
System.out.println("address from pub=" + Keys.getAddress(addrKey.getPublicKeyAsHex()));
看起来问题就在";不同的公钥";,即CCD_ 1和CCD_。我对密码学没有经验,所以这可能是一个愚蠢的问题。。。但如果有任何建议,我将不胜感激。
与比特币一样,以太坊使用secp256k1。以太坊地址如下所示:
- 步骤1:公钥的32个字节的x和y坐标连接到64个字节(如果需要,x和y的坐标都用前导0x00值填充(
- 步骤2:由此生成Keccak-256哈希
- 步骤3:最后20个字节用作以太坊地址
对于这里使用的示例,密钥是用生成的
String mnemonic = "elevator dinosaur switch you armor vote black syrup fork onion nurse illegal trim rocket combine";
DeterministicSeed seed = new DeterministicSeed(mnemonic, null, "", 1409478661L);
DeterministicKeyChain chain = DeterministicKeyChain.builder().seed(seed).build();
DeterministicKey addrKey = chain.getKeyByPath(HDUtils.parsePath("M/44H/60H/0H/0/0"), true);
这对应于以下公钥和以太坊地址:
X: a35bf0fdf5df296cc3600422c3c8af480edb766ff6231521a517eb822dff52cd
Y: 5440f87f5689c2929542e75e739ff30cd1e8cb0ef0beb77380d02cd7904978ca
Address: 23ad59cc6afff2e508772f69d22b19ffebf579e7
也可以通过网站进行验证https://iancoleman.io/bip39/.
步骤1:
在发布的问题中,表达式Sign.publicKeyFromPrivate()
和addrKey.getPublicKeyAsHex()
提供了不同的结果。这两个函数都以不同的类型返回公钥。当Sign.publicKeyFromPrivate()
使用BigInteger
时,addrKey.getPublicKeyAsHex()
提供十六进制字符串。为了进行直接比较,可以将BigInteger
转换为具有toString(16)
的十六进制字符串。当两个表达式的结果都显示为:时
System.out.println(Sign.publicKeyFromPrivate(addrKey.getPrivKey()).toString(16));
System.out.println(addrKey.getPublicKeyAsHex());
得到以下结果:
a35bf0fdf5df296cc3600422c3c8af480edb766ff6231521a517eb822dff52cd5440f87f5689c2929542e75e739ff30cd1e8cb0ef0beb77380d02cd7904978ca
02a35bf0fdf5df296cc3600422c3c8af480edb766ff6231521a517eb822dff52cd
CCD_ 10的输出具有64字节的长度,并且对应于步骤1中定义的级联的x和y坐标。因此,用它生成的地址是一个有效的以太坊地址,正如发布的问题中所描述的那样。
另一方面,Sign.publicKeyFromPrivate(addrKey.getPrivKey())
1的输出对应于前缀为0x02值的x坐标。这是公钥的压缩格式。如果y值为偶数(如本例所示(,则前导字节的值为0x02,或者为0x03。由于压缩格式不包含y坐标,因此这不能用于直接推断以太坊地址,或者如果无论如何都这样做,将导致错误的地址(当然,这是间接的,因为y坐标可以从压缩的公钥派生(。
可以获得公钥的未压缩格式,例如使用addrKey.decompress()
:
System.out.println(addrKey.decompress().getPublicKeyAsHex());
它给出了这样的结果:
04a35bf0fdf5df296cc3600422c3c8af480edb766ff6231521a517eb822dff52cd5440f87f5689c2929542e75e739ff30cd1e8cb0ef0beb77380d02cd7904978ca
未压缩的格式由一个前导标记字节组成,该字节的值为0x04,后跟x和y坐标。因此,如果删除前导标记字节,则只获得步骤1中的数据,这是导出以太坊地址所需的:
System.out.println(addrKey.decompress().getPublicKeyAsHex().substring(2));
结果是:
a35bf0fdf5df296cc3600422c3c8af480edb766ff6231521a517eb822dff52cd5440f87f5689c2929542e75e739ff30cd1e8cb0ef0beb77380d02cd7904978ca
步骤2和3:
步骤2和3由CCD_ 13执行。这允许使用未压缩的公钥获得以太坊地址,如下所示:
System.out.println(Keys.getAddress(addrKey.decompress().getPublicKeyAsHex().substring(2)));
System.out.println(Keys.getAddress(Sign.publicKeyFromPrivate(addrKey.getPrivKey()))); // For comparison
它给出了以太坊地址:
23ad59cc6afff2e508772f69d22b19ffebf579e7
23ad59cc6afff2e508772f69d22b19ffebf579e7
Keys.getAddress()
过载:
Keys.getAddress()
为数据类型BigInteger
、十六进制字符串和byte[]
提供各种重载。如果未压缩密钥被给定为byte[]
,例如与addrKey.getPubKeyPoint().getEncoded(false)
一起,则可以在去除标记字节之后直接使用addrKey.getPublicKeyAsHex()
0。或者,可以将byte[]
转换为去除了标记字节的BigInteger
:
byte[] uncompressed = addrKey.getPubKeyPoint().getEncoded(false);
System.out.println(bytesToHex(Keys.getAddress(Arrays.copyOfRange(uncompressed, 1, uncompressed.length))).toLowerCase()); // bytesToHex() from https://stackoverflow.com/a/9855338
System.out.println(Keys.getAddress(new BigInteger(1, uncompressed, 1, uncompressed.length - 1)));
System.out.println(Keys.getAddress(Sign.publicKeyFromPrivate(addrKey.getPrivKey()))); // For comparison
如预期返回相同的以太坊地址:
23ad59cc6afff2e508772f69d22b19ffebf579e7
23ad59cc6afff2e508772f69d22b19ffebf579e7
23ad59cc6afff2e508772f69d22b19ffebf579e7
这里需要注意的一点是,Keys.getAddress(byte[]
(不填充传递的byte[]
,而BigInteger
或十六进制字符串的重载隐含地填充。这可能是相关的,例如当将BigInteger
(例如由Sign.publicKeyFromPrivate(addrKey.getPrivKey())
提供(转换为byte[]
时,因为结果也可能具有小于64个字节(这将导致不同的Keccak-256散列(。如果在这种情况下使用Keys.getAddress(byte[])
,则必须用前导0x00值填充explicitly
,最大长度为64字节。