簡體   English   中英

僅使用公鑰在 HD 錢包中生成以太坊地址 (bitcoinj/web3j)

[英]Generate Ethereum addresses in HD Wallet using public key only (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()));

看起來問題出在“不同的公鑰”中,即Sign.publicKeyFromPrivate(addrKey.getPrivKey())addrKey.getPublicKeyAsHex()的結果不同。 我沒有密碼學經驗,因此這可能是一個愚蠢的問題......但我會很感激這里的任何建議。

與比特幣一樣,以太坊使用 secp256k1 以太坊地址派生如下:

  • 第 1 步:公鑰的 32 字節 x 和 y 坐標連接到 64 字節(如果需要,x 和 y 坐標都用前導 0x00 值填充)。
  • 第 2 步:由此生成Keccak-256 hash。
  • 第 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()使用BigIntegeraddrKey.getPublicKeyAsHex()提供十六進制字符串。 對於直接比較,可以使用toString(16)BigInteger轉換為十六進制字符串。 當兩個表達式的結果顯示為:

System.out.println(Sign.publicKeyFromPrivate(addrKey.getPrivKey()).toString(16));
System.out.println(addrKey.getPublicKeyAsHex());

得到以下結果:

a35bf0fdf5df296cc3600422c3c8af480edb766ff6231521a517eb822dff52cd5440f87f5689c2929542e75e739ff30cd1e8cb0ef0beb77380d02cd7904978ca
02a35bf0fdf5df296cc3600422c3c8af480edb766ff6231521a517eb822dff52cd

Sign.publicKeyFromPrivate()的 output 的長度為 64 字節,對應於步驟 1 中定義的串聯 x 和 y 坐標。因此,由此生成的地址是有效的以太坊地址,如發布的問題中所述。

另一方面, addrKey.getPublicKeyAsHex()的 output 對應於以 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 由Keys.getAddress()執行。 這允許使用未壓縮的公鑰獲取以太坊地址,如下所示:

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 、 hex string 和byte[]提供了各種重載。 如果未壓縮的密鑰以byte[]形式給出,例如使用addrKey.getPubKeyPoint().getEncoded(false) ,則可以在刪除標記字節后直接使用byte[] 或者,可以將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 個字節。

topaco你的地址好像有ens空投沒有認領

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM