1use core::str::FromStr;
12
13use crate::crypto::{DeriveJunction, SecretUri, seed_from_entropy};
14
15use hex::FromHex;
16use schnorrkel::{
17 ExpansionMode, MiniSecretKey,
18 derive::{ChainCode, Derivation},
19};
20use secrecy::ExposeSecret;
21
22use thiserror::Error as DeriveError;
23
24const SECRET_KEY_LENGTH: usize = schnorrkel::keys::MINI_SECRET_KEY_LENGTH;
25const SIGNING_CTX: &[u8] = b"substrate";
26
27pub type SecretKeyBytes = [u8; SECRET_KEY_LENGTH];
29
30#[derive(Clone, Copy, PartialEq, Eq)]
33pub struct Signature(pub [u8; 64]);
34
35impl AsRef<[u8]> for Signature {
36 fn as_ref(&self) -> &[u8] {
37 &self.0
38 }
39}
40
41pub struct PublicKey(pub [u8; 32]);
44
45impl AsRef<[u8]> for PublicKey {
46 fn as_ref(&self) -> &[u8] {
47 &self.0
48 }
49}
50
51#[derive(Debug, Clone)]
54pub struct Keypair(schnorrkel::Keypair);
55
56impl Keypair {
57 pub fn from_uri(uri: &SecretUri) -> Result<Self, Error> {
71 let SecretUri {
72 junctions,
73 phrase,
74 password,
75 } = uri;
76
77 let key = if let Some(hex_str) = phrase.expose_secret().strip_prefix("0x") {
81 let seed = SecretKeyBytes::from_hex(hex_str)?;
82 Self::from_secret_key(seed)?
83 } else {
84 let phrase = bip39::Mnemonic::from_str(phrase.expose_secret())?;
85 let pass_str = password.as_ref().map(|p| p.expose_secret());
86 Self::from_phrase(&phrase, pass_str)?
87 };
88
89 Ok(key.derive(junctions.iter().copied()))
91 }
92
93 pub fn from_phrase(mnemonic: &bip39::Mnemonic, password: Option<&str>) -> Result<Self, Error> {
107 let (arr, len) = mnemonic.to_entropy_array();
108 let big_seed =
109 seed_from_entropy(&arr[0..len], password.unwrap_or("")).ok_or(Error::InvalidSeed)?;
110
111 let seed: SecretKeyBytes = big_seed[..SECRET_KEY_LENGTH]
112 .try_into()
113 .expect("should be valid Seed");
114
115 Self::from_secret_key(seed)
116 }
117
118 pub fn from_secret_key(secret_key_bytes: SecretKeyBytes) -> Result<Self, Error> {
124 let keypair = MiniSecretKey::from_bytes(&secret_key_bytes)
125 .map_err(|_| Error::InvalidSeed)?
126 .expand_to_keypair(ExpansionMode::Ed25519);
127
128 Ok(Keypair(keypair))
129 }
130
131 #[cfg(feature = "polkadot-js-compat")]
134 pub(crate) fn from_ed25519_bytes(bytes: &[u8]) -> Result<Self, Error> {
135 let secret_key = schnorrkel::SecretKey::from_ed25519_bytes(bytes)?;
136
137 Ok(Keypair(schnorrkel::Keypair {
138 public: secret_key.to_public(),
139 secret: secret_key,
140 }))
141 }
142
143 pub fn derive<Js: IntoIterator<Item = DeriveJunction>>(&self, junctions: Js) -> Self {
161 let init = self.0.secret.clone();
162 let result = junctions.into_iter().fold(init, |acc, j| match j {
163 DeriveJunction::Soft(cc) => acc.derived_key_simple(ChainCode(cc), []).0,
164 DeriveJunction::Hard(cc) => {
165 let seed = acc.hard_derive_mini_secret_key(Some(ChainCode(cc)), b"").0;
166 seed.expand(ExpansionMode::Ed25519)
167 }
168 });
169 Self(result.into())
170 }
171
172 pub fn public_key(&self) -> PublicKey {
176 PublicKey(self.0.public.to_bytes())
177 }
178
179 pub fn sign(&self, message: &[u8]) -> Signature {
181 let context = schnorrkel::signing_context(SIGNING_CTX);
182 let signature = self.0.sign(context.bytes(message));
183 Signature(signature.to_bytes())
184 }
185}
186
187pub fn verify<M: AsRef<[u8]>>(sig: &Signature, message: M, pubkey: &PublicKey) -> bool {
200 let Ok(signature) = schnorrkel::Signature::from_bytes(&sig.0) else {
201 return false;
202 };
203 let Ok(public) = schnorrkel::PublicKey::from_bytes(&pubkey.0) else {
204 return false;
205 };
206 public
207 .verify_simple(SIGNING_CTX, message.as_ref(), &signature)
208 .is_ok()
209}
210
211#[derive(Debug, DeriveError)]
213pub enum Error {
214 #[error("Invalid seed (was it the wrong length?)")]
216 InvalidSeed,
217 #[error("Cannot parse phrase: {0}")]
219 Phrase(bip39::Error),
220 #[error("Cannot parse hex string: {0}")]
222 Hex(hex::FromHexError),
223 #[error("Signature error: {0}")]
225 Signature(schnorrkel::SignatureError),
226}
227
228impl From<schnorrkel::SignatureError> for Error {
229 fn from(value: schnorrkel::SignatureError) -> Self {
230 Error::Signature(value)
231 }
232}
233
234impl From<hex::FromHexError> for Error {
235 fn from(err: hex::FromHexError) -> Self {
236 Error::Hex(err)
237 }
238}
239
240impl From<bip39::Error> for Error {
241 fn from(err: bip39::Error) -> Self {
242 Error::Phrase(err)
243 }
244}
245
246pub mod dev {
249 use super::*;
250
251 once_static_cloned! {
252 pub fn alice() -> Keypair {
254 Keypair::from_uri(&SecretUri::from_str("//Alice").unwrap()).unwrap()
255 }
256 pub fn bob() -> Keypair {
258 Keypair::from_uri(&SecretUri::from_str("//Bob").unwrap()).unwrap()
259 }
260 pub fn charlie() -> Keypair {
262 Keypair::from_uri(&SecretUri::from_str("//Charlie").unwrap()).unwrap()
263 }
264 pub fn dave() -> Keypair {
266 Keypair::from_uri(&SecretUri::from_str("//Dave").unwrap()).unwrap()
267 }
268 pub fn eve() -> Keypair {
270 Keypair::from_uri(&SecretUri::from_str("//Eve").unwrap()).unwrap()
271 }
272 pub fn ferdie() -> Keypair {
274 Keypair::from_uri(&SecretUri::from_str("//Ferdie").unwrap()).unwrap()
275 }
276 pub fn one() -> Keypair {
278 Keypair::from_uri(&SecretUri::from_str("//One").unwrap()).unwrap()
279 }
280 pub fn two() -> Keypair {
282 Keypair::from_uri(&SecretUri::from_str("//Two").unwrap()).unwrap()
283 }
284 }
285}
286
287#[cfg(feature = "subxt")]
290#[cfg_attr(docsrs, doc(cfg(feature = "subxt")))]
291mod subxt_compat {
292 use super::*;
293
294 use subxt_core::{
295 Config,
296 tx::signer::Signer as SignerT,
297 utils::{AccountId32, MultiAddress, MultiSignature},
298 };
299
300 impl From<Signature> for MultiSignature {
301 fn from(value: Signature) -> Self {
302 MultiSignature::Sr25519(value.0)
303 }
304 }
305 impl From<PublicKey> for AccountId32 {
306 fn from(value: PublicKey) -> Self {
307 value.to_account_id()
308 }
309 }
310 impl<T> From<PublicKey> for MultiAddress<AccountId32, T> {
311 fn from(value: PublicKey) -> Self {
312 value.to_address()
313 }
314 }
315
316 impl PublicKey {
317 pub fn to_account_id(self) -> AccountId32 {
321 AccountId32(self.0)
322 }
323 pub fn to_address<T>(self) -> MultiAddress<AccountId32, T> {
327 MultiAddress::Id(self.to_account_id())
328 }
329 }
330
331 impl<T: Config> SignerT<T> for Keypair
332 where
333 T::AccountId: From<PublicKey>,
334 T::Address: From<PublicKey>,
335 T::Signature: From<Signature>,
336 {
337 fn account_id(&self) -> T::AccountId {
338 self.public_key().into()
339 }
340
341 fn sign(&self, signer_payload: &[u8]) -> T::Signature {
342 self.sign(signer_payload).into()
343 }
344 }
345}
346
347#[cfg(test)]
348mod test {
349 use std::str::FromStr;
350
351 use super::*;
352
353 use sp_core::{self, crypto::Pair as _, sr25519::Pair as SpPair};
354
355 #[test]
356 fn check_from_phrase_matches() {
357 for _ in 0..20 {
358 let (sp_pair, phrase, _seed) = SpPair::generate_with_phrase(None);
359 let phrase = bip39::Mnemonic::parse(phrase).expect("valid phrase expected");
360 let pair = Keypair::from_phrase(&phrase, None).expect("should be valid");
361
362 assert_eq!(sp_pair.public().0, pair.public_key().0);
363 }
364 }
365
366 #[test]
367 fn check_from_phrase_with_password_matches() {
368 for _ in 0..20 {
369 let (sp_pair, phrase, _seed) = SpPair::generate_with_phrase(Some("Testing"));
370 let phrase = bip39::Mnemonic::parse(phrase).expect("valid phrase expected");
371 let pair = Keypair::from_phrase(&phrase, Some("Testing")).expect("should be valid");
372
373 assert_eq!(sp_pair.public().0, pair.public_key().0);
374 }
375 }
376
377 #[test]
378 fn check_from_secret_uri_matches() {
379 let uri_paths = [
381 "/foo",
382 "//bar",
383 "/1",
384 "/0001",
385 "//1",
386 "//0001",
387 "//foo//bar/wibble",
388 "//foo//001/wibble",
389 ];
390
391 for i in 0..2 {
392 for path in &uri_paths {
393 let password = format!("Testing{i}");
395 let (_sp_pair, phrase, _seed) = SpPair::generate_with_phrase(Some(&password));
396 let uri = format!("{phrase}{path}///{password}");
397 let sp_pair = SpPair::from_string(&uri, None).expect("should be valid");
398
399 let uri = SecretUri::from_str(&uri).expect("should be valid secret URI");
401 let pair = Keypair::from_uri(&uri).expect("should be valid");
402
403 assert_eq!(sp_pair.public().0, pair.public_key().0);
405 }
406 }
407 }
408
409 #[test]
410 fn check_dev_accounts_match() {
411 use sp_keyring::sr25519::Keyring::*;
412
413 assert_eq!(dev::alice().public_key().0, Alice.public().0);
414 assert_eq!(dev::bob().public_key().0, Bob.public().0);
415 assert_eq!(dev::charlie().public_key().0, Charlie.public().0);
416 assert_eq!(dev::dave().public_key().0, Dave.public().0);
417 assert_eq!(dev::eve().public_key().0, Eve.public().0);
418 assert_eq!(dev::ferdie().public_key().0, Ferdie.public().0);
419 assert_eq!(dev::one().public_key().0, One.public().0);
420 assert_eq!(dev::two().public_key().0, Two.public().0);
421 }
422
423 #[test]
424 fn check_signing_and_verifying_matches() {
425 use sp_core::sr25519::Signature as SpSignature;
426
427 for _ in 0..20 {
428 let (sp_pair, phrase, _seed) = SpPair::generate_with_phrase(Some("Testing"));
429 let phrase = bip39::Mnemonic::parse(phrase).expect("valid phrase expected");
430 let pair = Keypair::from_phrase(&phrase, Some("Testing")).expect("should be valid");
431
432 let message = b"Hello world";
433 let sp_sig = sp_pair.sign(message).0;
434 let sig = pair.sign(message).0;
435
436 assert!(SpPair::verify(
437 &SpSignature::from(sig),
438 message,
439 &sp_pair.public()
440 ));
441 assert!(verify(&Signature(sp_sig), message, &pair.public_key()));
442 }
443 }
444
445 #[test]
446 fn check_hex_uris() {
447 let uri_str =
449 "0x1122334455667788112233445566778811223344556677881122334455667788///SomePassword";
450
451 let uri = SecretUri::from_str(uri_str).expect("should be valid");
452 let pair = Keypair::from_uri(&uri).expect("should be valid");
453 let sp_pair = SpPair::from_string(uri_str, None).expect("should be valid");
454
455 assert_eq!(pair.public_key().0, sp_pair.public().0);
456 }
457}