Skip to content

Commit ba889fa

Browse files
committed
add strict pointer and varuint parsing
1 parent 7ffae5c commit ba889fa

2 files changed

Lines changed: 187 additions & 5 deletions

File tree

pallas-addresses/src/lib.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,18 @@ pub type CertIdx = u64;
133133
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
134134
pub struct Pointer(Slot, TxIdx, CertIdx);
135135

136+
/// Errors produced by strict pointer parsing.
137+
#[derive(Error, Debug, PartialEq, Eq)]
138+
pub enum PointerStrictError {
139+
/// A variable-length uint inside a pointer address failed strict decoding.
140+
#[error("variable-length uint error: {0}")]
141+
VarUintError(varuint::StrictError),
142+
143+
/// The three pointer components were valid, but extra bytes remained.
144+
#[error("pointer payload has trailing bytes")]
145+
TrailingBytes,
146+
}
147+
136148
fn slice_to_hash(slice: &[u8]) -> Result<Hash<28>, Error> {
137149
if slice.len() == 28 {
138150
let mut sized = [0u8; 28];
@@ -159,6 +171,21 @@ impl Pointer {
159171
Ok(Pointer(a, b, c))
160172
}
161173

174+
/// Parse a pointer from its variable-length byte encoding, requiring
175+
/// canonical varuints and full input consumption.
176+
pub fn parse_strict(bytes: &[u8]) -> Result<Self, PointerStrictError> {
177+
let mut cursor = Cursor::new(bytes);
178+
let a = varuint::read_strict(&mut cursor).map_err(PointerStrictError::VarUintError)?;
179+
let b = varuint::read_strict(&mut cursor).map_err(PointerStrictError::VarUintError)?;
180+
let c = varuint::read_strict(&mut cursor).map_err(PointerStrictError::VarUintError)?;
181+
182+
if cursor.position() != bytes.len() as u64 {
183+
return Err(PointerStrictError::TrailingBytes);
184+
}
185+
186+
Ok(Pointer(a, b, c))
187+
}
188+
162189
/// Encode the pointer as its variable-length byte representation.
163190
pub fn to_vec(&self) -> Vec<u8> {
164191
let mut cursor = Cursor::new(vec![]);
@@ -1072,4 +1099,47 @@ mod tests {
10721099
);
10731100
assert!(matches!(addr, Ok(Address::Shelley(_))));
10741101
}
1102+
1103+
#[test]
1104+
fn pointer_parse_accepts_noncanonical_varuint_encoding() {
1105+
let noncanonical = [0x80, 0x00, 0x80, 0x00, 0x80, 0x00];
1106+
1107+
let parsed = Pointer::parse(&noncanonical).unwrap();
1108+
1109+
assert_eq!(parsed, Pointer::new(0, 0, 0));
1110+
assert_eq!(parsed.to_vec(), vec![0x00, 0x00, 0x00]);
1111+
assert_ne!(parsed.to_vec(), noncanonical);
1112+
}
1113+
1114+
#[test]
1115+
fn pointer_parse_strict_rejects_noncanonical_varuint_encoding() {
1116+
let noncanonical = [0x80, 0x00, 0x80, 0x00, 0x80, 0x00];
1117+
1118+
let parsed = Pointer::parse_strict(&noncanonical);
1119+
1120+
assert_eq!(
1121+
parsed,
1122+
Err(PointerStrictError::VarUintError(
1123+
varuint::StrictError::NonCanonical
1124+
))
1125+
);
1126+
}
1127+
1128+
#[test]
1129+
fn pointer_parse_strict_accepts_canonical_encoding() {
1130+
let canonical = [0x00, 0x00, 0x00];
1131+
1132+
let parsed = Pointer::parse_strict(&canonical).unwrap();
1133+
1134+
assert_eq!(parsed, Pointer::new(0, 0, 0));
1135+
}
1136+
1137+
#[test]
1138+
fn pointer_parse_strict_rejects_trailing_bytes() {
1139+
let bytes = [0x00, 0x00, 0x00, 0x00];
1140+
1141+
let parsed = Pointer::parse_strict(&bytes);
1142+
1143+
assert_eq!(parsed, Err(PointerStrictError::TrailingBytes));
1144+
}
10751145
}

pallas-addresses/src/varuint.rs

Lines changed: 117 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::io::{Cursor, Read, Write};
44

55
use thiserror::Error;
66

7-
#[derive(Error, Debug)]
7+
#[derive(Error, Debug, PartialEq, Eq)]
88
pub enum Error {
99
#[error("variable-length uint overflow")]
1010
VarUintOverflow,
@@ -13,20 +13,65 @@ pub enum Error {
1313
UnexpectedEof,
1414
}
1515

16+
#[derive(Error, Debug, PartialEq, Eq)]
17+
pub enum StrictError {
18+
#[error("variable-length uint overflow")]
19+
VarUintOverflow,
20+
21+
#[error("unexpected end-of-buffer")]
22+
UnexpectedEof,
23+
24+
#[error("non-canonical variable-length uint encoding")]
25+
NonCanonical,
26+
}
27+
1628
pub fn read(cursor: &mut Cursor<&[u8]>) -> Result<u64, Error> {
29+
read_inner(cursor, false).map_err(|e| match e {
30+
ReadModeError::Legacy(err) => err,
31+
ReadModeError::Strict(StrictError::VarUintOverflow) => Error::VarUintOverflow,
32+
ReadModeError::Strict(StrictError::UnexpectedEof) => Error::UnexpectedEof,
33+
ReadModeError::Strict(StrictError::NonCanonical) => unreachable!(),
34+
})
35+
}
36+
37+
pub fn read_strict(cursor: &mut Cursor<&[u8]>) -> Result<u64, StrictError> {
38+
read_inner(cursor, true).map_err(|e| match e {
39+
ReadModeError::Legacy(err) => match err {
40+
Error::VarUintOverflow => StrictError::VarUintOverflow,
41+
Error::UnexpectedEof => StrictError::UnexpectedEof,
42+
},
43+
ReadModeError::Strict(err) => err,
44+
})
45+
}
46+
47+
enum ReadModeError {
48+
Legacy(Error),
49+
Strict(StrictError),
50+
}
51+
52+
fn read_inner(cursor: &mut Cursor<&[u8]>, strict: bool) -> Result<u64, ReadModeError> {
1753
let mut output = 0u128;
1854
let mut buf = [0u8; 1];
55+
let start = cursor.position() as usize;
1956

2057
loop {
21-
cursor
22-
.read_exact(&mut buf)
23-
.map_err(|_| Error::UnexpectedEof)?;
58+
cursor.read_exact(&mut buf).map_err(|_| {
59+
if strict {
60+
ReadModeError::Strict(StrictError::UnexpectedEof)
61+
} else {
62+
ReadModeError::Legacy(Error::UnexpectedEof)
63+
}
64+
})?;
2465

2566
let byte = buf[0];
2667

2768
output = (output << 7) | (byte & 0x7F) as u128;
2869

2970
if output > u64::MAX.into() {
71+
if strict {
72+
return Err(ReadModeError::Strict(StrictError::VarUintOverflow));
73+
}
74+
3075
// Strictly speaking, if we find a value above max u64, an overflow error should
3176
// be returned. The problem is that testnet has some invalid address values
3277
// somehow minted in valid blocks. The node and many explorers, instead of
@@ -38,7 +83,19 @@ pub fn read(cursor: &mut Cursor<&[u8]>) -> Result<u64, Error> {
3883
}
3984

4085
if (byte & 0x80) == 0 {
41-
return Ok(output as u64);
86+
let output = output as u64;
87+
88+
if strict {
89+
let end = cursor.position() as usize;
90+
let consumed = &cursor.get_ref()[start..end];
91+
let canonical = encode_to_vec(output);
92+
93+
if consumed != canonical.as_slice() {
94+
return Err(ReadModeError::Strict(StrictError::NonCanonical));
95+
}
96+
}
97+
98+
return Ok(output);
4299
}
43100
}
44101
}
@@ -54,3 +111,58 @@ pub fn write(cursor: &mut Cursor<Vec<u8>>, mut num: u64) {
54111

55112
cursor.write_all(&output).unwrap();
56113
}
114+
115+
fn encode_to_vec(num: u64) -> Vec<u8> {
116+
let mut cursor = Cursor::new(vec![]);
117+
write(&mut cursor, num);
118+
cursor.into_inner()
119+
}
120+
121+
#[cfg(test)]
122+
mod tests {
123+
use super::*;
124+
125+
#[test]
126+
fn read_accepts_noncanonical_zero_for_compatibility() {
127+
let bytes = [0x80, 0x00];
128+
let mut cursor = Cursor::new(bytes.as_slice());
129+
130+
let value = read(&mut cursor).unwrap();
131+
132+
assert_eq!(value, 0);
133+
assert_eq!(cursor.position(), 2);
134+
}
135+
136+
#[test]
137+
fn read_strict_rejects_noncanonical_zero() {
138+
let bytes = [0x80, 0x00];
139+
let mut cursor = Cursor::new(bytes.as_slice());
140+
141+
let value = read_strict(&mut cursor);
142+
143+
assert_eq!(value, Err(StrictError::NonCanonical));
144+
}
145+
146+
#[test]
147+
fn read_strict_accepts_canonical_zero() {
148+
let bytes = [0x00];
149+
let mut cursor = Cursor::new(bytes.as_slice());
150+
151+
let value = read_strict(&mut cursor).unwrap();
152+
153+
assert_eq!(value, 0);
154+
assert_eq!(cursor.position(), 1);
155+
}
156+
157+
#[test]
158+
fn read_strict_rejects_overflow() {
159+
let bytes = [
160+
0x82, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00,
161+
];
162+
let mut cursor = Cursor::new(bytes.as_slice());
163+
164+
let value = read_strict(&mut cursor);
165+
166+
assert_eq!(value, Err(StrictError::VarUintOverflow));
167+
}
168+
}

0 commit comments

Comments
 (0)