仅使用公钥(bitcoinj/web3j)在高清钱包中生成以太坊地址



我试图为使用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字节。

相关内容

  • 没有找到相关文章

最新更新