-
Notifications
You must be signed in to change notification settings - Fork 24
feat: Add a deterministic (BIP-39) keygen #590
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: unstable
Are you sure you want to change the base?
Conversation
|
anchor/keygen/src/lib.rs
Outdated
fn generate_deterministic_rsa_key(mnemonic: &Mnemonic, index: u32) -> Result<Rsa<Private>, KeygenError> { | ||
// Convert mnemonic to seed using BIP39 derivation | ||
let seed = mnemonic.to_seed(""); | ||
let seed_bytes = &seed; | ||
|
||
// Create info string for HKDF using the derivation index | ||
let info = format!("anchor-rsa-key-{}", index); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The index does not seem to be actually used to create a derivation path for the seed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, I had generated an index 0 and an index 1 key and they were different. same index, same key luckily haha.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I guess the "info" does that
let mut key_material = [0u8; 512]; | ||
hkdf.expand(info.as_bytes(), &mut key_material) | ||
.map_err(|e| format!("HKDF expansion failed: {}", e))?; | ||
|
||
// Use the derived key material to deterministically generate RSA parameters | ||
generate_rsa_from_seed(&key_material) | ||
} | ||
|
||
fn generate_rsa_from_seed(seed: &[u8]) -> Result<Rsa<Private>, Box<dyn std::error::Error>> { | ||
// Create a deterministic RNG from the seed | ||
let mut rng = StdRng::from_seed({ | ||
let mut seed_array = [0u8; 32]; | ||
seed_array.copy_from_slice(&seed[..32]); | ||
seed_array | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're expanding the seed to 512 bytes just to cut it down to 32 again.
I feel like it makes more sense to use the expanded key material directly instead of passing it through a RNG again
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds deterministic BIP-39 based key generation to the anchor keygen
command. The feature allows node operators to generate RSA keys from mnemonic seed phrases, providing a more familiar and manageable way to secure key material compared to raw private keys.
- Implements BIP-39 mnemonic-based deterministic RSA key generation using HKDF for key derivation
- Adds CLI options for mnemonic input (direct string, file, or auto-generation) with derivation index support
- Includes comprehensive test coverage and documentation for the new deterministic key generation feature
Reviewed Changes
Copilot reviewed 6 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
File | Description |
---|---|
test_keygen_standalone/src/main.rs | Standalone test implementation demonstrating deterministic RSA key generation from BIP-39 mnemonics |
test_keygen_standalone/Cargo.toml | Dependencies for the standalone test including bip39, hkdf, openssl, rand, and sha2 |
anchor/keygen/src/lib.rs | Main implementation adding deterministic key generation with new CLI options, key derivation logic, and comprehensive tests |
anchor/keygen/README.md | Updated documentation explaining deterministic key generation usage and security considerations |
anchor/keygen/Cargo.toml | Added required dependencies for BIP-39 and cryptographic operations |
.github/wordlist.txt | Added BIP-39 and cryptography-related terms to the spelling allowlist |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
for i in 0..128 { | ||
p_bytes[i] ^= p_rng.next_u32() as u8; | ||
q_bytes[i] ^= q_rng.next_u32() as u8; | ||
} |
Copilot
AI
Sep 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using only the lowest byte of next_u32()
reduces entropy from 32 bits to 8 bits per iteration. Consider using rng.fill_bytes()
or extracting all 4 bytes from each u32
to maintain full entropy.
Copilot uses AI. Check for mistakes.
// XOR the first 128 bytes of extra entropy into p_bytes | ||
for i in 0..128 { | ||
p_bytes[i] ^= extra_entropy[i]; | ||
} | ||
// XOR the remaining 64 bytes of extra entropy into q_bytes (cycling through) | ||
for i in 0..64 { | ||
q_bytes[i] ^= extra_entropy[128 + i]; | ||
q_bytes[i + 64] ^= extra_entropy[128 + i]; // Use each byte twice for full coverage |
Copilot
AI
Sep 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reusing the same entropy byte for two different positions (q_bytes[i]
and q_bytes[i + 64]
) reduces the effective entropy. Consider using all 192 bytes of extra_entropy sequentially or with a different mixing strategy.
// XOR the first 128 bytes of extra entropy into p_bytes | |
for i in 0..128 { | |
p_bytes[i] ^= extra_entropy[i]; | |
} | |
// XOR the remaining 64 bytes of extra entropy into q_bytes (cycling through) | |
for i in 0..64 { | |
q_bytes[i] ^= extra_entropy[128 + i]; | |
q_bytes[i + 64] ^= extra_entropy[128 + i]; // Use each byte twice for full coverage | |
// XOR the first 96 bytes of extra entropy into p_bytes | |
for i in 0..96 { | |
p_bytes[i] ^= extra_entropy[i]; | |
} | |
// XOR the next 96 bytes of extra entropy into q_bytes | |
for i in 0..96 { | |
q_bytes[i] ^= extra_entropy[96 + i]; |
Copilot uses AI. Check for mistakes.
Should the tests be inside the keygen dir? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have had a small look through this.
Although I think this has great utility, I dont think its worth the security risk and we shouldn't promote it to the public. Here are my reasons:
- It builds the RSA components itself, against the general rule of "Dont roll your own crypto"
- RSA relies fairly heavily on generating random primes. It looks like this uses the seed for a PRNG but then sequentially looks for primes from this starting point. It not obvious to me that this method is as secure and randomly searching for primes, I suspect not.
- This PR would require significant review before we could think of adding this to production.
One way of doing this that I'd personally feel a bit better about, is just straight using a rust-based RSA library, removing all component generation and just creating the RNG trait which is seeded by the mnemonic seed. i,e we use this function: https://docs.rs/rsa/latest/src/rsa/key.rs.html#217-220
The mnemonic seed bytes is only 64 bytes, although it uses pbkdf and can be modified to generate larger key sizes, which may be necessary.
EDIT: There exists this thing: https://docs.rs/deterministic-keygen/latest/deterministic_keygen/fn.derive_rsa_key_from_phrase.html
Which looks like it creates the PRNG from the seed.
#[clap( | ||
long, | ||
help = "BIP39 mnemonic phrase for deterministic key generation (12 or 24 words)", | ||
requires = "deterministic" | ||
)] | ||
pub mnemonic: Option<String>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dont think we should allow mnemonics on the CLI. They will end up in the processes list and shell history.
Proposed Changes
Adds a deterministic BIP-39 based option to
anchor keygen
.As a node operator, I highly prefer securing seed phrases over raw key material.
Additional Info
This is 100% Claude's work. I have no idea what I'm doing. Seems to work fine but may be ultra dangerous. etc.