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, address_from_iota_pub_key},
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((
45 address_from_iota_pub_key(kp.public()),
46 IotaKeyPair::Ed25519(kp),
47 ))
48 }
49 SignatureScheme::Secp256k1 => {
50 let child_xprv = XPrv::derive_from_path(seed, &path)
51 .map_err(|e| IotaError::SignatureKeyGen(e.to_string()))?;
52 let kp = Secp256k1KeyPair::from(
53 Secp256k1PrivateKey::from_bytes(child_xprv.private_key().to_bytes().as_slice())
54 .map_err(|e| IotaError::SignatureKeyGen(e.to_string()))?,
55 );
56 Ok((
57 address_from_iota_pub_key(kp.public()),
58 IotaKeyPair::Secp256k1(kp),
59 ))
60 }
61 SignatureScheme::Secp256r1 => {
62 let child_xprv = XPrv::derive_from_path(seed, &path)
63 .map_err(|e| IotaError::SignatureKeyGen(e.to_string()))?;
64 let kp = Secp256r1KeyPair::from(
65 Secp256r1PrivateKey::from_bytes(child_xprv.private_key().to_bytes().as_slice())
66 .map_err(|e| IotaError::SignatureKeyGen(e.to_string()))?,
67 );
68 Ok((
69 address_from_iota_pub_key(kp.public()),
70 IotaKeyPair::Secp256r1(kp),
71 ))
72 }
73 #[allow(deprecated)]
74 SignatureScheme::BLS12381
75 | SignatureScheme::MultiSig
76 | SignatureScheme::ZkLoginAuthenticatorDeprecated
77 | SignatureScheme::PasskeyAuthenticator
78 | SignatureScheme::MoveAuthenticator => Err(IotaError::UnsupportedFeature {
79 error: format!("key derivation not supported {key_scheme:?}"),
80 }),
81 }
82}
83
84pub fn validate_path(
85 key_scheme: &SignatureScheme,
86 path: Option<DerivationPath>,
87) -> Result<DerivationPath, IotaError> {
88 match key_scheme {
89 SignatureScheme::ED25519 => {
90 match path {
91 Some(p) => {
92 if let &[purpose, coin_type, account, change, address] = p.as_ref() {
95 if Some(purpose)
96 == ChildNumber::new(DERVIATION_PATH_PURPOSE_ED25519, true).ok()
97 && (Some(coin_type)
98 == ChildNumber::new(DERIVATION_PATH_COIN_TYPE, true).ok())
99 && account.is_hardened()
100 && change.is_hardened()
101 && address.is_hardened()
102 {
103 Ok(p)
104 } else {
105 Err(IotaError::SignatureKeyGen("Invalid path".to_string()))
106 }
107 } else {
108 Err(IotaError::SignatureKeyGen("Invalid path".to_string()))
109 }
110 }
111 None => Ok(format!(
112 "m/{DERVIATION_PATH_PURPOSE_ED25519}'/{DERIVATION_PATH_COIN_TYPE}'/0'/0'/0'"
113 )
114 .parse()
115 .map_err(|_| IotaError::SignatureKeyGen("Cannot parse path".to_string()))?),
116 }
117 }
118 SignatureScheme::Secp256k1 => {
119 match path {
120 Some(p) => {
121 if let &[purpose, coin_type, account, change, address] = p.as_ref() {
124 if Some(purpose)
125 == ChildNumber::new(DERVIATION_PATH_PURPOSE_SECP256K1, true).ok()
126 && Some(coin_type)
127 == ChildNumber::new(DERIVATION_PATH_COIN_TYPE, true).ok()
128 && account.is_hardened()
129 && !change.is_hardened()
130 && !address.is_hardened()
131 {
132 Ok(p)
133 } else {
134 Err(IotaError::SignatureKeyGen("Invalid path".to_string()))
135 }
136 } else {
137 Err(IotaError::SignatureKeyGen("Invalid path".to_string()))
138 }
139 }
140 None => Ok(format!(
141 "m/{DERVIATION_PATH_PURPOSE_SECP256K1}'/{DERIVATION_PATH_COIN_TYPE}'/0'/0/0"
142 )
143 .parse()
144 .map_err(|_| IotaError::SignatureKeyGen("Cannot parse path".to_string()))?),
145 }
146 }
147 SignatureScheme::Secp256r1 => {
148 match path {
149 Some(p) => {
150 if let &[purpose, coin_type, account, change, address] = p.as_ref() {
153 if Some(purpose)
154 == ChildNumber::new(DERVIATION_PATH_PURPOSE_SECP256R1, true).ok()
155 && Some(coin_type)
156 == ChildNumber::new(DERIVATION_PATH_COIN_TYPE, true).ok()
157 && account.is_hardened()
158 && !change.is_hardened()
159 && !address.is_hardened()
160 {
161 Ok(p)
162 } else {
163 Err(IotaError::SignatureKeyGen("Invalid path".to_string()))
164 }
165 } else {
166 Err(IotaError::SignatureKeyGen("Invalid path".to_string()))
167 }
168 }
169 None => Ok(format!(
170 "m/{DERVIATION_PATH_PURPOSE_SECP256R1}'/{DERIVATION_PATH_COIN_TYPE}'/0'/0/0"
171 )
172 .parse()
173 .map_err(|_| IotaError::SignatureKeyGen("Cannot parse path".to_string()))?),
174 }
175 }
176 #[allow(deprecated)]
177 SignatureScheme::BLS12381
178 | SignatureScheme::MultiSig
179 | SignatureScheme::ZkLoginAuthenticatorDeprecated
180 | SignatureScheme::PasskeyAuthenticator
181 | SignatureScheme::MoveAuthenticator => Err(IotaError::UnsupportedFeature {
182 error: format!("key derivation not supported {key_scheme:?}"),
183 }),
184 }
185}
186
187pub fn generate_new_key(
188 key_scheme: SignatureScheme,
189 derivation_path: Option<DerivationPath>,
190 word_length: Option<String>,
191) -> Result<(IotaAddress, IotaKeyPair, SignatureScheme, String), anyhow::Error> {
192 let mnemonic = Mnemonic::new(parse_word_length(word_length)?, Language::English);
193 let seed = Seed::new(&mnemonic, "");
194 match derive_key_pair_from_path(seed.as_bytes(), derivation_path, &key_scheme) {
195 Ok((address, kp)) => Ok((address, kp, key_scheme, mnemonic.phrase().to_string())),
196 Err(e) => Err(anyhow!("Failed to generate keypair: {:?}", e)),
197 }
198}
199
200fn parse_word_length(s: Option<String>) -> Result<MnemonicType, anyhow::Error> {
201 match s {
202 None => Ok(MnemonicType::Words12),
203 Some(s) => match s.as_str() {
204 "word12" => Ok(MnemonicType::Words12),
205 "word15" => Ok(MnemonicType::Words15),
206 "word18" => Ok(MnemonicType::Words18),
207 "word21" => Ok(MnemonicType::Words21),
208 "word24" => Ok(MnemonicType::Words24),
209 _ => anyhow::bail!("Invalid word length"),
210 },
211 }
212}