duke_crypto_core/
lib.rs

1use anyhow::{Context, Result};
2use bip39::Mnemonic;
3use ed25519_dalek::{SigningKey, VerifyingKey, Signature, Signer, Verifier};
4use rand::RngCore;
5use rand_core::OsRng;
6use serde::{Deserialize, Serialize};
7use slip10::{BIP32Path, Curve};
8use std::str::FromStr;
9
10/// Key pair properties with mnemonic and derivation path information
11#[derive(Debug, Clone, Serialize, Deserialize, Eq, Hash, PartialEq)]
12pub struct KeyPairProperties {
13    pub seed_phrase_hd_path: String,
14    pub master_seed_phrase: String,
15    pub implicit_account_id: String,
16    #[serde(rename = "public_key")]
17    pub public_key_str: String,
18    #[serde(rename = "private_key")]
19    pub secret_keypair_str: String,
20}
21
22/// Account private key information
23#[derive(Debug, Clone, Serialize, Deserialize, Eq, Hash, PartialEq)]
24pub struct AccountPrivate {
25    pub account_id: String,
26    pub seed_phrase_hd_path: String,
27    pub master_seed_phrase: String,
28    #[serde(rename = "public_key")]
29    pub public_key_str: String,
30    #[serde(rename = "private_key")]
31    pub secret_keypair_str: String,
32}
33
34/// Public key information extracted from private key
35#[derive(Debug, Clone, Serialize, Deserialize, Eq, Hash, PartialEq)]
36pub struct PublicKeyInfo {
37    #[serde(rename = "private_key")]
38    pub secret_keypair_str: String,
39    #[serde(rename = "public_key")]
40    pub public_key_str: String,
41    pub implicit_account_id: String,
42}
43
44/// Generate a new BIP39 mnemonic phrase (12 words)
45pub fn generate_mnemonic() -> Result<String> {
46    let mut entropy = [0u8; 16];
47    OsRng.fill_bytes(&mut entropy);
48    let mnemonic = Mnemonic::from_entropy(&entropy)
49        .context("Failed to generate mnemonic")?;
50    Ok(mnemonic.to_string())
51}
52
53/// Generate a mnemonic from an existing phrase
54pub fn mnemonic_from_phrase(phrase: &str) -> Result<Mnemonic> {
55    Mnemonic::parse(phrase)
56        .context("Invalid mnemonic phrase")
57}
58
59/// Generate an ED25519 key pair from a mnemonic using SLIP-10 derivation
60pub fn generate_keypair_from_mnemonic(mnemonic: &Mnemonic) -> Result<(SigningKey, VerifyingKey)> {
61    let seed = mnemonic.to_seed("");
62    // Use first 32 bytes of the seed as the private key
63    let seed_bytes: [u8; 32] = seed[..32]
64        .try_into()
65        .map_err(|_| anyhow::anyhow!("Failed to convert seed to 32 bytes"))?;
66    
67    let signing_key = SigningKey::from_bytes(&seed_bytes);
68    let verifying_key = signing_key.verifying_key();
69    
70    Ok((signing_key, verifying_key))
71}
72
73/// Generate an ED25519 key pair from a mnemonic using BIP32 path derivation
74pub fn generate_keypair_from_mnemonic_with_path(
75    mnemonic: &Mnemonic,
76    path: &str,
77) -> Result<(SigningKey, VerifyingKey)> {
78    let seed = mnemonic.to_seed("");
79    let bip_path = BIP32Path::from_str(path)
80        .map_err(|e| anyhow::anyhow!("Invalid BIP32 path {}: {:?}", path, e))?;
81    
82    let derived_key = slip10::derive_key_from_path(&seed, Curve::Ed25519, &bip_path)
83        .map_err(|e| anyhow::anyhow!("Failed to derive key from path: {:?}", e))?;
84    
85    let signing_key = SigningKey::from_bytes(&derived_key.key);
86    let verifying_key = signing_key.verifying_key();
87    
88    Ok((signing_key, verifying_key))
89}
90
91/// Generate a new ED25519 key pair (raw keys)
92pub fn generate_keypair_raw() -> Result<(SigningKey, VerifyingKey)> {
93    let signing_key = SigningKey::generate(&mut OsRng);
94    let verifying_key = signing_key.verifying_key();
95    Ok((signing_key, verifying_key))
96}
97
98/// Generate key pair properties from mnemonic (optionally provided)
99/// Uses default path m/44'/397'/0' if no path is provided
100pub fn generate_keypair_properties(
101    mnemonic: Option<String>,
102    path: Option<String>,
103) -> Result<KeyPairProperties> {
104    let default_path = "m/44'/397'/0'".to_string();
105    let derivation_path = path.unwrap_or(default_path);
106    
107    let (master_seed_phrase, master_seed) = {
108        let mnemonic = if let Some(m) = mnemonic {
109            Mnemonic::parse(&m)
110                .context("Invalid mnemonic phrase")?
111        } else {
112            let mut entropy = [0u8; 16];
113            OsRng.fill_bytes(&mut entropy);
114            Mnemonic::from_entropy(&entropy)
115                .context("Failed to generate mnemonic")?
116        };
117        
118        let master_seed_phrase = mnemonic.words().collect::<Vec<&str>>().join(" ");
119        (master_seed_phrase, mnemonic.to_seed(""))
120    };
121
122    let bip_path = BIP32Path::from_str(&derivation_path)
123        .map_err(|e| anyhow::anyhow!("Invalid BIP32 path {}: {:?}", derivation_path, e))?;
124
125    let derived_private_key = slip10::derive_key_from_path(&master_seed, Curve::Ed25519, &bip_path)
126        .map_err(|e| anyhow::anyhow!("Failed to derive key from path: {:?}", e))?;
127
128    let secret_keypair = {
129        let secret = ed25519_dalek::SigningKey::from_bytes(&derived_private_key.key);
130        let public = secret.verifying_key();
131        (secret, public)
132    };
133
134    let implicit_account_id = hex::encode(secret_keypair.1.as_bytes());
135
136    let public_key_str = format!(
137        "ed25519:{}",
138        bs58::encode(secret_keypair.1.as_bytes()).into_string()
139    );
140
141    let secret_keypair_str = format!(
142        "ed25519:{}",
143        bs58::encode(secret_keypair.0.to_bytes()).into_string()
144    );
145
146    Ok(KeyPairProperties {
147        seed_phrase_hd_path: derivation_path,
148        master_seed_phrase,
149        implicit_account_id,
150        public_key_str,
151        secret_keypair_str,
152    })
153}
154
155/// Get key pair with account ID from mnemonic and index
156pub fn get_key_with_account_id(
157    mnemonic: &str,
158    index: u128,
159    account_id: &str,
160) -> Result<AccountPrivate> {
161    let path_buf = format!("m/44'/397'/{}'", index);
162    let kp = get_key_with_mnemonic_path(mnemonic, &path_buf)?;
163    
164    Ok(AccountPrivate {
165        account_id: account_id.to_string(),
166        seed_phrase_hd_path: kp.seed_phrase_hd_path,
167        master_seed_phrase: kp.master_seed_phrase,
168        public_key_str: kp.public_key_str,
169        secret_keypair_str: kp.secret_keypair_str,
170    })
171}
172
173/// Get key pair with mnemonic and BIP32 path
174pub fn get_key_with_mnemonic_path(
175    mnemonic: &str,
176    path: &str,
177) -> Result<KeyPairProperties> {
178    let mnemonic_obj = Mnemonic::parse(mnemonic)
179        .context("Invalid mnemonic phrase")?;
180
181    let master_seed = mnemonic_obj.to_seed("");
182
183    let bip_path = BIP32Path::from_str(path)
184        .map_err(|e| anyhow::anyhow!("Invalid BIP32 path {}: {:?}", path, e))?;
185
186    let derived_private_key = slip10::derive_key_from_path(&master_seed, Curve::Ed25519, &bip_path)
187        .map_err(|e| anyhow::anyhow!("Failed to derive key from path: {:?}", e))?;
188
189    let secret_keypair = {
190        let secret = ed25519_dalek::SigningKey::from_bytes(&derived_private_key.key);
191        let public = secret.verifying_key();
192        (secret, public)
193    };
194
195    let implicit_account_id = hex::encode(secret_keypair.1.as_bytes());
196
197    let public_key_str = format!(
198        "ed25519:{}",
199        bs58::encode(secret_keypair.1.as_bytes()).into_string()
200    );
201
202    let secret_keypair_str = format!(
203        "ed25519:{}",
204        bs58::encode(secret_keypair.0.to_bytes()).into_string()
205    );
206
207    Ok(KeyPairProperties {
208        seed_phrase_hd_path: path.to_string(),
209        master_seed_phrase: mnemonic.to_string(),
210        implicit_account_id,
211        public_key_str,
212        secret_keypair_str,
213    })
214}
215
216/// Encode a public key to base58
217pub fn encode_public_key(key: &VerifyingKey) -> String {
218    bs58::encode(key.as_bytes()).into_string()
219}
220
221/// Encode a private key to base58
222pub fn encode_private_key(key: &SigningKey) -> String {
223    bs58::encode(key.to_bytes()).into_string()
224}
225
226/// Encode a public key to ed25519:base58 format
227pub fn encode_public_key_ed25519(key: &VerifyingKey) -> String {
228    format!("ed25519:{}", encode_public_key(key))
229}
230
231/// Encode a private key to ed25519:base58 format
232pub fn encode_private_key_ed25519(key: &SigningKey) -> String {
233    format!("ed25519:{}", encode_private_key(key))
234}
235
236/// Decode a public key from base58
237pub fn decode_public_key(encoded: &str) -> Result<VerifyingKey> {
238    let bytes = bs58::decode(encoded)
239        .into_vec()
240        .context("Base58 decode error")?;
241    let len = bytes.len();
242    let key_bytes: [u8; 32] = bytes
243        .try_into()
244        .map_err(|_| anyhow::anyhow!("Invalid key length: expected 32 bytes, got {}", len))?;
245    VerifyingKey::from_bytes(&key_bytes)
246        .map_err(|e| anyhow::anyhow!("Invalid public key: {}", e))
247}
248
249/// Decode a public key from ed25519:base58 format
250pub fn decode_public_key_ed25519(encoded: &str) -> Result<VerifyingKey> {
251    let encoded = encoded
252        .strip_prefix("ed25519:")
253        .context("Invalid ed25519 public key format")?;
254    decode_public_key(encoded)
255}
256
257/// Decode a private key from base58
258pub fn decode_private_key(encoded: &str) -> Result<SigningKey> {
259    let bytes = bs58::decode(encoded)
260        .into_vec()
261        .context("Base58 decode error")?;
262    let len = bytes.len();
263    let key_bytes: [u8; 32] = bytes
264        .try_into()
265        .map_err(|_| anyhow::anyhow!("Invalid key length: expected 32 bytes, got {}", len))?;
266    Ok(SigningKey::from_bytes(&key_bytes))
267}
268
269/// Decode a private key from ed25519:base58 format
270pub fn decode_private_key_ed25519(encoded: &str) -> Result<SigningKey> {
271    let encoded = encoded
272        .strip_prefix("ed25519:")
273        .context("Invalid ed25519 private key format")?;
274    decode_private_key(encoded)
275}
276
277/// Sign a message with a signing key
278pub fn sign_message(key: &SigningKey, message: &[u8]) -> Signature {
279    key.sign(message)
280}
281
282/// Verify a signature with a verifying key
283pub fn verify_signature(key: &VerifyingKey, message: &[u8], signature: &Signature) -> bool {
284    key.verify(message, signature).is_ok()
285}
286
287/// Generate implicit account ID from public key
288pub fn generate_implicit_account_id(public_key: &VerifyingKey) -> String {
289    hex::encode(public_key.as_bytes())
290}
291
292/// Get signer with account ID and key string
293/// Returns (account_id, secret_key, public_key)
294pub fn get_signer_with_id_key(account_id: &str, key: &str) -> Result<(String, SigningKey, VerifyingKey)> {
295    let account_id_str = account_id.to_string();
296    let secret = decode_private_key_ed25519(key)
297        .or_else(|_| decode_private_key(key))
298        .context("Failed to decode private key")?;
299    let public_key = secret.verifying_key();
300    Ok((account_id_str, secret, public_key))
301}
302
303/// Generate public key information from private key string
304/// Takes a private key in ed25519:base58 format and returns the corresponding public key and implicit account ID
305pub fn get_public_key_from_private(secret_keypair_str: &str) -> Result<PublicKeyInfo> {
306    let signing_key = decode_private_key_ed25519(secret_keypair_str)
307        .or_else(|_| decode_private_key(secret_keypair_str))
308        .context("Failed to decode private key")?;
309    let verifying_key = signing_key.verifying_key();
310    let public_key_str = encode_public_key_ed25519(&verifying_key);
311    let implicit_account_id = generate_implicit_account_id(&verifying_key);
312    Ok(PublicKeyInfo {
313        secret_keypair_str: secret_keypair_str.to_string(),
314        public_key_str,
315        implicit_account_id,
316    })
317}
318
319/// Generate key pair properties with optional mnemonic
320/// If mnemonic is None, generates a new one
321/// Uses default path m/44'/397'/0'
322pub fn get_mnemonic_with_rand(mnemonic: Option<String>) -> Result<KeyPairProperties> {
323    generate_keypair_properties(mnemonic, None)
324}
325
326/// Generate key pair properties (alias for generate_keypair_properties with default path)
327pub fn generate_keypair(mnemonic: Option<String>) -> Result<KeyPairProperties> {
328    generate_keypair_properties(mnemonic, None)
329}
330
331#[cfg(test)]
332mod tests {
333    use super::*;
334
335    #[test]
336    fn test_generate_mnemonic() -> Result<()> {
337        let mnemonic = generate_mnemonic()?;
338        assert!(!mnemonic.is_empty());
339        let words: Vec<&str> = mnemonic.split_whitespace().collect();
340        assert_eq!(words.len(), 12);
341        Ok(())
342    }
343
344    #[test]
345    fn test_generate_keypair() -> Result<()> {
346        let (signing_key, verifying_key) = generate_keypair_raw()?;
347        let message = b"test message";
348        let signature = sign_message(&signing_key, message);
349        assert!(verify_signature(&verifying_key, message, &signature));
350        Ok(())
351    }
352
353    #[test]
354    fn test_mnemonic_to_keypair() -> Result<()> {
355        let mnemonic_str = generate_mnemonic()?;
356        let mnemonic = mnemonic_from_phrase(&mnemonic_str)?;
357        let (signing_key, verifying_key) = generate_keypair_from_mnemonic(&mnemonic)?;
358        let message = b"test message";
359        let signature = sign_message(&signing_key, message);
360        assert!(verify_signature(&verifying_key, message, &signature));
361        Ok(())
362    }
363
364    #[test]
365    fn test_mnemonic_to_keypair_with_path() -> Result<()> {
366        let mnemonic_str = generate_mnemonic()?;
367        let mnemonic = mnemonic_from_phrase(&mnemonic_str)?;
368        let (signing_key, verifying_key) = generate_keypair_from_mnemonic_with_path(
369            &mnemonic,
370            "m/44'/397'/0'",
371        )?;
372        let message = b"test message";
373        let signature = sign_message(&signing_key, message);
374        assert!(verify_signature(&verifying_key, message, &signature));
375        Ok(())
376    }
377
378    #[test]
379    fn test_encode_decode_keys() -> Result<()> {
380        let (signing_key, verifying_key) = generate_keypair_raw()?;
381        let encoded_priv = encode_private_key(&signing_key);
382        let encoded_pub = encode_public_key(&verifying_key);
383        
384        let decoded_priv = decode_private_key(&encoded_priv)?;
385        let decoded_pub = decode_public_key(&encoded_pub)?;
386        
387        assert_eq!(signing_key.to_bytes(), decoded_priv.to_bytes());
388        assert_eq!(verifying_key.as_bytes(), decoded_pub.as_bytes());
389        Ok(())
390    }
391
392    #[test]
393    fn test_generate_keypair_properties() -> Result<()> {
394        let kp = generate_keypair_properties(None, None)?;
395        assert!(!kp.master_seed_phrase.is_empty());
396        assert_eq!(kp.seed_phrase_hd_path, "m/44'/397'/0'");
397        assert!(!kp.implicit_account_id.is_empty());
398        assert!(kp.public_key_str.starts_with("ed25519:"));
399        assert!(kp.secret_keypair_str.starts_with("ed25519:"));
400        Ok(())
401    }
402
403    #[test]
404    fn test_get_key_with_mnemonic_path() -> Result<()> {
405        let mnemonic = generate_mnemonic()?;
406        let kp = get_key_with_mnemonic_path(&mnemonic, "m/44'/397'/0'")?;
407        assert_eq!(kp.master_seed_phrase, mnemonic);
408        assert_eq!(kp.seed_phrase_hd_path, "m/44'/397'/0'");
409        assert!(!kp.implicit_account_id.is_empty());
410        Ok(())
411    }
412
413    #[test]
414    fn test_ed25519_format() -> Result<()> {
415        let (signing_key, verifying_key) = generate_keypair_raw()?;
416        let pub_str = encode_public_key_ed25519(&verifying_key);
417        let priv_str = encode_private_key_ed25519(&signing_key);
418        
419        assert!(pub_str.starts_with("ed25519:"));
420        assert!(priv_str.starts_with("ed25519:"));
421        
422        let decoded_pub = decode_public_key_ed25519(&pub_str)?;
423        let decoded_priv = decode_private_key_ed25519(&priv_str)?;
424        
425        assert_eq!(verifying_key.as_bytes(), decoded_pub.as_bytes());
426        assert_eq!(signing_key.to_bytes(), decoded_priv.to_bytes());
427        Ok(())
428    }
429}