永久保存版！ウォレットを使わずにニーモニックからSymbolの秘密鍵を導出する方法

ある時、急にウォレットやSDKが使えなくなったとしても
ニーモニックを覚えておけば秘密鍵を導出することができます。
たとえ使うことがなかったとしても、避難訓練をしておきましょう。

:::note alert
これは非常時のスクリプトです。
暗号資産の入っていないニーモニックで検証するかテストネットで検証してください。
:::

```js
(async () => {
  const mnemonic = prompt("24語のニーモニックを入力（半角スペース区切り）:");
  const passphrase = ""; // 必要あれば設定

  // 正規化関数
  const encoder = new TextEncoder();
  const normalize = (s) => s.normalize("NFKD");

  // BIP39 → シード（512bit = 64byte）
  const salt = "mnemonic" + passphrase;
  const keyMaterial = await crypto.subtle.importKey(
    "raw",
    encoder.encode(normalize(mnemonic)),
    { name: "PBKDF2" },
    false,
    ["deriveBits"]
  );
  const seedBuffer = await crypto.subtle.deriveBits(
    {
      name: "PBKDF2",
      salt: encoder.encode(normalize(salt)),
      iterations: 2048,
      hash: "SHA-512"
    },
    keyMaterial,
    512
  );

  const seed = new Uint8Array(seedBuffer);

  // HMAC-SHA512 utility
  async function hmacSha512(keyBytes, dataBytes) {
    const key = await crypto.subtle.importKey(
      "raw",
      keyBytes,
      { name: "HMAC", hash: "SHA-512" },
      false,
      ["sign"]
    );
    const sig = await crypto.subtle.sign("HMAC", key, dataBytes);
    return new Uint8Array(sig);
  }

  // BIP32 path derivation
  async function deriveEd25519PrivateKey(seed, path) {
    // m (master key)
    let key = await hmacSha512(
      encoder.encode("ed25519 seed"),
      seed
    );
    let priv = key.slice(0, 32);
    let chainCode = key.slice(32);

    const segments = path
      .split('/')
      .slice(1)
      .map(seg => {
        const hardened = seg.endsWith("'");
        const index = parseInt(hardened ? seg.slice(0, -1) : seg, 10);
        return (hardened ? (index + 0x80000000) : index) >>> 0;
      });

    for (let i = 0; i < segments.length; i++) {
      const index = segments[i];

      const data = new Uint8Array(1 + 32 + 4); // 0x00 + priv + index
      data[0] = 0x00;
      data.set(priv, 1);
      data[33] = (index >>> 24) & 0xff;
      data[34] = (index >>> 16) & 0xff;
      data[35] = (index >>> 8) & 0xff;
      data[36] = index & 0xff;

      const I = await hmacSha512(chainCode, data);
      priv = I.slice(0, 32);
      chainCode = I.slice(32);
    }

    return priv;
  }

  const derived = await deriveEd25519PrivateKey(seed, "m/44'/4343'/0'/0'/0'");
  const hex = Array.from(derived).map(b => b.toString(16).padStart(2, '0')).join('');

  console.log("✅ Symbol用秘密鍵（hex）:");
  console.log(hex);
})();

```

実行する前に、ソースコード内部に `import` や `require` が含まれていないことを確認してください。これらが含まれていると、入力したニーモニックを外部に送信されてしまう可能性があります（それはここで提供されたソースコードではありません）。

また、事前に拡張機能の無効化をしたりクリップボードを記録するソフトウェアも使用しないようにしましょう。


テストネットで検証する場合は
```js
 const derived = await deriveEd25519PrivateKey(seed, "m/44'/4343'/0'/0'/0'");
```

の場所を

```js
 const derived = await deriveEd25519PrivateKey(seed, "m/44'/1'/0'/0'/0'");
```
にしてください。


# コード量圧縮版
```js
(async () => {
  const mnemonic = prompt("24語のニーモニックを入力:");
  const enc = new TextEncoder(), norm = s => s.normalize("NFKD");

  const key = await crypto.subtle.importKey("raw", enc.encode(norm(mnemonic)), { name: "PBKDF2" }, false, ["deriveBits"]);
  const seed = new Uint8Array(await crypto.subtle.deriveBits({ name: "PBKDF2", salt: enc.encode("mnemonic"), iterations: 2048, hash: "SHA-512" }, key, 512));

  const hmac = async (k, d) => {
    const ck = await crypto.subtle.importKey("raw", k, { name: "HMAC", hash: "SHA-512" }, false, ["sign"]);
    return new Uint8Array(await crypto.subtle.sign("HMAC", ck, d));
  };

  const path = [44, 4343, 0, 0, 0].map(i => i + 0x80000000);

  let I = await hmac(enc.encode("ed25519 seed"), seed);
  let priv = I.slice(0, 32), chain = I.slice(32);

  for (const i of path) {
    const d = new Uint8Array(37);
    d[0] = 0; d.set(priv, 1);
    d.set([(i >>> 24) & 255, (i >>> 16) & 255, (i >>> 8) & 255, i & 255], 33);
    I = await hmac(chain, d);
    priv = I.slice(0, 32); chain = I.slice(32);
  }

  console.log("✅ Symbol用秘密鍵:\n" + [...priv].map(b => b.toString(16).padStart(2, '0')).join(''));
})();

```