Skip to content

Commit 2dd9d1d

Browse files
authored
Always maintain the invariant for TransactionSignature (#67)
* Always use TransactionSignature wrap * Always maintain the invariant for TransactionSignature * use -> pub use * Make authorization list item signature malleable When the signature is invalid, the specification still asks that only the item is ignored, but not that decoding fails.
1 parent d3e246a commit 2dd9d1d

File tree

6 files changed

+275
-114
lines changed

6 files changed

+275
-114
lines changed

src/block.rs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,16 @@ impl From<BlockV1> for BlockV3 {
126126
mod tests {
127127
use super::*;
128128
use crate::transaction::{
129-
AuthorizationListItem, EIP7702Transaction, TransactionAction, TransactionV3,
129+
eip2930, eip7702::AuthorizationListItem, legacy::TransactionAction, EIP7702Transaction,
130+
TransactionV3,
130131
};
131132
use ethereum_types::{H160, H256, U256};
132133

134+
const ONE: H256 = H256([
135+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
136+
0, 1,
137+
]);
138+
133139
#[test]
134140
fn block_v3_with_eip7702_transaction() {
135141
// Create an EIP-7702 transaction
@@ -147,13 +153,13 @@ mod tests {
147153
chain_id: 1,
148154
address: H160::zero(),
149155
nonce: U256::zero(),
150-
y_parity: false,
151-
r: H256::zero(),
152-
s: H256::zero(),
156+
signature: eip2930::MalleableTransactionSignature {
157+
odd_y_parity: false,
158+
r: ONE,
159+
s: ONE,
160+
},
153161
}],
154-
odd_y_parity: false,
155-
r: H256::zero(),
156-
s: H256::zero(),
162+
signature: eip2930::TransactionSignature::new(false, ONE, ONE).unwrap(),
157163
});
158164

159165
// Create a block with the EIP-7702 transaction
@@ -207,9 +213,7 @@ mod tests {
207213
value: U256::zero(),
208214
input: vec![],
209215
access_list: vec![],
210-
odd_y_parity: false,
211-
r: H256::zero(),
212-
s: H256::zero(),
216+
signature: eip2930::TransactionSignature::new(false, ONE, ONE).unwrap(),
213217
});
214218

215219
let partial_header = PartialHeader {

src/transaction/eip1559.rs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ use ethereum_types::{H256, U256};
22
use rlp::{DecoderError, Rlp, RlpStream};
33
use sha3::{Digest, Keccak256};
44

5-
use crate::{
6-
transaction::{AccessList, TransactionAction},
7-
Bytes,
8-
};
5+
use crate::Bytes;
6+
7+
pub use super::eip2930::{AccessList, TransactionAction, TransactionSignature};
98

109
#[derive(Clone, Debug, PartialEq, Eq)]
1110
#[cfg_attr(
@@ -28,9 +27,7 @@ pub struct EIP1559Transaction {
2827
pub value: U256,
2928
pub input: Bytes,
3029
pub access_list: AccessList,
31-
pub odd_y_parity: bool,
32-
pub r: H256,
33-
pub s: H256,
30+
pub signature: TransactionSignature,
3431
}
3532

3633
impl EIP1559Transaction {
@@ -69,9 +66,9 @@ impl rlp::Encodable for EIP1559Transaction {
6966
s.append(&self.value);
7067
s.append(&self.input);
7168
s.append_list(&self.access_list);
72-
s.append(&self.odd_y_parity);
73-
s.append(&U256::from_big_endian(&self.r[..]));
74-
s.append(&U256::from_big_endian(&self.s[..]));
69+
s.append(&self.signature.odd_y_parity());
70+
s.append(&U256::from_big_endian(&self.signature.r()[..]));
71+
s.append(&U256::from_big_endian(&self.signature.s()[..]));
7572
}
7673
}
7774

@@ -91,9 +88,13 @@ impl rlp::Decodable for EIP1559Transaction {
9188
value: rlp.val_at(6)?,
9289
input: rlp.val_at(7)?,
9390
access_list: rlp.list_at(8)?,
94-
odd_y_parity: rlp.val_at(9)?,
95-
r: H256::from(rlp.val_at::<U256>(10)?.to_big_endian()),
96-
s: H256::from(rlp.val_at::<U256>(11)?.to_big_endian()),
91+
signature: {
92+
let odd_y_parity = rlp.val_at(9)?;
93+
let r = H256::from(rlp.val_at::<U256>(10)?.to_big_endian());
94+
let s = H256::from(rlp.val_at::<U256>(11)?.to_big_endian());
95+
TransactionSignature::new(odd_y_parity, r, s)
96+
.ok_or(DecoderError::Custom("Invalid transaction signature format"))?
97+
},
9798
})
9899
}
99100
}

src/transaction/eip2930.rs

Lines changed: 122 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,117 @@ use ethereum_types::{Address, H256, U256};
44
use rlp::{DecoderError, Rlp, RlpStream};
55
use sha3::{Digest, Keccak256};
66

7-
use crate::{transaction::TransactionAction, Bytes};
7+
use crate::Bytes;
8+
9+
pub use super::legacy::TransactionAction;
10+
11+
#[derive(Clone, Debug, PartialEq, Eq)]
12+
#[cfg_attr(
13+
feature = "with-scale",
14+
derive(
15+
scale_info::TypeInfo,
16+
scale_codec::Encode,
17+
scale_codec::Decode,
18+
scale_codec::DecodeWithMemTracking
19+
)
20+
)]
21+
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
22+
pub struct MalleableTransactionSignature {
23+
pub odd_y_parity: bool,
24+
pub r: H256,
25+
pub s: H256,
26+
}
27+
28+
#[derive(Clone, Debug, PartialEq, Eq)]
29+
#[cfg_attr(
30+
feature = "with-scale",
31+
derive(
32+
scale_info::TypeInfo,
33+
scale_codec::Encode,
34+
scale_codec::DecodeWithMemTracking
35+
)
36+
)]
37+
#[cfg_attr(feature = "with-serde", derive(serde::Serialize))]
38+
pub struct TransactionSignature {
39+
odd_y_parity: bool,
40+
r: H256,
41+
s: H256,
42+
}
43+
44+
impl TransactionSignature {
45+
#[must_use]
46+
pub fn new(odd_y_parity: bool, r: H256, s: H256) -> Option<Self> {
47+
const LOWER: H256 = H256([
48+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
49+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
50+
0x00, 0x00, 0x00, 0x01,
51+
]);
52+
const UPPER: H256 = H256([
53+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
54+
0xff, 0xfe, 0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b, 0xbf, 0xd2, 0x5e, 0x8c,
55+
0xd0, 0x36, 0x41, 0x41,
56+
]);
57+
58+
let is_valid = r < UPPER && r >= LOWER && s < UPPER && s >= LOWER;
59+
60+
if is_valid {
61+
Some(Self { odd_y_parity, r, s })
62+
} else {
63+
None
64+
}
65+
}
66+
67+
#[must_use]
68+
pub fn odd_y_parity(&self) -> bool {
69+
self.odd_y_parity
70+
}
71+
72+
#[must_use]
73+
pub fn r(&self) -> &H256 {
74+
&self.r
75+
}
76+
77+
#[must_use]
78+
pub fn s(&self) -> &H256 {
79+
&self.s
80+
}
81+
82+
#[must_use]
83+
pub fn is_low_s(&self) -> bool {
84+
const LOWER: H256 = H256([
85+
0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
86+
0xff, 0xff, 0x5d, 0x57, 0x6e, 0x73, 0x57, 0xa4, 0x50, 0x1d, 0xdf, 0xe9, 0x2f, 0x46,
87+
0x68, 0x1b, 0x20, 0xa0,
88+
]);
89+
90+
self.s <= LOWER
91+
}
92+
}
93+
94+
#[cfg(feature = "with-scale")]
95+
impl scale_codec::Decode for TransactionSignature {
96+
fn decode<I: scale_codec::Input>(value: &mut I) -> Result<Self, scale_codec::Error> {
97+
let unchecked = MalleableTransactionSignature::decode(value)?;
98+
match Self::new(unchecked.odd_y_parity, unchecked.r, unchecked.s) {
99+
Some(signature) => Ok(signature),
100+
None => Err(scale_codec::Error::from("Invalid signature")),
101+
}
102+
}
103+
}
104+
105+
#[cfg(feature = "with-serde")]
106+
impl<'de> serde::Deserialize<'de> for TransactionSignature {
107+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
108+
where
109+
D: serde::de::Deserializer<'de>,
110+
{
111+
let unchecked = MalleableTransactionSignature::deserialize(deserializer)?;
112+
Ok(
113+
TransactionSignature::new(unchecked.odd_y_parity, unchecked.r, unchecked.s)
114+
.ok_or(serde::de::Error::custom("invalid signature"))?,
115+
)
116+
}
117+
}
8118

9119
#[derive(Clone, Debug, PartialEq, Eq)]
10120
#[cfg_attr(
@@ -61,9 +171,7 @@ pub struct EIP2930Transaction {
61171
pub value: U256,
62172
pub input: Bytes,
63173
pub access_list: AccessList,
64-
pub odd_y_parity: bool,
65-
pub r: H256,
66-
pub s: H256,
174+
pub signature: TransactionSignature,
67175
}
68176

69177
impl EIP2930Transaction {
@@ -100,9 +208,9 @@ impl rlp::Encodable for EIP2930Transaction {
100208
s.append(&self.value);
101209
s.append(&self.input);
102210
s.append_list(&self.access_list);
103-
s.append(&self.odd_y_parity);
104-
s.append(&U256::from_big_endian(&self.r[..]));
105-
s.append(&U256::from_big_endian(&self.s[..]));
211+
s.append(&self.signature.odd_y_parity());
212+
s.append(&U256::from_big_endian(&self.signature.r()[..]));
213+
s.append(&U256::from_big_endian(&self.signature.s()[..]));
106214
}
107215
}
108216

@@ -121,9 +229,13 @@ impl rlp::Decodable for EIP2930Transaction {
121229
value: rlp.val_at(5)?,
122230
input: rlp.val_at(6)?,
123231
access_list: rlp.list_at(7)?,
124-
odd_y_parity: rlp.val_at(8)?,
125-
r: H256::from(rlp.val_at::<U256>(9)?.to_big_endian()),
126-
s: H256::from(rlp.val_at::<U256>(10)?.to_big_endian()),
232+
signature: {
233+
let odd_y_parity = rlp.val_at(8)?;
234+
let r = H256::from(rlp.val_at::<U256>(9)?.to_big_endian());
235+
let s = H256::from(rlp.val_at::<U256>(10)?.to_big_endian());
236+
TransactionSignature::new(odd_y_parity, r, s)
237+
.ok_or(DecoderError::Custom("Invalid transaction signature format"))?
238+
},
127239
})
128240
}
129241
}

0 commit comments

Comments
 (0)