subxt_signer/
sr25519.rs

1// Copyright 2019-2025 Parity Technologies (UK) Ltd.
2// This file is dual-licensed as Apache-2.0 or GPL-3.0.
3// see LICENSE for license details.
4
5//! An sr25519 keypair implementation.
6//!
7//! **Note:** This implementation requires the `getrandom` dependency to obtain randomness,
8//! and will not compile on targets that it does not support. See the supported `getrandom`
9//! targets here: <https://docs.rs/getrandom/latest/getrandom/#supported-targets>.
10
11use 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
27/// Seed bytes used to generate a key pair.
28pub type SecretKeyBytes = [u8; SECRET_KEY_LENGTH];
29
30/// A signature generated by [`Keypair::sign()`]. These bytes are equivalent
31/// to a Substrate `MultiSignature::sr25519(bytes)`.
32#[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
41/// The public key for an [`Keypair`] key pair. This is equivalent to a
42/// Substrate `AccountId32`.
43pub struct PublicKey(pub [u8; 32]);
44
45impl AsRef<[u8]> for PublicKey {
46    fn as_ref(&self) -> &[u8] {
47        &self.0
48    }
49}
50
51/// An sr25519 keypair implementation. While the API is slightly different, the logic for
52/// this has been taken from `sp_core::sr25519` and we test against this to ensure conformity.
53#[derive(Debug, Clone)]
54pub struct Keypair(schnorrkel::Keypair);
55
56impl Keypair {
57    /// Create am sr25519 keypair from a [`SecretUri`]. See the [`SecretUri`] docs for more.
58    ///
59    /// # Example
60    ///
61    /// ```rust,standalone_crate
62    /// use subxt_signer::{ SecretUri, sr25519::Keypair };
63    /// use std::str::FromStr;
64    ///
65    /// let uri = SecretUri::from_str("//Alice").unwrap();
66    /// let keypair = Keypair::from_uri(&uri).unwrap();
67    ///
68    /// keypair.sign(b"Hello world!");
69    /// ```
70    pub fn from_uri(uri: &SecretUri) -> Result<Self, Error> {
71        let SecretUri {
72            junctions,
73            phrase,
74            password,
75        } = uri;
76
77        // If the phrase is hex, convert bytes directly into a seed, ignoring password.
78        // Else, parse the phrase string taking the password into account. This is
79        // the same approach taken in sp_core::crypto::Pair::from_string_with_seed.
80        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        // Now, use any "junctions" to derive a new key from this root key.
90        Ok(key.derive(junctions.iter().copied()))
91    }
92
93    /// Create am sr25519 keypair from a BIP-39 mnemonic phrase and optional password.
94    ///
95    /// # Example
96    ///
97    /// ```rust,standalone_crate
98    /// use subxt_signer::{ bip39::Mnemonic, sr25519::Keypair };
99    ///
100    /// let phrase = "bottom drive obey lake curtain smoke basket hold race lonely fit walk";
101    /// let mnemonic = Mnemonic::parse(phrase).unwrap();
102    /// let keypair = Keypair::from_phrase(&mnemonic, None).unwrap();
103    ///
104    /// keypair.sign(b"Hello world!");
105    /// ```
106    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    /// Turn a 32 byte secret key into a keypair.
119    ///
120    /// # Warning
121    ///
122    /// This will only be secure if the seed is secure!
123    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    /// Construct a keypair from a slice of bytes, corresponding to
132    /// an Ed25519 expanded secret key.
133    #[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    /// Derive a child key from this one given a series of junctions.
144    ///
145    /// # Example
146    ///
147    /// ```rust,standalone_crate
148    /// use subxt_signer::{ bip39::Mnemonic, sr25519::Keypair, DeriveJunction };
149    ///
150    /// let phrase = "bottom drive obey lake curtain smoke basket hold race lonely fit walk";
151    /// let mnemonic = Mnemonic::parse(phrase).unwrap();
152    /// let keypair = Keypair::from_phrase(&mnemonic, None).unwrap();
153    ///
154    /// // Equivalent to the URI path '//Alice/stash':
155    /// let new_keypair = keypair.derive([
156    ///     DeriveJunction::hard("Alice"),
157    ///     DeriveJunction::soft("stash")
158    /// ]);
159    /// ```
160    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    /// Obtain the [`PublicKey`] part of this key pair, which can be used in calls to [`verify()`].
173    /// or otherwise converted into an address. The public key bytes are equivalent to a Substrate
174    /// `AccountId32`.
175    pub fn public_key(&self) -> PublicKey {
176        PublicKey(self.0.public.to_bytes())
177    }
178
179    /// Sign some message. These bytes can be used directly in a Substrate `MultiSignature::sr25519(..)`.
180    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
187/// Verify that some signature for a message was created by the owner of the [`PublicKey`].
188///
189/// ```rust,standalone_crate
190/// use subxt_signer::{ bip39::Mnemonic, sr25519 };
191///
192/// let keypair = sr25519::dev::alice();
193/// let message = b"Hello!";
194///
195/// let signature = keypair.sign(message);
196/// let public_key = keypair.public_key();
197/// assert!(sr25519::verify(&signature, message, &public_key));
198/// ```
199pub 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/// An error handed back if creating a keypair fails.
212#[derive(Debug, DeriveError)]
213pub enum Error {
214    /// Invalid seed.
215    #[error("Invalid seed (was it the wrong length?)")]
216    InvalidSeed,
217    /// Invalid phrase.
218    #[error("Cannot parse phrase: {0}")]
219    Phrase(bip39::Error),
220    /// Invalid hex.
221    #[error("Cannot parse hex string: {0}")]
222    Hex(hex::FromHexError),
223    /// Signature error.
224    #[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
246/// Dev accounts, helpful for testing but not to be used in production,
247/// since the secret keys are known.
248pub mod dev {
249    use super::*;
250
251    once_static_cloned! {
252        /// Equivalent to `{DEV_PHRASE}//Alice`.
253        pub fn alice() -> Keypair {
254            Keypair::from_uri(&SecretUri::from_str("//Alice").unwrap()).unwrap()
255        }
256        /// Equivalent to `{DEV_PHRASE}//Bob`.
257        pub fn bob() -> Keypair {
258            Keypair::from_uri(&SecretUri::from_str("//Bob").unwrap()).unwrap()
259        }
260        /// Equivalent to `{DEV_PHRASE}//Charlie`.
261        pub fn charlie() -> Keypair {
262            Keypair::from_uri(&SecretUri::from_str("//Charlie").unwrap()).unwrap()
263        }
264        /// Equivalent to `{DEV_PHRASE}//Dave`.
265        pub fn dave() -> Keypair {
266            Keypair::from_uri(&SecretUri::from_str("//Dave").unwrap()).unwrap()
267        }
268        /// Equivalent to `{DEV_PHRASE}//Eve`.
269        pub fn eve() -> Keypair {
270            Keypair::from_uri(&SecretUri::from_str("//Eve").unwrap()).unwrap()
271        }
272        /// Equivalent to `{DEV_PHRASE}//Ferdie`.
273        pub fn ferdie() -> Keypair {
274            Keypair::from_uri(&SecretUri::from_str("//Ferdie").unwrap()).unwrap()
275        }
276        /// Equivalent to `{DEV_PHRASE}//One`.
277        pub fn one() -> Keypair {
278            Keypair::from_uri(&SecretUri::from_str("//One").unwrap()).unwrap()
279        }
280        /// Equivalent to `{DEV_PHRASE}//Two`.
281        pub fn two() -> Keypair {
282            Keypair::from_uri(&SecretUri::from_str("//Two").unwrap()).unwrap()
283        }
284    }
285}
286
287// Make `Keypair` usable to sign transactions in Subxt. This is optional so that
288// `subxt-signer` can be used entirely independently of Subxt.
289#[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        /// A shortcut to obtain an [`AccountId32`] from a [`PublicKey`].
318        /// We often want this type, and using this method avoids any
319        /// ambiguous type resolution issues.
320        pub fn to_account_id(self) -> AccountId32 {
321            AccountId32(self.0)
322        }
323        /// A shortcut to obtain a [`MultiAddress`] from a [`PublicKey`].
324        /// We often want this type, and using this method avoids any
325        /// ambiguous type resolution issues.
326        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        // Some derive junctions to check that the logic there aligns:
380        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                // Build an sp_core::Pair that includes a phrase, path and password:
394                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                // Now build a local Keypair using the equivalent API:
400                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                // They should match:
404                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        // Hex URIs seem to ignore the password on sp_core and here. Check that this is consistent.
448        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}