Skip to content

Commit 63781ea

Browse files
authored
Additional precompile fixes (#261)
* Parse and bounds-check entire Modexp length inputs * Test large modexp computation and fix related typo * Bounds checks and unit tests for Ed25519Verify precompile * s/<spaces>/<tabs>/
1 parent 78fb3bc commit 63781ea

2 files changed

Lines changed: 215 additions & 22 deletions

File tree

frame/evm/precompile/ed25519/src/lib.rs

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,12 @@ impl LinearCostPrecompile for Ed25519Verify {
3535
input: &[u8],
3636
_: usize,
3737
) -> core::result::Result<(ExitSucceed, Vec<u8>), ExitError> {
38-
let len = min(input.len(), 128);
38+
if input.len() < 128 {
39+
return Err(ExitError::Other("input must contain 128 bytes".into()));
40+
};
3941

4042
let mut i = [0u8; 128];
41-
i[..len].copy_from_slice(&input[..len]);
43+
i[..128].copy_from_slice(&input[..128]);
4244

4345
let mut buf = [0u8; 4];
4446

@@ -58,3 +60,97 @@ impl LinearCostPrecompile for Ed25519Verify {
5860
Ok((ExitSucceed::Returned, buf.to_vec()))
5961
}
6062
}
63+
64+
#[cfg(test)]
65+
mod tests {
66+
use super::*;
67+
use ed25519_dalek::{Keypair, SecretKey, Signer};
68+
69+
#[test]
70+
fn test_empty_input() -> std::result::Result<(), ExitError> {
71+
72+
let input: [u8; 0] = [];
73+
let cost: usize = 1;
74+
75+
match Ed25519Verify::execute(&input, cost) {
76+
Ok((_, _)) => {
77+
panic!("Test not expected to pass");
78+
},
79+
Err(e) => {
80+
assert_eq!(e, ExitError::Other("input must contain 128 bytes".into()));
81+
Ok(())
82+
}
83+
}
84+
}
85+
86+
#[test]
87+
fn test_verify() -> std::result::Result<(), ExitError> {
88+
89+
let secret_key_bytes: [u8; ed25519_dalek::SECRET_KEY_LENGTH] = [
90+
157, 097, 177, 157, 239, 253, 090, 096,
91+
186, 132, 074, 244, 146, 236, 044, 196,
92+
068, 073, 197, 105, 123, 050, 105, 025,
93+
112, 059, 172, 003, 028, 174, 127, 096, ];
94+
95+
let secret_key = SecretKey::from_bytes(&secret_key_bytes)
96+
.expect("Failed to generate secretkey");
97+
let public_key = (&secret_key).into();
98+
99+
let keypair = Keypair { secret: secret_key, public: public_key };
100+
101+
let msg: &[u8] = b"abcdefghijklmnopqrstuvwxyz123456";
102+
assert_eq!(msg.len(), 32);
103+
let signature = keypair.sign(msg);
104+
105+
// input is:
106+
// 1) message (32 bytes)
107+
// 2) pubkey (32 bytes)
108+
// 3) signature (64 bytes)
109+
let mut input: Vec<u8> = Vec::with_capacity(128);
110+
input.extend_from_slice(msg);
111+
input.extend_from_slice(&public_key.to_bytes());
112+
input.extend_from_slice(&signature.to_bytes());
113+
assert_eq!(input.len(), 128);
114+
115+
let cost: usize = 1;
116+
117+
match Ed25519Verify::execute(&input, cost) {
118+
Ok((_, output)) => {
119+
assert_eq!(output.len(), 4);
120+
assert_eq!(output[0], 0u8);
121+
assert_eq!(output[1], 0u8);
122+
assert_eq!(output[2], 0u8);
123+
assert_eq!(output[3], 0u8);
124+
},
125+
Err(e) => {
126+
return Err(e);
127+
}
128+
};
129+
130+
// try again with a different message
131+
let msg: &[u8] = b"BAD_MESSAGE_mnopqrstuvwxyz123456";
132+
133+
let mut input: Vec<u8> = Vec::with_capacity(128);
134+
input.extend_from_slice(msg);
135+
input.extend_from_slice(&public_key.to_bytes());
136+
input.extend_from_slice(&signature.to_bytes());
137+
assert_eq!(input.len(), 128);
138+
139+
match Ed25519Verify::execute(&input, cost) {
140+
Ok((_, output)) => {
141+
assert_eq!(output.len(), 4);
142+
assert_eq!(output[0], 0u8);
143+
assert_eq!(output[1], 0u8);
144+
assert_eq!(output[2], 0u8);
145+
assert_eq!(output[3], 1u8); // non-zero indicates error (in our case, 1)
146+
},
147+
Err(e) => {
148+
return Err(e);
149+
}
150+
};
151+
152+
Ok(())
153+
}
154+
155+
}
156+

frame/evm/precompile/modexp/src/lib.rs

Lines changed: 117 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,26 @@ use alloc::vec::Vec;
2323
use core::cmp::max;
2424
use fp_evm::LinearCostPrecompile;
2525
use evm::{ExitSucceed, ExitError};
26-
use num::{BigUint, Zero, One};
26+
use num::{BigUint, Zero, One, ToPrimitive, FromPrimitive};
2727

2828
pub struct Modexp;
2929

30+
// ModExp expects the following as inputs:
31+
// 1) 32 bytes expressing the length of base
32+
// 2) 32 bytes expressing the length of exponent
33+
// 3) 32 bytes expressing the length of modulus
34+
// 4) base, size as described above
35+
// 5) exponent, size as described above
36+
// 6) modulus, size as described above
37+
//
38+
//
39+
// NOTE: input sizes are arbitrarily large (up to 256 bits), with the expectation
40+
// that gas limits would be applied before actual computation.
41+
//
42+
// maximum stack size will also prevent abuse.
43+
//
44+
// see: https://eips.ethereum.org/EIPS/eip-198
45+
3046
impl LinearCostPrecompile for Modexp {
3147
const BASE: usize = 15;
3248
const WORD: usize = 3;
@@ -39,23 +55,39 @@ impl LinearCostPrecompile for Modexp {
3955
return Err(ExitError::Other("input must contain at least 96 bytes".into()));
4056
};
4157

58+
// reasonable assumption: this must fit within the Ethereum EVM's max stack size
59+
let max_size_big = BigUint::from_u32(1024).expect("can't create BigUint");
60+
4261
let mut buf = [0; 32];
4362
buf.copy_from_slice(&input[0..32]);
44-
let mut len_bytes = [0u8; 8];
45-
len_bytes.copy_from_slice(&buf[24..]);
46-
let base_len = u64::from_be_bytes(len_bytes) as usize;
63+
let base_len_big = BigUint::from_bytes_be(&buf);
64+
if base_len_big > max_size_big {
65+
return Err(ExitError::Other("unreasonably large base length".into()));
66+
}
4767

48-
buf = [0; 32];
4968
buf.copy_from_slice(&input[32..64]);
50-
len_bytes = [0u8; 8];
51-
len_bytes.copy_from_slice(&buf[24..]);
52-
let exp_len = u64::from_be_bytes(len_bytes) as usize;
69+
let exp_len_big = BigUint::from_bytes_be(&buf);
70+
if exp_len_big > max_size_big {
71+
return Err(ExitError::Other("unreasonably large exponent length".into()));
72+
}
5373

54-
buf = [0; 32];
5574
buf.copy_from_slice(&input[64..96]);
56-
len_bytes = [0u8; 8];
57-
len_bytes.copy_from_slice(&buf[24..]);
58-
let mod_len = u64::from_be_bytes(len_bytes) as usize;
75+
let mod_len_big = BigUint::from_bytes_be(&buf);
76+
if mod_len_big > max_size_big {
77+
return Err(ExitError::Other("unreasonably large exponent length".into()));
78+
}
79+
80+
// bounds check handled above
81+
let base_len = base_len_big.to_usize().expect("base_len out of bounds");
82+
let exp_len = exp_len_big.to_usize().expect("exp_len out of bounds");
83+
let mod_len = mod_len_big.to_usize().expect("mod_len out of bounds");
84+
85+
// input length should be at least 96 + user-specified length of base + exp + mod
86+
let total_len = base_len + exp_len + mod_len + 96;
87+
if input.len() < total_len {
88+
let err_msg = format!("expected at least {} bytes but received {}", total_len, input.len());
89+
return Err(ExitError::Other(err_msg.into()));
90+
}
5991

6092
// Gas formula allows arbitrary large exp_len when base and modulus are empty, so we need to handle empty base first.
6193
let r = if base_len == 0 && mod_len == 0 {
@@ -72,6 +104,8 @@ impl LinearCostPrecompile for Modexp {
72104
let mod_start = exp_start + exp_len;
73105
let modulus = BigUint::from_bytes_be(&input[mod_start..mod_start + mod_len]);
74106

107+
// TODO: computation should only proceed if sufficient gas has been provided
108+
75109
if modulus.is_zero() || modulus.is_one() {
76110
BigUint::zero()
77111
} else {
@@ -120,15 +154,51 @@ mod tests {
120154
}
121155

122156
#[test]
123-
fn test_simple_inputs() {
157+
fn test_insufficient_input() -> std::result::Result<(), ExitError> {
158+
159+
let input = hex::decode(
160+
"0000000000000000000000000000000000000000000000000000000000000001\
161+
0000000000000000000000000000000000000000000000000000000000000001\
162+
0000000000000000000000000000000000000000000000000000000000000001")
163+
.expect("Decode failed");
164+
165+
let cost: usize = 1;
166+
167+
match Modexp::execute(&input, cost) {
168+
Ok((_, _)) => {
169+
panic!("Test not expected to pass");
170+
},
171+
Err(e) => {
172+
assert_eq!(e, ExitError::Other("expected at least 99 bytes but received 96".into()));
173+
Ok(())
174+
}
175+
}
176+
}
177+
178+
#[test]
179+
fn test_excessive_input() -> std::result::Result<(), ExitError> {
180+
181+
let input = hex::decode(
182+
"1000000000000000000000000000000000000000000000000000000000000001\
183+
0000000000000000000000000000000000000000000000000000000000000001\
184+
0000000000000000000000000000000000000000000000000000000000000001")
185+
.expect("Decode failed");
124186

125-
// ModExp expects the following as inputs:
126-
// 1) 32 bytes expressing the length of base
127-
// 2) 32 bytes expressing the length of exponent
128-
// 3) 32 bytes expressing the length of modulus
129-
// 4) base, size as described above
130-
// 5) exponent, size as described above
131-
// 6) modulus, size as described above
187+
let cost: usize = 1;
188+
189+
match Modexp::execute(&input, cost) {
190+
Ok((_, _)) => {
191+
panic!("Test not expected to pass");
192+
},
193+
Err(e) => {
194+
assert_eq!(e, ExitError::Other("unreasonably large base length".into()));
195+
Ok(())
196+
}
197+
}
198+
}
199+
200+
#[test]
201+
fn test_simple_inputs() {
132202

133203
let input = hex::decode(
134204
"0000000000000000000000000000000000000000000000000000000000000001\
@@ -183,4 +253,31 @@ mod tests {
183253
}
184254
}
185255
}
256+
257+
#[test]
258+
fn test_large_computation() {
259+
260+
let input = hex::decode(
261+
"0000000000000000000000000000000000000000000000000000000000000001\
262+
0000000000000000000000000000000000000000000000000000000000000020\
263+
0000000000000000000000000000000000000000000000000000000000000020\
264+
03\
265+
fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e\
266+
fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f")
267+
.expect("Decode failed");
268+
269+
let cost: usize = 1;
270+
271+
match Modexp::execute(&input, cost) {
272+
Ok((_, output)) => {
273+
assert_eq!(output.len(), 32); // should be same length as mod
274+
let result = BigUint::from_bytes_be(&output[..]);
275+
let expected = BigUint::parse_bytes(b"1", 10).unwrap();
276+
assert_eq!(result, expected);
277+
},
278+
Err(_) => {
279+
panic!("Modexp::execute() returned error"); // TODO: how to pass error on?
280+
}
281+
}
282+
}
186283
}

0 commit comments

Comments
 (0)