Skip to content

Commit 4a2c1f2

Browse files
authored
[RDS-2923] Check overflows + config fix (#32)
1 parent 35a3039 commit 4a2c1f2

File tree

8 files changed

+77
-67
lines changed

8 files changed

+77
-67
lines changed

crates/redstone/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "redstone"
3-
version = "2.0.0-pre"
3+
version = "2.0.1"
44
edition = "2021"
55
authors = ["RedStone <https://redstone.finance>"]
66
description = "A Rust implementation of deserializing&decrypting RedStone payload"
@@ -59,7 +59,7 @@ derive-getters = "0.5.0"
5959
getrandom = { version = "^0.2.15", default-features = false, features = ["js"] }
6060

6161
[dev-dependencies]
62-
criterion = "0.5.1"
62+
criterion = { version = "0.5.1", default-features = false }
6363
itertools = { version = "^0.13.0" }
6464
rand = "0.8.5"
6565

crates/redstone/src/contract/verification.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ const MAX_SIGNER_COUNT: usize = u8::MAX as usize;
6262

6363
/// Verifies if:
6464
/// * if `last_write_time` is not None if between `last_write_time` and `time_now`
65-
/// passed strictly more than `min_time_between_updates`.
65+
/// passed strictly more than `min_time_between_updates`.
6666
pub fn verify_write_timestamp(
6767
time_now: TimestampMillis,
6868
last_write_time: Option<TimestampMillis>,

crates/redstone/src/core/validator.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
use alloc::vec::Vec;
22

33
use crate::{
4-
core::config::Config,
5-
network::error::Error,
6-
protocol::constants::{MAX_TIMESTAMP_AHEAD_MS, MAX_TIMESTAMP_DELAY_MS},
7-
types::Value,
8-
utils::filter::FilterSome,
9-
FeedId, SignerAddress, TimestampMillis,
4+
core::config::Config, network::error::Error, types::Value, utils::filter::FilterSome, FeedId,
5+
SignerAddress, TimestampMillis,
106
};
117
/// A trait defining validation operations for data feeds and signers.
128
///
@@ -122,12 +118,13 @@ impl Validator for Config {
122118
timestamp: TimestampMillis,
123119
) -> Result<TimestampMillis, Error> {
124120
if !timestamp
125-
.add(MAX_TIMESTAMP_DELAY_MS)
121+
.add(*self.max_timestamp_delay_ms())
126122
.is_same_or_after(*self.block_timestamp())
127123
{
128124
return Err(Error::TimestampTooOld(index, timestamp));
129125
}
130-
if !timestamp.is_same_or_before(self.block_timestamp().add(MAX_TIMESTAMP_AHEAD_MS)) {
126+
if !timestamp.is_same_or_before(self.block_timestamp().add(*self.max_timestamp_ahead_ms()))
127+
{
131128
return Err(Error::TimestampTooFuture(index, timestamp));
132129
}
133130

crates/redstone/src/crypto/mod.rs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,22 @@ const ECDSA_N_DIV_2: U256 = U256([
1212
9223372036854775807,
1313
]);
1414

15+
const SIGNATURE_SIZE_BS: usize = 65;
16+
1517
#[derive(Clone, PartialEq, Eq, Debug)]
1618
pub enum CryptoError {
1719
RecoveryByte(u8),
1820
Signature(Vec<u8>),
1921
RecoverPreHash,
22+
InvalidSignatureLen(usize),
2023
}
2124
impl CryptoError {
2225
pub fn code(&self) -> u16 {
2326
match self {
2427
CryptoError::RecoveryByte(byte) => *byte as u16,
2528
CryptoError::Signature(vec) => vec.len() as u16,
2629
CryptoError::RecoverPreHash => 0,
30+
CryptoError::InvalidSignatureLen(len) => *len as u16,
2731
}
2832
}
2933
}
@@ -43,12 +47,16 @@ pub trait Crypto {
4347
message: A,
4448
signature: B,
4549
) -> Result<SignerAddress, CryptoError> {
46-
check_signature_malleability(signature.as_ref())?;
47-
let recovery_byte = signature.as_ref()[64]; // 65-byte representation
50+
let signature = signature.as_ref();
51+
if signature.len() != SIGNATURE_SIZE_BS {
52+
return Err(CryptoError::InvalidSignatureLen(signature.len()));
53+
}
54+
check_signature_malleability(signature)?;
55+
let recovery_byte = signature[64]; // 65-byte representation
4856
let msg_hash = Self::keccak256(message);
4957
let key = Self::recover_public_key(
5058
recovery_byte - (if recovery_byte >= 27 { 27 } else { 0 }),
51-
&signature.as_ref()[..64],
59+
&signature[..64],
5260
msg_hash,
5361
)?;
5462
let key_hash = Self::keccak256(&key.as_ref()[1..]); // skip first uncompressed-key byte
@@ -69,7 +77,7 @@ fn check_signature_malleability(sig: &[u8]) -> Result<(), CryptoError> {
6977
#[cfg(test)]
7078
#[allow(dead_code)] // this is test template for crypto implementations
7179
pub mod recovery_key_tests {
72-
use alloc::borrow::ToOwned;
80+
use alloc::{borrow::ToOwned, string::ToString};
7381

7482
use crate::{helpers::hex::hex_to_bytes, Crypto, CryptoError};
7583

@@ -145,13 +153,13 @@ pub mod recovery_key_tests {
145153
T: Crypto<KeccakOutput = [u8; 32]>,
146154
{
147155
let msg =
148-
b"4254430000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000058f32c910a001924dc0bd5000000020000001";
156+
hex_to_bytes("4254430000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000058f32c910a001924dc0bd5000000020000001".to_string());
149157

150158
let signature =
151-
b"6307247862e106f0d4b3cde75805ababa67325953145aa05bdd219d90a741e0eeba79b756bf3af6db6c26a8ed3810e3c584379476fd83096218e9deb95a7617e1b";
159+
hex_to_bytes("6307247862e106f0d4b3cde75805ababa67325953145aa05bdd219d90a741e0eeba79b756bf3af6db6c26a8ed3810e3c584379476fd83096218e9deb95a7617e1b".to_string());
152160

153-
let result = T::recover_address(msg, signature);
154-
assert_eq!(result, Err(CryptoError::RecoveryByte(74)));
161+
let result = T::recover_address(&msg, &signature);
162+
assert_eq!(result, Err(CryptoError::Signature(signature)));
155163
}
156164

157165
fn u8_slice<const N: usize>(str: &str) -> [u8; N] {

crates/redstone/src/network/error.rs

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
use alloc::{string::String, vec::Vec};
2-
use core::fmt::{Debug, Display, Formatter};
2+
use core::{
3+
fmt::{Debug, Display, Formatter},
4+
num::TryFromIntError,
5+
};
36

47
use crate::{
58
network::as_str::{AsAsciiStr, AsHexStr},
@@ -131,6 +134,12 @@ pub enum Error {
131134
///
132135
/// Includes the value of a current update timestamp and the last update timestamp.
133136
CurrentTimestampMustBeGreaterThanLatestUpdateTimestamp(TimestampMillis, TimestampMillis),
137+
138+
/// Indicates error while converting from one type of the integer to the other.
139+
NumberConversionFail,
140+
141+
/// Indicates error of usize overflow.
142+
UsizeOverflow,
134143
}
135144

136145
impl From<CryptoError> for Error {
@@ -139,6 +148,12 @@ impl From<CryptoError> for Error {
139148
}
140149
}
141150

151+
impl From<TryFromIntError> for Error {
152+
fn from(_: TryFromIntError) -> Self {
153+
Self::NumberConversionFail
154+
}
155+
}
156+
142157
impl Error {
143158
pub fn code(&self) -> u16 {
144159
match self {
@@ -163,6 +178,8 @@ impl Error {
163178
Error::TimestampTooFuture(data_package_index, _) => 1050 + *data_package_index as u16,
164179
Error::DataTimestampMustBeGreaterThanBefore(_, _) => 1101,
165180
Error::CurrentTimestampMustBeGreaterThanLatestUpdateTimestamp(_, _) => 1102,
181+
Error::NumberConversionFail => 1200,
182+
Error::UsizeOverflow => 1300,
166183
}
167184
}
168185
}
@@ -233,16 +250,20 @@ impl Display for Error {
233250
),
234251
Error::DataTimestampMustBeGreaterThanBefore(current, before) => {
235252
write!(
236-
f,
237-
"Package timestamp: {current:?} must be greater than package timestamp before: {before:?}"
238-
)
253+
f,
254+
"Package timestamp: {current:?} must be greater than package timestamp before: {before:?}"
255+
)
239256
}
240257
Error::CurrentTimestampMustBeGreaterThanLatestUpdateTimestamp(current, last) => {
241258
write!(
242-
f,
243-
"Current update timestamp: {current:?} must be greater than latest update timestamp: {last:?}"
244-
)
259+
f,
260+
"Current update timestamp: {current:?} must be greater than latest update timestamp: {last:?}"
261+
)
262+
}
263+
Error::NumberConversionFail => {
264+
write!(f, "Number conversion failed")
245265
}
266+
Error::UsizeOverflow => write!(f, "Usize overflow"),
246267
}
247268
}
248269
}

crates/redstone/src/protocol/payload.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
use alloc::vec::Vec;
2+
13
use crate::{
24
core::validator::Validator, network::error::Error, protocol::data_package::DataPackage,
35
TimestampMillis,
46
};
5-
use alloc::vec::Vec;
67

78
#[derive(Clone, Debug)]
89
pub struct Payload {
@@ -14,7 +15,7 @@ impl Payload {
1415
&self,
1516
validator: &impl Validator,
1617
) -> Result<TimestampMillis, Error> {
17-
let Some(first_package) = self.data_packages.get(0) else {
18+
let Some(first_package) = self.data_packages.first() else {
1819
return Err(Error::ArrayIsEmpty);
1920
};
2021

crates/redstone/src/protocol/payload_decoder.rs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use alloc::vec::Vec;
2+
use core::convert::TryInto;
23
use core::marker::PhantomData;
34

45
use crate::{
@@ -41,10 +42,12 @@ impl<Env: Environment, C: Crypto> PayloadDecoder<Env, C> {
4142
}
4243

4344
fn trim_metadata(payload: &mut Vec<u8>) -> Result<usize, Error> {
44-
let unsigned_metadata_size = payload.try_trim_end(UNSIGNED_METADATA_BYTE_SIZE_BS)?;
45+
let unsigned_metadata_size = payload
46+
.try_trim_end(UNSIGNED_METADATA_BYTE_SIZE_BS)?
47+
.try_into()?;
4548
let _: Vec<u8> = payload.trim_end(unsigned_metadata_size);
4649

47-
let data_package_count = payload.try_trim_end(DATA_PACKAGES_COUNT_BS)?;
50+
let data_package_count = payload.try_trim_end(DATA_PACKAGES_COUNT_BS)?.try_into()?;
4851

4952
Ok(data_package_count)
5053
}
@@ -67,15 +70,21 @@ impl<Env: Environment, C: Crypto> PayloadDecoder<Env, C> {
6770
let data_point_count = payload.try_trim_end(DATA_POINTS_COUNT_BS)?;
6871
let value_size = payload.try_trim_end(DATA_POINT_VALUE_BYTE_SIZE_BS)?;
6972
let timestamp = payload.try_trim_end(TIMESTAMP_BS)?;
70-
let size = data_point_count * (value_size + DATA_FEED_ID_BS)
71-
+ DATA_POINT_VALUE_BYTE_SIZE_BS
72-
+ TIMESTAMP_BS
73-
+ DATA_POINTS_COUNT_BS;
7473

75-
let signable_bytes: Vec<_> = tmp.trim_end(size);
74+
let size: u64 = data_point_count
75+
* (value_size + TryInto::<u64>::try_into(DATA_FEED_ID_BS)?)
76+
+ TryInto::<u64>::try_into(DATA_POINT_VALUE_BYTE_SIZE_BS)?
77+
+ TryInto::<u64>::try_into(TIMESTAMP_BS)?
78+
+ TryInto::<u64>::try_into(DATA_POINTS_COUNT_BS)?;
79+
80+
let signable_bytes: Vec<_> = tmp.trim_end(size.try_into()?);
7681
let signer_address = C::recover_address(signable_bytes, signature)?;
7782

78-
let data_points = Self::trim_data_points(payload, data_point_count, value_size)?;
83+
let data_points = Self::trim_data_points(
84+
payload,
85+
data_point_count.try_into()?,
86+
value_size.try_into()?,
87+
)?;
7988

8089
Ok(DataPackage {
8190
data_points,
@@ -268,7 +277,7 @@ mod tests {
268277
}
269278
}
270279

271-
#[should_panic(expected = "range end index 64 out of range for slice of length 0")]
280+
#[should_panic(expected = "CryptographicError(InvalidSignatureLen(0))")]
272281
#[test]
273282
fn test_trim_data_packages_bigger_number() {
274283
test_trim_data_packages_of(3, "");

crates/redstone/src/utils/trim.rs

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use alloc::vec::Vec;
22

3-
use crate::{network::error::Error, FeedId, Value};
3+
use crate::{network::error::Error, FeedId};
44

55
pub trait Trim<T>
66
where
@@ -34,15 +34,6 @@ impl Trim<FeedId> for Vec<u8> {
3434
}
3535
}
3636

37-
impl TryTrim<usize> for Vec<u8> {
38-
fn try_trim_end(&mut self, len: usize) -> Result<usize, Error> {
39-
let y: u64 = self.try_trim_end(len)?;
40-
41-
y.try_into()
42-
.map_err(|_| Error::NumberOverflow(Value::from(y)))
43-
}
44-
}
45-
4637
impl TryTrim<u64> for Vec<u8> {
4738
fn try_trim_end(&mut self, len: usize) -> Result<u64, Error> {
4839
let y: Vec<u8> = self.trim_end(len);
@@ -86,12 +77,6 @@ mod tests {
8677

8778
let (_, result) = test_try_trim_end(3);
8879
assert_eq!(result, Ok(256u64.pow(2) * 30));
89-
90-
let (_, result) = test_try_trim_end(3);
91-
assert_eq!(result, Ok(256usize.pow(2) * 30));
92-
93-
let (_, result): (_, Vec<u8>) = test_trim_end(3);
94-
assert_eq!(result.as_slice(), &REDSTONE_MARKER[6..]);
9580
}
9681

9782
#[test]
@@ -101,10 +86,10 @@ mod tests {
10186
assert_eq!(rest.as_slice(), &REDSTONE_MARKER);
10287

10388
let (_, result) = test_try_trim_end(0);
104-
assert_eq!(result, Ok(0_usize));
89+
assert_eq!(result, Ok(0));
10590

10691
let (_, result) = test_try_trim_end(0);
107-
assert_eq!(result, Ok(0_usize));
92+
assert_eq!(result, Ok(0));
10893

10994
let (_, result): (_, Vec<u8>) = test_trim_end(0);
11095
assert_eq!(result, Vec::<u8>::new());
@@ -132,7 +117,7 @@ mod tests {
132117
#[cfg(not(target_arch = "wasm32"))]
133118
{
134119
let (_, result) = test_try_trim_end(size);
135-
assert_eq!(result, Ok(823907890102272usize));
120+
assert_eq!(result, Ok(823907890102272));
136121
}
137122

138123
let (_rest, result): (_, Vec<u8>) = test_trim_end(size);
@@ -150,17 +135,6 @@ mod tests {
150135
assert_eq!(x, 18446744073709551615);
151136
}
152137

153-
#[cfg(target_arch = "wasm32")]
154-
#[test]
155-
fn test_trim_end_u64_overflow_usize_wasm32() {
156-
let (_, output): (_, Result<usize, _>) = test_try_trim_end(REDSTONE_MARKER_BS);
157-
158-
assert_eq!(
159-
output,
160-
Err(Error::NumberOverflow(823907890102272_u64.into()))
161-
);
162-
}
163-
164138
#[test]
165139
fn test_trim_end_u64_overflow_u64() {
166140
let mut bytes = vec![1u8, 2, 3, 4, 5, 6, 7, 8, 9];

0 commit comments

Comments
 (0)