Skip to content

Commit

Permalink
Implement int encoding directly
Browse files Browse the repository at this point in the history
  • Loading branch information
Rigidity committed Jul 28, 2024
1 parent d478acc commit 38ed939
Show file tree
Hide file tree
Showing 9 changed files with 227 additions and 37 deletions.
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions crates/clvm-traits/fuzz/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
target
corpus
artifacts
coverage
20 changes: 20 additions & 0 deletions crates/clvm-traits/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "clvm-traits-fuzz"
version = "0.0.0"
publish = false
edition = "2021"

[package.metadata]
cargo-fuzz = true

[dependencies]
libfuzzer-sys = { workspace = true }
clvmr = { workspace = true }
clvm-traits = { workspace = true }

[[bin]]
name = "int_encoding"
path = "fuzz_targets/int_encoding.rs"
test = false
doc = false
bench = false
40 changes: 40 additions & 0 deletions crates/clvm-traits/fuzz/fuzz_targets/int_encoding.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#![no_main]

use clvm_traits::{decode_number, encode_number};
use clvmr::Allocator;
use libfuzzer_sys::{arbitrary::Unstructured, fuzz_target};

fuzz_target!(|data: &[u8]| {
let mut unstructured = Unstructured::new(data);

macro_rules! impl_num {
( $num_type:ty, $signed:expr ) => {
let num: $num_type = unstructured.arbitrary().unwrap();
let mut allocator = Allocator::new();
let ptr = allocator.new_number(num.into()).unwrap();
let atom = allocator.atom(ptr);
let expected = atom.as_ref();

#[allow(unused_comparisons)]
let encoded = encode_number(&num.to_be_bytes(), num < 0);
assert_eq!(expected, encoded);

let expected = num.to_be_bytes();
let decoded = decode_number(&encoded, $signed).unwrap();
assert_eq!(expected, decoded);
};
}

impl_num!(u8, false);
impl_num!(i8, true);
impl_num!(u16, false);
impl_num!(i16, true);
impl_num!(u32, false);
impl_num!(i32, true);
impl_num!(u64, false);
impl_num!(i64, true);
impl_num!(u128, false);
impl_num!(i128, true);
impl_num!(usize, false);
impl_num!(isize, true);
});
14 changes: 14 additions & 0 deletions crates/clvm-traits/src/clvm_decoder.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use clvmr::{allocator::SExp, Allocator, Atom, NodePtr};
use num_bigint::BigInt;

use crate::{
destructure_list, destructure_quote, match_list, match_quote, FromClvm, FromClvmError,
Expand All @@ -11,6 +12,11 @@ pub trait ClvmDecoder: Sized {
fn decode_atom(&self, node: &Self::Node) -> Result<Atom<'_>, FromClvmError>;
fn decode_pair(&self, node: &Self::Node) -> Result<(Self::Node, Self::Node), FromClvmError>;

fn decode_bigint(&self, node: &Self::Node) -> Result<BigInt, FromClvmError> {
let atom = self.decode_atom(node)?;
Ok(BigInt::from_signed_bytes_be(atom.as_ref()))
}

fn decode_curried_arg(
&self,
node: &Self::Node,
Expand Down Expand Up @@ -49,6 +55,14 @@ impl ClvmDecoder for Allocator {
Err(FromClvmError::ExpectedPair)
}
}

fn decode_bigint(&self, node: &Self::Node) -> Result<BigInt, FromClvmError> {
if let SExp::Atom = self.sexp(*node) {
Ok(self.number(*node))
} else {
Err(FromClvmError::ExpectedAtom)
}
}
}

impl FromClvm<Allocator> for NodePtr {
Expand Down
65 changes: 30 additions & 35 deletions crates/clvm-traits/src/from_clvm.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use std::{rc::Rc, sync::Arc};

use num_bigint::{BigInt, Sign};
use num_bigint::BigInt;

use crate::{ClvmDecoder, FromClvmError};
use crate::{decode_number, ClvmDecoder, FromClvmError};

pub trait FromClvm<D>: Sized
where
Expand All @@ -12,50 +12,45 @@ where
}

macro_rules! clvm_primitive {
($primitive:ty) => {
($primitive:ty, $signed:expr) => {
impl<N, D: ClvmDecoder<Node = N>> FromClvm<D> for $primitive {
fn from_clvm(decoder: &D, node: N) -> Result<Self, FromClvmError> {
const LEN: usize = std::mem::size_of::<$primitive>();

let bytes = decoder.decode_atom(&node)?;
let number = BigInt::from_signed_bytes_be(bytes.as_ref());
let (sign, mut vec) = number.to_bytes_be();

if vec.len() < std::mem::size_of::<$primitive>() {
let mut zeros = vec![0; LEN - vec.len()];
zeros.extend(vec);
vec = zeros;
}
let atom = decoder.decode_atom(&node)?;
let slice = atom.as_ref();

let value = <$primitive>::from_be_bytes(vec.as_slice().try_into().or(Err(
FromClvmError::WrongAtomLength {
let Some(bytes) = decode_number(slice, $signed) else {
return Err(FromClvmError::WrongAtomLength {
expected: LEN,
found: bytes.as_ref().len(),
},
))?);

Ok(if sign == Sign::Minus {
value.wrapping_neg()
} else {
value
})
found: slice.len(),
});
};

Ok(<$primitive>::from_be_bytes(bytes))
}
}
};
}

clvm_primitive!(u8);
clvm_primitive!(i8);
clvm_primitive!(u16);
clvm_primitive!(i16);
clvm_primitive!(u32);
clvm_primitive!(i32);
clvm_primitive!(u64);
clvm_primitive!(i64);
clvm_primitive!(u128);
clvm_primitive!(i128);
clvm_primitive!(usize);
clvm_primitive!(isize);
clvm_primitive!(u8, false);
clvm_primitive!(i8, true);
clvm_primitive!(u16, false);
clvm_primitive!(i16, true);
clvm_primitive!(u32, false);
clvm_primitive!(i32, true);
clvm_primitive!(u64, false);
clvm_primitive!(i64, true);
clvm_primitive!(u128, false);
clvm_primitive!(i128, true);
clvm_primitive!(usize, false);
clvm_primitive!(isize, true);

impl<N, D: ClvmDecoder<Node = N>> FromClvm<D> for BigInt {
fn from_clvm(decoder: &D, node: N) -> Result<Self, FromClvmError> {
decoder.decode_bigint(&node)
}
}

impl<N, D: ClvmDecoder<Node = N>> FromClvm<D> for bool {
fn from_clvm(decoder: &D, node: N) -> Result<Self, FromClvmError> {
Expand Down
98 changes: 98 additions & 0 deletions crates/clvm-traits/src/int_encoding.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
pub fn encode_number(slice: &[u8], negative: bool) -> Vec<u8> {
let mut start = 0;
let pad_byte = if negative { 0xFF } else { 0x00 };

// Skip leading pad bytes
while start < slice.len() && slice[start] == pad_byte {
start += 1;
}

let needs_padding = if negative {
start == slice.len() || (slice[start] & 0x80) == 0
} else {
start < slice.len() && (slice[start] & 0x80) != 0
};

let mut result = Vec::with_capacity(if needs_padding {
slice.len() - start + 1
} else {
slice.len() - start
});

if needs_padding {
result.push(pad_byte);
}

result.extend_from_slice(&slice[start..]);
result
}

pub fn decode_number<const LEN: usize>(mut slice: &[u8], signed: bool) -> Option<[u8; LEN]> {
let negative = signed && !slice.is_empty() && slice[0] & 0x80 != 0;
let padding_byte = if negative { 0xFF } else { 0x00 };

if slice.len() > LEN && slice[0] == padding_byte {
slice = &slice[slice.len() - LEN..];
}

if slice.len() > LEN {
return None;
}

assert!(slice.len() <= LEN);

let mut result = [padding_byte; LEN];
let start = LEN - slice.len();

result[start..].copy_from_slice(slice);

Some(result)
}

#[cfg(test)]
mod tests {
use super::*;

use clvmr::Allocator;

macro_rules! test_roundtrip {
( $num:expr, $signed:expr ) => {
let mut allocator = Allocator::new();
let ptr = allocator.new_number($num.into()).unwrap();
let atom = allocator.atom(ptr);
let expected = atom.as_ref();

#[allow(unused_comparisons)]
let encoded = encode_number(&$num.to_be_bytes(), $num < 0);
assert_eq!(expected, encoded);

let expected = $num.to_be_bytes();
let decoded = decode_number(&encoded, $signed).unwrap();
assert_eq!(expected, decoded);
};
}

#[test]
fn test_signed_encoding() {
test_roundtrip!(0i32, true);
test_roundtrip!(1i32, true);
test_roundtrip!(2i32, true);
test_roundtrip!(3i32, true);
test_roundtrip!(255i32, true);
test_roundtrip!(4716i32, true);
test_roundtrip!(-255i32, true);
test_roundtrip!(-10i32, true);
test_roundtrip!(i32::MIN, true);
test_roundtrip!(i32::MAX, true);
}

#[test]
fn test_unsigned_encoding() {
test_roundtrip!(0u32, false);
test_roundtrip!(1u32, false);
test_roundtrip!(2u32, false);
test_roundtrip!(3u32, false);
test_roundtrip!(255u32, false);
test_roundtrip!(u32::MAX, false);
}
}
2 changes: 2 additions & 0 deletions crates/clvm-traits/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod clvm_decoder;
mod clvm_encoder;
mod error;
mod from_clvm;
mod int_encoding;
mod macros;
mod match_byte;
mod to_clvm;
Expand All @@ -22,6 +23,7 @@ pub use clvm_decoder::*;
pub use clvm_encoder::*;
pub use error::*;
pub use from_clvm::*;
pub use int_encoding::*;
pub use match_byte::*;
pub use to_clvm::*;
pub use wrappers::*;
Expand Down
12 changes: 10 additions & 2 deletions crates/clvm-traits/src/to_clvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{rc::Rc, sync::Arc};
use clvmr::Atom;
use num_bigint::BigInt;

use crate::{ClvmEncoder, ToClvmError};
use crate::{encode_number, ClvmEncoder, ToClvmError};

pub trait ToClvm<E>
where
Expand All @@ -16,7 +16,9 @@ macro_rules! clvm_primitive {
($primitive:ty) => {
impl<N, E: ClvmEncoder<Node = N>> ToClvm<E> for $primitive {
fn to_clvm(&self, encoder: &mut E) -> Result<N, ToClvmError> {
encoder.encode_bigint(BigInt::from(*self))
let bytes = self.to_be_bytes();
#[allow(unused_comparisons)]
encoder.encode_atom(Atom::Borrowed(&encode_number(&bytes, *self < 0)))
}
}
};
Expand All @@ -35,6 +37,12 @@ clvm_primitive!(i128);
clvm_primitive!(usize);
clvm_primitive!(isize);

impl<N, E: ClvmEncoder<Node = N>> ToClvm<E> for BigInt {
fn to_clvm(&self, encoder: &mut E) -> Result<<E as ClvmEncoder>::Node, ToClvmError> {
encoder.encode_bigint(self.clone())
}
}

impl<N, E: ClvmEncoder<Node = N>> ToClvm<E> for bool {
fn to_clvm(&self, encoder: &mut E) -> Result<N, ToClvmError> {
i32::from(*self).to_clvm(encoder)
Expand Down

0 comments on commit 38ed939

Please sign in to comment.