use crate::{transaction::util::secp256k1, Address, B256, U256};
use alloy_primitives::Bytes;
use alloy_rlp::{Decodable, Encodable, Error as RlpError};
use serde::{Deserialize, Serialize};
#[cfg(test)]
use reth_codecs::Compact;
const SECP256K1N_HALF: U256 = U256::from_be_bytes([
0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x5D, 0x57, 0x6E, 0x73, 0x57, 0xA4, 0x50, 0x1D, 0xDF, 0xE9, 0x2F, 0x46, 0x68, 0x1B, 0x20, 0xA0,
]);
#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::derive_arbitrary(compact))]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
pub struct Signature {
pub r: U256,
pub s: U256,
pub odd_y_parity: bool,
}
impl Signature {
#[cfg(feature = "optimism")]
pub const fn optimism_deposit_tx_signature() -> Self {
Self { r: U256::ZERO, s: U256::ZERO, odd_y_parity: false }
}
}
#[cfg(any(test, feature = "reth-codec"))]
impl reth_codecs::Compact for Signature {
fn to_compact<B>(&self, buf: &mut B) -> usize
where
B: bytes::BufMut + AsMut<[u8]>,
{
buf.put_slice(&self.r.as_le_bytes());
buf.put_slice(&self.s.as_le_bytes());
self.odd_y_parity as usize
}
fn from_compact(mut buf: &[u8], identifier: usize) -> (Self, &[u8]) {
use bytes::Buf;
assert!(buf.len() >= 64);
let r = U256::from_le_slice(&buf[0..32]);
let s = U256::from_le_slice(&buf[32..64]);
buf.advance(64);
(Self { r, s, odd_y_parity: identifier != 0 }, buf)
}
}
impl Signature {
pub(crate) fn payload_len_with_eip155_chain_id(&self, chain_id: Option<u64>) -> usize {
self.v(chain_id).length() + self.r.length() + self.s.length()
}
pub(crate) fn encode_with_eip155_chain_id(
&self,
out: &mut dyn alloy_rlp::BufMut,
chain_id: Option<u64>,
) {
self.v(chain_id).encode(out);
self.r.encode(out);
self.s.encode(out);
}
#[inline]
#[allow(clippy::missing_const_for_fn)]
pub fn v(&self, chain_id: Option<u64>) -> u64 {
if let Some(chain_id) = chain_id {
self.odd_y_parity as u64 + chain_id * 2 + 35
} else {
#[cfg(feature = "optimism")]
if *self == Self::optimism_deposit_tx_signature() {
return 0
}
self.odd_y_parity as u64 + 27
}
}
pub(crate) fn decode_with_eip155_chain_id(
buf: &mut &[u8],
) -> alloy_rlp::Result<(Self, Option<u64>)> {
let v = u64::decode(buf)?;
let r: U256 = Decodable::decode(buf)?;
let s: U256 = Decodable::decode(buf)?;
if v < 35 {
if v != 27 && v != 28 {
#[cfg(feature = "optimism")]
if v == 0 && r.is_zero() && s.is_zero() {
return Ok((Self { r, s, odd_y_parity: false }, None))
}
}
}
let (odd_y_parity, chain_id) = extract_chain_id(v)?;
Ok((Self { r, s, odd_y_parity }, chain_id))
}
pub fn payload_len(&self) -> usize {
self.odd_y_parity.length() + self.r.length() + self.s.length()
}
pub fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
self.odd_y_parity.encode(out);
self.r.encode(out);
self.s.encode(out);
}
pub fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
Ok(Self {
odd_y_parity: Decodable::decode(buf)?,
r: Decodable::decode(buf)?,
s: Decodable::decode(buf)?,
})
}
pub fn recover_signer_unchecked(&self, hash: B256) -> Option<Address> {
let mut sig: [u8; 65] = [0; 65];
sig[0..32].copy_from_slice(&self.r.to_be_bytes::<32>());
sig[32..64].copy_from_slice(&self.s.to_be_bytes::<32>());
sig[64] = self.odd_y_parity as u8;
secp256k1::recover_signer_unchecked(&sig, &hash.0).ok()
}
pub fn recover_signer(&self, hash: B256) -> Option<Address> {
if self.s > SECP256K1N_HALF {
return None
}
self.recover_signer_unchecked(hash)
}
pub fn to_bytes(&self) -> [u8; 65] {
let mut sig = [0u8; 65];
sig[..32].copy_from_slice(&self.r.to_be_bytes::<32>());
sig[32..64].copy_from_slice(&self.s.to_be_bytes::<32>());
let v = u8::from(self.odd_y_parity) + 27;
sig[64] = v;
sig
}
pub fn to_hex_bytes(&self) -> Bytes {
self.to_bytes().into()
}
#[inline]
pub const fn size(&self) -> usize {
core::mem::size_of::<Self>()
}
}
#[inline]
pub const fn extract_chain_id(v: u64) -> alloy_rlp::Result<(bool, Option<u64>)> {
if v < 35 {
if v != 27 && v != 28 {
return Err(RlpError::Custom("invalid Ethereum signature (V is not 27 or 28)"))
}
Ok((v == 28, None))
} else {
let odd_y_parity = ((v - 35) % 2) != 0;
let chain_id = (v - 35) >> 1;
Ok((odd_y_parity, Some(chain_id)))
}
}
#[cfg(test)]
mod tests {
use crate::{transaction::signature::SECP256K1N_HALF, Address, Signature, B256, U256};
use alloy_primitives::{hex, hex::FromHex, Bytes};
use std::str::FromStr;
#[test]
fn test_payload_len_with_eip155_chain_id() {
let signature = Signature { r: U256::from(1), s: U256::from(1), odd_y_parity: false };
assert_eq!(3, signature.payload_len_with_eip155_chain_id(None));
assert_eq!(3, signature.payload_len_with_eip155_chain_id(Some(1)));
assert_eq!(4, signature.payload_len_with_eip155_chain_id(Some(47)));
}
#[test]
fn test_v() {
let signature = Signature { r: U256::from(1), s: U256::from(1), odd_y_parity: false };
assert_eq!(27, signature.v(None));
assert_eq!(37, signature.v(Some(1)));
let signature = Signature { r: U256::from(1), s: U256::from(1), odd_y_parity: true };
assert_eq!(28, signature.v(None));
assert_eq!(38, signature.v(Some(1)));
}
#[test]
fn test_encode_and_decode_with_eip155_chain_id() {
let signature = Signature { r: U256::from(1), s: U256::from(1), odd_y_parity: false };
let mut encoded = Vec::new();
signature.encode_with_eip155_chain_id(&mut encoded, None);
assert_eq!(encoded.len(), signature.payload_len_with_eip155_chain_id(None));
let (decoded, chain_id) = Signature::decode_with_eip155_chain_id(&mut &*encoded).unwrap();
assert_eq!(signature, decoded);
assert_eq!(None, chain_id);
let mut encoded = Vec::new();
signature.encode_with_eip155_chain_id(&mut encoded, Some(1));
assert_eq!(encoded.len(), signature.payload_len_with_eip155_chain_id(Some(1)));
let (decoded, chain_id) = Signature::decode_with_eip155_chain_id(&mut &*encoded).unwrap();
assert_eq!(signature, decoded);
assert_eq!(Some(1), chain_id);
}
#[test]
fn test_payload_len() {
let signature = Signature { r: U256::default(), s: U256::default(), odd_y_parity: false };
assert_eq!(3, signature.payload_len());
}
#[test]
fn test_encode_and_decode() {
let signature = Signature { r: U256::default(), s: U256::default(), odd_y_parity: false };
let mut encoded = Vec::new();
signature.encode(&mut encoded);
assert_eq!(encoded.len(), signature.payload_len());
let decoded = Signature::decode(&mut &*encoded).unwrap();
assert_eq!(signature, decoded);
}
#[test]
fn test_recover_signer() {
let signature = Signature {
r: U256::from_str(
"18515461264373351373200002665853028612451056578545711640558177340181847433846",
)
.unwrap(),
s: U256::from_str(
"46948507304638947509940763649030358759909902576025900602547168820602576006531",
)
.unwrap(),
odd_y_parity: false,
};
let hash =
B256::from_str("daf5a779ae972f972197303d7b574746c7ef83eadac0f2791ad23db92e4c8e53")
.unwrap();
let signer = signature.recover_signer(hash).unwrap();
let expected = Address::from_str("0x9d8a62f656a8d1615c1294fd71e9cfb3e4855a4f").unwrap();
assert_eq!(expected, signer);
}
#[test]
fn ensure_size_equals_sum_of_fields() {
let signature = Signature {
r: U256::from_str(
"18515461264373351373200002665853028612451056578545711640558177340181847433846",
)
.unwrap(),
s: U256::from_str(
"46948507304638947509940763649030358759909902576025900602547168820602576006531",
)
.unwrap(),
odd_y_parity: false,
};
assert!(signature.size() >= 65);
}
#[test]
fn test_to_hex_bytes() {
let signature = Signature {
r: U256::from_str(
"18515461264373351373200002665853028612451056578545711640558177340181847433846",
)
.unwrap(),
s: U256::from_str(
"46948507304638947509940763649030358759909902576025900602547168820602576006531",
)
.unwrap(),
odd_y_parity: false,
};
let expected = Bytes::from_hex("0x28ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa63627667cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d831b").unwrap();
assert_eq!(signature.to_hex_bytes(), expected);
}
#[test]
fn eip_2_reject_high_s_value() {
let raw_tx = hex!("f86d8085746a52880082520894c93f2250589a6563f5359051c1ea25746549f0d889208686e75e903bc000801ba034b6fdc33ea520e8123cf5ac4a9ff476f639cab68980cd9366ccae7aef437ea0a0e517caa5f50e27ca0d1e9a92c503b4ccb039680c6d9d0c71203ed611ea4feb33");
let tx = crate::transaction::TransactionSigned::decode_enveloped(&mut &raw_tx[..]).unwrap();
let signature = tx.signature();
assert!(signature.s > SECP256K1N_HALF);
let hash = tx.hash();
assert!(signature.recover_signer(hash).is_none());
assert!(signature.recover_signer_unchecked(hash).is_some());
}
}