Skip to content

Commit

Permalink
feat: use EncodableSignature for tx encoding (#1100)
Browse files Browse the repository at this point in the history
* feat: use EncodableSignature for tx encoding

* feat: integrate EncodableSignature in alloy directly
  • Loading branch information
leruaa authored Jul 25, 2024
1 parent 8ff415d commit 2ce8525
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 19 deletions.
108 changes: 108 additions & 0 deletions crates/consensus/src/encodable_signature.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
use alloy_primitives::{Parity, SignatureError, U256};

/// Helper trait used to streamline signatures encoding.
pub trait EncodableSignature: Sized {
/// Instantiate from v, r, s.
fn from_rs_and_parity<P: TryInto<Parity, Error = E>, E: Into<SignatureError>>(
r: U256,
s: U256,
parity: P,
) -> Result<Self, SignatureError>;

/// Returns the `r` component of this signature.
fn r(&self) -> U256;

/// Returns the `s` component of this signature.
fn s(&self) -> U256;

/// Returns the recovery ID as a `u8`.
fn v(&self) -> Parity;

/// Sets the recovery ID by normalizing a `v` value.
fn with_parity<T: Into<Parity>>(self, parity: T) -> Self;

/// Modifies the recovery ID by applying [EIP-155] to a `v` value.
///
/// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155
#[inline]
fn with_chain_id(self, chain_id: u64) -> Self
where
Self: Copy,
{
self.with_parity(self.v().with_chain_id(chain_id))
}

/// Modifies the recovery ID by dropping any [EIP-155] v value, converting
/// to a simple parity bool.
fn with_parity_bool(self) -> Self
where
Self: Copy,
{
self.with_parity(self.v().to_parity_bool())
}

/// Decode an RLP-encoded VRS signature.
fn decode_rlp_vrs(buf: &mut &[u8]) -> Result<Self, alloy_rlp::Error> {
use alloy_rlp::Decodable;

let parity: Parity = Decodable::decode(buf)?;
let r = Decodable::decode(buf)?;
let s = Decodable::decode(buf)?;

Self::from_rs_and_parity(r, s, parity)
.map_err(|_| alloy_rlp::Error::Custom("attempted to decode invalid field element"))
}

/// Length of RLP RS field encoding
fn rlp_rs_len(&self) -> usize {
alloy_rlp::Encodable::length(&self.r()) + alloy_rlp::Encodable::length(&self.s())
}

/// Length of RLP V field encoding
fn rlp_vrs_len(&self) -> usize {
self.rlp_rs_len() + alloy_rlp::Encodable::length(&self.v())
}

/// Write R and S to an RLP buffer in progress.
fn write_rlp_rs(&self, out: &mut dyn alloy_rlp::BufMut) {
alloy_rlp::Encodable::encode(&self.r(), out);
alloy_rlp::Encodable::encode(&self.s(), out);
}

/// Write the V to an RLP buffer without using EIP-155.
fn write_rlp_v(&self, out: &mut dyn alloy_rlp::BufMut) {
alloy_rlp::Encodable::encode(&self.v(), out);
}

/// Write the VRS to the output. The V will always be 27 or 28.
fn write_rlp_vrs(&self, out: &mut dyn alloy_rlp::BufMut) {
self.write_rlp_v(out);
self.write_rlp_rs(out);
}
}

impl EncodableSignature for alloy_primitives::Signature {
fn from_rs_and_parity<P: TryInto<Parity, Error = E>, E: Into<SignatureError>>(
r: U256,
s: U256,
parity: P,
) -> Result<Self, SignatureError> {
Self::from_rs_and_parity(r, s, parity)
}

fn r(&self) -> U256 {
self.r()
}

fn s(&self) -> U256 {
self.s()
}

fn v(&self) -> Parity {
self.v()
}

fn with_parity<T: Into<Parity>>(self, parity: T) -> Self {
self.with_parity(parity)
}
}
3 changes: 3 additions & 0 deletions crates/consensus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ pub use account::Account;

pub mod constants;

mod encodable_signature;
pub use encodable_signature::EncodableSignature;

mod header;
pub use header::{Header, EMPTY_OMMER_ROOT_HASH, EMPTY_ROOT_HASH};

Expand Down
12 changes: 9 additions & 3 deletions crates/consensus/src/transaction/eip1559.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{SignableTransaction, Signed, Transaction, TxType};
use crate::{EncodableSignature, SignableTransaction, Signed, Transaction, TxType};
use alloy_eips::eip2930::AccessList;
use alloy_primitives::{keccak256, Bytes, ChainId, Signature, TxKind, U256};
use alloy_rlp::{BufMut, Decodable, Encodable, Header};
Expand Down Expand Up @@ -153,7 +153,10 @@ impl TxEip1559 {
///
/// If `with_header` is `true`, the payload length will include the RLP header length.
/// If `with_header` is `false`, the payload length will not include the RLP header length.
pub fn encoded_len_with_signature(&self, signature: &Signature, with_header: bool) -> usize {
pub fn encoded_len_with_signature<S>(&self, signature: &S, with_header: bool) -> usize
where
S: EncodableSignature,
{
// this counts the tx fields and signature fields
let payload_length = self.fields_len() + signature.rlp_vrs_len();

Expand Down Expand Up @@ -228,7 +231,10 @@ impl TxEip1559 {
/// tx type byte or string header.
///
/// This __does__ encode a list header and include a signature.
pub(crate) fn encode_with_signature_fields(&self, signature: &Signature, out: &mut dyn BufMut) {
pub fn encode_with_signature_fields<S>(&self, signature: &S, out: &mut dyn BufMut)
where
S: EncodableSignature,
{
let payload_length = self.fields_len() + signature.rlp_vrs_len();
let header = Header { list: true, payload_length };
header.encode(out);
Expand Down
12 changes: 9 additions & 3 deletions crates/consensus/src/transaction/eip2930.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{SignableTransaction, Signed, Transaction, TxType};
use crate::{EncodableSignature, SignableTransaction, Signed, Transaction, TxType};
use alloy_eips::eip2930::AccessList;
use alloy_primitives::{keccak256, Bytes, ChainId, Signature, TxKind, U256};
use alloy_rlp::{length_of_length, BufMut, Decodable, Encodable, Header};
Expand Down Expand Up @@ -131,7 +131,10 @@ impl TxEip2930 {
///
/// If `with_header` is `true`, the payload length will include the RLP header length.
/// If `with_header` is `false`, the payload length will not include the RLP header length.
pub fn encoded_len_with_signature(&self, signature: &Signature, with_header: bool) -> usize {
pub fn encoded_len_with_signature<S>(&self, signature: &S, with_header: bool) -> usize
where
S: EncodableSignature,
{
// this counts the tx fields and signature fields
let payload_length = self.fields_len() + signature.rlp_vrs_len();

Expand Down Expand Up @@ -176,7 +179,10 @@ impl TxEip2930 {
/// tx type byte or string header.
///
/// This __does__ encode a list header and include a signature.
pub(crate) fn encode_with_signature_fields(&self, signature: &Signature, out: &mut dyn BufMut) {
pub fn encode_with_signature_fields<S>(&self, signature: &S, out: &mut dyn BufMut)
where
S: EncodableSignature,
{
let payload_length = self.fields_len() + signature.rlp_vrs_len();
let header = Header { list: true, payload_length };
header.encode(out);
Expand Down
12 changes: 9 additions & 3 deletions crates/consensus/src/transaction/eip4844.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{SignableTransaction, Signed, Transaction, TxType};
use crate::{EncodableSignature, SignableTransaction, Signed, Transaction, TxType};

use alloy_eips::{eip2930::AccessList, eip4844::DATA_GAS_PER_BLOB};
use alloy_primitives::{keccak256, Address, Bytes, ChainId, Signature, TxKind, B256, U256};
Expand Down Expand Up @@ -493,7 +493,10 @@ impl TxEip4844 {
///
/// If `with_header` is `true`, the payload length will include the RLP header length.
/// If `with_header` is `false`, the payload length will not include the RLP header length.
pub fn encoded_len_with_signature(&self, signature: &Signature, with_header: bool) -> usize {
pub fn encoded_len_with_signature<S>(&self, signature: &S, with_header: bool) -> usize
where
S: EncodableSignature,
{
// this counts the tx fields and signature fields
let payload_length = self.fields_len() + signature.rlp_vrs_len();

Expand Down Expand Up @@ -538,7 +541,10 @@ impl TxEip4844 {
/// tx type byte or string header.
///
/// This __does__ encode a list header and include a signature.
pub(crate) fn encode_with_signature_fields(&self, signature: &Signature, out: &mut dyn BufMut) {
pub fn encode_with_signature_fields<S>(&self, signature: &S, out: &mut dyn BufMut)
where
S: EncodableSignature,
{
let payload_length = self.fields_len() + signature.rlp_vrs_len();
let header = Header { list: true, payload_length };
header.encode(out);
Expand Down
12 changes: 9 additions & 3 deletions crates/consensus/src/transaction/eip7702.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{SignableTransaction, Signed, Transaction, TxType};
use crate::{EncodableSignature, SignableTransaction, Signed, Transaction, TxType};
use alloy_eips::eip2930::AccessList;
use alloy_primitives::{keccak256, Bytes, ChainId, Signature, TxKind, U256};
use alloy_rlp::{BufMut, Decodable, Encodable, Header};
Expand Down Expand Up @@ -161,7 +161,10 @@ impl TxEip7702 {
///
/// If `with_header` is `true`, the payload length will include the RLP header length.
/// If `with_header` is `false`, the payload length will not include the RLP header length.
pub fn encoded_len_with_signature(&self, signature: &Signature, with_header: bool) -> usize {
pub fn encoded_len_with_signature<S>(&self, signature: &S, with_header: bool) -> usize
where
S: EncodableSignature,
{
// this counts the tx fields and signature fields
let payload_length = self.fields_len() + signature.rlp_vrs_len();

Expand Down Expand Up @@ -236,7 +239,10 @@ impl TxEip7702 {
/// tx type byte or string header.
///
/// This __does__ encode a list header and include a signature.
pub(crate) fn encode_with_signature_fields(&self, signature: &Signature, out: &mut dyn BufMut) {
pub fn encode_with_signature_fields<S>(&self, signature: &S, out: &mut dyn BufMut)
where
S: EncodableSignature,
{
let payload_length = self.fields_len() + signature.rlp_vrs_len();
let header = Header { list: true, payload_length };
header.encode(out);
Expand Down
16 changes: 9 additions & 7 deletions crates/consensus/src/transaction/legacy.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{SignableTransaction, Signed, Transaction};
use crate::{EncodableSignature, SignableTransaction, Signed, Transaction};
use alloy_primitives::{keccak256, Bytes, ChainId, Signature, TxKind, U256};
use alloy_rlp::{length_of_length, BufMut, Decodable, Encodable, Header, Result};
use core::mem;
Expand Down Expand Up @@ -104,11 +104,10 @@ impl TxLegacy {
/// tx type byte or string header.
///
/// This __does__ encode a list header and include a signature.
pub fn encode_with_signature_fields(
&self,
signature: &Signature,
out: &mut dyn alloy_rlp::BufMut,
) {
pub fn encode_with_signature_fields<S>(&self, signature: &S, out: &mut dyn alloy_rlp::BufMut)
where
S: EncodableSignature,
{
let payload_length = self.fields_len() + signature.rlp_vrs_len();
let header = Header { list: true, payload_length };
header.encode(out);
Expand All @@ -118,7 +117,10 @@ impl TxLegacy {

/// Returns what the encoded length should be, if the transaction were RLP encoded with the
/// given signature.
pub fn encoded_len_with_signature(&self, signature: &Signature) -> usize {
pub fn encoded_len_with_signature<S>(&self, signature: &S) -> usize
where
S: EncodableSignature,
{
let payload_length = self.fields_len() + signature.rlp_vrs_len();
Header { list: true, payload_length }.length() + payload_length
}
Expand Down
2 changes: 2 additions & 0 deletions crates/signer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ macro_rules! sign_transaction_with_chain_id {
// sign: lazy Signature,
// )
($signer:expr, $tx:expr, $sign:expr) => {{
use alloy_consensus::EncodableSignature;

if let Some(chain_id) = $signer.chain_id() {
if !$tx.set_chain_id_checked(chain_id) {
return Err(alloy_signer::Error::TransactionChainIdMismatch {
Expand Down

0 comments on commit 2ce8525

Please sign in to comment.