oasis_runtime_sdk/crypto/signature/
sr25519.rs

1//! Sr25519 signatures.
2use base64::prelude::*;
3use rand_core::{CryptoRng, RngCore};
4use schnorrkel::{self, context::SigningTranscript};
5use sha2::{Digest, Sha512_256};
6
7use crate::crypto::signature::{Error, Signature, Signer};
8
9/// A Sr25519 public key.
10#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, cbor::Encode, cbor::Decode)]
11#[cbor(transparent, no_default)]
12pub struct PublicKey(Vec<u8>);
13
14impl PublicKey {
15    /// Return a byte representation of this public key.
16    pub fn as_bytes(&self) -> &[u8] {
17        // schnorrkel::keys::PublicKey only has to_bytes, which
18        // returns a new array.
19        //
20        // Since we need to return a reference the easiest way to
21        // placate the borrow-checker involves just keeping the
22        // byte-serialized form of the public key instead of the
23        // decompressed one, and doing point-decompression each
24        // time we want to actually do something useful.
25        &self.0
26    }
27
28    /// Construct a public key from a slice of bytes.
29    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
30        // Ensure the bytes represents a valid public key.
31        PublicKey::decompress_public_key(bytes)?;
32        Ok(PublicKey(bytes.to_vec()))
33    }
34
35    /// Verify a signature used in Oasis SDK transactions.
36    pub fn verify(
37        &self,
38        context: &[u8],
39        message: &[u8],
40        signature: &Signature,
41    ) -> Result<(), Error> {
42        // Convert the context to a Sr25519 SigningContext.
43        let context = schnorrkel::context::SigningContext::new(context);
44
45        // Generate a SigningTranscript from the context, and a pre-hash
46        // of the message.
47        //
48        // Note: This requires using Sha512_256 instead of our hash,
49        // due to the need for FixedOutput.
50        let mut digest = Sha512_256::new();
51        digest.update(message);
52        let transcript = context.hash256(digest);
53
54        self.verify_transcript(transcript, signature)
55    }
56
57    /// Verify a signature.
58    pub fn verify_raw(
59        &self,
60        context: &[u8],
61        message: &[u8],
62        signature: &Signature,
63    ) -> Result<(), Error> {
64        // Convert the context to a Sr25519 SigningContext.
65        let context = schnorrkel::signing_context(context);
66        let transcript = context.bytes(message);
67        self.verify_transcript(transcript, signature)
68    }
69
70    /// Verify a signature using the given transcript.
71    pub fn verify_transcript<T: SigningTranscript>(
72        &self,
73        transcript: T,
74        signature: &Signature,
75    ) -> Result<(), Error> {
76        let public_key = PublicKey::decompress_public_key(&self.0)?;
77
78        let signature = schnorrkel::Signature::from_bytes(signature.as_ref())
79            .map_err(|_| Error::MalformedSignature)?;
80
81        public_key
82            .verify(transcript, &signature)
83            .map_err(|_| Error::VerificationFailed)
84    }
85
86    fn decompress_public_key(bytes: &[u8]) -> Result<schnorrkel::PublicKey, Error> {
87        schnorrkel::PublicKey::from_bytes(bytes).map_err(|_| Error::MalformedPublicKey)
88    }
89}
90
91impl From<&'static str> for PublicKey {
92    fn from(s: &'static str) -> PublicKey {
93        PublicKey::from_bytes(&BASE64_STANDARD.decode(s).unwrap()).unwrap()
94    }
95}
96
97/// A memory-backed signer for Sr25519.
98pub struct MemorySigner {
99    keypair: schnorrkel::Keypair,
100}
101
102impl Signer for MemorySigner {
103    fn random(rng: &mut (impl RngCore + CryptoRng)) -> Result<Self, Error> {
104        Ok(Self {
105            keypair: schnorrkel::Keypair::generate_with(rng),
106        })
107    }
108
109    fn new_from_seed(seed: &[u8]) -> Result<Self, Error> {
110        let msk =
111            schnorrkel::MiniSecretKey::from_bytes(seed).map_err(|_| Error::MalformedPrivateKey)?;
112        let keypair = msk.expand_to_keypair(schnorrkel::ExpansionMode::Ed25519);
113        Ok(Self { keypair })
114    }
115
116    fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
117        Ok(Self {
118            keypair: schnorrkel::Keypair::from_half_ed25519_bytes(bytes)
119                .map_err(|_| Error::MalformedPrivateKey)?,
120        })
121    }
122
123    fn to_bytes(&self) -> Vec<u8> {
124        self.keypair.to_half_ed25519_bytes().to_vec()
125    }
126
127    fn public_key(&self) -> super::PublicKey {
128        super::PublicKey::Sr25519(PublicKey(self.keypair.public.to_bytes().to_vec()))
129    }
130
131    fn sign(&self, context: &[u8], message: &[u8]) -> Result<Signature, Error> {
132        let sig = self.keypair.sign_simple(context, message);
133        Ok(Signature(sig.to_bytes().to_vec()))
134    }
135
136    fn sign_raw(&self, _message: &[u8]) -> Result<Signature, Error> {
137        // Sr25519 requires the use of domain separation context.
138        Err(Error::InvalidArgument)
139    }
140}
141
142#[cfg(test)]
143mod test {
144    use super::*;
145
146    #[test]
147    fn test_polkadot_vectors() {
148        // Test vectors taken from https://github.com/polkadot-js/wasm/blob/10010830094e7d033bd11b16c5e3bc01a7045309/packages/wasm-crypto/src/rs/sr25519.rs.
149        let seed = hex::decode("fac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e")
150            .unwrap();
151        let signer = MemorySigner::new_from_seed(&seed).unwrap();
152        assert_eq!(
153            hex::encode(signer.public_key().as_bytes()),
154            "46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a",
155        );
156
157        let raw = hex::decode("28b0ae221c6bb06856b287f60d7ea0d98552ea5a16db16956849aa371db3eb51fd190cce74df356432b410bd64682309d6dedb27c76845daf388557cbac3ca3446ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a").unwrap();
158        let signer = MemorySigner::from_bytes(&raw).unwrap();
159        assert_eq!(
160            hex::encode(signer.public_key().as_bytes()),
161            "46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a",
162        );
163
164        // Should verify.
165        let msg =
166            b"I hereby verify that I control 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY";
167        let signature = hex::decode("1037eb7e51613d0dcf5930ae518819c87d655056605764840d9280984e1b7063c4566b55bf292fcab07b369d01095879b50517beca4d26e6a65866e25fec0d83").unwrap();
168        let pk = hex::decode("d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d")
169            .unwrap();
170        let pk = PublicKey::from_bytes(&pk).unwrap();
171        pk.verify_raw(b"substrate", msg, &signature.into()).unwrap();
172
173        // Should verify "wrapped".
174        let msg = b"<Bytes>message to sign</Bytes>";
175        let signature = hex::decode("48ce2c90e08651adfc8ecef84e916f6d1bb51ebebd16150ee12df247841a5437951ea0f9d632ca165e6ab391532e75e701be6a1caa88c8a6bcca3511f55b4183").unwrap();
176        let pk = hex::decode("f84d048da2ddae2d9d8fd6763f469566e8817a26114f39408de15547f6d47805")
177            .unwrap();
178        let pk = PublicKey::from_bytes(&pk).unwrap();
179        pk.verify_raw(b"substrate", msg, &signature.clone().into())
180            .unwrap();
181
182        // Should fail on "unwrapped" message.
183        let msg = b"message to sign";
184        pk.verify_raw(b"substrate", msg, &signature.into())
185            .unwrap_err();
186    }
187}