0xhardman

0xhardman

twitter
medium
jike
github

助記詞をETHアドレスに変換することについて、あなたが知っておくべきすべてのこと

イントロダクション#

あなたがどのようにイーサリアムの旅を始めたかを思い出してみましょう。Metamask プラグインをインストールし、ニーモニックフレーズを作成し、それを紙に書き写して、確認ボタンをクリックしました! おめでとうございます、あなたは自分のイーサリアムアカウントを持っています。あなたは自分のウォレットアドレスをコピーして他の人に送信し、彼らがそれを使ってイーサリアムを受け取ることができます。

しかし、あなたは次のような疑問を持っていませんか?

  1. 辞書から自分のニーモニックフレーズを作成できますか?
  2. なぜニーモニックフレーズが重要なのですか?なぜ私のニーモニックフレーズを他の人に渡してはいけないのですか?
  3. 私は自分の ETH 秘密鍵を使って BTC アドレスを生成できますか?
  4. この 12 のニーモニックフレーズはどのようにしてあなたの秘密鍵とアドレスに変わるのですか?

あなたは正しい場所に来ました。この記事では、Rust 言語を使用してニーモニックフレーズをアドレスに変換する方法をステップバイステップで説明します。ニーモニックフレーズからアドレスへのプロセスを理解することで、ブロックチェーンシステムと暗号原理に対する理解が深まり、雇用機会が向上し、関連する開発ニーズを満たし、効率が向上します。したがって、ブロックチェーン技術の開発を深く理解し、関与したいと考えている人々にとって、これらの知識は非常に重要で不可欠です。

なぜ Rust を選ぶのか#

Rust は Mozilla によって開発された汎用コンパイルプログラミング言語で、その信頼性と効率性からますます多くの開発者に好まれています。したがって、私は将来的にイーサリアムエコシステムの中で Rust を使用して再構築やアップグレードを行うプロジェクトが増えると信じています。イーサリアムエコシステムには、Rust に基づく多くの有名なプロジェクトも登場しています。例えば:

Foundry - イーサリアムアプリケーション開発のための迅速で移植可能なモジュール式ツールキット。

Reth - イーサリアムプロトコルのためのモジュール式で貢献者に優しく、非常に高速な実装。

イーサリアムにおける Rust について早く理解しましょう!

開発者ではないですか?#

大丈夫です!この記事では、全体のプロセスを理解するための詳細な説明とノーコードの例も提供しています。

ステップ 1 - ニーモニックフレーズの生成#

ニーモニックフレーズは、記憶しやすく書きやすい単語 / 文字の列です。あなたはニーモニックフレーズがランダムに生成されると思っていますか?実際には完全にランダムではありません。詳しく見てみましょう。

ニーモニックフレーズの各単語は、0 から 2047 の間の数字で表すことができ、合計 2048 の数字があります。詳細については、BIP39 単語リストを参照してください。もう一つ興味深いことは、英語のニーモニックフレーズだけでなく、簡体字中国語、繁体字中国語、日本語、韓国語、スペイン語のニーモニックフレーズも使用できることです。例えば:

英語: indoor dish desk flag debris potato excuse depart ticket judge file exit
韓国語: 수집 몸속 명의 분야 만족 인격 법원 멀리 터미널 시멘트 부작용 변명
簡体字中国語: 诗 失 圆 块 亲 幼 杂 却 厉 齐 顶 互
繁体字中国語: 詩 失 圓 塊 親 幼 雜 卻 厲 齊 頂 互

問題 1 を振り返る:辞書から自分のニーモニックフレーズを作成できますか?

できません。ニーモニックフレーズとして使用できる単語 / 文字の数は限られており、最後の単語は「チェックサム」であり、つまり最後の単語は前のすべての単語の計算結果であり、前の 11 の単語と対応関係があります。実際には、私たちの身分証明書番号の最後の桁も「チェックサム」であり、ニーモニックフレーズもそれに似ています。

次に、ニーモニックフレーズをバイナリ数に変換できます。例えば「indoor」は 920 で、バイナリ数は 1110011000 です。各バイナリ数は 11 ビットです。

ニーモニックフレーズ(Mnemonic)
indoor dish desk flag debris potato excuse depart ticket judge file exit

バイナリでのニーモニックフレーズ(MnemonicBinary)
01110011000 00111111001 00111011111 01011000001 00111000011 10101000110 01001111000 00111010110 11100001101 01111000101 01010110001 01001111111

バイナリでのエントロピー(Entropy)
01110011000 00111111001 00111011111 01011000001 00111000011 10101000110 01001111000 00111010110 11100001101 01111000101 01010110001 0100111

16進数エントロピー(EntropyInHex)
7307e4efac13875193c1d6e1af1558a7

バイナリでのチェックサム(CheckSum)
1111

エントロピーのバイナリ長(lengthOfEntropy)
128

ニーモニックフレーズのバイナリ長(lengthOfMnemonicBinary)
132

ニーモニックフレーズ = エントロピー + チェックサム

チェックサム = SHA256(entropy)[0:len(entropy)/32]=SHA256(entropy)[0:4]=

言い換えれば、私たちはニーモニックフレーズの材料としてランダムに 01 の組み合わせを生成することができ、つまりエントロピーです。エントロピーの長さは 32 の倍数です。12 のニーモニックフレーズの場合、エントロピーの長さは 128 です。

これは、ニーモニックフレーズの長さが 3 の倍数であることを意味しますが、最大で 24 の単語を超えることはありません。単語数が多いほど安全です。

ニーモニックフレーズが有効であることを確認するために、ニーモニックフレーズのチェックサムを計算する必要があります。チェックサムはエントロピーの SHA256 ハッシュ値の最初の数桁です。次に、132 ビットのバイナリ長を得ることができ、12 のニーモニックフレーズ(11 ビットごとに 1 単語)に変換できます。

fn step1_generate_mnemonic() {
    // generate mnemonic
    let entropy = &[
        0x33, 0xE4, 0x6B, 0xB1, 0x3A, 0x74, 0x6E, 0xA4, 0x1C, 0xDD, 0xE4, 0x5C, 0x90, 0x84, 0x6A,
        0x79,
    ];
    let mnemonic = Mnemonic::from_entropy(entropy, Language::English).unwrap();

    // let mnemonic = Mnemonic::new(MnemonicType::Words12, Language::English);
    // let mnemonic: Mnemonic = Mnemonic::from_phrase(
    //     "indoor dish desk flag debris potato excuse depart ticket judge file exit", // It will be unvalid, if you change any word in it.
    //     Language::English,
    // )
    // .unwrap();
    let phrase = mnemonic.phrase();
    println!("Generated Mnemonic: {}", phrase);
}

ステップ 2 - ニーモニックフレーズからシードへ#

ニーモニックフレーズをシードに変換するには、PBKDF2 関数を使用し、ニーモニックフレーズをパスワードとして、文字列「ニーモニックフレーズ」とパスフレーズをソルトとして使用します。

PBKDF2 の主な機能は、パスワードを暗号化キーに変換することです。従来の単一ハッシュ関数とは異なり、PBKDF2 はパスワードとソルトを組み合わせ、ハッシュ関数を何度も繰り返し適用することでキーを生成します。

通常、反復回数は 2048 回に設定され、HMAC-SHA512 をハッシュ関数として使用して、ブルートフォース攻撃の難易度を上げます。SHA512 は512 ビットのハッシュ値を生成する関数と理解できます。したがって、シードの長さは 512 ビット(64 バイト)です。

fn step2_mnemonic_to_seed(mnemonic: &Mnemonic) -> String {
    let seed = Seed::new(mnemonic, "");
    seed.as_bytes();
    hex::encode(seed.as_bytes())
}
// return 3bd0bda567d4ea90f01e92d1921aacc5046128fd0e9bee96d070e1d606cb79225ee3e488bf6c898a857b5f980070d4d4ce9adf07d73458a271846ef3a8415320

ステップ 3 - シードからマスターキーへ#

image

BIP32

今、私たちは BIP32 - 階層的決定論的ウォレット(HDW)について深く理解する必要があります。HDW の主な目的は、ウォレットをより良く管理することです。1 つのシードで、それによって生成されたすべてのウォレットを復元できます。HDW を使用すると、取引を行うたびに新しいアドレスを使用できるため、匿名性をより良く確保できます。BIP32 は最初はビットコインのために設計されましたが、その原理は他の暗号通貨にも適用でき、同じシードで複数の通貨の複数のウォレットアドレスを生成できます。すべての階層はマスターキーから派生します。さあ、詳しく見てみましょう。

fn step3_seed_to_master_key(seed_hex: &String) -> (String, String) {
    let seed_bytes = hex::decode(seed_hex).unwrap();

    let key = hmac::Key::new(hmac::HMAC_SHA512, b"Bitcoin seed");
    let tag = hmac::sign(&key, &seed_bytes);

    let (il, ir) = tag.as_ref().split_at(32);
    (hex::encode(il), hex::encode(ir))
}
// return 5e01502044f205b98ba493971561284565e41f34f03494bb521654b0c35cb3a9 bccd1f17319e02baa4b2688f5656267d2eeaf8b49a49607e4b37efe815629c82

マスターキーを使用して、異なる派生パスから子鍵を派生することができます。

問題 2 を振り返る:なぜニーモニックフレーズが重要なのですか?なぜ私のニーモニックフレーズを他の人に渡してはいけないのですか?

ニーモニックフレーズは、あなたのすべてのウォレットを取り戻すための鍵です。秘密鍵を失うと、あなたの資産にアクセスできなくなります。ニーモニックフレーズが漏洩すると、そのニーモニックフレーズから派生したアカウント内のすべての資産が悪意のある者に盗まれる可能性があります。

ステップ 4 - マスターキーから秘密鍵へ#

マスターキーとチェーンコードがあれば、最初のアカウントの秘密鍵をエクスポートできます。

まず、派生パスを確認しましょう:

m / purpose' / coin_type' / account' / change / address_index
  1. m はマスターキーです;
  2. purpose は 44' で、BIP44 を示します;coin type は 0' で、ビットコインを示します;60' はイーサリアムを示します。
  3. accountはアカウントのインデックスで、0 から始まります。0 を日常使用のメインアカウント、1 を寄付やその他の用途のアカウントとして定義できます。
  4. change フィールドは内部チェーンと外部チェーンを区別するために使用されます。
  5. address はチェーン上のアドレスのインデックスです。これを使用して複数のアドレスを生成できます。

秘密鍵を取得するには、マスターキーから派生させた秘密鍵と各レベルのパスパラメータを使用する必要があります。

派生関数の役割は次のとおりです:

// CKDpriv((key_parent, chain_code_parent), i) -> (child_key_i, child_chain_code_i)
// `i` はレベル番号です。
// CKDpriv: 子鍵の派生(プライベート)

pub fn derive_with_path(
    master_private_key: SecretKey,
    master_chain_code: [u8; 32],
    path_numbers: &[u32; 5],
) -> SecretKey {
    let mut depth = 0;

    let mut child_number: Option<u32> = None;
    let mut private_key = master_private_key;
    let mut chain_code = master_chain_code;

    for &i in path_numbers {
        depth += 1;
        println!("depth: {}", depth);

        child_number = Some(i);
        println!("child_number: {:?}", child_number);

        (private_key, chain_code) = derive(child_number.unwrap(), private_key, chain_code);
    }
    private_key
}

pub fn derive(
    child_number: u32,
    private_key: SecretKey,
    chain_code: [u8; 32],
) -> (SecretKey, [u8; 32]) {
    println!("child_number: {:?}", child_number);

    let child_fingerprint = fingerprint_from_private_key(private_key.clone());
    println!("child_fingerprint: {:?}", hex::encode(child_fingerprint));

    let derived = derive_ext_private_key(private_key.clone(), &chain_code, child_number);
    let private_key = derived.0;
    let chain_code = derived.1;

    println!("private_key: {:?}", hex::encode(private_key.as_ref()));
    println!("chain_code: {:?}\n", hex::encode(chain_code));

    (private_key, chain_code)
}

// 秘密鍵のフィンガープリントを計算します。
pub fn fingerprint_from_private_key(k: SecretKey) -> [u8; 4] {
    let pk = curve_point_from_int(k);

    // 公開鍵を圧縮形式でシリアライズします
    let pk_compressed = serialize_curve_point(pk);

    // SHA256ハッシュを実行します
    let sha256_result = digest::digest(&digest::SHA256, &pk_compressed);

    // RIPEMD160ハッシュを実行します

    let ripemd_result = ripemd160::Hash::hash(sha256_result.as_ref());

    // 最初の4バイトをフィンガープリントとして返します
    ripemd_result[0..4].try_into().unwrap()
}

// 派生されたExtPrivate鍵
pub fn derive_ext_private_key(
    private_key: SecretKey,
    chain_code: &[u8],
    child_number: u32,
) -> (SecretKey, [u8; 32]) {
    let key = hmac::Key::new(hmac::HMAC_SHA512, chain_code);

    let mut data = if child_number >= (1 << 31) {
        [&[0u8], &private_key[..]].concat()
    } else {
        let p = curve_point_from_int(private_key);
        serialize_curve_point(p)
        // private_key.as_ref().to_vec()
    };
    data.extend_from_slice(&child_number.to_be_bytes());

    let hmac_result = hmac::sign(&key, &data);

    let (l, r) = hmac_result.as_ref().split_at(32);

    let l = (*l).to_owned();
    let r = (*r).to_owned();

    let mut l_32 = [0u8; 32];
    l_32.clone_from_slice(&l);

    let private_byte = private_key.as_ref();

    let l_secret = SecretKey::from_slice(&l).unwrap();
    let child_private_key = l_secret
        .add_tweak(&Scalar::from_be_bytes(*private_byte).unwrap())
        .unwrap();
    let child_chain_code = r;

    (child_private_key, child_chain_code.try_into().unwrap())
}

derive_with_path 関数は、派生パス(path_numbers)、マスターキー(master_master_key)、マスターチェーンコード(master_chain_code)を反復的に呼び出して派生関数(derive)を計算し、対応するアカウントの秘密鍵を計算します。例えば、パス「m/44'/ 60'/ 0'/ 0/ 0」を使用して最初のアカウントの秘密鍵を取得し、パス「m/44'/ 60'/ 0'/ 0/ 1」を使用して 2 番目のアカウントの秘密鍵を取得できます。

問題 3 を振り返る:私は自分の ETH 秘密鍵を使って BTC アドレスを生成できますか?

できません。ビットコインの派生パスは m/44'/0'/0'/0 であり、ETH の派生パスは m/44'/60'/0'/0/0 です。私たちは子秘密鍵を使用して親秘密鍵を計算することはできません。これは、マスターキーを持っている場合、すべてのチェーン上のすべてのウォレットを計算できることを意味します。

もう一つ説明する必要があるのは、パス内のアポストロフィは BIP32 強化派生を使用していることを示します。例えば、44' は強化派生を示し、44 は示しません。そして、44' の実際の意味は 2³¹+44 です。

BIP32 における「強化」は、派生鍵のセキュリティを向上させ、単一の公開鍵と子鍵を使用して他の子鍵を派生させることができないようにし、潜在的な攻撃者があなたの鍵階層にアクセスするのを効果的に防ぎます。

fn step3_master_kay_to_private_key(
    master_secret_key_hex: String,
    master_chain_code_hex: String,
    derived_path: [u32; 5],
) -> String {
    let master_secret_key_vec = hex::decode(master_secret_key_hex).unwrap();
    let master_secret_key: &[u8] = master_secret_key_vec.as_ref();
    let master_code_vec: Vec<u8> = hex::decode(master_chain_code_hex).unwrap();
    let master_code: &[u8] = master_code_vec.as_ref();

    let private_key = derive_with_path(
        SecretKey::from_slice(master_secret_key.clone()).unwrap(),
        master_code.try_into().unwrap(),
        &derived_path,
    );
    hex::encode(private_key.as_ref())
}

ステップ 5:秘密鍵から公開鍵へ#

難しい部分は終わりました。今、私たちは秘密鍵から公開鍵を取得できます!

公開鍵は、秘密鍵を生成点と掛け算することで生成される楕円曲線上の点です。

fn step5_private_key_to_public_key(private_key_hex: String) -> String {
    let private_key_vec = hex::decode(private_key_hex).unwrap();
    let private_key = SecretKey::from_slice(private_key_vec.as_ref()).unwrap();
    let public_key = curve_point_from_int(private_key);
    hex::encode(serialize_curve_point(public_key))
}

ステップ 6 - 公開鍵アドレス#

アドレスは、公開鍵の Keccak-256 ハッシュ値の最後の 20 バイトです。

fn step6_public_key_to_address(pub_key_hex: String) -> String {
    let public_key_vec = hex::decode(pub_key_hex).unwrap();
    let public_key = PublicKey::from_slice(public_key_vec.as_ref()).unwrap();
    let serialized_pub_key = public_key.serialize_uncompressed();
    let public_key_bytes = &serialized_pub_key[1..];
    let mut hasher = Keccak::v256();
    hasher.update(public_key_bytes);
    let mut output = [0u8; 32];
    hasher.finalize(&mut output);

    let address = &output[12..];
    hex::encode(address)
}

問題 4 を振り返る:この 12 のニーモニックフレーズはどのようにしてあなたの秘密鍵とアドレスに変わるのですか?

要するに、ニーモニックフレーズはエントロピーから生成され、その後 PBKDF2 を通じてシードに変換されます。シードは HMAC-SHA512 を使用してマスターキーとチェーンコードを生成するために使用されます。特定のパスを通じて、マスターキーから秘密鍵を抽出します。最後に、秘密鍵が公開鍵を生成し、公開鍵の Keccak-256 ハッシュ値の最後の 20 バイトがイーサリアムアドレスを構成します。

参考資料#

Ethereum 201: MnemonicsBIP 39 ニーモニックワードとシード生成のガイドツアー、Python の例付きwolovim.medium.com

Ethereum 201: HD Wallets* ニーモニックワードから公開アドレスへ — BIP 32 と BIP 44 のウォークスルー、Python の例付き.*wolovim.medium.com

ニーモニック(12 ワード)を秘密鍵とアドレスウォレットビットコインおよびイーサリアムに変換する方法* ニーモニック 12 ワードを秘密鍵 16 進数(SHA256)およびアドレスビットコインおよびイーサリアムウォレットに変換します。*mdrza.medium.com

推奨ツール#

BIP39 - ニーモニックコードビットコインニーモニックコンバーターiancoleman.io

Github#

GitHub - 0xhardman/rust-mnemonic-to-address*0xhardman/rust-mnemonic-to-address の開発に貢献するために GitHub でアカウントを作成します。*github.com

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。