Skip to main content

alloy_eip8141/
signature.rs

1use alloy_primitives::{Address, B256, Bytes, Signature, U256};
2use alloy_rlp::{Decodable, Encodable, Header, RlpDecodable, RlpEncodable};
3
4use crate::{Eip8141Error, FrameAddress, P256_SIGNATURE_LENGTH, SECP256K1_SIGNATURE_LENGTH};
5
6/// EIP-8141 transaction signature scheme.
7#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
8#[repr(u8)]
9#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
10#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
11#[cfg_attr(feature = "borsh", borsh(use_discriminant = true))]
12pub enum SignatureScheme {
13    /// Arbitrary witness bytes interpreted by EVM validation code.
14    #[default]
15    Arbitrary = 0x00,
16    /// Secp256k1 signature.
17    Secp256k1 = 0x01,
18    /// P-256 signature.
19    P256 = 0x02,
20}
21
22impl SignatureScheme {
23    /// Returns true if this is [`Self::Arbitrary`].
24    pub const fn is_arbitrary(self) -> bool {
25        matches!(self, Self::Arbitrary)
26    }
27
28    /// Returns true if this is [`Self::Secp256k1`].
29    pub const fn is_secp256k1(self) -> bool {
30        matches!(self, Self::Secp256k1)
31    }
32
33    /// Returns true if this is [`Self::P256`].
34    pub const fn is_p256(self) -> bool {
35        matches!(self, Self::P256)
36    }
37
38    /// Attempts to convert a raw scheme byte into a [`SignatureScheme`].
39    pub const fn try_from_u8(value: u8) -> Option<Self> {
40        match value {
41            0x00 => Some(Self::Arbitrary),
42            0x01 => Some(Self::Secp256k1),
43            0x02 => Some(Self::P256),
44            _ => None,
45        }
46    }
47
48    /// Returns the protocol signature verification gas cost.
49    pub const fn verification_gas(self) -> u64 {
50        match self {
51            Self::Arbitrary => 100,
52            Self::Secp256k1 => 2_800,
53            Self::P256 => 6_700,
54        }
55    }
56
57    /// Returns the fixed signature length of a protocol-validated scheme.
58    ///
59    /// Arbitrary witnesses have no fixed length.
60    pub const fn signature_length(self) -> Option<usize> {
61        match self {
62            Self::Arbitrary => None,
63            Self::Secp256k1 => Some(SECP256K1_SIGNATURE_LENGTH),
64            Self::P256 => Some(P256_SIGNATURE_LENGTH),
65        }
66    }
67}
68
69impl_u8_discriminant!(SignatureScheme, InvalidScheme, "invalid EIP-8141 signature scheme");
70
71/// The message authorized by an EIP-8141 signature entry.
72///
73/// RLP encodes the transaction hash case as an empty byte string and an explicit digest as its 32
74/// bytes, preserving the EIP-8141 wire format. Decoding rejects other lengths and the reserved
75/// zero digest. JSON uses hex byte strings, including `"0x"` for the transaction hash case; `null`
76/// also deserializes as the transaction hash.
77#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
78#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
79pub enum SignatureMessage {
80    /// The signature signs the canonical transaction signature hash.
81    #[default]
82    TransactionHash,
83    /// The signature signs an explicit non-zero 32-byte digest.
84    ///
85    /// Use [`Self::explicit`] to reject the reserved zero digest.
86    Explicit(B256),
87}
88
89impl SignatureMessage {
90    /// Returns true if this is [`Self::Explicit`].
91    pub const fn is_explicit(self) -> bool {
92        matches!(self, Self::Explicit(_))
93    }
94
95    /// Creates an explicit message, rejecting the reserved zero digest.
96    pub fn explicit(digest: B256) -> Result<Self, Eip8141Error> {
97        if digest.is_zero() { Err(Eip8141Error::ZeroMessage) } else { Ok(Self::Explicit(digest)) }
98    }
99
100    /// Returns true if the signature signs the canonical transaction signature hash.
101    pub const fn is_transaction_hash(self) -> bool {
102        matches!(self, Self::TransactionHash)
103    }
104
105    /// Returns the explicit digest, or `None` for the transaction hash case.
106    pub const fn digest(self) -> Option<B256> {
107        match self {
108            Self::TransactionHash => None,
109            Self::Explicit(digest) => Some(digest),
110        }
111    }
112
113    /// Returns the byte string carried by a signature entry: empty for the transaction hash.
114    pub const fn as_bytes(&self) -> &[u8] {
115        match self {
116            Self::TransactionHash => &[],
117            Self::Explicit(digest) => digest.as_slice(),
118        }
119    }
120}
121
122impl TryFrom<&[u8]> for SignatureMessage {
123    type Error = Eip8141Error;
124
125    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
126        if value.is_empty() {
127            return Ok(Self::TransactionHash);
128        }
129        B256::try_from(value)
130            .map_err(|_| Eip8141Error::InvalidMessageLength(value.len()))
131            .and_then(Self::explicit)
132    }
133}
134
135impl Encodable for SignatureMessage {
136    fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
137        match self {
138            Self::TransactionHash => out.put_u8(alloy_rlp::EMPTY_STRING_CODE),
139            Self::Explicit(digest) => digest.encode(out),
140        }
141    }
142
143    fn length(&self) -> usize {
144        match self {
145            Self::TransactionHash => 1,
146            Self::Explicit(digest) => digest.length(),
147        }
148    }
149}
150
151impl Decodable for SignatureMessage {
152    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
153        Self::try_from(Header::decode_bytes(buf, false)?).map_err(|err| match err {
154            Eip8141Error::ZeroMessage => {
155                alloy_rlp::Error::Custom("EIP-8141 signature message must be nonzero")
156            }
157            _ => alloy_rlp::Error::Custom("invalid EIP-8141 signature message length"),
158        })
159    }
160}
161
162#[cfg(feature = "arbitrary")]
163impl<'a> arbitrary::Arbitrary<'a> for SignatureMessage {
164    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
165        Ok(match u.arbitrary::<Option<B256>>()? {
166            Some(digest) if !digest.is_zero() => Self::Explicit(digest),
167            _ => Self::TransactionHash,
168        })
169    }
170}
171
172#[cfg(feature = "serde")]
173impl serde::Serialize for SignatureMessage {
174    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
175        let digest = match self {
176            Self::TransactionHash => None,
177            Self::Explicit(digest) if digest.is_zero() => {
178                return Err(serde::ser::Error::custom(Eip8141Error::ZeroMessage));
179            }
180            Self::Explicit(digest) => Some(*digest),
181        };
182        crate::serde_utils::serialize_optional_bytes(digest, serializer)
183    }
184}
185
186#[cfg(feature = "serde")]
187impl<'de> serde::Deserialize<'de> for SignatureMessage {
188    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
189        crate::serde_utils::deserialize_optional_bytes::<32, D>(deserializer)?
190            .map_or(Ok(Self::TransactionHash), |digest| {
191                Self::explicit(digest).map_err(serde::de::Error::custom)
192            })
193    }
194}
195
196/// A signature entry attached to an EIP-8141 frame transaction.
197#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, RlpEncodable, RlpDecodable)]
198#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
199#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
200#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
201#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
202pub struct FrameSignature {
203    /// Signature scheme identifier.
204    pub scheme: SignatureScheme,
205    /// Scheme-dependent signer metadata. For `ARBITRARY`, this must be empty.
206    pub signer: FrameAddress,
207    /// The signed message: the canonical transaction signature hash or an explicit digest.
208    pub msg: SignatureMessage,
209    /// Raw signature bytes.
210    pub signature: Bytes,
211}
212
213impl FrameSignature {
214    /// Creates a new frame signature from raw field values.
215    ///
216    /// Call [`Self::validate_structure`] before using an untrusted entry, then perform the
217    /// scheme-specific cryptographic verification. This constructor does not validate the fields.
218    pub const fn new(
219        scheme: SignatureScheme,
220        signer: FrameAddress,
221        msg: SignatureMessage,
222        signature: Bytes,
223    ) -> Self {
224        Self { scheme, signer, msg, signature }
225    }
226
227    /// Returns true if this signature signs the canonical transaction signature hash.
228    pub const fn signs_transaction_hash(&self) -> bool {
229        self.msg.is_transaction_hash()
230    }
231
232    /// Returns the explicit signed digest, or `None` for the transaction hash case.
233    pub const fn explicit_message(&self) -> Option<B256> {
234        self.msg.digest()
235    }
236
237    /// Returns a borrowed RLP view for the transaction's signing preimage.
238    ///
239    /// The view preserves the scheme, signer, and message. It encodes an empty signature when
240    /// [`Self::signs_transaction_hash`] is true to avoid self-reference; explicit-message entries
241    /// retain their signature bytes. This does not validate or modify the entry.
242    pub fn as_signing(&self) -> SigningFrameSignature<'_> {
243        SigningFrameSignature {
244            scheme: self.scheme,
245            signer: self.signer,
246            msg: self.msg,
247            signature: if self.signs_transaction_hash() { &[] } else { &self.signature },
248        }
249    }
250
251    /// Returns the explicit signer address of a protocol-validated scheme.
252    ///
253    /// Returns `None` for an empty signer, which resolves to the transaction sender, and for
254    /// arbitrary signatures, which have no signer. Use [`Self::resolved_signer`] when the
255    /// transaction sender is available.
256    pub const fn signer_address(&self) -> Option<Address> {
257        match self.scheme {
258            SignatureScheme::Arbitrary => None,
259            _ => self.signer.address(),
260        }
261    }
262
263    /// Resolves the signer, rejecting a nonempty signer on an arbitrary signature.
264    pub const fn resolved_signer(&self, sender: Address) -> Result<Option<Address>, Eip8141Error> {
265        match self.scheme {
266            SignatureScheme::Arbitrary => {
267                if self.signer.is_empty() {
268                    Ok(None)
269                } else {
270                    Err(Eip8141Error::UnexpectedSigner)
271                }
272            }
273            _ => Ok(Some(self.signer.resolve(sender))),
274        }
275    }
276
277    /// Checks the signer, signature length, parity, and canonical scalar bounds.
278    ///
279    /// This does not perform cryptographic verification: callers must still recover the secp256k1
280    /// signer or verify the P-256 signature against the resolved signer and message. Decoding a
281    /// signature entry does not imply that these checks have passed.
282    pub fn validate_structure(&self) -> Result<(), Eip8141Error> {
283        let (expected, order) = match self.scheme {
284            SignatureScheme::Arbitrary => {
285                return if self.signer.is_empty() {
286                    Ok(())
287                } else {
288                    Err(Eip8141Error::UnexpectedSigner)
289                };
290            }
291            SignatureScheme::Secp256k1 => (SECP256K1_SIGNATURE_LENGTH, crate::SECP256K1N),
292            SignatureScheme::P256 => (P256_SIGNATURE_LENGTH, crate::SECP256R1N),
293        };
294        if self.signature.len() != expected {
295            return Err(Eip8141Error::InvalidSignatureLength {
296                expected,
297                actual: self.signature.len(),
298            });
299        }
300        let offset = if self.scheme == SignatureScheme::Secp256k1 {
301            if self.signature[0] > 1 {
302                return Err(Eip8141Error::InvalidParity(self.signature[0]));
303            }
304            1
305        } else {
306            0
307        };
308        let r = U256::from_be_slice(&self.signature[offset..offset + 32]);
309        let s = U256::from_be_slice(&self.signature[offset + 32..offset + 64]);
310        if r.is_zero() || r >= order || s.is_zero() || s > order >> 1 {
311            return Err(Eip8141Error::InvalidSignatureScalar);
312        }
313        Ok(())
314    }
315
316    /// Runs [`Self::validate_structure`] and checks that a P-256 public key hashes to the resolved
317    /// signer.
318    ///
319    /// This covers every check of the specification's `validate_signature` that does not need a
320    /// cryptographic backend.
321    pub fn validate_structure_with_sender(&self, sender: Address) -> Result<(), Eip8141Error> {
322        self.validate_structure()?;
323        if let Some(derived) = self.p256_signer_address() {
324            let expected = self.signer.resolve(sender);
325            if derived != expected {
326                return Err(Eip8141Error::P256SignerMismatch { expected, derived });
327            }
328        }
329        Ok(())
330    }
331
332    /// Parses the `v || r || s` payload of a secp256k1 entry.
333    ///
334    /// Returns `None` unless the entry uses the secp256k1 scheme with a 65-byte signature whose
335    /// parity byte is zero or one.
336    pub fn secp256k1_signature(&self) -> Option<Signature> {
337        if self.scheme != SignatureScheme::Secp256k1
338            || self.signature.len() != SECP256K1_SIGNATURE_LENGTH
339        {
340            return None;
341        }
342        let parity = match self.signature[0] {
343            0 => false,
344            1 => true,
345            _ => return None,
346        };
347        Some(Signature::from_bytes_and_parity(&self.signature[1..], parity))
348    }
349
350    /// Derives the signer address committed to by the public key of a P-256 entry.
351    ///
352    /// Returns `None` unless the entry uses the P-256 scheme with a 128-byte signature.
353    pub fn p256_signer_address(&self) -> Option<Address> {
354        (self.scheme == SignatureScheme::P256 && self.signature.len() == P256_SIGNATURE_LENGTH)
355            .then(|| Address::from_raw_public_key(&self.signature[64..]))
356    }
357
358    /// Creates a structurally checked secp256k1 entry using EIP-8141's `v || r || s` layout.
359    ///
360    /// Unlike legacy signatures, the parity byte is zero or one and precedes the scalars.
361    pub fn from_secp256k1(
362        signer: FrameAddress,
363        msg: SignatureMessage,
364        signature: Signature,
365    ) -> Result<Self, Eip8141Error> {
366        let mut bytes = [0u8; SECP256K1_SIGNATURE_LENGTH];
367        bytes[0] = u8::from(signature.v());
368        bytes[1..33].copy_from_slice(&signature.r().to_be_bytes::<32>());
369        bytes[33..].copy_from_slice(&signature.s().to_be_bytes::<32>());
370        let entry = Self::new(SignatureScheme::Secp256k1, signer, msg, bytes.into());
371        entry.validate_structure()?;
372        Ok(entry)
373    }
374
375    /// Returns the protocol signature verification gas cost.
376    pub const fn verification_gas(&self) -> u64 {
377        self.scheme.verification_gas()
378    }
379}
380
381/// Borrowed RLP view of a signature entry in a transaction's signing preimage.
382///
383/// Created by [`FrameSignature::as_signing`]. Its [`Encodable`] implementation includes the entry's
384/// list header and applies signature elision independently of the signature scheme.
385#[derive(Clone, Copy, Debug, RlpEncodable)]
386pub struct SigningFrameSignature<'a> {
387    scheme: SignatureScheme,
388    signer: FrameAddress,
389    msg: SignatureMessage,
390    signature: &'a [u8],
391}
392
393/// Borrowed RLP view of a signature list in a transaction's signing preimage.
394///
395/// Each entry is encoded through [`FrameSignature::as_signing`], and the outer RLP list header
396/// reflects the transformed entries' lengths. Encoding does not allocate or modify the entries.
397///
398/// ```
399/// use alloy_eip8141::{FrameSignature, SigningFrameSignatures};
400/// use alloy_rlp::Encodable;
401///
402/// let entries = [FrameSignature::default()];
403/// let signing = SigningFrameSignatures::new(&entries);
404/// let encoded = alloy_rlp::encode(signing);
405/// assert_eq!(encoded.len(), signing.length());
406/// assert_eq!(encoded, [0xc5, 0xc4, 0x80, 0x80, 0x80, 0x80]);
407/// ```
408#[derive(Clone, Copy, Debug)]
409pub struct SigningFrameSignatures<'a>(&'a [FrameSignature]);
410
411impl<'a> SigningFrameSignatures<'a> {
412    /// Creates a signing-preimage view of the signature list.
413    pub const fn new(signatures: &'a [FrameSignature]) -> Self {
414        Self(signatures)
415    }
416}
417
418impl Encodable for SigningFrameSignatures<'_> {
419    fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
420        alloy_rlp::encode_iter(self.0.iter().map(FrameSignature::as_signing), out);
421    }
422
423    fn length(&self) -> usize {
424        let payload_length = self.0.iter().map(|signature| signature.as_signing().length()).sum();
425        Header { list: true, payload_length }.length_with_payload()
426    }
427}