web3_signature/
lib.rs

1//! Type to represent an ECDSA signature with a recovery identifier.
2//!
3//! Supports serde and single party signatures.
4//!
5//! Support for signatures generated by MPC crates will be coming soon.
6#![deny(missing_docs)]
7use ethereum_types::U256;
8use serde::{Deserialize, Serialize};
9use std::str::FromStr;
10use thiserror::Error;
11
12#[cfg(feature = "single-party")]
13use k256::{ecdsa::RecoveryId, FieldBytes};
14
15/// Errors thrown converting to and from signatures.
16#[derive(Debug, Error)]
17pub enum SignatureError {
18    /// Invalid length, secp256k1 signatures are 65 bytes
19    #[error("invalid signature length, got {0}, expected 65")]
20    InvalidLength(usize),
21
22    /// Expected a recovery identifier.
23    #[error("recovery identifier is expected")]
24    RecoveryId,
25
26    /// When parsing a signature from string to hex
27    #[error(transparent)]
28    DecodingError(#[from] hex::FromHexError),
29
30    /// Error generated by the k256 library.
31    #[cfg(feature = "single-party")]
32    #[error(transparent)]
33    Ecdsa(#[from] k256::ecdsa::Error),
34}
35
36/// An ECDSA signature with a recovery identifier.
37///
38/// The recovery identifier may be normalized, in Electrum notation
39/// or have EIP155 chain replay protection applied.
40#[derive(
41    Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Copy,
42)]
43pub struct Signature {
44    /// R value
45    pub r: U256,
46    /// S value
47    pub s: U256,
48    /// V value for the recovery identifier
49    pub v: u64,
50}
51
52impl Signature {
53    /// Create a signature with normalized recovery identifier.
54    pub fn new_normalized(r: U256, s: U256, v: u64) -> Self {
55        debug_assert!(v == 0 || v == 1);
56        Self { r, s, v }
57    }
58
59    /// Create a signature with electrum recovery identifier.
60    pub fn new_electrum(r: U256, s: U256, v: u64) -> Self {
61        debug_assert!(v == 27 || v == 28);
62        Self { r, s, v }
63    }
64
65    /// Create a signature with EIP155 chain replay protection.
66    pub fn new_eip155(r: U256, s: U256, v: u64) -> Self {
67        debug_assert!(v >= 35);
68        Self { r, s, v }
69    }
70
71    /// Is the recovery identifier for this signature in
72    /// the normalized form (`0` or `1`).
73    pub fn is_normalized(&self) -> bool {
74        self.v == 0 || self.v == 1
75    }
76
77    /// Is the recovery identifier for this signature in
78    /// the electrum form (`27` or `28`).
79    pub fn is_electrum(&self) -> bool {
80        self.v == 27 || self.v == 28
81    }
82
83    /// Is the recovery identifier for this signature in
84    /// the EIP155 form.
85    pub fn is_eip155(&self) -> bool {
86        self.v >= 35
87    }
88
89    /// Converts this signature into normalized form from an Electrum
90    /// signature.
91    ///
92    /// Panics if this signature is not in Electrum format.
93    pub fn normalize(self) -> Self {
94        assert!(self.is_electrum());
95        Self {
96            r: self.r,
97            s: self.s,
98            v: self.v - 27,
99        }
100    }
101
102    /// Converts this signature into normalized form from an EIP155
103    /// signature.
104    ///
105    /// Panics if the signature could not be safely normalized for
106    /// example if a `chain_id` was supplied that would cause the
107    /// existing `v` value to become negative.
108    pub fn normalize_eip155(self, chain_id: u64) -> Self {
109        if self.v >= 35 + (chain_id * 2) {
110            Self {
111                r: self.r,
112                s: self.s,
113                v: self.v - chain_id * 2 - 35,
114            }
115        } else {
116            panic!("cannot safely normalize signature recovery identifier")
117        }
118    }
119
120    /// Converts this signature into Electrum form.
121    ///
122    /// Panics if this signature is not in it's normalized form.
123    pub fn into_electrum(self) -> Self {
124        assert!(self.is_normalized());
125        Self {
126            r: self.r,
127            s: self.s,
128            v: self.v + 27,
129        }
130    }
131
132    /// Converts this signature applying
133    /// [EIP155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md)
134    /// chain replay protection.
135    ///
136    /// Panics if this signature is not in it's normalized form.
137    pub fn into_eip155(self, chain_id: u64) -> Self {
138        assert!(self.is_normalized());
139        Self {
140            r: self.r,
141            s: self.s,
142            v: self.v + 35 + chain_id * 2,
143        }
144    }
145
146    /// Get the bytes for the r, s and v values.
147    ///
148    /// Panics if this signature is not normalized.
149    pub fn to_bytes(&self) -> [u8; 65] {
150        if !self.is_normalized() {
151            panic!("signature must be normalized to convert to byte array");
152        }
153
154        let mut out = [0u8; 64];
155        let mut r: [u8; 32] = [0u8; 32];
156        let mut s: [u8; 32] = [0u8; 32];
157        self.r.to_big_endian(&mut r);
158        self.s.to_big_endian(&mut s);
159        let (left, right) = out.split_at_mut(32);
160        left.copy_from_slice(&r);
161        right.copy_from_slice(&s);
162
163        let mut result = [0u8; 65];
164        let (left, right) = result.split_at_mut(64);
165        left.copy_from_slice(&out);
166        right[0] = self.v as u8;
167        result
168    }
169}
170
171impl<'a> TryFrom<&'a [u8]> for Signature {
172    type Error = SignatureError;
173
174    /// Parses a raw signature which is expected to be 65 bytes long where
175    /// the first 32 bytes is the `r` value, the second 32 bytes the `s` value
176    /// and the final byte is the `v` value in 'Electrum' notation.
177    fn try_from(bytes: &'a [u8]) -> Result<Self, Self::Error> {
178        if bytes.len() != 65 {
179            return Err(SignatureError::InvalidLength(bytes.len()));
180        }
181
182        let v = bytes[64];
183        let r = U256::from_big_endian(&bytes[0..32]);
184        let s = U256::from_big_endian(&bytes[32..64]);
185
186        Ok(Signature { r, s, v: v.into() })
187    }
188}
189
190impl FromStr for Signature {
191    type Err = SignatureError;
192
193    fn from_str(s: &str) -> Result<Self, Self::Err> {
194        let s = s.strip_prefix("0x").unwrap_or(s);
195        let bytes = hex::decode(s)?;
196        Signature::try_from(&bytes[..])
197    }
198}
199
200impl From<[u8; 65]> for Signature {
201    fn from(value: [u8; 65]) -> Self {
202        let r = &value[0..32];
203        let s = &value[32..64];
204        let v = &value[64];
205        Self {
206            r: U256::from_big_endian(r),
207            s: U256::from_big_endian(s),
208            v: *v as u64,
209        }
210    }
211}
212
213#[cfg(feature = "single-party")]
214impl TryFrom<(k256::ecdsa::Signature, Option<RecoveryId>)> for Signature {
215    type Error = SignatureError;
216
217    fn try_from(
218        sig: (k256::ecdsa::Signature, Option<RecoveryId>),
219    ) -> Result<Self, Self::Error> {
220        let r_bytes: FieldBytes = sig.0.r().into();
221        let s_bytes: FieldBytes = sig.0.s().into();
222        let v: u8 = sig.1.ok_or(SignatureError::RecoveryId)?.into();
223        Ok(Self {
224            r: U256::from_big_endian(r_bytes.as_slice()),
225            s: U256::from_big_endian(s_bytes.as_slice()),
226            v: v as u64,
227        })
228    }
229}
230
231#[cfg(feature = "single-party")]
232impl TryFrom<Signature> for (k256::ecdsa::Signature, RecoveryId) {
233    type Error = SignatureError;
234    fn try_from(value: Signature) -> Result<Self, Self::Error> {
235        let mut r: [u8; 32] = [0u8; 32];
236        let mut s: [u8; 32] = [0u8; 32];
237        value.r.to_big_endian(&mut r);
238        value.s.to_big_endian(&mut s);
239        let signature = k256::ecdsa::Signature::from_scalars(r, s)?;
240        let recid = RecoveryId::try_from(value.v as u8)?;
241        Ok((signature, recid))
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248
249    #[test]
250    fn signature_into_electrum() {
251        let sig = Signature {
252            r: Default::default(),
253            s: Default::default(),
254            v: 1,
255        };
256        let electrum = sig.into_electrum();
257        assert_eq!(28, electrum.v);
258    }
259
260    #[test]
261    fn signature_from_electrum() {
262        let electrum = Signature {
263            r: Default::default(),
264            s: Default::default(),
265            v: 37,
266        };
267        let sig = electrum.normalize_eip155(1);
268        assert_eq!(0, sig.v);
269    }
270
271    #[test]
272    fn signature_into_eip155() {
273        let sig = Signature {
274            r: Default::default(),
275            s: Default::default(),
276            v: 1,
277        };
278        let eip155 = sig.into_eip155(1337u64);
279        assert_eq!(2710, eip155.v);
280    }
281}