NIP-06: 助記詞密鑰派生
從 BIP-39 助記詞派生 Nostr 密鑰對的標準方法
概述
NIP-06 定義了從助記詞(mnemonic seed phrase)派生 Nostr 密鑰對的標準方法。 這個規範基於比特幣的 BIP-39 和 BIP-32 標準,讓用戶可以使用熟悉的 12 或 24 個單詞 來備份和恢復他們的 Nostr 身份。
使用的標準
| 標準 | 用途 |
|---|---|
| BIP-39 | 生成助記詞並從中派生二進制種子 |
| BIP-32 | 階層式確定性(HD)密鑰派生 |
| SLIP-44 | Nostr 的幣種類型編號:1237 |
派生路徑
Nostr 使用以下 BIP-32 派生路徑:
m/44'/1237'/<account>'/0/0 | 層級 | 值 | 說明 |
|---|---|---|
44' | 固定 | BIP-44 用途(硬化派生) |
1237' | 固定 | Nostr 的 SLIP-44 幣種編號 |
account' | 0, 1, 2... | 帳戶索引(硬化派生) |
0 | 固定 | 外部鏈 |
0 | 固定 | 地址索引 |
多帳戶支援:基本客戶端可以使用 account = 0。 需要多個身份的用戶可以遞增帳戶索引,從同一組助記詞派生實際上無限的密鑰對。
密鑰生成流程
- 生成助記詞:使用 BIP-39 生成 12 或 24 個單詞
- 派生種子:從助記詞(可選加上密碼)派生 512 位元種子
- 生成主密鑰:使用 BIP-32 從種子生成主密鑰
- 派生子密鑰:按照路徑 m/44'/1237'/0'/0/0 派生
- 提取私鑰:取得 32 位元組的私鑰
- 計算公鑰:從私鑰計算 secp256k1 公鑰(x 座標)
測試向量
測試向量 1
助記詞: leader monkey parrot ring guide accident before fence cannon height naive bean
派生路徑: m/44'/1237'/0'/0/0
私鑰 (hex): 7f7ff03d123792d6ac594bfa67bf6d0c0ab55b6b1fdb6249303fe861f1ccba9a
nsec: nsec10allq0gjx7fddtzef0ax00mdps9t2kmtrldkyjfs8l5xruwvh2dq0lhhkp
公鑰 (hex): 17162c921dc4d2518f9a101db33695df1afb56ab82f5ff3e5da6571e474dc088
npub: npub1zutzeysacnf9rru6zqwmxd54mud0k44tst6l7vaex3s3rr4ppjfs6mq2yp 測試向量 2
助記詞: what bleak badge arrange retreat wolf trade produce cricket blur garlic valid proud rude strong choose busy staff weather area salt hollow arm fade
派生路徑: m/44'/1237'/0'/0/0
私鑰 (hex): c15d739894c81a2fcfd3a2df85a0d2c0dbc47a280d092799f144d73d7ae78add
nsec: nsec1c9wh8xy5eqdzln7n5t0ctgxjcrdug73gp5yj0x03gntn67h83twssdfhel
公鑰 (hex): d41b22899549e1f3d335a31002cfd382174006e166d3e658e3a5eecdb6463573
npub: npub16sdj9zv4f8sl85e45vgq3n7vced8zpr5fjkayqh2jtqwxgwqdqys8t4uvx TypeScript 實作
安裝依賴
npm install @scure/bip39 @scure/bip32 @noble/secp256k1 nostr-tools 從助記詞派生密鑰
import { generateMnemonic, mnemonicToSeedSync, validateMnemonic } from '@scure/bip39';
import { wordlist } from '@scure/bip39/wordlists/english';
import { HDKey } from '@scure/bip32';
import { bytesToHex } from '@noble/hashes/utils';
import { getPublicKey, nip19 } from 'nostr-tools';
// Nostr 的 BIP-32 派生路徑
const NOSTR_DERIVATION_PATH = "m/44'/1237'/0'/0/0";
/**
* 生成新的助記詞
*/
function generateNostrMnemonic(strength: 128 | 256 = 128): string {
// 128 bits = 12 words, 256 bits = 24 words
return generateMnemonic(wordlist, strength);
}
/**
* 驗證助記詞是否有效
*/
function isValidMnemonic(mnemonic: string): boolean {
return validateMnemonic(mnemonic, wordlist);
}
/**
* 從助記詞派生 Nostr 密鑰對
*/
function deriveNostrKeys(
mnemonic: string,
accountIndex: number = 0,
passphrase: string = ''
): {
privateKey: Uint8Array;
privateKeyHex: string;
nsec: string;
publicKey: string;
npub: string;
} {
// 驗證助記詞
if (!isValidMnemonic(mnemonic)) {
throw new Error('無效的助記詞');
}
// 從助記詞派生種子
const seed = mnemonicToSeedSync(mnemonic, passphrase);
// 建立 HD 密鑰
const hdKey = HDKey.fromMasterSeed(seed);
// 派生路徑(支援多帳戶)
const path = `m/44'/1237'/${accountIndex}'/0/0`;
const derived = hdKey.derive(path);
if (!derived.privateKey) {
throw new Error('無法派生私鑰');
}
const privateKey = derived.privateKey;
const privateKeyHex = bytesToHex(privateKey);
const publicKey = getPublicKey(privateKey);
return {
privateKey,
privateKeyHex,
nsec: nip19.nsecEncode(privateKey),
publicKey,
npub: nip19.npubEncode(publicKey),
};
}
// 使用範例
const mnemonic = generateNostrMnemonic();
console.log('助記詞:', mnemonic);
const keys = deriveNostrKeys(mnemonic);
console.log('私鑰 (hex):', keys.privateKeyHex);
console.log('nsec:', keys.nsec);
console.log('公鑰 (hex):', keys.publicKey);
console.log('npub:', keys.npub); 多帳戶派生
/**
* 從同一組助記詞派生多個帳戶
*/
function deriveMultipleAccounts(
mnemonic: string,
count: number,
passphrase: string = ''
) {
const accounts = [];
for (let i = 0; i < count; i++) {
const keys = deriveNostrKeys(mnemonic, i, passphrase);
accounts.push({
accountIndex: i,
path: `m/44'/1237'/${i}'/0/0`,
...keys,
});
}
return accounts;
}
// 使用範例:派生 5 個帳戶
const accounts = deriveMultipleAccounts(mnemonic, 5);
accounts.forEach((account) => {
console.log(`帳戶 ${account.accountIndex}:`);
console.log(` 路徑: ${account.path}`);
console.log(` npub: ${account.npub}`);
}); 帶密碼的派生
// 使用額外的密碼短語增加安全性
// 相同的助記詞配合不同密碼會產生完全不同的密鑰
const keysWithPassphrase = deriveNostrKeys(
mnemonic,
0,
'my-secret-passphrase'
);
console.log('帶密碼的 npub:', keysWithPassphrase.npub);
// 注意:忘記密碼將無法恢復密鑰! 完整的密鑰管理類別
import { generateMnemonic, mnemonicToSeedSync, validateMnemonic } from '@scure/bip39';
import { wordlist } from '@scure/bip39/wordlists/english';
import { HDKey } from '@scure/bip32';
import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
import { getPublicKey, nip19, finalizeEvent } from 'nostr-tools';
class NostrHDWallet {
private seed: Uint8Array;
private hdKey: HDKey;
constructor(mnemonic: string, passphrase: string = '') {
if (!validateMnemonic(mnemonic, wordlist)) {
throw new Error('無效的助記詞');
}
this.seed = mnemonicToSeedSync(mnemonic, passphrase);
this.hdKey = HDKey.fromMasterSeed(this.seed);
}
static generate(strength: 128 | 256 = 128): {
mnemonic: string;
wallet: NostrHDWallet;
} {
const mnemonic = generateMnemonic(wordlist, strength);
return {
mnemonic,
wallet: new NostrHDWallet(mnemonic),
};
}
static fromMnemonic(mnemonic: string, passphrase?: string): NostrHDWallet {
return new NostrHDWallet(mnemonic, passphrase);
}
getAccount(index: number = 0) {
const path = `m/44'/1237'/${index}'/0/0`;
const derived = this.hdKey.derive(path);
if (!derived.privateKey) {
throw new Error('無法派生私鑰');
}
const privateKey = derived.privateKey;
const publicKey = getPublicKey(privateKey);
return {
index,
path,
privateKey,
privateKeyHex: bytesToHex(privateKey),
nsec: nip19.nsecEncode(privateKey),
publicKey,
npub: nip19.npubEncode(publicKey),
// 簽名事件的便利方法
signEvent: (event: any) => {
return finalizeEvent(event, privateKey);
},
};
}
getAccounts(count: number) {
return Array.from({ length: count }, (_, i) => this.getAccount(i));
}
}
// 使用範例
const { mnemonic: newMnemonic, wallet } = NostrHDWallet.generate(256); // 24 words
console.log('請安全保存此助記詞:', newMnemonic);
const mainAccount = wallet.getAccount(0);
console.log('主帳戶 npub:', mainAccount.npub);
// 簽名事件
const signedEvent = mainAccount.signEvent({
kind: 1,
content: 'Hello from HD wallet!',
tags: [],
created_at: Math.floor(Date.now() / 1000),
}); 安全考量
重要安全提醒:
- 助記詞等同於私鑰,必須安全保存
- 永遠不要在線上儲存或分享助記詞
- 建議使用 24 個單詞以獲得更高安全性
- 考慮使用額外密碼短語作為第二層保護
- 忘記密碼短語將永久失去存取權限
備份建議
- 將助記詞寫在紙上,存放在安全的地方
- 考慮使用金屬板刻印以防火防水
- 不要截圖或存在雲端
- 可以分散存放(如 Shamir 分割)
相容性
由於 NIP-06 使用標準的 BIP-39/BIP-32,助記詞可以:
- 在任何支援 NIP-06 的 Nostr 客戶端中恢復
- 與比特幣錢包共用(使用不同的派生路徑)
- 在硬體錢包中安全儲存
使用場景
單一身份
- 使用 account = 0 作為主要 Nostr 身份
- 簡單易用,適合大多數用戶
多重身份
- 個人帳戶(account = 0)
- 工作帳戶(account = 1)
- 匿名帳戶(account = 2)
- 全部從同一組助記詞管理
組織管理
- 為團隊成員派生不同帳戶
- 集中備份,分散使用
相關 NIPs
參考資源
已複製連結