ic_web3_rs/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")]
27// pub use feature_gated::*;
28
29// #[cfg(feature = "signing")]
30// mod feature_gated {
31// use super::*;
32// use crate::types::Address;
33// use once_cell::sync::Lazy;
34// pub(crate) use secp256k1::SecretKey;
35// use secp256k1::{
36// ecdsa::{RecoverableSignature, RecoveryId},
37// All, Message, PublicKey, Secp256k1,
38// };
39// use std::ops::Deref;
40
41// static CONTEXT: Lazy<Secp256k1<All>> = Lazy::new(Secp256k1::new);
42
43// /// A trait representing ethereum-compatible key with signing capabilities.
44// ///
45// /// The purpose of this trait is to prevent leaking `secp256k1::SecretKey` struct
46// /// in stack or memory.
47// /// To use secret keys securely, they should be wrapped in a struct that prevents
48// /// leaving copies in memory (both when it's moved or dropped). Please take a look
49// /// at:
50// /// - https://github.com/graphprotocol/solidity-bindgen/blob/master/solidity-bindgen/src/secrets.rs
51// /// - or https://crates.io/crates/zeroize
52// /// if you care enough about your secrets to be used securely.
53// ///
54// /// If it's enough to pass a reference to `SecretKey` (lifetimes) than you can use `SecretKeyRef`
55// /// wrapper.
56// pub trait Key {
57// /// Sign given message and include chain-id replay protection.
58// ///
59// /// When a chain ID is provided, the `Signature`'s V-value will have chain replay
60// /// protection added (as per EIP-155). Otherwise, the V-value will be in
61// /// 'Electrum' notation.
62// fn sign(&self, message: &[u8], chain_id: Option<u64>) -> Result<Signature, SigningError>;
63
64// /// Sign given message without manipulating V-value; used for typed transactions
65// /// (AccessList and EIP-1559)
66// fn sign_message(&self, message: &[u8]) -> Result<Signature, SigningError>;
67
68// /// Get public address that this key represents.
69// fn address(&self) -> Address;
70// }
71
72// /// A `SecretKey` reference wrapper.
73// ///
74// /// A wrapper around `secp256k1::SecretKey` reference, which enables it to be used in methods expecting
75// /// `Key` capabilities.
76// pub struct SecretKeyRef<'a> {
77// pub(super) key: &'a SecretKey,
78// }
79
80// impl<'a> SecretKeyRef<'a> {
81// /// A simple wrapper around a reference to `SecretKey` which allows it to be usable for signing.
82// pub fn new(key: &'a SecretKey) -> Self {
83// Self { key }
84// }
85// }
86
87// impl<'a> From<&'a SecretKey> for SecretKeyRef<'a> {
88// fn from(key: &'a SecretKey) -> Self {
89// Self::new(key)
90// }
91// }
92
93// impl<'a> Deref for SecretKeyRef<'a> {
94// type Target = SecretKey;
95
96// fn deref(&self) -> &Self::Target {
97// self.key
98// }
99// }
100
101// impl<T: Deref<Target = SecretKey>> Key for T {
102// fn sign(&self, message: &[u8], chain_id: Option<u64>) -> Result<Signature, SigningError> {
103// let message = Message::from_slice(message).map_err(|_| SigningError::InvalidMessage)?;
104// let (recovery_id, signature) = CONTEXT.sign_ecdsa_recoverable(&message, self).serialize_compact();
105
106// let standard_v = recovery_id.to_i32() as u64;
107// let v = if let Some(chain_id) = chain_id {
108// // When signing with a chain ID, add chain replay protection.
109// standard_v + 35 + chain_id * 2
110// } else {
111// // Otherwise, convert to 'Electrum' notation.
112// standard_v + 27
113// };
114// let r = H256::from_slice(&signature[..32]);
115// let s = H256::from_slice(&signature[32..]);
116
117// Ok(Signature { v, r, s })
118// }
119
120// fn sign_message(&self, message: &[u8]) -> Result<Signature, SigningError> {
121// let message = Message::from_slice(message).map_err(|_| SigningError::InvalidMessage)?;
122// let (recovery_id, signature) = CONTEXT.sign_ecdsa_recoverable(&message, self).serialize_compact();
123
124// let v = recovery_id.to_i32() as u64;
125// let r = H256::from_slice(&signature[..32]);
126// let s = H256::from_slice(&signature[32..]);
127
128// Ok(Signature { v, r, s })
129// }
130
131// fn address(&self) -> Address {
132// secret_key_address(self)
133// }
134// }
135
136// /// Recover a sender, given message and the signature.
137// ///
138// /// Signature and `recovery_id` can be obtained from `types::Recovery` type.
139// pub fn recover(message: &[u8], signature: &[u8], recovery_id: i32) -> Result<Address, RecoveryError> {
140// let message = Message::from_slice(message).map_err(|_| RecoveryError::InvalidMessage)?;
141// let recovery_id = RecoveryId::from_i32(recovery_id).map_err(|_| RecoveryError::InvalidSignature)?;
142// let signature =
143// RecoverableSignature::from_compact(signature, recovery_id).map_err(|_| RecoveryError::InvalidSignature)?;
144// let public_key = CONTEXT
145// .recover_ecdsa(&message, &signature)
146// .map_err(|_| RecoveryError::InvalidSignature)?;
147
148// Ok(public_key_address(&public_key))
149// }
150
151// /// Gets the address of a public key.
152// ///
153// /// The public address is defined as the low 20 bytes of the keccak hash of
154// /// the public key. Note that the public key returned from the `secp256k1`
155// /// crate is 65 bytes long, that is because it is prefixed by `0x04` to
156// /// indicate an uncompressed public key; this first byte is ignored when
157// /// computing the hash.
158// pub(crate) fn public_key_address(public_key: &PublicKey) -> Address {
159// let public_key = public_key.serialize_uncompressed();
160
161// debug_assert_eq!(public_key[0], 0x04);
162// let hash = keccak256(&public_key[1..]);
163
164// Address::from_slice(&hash[12..])
165// }
166
167// /// Gets the public address of a private key.
168// pub(crate) fn secret_key_address(key: &SecretKey) -> Address {
169// let secp = &*CONTEXT;
170// let public_key = PublicKey::from_secret_key(secp, key);
171// public_key_address(&public_key)
172// }
173// }
174
175/// A struct that represents the components of a secp256k1 signature.
176pub struct Signature {
177 /// V component in electrum format with chain-id replay protection.
178 pub v: u64,
179 /// R component of the signature.
180 pub r: H256,
181 /// S component of the signature.
182 pub s: H256,
183}
184
185/// Compute the Keccak-256 hash of input bytes.
186pub fn keccak256(bytes: &[u8]) -> [u8; 32] {
187 use tiny_keccak::{Hasher, Keccak};
188 let mut output = [0u8; 32];
189 let mut hasher = Keccak::v256();
190 hasher.update(bytes);
191 hasher.finalize(&mut output);
192 output
193}
194
195// /// Result of the name hash algotithm.
196// pub type NameHash = [u8; 32];
197
198// /// Compute the hash of a domain name using the namehash algorithm.
199// ///
200// /// [Specification](https://docs.ens.domains/contract-api-reference/name-processing#hashing-names)
201// pub fn namehash(name: &str) -> NameHash {
202// let mut node = [0u8; 32];
203
204// if name.is_empty() {
205// return node;
206// }
207
208// let mut labels: Vec<&str> = name.split('.').collect();
209
210// labels.reverse();
211
212// for label in labels.iter() {
213// let label_hash = keccak256(label.as_bytes());
214
215// node = keccak256(&[node, label_hash].concat());
216// }
217
218// node
219// }
220
221/// Hash a message according to EIP-191.
222///
223/// The data is a UTF-8 encoded string and will enveloped as follows:
224/// `"\x19Ethereum Signed Message:\n" + message.length + message` and hashed
225/// using keccak256.
226pub fn hash_message<S>(message: S) -> H256
227where
228 S: AsRef<[u8]>,
229{
230 let message = message.as_ref();
231
232 let mut eth_message = format!("\x19Ethereum Signed Message:\n{}", message.len()).into_bytes();
233 eth_message.extend_from_slice(message);
234
235 keccak256(ð_message).into()
236}
237
238// #[cfg(test)]
239// mod tests {
240// use super::*;
241
242// //See -> https://eips.ethereum.org/EIPS/eip-137 for test cases
243
244// #[test]
245// fn name_hash_empty() {
246// let input = "";
247
248// let result = namehash(input);
249
250// let expected = [0u8; 32];
251
252// assert_eq!(expected, result);
253// }
254
255// #[test]
256// fn name_hash_eth() {
257// let input = "eth";
258
259// let result = namehash(input);
260
261// let expected = [
262// 0x93, 0xcd, 0xeb, 0x70, 0x8b, 0x75, 0x45, 0xdc, 0x66, 0x8e, 0xb9, 0x28, 0x01, 0x76, 0x16, 0x9d, 0x1c, 0x33,
263// 0xcf, 0xd8, 0xed, 0x6f, 0x04, 0x69, 0x0a, 0x0b, 0xcc, 0x88, 0xa9, 0x3f, 0xc4, 0xae,
264// ];
265
266// assert_eq!(expected, result);
267// }
268
269// #[test]
270// fn name_hash_foo_eth() {
271// let input = "foo.eth";
272
273// let result = namehash(input);
274
275// let expected = [
276// 0xde, 0x9b, 0x09, 0xfd, 0x7c, 0x5f, 0x90, 0x1e, 0x23, 0xa3, 0xf1, 0x9f, 0xec, 0xc5, 0x48, 0x28, 0xe9, 0xc8,
277// 0x48, 0x53, 0x98, 0x01, 0xe8, 0x65, 0x91, 0xbd, 0x98, 0x01, 0xb0, 0x19, 0xf8, 0x4f,
278// ];
279
280// assert_eq!(expected, result);
281// }
282// }