Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# 0.7.0 - 2025-12-11

* Improve `Display` for `Value` in several ways [#328](https://github.com/BlockstreamResearch/rust-simplicity/pull/328)
* Replace `ExecTracker` trait with a more general API and add `PruneTracker` extension trait [#325](https://github.com/BlockstreamResearch/rust-simplicity/pull/325)
* Add `CONTRIBUTING.md` [#321](https://github.com/BlockstreamResearch/rust-simplicity/pull/321)

# 0.6.0 - 2025-09-17

* Improve FFI API and fix soundness issues [#307](https://github.com/BlockstreamResearch/rust-simplicity/pull/288)
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "simplicity-lang"
version = "0.6.0"
version = "0.7.0"
authors = ["Andrew Poelstra <apoelstra@wpsoftware.net>"]
license = "CC0-1.0"
homepage = "https://github.com/BlockstreamResearch/rust-simplicity/"
Expand Down
2 changes: 1 addition & 1 deletion fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ path = "fuzz_lib/lib.rs"
libfuzzer-sys = "0.4"
# We shouldn't need an explicit version on the next line, but Andrew's tools
# choke on it otherwise. See https://github.com/nix-community/crate2nix/issues/373
simplicity-lang = { path = "..", features = ["test-utils"], version = "0.6.0" }
simplicity-lang = { path = "..", features = ["test-utils"], version = "0.7.0" }
old_simplicity = { package = "simplicity-lang", version = "0.3.1", default-features = false }

[dev-dependencies]
Expand Down
2 changes: 1 addition & 1 deletion simpcli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ edition = "2018"

[dependencies]
# todo add lexopt for command line parsing
simplicity-lang = { version = "0.6.0", path = "..", features = [ "base64", "serde", "elements" ] }
simplicity-lang = { version = "0.7.0", path = "..", features = [ "base64", "serde", "elements" ] }

[[bin]]
name = "simpcli"
Expand Down
2 changes: 1 addition & 1 deletion src/bit_machine/tracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ impl<J: Jet> ExecTracker<J> 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}");
}
}

Expand Down
124 changes: 87 additions & 37 deletions src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -196,10 +196,38 @@ 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> {
value: &'v Value,
value: ValueRef<'v>,
yielded_bytes: usize,
}

Expand Down Expand Up @@ -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,
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.
Expand Down Expand Up @@ -736,43 +759,70 @@ 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()));

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::Disp(ref value) => value,
S::DispCh(ch) => {
write!(f, "{}", ch)?;
continue;
}
};

if value.is_unit() {
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('('));
f.write_str("ε")?;
} else {
unreachable!()
// First, write any bitstrings out
for tmr in &Tmr::TWO_TWO_N {
if value.ty.tmr() == *tmr {
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;
}
}

// If we don't have a bitstring, then write out the explicit value.
if let Some(l_value) = value.as_left() {
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("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));
stack.push(S::DispCh(','));
stack.push(S::Disp(l_value));
stack.push(S::DispCh('('));
} else {
unreachable!()
}
}
}
Ok(())
Expand Down Expand Up @@ -1070,9 +1120,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(), "((0,1),(1,0))",);
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]
Expand Down