1use anyhow::anyhow;
6use bip32::{ChildNumber, DerivationPath, XPrv};
7use bip39::{Language, Mnemonic, MnemonicType, Seed};
8use fastcrypto::{
9 ed25519::{Ed25519KeyPair, Ed25519PrivateKey},
10 secp256k1::{Secp256k1KeyPair, Secp256k1PrivateKey},
11 secp256r1::{Secp256r1KeyPair, Secp256r1PrivateKey},
12 traits::{KeyPair, ToFromBytes},
13};
14use iota_types::{
15 base_types::IotaAddress,
16 crypto::{IotaKeyPair, SignatureScheme},
17 error::IotaError,
18};
19use slip10_ed25519::derive_ed25519_private_key;
20
21pub const DERIVATION_PATH_COIN_TYPE: u32 = 4218;
22pub const DERVIATION_PATH_PURPOSE_ED25519: u32 = 44;
23pub const DERVIATION_PATH_PURPOSE_SECP256K1: u32 = 54;
24pub const DERVIATION_PATH_PURPOSE_SECP256R1: u32 = 74;
25
26pub fn derive_key_pair_from_path(
32 seed: &[u8],
33 derivation_path: Option<DerivationPath>,
34 key_scheme: &SignatureScheme,
35) -> Result<(IotaAddress, IotaKeyPair), IotaError> {
36 let path = validate_path(key_scheme, derivation_path)?;
37 match key_scheme {
38 SignatureScheme::ED25519 => {
39 let indexes = path.into_iter().map(|i| i.into()).collect::<Vec<_>>();
40 let derived = derive_ed25519_private_key(seed, &indexes);
41 let sk = Ed25519PrivateKey::from_bytes(&derived)
42 .map_err(|e| IotaError::SignatureKeyGen(e.to_string()))?;
43 let kp: Ed25519KeyPair = sk.into();
44 Ok((kp.public().into(), IotaKeyPair::Ed25519(kp)))
45 }
46 SignatureScheme::Secp256k1 => {
47 let child_xprv = XPrv::derive_from_path(seed, &path)
48 .map_err(|e| IotaError::SignatureKeyGen(e.to_string()))?;
49 let kp = Secp256k1KeyPair::from(
50 Secp256k1PrivateKey::from_bytes(child_xprv.private_key().to_bytes().as_slice())
51 .map_err(|e| IotaError::SignatureKeyGen(e.to_string()))?,
52 );
53 Ok((kp.public().into(), IotaKeyPair::Secp256k1(kp)))
54 }
55 SignatureScheme::Secp256r1 => {
56 let child_xprv = XPrv::derive_from_path(seed, &path)
57 .map_err(|e| IotaError::SignatureKeyGen(e.to_string()))?;
58 let kp = Secp256r1KeyPair::from(
59 Secp256r1PrivateKey::from_bytes(child_xprv.private_key().to_bytes().as_slice())
60 .map_err(|e| IotaError::SignatureKeyGen(e.to_string()))?,
61 );
62 Ok((kp.public().into(), IotaKeyPair::Secp256r1(kp)))
63 }
64 SignatureScheme::BLS12381
65 | SignatureScheme::MultiSig
66 | SignatureScheme::ZkLoginAuthenticator
67 | SignatureScheme::PasskeyAuthenticator
68 | SignatureScheme::MoveAuthenticator => Err(IotaError::UnsupportedFeature {
69 error: format!("key derivation not supported {key_scheme:?}"),
70 }),
71 }
72}
73
74pub fn validate_path(
75 key_scheme: &SignatureScheme,
76 path: Option<DerivationPath>,
77) -> Result<DerivationPath, IotaError> {
78 match key_scheme {
79 SignatureScheme::ED25519 => {
80 match path {
81 Some(p) => {
82 if let &[purpose, coin_type, account, change, address] = p.as_ref() {
85 if Some(purpose)
86 == ChildNumber::new(DERVIATION_PATH_PURPOSE_ED25519, true).ok()
87 && (Some(coin_type)
88 == ChildNumber::new(DERIVATION_PATH_COIN_TYPE, true).ok())
89 && account.is_hardened()
90 && change.is_hardened()
91 && address.is_hardened()
92 {
93 Ok(p)
94 } else {
95 Err(IotaError::SignatureKeyGen("Invalid path".to_string()))
96 }
97 } else {
98 Err(IotaError::SignatureKeyGen("Invalid path".to_string()))
99 }
100 }
101 None => Ok(format!(
102 "m/{DERVIATION_PATH_PURPOSE_ED25519}'/{DERIVATION_PATH_COIN_TYPE}'/0'/0'/0'"
103 )
104 .parse()
105 .map_err(|_| IotaError::SignatureKeyGen("Cannot parse path".to_string()))?),
106 }
107 }
108 SignatureScheme::Secp256k1 => {
109 match path {
110 Some(p) => {
111 if let &[purpose, coin_type, account, change, address] = p.as_ref() {
114 if Some(purpose)
115 == ChildNumber::new(DERVIATION_PATH_PURPOSE_SECP256K1, true).ok()
116 && Some(coin_type)
117 == ChildNumber::new(DERIVATION_PATH_COIN_TYPE, true).ok()
118 && account.is_hardened()
119 && !change.is_hardened()
120 && !address.is_hardened()
121 {
122 Ok(p)
123 } else {
124 Err(IotaError::SignatureKeyGen("Invalid path".to_string()))
125 }
126 } else {
127 Err(IotaError::SignatureKeyGen("Invalid path".to_string()))
128 }
129 }
130 None => Ok(format!(
131 "m/{DERVIATION_PATH_PURPOSE_SECP256K1}'/{DERIVATION_PATH_COIN_TYPE}'/0'/0/0"
132 )
133 .parse()
134 .map_err(|_| IotaError::SignatureKeyGen("Cannot parse path".to_string()))?),
135 }
136 }
137 SignatureScheme::Secp256r1 => {
138 match path {
139 Some(p) => {
140 if let &[purpose, coin_type, account, change, address] = p.as_ref() {
143 if Some(purpose)
144 == ChildNumber::new(DERVIATION_PATH_PURPOSE_SECP256R1, true).ok()
145 && Some(coin_type)
146 == ChildNumber::new(DERIVATION_PATH_COIN_TYPE, true).ok()
147 && account.is_hardened()
148 && !change.is_hardened()
149 && !address.is_hardened()
150 {
151 Ok(p)
152 } else {
153 Err(IotaError::SignatureKeyGen("Invalid path".to_string()))
154 }
155 } else {
156 Err(IotaError::SignatureKeyGen("Invalid path".to_string()))
157 }
158 }
159 None => Ok(format!(
160 "m/{DERVIATION_PATH_PURPOSE_SECP256R1}'/{DERIVATION_PATH_COIN_TYPE}'/0'/0/0"
161 )
162 .parse()
163 .map_err(|_| IotaError::SignatureKeyGen("Cannot parse path".to_string()))?),
164 }
165 }
166 SignatureScheme::BLS12381
167 | SignatureScheme::MultiSig
168 | SignatureScheme::ZkLoginAuthenticator
169 | SignatureScheme::PasskeyAuthenticator
170 | SignatureScheme::MoveAuthenticator => Err(IotaError::UnsupportedFeature {
171 error: format!("key derivation not supported {key_scheme:?}"),
172 }),
173 }
174}
175
176pub fn generate_new_key(
177 key_scheme: SignatureScheme,
178 derivation_path: Option<DerivationPath>,
179 word_length: Option<String>,
180) -> Result<(IotaAddress, IotaKeyPair, SignatureScheme, String), anyhow::Error> {
181 let mnemonic = Mnemonic::new(parse_word_length(word_length)?, Language::English);
182 let seed = Seed::new(&mnemonic, "");
183 match derive_key_pair_from_path(seed.as_bytes(), derivation_path, &key_scheme) {
184 Ok((address, kp)) => Ok((address, kp, key_scheme, mnemonic.phrase().to_string())),
185 Err(e) => Err(anyhow!("Failed to generate keypair: {:?}", e)),
186 }
187}
188
189fn parse_word_length(s: Option<String>) -> Result<MnemonicType, anyhow::Error> {
190 match s {
191 None => Ok(MnemonicType::Words12),
192 Some(s) => match s.as_str() {
193 "word12" => Ok(MnemonicType::Words12),
194 "word15" => Ok(MnemonicType::Words15),
195 "word18" => Ok(MnemonicType::Words18),
196 "word21" => Ok(MnemonicType::Words21),
197 "word24" => Ok(MnemonicType::Words24),
198 _ => anyhow::bail!("Invalid word length"),
199 },
200 }
201}