From 22d5618701e1d7f0e0b79b242ea5bf594197bc50 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 11 Dec 2025 14:19:23 +0000 Subject: [PATCH 1/7] value: use ValueRef internally in RawByteIter --- src/value.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/value.rs b/src/value.rs index f3fdca11..f4b5e435 100644 --- a/src/value.rs +++ b/src/value.rs @@ -199,7 +199,7 @@ impl ValueRef<'_> { } pub struct RawByteIter<'v> { - value: &'v Value, + value: ValueRef<'v>, yielded_bytes: usize, } @@ -592,7 +592,7 @@ impl Value { /// but this method is more efficient in some contexts. pub fn raw_byte_iter(&self) -> RawByteIter<'_> { RawByteIter { - value: self, + value: self.as_ref(), yielded_bytes: 0, } } From 38c5776c3eba9c86e76908de096db13a420d3fb6 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 11 Dec 2025 14:21:00 +0000 Subject: [PATCH 2/7] value: impl Copy for ValueRef ValueRef is a &-reference with a bit of small metadata attached. It should be Copy just like &Value is. --- src/value.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/value.rs b/src/value.rs index f4b5e435..c4c7468f 100644 --- a/src/value.rs +++ b/src/value.rs @@ -38,7 +38,7 @@ pub struct Value { } /// Reference to a value, or to a sub-value of a value. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub struct ValueRef<'v> { inner: &'v Arc<[u8]>, bit_offset: usize, From 1ca2498a416ae49d7a6afd0890f5cc31ce85bc3c Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 11 Dec 2025 14:20:49 +0000 Subject: [PATCH 3/7] value: move iterator methods from Value to ValueRef These all conceptually work on references to values, so they should be available from a ValueRef. --- src/value.rs | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/src/value.rs b/src/value.rs index c4c7468f..3bedf42c 100644 --- a/src/value.rs +++ b/src/value.rs @@ -101,7 +101,7 @@ impl DagLike for ValueRef<'_> { } } -impl ValueRef<'_> { +impl<'v> ValueRef<'v> { /// Check if the value is a unit. pub fn is_unit(&self) -> bool { self.ty.is_unit() @@ -196,6 +196,34 @@ impl ValueRef<'_> { n, }) } + + /// Yields an iterator over the "raw bytes" of the value. + /// + /// The returned bytes match the padded bit-encoding of the value. You + /// may wish to call [`Self::iter_padded`] instead to obtain the bits, + /// but this method is more efficient in some contexts. + pub fn raw_byte_iter(&self) -> RawByteIter<'v> { + RawByteIter { + value: *self, + yielded_bytes: 0, + } + } + + /// Return an iterator over the compact bit encoding of the value. + /// + /// This encoding is used for writing witness data and for computing IHRs. + pub fn iter_compact(&self) -> CompactBitsIter<'v> { + CompactBitsIter::new(*self) + } + + /// Return an iterator over the padded bit encoding of the value. + /// + /// This encoding is used to represent the value in the Bit Machine. + pub fn iter_padded(&self) -> PreOrderIter<'v> { + PreOrderIter { + inner: BitIter::new(self.raw_byte_iter()).take(self.ty.bit_width()), + } + } } pub struct RawByteIter<'v> { @@ -591,26 +619,21 @@ impl Value { /// may wish to call [`Self::iter_padded`] instead to obtain the bits, /// but this method is more efficient in some contexts. pub fn raw_byte_iter(&self) -> RawByteIter<'_> { - RawByteIter { - value: self.as_ref(), - yielded_bytes: 0, - } + self.as_ref().raw_byte_iter() } /// Return an iterator over the compact bit encoding of the value. /// /// This encoding is used for writing witness data and for computing IHRs. pub fn iter_compact(&self) -> CompactBitsIter<'_> { - CompactBitsIter::new(self.as_ref()) + self.as_ref().iter_compact() } /// Return an iterator over the padded bit encoding of the value. /// /// This encoding is used to represent the value in the Bit Machine. pub fn iter_padded(&self) -> PreOrderIter<'_> { - PreOrderIter { - inner: BitIter::new(self.raw_byte_iter()).take(self.ty.bit_width()), - } + self.as_ref().iter_padded() } /// Check if the value is of the given type. From d2da3a0c7177bdf1e6675507928256f4560f7248 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 11 Dec 2025 14:17:21 +0000 Subject: [PATCH 4/7] value: write out power-of-two length bitstrings compactly in Display --- src/value.rs | 45 +++++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/src/value.rs b/src/value.rs index 3bedf42c..89f22dfc 100644 --- a/src/value.rs +++ b/src/value.rs @@ -7,7 +7,7 @@ use crate::dag::{Dag, DagLike}; use crate::types::{CompleteBound, Final}; -use crate::BitIter; +use crate::{BitIter, Tmr}; use crate::{BitCollector, EarlyEndOfStreamError}; use core::{cmp, fmt, iter}; @@ -769,7 +769,7 @@ impl fmt::Display for Value { // a unit is displayed simply as 0 or 1. stack.push(S::Disp(self.as_ref())); - while let Some(next) = stack.pop() { + 'main_loop: while let Some(next) = stack.pop() { let value = match next { S::Disp(ref value) | S::DispUnlessUnit(ref value) => value, S::DispCh(ch) => { @@ -782,20 +782,33 @@ impl fmt::Display for Value { if !matches!(next, S::DispUnlessUnit(..)) { f.write_str("ε")?; } - } else if let Some(l_value) = value.as_left() { - f.write_str("0")?; - stack.push(S::DispUnlessUnit(l_value)); - } else if let Some(r_value) = value.as_right() { - f.write_str("1")?; - stack.push(S::DispUnlessUnit(r_value)); - } else if let Some((l_value, r_value)) = value.as_product() { - stack.push(S::DispCh(')')); - stack.push(S::Disp(r_value)); - stack.push(S::DispCh(',')); - stack.push(S::Disp(l_value)); - stack.push(S::DispCh('(')); } else { - unreachable!() + // First, write any bitstrings out + for tmr in &Tmr::TWO_TWO_N { + if value.ty.tmr() == *tmr { + for bit in value.iter_padded() { + f.write_str(if bit { "1" } else { "0" })?; + } + continue 'main_loop; + } + } + + // If we don't have a bitstring, then write out the explicit value. + if let Some(l_value) = value.as_left() { + f.write_str("0")?; + stack.push(S::DispUnlessUnit(l_value)); + } else if let Some(r_value) = value.as_right() { + f.write_str("1")?; + stack.push(S::DispUnlessUnit(r_value)); + } else if let Some((l_value, r_value)) = value.as_product() { + stack.push(S::DispCh(')')); + stack.push(S::Disp(r_value)); + stack.push(S::DispCh(',')); + stack.push(S::Disp(l_value)); + stack.push(S::DispCh('(')); + } else { + unreachable!() + } } } Ok(()) @@ -1095,7 +1108,7 @@ mod tests { // at some point and will have to redo this test. assert_eq!(Value::u1(0).to_string(), "0",); assert_eq!(Value::u1(1).to_string(), "1",); - assert_eq!(Value::u4(6).to_string(), "((0,1),(1,0))",); + assert_eq!(Value::u4(6).to_string(), "0110",); } #[test] From 6c88a8c4a4309859353d3f1953fecc2fbce7028a Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 11 Dec 2025 14:29:55 +0000 Subject: [PATCH 5/7] value: write out bitstrings with len > 4 as hex rather than binary --- src/value.rs | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/value.rs b/src/value.rs index 89f22dfc..6eba5470 100644 --- a/src/value.rs +++ b/src/value.rs @@ -786,8 +786,25 @@ impl fmt::Display for Value { // First, write any bitstrings out for tmr in &Tmr::TWO_TWO_N { if value.ty.tmr() == *tmr { - for bit in value.iter_padded() { - f.write_str(if bit { "1" } else { "0" })?; + if value.ty.bit_width() < 4 { + f.write_str("0b")?; + for bit in value.iter_padded() { + f.write_str(if bit { "1" } else { "0" })?; + } + } else { + f.write_str("0x")?; + // Annoyingly `array_chunks` is unstable so we have to do it manually + // https://github.com/rust-lang/rust/issues/100450 + let mut iter = value.iter_padded(); + while let (Some(a), Some(b), Some(c), Some(d)) = + (iter.next(), iter.next(), iter.next(), iter.next()) + { + let n = (u8::from(a) << 3) + + (u8::from(b) << 2) + + (u8::from(c) << 1) + + u8::from(d); + write!(f, "{:x}", n)?; + } } continue 'main_loop; } @@ -1106,9 +1123,9 @@ mod tests { fn value_display() { // Only test a couple values becasue we probably want to change this // at some point and will have to redo this test. - assert_eq!(Value::u1(0).to_string(), "0",); - assert_eq!(Value::u1(1).to_string(), "1",); - assert_eq!(Value::u4(6).to_string(), "0110",); + assert_eq!(Value::u1(0).to_string(), "0b0",); + assert_eq!(Value::u1(1).to_string(), "0b1",); + assert_eq!(Value::u4(6).to_string(), "0x6",); } #[test] From 271dde9149c7aa1a76a04273375d8b6f62a4ea50 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 11 Dec 2025 14:34:06 +0000 Subject: [PATCH 6/7] value: display left and rights as L(x) and R(x); drop DispUnlessUnit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously we would display left branches as 0 and right branches as 1, and had a special case where if the child node was unit, we would not display the inner (ε) to reduce noise. Now that we special-case all bitstrings, in particular bitstrings of length 1, this special-case is unnecessary and actually confusing. Now if we encounter a sum type we know it's *not* a bit and it would be clearer to indicate it as L or R, and if the child is a unit, we should unconditionally display that. --- src/value.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/value.rs b/src/value.rs index 6eba5470..4b9bf285 100644 --- a/src/value.rs +++ b/src/value.rs @@ -759,19 +759,16 @@ impl fmt::Display for Value { // that we handle products more explicitly. enum S<'v> { Disp(ValueRef<'v>), - DispUnlessUnit(ValueRef<'v>), DispCh(char), } let mut stack = Vec::with_capacity(1024); - // Next node to visit, and a boolean indicating whether we should - // display units explicitly (turned off for sums, since a sum of - // a unit is displayed simply as 0 or 1. + // Next node to visit. stack.push(S::Disp(self.as_ref())); 'main_loop: while let Some(next) = stack.pop() { let value = match next { - S::Disp(ref value) | S::DispUnlessUnit(ref value) => value, + S::Disp(ref value) => value, S::DispCh(ch) => { write!(f, "{}", ch)?; continue; @@ -779,9 +776,7 @@ impl fmt::Display for Value { }; if value.is_unit() { - if !matches!(next, S::DispUnlessUnit(..)) { - f.write_str("ε")?; - } + f.write_str("ε")?; } else { // First, write any bitstrings out for tmr in &Tmr::TWO_TWO_N { @@ -812,11 +807,13 @@ impl fmt::Display for Value { // If we don't have a bitstring, then write out the explicit value. if let Some(l_value) = value.as_left() { - f.write_str("0")?; - stack.push(S::DispUnlessUnit(l_value)); + f.write_str("L(")?; + stack.push(S::DispCh(')')); + stack.push(S::Disp(l_value)); } else if let Some(r_value) = value.as_right() { - f.write_str("1")?; - stack.push(S::DispUnlessUnit(r_value)); + f.write_str("R(")?; + stack.push(S::DispCh(')')); + stack.push(S::Disp(r_value)); } else if let Some((l_value, r_value)) = value.as_product() { stack.push(S::DispCh(')')); stack.push(S::Disp(r_value)); From 5e5fbe226b6e560df4da5c7f878f0cb2017fc42e Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 11 Dec 2025 14:38:43 +0000 Subject: [PATCH 7/7] bit_machine: add missing space to StderrTracker This was annoying me. --- src/bit_machine/tracker.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bit_machine/tracker.rs b/src/bit_machine/tracker.rs index 0fef2494..8cecd337 100644 --- a/src/bit_machine/tracker.rs +++ b/src/bit_machine/tracker.rs @@ -163,7 +163,7 @@ impl ExecTracker for StderrTracker { NodeOutput::Success(mut output) => { let output_val = Value::from_padded_bits(&mut output, &node.arrow().target) .expect("output from bit machine will always be well-formed"); - eprintln!(" output {output_val}"); + eprintln!(" output {output_val}"); } }