Skip to content

Commit 6ed9210

Browse files
committed
Implement BIP39
1 parent a26da0e commit 6ed9210

File tree

8 files changed

+861
-0
lines changed

8 files changed

+861
-0
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ ark-std = { version = "0.5", features = ["parallel"] }
3838
ark-test-curves = { version = "0.5", features = ["parallel", "asm"] }
3939
base64 = "0.21.5"
4040
bcs = "0.1.3"
41+
bip39 = { version = "2.1", features = ["std"] }
4142
bitvec = "1.0.0"
4243
blake2 = "0.10.0"
4344
bs58 = "0.5.0"
@@ -52,6 +53,7 @@ elf = "0.7.2"
5253
env_logger = "0.11.1"
5354
getrandom = { version = "0.2.15", features = ["js"] }
5455
hex = { version = "0.4", features = ["serde"] }
56+
hmac = "0.12.1"
5557
iai = "0.1"
5658
itertools = "0.12.1"
5759
js-sys = "=0.3.64"

signer/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ path = "src/lib.rs"
1515
[dependencies]
1616
ark-ec.workspace = true
1717
ark-ff.workspace = true
18+
bip39.workspace = true
1819
bitvec.workspace = true
1920
blake2.workspace = true
2021
bs58.workspace = true
2122
hex.workspace = true
23+
hmac.workspace = true
2224
mina-curves.workspace = true
2325
mina-hasher.workspace = true
2426
num-bigint.workspace = true

signer/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,37 @@ The scalar field is larger than the base field by exactly
2020
Both fields are approximately 2^254, making signatures 64 bytes total (32 bytes
2121
for `rx` + 32 bytes for `s`).
2222

23+
## BIP39 Mnemonic Support
24+
25+
The `mina_signer` crate now supports deriving Mina keypairs from BIP39 mnemonic seed phrases. This enables:
26+
27+
* Generating and using 12-24 word mnemonic phrases
28+
* BIP32 hierarchical deterministic (HD) wallet derivation
29+
* Ledger hardware wallet compatibility (using path `m/44'/12586'/account'/0/0`)
30+
* Multiple account derivation from a single mnemonic
31+
32+
### Quick Example
33+
34+
```rust
35+
use mina_signer::bip39::Bip39;
36+
37+
# fn main() -> Result<(), Box<dyn std::error::Error>> {
38+
// Generate a new 24-word mnemonic
39+
let mnemonic = Bip39::generate_mnemonic(24)?;
40+
41+
// Derive a keypair using BIP32 (Ledger-compatible)
42+
let keypair = Bip39::mnemonic_to_keypair_bip32(&mnemonic, None, 0)?;
43+
let address = keypair.public.into_address();
44+
45+
// Derive multiple accounts
46+
let account1 = Bip39::mnemonic_to_keypair_bip32(&mnemonic, None, 1)?;
47+
# Ok(())
48+
# }
49+
```
50+
51+
For a complete example with multiple derivation methods, see:
52+
`cargo run --example bip39_derivation`
53+
2354
## Signer interface
2455

2556
The `mina_signer` crate currently supports creating both legacy and current signers.
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
//! Comprehensive example of BIP39 mnemonic seed phrase support for Mina
2+
//!
3+
//! This example demonstrates:
4+
//! 1. Generating BIP39 mnemonic phrases
5+
//! 2. Deriving Mina keypairs from mnemonics
6+
//! 3. Using BIP32 hierarchical deterministic derivation (Ledger-compatible)
7+
//! 4. Deriving multiple accounts from a single mnemonic
8+
//!
9+
//! Run with:
10+
//! ```
11+
//! cargo run --example bip39_derivation
12+
//! ```
13+
14+
use mina_signer::bip39::{Bip39, MINA_COIN_TYPE};
15+
16+
fn main() -> Result<(), Box<dyn std::error::Error>> {
17+
println!("=== Mina BIP39 Derivation Example ===\n");
18+
19+
// Example 1: Generate a new mnemonic
20+
println!("1. Generate a new 24-word mnemonic:");
21+
let mnemonic = Bip39::generate_mnemonic(24)?;
22+
println!(" Mnemonic: {}\n", mnemonic);
23+
24+
// Example 2: Use a known test mnemonic
25+
let test_mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
26+
println!("2. Using test mnemonic for reproducible results:");
27+
println!(" Mnemonic: {}\n", test_mnemonic);
28+
29+
// Example 3: Simple derivation (non-BIP32)
30+
println!("3. Simple derivation (non-BIP32 hierarchical):");
31+
let keypair_simple = Bip39::mnemonic_to_keypair(test_mnemonic, None)?;
32+
println!(" Secret key: {}", keypair_simple.secret.to_hex());
33+
println!(" Address: {}\n", keypair_simple.public.into_address());
34+
35+
// Example 4: BIP32 hierarchical derivation (Ledger-compatible)
36+
println!("4. BIP32 hierarchical derivation (Ledger-compatible):");
37+
println!(" Path: m/44'/{}'/<account>'/0/0", MINA_COIN_TYPE);
38+
let keypair_bip32 = Bip39::mnemonic_to_keypair_bip32(test_mnemonic, None, 0)?;
39+
println!(" Account 0:");
40+
println!(" Secret key: {}", keypair_bip32.secret.to_hex());
41+
println!(" Address: {}\n", keypair_bip32.public.into_address());
42+
43+
// Example 5: Multiple accounts from the same mnemonic
44+
println!("5. Derive multiple accounts (BIP32 HD wallet):");
45+
for account in 0..3 {
46+
let keypair = Bip39::mnemonic_to_keypair_bip32(test_mnemonic, None, account)?;
47+
println!(" Account {}: {}", account, keypair.public.into_address());
48+
}
49+
println!();
50+
51+
// Example 6: Using a passphrase for additional security
52+
println!("6. Derivation with optional passphrase:");
53+
let keypair_no_pass = Bip39::mnemonic_to_keypair_bip32(test_mnemonic, None, 0)?;
54+
let keypair_with_pass =
55+
Bip39::mnemonic_to_keypair_bip32(test_mnemonic, Some("my-secret-passphrase"), 0)?;
56+
57+
println!(
58+
" Without passphrase: {}",
59+
keypair_no_pass.public.into_address()
60+
);
61+
println!(
62+
" With passphrase: {}",
63+
keypair_with_pass.public.into_address()
64+
);
65+
println!(" (Different passphrases produce different keys)\n");
66+
67+
// Example 7: Working with seeds directly
68+
println!("7. Advanced: Working with seeds directly:");
69+
let seed = Bip39::mnemonic_to_seed(test_mnemonic, None)?;
70+
println!(" Seed length: {} bytes", seed.len());
71+
println!(" Seed (hex): {}...", hex::encode(&seed[..16]));
72+
73+
// Derive from seed
74+
let keypair_from_seed = Bip39::seed_to_keypair_bip32(&seed, 0)?;
75+
println!(
76+
" Derived address: {}\n",
77+
keypair_from_seed.public.into_address()
78+
);
79+
80+
// Example 8: Non-BIP32 account indexing
81+
println!("8. Simple account indexing (non-BIP32):");
82+
for account_index in 0..3 {
83+
let keypair = Bip39::mnemonic_to_keypair_with_index(test_mnemonic, None, account_index)?;
84+
println!(
85+
" Account {}: {}",
86+
account_index,
87+
keypair.public.into_address()
88+
);
89+
}
90+
91+
println!("\n=== Best Practices ===");
92+
println!("1. Use BIP32 derivation (mnemonic_to_keypair_bip32) for Ledger compatibility");
93+
println!("2. Store mnemonics securely - they provide access to all derived accounts");
94+
println!("3. Use passphrases for an additional layer of security");
95+
println!("4. BIP44 path for Mina: m/44'/12586'/<account>'/0/0");
96+
println!("5. Never share your mnemonic or secret keys");
97+
98+
Ok(())
99+
}

0 commit comments

Comments
 (0)