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