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")]
290mod subxt_compat {
291 use super::*;
292 use subxt::Config;
293 use subxt::transactions::Signer as SignerT;
294 use subxt::utils::{AccountId32, MultiAddress, MultiSignature};
295
296 impl From<Signature> for MultiSignature {
297 fn from(value: Signature) -> Self {
298 MultiSignature::Sr25519(value.0)
299 }
300 }
301 impl From<PublicKey> for AccountId32 {
302 fn from(value: PublicKey) -> Self {
303 value.to_account_id()
304 }
305 }
306 impl<T> From<PublicKey> for MultiAddress<AccountId32, T> {
307 fn from(value: PublicKey) -> Self {
308 value.to_address()
309 }
310 }
311
312 impl PublicKey {
313 pub fn to_account_id(self) -> AccountId32 {
317 AccountId32(self.0)
318 }
319 pub fn to_address<T>(self) -> MultiAddress<AccountId32, T> {
323 MultiAddress::Id(self.to_account_id())
324 }
325 }
326
327 impl<T: Config> SignerT<T> for Keypair
328 where
329 T::AccountId: From<PublicKey>,
330 T::Address: From<PublicKey>,
331 T::Signature: From<Signature>,
332 {
333 fn account_id(&self) -> T::AccountId {
334 self.public_key().into()
335 }
336
337 fn sign(&self, signer_payload: &[u8]) -> T::Signature {
338 self.sign(signer_payload).into()
339 }
340 }
341}
342
343#[cfg(test)]
344mod test {
345 use std::str::FromStr;
346
347 use super::*;
348
349 use sp_core::{self, crypto::Pair as _, sr25519::Pair as SpPair};
350
351 #[test]
352 fn check_from_phrase_matches() {
353 for _ in 0..20 {
354 let (sp_pair, phrase, _seed) = SpPair::generate_with_phrase(None);
355 let phrase = bip39::Mnemonic::parse(phrase).expect("valid phrase expected");
356 let pair = Keypair::from_phrase(&phrase, None).expect("should be valid");
357
358 assert_eq!(sp_pair.public().0, pair.public_key().0);
359 }
360 }
361
362 #[test]
363 fn check_from_phrase_with_password_matches() {
364 for _ in 0..20 {
365 let (sp_pair, phrase, _seed) = SpPair::generate_with_phrase(Some("Testing"));
366 let phrase = bip39::Mnemonic::parse(phrase).expect("valid phrase expected");
367 let pair = Keypair::from_phrase(&phrase, Some("Testing")).expect("should be valid");
368
369 assert_eq!(sp_pair.public().0, pair.public_key().0);
370 }
371 }
372
373 #[test]
374 fn check_from_secret_uri_matches() {
375 let uri_paths = [
377 "/foo",
378 "//bar",
379 "/1",
380 "/0001",
381 "//1",
382 "//0001",
383 "//foo//bar/wibble",
384 "//foo//001/wibble",
385 ];
386
387 for i in 0..2 {
388 for path in &uri_paths {
389 let password = format!("Testing{i}");
391 let (_sp_pair, phrase, _seed) = SpPair::generate_with_phrase(Some(&password));
392 let uri = format!("{phrase}{path}///{password}");
393 let sp_pair = SpPair::from_string(&uri, None).expect("should be valid");
394
395 let uri = SecretUri::from_str(&uri).expect("should be valid secret URI");
397 let pair = Keypair::from_uri(&uri).expect("should be valid");
398
399 assert_eq!(sp_pair.public().0, pair.public_key().0);
401 }
402 }
403 }
404
405 #[test]
406 fn check_dev_accounts_match() {
407 use sp_keyring::sr25519::Keyring::*;
408
409 assert_eq!(dev::alice().public_key().0, Alice.public().0);
410 assert_eq!(dev::bob().public_key().0, Bob.public().0);
411 assert_eq!(dev::charlie().public_key().0, Charlie.public().0);
412 assert_eq!(dev::dave().public_key().0, Dave.public().0);
413 assert_eq!(dev::eve().public_key().0, Eve.public().0);
414 assert_eq!(dev::ferdie().public_key().0, Ferdie.public().0);
415 assert_eq!(dev::one().public_key().0, One.public().0);
416 assert_eq!(dev::two().public_key().0, Two.public().0);
417 }
418
419 #[test]
420 fn check_signing_and_verifying_matches() {
421 use sp_core::sr25519::Signature as SpSignature;
422
423 for _ in 0..20 {
424 let (sp_pair, phrase, _seed) = SpPair::generate_with_phrase(Some("Testing"));
425 let phrase = bip39::Mnemonic::parse(phrase).expect("valid phrase expected");
426 let pair = Keypair::from_phrase(&phrase, Some("Testing")).expect("should be valid");
427
428 let message = b"Hello world";
429 let sp_sig = sp_pair.sign(message).0;
430 let sig = pair.sign(message).0;
431
432 assert!(SpPair::verify(
433 &SpSignature::from(sig),
434 message,
435 &sp_pair.public()
436 ));
437 assert!(verify(&Signature(sp_sig), message, &pair.public_key()));
438 }
439 }
440
441 #[test]
442 fn check_hex_uris() {
443 let uri_str =
445 "0x1122334455667788112233445566778811223344556677881122334455667788///SomePassword";
446
447 let uri = SecretUri::from_str(uri_str).expect("should be valid");
448 let pair = Keypair::from_uri(&uri).expect("should be valid");
449 let sp_pair = SpPair::from_string(uri_str, None).expect("should be valid");
450
451 assert_eq!(pair.public_key().0, sp_pair.public().0);
452 }
453}