Skip to content

Commit

Permalink
Merge #244: Typed value
Browse files Browse the repository at this point in the history
5a5a4e2 refactor: Value constructors (Christian Lewe)
a3fe129 refactor: Type-check Simplicity values (Christian Lewe)
d58710a feat: Add word type constructors (Christian Lewe)
5483fec feat: Compute padding (Christian Lewe)
d4e78a7 feat: Add BitCollector (Christian Lewe)
0be5b02 fix: Remove iterations counter (Christian Lewe)

Pull request description:

  We need to encode values with padding on the Bit Machine. This encoding requires information about the type of the value. It turns out that universally typing all Simplicity values produces the smallest diff, and it is the safest API. This PR refactors the `Value` struct to include type information.

  I also have ideas for a `Word` struct that wraps values of the word type. Only these values are safe to use in word jets. I will leave this for a follow-up PR.

ACKs for top commit:
  apoelstra:
    ACK 5a5a4e2 successfully ran local tests; nice! And does the API changes needed to later replace the `Value` internal representation with a flat bit array, which should greatly speed things up

Tree-SHA512: 17343a5642946d486533b67e083f5b5dc891de9154dd492c479e7f03a031e6e2e2d4eda95a3d424e990647e0bf74dc885308d7ac9374afc30777c0e27b6156ca
apoelstra committed Aug 29, 2024

Verified

This commit was signed with the committer’s verified signature.
apoelstra Andrew Poelstra
2 parents f6e7ecf + 5a5a4e2 commit 7949187
Showing 26 changed files with 519 additions and 396 deletions.
17 changes: 9 additions & 8 deletions jets-bench/benches/elements/main.rs
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ use simplicity::elements;
use simplicity::jet::elements::ElementsEnv;
use simplicity::jet::{Elements, Jet};
use simplicity::types;
use simplicity::types::Final;
use simplicity::Value;
use simplicity_bench::input::{
self, EqProduct, GenericProduct, InputSample, PrefixBit, Sha256Ctx, UniformBits,
@@ -751,36 +752,36 @@ fn bench(c: &mut Criterion) {
}

// Input to outpoint hash jet
fn outpoint_hash() -> Arc<Value> {
fn outpoint_hash() -> Value {
let ctx8 = SimplicityCtx8::with_len(511).value();
let genesis_pegin = genesis_pegin();
let outpoint = elements::OutPoint::sample().value();
Value::product(ctx8, Value::product(genesis_pegin, outpoint))
}

fn asset_amount_hash() -> Arc<Value> {
fn asset_amount_hash() -> Value {
let ctx8 = SimplicityCtx8::with_len(511).value();
let asset = confidential::Asset::sample().value();
let amount = confidential::Value::sample().value();
Value::product(ctx8, Value::product(asset, amount))
}

fn nonce_hash() -> Arc<Value> {
fn nonce_hash() -> Value {
let ctx8 = SimplicityCtx8::with_len(511).value();
let nonce = confidential::Nonce::sample().value();
Value::product(ctx8, nonce)
}

fn annex_hash() -> Arc<Value> {
fn annex_hash() -> Value {
let ctx8 = SimplicityCtx8::with_len(511).value();
let annex = if rand::random() {
Value::right(Value::u256(rand::random::<[u8; 32]>()))
Value::some(Value::u256(rand::random::<[u8; 32]>()))
} else {
Value::left(Value::unit())
Value::none(Final::u256())
};
Value::product(ctx8, annex)
}
let arr: [(Elements, Arc<dyn Fn() -> Arc<Value>>); 4] = [
let arr: [(Elements, Arc<dyn Fn() -> Value>); 4] = [
(Elements::OutpointHash, Arc::new(&outpoint_hash)),
(Elements::AssetAmountHash, Arc::new(&asset_amount_hash)),
(Elements::NonceHash, Arc::new(nonce_hash)),
@@ -814,7 +815,7 @@ fn bench(c: &mut Criterion) {
}

// Operations that use tx input or output index.
fn index_value(bound: u32) -> Arc<Value> {
fn index_value(bound: u32) -> Value {
let v = rand::random::<u32>() % bound;
Value::u32(v)
}
63 changes: 33 additions & 30 deletions jets-bench/src/data_structures.rs
Original file line number Diff line number Diff line change
@@ -8,10 +8,9 @@ use simplicity::{
bitcoin, elements,
hashes::Hash,
hex::FromHex,
types::{self, Type},
types::Final,
BitIter, Error, Value,
};
use std::sync::Arc;

/// Engine to compute SHA256 hash function.
/// We can't use hashes::sha256::HashEngine because it does not accept
@@ -55,21 +54,19 @@ impl SimplicityCtx8 {
/// # Panics:
///
/// Panics if the length of the slice is >= 2^(n + 1) bytes
pub fn var_len_buf_from_slice(v: &[u8], mut n: usize) -> Result<Arc<Value>, Error> {
pub fn var_len_buf_from_slice(v: &[u8], mut n: usize) -> Result<Value, Error> {
// Simplicity consensus rule for n < 16 while reading buffers.
assert!(n < 16);
assert!(v.len() < (1 << (n + 1)));
let mut iter = BitIter::new(v.iter().copied());
let ctx = types::Context::new();
let types = Type::powers_of_two(&ctx, n); // size n + 1
let mut res = None;
while n > 0 {
let ty = Final::two_two_n(n);
let v = if v.len() >= (1 << (n + 1)) {
let ty = &types[n];
let val = iter.read_value(&ty.final_data().unwrap())?;
Value::right(val)
let val = iter.read_value(&ty)?;
Value::some(val)
} else {
Value::left(Value::unit())
Value::none(ty)
};
res = match res {
Some(prod) => Some(Value::product(prod, v)),
@@ -155,11 +152,11 @@ pub struct SimplicityPoint(pub bitcoin::secp256k1::PublicKey);
/// Trait defining how to encode a data structure into a Simplicity value
/// This is then used to write these vales into the bit machine.
pub trait SimplicityEncode {
fn value(&self) -> Arc<Value>;
fn value(&self) -> Value;
}

impl SimplicityEncode for SimplicityCtx8 {
fn value(&self) -> Arc<Value> {
fn value(&self) -> Value {
let buf_len = self.length % 512;
let buf = var_len_buf_from_slice(&self.buffer[..buf_len], 8).unwrap();
let len = Value::u64(self.length as u64);
@@ -174,74 +171,80 @@ impl SimplicityEncode for SimplicityCtx8 {
}

impl SimplicityEncode for elements::OutPoint {
fn value(&self) -> Arc<Value> {
fn value(&self) -> Value {
let txid = Value::u256(self.txid.to_byte_array());
let vout = Value::u32(self.vout);
Value::product(txid, vout)
}
}

impl SimplicityEncode for elements::confidential::Asset {
fn value(&self) -> Arc<Value> {
fn value(&self) -> Value {
match self {
elements::confidential::Asset::Explicit(a) => {
Value::right(Value::u256(a.into_inner().to_byte_array()))
let left = Final::product(Final::u1(), Final::u256());
Value::right(left, Value::u256(a.into_inner().to_byte_array()))
}
elements::confidential::Asset::Confidential(gen) => {
let ser = gen.serialize();
let odd_gen = ser[0] & 1 == 1;
let x_bytes = (&ser[1..33]).try_into().unwrap();
let x_pt = Value::u256(x_bytes);
let y_pt = Value::u1(odd_gen as u8);
Value::left(Value::product(y_pt, x_pt))
Value::left(Value::product(y_pt, x_pt), Final::u256())
}
elements::confidential::Asset::Null => panic!("Tried to encode Null asset"),
}
}
}

impl SimplicityEncode for elements::confidential::Value {
fn value(&self) -> Arc<Value> {
fn value(&self) -> Value {
match self {
elements::confidential::Value::Explicit(v) => Value::right(Value::u64(*v)),
elements::confidential::Value::Explicit(v) => {
let left = Final::product(Final::u1(), Final::u256());
Value::right(left, Value::u64(*v))
},
elements::confidential::Value::Confidential(v) => {
let ser = v.serialize();
let x_bytes = (&ser[1..33]).try_into().unwrap();
let x_pt = Value::u256(x_bytes);
let y_pt = Value::u1((ser[0] & 1 == 1) as u8);
Value::left(Value::product(y_pt, x_pt))
Value::left(Value::product(y_pt, x_pt), Final::u64())
}
elements::confidential::Value::Null => panic!("Tried to encode Null value"),
}
}
}

impl SimplicityEncode for elements::confidential::Nonce {
fn value(&self) -> Arc<Value> {
fn value(&self) -> Value {
let ty_l = Final::product(Final::u1(), Final::u256());
let ty_r = Final::u256();
match self {
elements::confidential::Nonce::Explicit(n) => {
Value::right(Value::right(Value::u256(*n)))
Value::some(Value::right(ty_l, Value::u256(*n)))
}
elements::confidential::Nonce::Confidential(n) => {
let ser = n.serialize();
let x_bytes = (&ser[1..33]).try_into().unwrap();
let x_pt = Value::u256(x_bytes);
let y_pt = Value::u1((ser[0] & 1 == 1) as u8);
Value::right(Value::left(Value::product(y_pt, x_pt)))
Value::some(Value::left(Value::product(y_pt, x_pt), ty_r))
}
elements::confidential::Nonce::Null => Value::left(Value::unit()),
elements::confidential::Nonce::Null => Value::none(Final::sum(ty_l, ty_r)),
}
}
}

impl SimplicityEncode for SimplicityFe {
fn value(&self) -> Arc<Value> {
fn value(&self) -> Value {
Value::u256(*self.as_inner())
}
}

impl SimplicityEncode for SimplicityGe {
fn value(&self) -> Arc<Value> {
fn value(&self) -> Value {
let ser = match &self {
SimplicityGe::ValidPoint(p) => p.serialize_uncompressed(),
SimplicityGe::InvalidPoint(x, y) => {
@@ -261,21 +264,21 @@ impl SimplicityEncode for SimplicityGe {
}

impl SimplicityEncode for SimplicityGej {
fn value(&self) -> Arc<Value> {
fn value(&self) -> Value {
let ge = self.ge.value();
let z = self.z.value();
Value::product(ge, z)
}
}

impl SimplicityEncode for SimplicityScalar {
fn value(&self) -> Arc<Value> {
fn value(&self) -> Value {
Value::u256(self.0)
}
}

impl SimplicityEncode for SimplicityPoint {
fn value(&self) -> Arc<Value> {
fn value(&self) -> Value {
let ser = self.0.serialize(); // compressed
let x_bytes = (&ser[1..33]).try_into().unwrap();
let y_pt = Value::u1((ser[0] & 1 == 1) as u8);
@@ -407,11 +410,11 @@ impl BenchSample for SimplicityPoint {
}

// Sample genesis pegin with 50% probability
pub fn genesis_pegin() -> Arc<Value> {
pub fn genesis_pegin() -> Value {
if rand::random() {
Value::left(Value::unit())
Value::none(Final::two_two_n(8))
} else {
let genesis_hash = rand::random::<[u8; 32]>();
Value::right(Value::u256(genesis_hash))
Value::some(Value::u256(genesis_hash))
}
}
38 changes: 22 additions & 16 deletions jets-bench/src/input.rs
Original file line number Diff line number Diff line change
@@ -10,11 +10,11 @@ use simplicity::jet::Elements;
use simplicity::types::{self, CompleteBound};
use simplicity::Value;

pub fn random_value(ty: &types::Final, rng: &mut ThreadRng) -> Arc<Value> {
pub fn random_value(ty: &types::Final, rng: &mut ThreadRng) -> Value {
enum StackItem<'a> {
Type(&'a types::Final),
LeftSum,
RightSum,
LeftSum(Arc<types::Final>),
RightSum(Arc<types::Final>),
Product,
}

@@ -27,10 +27,10 @@ pub fn random_value(ty: &types::Final, rng: &mut ThreadRng) -> Arc<Value> {
CompleteBound::Unit => value_stack.push(Value::unit()),
CompleteBound::Sum(left, right) => {
if rng.gen() {
call_stack.push(StackItem::LeftSum);
call_stack.push(StackItem::LeftSum(Arc::clone(right)));
call_stack.push(StackItem::Type(left));
} else {
call_stack.push(StackItem::RightSum);
call_stack.push(StackItem::RightSum(Arc::clone(left)));
call_stack.push(StackItem::Type(right));
}
}
@@ -40,13 +40,13 @@ pub fn random_value(ty: &types::Final, rng: &mut ThreadRng) -> Arc<Value> {
call_stack.push(StackItem::Type(left));
}
},
StackItem::LeftSum => {
StackItem::LeftSum(right) => {
let left = value_stack.pop().unwrap();
value_stack.push(Value::left(left));
value_stack.push(Value::left(left, right));
}
StackItem::RightSum => {
StackItem::RightSum(left) => {
let right = value_stack.pop().unwrap();
value_stack.push(Value::right(right));
value_stack.push(Value::right(left, right));
}
StackItem::Product => {
let right = value_stack.pop().unwrap();
@@ -411,10 +411,10 @@ pub enum InputSampling {
Random,
/// A given, fixed bit string (whose length is multiple of 8)
/// Worst-case inputs
Fixed(Arc<Value>),
Fixed(Value),
/// Custom sampling method, read first src type bits from input
/// Useful for cases where we want to sample inputs according to some distributions
Custom(Arc<dyn Fn() -> Arc<Value>>),
Custom(Arc<dyn Fn() -> Value>),
}

impl InputSampling {
@@ -424,19 +424,25 @@ impl InputSampling {
src_ty: &types::Final,
rng: &mut ThreadRng,
) {
let write_bit = |bit: bool| unsafe { c_writeBit(src_frame, bit) };
let mut write_bit = |bit: bool| unsafe { c_writeBit(src_frame, bit) };

match self {
InputSampling::Random => {
let value = random_value(src_ty, rng);
value.do_each_bit(write_bit);
for bit in value.iter_padded() {
write_bit(bit);
}
}
InputSampling::Fixed(v) => {
v.do_each_bit(write_bit);
InputSampling::Fixed(value) => {
for bit in value.iter_padded() {
write_bit(bit);
}
}
InputSampling::Custom(gen_bytes) => {
let value = gen_bytes();
value.do_each_bit(write_bit);
for bit in value.iter_padded() {
write_bit(bit);
}
}
}
}
2 changes: 1 addition & 1 deletion src/analysis.rs
Original file line number Diff line number Diff line change
@@ -317,7 +317,7 @@ impl NodeBounds {
NodeBounds {
extra_cells: 0,
extra_frames: 0,
cost: Cost::OVERHEAD + Cost::of_type(value.len()),
cost: Cost::OVERHEAD + Cost::of_type(value.padded_len()),
}
}

Loading

0 comments on commit 7949187

Please sign in to comment.