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