Merge rust-bitcoin/rust-bitcoin#3293: priority: Re-write (and re-name) `read_uint_iter`

0f897f80a5 Re-write (and re-name) read_uint_iter (Tobin C. Harding)

Pull request description:

  The `UintError` type (returned by `read_uint_iter`) is not that useful because one variant is unreachable. Re-write the function by doing:n

  - Re-write the function to reduce the error cases returned.
  - Re-name it to `read_push_data_len`
  - Move it to `internals`
  - Use `PushDataLenLen` enum instead of an int parameter

ACKs for top commit:
  apoelstra:
    ACK 0f897f80a5 successfully ran local tests; lol so much better than the old code
  Kixunil:
    ACK 0f897f80a5

Tree-SHA512: 095017a32c2d5bb2268cb1a059d0022e122faf8b41295f14970e7968374dd1c35c3b95357aba5aabaa17843439aebc237000009015ea9b8bc58ab1b337e8e1bc
This commit is contained in:
merge-script 2024-09-09 16:37:30 +00:00
commit cfe6c0a999
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
4 changed files with 87 additions and 71 deletions

View File

@ -1,7 +1,9 @@
// SPDX-License-Identifier: CC0-1.0 // SPDX-License-Identifier: CC0-1.0
use internals::script::{self, PushDataLenLen};
use super::{ use super::{
read_uint_iter, Error, PushBytes, Script, ScriptBuf, ScriptBufExtPriv as _, UintError, Error, PushBytes, Script, ScriptBuf, ScriptBufExtPriv as _,
}; };
use crate::opcodes::{self, Opcode}; use crate::opcodes::{self, Opcode};
@ -129,13 +131,9 @@ impl<'a> Instructions<'a> {
len: PushDataLenLen, len: PushDataLenLen,
min_push_len: usize, min_push_len: usize,
) -> Option<Result<Instruction<'a>, Error>> { ) -> Option<Result<Instruction<'a>, Error>> {
let n = match read_uint_iter(&mut self.data, len as usize) { let n = match script::read_push_data_len(&mut self.data, len) {
Ok(n) => n, Ok(n) => n,
// We do exhaustive matching to not forget to handle new variants if we extend Err(_) => {
// `UintError` type.
// Overflow actually means early end of script (script is definitely shorter
// than `usize::MAX`)
Err(UintError::EarlyEndOfScript) | Err(UintError::NumericOverflow) => {
self.kill(); self.kill();
return Some(Err(Error::EarlyEndOfScript)); return Some(Err(Error::EarlyEndOfScript));
} }
@ -153,15 +151,6 @@ impl<'a> Instructions<'a> {
} }
} }
/// Allowed length of push data length.
///
/// This makes it easier to prove correctness of `next_push_data_len`.
pub(super) enum PushDataLenLen {
One = 1,
Two = 2,
Four = 4,
}
impl<'a> Iterator for Instructions<'a> { impl<'a> Iterator for Instructions<'a> {
type Item = Result<Instruction<'a>, Error>; type Item = Result<Instruction<'a>, Error>;

View File

@ -65,7 +65,8 @@ use core::fmt;
use core::ops::{Deref, DerefMut}; use core::ops::{Deref, DerefMut};
use hashes::{hash160, sha256}; use hashes::{hash160, sha256};
use internals::impl_to_hex_from_lower_hex; use internals::{impl_to_hex_from_lower_hex};
use internals::script::{self, PushDataLenLen};
use io::{BufRead, Write}; use io::{BufRead, Write};
use crate::consensus::{encode, Decodable, Encodable}; use crate::consensus::{encode, Decodable, Encodable};
@ -294,28 +295,6 @@ pub fn read_scriptbool(v: &[u8]) -> bool {
} }
} }
// We internally use implementation based on iterator so that it automatically advances as needed
// Errors are same as above, just different type.
fn read_uint_iter(data: &mut core::slice::Iter<'_, u8>, size: usize) -> Result<usize, UintError> {
if data.len() < size {
Err(UintError::EarlyEndOfScript)
} else if size > usize::from(u16::MAX / 8) {
// Casting to u32 would overflow
Err(UintError::NumericOverflow)
} else {
let mut ret = 0;
for (i, item) in data.take(size).enumerate() {
ret = usize::from(*item)
// Casting is safe because we checked above to not repeat the same check in a loop
.checked_shl((i * 8) as u32)
.ok_or(UintError::NumericOverflow)?
.checked_add(ret)
.ok_or(UintError::NumericOverflow)?;
}
Ok(ret)
}
}
fn opcode_to_verify(opcode: Option<Opcode>) -> Option<Opcode> { fn opcode_to_verify(opcode: Option<Opcode>) -> Option<Opcode> {
opcode.and_then(|opcode| match opcode { opcode.and_then(|opcode| match opcode {
OP_EQUAL => Some(OP_EQUALVERIFY), OP_EQUAL => Some(OP_EQUALVERIFY),
@ -661,23 +640,15 @@ impl Decodable for ScriptBuf {
pub(super) fn bytes_to_asm_fmt(script: &[u8], f: &mut dyn fmt::Write) -> fmt::Result { pub(super) fn bytes_to_asm_fmt(script: &[u8], f: &mut dyn fmt::Write) -> fmt::Result {
// This has to be a macro because it needs to break the loop // This has to be a macro because it needs to break the loop
macro_rules! read_push_data_len { macro_rules! read_push_data_len {
($iter:expr, $len:literal, $formatter:expr) => { ($iter:expr, $size:path, $formatter:expr) => {
match read_uint_iter($iter, $len) { match script::read_push_data_len($iter, $size) {
Ok(n) => { Ok(n) => {
n n
}, },
Err(UintError::EarlyEndOfScript) => { Err(_) => {
$formatter.write_str("<unexpected end>")?; $formatter.write_str("<unexpected end>")?;
break; break;
} }
// We got the data in a slice which implies it being shorter than `usize::MAX`
// So if we got overflow, we can confidently say the number is higher than length of
// the slice even though we don't know the exact number. This implies attempt to push
// past end.
Err(UintError::NumericOverflow) => {
$formatter.write_str("<push past end>")?;
break;
}
} }
} }
} }
@ -698,15 +669,15 @@ pub(super) fn bytes_to_asm_fmt(script: &[u8], f: &mut dyn fmt::Write) -> fmt::Re
match opcode { match opcode {
OP_PUSHDATA1 => { OP_PUSHDATA1 => {
// side effects: may write and break from the loop // side effects: may write and break from the loop
read_push_data_len!(&mut iter, 1, f) read_push_data_len!(&mut iter, PushDataLenLen::One, f)
} }
OP_PUSHDATA2 => { OP_PUSHDATA2 => {
// side effects: may write and break from the loop // side effects: may write and break from the loop
read_push_data_len!(&mut iter, 2, f) read_push_data_len!(&mut iter, PushDataLenLen::Two, f)
} }
OP_PUSHDATA4 => { OP_PUSHDATA4 => {
// side effects: may write and break from the loop // side effects: may write and break from the loop
read_push_data_len!(&mut iter, 4, f) read_push_data_len!(&mut iter, PushDataLenLen::Four, f)
} }
_ => 0, _ => 0,
} }
@ -791,24 +762,6 @@ impl std::error::Error for Error {
} }
} }
// Our internal error proves that we only return these two cases from `read_uint_iter`.
// Since it's private we don't bother with trait impls besides From.
enum UintError {
EarlyEndOfScript,
NumericOverflow,
}
internals::impl_from_infallible!(UintError);
impl From<UintError> for Error {
fn from(error: UintError) -> Self {
match error {
UintError::EarlyEndOfScript => Error::EarlyEndOfScript,
UintError::NumericOverflow => Error::NumericOverflow,
}
}
}
/// Error while hashing a redeem script. /// Error while hashing a redeem script.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct RedeemScriptSizeError { pub struct RedeemScriptSizeError {

View File

@ -39,6 +39,7 @@ pub mod const_tools;
pub mod error; pub mod error;
pub mod macros; pub mod macros;
mod parse; mod parse;
pub mod script;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
#[macro_use] #[macro_use]
pub mod serde; pub mod serde;

73
internals/src/script.rs Normal file
View File

@ -0,0 +1,73 @@
// SPDX-License-Identifier: CC0-1.0
//! Internal script related helper functions and types.
/// Reads a `usize` from an iterator.
///
/// A script push data instruction includes the length of the data being pushed, this function reads
/// that length from an iterator (encoded in either 1, 2, or 4 bytes).
// We internally use implementation based on iterator so that it automatically advances as needed.
pub fn read_push_data_len(
data: &mut core::slice::Iter<'_, u8>,
size: PushDataLenLen,
) -> Result<usize, EarlyEndOfScriptError> {
// The `size` enum enforces that the maximum shift will be 32 and
// that we can only ever read up to 4 bytes.
let size = size as usize;
if data.len() < size {
return Err(EarlyEndOfScriptError);
};
let mut ret = 0;
for (i, item) in data.take(size).enumerate() {
ret |= usize::from(*item) << (i * 8);
}
Ok(ret)
}
/// The number of bytes used to encode an unsigned integer as the length of a push data instruction.
///
/// This makes it easier to prove correctness of `next_push_data_len` and `read_push_data_len`.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum PushDataLenLen {
/// Unsigned integer comprising of a single byte.
One = 1,
/// Unsigned integer comprising of two bytes.
Two = 2,
/// Unsigned integer comprising of four bytes.
Four = 4,
}
/// Indicates that we tried to read more bytes from the script than available.
#[derive(Debug)]
pub struct EarlyEndOfScriptError;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reads_4_bytes() {
let bytes = [0x01, 0x23, 0x45, 0x67];
let want = u32::from_le_bytes([0x01, 0x23, 0x45, 0x67]);
let got = read_push_data_len(&mut bytes.iter(), PushDataLenLen::Four).unwrap();
assert_eq!(got, want as usize)
}
#[test]
fn reads_2_bytes() {
let bytes = [0x01, 0x23];
let want = u16::from_le_bytes([0x01, 0x23]);
let got = read_push_data_len(&mut bytes.iter(), PushDataLenLen::Two).unwrap();
assert_eq!(got, want as usize)
}
#[test]
fn reads_1_byte() {
let bytes = [0x01];
let want = 0x01;
let got = read_push_data_len(&mut bytes.iter(), PushDataLenLen::One).unwrap();
assert_eq!(got, want as usize)
}
}