Skip to content

Commit d8051de

Browse files
committed
feat: add BIP39 mnemonic utilities
1 parent 0c50c8d commit d8051de

4 files changed

Lines changed: 182 additions & 3 deletions

File tree

package-lock.json

Lines changed: 37 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@amadeus-protocol/sdk",
3-
"version": "1.0.7",
3+
"version": "1.0.8",
44
"description": "Official TypeScript/JavaScript SDK for Amadeus Protocol - Core utilities for serialization, cryptography, transaction building, and API client",
55
"repository": {
66
"type": "git",
@@ -65,6 +65,7 @@
6565
"dependencies": {
6666
"@noble/curves": "^1.9.1",
6767
"@noble/hashes": "^1.8.0",
68+
"@scure/bip39": "^2.0.1",
6869
"bs58": "^6.0.0",
6970
"effect": "^3.19.8"
7071
},

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export * from './crypto'
4343
export * from './conversion'
4444
export * from './encoding'
4545
export * from './encryption'
46+
export * from './mnemonic'
4647
export * from './validation'
4748
export * from './formatters'
4849
export * from './explorer'

src/mnemonic.ts

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/**
2+
* BIP39 Mnemonic Utilities for Amadeus Protocol
3+
*
4+
* This module provides BIP39 mnemonic generation, validation, and seed derivation
5+
* for use with Amadeus Protocol wallets.
6+
*/
7+
8+
import * as bip39 from '@scure/bip39'
9+
import { wordlist } from '@scure/bip39/wordlists/english.js'
10+
import { toBase58 } from './encoding'
11+
12+
/**
13+
* Generate a new 12-word BIP39 mnemonic
14+
*
15+
* @returns 12-word mnemonic phrase
16+
*
17+
* @example
18+
* ```ts
19+
* const mnemonic = generateMnemonic()
20+
* // "abandon ability able about above absent absorb abstract absurd abuse access accident"
21+
* ```
22+
*/
23+
export function generateMnemonic(): string {
24+
return bip39.generateMnemonic(wordlist, 128) // 128 bits = 12 words
25+
}
26+
27+
/**
28+
* Validate a BIP39 mnemonic phrase
29+
*
30+
* @param mnemonic - Mnemonic phrase to validate
31+
* @returns true if the mnemonic is valid
32+
*
33+
* @example
34+
* ```ts
35+
* const isValid = validateMnemonic('abandon ability able ...')
36+
* ```
37+
*/
38+
export function validateMnemonic(mnemonic: string): boolean {
39+
return bip39.validateMnemonic(mnemonic.trim().toLowerCase(), wordlist)
40+
}
41+
42+
/**
43+
* Derive an Amadeus seed (base58-encoded 64 bytes) from a BIP39 mnemonic.
44+
* Uses BIP39 seed derivation (PBKDF2 with SHA-512) which produces a 64-byte seed,
45+
* matching the Amadeus SDK's expected seed length.
46+
*
47+
* @param mnemonic - BIP39 mnemonic phrase
48+
* @returns Base58-encoded 64-byte seed
49+
*
50+
* @example
51+
* ```ts
52+
* const seed = mnemonicToSeedBase58('abandon ability able ...')
53+
* ```
54+
*/
55+
export function mnemonicToSeedBase58(mnemonic: string): string {
56+
const seed = bip39.mnemonicToSeedSync(mnemonic.trim().toLowerCase())
57+
return toBase58(seed)
58+
}
59+
60+
/**
61+
* Detect whether input is a mnemonic phrase or a raw private key.
62+
*
63+
* @param input - User input string
64+
* @returns 'mnemonic' if the input looks like a word-based mnemonic, or 'seed' if it looks like a base58-encoded key
65+
*
66+
* @example
67+
* ```ts
68+
* const type = detectInputType('abandon ability able ...')
69+
* // 'mnemonic'
70+
* ```
71+
*/
72+
export function detectInputType(input: string): 'mnemonic' | 'seed' {
73+
const trimmed = input.trim()
74+
const words = trimmed.split(/\s+/)
75+
// BIP39 mnemonics are 12 or 24 words
76+
if (words.length === 12 || words.length === 24) {
77+
// Check if all words look like English words (no numbers, no special chars)
78+
const allWords = words.every((w) => /^[a-zA-Z]+$/.test(w))
79+
if (allWords) return 'mnemonic'
80+
}
81+
return 'seed'
82+
}
83+
84+
/**
85+
* Vault data format that supports both mnemonic and raw seed storage.
86+
* When decrypted, old vaults return a plain base58 string,
87+
* new vaults return a JSON string with this shape.
88+
*/
89+
export interface VaultSecretData {
90+
seed: string
91+
mnemonic?: string
92+
}
93+
94+
/**
95+
* Encode vault secret data as a string for encryption.
96+
* Always stores as JSON to include the optional mnemonic.
97+
*
98+
* @param seed - Base58-encoded seed
99+
* @param mnemonic - Optional BIP39 mnemonic phrase
100+
* @returns JSON string for vault encryption
101+
*
102+
* @example
103+
* ```ts
104+
* const vaultData = encodeVaultSecret(seed, mnemonic)
105+
* const encrypted = await encryptWithPassword(vaultData, password)
106+
* ```
107+
*/
108+
export function encodeVaultSecret(seed: string, mnemonic?: string): string {
109+
const data: VaultSecretData = { seed }
110+
if (mnemonic) {
111+
data.mnemonic = mnemonic
112+
}
113+
return JSON.stringify(data)
114+
}
115+
116+
/**
117+
* Decode vault secret data from a decrypted string.
118+
* Handles both old format (plain base58 seed) and new format (JSON with seed + mnemonic).
119+
*
120+
* @param decrypted - Decrypted vault string
121+
* @returns Parsed vault secret data
122+
*
123+
* @example
124+
* ```ts
125+
* const decrypted = await decryptWithPassword(encrypted, password)
126+
* const { seed, mnemonic } = decodeVaultSecret(decrypted)
127+
* ```
128+
*/
129+
export function decodeVaultSecret(decrypted: string): VaultSecretData {
130+
try {
131+
const parsed = JSON.parse(decrypted)
132+
if (parsed && typeof parsed.seed === 'string') {
133+
return {
134+
seed: parsed.seed,
135+
mnemonic: parsed.mnemonic || undefined
136+
}
137+
}
138+
} catch {
139+
// Not JSON — old format, plain base58 seed
140+
}
141+
return { seed: decrypted }
142+
}

0 commit comments

Comments
 (0)