mwc_web3/
signing.rs

1//! Signing capabilities and utilities.
2
3use crate::types::H256;
4
5/// Error during signing.
6#[derive(Debug, derive_more::Display, PartialEq, Clone)]
7pub enum SigningError {
8    /// A message to sign is invalid. Has to be a non-zero 32-bytes slice.
9    #[display(fmt = "Message has to be a non-zero 32-bytes slice.")]
10    InvalidMessage,
11}
12impl std::error::Error for SigningError {}
13
14/// Error during sender recovery.
15#[derive(Debug, derive_more::Display, PartialEq, Clone)]
16pub enum RecoveryError {
17    /// A message to recover is invalid. Has to be a non-zero 32-bytes slice.
18    #[display(fmt = "Message has to be a non-zero 32-bytes slice.")]
19    InvalidMessage,
20    /// A signature is invalid and the sender could not be recovered.
21    #[display(fmt = "Signature is invalid (check recovery id).")]
22    InvalidSignature,
23}
24impl std::error::Error for RecoveryError {}
25
26#[cfg(feature = "signing")]
27pub use feature_gated::*;
28
29#[cfg(feature = "signing")]
30mod feature_gated {
31    use super::*;
32    use crate::types::Address;
33    pub(crate) use secp256k1::SecretKey;
34    use secp256k1::{
35        recovery::{RecoverableSignature, RecoveryId},
36        Message, PublicKey, Secp256k1,
37    };
38    use std::ops::Deref;
39
40    /// A trait representing ethereum-compatible key with signing capabilities.
41    ///
42    /// The purpose of this trait is to prevent leaking `secp256k1::SecretKey` struct
43    /// in stack or memory.
44    /// To use secret keys securely, they should be wrapped in a struct that prevents
45    /// leaving copies in memory (both when it's moved or dropped). Please take a look
46    /// at:
47    /// - https://github.com/graphprotocol/solidity-bindgen/blob/master/solidity-bindgen/src/secrets.rs
48    /// - or https://crates.io/crates/zeroize
49    /// if you care enough about your secrets to be used securely.
50    ///
51    /// If it's enough to pass a reference to `SecretKey` (lifetimes) than you can use `SecretKeyRef`
52    /// wrapper.
53    pub trait Key {
54        /// Sign given message and include chain-id replay protection.
55        ///
56        /// When a chain ID is provided, the `Signature`'s V-value will have chain relay
57        /// protection added (as per EIP-155). Otherwise, the V-value will be in
58        /// 'Electrum' notation.
59        fn sign(&self, message: &[u8], chain_id: Option<u64>) -> Result<Signature, SigningError>;
60
61        /// Get public address that this key represents.
62        fn address(&self) -> Address;
63    }
64
65    /// A `SecretKey` reference wrapper.
66    ///
67    /// A wrapper around `secp256k1::SecretKey` reference, which enables it to be used in methods expecting
68    /// `Key` capabilities.
69    pub struct SecretKeyRef<'a> {
70        pub(super) key: &'a SecretKey,
71    }
72
73    impl<'a> SecretKeyRef<'a> {
74        /// A simple wrapper around a reference to `SecretKey` which allows it to be usable for signing.
75        pub fn new(key: &'a SecretKey) -> Self {
76            Self { key }
77        }
78    }
79
80    impl<'a> From<&'a SecretKey> for SecretKeyRef<'a> {
81        fn from(key: &'a SecretKey) -> Self {
82            Self::new(key)
83        }
84    }
85
86    impl<'a> Deref for SecretKeyRef<'a> {
87        type Target = SecretKey;
88
89        fn deref(&self) -> &Self::Target {
90            &self.key
91        }
92    }
93
94    impl<T: Deref<Target = SecretKey>> Key for T {
95        fn sign(&self, message: &[u8], chain_id: Option<u64>) -> Result<Signature, SigningError> {
96            let message = Message::from_slice(&message).map_err(|_| SigningError::InvalidMessage)?;
97            let (recovery_id, signature) = Secp256k1::signing_only()
98                .sign_recoverable(&message, self)
99                .serialize_compact();
100
101            let standard_v = recovery_id.to_i32() as u64;
102            let v = if let Some(chain_id) = chain_id {
103                // When signing with a chain ID, add chain replay protection.
104                standard_v + 35 + chain_id * 2
105            } else {
106                // Otherwise, convert to 'Electrum' notation.
107                standard_v + 27
108            };
109            let r = H256::from_slice(&signature[..32]);
110            let s = H256::from_slice(&signature[32..]);
111
112            Ok(Signature { v, r, s })
113        }
114
115        fn address(&self) -> Address {
116            secret_key_address(self)
117        }
118    }
119
120    /// Recover a sender, given message and the signature.
121    ///
122    /// Signature and `recovery_id` can be obtained from `types::Recovery` type.
123    pub fn recover(message: &[u8], signature: &[u8], recovery_id: i32) -> Result<Address, RecoveryError> {
124        let message = Message::from_slice(message).map_err(|_| RecoveryError::InvalidMessage)?;
125        let recovery_id = RecoveryId::from_i32(recovery_id).map_err(|_| RecoveryError::InvalidSignature)?;
126        let signature =
127            RecoverableSignature::from_compact(&signature, recovery_id).map_err(|_| RecoveryError::InvalidSignature)?;
128        let public_key = Secp256k1::verification_only()
129            .recover(&message, &signature)
130            .map_err(|_| RecoveryError::InvalidSignature)?;
131
132        Ok(public_key_address(&public_key))
133    }
134
135    /// Gets the address of a public key.
136    ///
137    /// The public address is defined as the low 20 bytes of the keccak hash of
138    /// the public key. Note that the public key returned from the `secp256k1`
139    /// crate is 65 bytes long, that is because it is prefixed by `0x04` to
140    /// indicate an uncompressed public key; this first byte is ignored when
141    /// computing the hash.
142    pub(crate) fn public_key_address(public_key: &PublicKey) -> Address {
143        let public_key = public_key.serialize_uncompressed();
144
145        debug_assert_eq!(public_key[0], 0x04);
146        let hash = keccak256(&public_key[1..]);
147
148        Address::from_slice(&hash[12..])
149    }
150
151    /// Gets the public address of a private key.
152    pub(crate) fn secret_key_address(key: &SecretKey) -> Address {
153        let secp = Secp256k1::signing_only();
154        let public_key = PublicKey::from_secret_key(&secp, key);
155        public_key_address(&public_key)
156    }
157}
158
159/// A struct that represents the components of a secp256k1 signature.
160pub struct Signature {
161    /// V component in electrum format with chain-id replay protection.
162    pub v: u64,
163    /// R component of the signature.
164    pub r: H256,
165    /// S component of the signature.
166    pub s: H256,
167}
168
169/// Compute the Keccak-256 hash of input bytes.
170pub fn keccak256(bytes: &[u8]) -> [u8; 32] {
171    use tiny_keccak::{Hasher, Keccak};
172    let mut output = [0u8; 32];
173    let mut hasher = Keccak::v256();
174    hasher.update(bytes);
175    hasher.finalize(&mut output);
176    output
177}