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 のニーモニックフレーズ(1 単語あたり 11 ビット)に変換できます。

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 ニーモニックワードとシード生成のガイドツアーwolovim.medium.com

Ethereum 201: HD Walletsニーモニックワードから公開アドレスへ — BIP 32 と BIP 44 のウォークスルー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

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