ある時、急にウォレットやSDKが使えなくなったとしても
ニーモニックを覚えておけば秘密鍵を導出することができます。
たとえ使うことがなかったとしても、避難訓練をしておきましょう。
これは非常時のスクリプトです。
暗号資産の入っていないニーモニックで検証するかテストネットで検証してください。
(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
が含まれていないことを確認してください。これらが含まれていると、入力したニーモニックを外部に送信されてしまう可能性があります(それはここで提供されたソースコードではありません)。
また、事前に拡張機能の無効化をしたりクリップボードを記録するソフトウェアも使用しないようにしましょう。
テストネットで検証する場合は
const derived = await deriveEd25519PrivateKey(seed, "m/44'/4343'/0'/0'/0'");
の場所を
const derived = await deriveEd25519PrivateKey(seed, "m/44'/1'/0'/0'/0'");
にしてください。
コード量圧縮版
(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(''));
})();