Skip to content

Commit

Permalink
wip on scripts length control
Browse files Browse the repository at this point in the history
  • Loading branch information
dr-orlovsky committed Oct 7, 2023
1 parent 7403cd1 commit d22c215
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 29 deletions.
7 changes: 6 additions & 1 deletion primitives/src/coding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ use crate::{
TxIn, TxOut, TxVer, Txid, Vout, Witness, WitnessScript, LIB_NAME_BITCOIN,
};

/// Bitcoin consensus allows arrays which length is encoded as VarInt to grow up
/// to 64-bit values. However, at the same time no consensus rule allows any
/// block data structure to exceed 2^32 bytes (4GB), and any change to that rule
/// will be a hardfork. So for practical reasons we are safe to restrict the
/// maximum size here with just 32 bits.
pub type VarIntArray<T> = Confined<Vec<T>, 0, U32>;

/// A variable-length unsigned integer.
Expand Down Expand Up @@ -97,7 +102,7 @@ impl AsRef<[u8]> for ByteStr {
}

impl From<Vec<u8>> for ByteStr {
fn from(value: Vec<u8>) -> Self { Self(Confined::try_from(value).expect("u64 >= usize")) }
fn from(value: Vec<u8>) -> Self { Self(Confined::try_from(value).expect("u32 >= usize")) }
}

impl LowerHex for ByteStr {
Expand Down
94 changes: 76 additions & 18 deletions primitives/src/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

use std::fmt::{Formatter, LowerHex, UpperHex};

use amplify::confinement;
use amplify::confinement::Confined;
use amplify::hex::{self, FromHex, ToHex};

Expand Down Expand Up @@ -118,11 +119,14 @@ pub enum OpCode {
derive(Serialize, Deserialize),
serde(crate = "serde_crate", transparent)
)]
pub struct SigScript(
#[from]
#[from(Vec<u8>)]
ScriptBytes,
);
pub struct SigScript(ScriptBytes);

impl TryFrom<Vec<u8>> for SigScript {
type Error = confinement::Error;
fn try_from(script_bytes: Vec<u8>) -> Result<Self, Self::Error> {
ScriptBytes::try_from(script_bytes).map(Self)
}
}

impl FromHex for SigScript {
fn from_hex(s: &str) -> Result<Self, hex::Error> { ScriptBytes::from_hex(s).map(Self) }
Expand All @@ -134,7 +138,23 @@ impl FromHex for SigScript {
}

impl SigScript {
#[inline]
pub fn empty() -> Self { SigScript::default() }

#[inline]
pub fn new() -> Self { Self::default() }

#[inline]
pub fn with_capacity(capacity: usize) -> Self {
Self(ScriptBytes::from(Confined::with_capacity(capacity)))
}

/// Constructs script object assuming the script length is less than 4GB.
/// Panics otherwise.
#[inline]
fn from_unsafe(script_bytes: Vec<u8>) -> Self { Self(ScriptBytes::from_unsafe(script_bytes)) }

#[inline]
pub fn as_script_bytes(&self) -> &ScriptBytes { &self.0 }
}

Expand All @@ -148,19 +168,29 @@ impl SigScript {
derive(Serialize, Deserialize),
serde(crate = "serde_crate", transparent)
)]
pub struct ScriptPubkey(
#[from]
#[from(Vec<u8>)]
ScriptBytes,
);
pub struct ScriptPubkey(ScriptBytes);

impl TryFrom<Vec<u8>> for ScriptPubkey {
type Error = confinement::Error;
fn try_from(script_bytes: Vec<u8>) -> Result<Self, Self::Error> {
ScriptBytes::try_from(script_bytes).map(Self)
}
}

impl ScriptPubkey {
#[inline]
pub fn new() -> Self { Self::default() }

#[inline]
pub fn with_capacity(capacity: usize) -> Self {
Self(ScriptBytes::from(Confined::with_capacity(capacity)))
}

/// Constructs script object assuming the script length is less than 4GB.
/// Panics otherwise.
#[inline]
fn from_unsafe(script_bytes: Vec<u8>) -> Self { Self(ScriptBytes::from_unsafe(script_bytes)) }

pub fn p2pkh(hash: impl Into<[u8; 20]>) -> Self {
let mut script = Self::with_capacity(25);
script.push_opcode(OpCode::Dup);
Expand Down Expand Up @@ -206,11 +236,14 @@ impl ScriptPubkey {
self.0[22] == OP_EQUAL
}

#[inline]
pub fn is_op_return(&self) -> bool { self[0] == OpCode::Return as u8 }

/// Adds a single opcode to the script.
#[inline]
pub fn push_opcode(&mut self, op_code: OpCode) { self.0.push(op_code as u8) }

#[inline]
pub fn as_script_bytes(&self) -> &ScriptBytes { &self.0 }
}

Expand All @@ -233,22 +266,34 @@ impl FromHex for ScriptPubkey {
derive(Serialize, Deserialize),
serde(crate = "serde_crate", transparent)
)]
pub struct RedeemScript(
#[from]
#[from(Vec<u8>)]
ScriptBytes,
);
pub struct RedeemScript(ScriptBytes);

impl TryFrom<Vec<u8>> for RedeemScript {
type Error = confinement::Error;
fn try_from(script_bytes: Vec<u8>) -> Result<Self, Self::Error> {
ScriptBytes::try_from(script_bytes).map(Self)
}
}

impl RedeemScript {
#[inline]
pub fn new() -> Self { Self::default() }

#[inline]
pub fn with_capacity(capacity: usize) -> Self {
Self(ScriptBytes::from(Confined::with_capacity(capacity)))
}

/// Constructs script object assuming the script length is less than 4GB.
/// Panics otherwise.
#[inline]
fn from_unsafe(script_bytes: Vec<u8>) -> Self { Self(ScriptBytes::from_unsafe(script_bytes)) }

/// Adds a single opcode to the script.
#[inline]
pub fn push_opcode(&mut self, op_code: OpCode) { self.0.push(op_code as u8); }

#[inline]
pub fn as_script_bytes(&self) -> &ScriptBytes { &self.0 }
}

Expand All @@ -268,8 +313,11 @@ impl FromHex for RedeemScript {
#[wrapper_mut(DerefMut, IndexMut, RangeMut, BorrowSliceMut)]
pub struct ScriptBytes(VarIntArray<u8>);

impl From<Vec<u8>> for ScriptBytes {
fn from(value: Vec<u8>) -> Self { Self(Confined::try_from(value).expect("u64 >= usize")) }
impl TryFrom<Vec<u8>> for ScriptBytes {
type Error = confinement::Error;
fn try_from(script_bytes: Vec<u8>) -> Result<Self, Self::Error> {
Confined::try_from(script_bytes).map(Self)
}
}

impl LowerHex for ScriptBytes {
Expand All @@ -285,14 +333,24 @@ impl UpperHex for ScriptBytes {
}

impl FromHex for ScriptBytes {
fn from_hex(s: &str) -> Result<Self, hex::Error> { Vec::<u8>::from_hex(s).map(Self::from) }
fn from_hex(s: &str) -> Result<Self, hex::Error> {
let bytes = Vec::<u8>::from_hex(s)?;
Self::try_from(bytes).map_err(|_| hex::Error::InvalidLength(u32::MAX as usize, bytes.len()))
}
fn from_byte_iter<I>(_: I) -> Result<Self, hex::Error>
where I: Iterator<Item = Result<u8, hex::Error>> + ExactSizeIterator + DoubleEndedIterator {
unreachable!()
}
}

impl ScriptBytes {
/// Constructs script object assuming the script length is less than 4GB.
/// Panics otherwise.
#[inline]
pub fn from_unsafe(script_bytes: Vec<u8>) -> Self {
Self(Confined::try_from(script_bytes).expect("script exceeding 4GB"))
}

/// Adds instructions to push some arbitrary data onto the stack.
///
/// ## Panics
Expand Down
36 changes: 26 additions & 10 deletions primitives/src/segwit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ use std::vec;

use amplify::confinement::Confined;
use amplify::hex::{self, FromHex};
use amplify::Bytes32StrRev;
use amplify::{confinement, Bytes32StrRev};

use crate::opcodes::*;
use crate::{OpCode, ScriptBytes, ScriptPubkey, VarIntArray, LIB_NAME_BITCOIN};
use crate::{OpCode, ScriptBytes, ScriptPubkey, VarIntArray, LIB_NAME_BITCOIN, ByteStr};

#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display, Error)]
#[display(doc_comments)]
Expand Down Expand Up @@ -311,22 +311,34 @@ impl ScriptPubkey {
derive(Serialize, Deserialize),
serde(crate = "serde_crate", transparent)
)]
pub struct WitnessScript(
#[from]
#[from(Vec<u8>)]
ScriptBytes,
);
pub struct WitnessScript(ScriptBytes);

impl TryFrom<Vec<u8>> for WitnessScript {
type Error = confinement::Error;
fn try_from(script_bytes: Vec<u8>) -> Result<Self, Self::Error> {
ScriptBytes::try_from(script_bytes).map(Self)
}
}

impl WitnessScript {
#[inline]
pub fn new() -> Self { Self::default() }

#[inline]
pub fn with_capacity(capacity: usize) -> Self {
Self(ScriptBytes::from(Confined::with_capacity(capacity)))
}

/// Constructs script object assuming the script length is less than 4GB.
/// Panics otherwise.
#[inline]
fn from_unsafe(script_bytes: Vec<u8>) -> Self { Self(ScriptBytes::from_unsafe(script_bytes)) }

/// Adds a single opcode to the script.
#[inline]
pub fn push_opcode(&mut self, op_code: OpCode) { self.0.push(op_code as u8); }

#[inline]
pub fn as_script_bytes(&self) -> &ScriptBytes { &self.0 }
}

Expand Down Expand Up @@ -367,7 +379,7 @@ impl FromHex for Wtxid {
#[wrapper(Deref, Index, RangeOps)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_BITCOIN)]
pub struct Witness(VarIntArray<VarIntArray<u8>>);
pub struct Witness(VarIntArray<ByteStr>);

impl IntoIterator for Witness {
type Item = VarIntArray<u8>;
Expand All @@ -377,21 +389,24 @@ impl IntoIterator for Witness {
}

impl Witness {
#[inline]
pub fn new() -> Self { default!() }

#[inline]
pub fn elements(&self) -> impl Iterator<Item = &'_ [u8]> {
self.0.iter().map(|el| el.as_slice())
}

pub fn from_consensus_stack(witness: impl IntoIterator<Item = Vec<u8>>) -> Witness {
let iter = witness.into_iter().map(|vec| {
VarIntArray::try_from(vec).expect("witness stack element length exceeds 2^64 bytes")
VarIntArray::try_from(vec).expect("witness stack element length exceeds 2^32 bytes")
});
let stack =
VarIntArray::try_from_iter(iter).expect("witness stack size exceeds 2^64 bytes");
VarIntArray::try_from_iter(iter).expect("witness stack size exceeds 2^32 elements");
Witness(stack)
}

#[inline]
pub(crate) fn as_var_int_array(&self) -> &VarIntArray<VarIntArray<u8>> { &self.0 }
}

Expand All @@ -409,6 +424,7 @@ mod _serde {
where S: Serializer {
let mut ser = serializer.serialize_seq(Some(self.len()))?;
for el in &self.0 {
let bytes = ScriptBytes::try_from(el.to_inner()).map_err(|_| )?;

Check failure on line 427 in primitives/src/segwit.rs

View workflow job for this annotation

GitHub Actions / fmt

expected expression, found `)`
ser.serialize_element(&ScriptBytes::from(el.to_inner()))?;
}
ser.end()
Expand Down
16 changes: 16 additions & 0 deletions primitives/src/taproot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -607,16 +607,32 @@ pub enum TapCode {
pub struct TapScript(ScriptBytes);
// TODO: impl Display/FromStr for TapScript providing correct opcodes

impl TryFrom<Vec<u8>> for TapScript {
type Error = confinement::Error;
fn try_from(script_bytes: Vec<u8>) -> Result<Self, Self::Error> {
ScriptBytes::try_from(script_bytes).map(Self)
}
}

impl TapScript {
#[inline]
pub fn new() -> Self { Self::default() }

#[inline]
pub fn with_capacity(capacity: usize) -> Self {
Self(ScriptBytes::from(Confined::with_capacity(capacity)))
}

/// Constructs script object assuming the script length is less than 4GB.
/// Panics otherwise.
#[inline]
fn from_unsafe(script_bytes: Vec<u8>) -> Self { Self(ScriptBytes::from_unsafe(script_bytes)) }

/// Adds a single opcode to the script.
#[inline]
pub fn push_opcode(&mut self, op_code: TapCode) { self.0.push(op_code as u8); }

#[inline]
pub fn as_script_bytes(&self) -> &ScriptBytes { &self.0 }
}

Expand Down

0 comments on commit d22c215

Please sign in to comment.