And*_*rew 8 java cryptography bitcoinj ethereum web3-java
我尝试为使用 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())));
Run Code Online (Sandbox Code Playgroud)
此代码根据https://iancoleman.io/bip39/打印正确的以太坊地址。这里一切都很好。
但是,当我尝试避免使用私钥并使用公钥生成非强化密钥时,我只会得到不同的结果,即调用返回另一个结果:
System.out.println("address from pub=" + Keys.getAddress(addrKey.getPublicKeyAsHex()));
Run Code Online (Sandbox Code Playgroud)
看起来问题出在“不同的公钥”上,即Sign.publicKeyFromPrivate(addrKey.getPrivKey())和 的结果addrKey.getPublicKeyAsHex()是不同的。我对密码学没有经验,因此这可能是一个愚蠢的问题......但我会很感激这里的任何建议。
与比特币一样,以太坊使用secp256k1。以太坊地址的推导如下:
对于此处使用的示例,密钥是通过以下方式生成的:
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);
Run Code Online (Sandbox Code Playgroud)
这对应于以下公钥和以太坊地址:
X: a35bf0fdf5df296cc3600422c3c8af480edb766ff6231521a517eb822dff52cd
Y: 5440f87f5689c2929542e75e739ff30cd1e8cb0ef0beb77380d02cd7904978ca
Address: 23ad59cc6afff2e508772f69d22b19ffebf579e7
Run Code Online (Sandbox Code Playgroud)
也可以通过网站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());
Run Code Online (Sandbox Code Playgroud)
得到以下结果:
a35bf0fdf5df296cc3600422c3c8af480edb766ff6231521a517eb822dff52cd5440f87f5689c2929542e75e739ff30cd1e8cb0ef0beb77380d02cd7904978ca
02a35bf0fdf5df296cc3600422c3c8af480edb766ff6231521a517eb822dff52cd
Run Code Online (Sandbox Code Playgroud)
的输出Sign.publicKeyFromPrivate()长度为 64 字节,对应于步骤 1 中定义的串联 x 和 y 坐标。因此,由此生成的地址是有效的以太坊地址,正如发布的问题中所述。
addrKey.getPublicKeyAsHex()另一方面,的输出对应于前缀为 0x02 值的 x 坐标。这是公钥的压缩格式。如果 y 值为偶数(如本例所示),则前导字节的值为 0x02,或者值为 0x03。由于压缩格式不包含 y 坐标,因此不能用来直接推断以太坊地址,或者无论如何都这样做,将导致错误的地址(当然,间接地,这是可能的,因为 y 坐标可以从压缩的公钥中导出)。
可以获取公钥的未压缩格式,例如addrKey.decompress():
System.out.println(addrKey.decompress().getPublicKeyAsHex());
Run Code Online (Sandbox Code Playgroud)
结果如下:
04a35bf0fdf5df296cc3600422c3c8af480edb766ff6231521a517eb822dff52cd5440f87f5689c2929542e75e739ff30cd1e8cb0ef0beb77380d02cd7904978ca
Run Code Online (Sandbox Code Playgroud)
未压缩格式包含一个值为 0x04 的前导标记字节,后跟 x 和 y 坐标。因此,如果删除前导标记字节,则仅获得步骤 1 中的数据,这是推导以太坊地址所需的:
System.out.println(addrKey.decompress().getPublicKeyAsHex().substring(2));
Run Code Online (Sandbox Code Playgroud)
结果是:
a35bf0fdf5df296cc3600422c3c8af480edb766ff6231521a517eb822dff52cd5440f87f5689c2929542e75e739ff30cd1e8cb0ef0beb77380d02cd7904978ca
Run Code Online (Sandbox Code Playgroud)
步骤 2 和 3:
步骤 2 和 3 由 执行Keys.getAddress()。这允许使用未压缩的公钥获取以太坊地址,如下所示:
System.out.println(Keys.getAddress(addrKey.decompress().getPublicKeyAsHex().substring(2)));
System.out.println(Keys.getAddress(Sign.publicKeyFromPrivate(addrKey.getPrivKey()))); // For comparison
Run Code Online (Sandbox Code Playgroud)
其中给出了以太坊地址:
23ad59cc6afff2e508772f69d22b19ffebf579e7
23ad59cc6afff2e508772f69d22b19ffebf579e7
Run Code Online (Sandbox Code Playgroud)
重载Keys.getAddress():
Keys.getAddress()为数据类型BigInteger、十六进制字符串和提供各种重载byte[]。如果未压缩的密钥给出为byte[],例如 with addrKey.getPubKeyPoint().getEncoded(false),则byte[]可以在删除标记字节后直接使用 。或者,byte[]可以将 转换为BigInteger删除标记字节的 a:
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
Run Code Online (Sandbox Code Playgroud)
正如预期的那样返回相同的以太坊地址:
23ad59cc6afff2e508772f69d22b19ffebf579e7
23ad59cc6afff2e508772f69d22b19ffebf579e7
23ad59cc6afff2e508772f69d22b19ffebf579e7
Run Code Online (Sandbox Code Playgroud)
这里需要注意的一件事是Keys.getAddress(byte[])不会填充传递的,而或 十六进制字符串byte[]的重载则隐式填充。例如,当将 a (例如由 提供)转换为 a时,这可能是相关的,因为结果也可能少于 64 个字节(这将导致不同的Keccak-256哈希值)。如果在这种情况下使用,则必须用前导 0x00 值填充,长度最多为 64 字节。BigIntegerBigIntegerSign.publicKeyFromPrivate(addrKey.getPrivKey())byte[]Keys.getAddress(byte[])explicitly
| 归档时间: |
|
| 查看次数: |
2980 次 |
| 最近记录: |