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