diff --git a/src/analysis.rs b/src/analysis.rs index 624eeeb8..575c654d 100644 --- a/src/analysis.rs +++ b/src/analysis.rs @@ -1,9 +1,9 @@ // SPDX-License-Identifier: CC0-1.0 use crate::jet::Jet; -use crate::Value; use std::{cmp, fmt}; +use crate::value::Word; #[cfg(feature = "elements")] use elements::encode::Encodable; #[cfg(feature = "elements")] @@ -360,11 +360,11 @@ impl NodeBounds { } /// Node bounds for an arbitrary constant word node - pub fn const_word(value: &Value) -> NodeBounds { + pub fn const_word(word: &Word) -> NodeBounds { NodeBounds { extra_cells: 0, extra_frames: 0, - cost: Cost::OVERHEAD + Cost::of_type(value.padded_len()), + cost: Cost::OVERHEAD + Cost::of_type(word.len()), } } diff --git a/src/bit_encoding/decode.rs b/src/bit_encoding/decode.rs index e0fdfa1b..d396a3dc 100644 --- a/src/bit_encoding/decode.rs +++ b/src/bit_encoding/decode.rs @@ -13,7 +13,8 @@ use crate::node::{ WitnessConstructible, }; use crate::types; -use crate::{BitIter, FailEntropy, Value}; +use crate::value::Word; +use crate::{BitIter, FailEntropy}; use std::collections::HashSet; use std::sync::Arc; use std::{error, fmt}; @@ -129,7 +130,7 @@ enum DecodeNode { Fail(FailEntropy), Hidden(Cmr), Jet(J), - Word(Value), + Word(Word), } impl<'d, J: Jet> DagLike for (usize, &'d [DecodeNode]) { @@ -265,8 +266,8 @@ fn decode_node, J: Jet>( if bits.read_bit()? { J::decode(bits).map(|jet| DecodeNode::Jet(jet)) } else { - let depth = bits.read_natural(Some(32))?; - let word = decode_power_of_2(bits, 1 << (depth - 1))?; + let n = bits.read_natural(Some(32))? as u32; // cast safety: decoded number is at most the number 32 + let word = Word::from_bits(bits, n - 1)?; Ok(DecodeNode::Word(word)) } } else { @@ -320,43 +321,6 @@ fn decode_node, J: Jet>( } } -/// Decode a value from bits, of the form 2^exp -/// -/// # Panics -/// -/// Panics if `exp` itself is not a power of 2 -pub fn decode_power_of_2>( - iter: &mut I, - exp: usize, -) -> Result { - struct StackElem { - value: Value, - width: usize, - } - - assert_eq!(exp.count_ones(), 1, "exp must be a power of 2"); - - let mut stack = Vec::with_capacity(32); - for _ in 0..exp { - // Read next bit - let bit = Value::u1(u8::from(iter.next().ok_or(Error::EndOfStream)?)); - stack.push(StackElem { - value: bit, - width: 1, - }); - - while stack.len() >= 2 && stack[stack.len() - 1].width == stack[stack.len() - 2].width { - let right = stack.pop().unwrap(); - let left = stack.pop().unwrap(); - stack.push(StackElem { - value: Value::product(left.value, right.value), - width: left.width * 2, - }); - } - } - Ok(stack.pop().unwrap().value) -} - /// Decode a natural number from bits. /// If a bound is specified, then the decoding terminates before trying to decode a larger number. pub fn decode_natural>( diff --git a/src/bit_encoding/encode.rs b/src/bit_encoding/encode.rs index 9469fdbc..8ccf35db 100644 --- a/src/bit_encoding/encode.rs +++ b/src/bit_encoding/encode.rs @@ -256,13 +256,11 @@ fn encode_node( w.write_bit(true)?; // jet jet.encode(w)?; } - node::Inner::Word(val) => { + node::Inner::Word(word) => { w.write_bit(true)?; // jet or word w.write_bit(false)?; // word - assert_eq!(val.compact_len().count_ones(), 1); - let depth = val.compact_len().trailing_zeros(); - encode_natural(1 + depth as usize, w)?; - encode_value(val, w)?; + encode_natural(1 + word.n() as usize, w)?; + encode_value(word.as_value(), w)?; } _ => unreachable!(), } @@ -288,18 +286,9 @@ where /// Encode a value to bits. pub fn encode_value(value: &Value, w: &mut BitWriter) -> io::Result { let n_start = w.n_total_written(); - - if let Some(left) = value.as_left() { - w.write_bit(false)?; - encode_value(left, w)?; - } else if let Some(right) = value.as_right() { - w.write_bit(true)?; - encode_value(right, w)?; - } else if let Some((left, right)) = value.as_product() { - encode_value(left, w)?; - encode_value(right, w)?; + for bit in value.iter_compact() { + w.write_bit(bit)?; } - Ok(w.n_total_written() - n_start) } diff --git a/src/bit_machine/mod.rs b/src/bit_machine/mod.rs index 3e541d1f..2fbb52aa 100644 --- a/src/bit_machine/mod.rs +++ b/src/bit_machine/mod.rs @@ -334,7 +334,7 @@ impl BitMachine { } node::Inner::Witness(value) => self.write_value(value), node::Inner::Jet(jet) => self.exec_jet(*jet, env)?, - node::Inner::Word(value) => self.write_value(value), + node::Inner::Word(value) => self.write_value(value.as_value()), node::Inner::Fail(entropy) => { return Err(ExecutionError::ReachedFailNode(*entropy)) } diff --git a/src/human_encoding/mod.rs b/src/human_encoding/mod.rs index 71e1131d..fb8915b8 100644 --- a/src/human_encoding/mod.rs +++ b/src/human_encoding/mod.rs @@ -9,7 +9,6 @@ mod error; mod named_node; mod parse; -mod serialize; use crate::dag::{DagLike, MaxSharing}; use crate::jet::Jet; @@ -150,8 +149,8 @@ impl Forest { node::Inner::AssertR(cmr, _) => format!("{} := assertr #{}", name, cmr), node::Inner::Fail(entropy) => format!("{} := fail {}", name, entropy), node::Inner::Jet(ref j) => format!("{} := jet_{}", name, j), - node::Inner::Word(ref v) => { - format!("{} := const {}", name, serialize::DisplayWord(v)) + node::Inner::Word(ref word) => { + format!("{} := const {}", name, word) } inner => format!("{} := {}", name, inner), }; diff --git a/src/human_encoding/parse/ast.rs b/src/human_encoding/parse/ast.rs index aa2214af..de4e5e70 100644 --- a/src/human_encoding/parse/ast.rs +++ b/src/human_encoding/parse/ast.rs @@ -7,7 +7,8 @@ use std::sync::Arc; use crate::human_encoding::{Error, ErrorSet, Position, WitnessOrHole}; use crate::jet::Jet; -use crate::{node, types, Value}; +use crate::value::Word; +use crate::{node, types}; use crate::{BitIter, Cmr, FailEntropy}; use santiago::grammar::{Associativity, Grammar}; use santiago::lexer::{Lexeme, LexerRules}; @@ -644,12 +645,11 @@ fn grammar() -> Grammar> { Error::BadWordLength { bit_length }, )); } - let ty = types::Final::two_two_n(bit_length.trailing_zeros() as usize); // unwrap ok here since literally every sequence of bits is a valid // value for the given type - let value = Value::from_compact_bits(&mut iter, &ty).unwrap(); + let word = Word::from_bits(&mut iter, bit_length.trailing_zeros()).unwrap(); Ast::Expression(Expression { - inner: ExprInner::Inline(node::Inner::Word(value)), + inner: ExprInner::Inline(node::Inner::Word(word)), position, }) }; diff --git a/src/human_encoding/parse/mod.rs b/src/human_encoding/parse/mod.rs index f91bcb8f..cf227eac 100644 --- a/src/human_encoding/parse/mod.rs +++ b/src/human_encoding/parse/mod.rs @@ -575,6 +575,7 @@ mod tests { use crate::human_encoding::Forest; use crate::jet::{Core, Jet}; use crate::node::Inner; + use crate::value::Word; use crate::{BitMachine, Value}; fn assert_cmr_witness( @@ -619,15 +620,15 @@ mod tests { } } - fn assert_const(s: &str, value: Value) { + fn assert_const(s: &str, word: Word) { match parse::(s) { Ok(forest) => { assert_eq!(forest.len(), 1); let main = &forest["main"]; for data in main.clone().post_order_iter::>() { - if let Inner::Word(parsed_value) = data.node.inner() { - assert_eq!(&value, parsed_value); + if let Inner::Word(parsed_word) = data.node.inner() { + assert_eq!(&word, parsed_word); } } } @@ -743,32 +744,34 @@ mod tests { #[test] fn const_word() { - let human_values = [ - ("0b0", Value::u1(0b0)), - ("0b1", Value::u1(0b1)), - ("0b00", Value::u2(0b00)), - ("0b11", Value::u2(0b11)), - ("0b0000", Value::u4(0b0000)), - ("0b1111", Value::u4(0b1111)), - ("0b00000000", Value::u8(0b00000000)), - ("0b11111111", Value::u8(0b11111111)), + let human_words = [ + ("0b0", Word::u1(0b0)), + ("0b1", Word::u1(0b1)), + ("0b00", Word::u2(0b00)), + ("0b11", Word::u2(0b11)), + ("0b0000", Word::u4(0b0000)), + ("0b1111", Word::u4(0b1111)), + ("0b00000000", Word::u8(0b00000000)), + ("0b11111111", Word::u8(0b11111111)), ( "0b00000001001000110100010101100111", - Value::from_byte_array([0b00000001, 0b00100011, 0b01000101, 0b01100111]), + Word::u32(u32::from_be_bytes([ + 0b00000001, 0b00100011, 0b01000101, 0b01100111, + ])), ), - ("0x0", Value::u4(0x0)), - ("0xf", Value::u4(0xf)), - ("0x00", Value::u8(0x00)), - ("0xff", Value::u8(0xff)), + ("0x0", Word::u4(0x0)), + ("0xf", Word::u4(0xf)), + ("0x00", Word::u8(0x00)), + ("0xff", Word::u8(0xff)), ( "0xdeadbeef", - Value::from_byte_array([0xde, 0xad, 0xbe, 0xef]), + Word::u32(u32::from_be_bytes([0xde, 0xad, 0xbe, 0xef])), ), ]; - for (human, value) in human_values { + for (human, word) in human_words { let s = format!("main := comp const {human} unit"); - assert_const::(s.as_str(), value); + assert_const::(s.as_str(), word); } } diff --git a/src/human_encoding/serialize.rs b/src/human_encoding/serialize.rs deleted file mode 100644 index adba2441..00000000 --- a/src/human_encoding/serialize.rs +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! Serialization - -use crate::bit_encoding::BitCollector; -use hex::DisplayHex; -use std::fmt; - -pub struct DisplayWord<'a>(pub &'a crate::Value); - -impl<'a> fmt::Display for DisplayWord<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // The default value serialization shows the whole structure of - // the value; but for words, the structure is always fixed by the - // length, so it is fine to just serialize the bits. - if let Ok(hex) = self.0.iter_compact().try_collect_bytes() { - write!(f, "0x{}", hex.as_hex())?; - } else { - f.write_str("0b")?; - for bit in self.0.iter_compact() { - match bit { - false => f.write_str("0")?, - true => f.write_str("1")?, - } - } - } - Ok(()) - } -} diff --git a/src/jet/mod.rs b/src/jet/mod.rs index 705ce8aa..dc8e1dab 100644 --- a/src/jet/mod.rs +++ b/src/jet/mod.rs @@ -94,6 +94,7 @@ mod tests { use crate::jet::Core; use crate::node::{ConstructNode, CoreConstructible, JetConstructible}; use crate::types; + use crate::value::Word; use crate::{BitMachine, Value}; use std::sync::Arc; @@ -102,8 +103,8 @@ mod tests { let ctx = types::Context::new(); let two_words = Arc::>::comp( &Arc::>::pair( - &Arc::>::const_word(&ctx, Value::u32(2)), - &Arc::>::const_word(&ctx, Value::u32(16)), + &Arc::>::const_word(&ctx, Word::u32(2)), + &Arc::>::const_word(&ctx, Word::u32(16)), ) .unwrap(), &Arc::>::jet(&ctx, Core::Add32), @@ -122,8 +123,8 @@ mod tests { fn test_simple() { let ctx = types::Context::new(); let two_words = Arc::>::pair( - &Arc::>::const_word(&ctx, Value::u32(2)), - &Arc::>::const_word(&ctx, Value::u16(16)), + &Arc::>::const_word(&ctx, Word::u32(2)), + &Arc::>::const_word(&ctx, Word::u16(16)), ) .unwrap(); assert_eq!( diff --git a/src/merkle/amr.rs b/src/merkle/amr.rs index 4220760e..bd81dc14 100644 --- a/src/merkle/amr.rs +++ b/src/merkle/amr.rs @@ -4,6 +4,7 @@ use crate::impl_midstate_wrapper; use crate::jet::Jet; use crate::merkle::compact_value; use crate::types::arrow::FinalArrow; +use crate::value::Word; use crate::{Cmr, Tmr, Value}; use hashes::sha256::Midstate; @@ -167,8 +168,8 @@ impl Amr { /// /// This is equal to the IMR of the equivalent scribe, converted to a CMR in /// the usual way for jets. - pub fn const_word(v: &Value) -> Self { - Cmr::const_word(v).into() + pub fn const_word(word: &Word) -> Self { + Cmr::const_word(word).into() } #[rustfmt::skip] diff --git a/src/merkle/cmr.rs b/src/merkle/cmr.rs index 469fe326..2403eab3 100644 --- a/src/merkle/cmr.rs +++ b/src/merkle/cmr.rs @@ -6,7 +6,8 @@ use crate::node::{ CoreConstructible, DisconnectConstructible, JetConstructible, WitnessConstructible, }; use crate::types::{self, Error}; -use crate::{FailEntropy, Tmr, Value}; +use crate::value::Word; +use crate::{FailEntropy, Tmr}; use hashes::sha256::Midstate; use super::bip340_iv; @@ -100,13 +101,12 @@ impl Cmr { /// /// This is equal to the IMR of the equivalent scribe, converted to a CMR in /// the usual way for jets. - pub fn const_word(v: &Value) -> Self { - assert_eq!(v.compact_len().count_ones(), 1); - let w = 1 + v.compact_len().trailing_zeros() as usize; + pub fn const_word(word: &Word) -> Self { + let w = 1 + word.n() as usize; let mut cmr_stack = Vec::with_capacity(33); // 1. Compute the CMR for the `scribe` corresponding to this word jet - for (bit_idx, bit) in v.iter_compact().enumerate() { + for (bit_idx, bit) in word.iter().enumerate() { cmr_stack.push(Cmr::BITS[usize::from(bit)]); let mut j = bit_idx; while j & 1 == 1 { @@ -123,7 +123,7 @@ impl Cmr { // 2. Add TMRs to get the pass-two IMR let imr_pass2 = imr_pass1.update(Tmr::unit().into(), Tmr::POWERS_OF_TWO[w - 1].into()); // 3. Convert to a jet CMR - Cmr(bip340_iv(b"Simplicity\x1fJet")).update_with_weight(v.compact_len() as u64, imr_pass2) + Cmr(bip340_iv(b"Simplicity\x1fJet")).update_with_weight(word.len() as u64, imr_pass2) } #[rustfmt::skip] @@ -344,7 +344,7 @@ impl CoreConstructible for ConstructibleCmr { } } - fn const_word(inference_context: &types::Context, word: Value) -> Self { + fn const_word(inference_context: &types::Context, word: Word) -> Self { ConstructibleCmr { cmr: Cmr::const_word(&word), inference_context: inference_context.shallow_clone(), @@ -417,7 +417,7 @@ mod tests { #[test] fn fixed_const_word_cmr() { // Checked against C implementation - let bit0 = Value::u1(0); + let bit0 = Word::u1(0); #[rustfmt::skip] assert_eq!( Cmr::const_word(&bit0), @@ -480,10 +480,23 @@ mod tests { #[test] fn const_bits() { + /// The scribe expression, as defined in the Simplicity tech report. + fn scribe(bit: u8) -> Arc> { + match bit { + 0 => Arc::>::injl(&Arc::>::unit( + &types::Context::new(), + )), + 1 => Arc::>::injr(&Arc::>::unit( + &types::Context::new(), + )), + _ => panic!("Unexpected bit {bit}"), + } + } + fn check_bit(target: Cmr, index: u8) { // Uncomment this if the IVs ever change /* - let target = Arc::>::scribe(&types::Context::new(), &Value::u1(index)).cmr(); + let target = scribe(index).cmr(); println!(" Cmr(Midstate(["); print!(" "); for ch in &target.0[0..8] { print!(" 0x{:02x},", ch); }; println!(); print!(" "); for ch in &target.0[8..16] { print!(" 0x{:02x},", ch); }; println!(); @@ -493,7 +506,7 @@ mod tests { */ assert_eq!( target, - Arc::>::scribe(&types::Context::new(), &Value::u1(index)).cmr(), + scribe(index).cmr(), "mismatch on CMR for bit {index}", ); } diff --git a/src/merkle/imr.rs b/src/merkle/imr.rs index 8af6c77c..acc8d605 100644 --- a/src/merkle/imr.rs +++ b/src/merkle/imr.rs @@ -3,6 +3,7 @@ use crate::impl_midstate_wrapper; use crate::jet::Jet; use crate::types::arrow::FinalArrow; +use crate::value::Word; use crate::{Cmr, Tmr, Value}; use hashes::sha256::Midstate; @@ -125,8 +126,8 @@ impl FirstPassImr { /// /// This is equal to the IMR of the equivalent scribe, converted to a CMR in /// the usual way for jets. - pub fn const_word(v: &Value) -> Self { - Cmr::const_word(v).into() + pub fn const_word(word: &Word) -> Self { + Cmr::const_word(word).into() } #[rustfmt::skip] diff --git a/src/node/construct.rs b/src/node/construct.rs index 9bd2e4e7..75930e4c 100644 --- a/src/node/construct.rs +++ b/src/node/construct.rs @@ -4,8 +4,9 @@ use crate::dag::{InternalSharing, PostOrderIterItem}; use crate::encode; use crate::jet::Jet; use crate::types::{self, arrow::Arrow}; -use crate::{BitIter, BitWriter, Cmr, FailEntropy, Value}; +use crate::{BitIter, BitWriter, Cmr, FailEntropy}; +use crate::value::Word; use std::io; use std::marker::PhantomData; use std::sync::Arc; @@ -256,7 +257,7 @@ impl CoreConstructible for ConstructData { } } - fn const_word(inference_context: &types::Context, word: Value) -> Self { + fn const_word(inference_context: &types::Context, word: Word) -> Self { ConstructData { arrow: Arrow::const_word(inference_context, word), phantom: PhantomData, @@ -303,6 +304,8 @@ impl JetConstructible for ConstructData { mod tests { use super::*; use crate::jet::Core; + use crate::types::Final; + use crate::Value; #[test] fn occurs_check_error() { @@ -384,9 +387,8 @@ mod tests { // about CMRs, for which type inference is irrelevant. let ctx = types::Context::new(); let unit = Arc::>::unit(&ctx); - let bit0 = Arc::>::injl(&unit); - let bit1 = Arc::>::injr(&unit); - let bits01 = Arc::>::pair(&bit0, &bit1).unwrap(); + let bit0 = Arc::>::const_word(&ctx, Word::u1(0)); + let bit1 = Arc::>::const_word(&ctx, Word::u1(1)); assert_eq!( unit.cmr(), @@ -401,8 +403,25 @@ mod tests { Arc::>::scribe(&ctx, &Value::u1(1)).cmr() ); assert_eq!( - bits01.cmr(), + Arc::>::const_word(&ctx, Word::u2(1)).cmr(), Arc::>::scribe(&ctx, &Value::u2(1)).cmr() ); + assert_eq!( + Arc::>::injl(&bit0).cmr(), + Arc::>::scribe(&ctx, &Value::left(Value::u1(0), Final::unit())) + .cmr() + ); + assert_eq!( + Arc::>::injr(&bit1).cmr(), + Arc::>::scribe(&ctx, &Value::right(Final::unit(), Value::u1(1))) + .cmr() + ); + assert_eq!( + Arc::>::pair(&unit, &unit) + .unwrap() + .cmr(), + Arc::>::scribe(&ctx, &Value::product(Value::unit(), Value::unit())) + .cmr() + ); } } diff --git a/src/node/inner.rs b/src/node/inner.rs index 1da282da..06bbd5d1 100644 --- a/src/node/inner.rs +++ b/src/node/inner.rs @@ -2,8 +2,9 @@ use super::{Disconnectable, FailEntropy}; use crate::dag::Dag; -use crate::{Cmr, Value}; +use crate::Cmr; +use crate::value::Word; use std::fmt; use std::sync::Arc; @@ -44,7 +45,7 @@ pub enum Inner { /// Application jet Jet(J), /// Constant word - Word(Value), + Word(Word), } impl Inner { diff --git a/src/node/mod.rs b/src/node/mod.rs index 84a8643d..4ba46873 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -63,7 +63,7 @@ //! completeness. //! -use crate::dag::{DagLike, MaxSharing, NoSharing, SharingTracker}; +use crate::dag::{DagLike, MaxSharing, SharingTracker}; use crate::jet::Jet; use crate::{types, Cmr, FailEntropy, Value}; @@ -79,6 +79,7 @@ mod inner; mod redeem; mod witness; +use crate::value::Word; pub use commit::{Commit, CommitData, CommitNode}; pub use construct::{Construct, ConstructData, ConstructNode}; pub use convert::{Converter, Hide, SimpleFinalizer}; @@ -177,33 +178,66 @@ pub trait CoreConstructible: Sized { fn assertr(left: Cmr, right: &Self) -> Result; fn pair(left: &Self, right: &Self) -> Result; fn fail(inference_context: &types::Context, entropy: FailEntropy) -> Self; - fn const_word(inference_context: &types::Context, word: Value) -> Self; + fn const_word(inference_context: &types::Context, word: Word) -> Self; /// Accessor for the type inference context used to create the object. fn inference_context(&self) -> &types::Context; - /// Create a DAG that takes any input and returns `value` as constant output. + /// Create an expression that produces the given `value`. /// - /// _Overall type: A → B where value: B_ - fn scribe(inference_context: &types::Context, value: &Value) -> Self { - let mut stack = vec![]; - for data in value.post_order_iter::() { - if data.node.is_unit() { - stack.push(Self::unit(inference_context)); - } else if data.node.as_left().is_some() { - let child = stack.pop().unwrap(); - stack.push(Self::injl(&child)); - } else if data.node.as_right().is_some() { - let child = stack.pop().unwrap(); - stack.push(Self::injr(&child)); - } else if data.node.as_product().is_some() { - let right = stack.pop().unwrap(); - let left = stack.pop().unwrap(); - stack.push(Self::pair(&left, &right).expect("source of scribe has no constraints")); + /// The expression is minimized by using as many word jets as possible. + fn scribe(ctx: &types::Context, value: &Value) -> Self { + #[derive(Debug, Clone)] + enum Task<'a> { + Process(&'a Value), + MakeLeft, + MakeRight, + MakeProduct, + } + + let mut input = vec![Task::Process(value)]; + let mut output = vec![]; + while let Some(top) = input.pop() { + match top { + Task::Process(value) => { + if value.is_unit() { + output.push(Self::unit(ctx)); + } else if let Some(word) = value.to_word() { + output.push(Self::const_word(ctx, word)); + } else if let Some(left) = value.as_left() { + input.push(Task::MakeLeft); + input.push(Task::Process(left)); + } else if let Some(right) = value.as_right() { + input.push(Task::MakeRight); + input.push(Task::Process(right)); + } else if let Some((left, right)) = value.as_product() { + input.push(Task::MakeProduct); + input.push(Task::Process(right)); + input.push(Task::Process(left)); + } + } + Task::MakeLeft => { + let inner = output.pop().unwrap(); + output.push(Self::injl(&inner)); + } + Task::MakeRight => { + let inner = output.pop().unwrap(); + output.push(Self::injr(&inner)); + } + Task::MakeProduct => { + let right = output.pop().unwrap(); + let left = output.pop().unwrap(); + // simfony::PairBuilder would remove this `.expect()` call + output.push( + Self::pair(&left, &right).expect( + "`pair` should always succeed because input type is unrestricted", + ), + ); + } } } - assert_eq!(stack.len(), 1); - stack.pop().unwrap() + debug_assert_eq!(output.len(), 1); + output.pop().unwrap() } /// Create a DAG that takes any input and returns bit `0` as constant output. @@ -474,11 +508,11 @@ where }) } - fn const_word(inference_context: &types::Context, value: Value) -> Self { + fn const_word(inference_context: &types::Context, word: Word) -> Self { Arc::new(Node { - cmr: Cmr::const_word(&value), - data: N::CachedData::const_word(inference_context, value.shallow_clone()), - inner: Inner::Word(value), + cmr: Cmr::const_word(&word), + data: N::CachedData::const_word(inference_context, word.shallow_clone()), + inner: Inner::Word(word), }) } diff --git a/src/node/witness.rs b/src/node/witness.rs index fc9395fd..8c55e2da 100644 --- a/src/node/witness.rs +++ b/src/node/witness.rs @@ -5,6 +5,7 @@ use crate::jet::Jet; use crate::types::{self, arrow::Arrow}; use crate::{Cmr, Error, FailEntropy, Value}; +use crate::value::Word; use std::marker::PhantomData; use std::sync::Arc; @@ -333,7 +334,7 @@ impl CoreConstructible for WitnessData { } } - fn const_word(inference_context: &types::Context, word: Value) -> Self { + fn const_word(inference_context: &types::Context, word: Word) -> Self { WitnessData { arrow: Arrow::const_word(inference_context, word), must_prune: false, diff --git a/src/policy/serialize.rs b/src/policy/serialize.rs index 1deceff0..0d02b81c 100644 --- a/src/policy/serialize.rs +++ b/src/policy/serialize.rs @@ -6,11 +6,12 @@ use crate::jet::{Elements, Jet}; use crate::merkle::cmr::ConstructibleCmr; use crate::node::{CoreConstructible, JetConstructible, WitnessConstructible}; use crate::types; +use crate::FailEntropy; use crate::{Cmr, ConstructNode, ToXOnlyPubkey}; -use crate::{FailEntropy, Value}; use hashes::Hash; +use crate::value::Word; use std::convert::TryFrom; use std::sync::Arc; @@ -56,7 +57,7 @@ where Pk: ToXOnlyPubkey, N: CoreConstructible + JetConstructible + WitnessConstructible, { - let key_value = Value::u256(key.to_x_only_pubkey().serialize()); + let key_value = Word::u256(key.to_x_only_pubkey().serialize()); let const_key = N::const_word(inference_context, key_value); let sighash_all = N::jet(inference_context, Elements::SigAllHash); let pair_key_msg = N::pair(&const_key, &sighash_all).expect("consistent types"); @@ -71,7 +72,7 @@ pub fn after(inference_context: &types::Context, n: u32) -> N where N: CoreConstructible + JetConstructible, { - let n_value = Value::u32(n); + let n_value = Word::u32(n); let const_n = N::const_word(inference_context, n_value); let check_lock_height = N::jet(inference_context, Elements::CheckLockHeight); @@ -82,7 +83,7 @@ pub fn older(inference_context: &types::Context, n: u16) -> N where N: CoreConstructible + JetConstructible, { - let n_value = Value::u16(n); + let n_value = Word::u16(n); let const_n = N::const_word(inference_context, n_value); let check_lock_distance = N::jet(inference_context, Elements::CheckLockDistance); @@ -120,7 +121,7 @@ where Pk: ToXOnlyPubkey, N: CoreConstructible + JetConstructible + WitnessConstructible, { - let hash_value = Value::u256(Pk::to_sha256(hash).to_byte_array()); + let hash_value = Word::u256(Pk::to_sha256(hash).to_byte_array()); let const_hash = N::const_word(inference_context, hash_value); let witness256 = N::witness(inference_context, witness); let computed_hash = compute_sha256(&witness256); @@ -174,11 +175,11 @@ where let selector = selector(child.inference_context(), witness_bit); // 1 → 2^32 - let const_one = N::const_word(child.inference_context(), Value::u32(1)); + let const_one = N::const_word(child.inference_context(), Word::u32(1)); // 1 → 2^32 let child_one = N::comp(child, &const_one).expect("consistent types"); // 1 → 2^32 - let const_zero = N::const_word(child.inference_context(), Value::u32(0)); + let const_zero = N::const_word(child.inference_context(), Word::u32(0)); // 1 × 1 → 2^32 let drop_left = N::drop_(&const_zero); @@ -226,7 +227,7 @@ where N: CoreConstructible + JetConstructible, { // 1 → 2^32 - let const_k = N::const_word(sum.inference_context(), Value::u32(k)); + let const_k = N::const_word(sum.inference_context(), Word::u32(k)); // 1 → 2^32 × 2^32 let pair_k_sum = N::pair(&const_k, sum).expect("consistent types"); // 2^32 × 2^32 → 2 diff --git a/src/types/arrow.rs b/src/types/arrow.rs index c9faca16..40e73703 100644 --- a/src/types/arrow.rs +++ b/src/types/arrow.rs @@ -14,12 +14,13 @@ use std::fmt; use std::sync::Arc; +use crate::jet::Jet; use crate::node::{ CoreConstructible, DisconnectConstructible, JetConstructible, NoDisconnect, WitnessConstructible, }; use crate::types::{Context, Error, Final, Type}; -use crate::{jet::Jet, Value}; +use crate::value::Word; use super::variable::new_name; @@ -310,14 +311,10 @@ impl CoreConstructible for Arrow { } } - fn const_word(inference_context: &Context, word: Value) -> Self { - let len = word.compact_len(); - assert!(len > 0, "Words must not be the empty bitstring"); - assert!(len.is_power_of_two()); - let depth = len.trailing_zeros(); + fn const_word(inference_context: &Context, word: Word) -> Self { Arrow { source: Type::unit(inference_context), - target: Type::two_two_n(inference_context, depth as usize), + target: Type::two_two_n(inference_context, word.n() as usize), // cast safety: 32-bit machine or higher inference_context: inference_context.shallow_clone(), } } diff --git a/src/types/final_data.rs b/src/types/final_data.rs index f85283a6..e50808ee 100644 --- a/src/types/final_data.rs +++ b/src/types/final_data.rs @@ -247,6 +247,17 @@ impl Final { } } + /// If the type is of the form `TWO^(2^n)`, then return `n`. + /// + /// ## Post condition + /// + /// 0 ≤ n < 32. + pub fn as_word(&self) -> Option { + (0..32u32).find(|&n| { + self.tmr == Tmr::POWERS_OF_TWO[n as usize] // cast safety: 32-bit machine or higher + }) + } + /// Compute the padding of left values of the sum type `Self + Other`. pub fn pad_left(&self, other: &Self) -> usize { cmp::max(self.bit_width, other.bit_width) - self.bit_width diff --git a/src/value.rs b/src/value.rs index 483d9511..9d212dbc 100644 --- a/src/value.rs +++ b/src/value.rs @@ -8,7 +8,7 @@ use crate::dag::{Dag, DagLike, NoSharing}; use crate::types::Final; -use crate::{types, EarlyEndOfStreamError}; +use crate::{types, BitCollector, EarlyEndOfStreamError}; use std::collections::VecDeque; use std::fmt; use std::hash::Hash; @@ -290,6 +290,16 @@ impl Value { pub fn is_of_type(&self, ty: &Final) -> bool { self.ty.as_ref() == ty } + + /// Try to convert the value into a word. + /// + /// The value is cheaply cloned. + pub fn to_word(&self) -> Option { + self.ty.as_word().map(|n| Word { + value: self.shallow_clone(), + n, + }) + } } impl fmt::Debug for Value { @@ -521,6 +531,151 @@ impl Value { } } +/// A Simplicity word. A value of type `TWO^(2^n)` for some `0 ≤ n < 32`. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct Word { + /// Value of type `TWO^(2^n)`. + value: Value, + /// 0 ≤ n < 32. + n: u32, +} + +macro_rules! construct_word_fallible { + ($name: ident, $n: expr, $text: expr) => { + #[doc = "Create"] + #[doc = $text] + #[doc = "word.\n\n"] + #[doc = "## Panics\n"] + #[doc = "The value is ouf of range."] + pub fn $name(bit: u8) -> Self { + Self { + value: Value::$name(bit), + n: $n, + } + } + }; +} + +macro_rules! construct_word { + ($name: ident, $ty: ty, $n: expr, $text: expr) => { + #[doc = "Create"] + #[doc = $text] + #[doc = "word."] + pub fn $name(bit: $ty) -> Self { + Self { + value: Value::$name(bit), + n: $n, + } + } + }; +} + +impl Word { + /// Concatenate two words into a larger word. + /// + /// Both words have to have the same length, which is 2^n bits. + /// The resulting word will be 2^(n + 1) bits long. + /// + /// Returns `None` if the words differ in length. + /// + /// Returns `None` if the words are already 2^31 bits long + /// _(the resulting word would be longer than 2^31 bits, which is not supported)_. + pub fn product(self, right: Self) -> Option { + if self.n == right.n && self.n < 30 { + Some(Self { + value: Value::product(self.value, right.value), + n: self.n + 1, + }) + } else { + None + } + } + + construct_word_fallible!(u1, 0, "a 1-bit"); + construct_word_fallible!(u2, 1, "a 2-bit"); + construct_word_fallible!(u4, 2, "a 4-bit"); + construct_word!(u8, u8, 3, "an 8-bit"); + construct_word!(u16, u16, 4, "a 16-bit"); + construct_word!(u32, u32, 5, "a 32-bit"); + construct_word!(u64, u64, 6, "a 64-bit"); + construct_word!(u128, u128, 7, "a 128-bit"); + construct_word!(u256, [u8; 32], 8, "a 256-bit"); + construct_word!(u512, [u8; 64], 9, "a 512-bit"); + + /// Make a cheap copy of the word. + pub fn shallow_clone(&self) -> Self { + Self { + value: self.value.shallow_clone(), + n: self.n, + } + } + + /// Access the value of the word. + pub fn as_value(&self) -> &Value { + &self.value + } + + /// The word is of type `TWO^(2^n)`. Return `n`. + pub fn n(&self) -> u32 { + self.n + } + + /// Return the bit length of the word. + /// + /// The word is of type `TWO^(2^n)`. Return `2^n`. + pub fn len(&self) -> usize { + 2usize.pow(self.n) + } + + /// Return an iterator over the bit encoding of the word. + /// + /// Words have no padding, so their compact encoding is the same as the padded encoding. + /// The universal encoding can be used in all situations. + pub fn iter(&self) -> impl Iterator + '_ { + self.value.iter_compact() + } + + /// Decode a word of type `TWO^(2^n)` from bits. + /// + /// ## Panics + /// + /// n is greater than 31. + pub fn from_bits>( + bits: &mut I, + n: u32, + ) -> Result { + assert!(n < 32, "TWO^(2^{n}) is not supported as a word type"); + let ty = Final::two_two_n(n as usize); // cast safety: 32-bit machine or higher + let value = Value::from_compact_bits(bits, &ty)?; + Ok(Self { value, n }) + } +} + +impl fmt::Debug for Word { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl fmt::Display for Word { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use hex::DisplayHex; + + if let Ok(hex) = self.iter().try_collect_bytes() { + write!(f, "0x{}", hex.as_hex()) + } else { + f.write_str("0b")?; + for bit in self.iter() { + match bit { + false => f.write_str("0")?, + true => f.write_str("1")?, + } + } + Ok(()) + } + } +} + #[cfg(test)] mod tests { use super::*;