Skip to main content

oasis_core_runtime/common/crypto/
signature.rs

1//! Signature types.
2use std::{cmp::Ordering, convert::TryInto, io::Cursor};
3
4use anyhow::Result;
5use byteorder::{LittleEndian, ReadBytesExt};
6use curve25519_dalek::{
7    edwards::{CompressedEdwardsY, EdwardsPoint},
8    scalar::Scalar,
9};
10use ed25519_dalek::{Digest as _, Sha512, Signer as _};
11use rand::{rand_core::UnwrapErr, rngs::SysRng};
12use thiserror::Error;
13use zeroize::Zeroize;
14
15use crate::common::namespace::Namespace;
16
17use super::hash::Hash;
18
19/// The chain separator used to add additional domain separation based on the chain context.
20const CHAIN_SIGNATURE_CONTEXT_SEPARATOR: &[u8] = b" for chain ";
21/// The runtime separator used to add additional domain separation based on the runtime ID.
22const RUNTIME_SIGNATURE_CONTEXT_SEPARATOR: &[u8] = b" for runtime ";
23
24impl_bytes!(
25    PublicKey,
26    ed25519_dalek::PUBLIC_KEY_LENGTH,
27    "An Ed25519 public key."
28);
29
30/// Signature error.
31#[derive(Error, Debug)]
32enum SignatureError {
33    #[error("point decompression failed")]
34    PointDecompression,
35    #[error("small order A")]
36    SmallOrderA,
37    #[error("small order R")]
38    SmallOrderR,
39    #[error("signature malleability check failed")]
40    Malleability,
41    #[error("invalid signature")]
42    InvalidSignature,
43}
44
45static CURVE_ORDER: &[u64] = &[
46    0x1000000000000000,
47    0,
48    0x14def9dea2f79cd6,
49    0x5812631a5cf5d3ed,
50];
51
52/// An Ed25519 private key.
53pub struct PrivateKey(pub ed25519_dalek::SigningKey);
54
55impl PrivateKey {
56    /// Generates a new private key pair.
57    pub fn generate() -> Self {
58        let mut rng = UnwrapErr(SysRng);
59        PrivateKey(ed25519_dalek::SigningKey::generate(&mut rng))
60    }
61
62    /// Convert this private key into bytes.
63    pub fn to_bytes(&self) -> Vec<u8> {
64        let mut bytes = self.0.to_bytes();
65        let bvec = bytes.to_vec();
66        bytes.zeroize();
67        bvec
68    }
69
70    /// Construct a private key from bytes returned by `to_bytes`.
71    ///
72    /// # Panics
73    ///
74    /// This method will panic in case the passed bytes do not have the correct length.
75    pub fn from_bytes(bytes: Vec<u8>) -> PrivateKey {
76        let mut sk = bytes.try_into().unwrap();
77        let secret = ed25519_dalek::SigningKey::from_bytes(&sk);
78        sk.zeroize();
79
80        PrivateKey(secret)
81    }
82
83    /// Generate a new private key from a test key seed.
84    pub fn from_test_seed(seed: String) -> Self {
85        let mut seed = Hash::digest_bytes(seed.as_bytes());
86        let sk = Self::from_bytes(seed.as_ref().to_vec());
87        seed.zeroize();
88
89        sk
90    }
91
92    /// Returns the public key.
93    pub fn public_key(&self) -> PublicKey {
94        PublicKey(self.0.verifying_key().to_bytes())
95    }
96}
97
98impl Signer for PrivateKey {
99    fn public(&self) -> PublicKey {
100        self.public_key()
101    }
102
103    fn sign(&self, context: &[u8], message: &[u8]) -> Result<Signature> {
104        // TODO/#2103: Replace this with Ed25519ctx.
105        let digest = Hash::digest_bytes_list(&[context, message]);
106
107        Ok(Signature(self.0.sign(digest.as_ref()).to_bytes()))
108    }
109}
110
111impl_bytes!(Signature, 64, "An Ed25519 signature.");
112
113impl Signature {
114    /// Verify signature.
115    pub fn verify(&self, pk: &PublicKey, context: &[u8], message: &[u8]) -> Result<()> {
116        // Apply the Oasis core specific domain separation.
117        //
118        // Note: This should be Ed25519ctx based but "muh Ledger".
119        let digest = Hash::digest_bytes_list(&[context, message]);
120
121        self.verify_raw(pk, digest.as_ref())
122    }
123
124    /// Verify signature without applying domain separation.
125    #[allow(non_snake_case)] // Variable names matching RFC 8032 is more readable.
126    pub fn verify_raw(&self, pk: &PublicKey, msg: &[u8]) -> Result<()> {
127        // We have a very specific idea of what a valid Ed25519 signature
128        // is, that is different from what ed25519-dalek defines, so this
129        // needs to be done by hand.
130
131        // Decompress A (PublicKey)
132        //
133        // TODO/perf:
134        //  * PublicKey could just be an EdwardsPoint.
135        //  * Could cache the results of is_small_order() in PublicKey.
136        let A = CompressedEdwardsY::from_slice(pk.as_ref())
137            .map_err(|_| SignatureError::PointDecompression)?;
138        let A = A.decompress().ok_or(SignatureError::PointDecompression)?;
139        if A.is_small_order() {
140            return Err(SignatureError::SmallOrderA.into());
141        }
142
143        // Decompress R (signature point), S (signature scalar).
144        //
145        // Note:
146        //  * Reject S > L, small order A/R
147        //  * Accept non-canonical A/R
148        let sig_slice = self.as_ref();
149        let R_bits = &sig_slice[..32];
150        let S_bits = &sig_slice[32..];
151
152        let R = CompressedEdwardsY::from_slice(R_bits)
153            .map_err(|_| SignatureError::PointDecompression)?;
154        let R = R.decompress().ok_or(SignatureError::PointDecompression)?;
155        if R.is_small_order() {
156            return Err(SignatureError::SmallOrderR.into());
157        }
158
159        if !sc_minimal(S_bits) {
160            return Err(SignatureError::Malleability.into());
161        }
162        let mut S: [u8; 32] = [0u8; 32];
163        S.copy_from_slice(S_bits);
164        #[allow(deprecated)] // S is only used for vartime_double_scalar_mul_basepoint.
165        let S = Scalar::from_bits(S);
166
167        // k = H(R,A,m)
168        let mut k: Sha512 = Sha512::new();
169        k.update(R_bits);
170        k.update(pk.as_ref());
171        k.update(msg);
172        let k = Scalar::from_hash(k);
173
174        // Check the cofactored group equation ([8][S]B = [8]R + [8][k]A').
175        let neg_A = -A;
176        let should_be_small_order =
177            EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &neg_A, &S) - R;
178        match should_be_small_order.is_small_order() {
179            true => Ok(()),
180            false => Err(SignatureError::InvalidSignature.into()),
181        }
182    }
183}
184
185/// Blob signed with one public key.
186#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
187pub struct Signed {
188    /// Signed blob.
189    #[cbor(rename = "untrusted_raw_value")]
190    pub blob: Vec<u8>,
191    /// Signature over the blob.
192    pub signature: SignatureBundle,
193}
194
195/// Blob signed by multiple public keys.
196#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
197pub struct MultiSigned {
198    /// Signed blob.
199    #[cbor(rename = "untrusted_raw_value")]
200    pub blob: Vec<u8>,
201    /// Signatures over the blob.
202    pub signatures: Vec<SignatureBundle>,
203}
204
205/// A signature bundled with a public key.
206#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
207pub struct SignatureBundle {
208    /// Public key that produced the signature.
209    pub public_key: PublicKey,
210    /// Actual signature.
211    pub signature: Signature,
212}
213
214impl SignatureBundle {
215    /// Verify returns true iff the signature is valid over the given context
216    /// and message.
217    pub fn verify(&self, context: &[u8], message: &[u8]) -> bool {
218        self.signature
219            .verify(&self.public_key, context, message)
220            .is_ok()
221    }
222}
223
224/// A abstract signer.
225pub trait Signer: Send + Sync {
226    /// Returns the public key corresponding to the signer.
227    fn public(&self) -> PublicKey;
228
229    /// Generates a signature over the context and message.
230    fn sign(&self, context: &[u8], message: &[u8]) -> Result<Signature>;
231}
232
233// Check if s < L, per RFC 8032, inspired by the Go runtime library's version
234// of this check.
235fn sc_minimal(raw_s: &[u8]) -> bool {
236    let mut rd = Cursor::new(raw_s);
237    let mut s = [0u64; 4];
238
239    // Read the raw scalar into limbs, and reverse it, because the raw
240    // representation is little-endian.
241    rd.read_u64_into::<LittleEndian>(&mut s[..]).unwrap();
242    s.reverse();
243
244    // Compare each limb, from most significant to least.
245    for i in 0..4 {
246        match s[i].cmp(&CURVE_ORDER[i]) {
247            Ordering::Greater => return false,
248            Ordering::Less => return true,
249            Ordering::Equal => {}
250        }
251    }
252
253    // The scalar is equal to the order of the curve.
254    false
255}
256
257/// Extends signature context with additional domain separation based on the runtime ID.
258pub fn signature_context_with_runtime_separation(
259    mut context: Vec<u8>,
260    runtime_id: &Namespace,
261) -> Vec<u8> {
262    context.extend(RUNTIME_SIGNATURE_CONTEXT_SEPARATOR);
263    context.extend(runtime_id.0);
264    context
265}
266
267/// Extends signature context with additional domain separation based on the chain context.
268pub fn signature_context_with_chain_separation(
269    mut context: Vec<u8>,
270    chain_context: &String,
271) -> Vec<u8> {
272    context.extend(CHAIN_SIGNATURE_CONTEXT_SEPARATOR);
273    context.extend(chain_context.as_bytes());
274    context
275}
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280    use rustc_hex::FromHex;
281
282    #[test]
283    fn test_sc_minimal() {
284        // L - 2^0
285        assert!(sc_minimal(&[
286            0xec, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9,
287            0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
288            0x00, 0x00, 0x00, 0x10
289        ]));
290
291        // L - 2^64
292        assert!(sc_minimal(&[
293            0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd5, 0x9c, 0xf7, 0xa2, 0xde, 0xf9,
294            0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
295            0x00, 0x00, 0x00, 0x10
296        ]));
297
298        // L - 2^192
299        assert!(sc_minimal(&[
300            0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd5, 0x9c, 0xf7, 0xa2, 0xde, 0xf9,
301            0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
302            0xff, 0xff, 0xff, 0x0f,
303        ]));
304
305        // L
306        assert!(!sc_minimal(&[
307            0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9,
308            0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
309            0x00, 0x00, 0x00, 0x10
310        ]));
311
312        // L + 2^0
313        assert!(!sc_minimal(&[
314            0xef, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9,
315            0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
316            0x00, 0x00, 0x00, 0x10
317        ]));
318
319        // L + 2^64
320        assert!(!sc_minimal(&[
321            0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd7, 0x9c, 0xf7, 0xa2, 0xde, 0xf9,
322            0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
323            0x00, 0x00, 0x00, 0x10
324        ]));
325
326        // L + 2^128
327        assert!(!sc_minimal(&[
328            0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9,
329            0xde, 0x14, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
330            0x00, 0x00, 0x00, 0x10
331        ]));
332
333        // L + 2^192
334        assert!(!sc_minimal(&[
335            0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9,
336            0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
337            0x00, 0x00, 0x00, 0x10
338        ]));
339
340        // Scalar from the go runtime's test case.
341        assert!(!sc_minimal(&[
342            0x67, 0x65, 0x4b, 0xce, 0x38, 0x32, 0xc2, 0xd7, 0x6f, 0x8f, 0x6f, 0x5d, 0xaf, 0xc0,
343            0x8d, 0x93, 0x39, 0xd4, 0xee, 0xf6, 0x76, 0x57, 0x33, 0x36, 0xa5, 0xc5, 0x1e, 0xb6,
344            0xf9, 0x46, 0xb3, 0x1d,
345        ]))
346    }
347
348    #[test]
349    fn test_private_key_to_bytes() {
350        let secret = PrivateKey::generate();
351        let bytes = secret.to_bytes();
352        let from_bytes = PrivateKey::from_bytes(bytes);
353        assert_eq!(secret.public_key(), from_bytes.public_key());
354    }
355
356    #[test]
357    #[should_panic]
358    fn test_private_key_to_bytes_malformed_a() {
359        PrivateKey::from_bytes(vec![]);
360    }
361
362    #[test]
363    #[should_panic]
364    fn test_private_key_to_bytes_malformed_b() {
365        PrivateKey::from_bytes(vec![1, 2, 3]);
366    }
367
368    #[test]
369    fn verification_small_order_a() {
370        // Case 1 from ed25519-speccheck
371        let pbk = "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa";
372        let msg = "9bd9f44f4dcc75bd531b56b2cd280b0bb38fc1cd6d1230e14861d861de092e79";
373        let sig = "f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43a5bb704786be79fc476f91d3f3f89b03984d8068dcf1bb7dfc6637b45450ac04";
374
375        let pbk: Vec<u8> = pbk.from_hex().unwrap();
376        let msg: Vec<u8> = msg.from_hex().unwrap();
377        let sig: Vec<u8> = sig.from_hex().unwrap();
378
379        let pbk = PublicKey::from(pbk);
380        let sig = Signature::from(sig);
381
382        assert!(
383            sig.verify_raw(&pbk, &msg).is_err(),
384            "small order A not rejected"
385        )
386    }
387
388    #[test]
389    fn verification_small_order_r() {
390        // Case 2 from ed25519-speccheck
391        let pbk = "f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43";
392        let msg = "aebf3f2601a0c8c5d39cc7d8911642f740b78168218da8471772b35f9d35b9ab";
393        let sig = "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa8c4bd45aecaca5b24fb97bc10ac27ac8751a7dfe1baff8b953ec9f5833ca260e";
394
395        let pbk: Vec<u8> = pbk.from_hex().unwrap();
396        let msg: Vec<u8> = msg.from_hex().unwrap();
397        let sig: Vec<u8> = sig.from_hex().unwrap();
398
399        let pbk = PublicKey::from(pbk);
400        let sig = Signature::from(sig);
401
402        assert!(
403            sig.verify_raw(&pbk, &msg).is_err(),
404            "small order R not rejected"
405        )
406    }
407
408    #[test]
409    fn verification_is_cofactored() {
410        // Case 4 from ed25519-speccheck
411        let pbk = "cdb267ce40c5cd45306fa5d2f29731459387dbf9eb933b7bd5aed9a765b88d4d";
412        let msg = "e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec4011eaccd55b53f56c";
413        let sig = "160a1cb0dc9c0258cd0a7d23e94d8fa878bcb1925f2c64246b2dee1796bed5125ec6bc982a269b723e0668e540911a9a6a58921d6925e434ab10aa7940551a09";
414
415        let pbk: Vec<u8> = pbk.from_hex().unwrap();
416        let msg: Vec<u8> = msg.from_hex().unwrap();
417        let sig: Vec<u8> = sig.from_hex().unwrap();
418
419        let pbk = PublicKey::from(pbk);
420        let sig = Signature::from(sig);
421
422        assert!(
423            sig.verify_raw(&pbk, &msg).is_ok(),
424            "verification is not cofactored(?)"
425        )
426    }
427
428    // Note: It is hard to test rejects small order A/R combined with
429    // accepts non-canonical A/R as there are no known non-small order
430    // points with a non-canonical encoding, that are not also small
431    // order.
432}