イーサリアムの公開鍵と秘密鍵のペアを生成し、一意のイーサリアムアドレスを生成するECDSA(楕円曲線暗号化)アルゴリズムを紹介する記事がインターネット上に多数あります。それらのほとんどは、非圧縮公開鍵のアドレスを生成するときに、最初にハッシュ操作が実行され、次に最後の40ビットがアドレスと見なされると述べています。しかし、彼はそれについて何も知りませんでした。それはどのようにハッシュされ、公開鍵と秘密鍵の形式は何ですか、私と同じくらい多くの人が不明瞭だと思います!
著者は何かを研究しているので、イーサリアムの公開鍵のアドレスを生成するプロセスの詳細を理解する必要があります。著者は最初に百度検索を行い、知乎に関する記事「イーサリアムの秘密鍵、公開鍵、住所、アカウント」を参照しました。しかし、長い間手探りした後、記事に記載されている結果を得ることができませんでした。最後に、作成者が記事に秘密鍵をインポートした後、MetaMask
最終的に取得されたアドレスが記事のサンプルアドレスではなかったことは驚くべきことでした。何かがおかしいと思われるので、作者はアドレスへの公開鍵の明確な生成プロセスを明確にすることにしました。これはもはや曖昧ではありません。
知乎のこの記事にはフローチャートがあります。
このフローチャートは非常に正確で、簡単な手順は3つしかありませんが、結果は正しくありません。問題が発生する可能性が最も高いのは、公開鍵からkeccak-256を経由することです。圧縮された公開鍵へのアルゴリズム。特定のKeccak-256関数は何ですか?言語が異なれば実装も異なります。ここでも、この問題を検証するために最も一般的に使用されているNode.js
フレームワークを使用しています。ethers
幸い、ethers
フレームワークには公開鍵を直接使用してアドレスを計算する関数があります。関数の定義と説明は次のとおりです。
ethers.utils.computeAddress( publicOrPrivateKey ) ⇒ string< Address >source
Returns the address for publicOrPrivateKey. A public key may be compressed or uncompressed,
and a private key will be converted automatically to a public key for the derivation.
これはパッケージ化されたコンピューティングライブラリです。詳細なプロセスを知りたい場合は、ソースコードの実装を参照することで入手できます。ここでは特定の参照プロセスについては説明しません。検証を成功させるためのスクリプトは、以下に直接投稿されています(と呼ばれると想定されていますtest.sol
)。
//根据公钥生成地址实例详细流程
const eccrypto = require("eccrypto");
const sha3 = require("js-sha3");
const {
ethers,utils} = require("ethers")
const private_key = "18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725";
const my_wallet = new ethers.Wallet(private_key)
const HexCharacters = "0123456789abcdef";
const public_key = my_wallet.publicKey
printPublicKey(public_key)
//第一步: 移除公钥前两位04,如果包含0x就是移除四位了,再重新加上0x构造
let new_key = "0x" + public_key.substring(4)
//第二步:对上面的结果转化成bytesLike(不能漏)
let new_bytes = utils.arrayify(new_key)
//第三步,keccak_256,得到一个长度为64的哈希值
new_key = sha3.keccak_256(new_bytes)
//第四步,取上面结果的最后40位,就得到了全小写的地址。
let result = "0x" + new_key.substring(24)
//最后,将地址转换成检验后的地址
result = utils.getAddress(result)
console.log("")
console.log(result)
console.log(result === my_wallet.address)
function printPublicKey(public_key) {
console.log(public_key.substring(2,4))
let half = (public_key.length - 4)/2
console.log(public_key.substring(4, 4+half))
console.log(public_key.substring(4+half))
}
//unused
function convertBytesToHexString(value) {
let result = "0x";
for (let i = 0; i < value.length; i++) {
let v = value[i];
result += HexCharacters[(v & 0xf0) >> 4] + HexCharacters[v & 0x0f];
}
return result;
}
上記のコードからわかるように、公開鍵を使用してアドレスを生成するには、いくつかの手順があります。
- このZhihuの記事で説明されているように、公開鍵のプレフィックス04を削除します。
楕円曲線デジタル署名アルゴリズムECDSA-secp256k1は、秘密鍵(32バイト)を公開鍵(65バイト)にマップするために使用されます(接頭辞04 +X公開鍵+Y公開鍵):
ただし、Keccak-256を計算するときにプレフィックスを削除することについては言及されていません。
- 上で削除
04
した16進文字列を再度バイト配列に変換します - 前の手順で取得したバイト配列に対してkeccak_256操作を実行して、長さ64のハッシュ値(圧縮された公開鍵、32バイト)を取得します。
- 上記の結果の最後の40ビットを取得して、すべて小文字のアドレスを取得します。
- オプション。すべての小文字のアドレスを検証済みのアドレスに変換します。
node test.js
次の出力を直接取得します。
04
50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352
2cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6
0x3E9003153d9A39D3f57B126b0c38513D5e289c3E
true
この形式の出力は、元の知乎の記事と比較するためのものです。
元の記事で最終的に取得されたアドレスが間違っている理由については04
、バイト配列に変換せずに、削除された公開鍵文字列に対して直接Keccak-256操作を実行するため、著者はここでそれを再現します。