1#![cfg_attr(doc_cfg, feature(doc_cfg))]
6
7use iota_types::{PersonalMessage, Transaction, UserSignature};
8pub use signature::{Error as SignatureError, Signer, Verifier};
9
10#[derive(thiserror::Error, Debug)]
12#[non_exhaustive]
13pub enum PrivateKeyError {
14 #[error("empty data: {0}")]
16 EmptyData(String),
17 #[error("invalid signature scheme: {0}")]
19 InvalidScheme(String),
20 #[error("bech32 error: {0}")]
22 Bech32(String),
23 #[error("bech32 HRP error: {0}")]
25 Bech32Hrp(String),
26 #[cfg(feature = "mnemonic")]
27 #[error("mnemonic error: {0}")]
28 Bip32(#[from] bip32::Error),
29 #[cfg(feature = "mnemonic")]
30 #[error("mnemonic error: {0}")]
31 Bip39(#[from] bip39::Error),
32}
33
34#[cfg(feature = "bls12381")]
35#[cfg_attr(doc_cfg, doc(cfg(feature = "bls12381")))]
36pub mod bls12381;
37
38#[cfg(feature = "bls12381")]
39#[cfg_attr(doc_cfg, doc(cfg(feature = "bls12381")))]
40pub mod validator;
41
42#[cfg(feature = "ed25519")]
43#[cfg_attr(doc_cfg, doc(cfg(feature = "ed25519")))]
44pub mod ed25519;
45
46#[cfg(feature = "secp256k1")]
47#[cfg_attr(doc_cfg, doc(cfg(feature = "secp256k1")))]
48pub mod secp256k1;
49
50#[cfg(feature = "secp256r1")]
51#[cfg_attr(doc_cfg, doc(cfg(feature = "secp256r1")))]
52pub mod secp256r1;
53
54#[cfg(feature = "passkey")]
55#[cfg_attr(doc_cfg, doc(cfg(feature = "passkey")))]
56pub mod passkey;
57
58#[cfg(feature = "zklogin")]
59#[cfg_attr(doc_cfg, doc(cfg(feature = "zklogin")))]
60pub mod zklogin;
61
62#[cfg(any(
63 feature = "ed25519",
64 feature = "secp256r1",
65 feature = "secp256k1",
66 feature = "zklogin"
67))]
68#[cfg_attr(
69 doc_cfg,
70 doc(cfg(any(
71 feature = "ed25519",
72 feature = "secp256r1",
73 feature = "secp256k1",
74 feature = "zklogin"
75 )))
76)]
77pub mod simple;
78
79#[cfg(any(
80 feature = "ed25519",
81 feature = "secp256r1",
82 feature = "secp256k1",
83 feature = "zklogin"
84))]
85#[cfg_attr(
86 doc_cfg,
87 doc(cfg(any(
88 feature = "ed25519",
89 feature = "secp256r1",
90 feature = "secp256k1",
91 feature = "zklogin"
92 )))
93)]
94pub mod multisig;
95
96#[cfg(any(
97 feature = "ed25519",
98 feature = "secp256r1",
99 feature = "secp256k1",
100 feature = "zklogin"
101))]
102#[cfg_attr(
103 doc_cfg,
104 doc(cfg(any(
105 feature = "ed25519",
106 feature = "secp256r1",
107 feature = "secp256k1",
108 feature = "zklogin"
109 )))
110)]
111#[doc(inline)]
112pub use multisig::UserSignatureVerifier;
113
114pub trait IotaSigner {
123 fn sign_transaction(&self, transaction: &Transaction) -> Result<UserSignature, SignatureError>;
124 fn sign_personal_message(
125 &self,
126 message: &PersonalMessage<'_>,
127 ) -> Result<UserSignature, SignatureError>;
128}
129
130impl<T: Signer<UserSignature>> IotaSigner for T {
131 fn sign_transaction(&self, transaction: &Transaction) -> Result<UserSignature, SignatureError> {
132 let msg = transaction.signing_digest();
133 self.try_sign(&msg)
134 }
135
136 fn sign_personal_message(
137 &self,
138 message: &PersonalMessage<'_>,
139 ) -> Result<UserSignature, SignatureError> {
140 let msg = message.signing_digest();
141 self.try_sign(&msg)
142 }
143}
144
145pub trait IotaVerifier {
154 fn verify_transaction(
155 &self,
156 transaction: &Transaction,
157 signature: &UserSignature,
158 ) -> Result<(), SignatureError>;
159 fn verify_personal_message(
160 &self,
161 message: &PersonalMessage<'_>,
162 signature: &UserSignature,
163 ) -> Result<(), SignatureError>;
164}
165
166impl<T: Verifier<UserSignature>> IotaVerifier for T {
167 fn verify_transaction(
168 &self,
169 transaction: &Transaction,
170 signature: &UserSignature,
171 ) -> Result<(), SignatureError> {
172 let message = transaction.signing_digest();
173 self.verify(&message, signature)
174 }
175
176 fn verify_personal_message(
177 &self,
178 message: &PersonalMessage<'_>,
179 signature: &UserSignature,
180 ) -> Result<(), SignatureError> {
181 let message = message.signing_digest();
182 self.verify(&message, signature)
183 }
184}
185
186#[cfg(feature = "bech32")]
188#[cfg_attr(doc_cfg, doc(cfg(feature = "bech32")))]
189pub const IOTA_PRIV_KEY_PREFIX: &str = "iotaprivkey";
190
191#[cfg(feature = "mnemonic")]
192pub const DERIVATION_PATH_COIN_TYPE: u32 = 4218;
193#[cfg(feature = "mnemonic")]
194pub const DERIVATION_PATH_PURPOSE_ED25519: u32 = 44;
195#[cfg(feature = "mnemonic")]
196pub const DERIVATION_PATH_PURPOSE_SECP256K1: u32 = 54;
197#[cfg(feature = "mnemonic")]
198pub const DERIVATION_PATH_PURPOSE_SECP256R1: u32 = 74;
199
200pub trait PrivateKeyScheme {
202 const SCHEME: iota_types::SignatureScheme;
203
204 fn scheme(&self) -> iota_types::SignatureScheme {
206 Self::SCHEME
207 }
208}
209
210pub trait ToFromBytes {
212 type Error;
213 type ByteArray;
214
215 fn to_bytes(&self) -> Self::ByteArray;
217
218 fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error>
220 where
221 Self: Sized;
222}
223
224pub trait ToFromFlaggedBytes {
227 type Error;
228
229 fn to_flagged_bytes(&self) -> Vec<u8>;
231
232 fn from_flagged_bytes(bytes: &[u8]) -> Result<Self, Self::Error>
234 where
235 Self: Sized;
236}
237
238impl<T: ToFromBytes<Error = PrivateKeyError> + PrivateKeyScheme> ToFromFlaggedBytes for T
239where
240 T::ByteArray: AsRef<[u8]>,
241{
242 type Error = PrivateKeyError;
243
244 fn to_flagged_bytes(&self) -> Vec<u8> {
246 let key_bytes = self.to_bytes();
247 let mut bytes = Vec::with_capacity(1 + key_bytes.as_ref().len());
248 bytes.push(self.scheme().to_u8());
249 bytes.extend_from_slice(key_bytes.as_ref());
250 bytes
251 }
252
253 fn from_flagged_bytes(bytes: &[u8]) -> Result<Self, Self::Error>
254 where
255 Self: Sized,
256 {
257 if bytes.is_empty() {
258 return Err(PrivateKeyError::EmptyData("flagged bytes".to_string()));
259 }
260
261 let flag = iota_types::SignatureScheme::from_byte(bytes[0])
262 .map_err(|e| PrivateKeyError::InvalidScheme(format!("{e:?}")))?;
263
264 if flag != Self::SCHEME {
265 return Err(PrivateKeyError::InvalidScheme(format!(
266 "expected {:?}, got {flag:?}",
267 Self::SCHEME
268 )));
269 }
270
271 let key_bytes = &bytes[1..];
272 Self::from_bytes(key_bytes)
273 }
274}
275
276#[cfg(feature = "bech32")]
278pub trait ToFromBech32 {
279 type Error;
280
281 fn to_bech32(&self) -> Result<String, Self::Error>;
283
284 fn from_bech32(value: &str) -> Result<Self, Self::Error>
286 where
287 Self: Sized;
288}
289
290#[cfg(feature = "bech32")]
291impl<T: ToFromFlaggedBytes<Error = PrivateKeyError>> ToFromBech32 for T {
292 type Error = PrivateKeyError;
293
294 #[cfg(feature = "bech32")]
295 fn to_bech32(&self) -> Result<String, Self::Error> {
296 use bech32::Hrp;
297
298 let hrp = Hrp::parse(IOTA_PRIV_KEY_PREFIX)
299 .map_err(|e| PrivateKeyError::Bech32Hrp(format!("{e}")))?;
300
301 let bytes = self.to_flagged_bytes();
302
303 bech32::encode::<bech32::Bech32>(hrp, &bytes)
304 .map_err(|e| PrivateKeyError::Bech32(format!("encoding failed: {e}")))
305 }
306
307 #[cfg(feature = "bech32")]
308 fn from_bech32(value: &str) -> Result<Self, Self::Error> {
309 use bech32::Hrp;
310
311 let expected_hrp = Hrp::parse(IOTA_PRIV_KEY_PREFIX)
312 .map_err(|e| PrivateKeyError::Bech32Hrp(format!("{e}")))?;
313
314 let (hrp, data) = bech32::decode(value)
315 .map_err(|e| PrivateKeyError::Bech32(format!("decoding failed: {e}")))?;
316
317 if hrp != expected_hrp {
318 return Err(PrivateKeyError::Bech32Hrp(format!(
319 "expected {IOTA_PRIV_KEY_PREFIX}, got {hrp}"
320 )));
321 }
322
323 if data.is_empty() {
324 return Err(PrivateKeyError::EmptyData("bech32 data".to_string()));
325 }
326
327 Self::from_flagged_bytes(&data)
328 }
329}
330
331#[cfg(feature = "mnemonic")]
333pub trait FromMnemonic {
334 type Error;
335
336 fn from_mnemonic(
338 phrase: &str,
339 account_index: impl Into<Option<u64>>,
340 password: impl Into<Option<String>>,
341 ) -> Result<Self, Self::Error>
342 where
343 Self: Sized;
344
345 fn from_mnemonic_with_path(
350 phrase: &str,
351 path: String,
352 password: impl Into<Option<String>>,
353 ) -> Result<Self, Self::Error>
354 where
355 Self: Sized;
356}
357
358#[cfg(test)]
359mod tests {
360 use super::*;
361 use crate::{
362 ed25519::Ed25519PrivateKey, secp256k1::Secp256k1PrivateKey, secp256r1::Secp256r1PrivateKey,
363 };
364
365 #[cfg(feature = "mnemonic")]
366 #[test]
367 fn test_mnemonics_ed25519() {
368 const TEST_CASES: [[&str; 3]; 3] = [
369 [
370 "film crazy soon outside stand loop subway crumble thrive popular green nuclear struggle pistol arm wife phrase warfare march wheat nephew ask sunny firm",
371 "iotaprivkey1qrqqxhsu3ndp96644fjk4z5ams5ulgmvprklngt2jhvg2ujn5w4q2d2vplv",
372 "0x9f8e5379678525edf768d7b507dc1ba9016fc4f0eac976ab7f74077d95fba312",
373 ],
374 [
375 "require decline left thought grid priority false tiny gasp angle royal system attack beef setup reward aunt skill wasp tray vital bounce inflict level",
376 "iotaprivkey1qqcxaf57fnenvflpacacaumf6vl0rt0edddhytanvzhkqhwnjk0zspg902d",
377 "0x862738192e40540e0a5c9a5aca636f53b0cd76b0a9bef3386e05647feb4914ac",
378 ],
379 [
380 "organ crash swim stick traffic remember army arctic mesh slice swear summer police vast chaos cradle squirrel hood useless evidence pet hub soap lake",
381 "iotaprivkey1qzq39vxzm0gq7l8dc5dj5allpuww4mavhwhg8mua4cl3lj2c3fvhcv5l2vn",
382 "0x2391788ca49c7f0f00699bc2bad45f80c343b4d1df024285c132259433d7ff31",
383 ],
384 ];
385
386 for [mnemonic, bech32, address] in TEST_CASES {
387 let key = Ed25519PrivateKey::from_mnemonic(mnemonic, None, None).unwrap();
388 assert_eq!(key.to_bech32().unwrap(), bech32);
389 assert_eq!(key.public_key().derive_address().to_string(), address);
390 }
391 }
392
393 #[cfg(feature = "mnemonic")]
394 #[test]
395 fn test_mnemonics_secp256k1() {
396 const TEST_CASES: [[&str; 3]; 3] = [
397 [
398 "film crazy soon outside stand loop subway crumble thrive popular green nuclear struggle pistol arm wife phrase warfare march wheat nephew ask sunny firm",
399 "iotaprivkey1q8cy2ll8a0dmzzzwn9zavrug0qf47cyuj6k2r4r6rnjtpjhrdh52vpegd4f",
400 "0x8520d58dde1ab268349b9a46e5124ae6fe7e4c61df4ca2bc9c97d3c4d07b0b55",
401 ],
402 [
403 "require decline left thought grid priority false tiny gasp angle royal system attack beef setup reward aunt skill wasp tray vital bounce inflict level",
404 "iotaprivkey1q9hm330d05jcxfvmztv046p8kclyaj39hk6elqghgpq4sz4x23hk2wd6cfz",
405 "0x3740d570eefba29dfc0fdd5829848902064e31ecd059ca05c401907fa8646f61",
406 ],
407 [
408 "organ crash swim stick traffic remember army arctic mesh slice swear summer police vast chaos cradle squirrel hood useless evidence pet hub soap lake",
409 "iotaprivkey1qx2dnch6363h7gdqqfkzmmlequzj4ul3x4fq6dzyajk7wc2c0jgcx32axh5",
410 "0x943b852c37fef403047e06ff5a2fa216557a4386212fb29554babdd3e1899da5",
411 ],
412 ];
413
414 for [mnemonic, bech32, address] in TEST_CASES {
415 let key = Secp256k1PrivateKey::from_mnemonic(mnemonic, None, None).unwrap();
416 assert_eq!(key.to_bech32().unwrap(), bech32);
417 assert_eq!(key.public_key().derive_address().to_string(), address);
418 }
419 }
420
421 #[cfg(feature = "mnemonic")]
422 #[test]
423 fn test_mnemonics_secp256r1() {
424 const TEST_CASES: [[&str; 3]; 3] = [
425 [
426 "act wing dilemma glory episode region allow mad tourist humble muffin oblige",
427 "iotaprivkey1qtt65ua2lhal76zg4cxd6umdqynv2rj2gzrntp5rwlnyj370jg3pwtqlwdn",
428 "0x779a63b28528210a5ec6c4af5a70382fa3f0c2d3f98dcbe4e3a4ae2f8c39cc9c",
429 ],
430 [
431 "flag rebel cabbage captain minimum purpose long already valley horn enrich salt",
432 "iotaprivkey1qtcjgmue7q8u4gtutfvfpx3zj3aa2r9pqssuusrltxfv68eqhzsgjc3p4z7",
433 "0x8b45523042933aa55f57e2ccc661304baed292529b6e67a0c9857c1f3f871806",
434 ],
435 [
436 "area renew bar language pudding trial small host remind supreme cabbage era",
437 "iotaprivkey1qtxafg26qxeqy7f56gd2rvsup0a5kl4cre7nt2rtcrf0p3v5pwd4cgrrff2",
438 "0x8528ef86150ec331928a8b3edb8adbe2fb523db8c84679aa57a931da6a4cdb25",
439 ],
440 ];
441
442 for [mnemonic, bech32, address] in TEST_CASES {
443 let key = Secp256r1PrivateKey::from_mnemonic(mnemonic, None, None).unwrap();
444 assert_eq!(key.to_bech32().unwrap(), bech32);
445 assert_eq!(key.public_key().derive_address().to_string(), address);
446 }
447 }
448}