Skip to main content

simplicity/node/
commit.rs

1// SPDX-License-Identifier: CC0-1.0
2
3use crate::dag::{DagLike, MaxSharing, NoSharing, PostOrderIterItem};
4use crate::jet::Jet;
5use crate::types::{Arrow, FinalArrow};
6use crate::{encode, types, Value};
7use crate::{Amr, BitIter, BitWriter, Cmr, DecodeError, Ihr, Imr};
8
9use super::{
10    Construct, ConstructData, ConstructNode, Converter, Inner, Marker, NoDisconnect, NoWitness,
11    Node, Redeem, RedeemNode,
12};
13
14use std::io;
15use std::sync::Arc;
16
17#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
18pub struct Commit {
19    /// Makes the type non-constructible.
20    never: std::convert::Infallible,
21}
22
23impl Marker for Commit {
24    type CachedData = Arc<CommitData>;
25    type Witness = NoWitness;
26    type Disconnect = NoDisconnect;
27    type SharingId = Ihr;
28
29    fn compute_sharing_id(_: Cmr, cached_data: &Arc<CommitData>) -> Option<Ihr> {
30        cached_data.ihr
31    }
32}
33
34#[derive(Clone, Debug, PartialEq, Eq, Hash)]
35pub struct CommitData {
36    /// The source and target types of the node
37    arrow: FinalArrow,
38    /// The IMR of the node if it exists. This is distinct from its IHR; it is
39    /// used during computation of the IHR.
40    imr: Option<Imr>,
41    /// The AMR of the node if it exists, meaning, if it is not (an ancestor of)
42    /// a witness or disconnect node.
43    amr: Option<Amr>,
44    /// The IHR of the node if it exists, meaning, if it is not (an ancestor of)
45    /// a witness or disconnect node.
46    ihr: Option<Ihr>,
47}
48
49impl CommitData {
50    /// Accessor for the node's arrow
51    pub fn arrow(&self) -> &FinalArrow {
52        &self.arrow
53    }
54
55    /// Accessor for the node's IHR, if known
56    pub fn ihr(&self) -> Option<Ihr> {
57        self.ihr
58    }
59
60    /// Helper function to compute a cached AMR
61    fn incomplete_amr(
62        inner: Inner<&Arc<Self>, &NoDisconnect, &NoWitness>,
63        arrow: &FinalArrow,
64    ) -> Option<Amr> {
65        match inner {
66            Inner::Iden => Some(Amr::iden(arrow)),
67            Inner::Unit => Some(Amr::unit(arrow)),
68            Inner::InjL(child) => child.amr.map(|amr| Amr::injl(arrow, amr)),
69            Inner::InjR(child) => child.amr.map(|amr| Amr::injr(arrow, amr)),
70            Inner::Take(child) => child.amr.map(|amr| Amr::take(arrow, amr)),
71            Inner::Drop(child) => child.amr.map(|amr| Amr::drop(arrow, amr)),
72            Inner::Comp(left, right) => left
73                .amr
74                .zip(right.amr)
75                .map(|(a, b)| Amr::comp(arrow, &left.arrow, a, b)),
76            Inner::Case(left, right) => {
77                left.amr.zip(right.amr).map(|(a, b)| Amr::case(arrow, a, b))
78            }
79            Inner::AssertL(left, r_cmr) => left
80                .amr
81                .map(|l_amr| Amr::assertl(arrow, l_amr, r_cmr.into())),
82            Inner::AssertR(l_cmr, right) => right
83                .amr
84                .map(|r_amr| Amr::assertr(arrow, l_cmr.into(), r_amr)),
85            Inner::Pair(left, right) => left
86                .amr
87                .zip(right.amr)
88                .map(|(a, b)| Amr::pair(arrow, &left.arrow, &right.arrow, a, b)),
89            Inner::Disconnect(..) => None,
90            Inner::Witness(..) => None,
91            Inner::Fail(entropy) => Some(Amr::fail(entropy)),
92            Inner::Jet(jet) => Some(Amr::jet(jet.as_ref())),
93            Inner::Word(ref val) => Some(Amr::const_word(val)),
94        }
95    }
96
97    /// Helper function to compute a cached first-pass IHR
98    fn imr(inner: Inner<&Arc<Self>, &NoDisconnect, &NoWitness>) -> Option<Imr> {
99        match inner {
100            Inner::Iden => Some(Imr::iden()),
101            Inner::Unit => Some(Imr::unit()),
102            Inner::InjL(child) => child.imr.map(Imr::injl),
103            Inner::InjR(child) => child.imr.map(Imr::injr),
104            Inner::Take(child) => child.imr.map(Imr::take),
105            Inner::Drop(child) => child.imr.map(Imr::drop),
106            Inner::Comp(left, right) => left.imr.zip(right.imr).map(|(a, b)| Imr::comp(a, b)),
107            Inner::Case(left, right) => left.imr.zip(right.imr).map(|(a, b)| Imr::case(a, b)),
108            Inner::AssertL(left, r_cmr) => left.imr.map(|l_ihr| Imr::case(l_ihr, r_cmr.into())),
109            Inner::AssertR(l_cmr, right) => right.imr.map(|r_ihr| Imr::case(l_cmr.into(), r_ihr)),
110            Inner::Pair(left, right) => left.imr.zip(right.imr).map(|(a, b)| Imr::pair(a, b)),
111            Inner::Disconnect(..) => None,
112            Inner::Witness(..) => None,
113            Inner::Fail(entropy) => Some(Imr::fail(entropy)),
114            Inner::Jet(jet) => Some(Imr::jet(jet.as_ref())),
115            Inner::Word(ref val) => Some(Imr::const_word(val)),
116        }
117    }
118
119    pub fn new(
120        arrow: &Arrow,
121        inner: Inner<&Arc<Self>, &NoDisconnect, &NoWitness>,
122    ) -> Result<Self, types::Error> {
123        let final_arrow = arrow.finalize()?;
124        let imr = Self::imr(inner.clone());
125        let amr = Self::incomplete_amr(inner, &final_arrow);
126        Ok(CommitData {
127            imr,
128            amr,
129            ihr: imr.map(|ihr| Ihr::from_imr(ihr, &final_arrow)),
130            arrow: final_arrow,
131        })
132    }
133
134    pub fn from_final(
135        arrow: FinalArrow,
136        inner: Inner<&Arc<Self>, &NoDisconnect, &NoWitness>,
137    ) -> Self {
138        let imr = Self::imr(inner.clone());
139        let amr = Self::incomplete_amr(inner, &arrow);
140        CommitData {
141            imr,
142            amr,
143            ihr: imr.map(|ihr| Ihr::from_imr(ihr, &arrow)),
144            arrow,
145        }
146    }
147}
148
149pub type CommitNode = Node<Commit>;
150
151impl CommitNode {
152    /// Accessor for the node's arrow
153    pub fn arrow(&self) -> &FinalArrow {
154        &self.data.arrow
155    }
156
157    /// Accessor for the node's AMR, if known
158    pub fn amr(&self) -> Option<Amr> {
159        self.data.amr
160    }
161
162    /// Accessor for the node's IHR, if known
163    pub fn ihr(&self) -> Option<Ihr> {
164        self.data.ihr
165    }
166
167    /// Finalizes a DAG, by iterating through through it without sharing, attaching
168    /// witnesses, and hiding branches.
169    ///
170    /// This is a thin wrapper around [`Node::convert`] which fixes a few types to make
171    /// it easier to use.
172    pub fn finalize<C: Converter<Commit, Redeem>>(
173        &self,
174        converter: &mut C,
175    ) -> Result<Arc<RedeemNode>, C::Error> {
176        self.convert::<NoSharing, Redeem, _>(converter)
177    }
178
179    /// Convert a [`CommitNode`] back to a [`ConstructNode`] by redoing type inference
180    pub fn unfinalize_types<'brand>(
181        &self,
182        inference_context: &types::Context<'brand>,
183    ) -> Result<Arc<ConstructNode<'brand>>, types::Error> {
184        struct UnfinalizeTypes<'a, 'brand> {
185            inference_context: &'a types::Context<'brand>,
186        }
187
188        impl<'brand> Converter<Commit, Construct<'brand>> for UnfinalizeTypes<'_, 'brand> {
189            type Error = types::Error;
190            fn convert_witness(
191                &mut self,
192                _: &PostOrderIterItem<&CommitNode>,
193                _: &NoWitness,
194            ) -> Result<Option<Value>, Self::Error> {
195                Ok(None)
196            }
197
198            fn convert_disconnect(
199                &mut self,
200                _: &PostOrderIterItem<&CommitNode>,
201                _: Option<&Arc<ConstructNode<'brand>>>,
202                _: &NoDisconnect,
203            ) -> Result<Option<Arc<ConstructNode<'brand>>>, Self::Error> {
204                Ok(None)
205            }
206
207            fn convert_data(
208                &mut self,
209                _: &PostOrderIterItem<&CommitNode>,
210                inner: Inner<
211                    &Arc<ConstructNode<'brand>>,
212                    &Option<Arc<ConstructNode<'brand>>>,
213                    &Option<Value>,
214                >,
215            ) -> Result<ConstructData<'brand>, Self::Error> {
216                use crate::node::DisconnectConstructible as _;
217
218                let new_arrow = match inner {
219                    Inner::Iden => Arrow::iden(self.inference_context),
220                    Inner::Unit => Arrow::unit(self.inference_context),
221                    Inner::InjL(child) => Arrow::injl(child.arrow()),
222                    Inner::InjR(child) => Arrow::injr(child.arrow()),
223                    Inner::Take(child) => Arrow::take(child.arrow()),
224                    Inner::Drop(child) => Arrow::drop_(child.arrow()),
225                    Inner::Comp(lft, rgt) => Arrow::comp(lft.arrow(), rgt.arrow())?,
226                    Inner::Case(lft, rgt) => Arrow::case(lft.arrow(), rgt.arrow())?,
227                    Inner::Pair(lft, rgt) => Arrow::pair(lft.arrow(), rgt.arrow())?,
228                    Inner::Disconnect(lft, rgt) => {
229                        Arrow::disconnect(lft.arrow(), &rgt.as_ref().map(|node| node.arrow()))?
230                    }
231                    Inner::AssertL(lft, _) => Arrow::assertl(lft.arrow())?,
232                    Inner::AssertR(_, rgt) => Arrow::assertr(rgt.arrow())?,
233                    Inner::Witness(_) => Arrow::witness(self.inference_context),
234                    Inner::Fail(_) => Arrow::fail(self.inference_context),
235                    Inner::Jet(ref jet) => Arrow::jet(self.inference_context, jet.as_ref()),
236                    Inner::Word(ref word) => Arrow::const_word(self.inference_context, word),
237                };
238
239                Ok(ConstructData::new(new_arrow))
240            }
241        }
242
243        self.convert::<MaxSharing<Commit>, _, _>(&mut UnfinalizeTypes { inference_context })
244    }
245
246    /// Decode a Simplicity program from bits, without witness data.
247    ///
248    /// # Usage
249    ///
250    /// Use this method only if the serialization **does not** include the witness data.
251    /// This means, the program simply has no witness during commitment,
252    /// or the witness is provided by other means.
253    ///
254    /// If the serialization contains the witness data, then use [`RedeemNode::decode()`].
255    pub fn decode<I: Iterator<Item = u8>, J: Jet>(
256        bits: BitIter<I>,
257    ) -> Result<Arc<Self>, DecodeError> {
258        use crate::decode;
259
260        // 1. Decode program with out witnesses.
261        let program = types::Context::with_context(|ctx| {
262            let construct =
263                crate::ConstructNode::decode::<I, J>(&ctx, bits).map_err(DecodeError::Decode)?;
264            construct.finalize_types().map_err(DecodeError::Type)
265        })?;
266        // 2. Do sharing check, using incomplete IHRs
267        if program.as_ref().is_shared_as::<MaxSharing<Commit>>() {
268            Ok(program)
269        } else {
270            Err(DecodeError::Decode(decode::Error::SharingNotMaximal))
271        }
272    }
273
274    #[cfg(feature = "base64")]
275    #[allow(clippy::should_implement_trait)] // returns Arc<Self>
276    pub fn from_str(s: &str) -> Result<Arc<Self>, crate::ParseError> {
277        use crate::base64::engine::general_purpose;
278        use crate::base64::Engine as _;
279
280        let v = general_purpose::STANDARD
281            .decode(s)
282            .map_err(crate::ParseError::Base64)?;
283        let iter = crate::BitIter::new(v.into_iter());
284        Self::decode::<_, crate::jet::Core>(iter).map_err(crate::ParseError::Decode)
285    }
286
287    /// Encode a Simplicity expression to bits without any witness data
288    #[deprecated(since = "0.5.0", note = "use Self::encode_without_witness instead")]
289    pub fn encode(&self, w: &mut BitWriter<&mut dyn io::Write>) -> io::Result<usize> {
290        let program_bits = encode::encode_program(self, w)?;
291        w.flush_all()?;
292        Ok(program_bits)
293    }
294
295    /// Encode a Simplicity program to a vector of bytes, without any witness data.
296    #[deprecated(since = "0.5.0", note = "use Self::to_vec_without_witness instead")]
297    pub fn encode_to_vec(&self) -> Vec<u8> {
298        let mut program = Vec::<u8>::new();
299        self.encode_without_witness(&mut program)
300            .expect("write to vector never fails");
301        debug_assert!(!program.is_empty());
302
303        program
304    }
305}
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310
311    use hex::DisplayHex;
312    use std::fmt;
313
314    use crate::{decode::Error, jet::Core};
315    #[cfg(feature = "human_encoding")]
316    use crate::{human_encoding::Forest, jet::CoreEnv, node::SimpleFinalizer, BitMachine, Value};
317
318    #[cfg(feature = "human_encoding")]
319    #[cfg_attr(not(feature = "base64"), allow(unused_variables))]
320    #[track_caller]
321    fn assert_program_deserializable<J: Jet>(
322        prog_str: &str,
323        prog_bytes: &[u8],
324        cmr_str: &str,
325        b64_str: &str,
326    ) -> Arc<CommitNode> {
327        let forest = match Forest::parse::<J>(prog_str) {
328            Ok(forest) => forest,
329            Err(e) => panic!("Failed to parse program `{}`: {}", prog_str, e),
330        };
331        assert_eq!(
332            forest.roots().len(),
333            1,
334            "program `{}` has multiple roots",
335            prog_str
336        );
337        let main = match forest.roots().get("main") {
338            Some(root) => root,
339            None => panic!("Program `{}` has no main", prog_str),
340        };
341
342        let prog_hex = prog_bytes.as_hex();
343        let main_bytes = main.to_vec_without_witness();
344        assert_eq!(
345            prog_bytes,
346            main_bytes,
347            "Program string `{}` encoded to {} (expected {})",
348            prog_str,
349            main_bytes.as_hex(),
350            prog_hex,
351        );
352
353        let iter = BitIter::from(prog_bytes);
354        let prog = match CommitNode::decode::<_, J>(iter) {
355            Ok(prog) => prog,
356            Err(e) => panic!("program {} failed: {}", prog_hex, e),
357        };
358
359        assert_eq!(
360            prog.cmr().to_string(),
361            cmr_str,
362            "CMR mismatch (got {} expected {}) for program {}",
363            prog.cmr(),
364            cmr_str,
365            prog_hex,
366        );
367
368        let reser_sink = prog.to_vec_without_witness();
369        assert_eq!(
370            prog_bytes,
371            &reser_sink[..],
372            "program {} reserialized as {}",
373            prog_hex,
374            reser_sink.as_hex(),
375        );
376
377        #[cfg(feature = "base64")]
378        {
379            assert_eq!(prog.to_string(), b64_str);
380            assert_eq!(prog.display().program().to_string(), b64_str);
381            assert_eq!(prog, CommitNode::from_str(b64_str).unwrap());
382        }
383
384        prog
385    }
386
387    #[track_caller]
388    fn assert_program_not_deserializable<J: Jet>(prog: &[u8], err: &dyn fmt::Display) {
389        let prog_hex = prog.as_hex();
390        let err_str = err.to_string();
391
392        let iter = BitIter::from(prog);
393        match CommitNode::decode::<_, J>(iter) {
394            Ok(prog) => panic!(
395                "Program {} succeded (expected error {}). Program parsed as:\n{:?}",
396                prog_hex, err, prog
397            ),
398            Err(e) if e.to_string() == err_str => {} // ok
399            Err(e) => panic!(
400                "Program {} failed with error {} (expected error {})",
401                prog_hex, e, err
402            ),
403        };
404    }
405
406    #[test]
407    fn canonical_order() {
408        // "main = comp unit iden", but with the iden serialized before the unit
409        // To obtain this test vector I temporarily swapped `get_left` and `get_right`
410        // in the implementation of `PostOrderIter`
411        assert_program_not_deserializable::<Core>(&[0xa8, 0x48, 0x10], &Error::NotInCanonicalOrder);
412
413        // "main = iden", but prefixed by some unused nodes, the first of which is also iden.
414        assert_program_not_deserializable::<Core>(
415            &[0xc1, 0x00, 0x06, 0x20],
416            &Error::NotInCanonicalOrder,
417        );
418    }
419
420    #[test]
421    fn hidden_node() {
422        // main = hidden deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef
423        #[rustfmt::skip]
424        let hidden = [
425            0x36, 0xf5, 0x6d, 0xf7, 0x7e, 0xf5, 0x6d, 0xf7,
426            0x7e, 0xf5, 0x6d, 0xf7, 0x7e, 0xf5, 0x6d, 0xf7,
427            0x7e, 0xf5, 0x6d, 0xf7, 0x7e, 0xf5, 0x6d, 0xf7,
428            0x7e, 0xf5, 0x6d, 0xf7, 0x7e, 0xf5, 0x6d, 0xf7,
429            78,
430        ];
431        assert_program_not_deserializable::<Core>(&hidden, &Error::HiddenNode);
432
433        // main = comp witness hidden deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef
434        let hidden = [
435            0xae, 0xdb, 0xd5, 0xb7, 0xdd, 0xfb, 0xd5, 0xb7, 0xdd, 0xfb, 0xd5, 0xb7, 0xdd, 0xfb,
436            0xd5, 0xb7, 0xdd, 0xfb, 0xd5, 0xb7, 0xdd, 0xfb, 0xd5, 0xb7, 0xdd, 0xfb, 0xd5, 0xb7,
437            0xdd, 0xfb, 0xd5, 0xb7, 0xdd, 0xe0, 0x80,
438        ];
439        assert_program_not_deserializable::<Core>(&hidden, &Error::HiddenNode);
440    }
441
442    #[test]
443    fn case_both_children_hidden() {
444        // h1 = hidden deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef
445        // main = case h1 h1
446        #[rustfmt::skip]
447        let hidden = [
448            0x8d, 0xbd, 0x5b, 0x7d, 0xdf, 0xbd, 0x5b, 0x7d,
449            0xdf, 0xbd, 0x5b, 0x7d, 0xdf, 0xbd, 0x5b, 0x7d,
450            0xdf, 0xbd, 0x5b, 0x7d, 0xdf, 0xbd, 0x5b, 0x7d,
451            0xdf, 0xbd, 0x5b, 0x7d, 0xdf, 0xbd, 0x5b, 0x7d,
452            0xde, 0x10,
453        ];
454        assert_program_not_deserializable::<Core>(&hidden, &Error::BothChildrenHidden);
455    }
456
457    #[test]
458    fn unshared_hidden() {
459        // This program has a repeated hidden node, but all other sharing is correct
460        // and the order is canonical, etc.
461        #[rustfmt::skip]
462        let hidden = [
463            0xd6, 0xe9, 0x62, 0x56, 0x62, 0xc9, 0x38, 0x8a,
464            0x44, 0x31, 0x85, 0xee, 0xc2, 0x2b, 0x91, 0x48,
465            0x87, 0xe1, 0xfd, 0x18, 0x57, 0xc2, 0x8c, 0x4a,
466            0x28, 0x44, 0x2f, 0xa8, 0x61, 0x5c, 0xa7, 0x6e,
467            0x8c, 0xf9, 0x80, 0xc2, 0x18, 0x95, 0x98, 0xb2,
468            0x4e, 0x22, 0x91, 0x0c, 0x61, 0x7b, 0xb0, 0x8a,
469            0xe4, 0x52, 0x21, 0xf8, 0x7f, 0x46, 0x15, 0xf0,
470            0xa3, 0x12, 0x8a, 0x11, 0x0b, 0xea, 0x18, 0x57,
471            0x29, 0xdb, 0xa3, 0x3e, 0x60, 0x30, 0x2c, 0x00,
472            0xd0, 0x48, 0x20,
473        ];
474        assert_program_not_deserializable::<Core>(&hidden, &Error::SharingNotMaximal);
475    }
476
477    #[test]
478    #[cfg(feature = "human_encoding")]
479    fn shared_witnesses() {
480        assert_program_deserializable::<Core>(
481            "main := witness",
482            &[0x38],
483            "a0fc8debd6796917c86b77aded82e6c61649889ae8f2ed65b57b41aa9d90e375",
484            "OA==",
485        );
486
487        #[rustfmt::skip]
488        let bad_diff1s = vec![
489            // Above program, but with both witness nodes shared (note they have
490            // the same type and CMR)
491            vec![
492                0xda, 0xe2, 0x39, 0xa3, 0x10, 0x42, 0x0e, 0x05,
493                0x71, 0x88, 0xa3, 0x6d, 0xc4, 0x11, 0x80, 0x80
494            ],
495            // Same program but with each `witness` replaced by `comp iden witness`, which
496            // is semantically the same but buries the offending witness nodes a bit to
497            // trip up naive sharing logic.
498            vec![
499                0xde, 0x87, 0x04, 0x08, 0xe6, 0x8c, 0x41, 0x08,
500                0x38, 0x15, 0xc6, 0x22, 0x8d, 0xb7, 0x10, 0x46,
501                0x02, 0x00,
502            ],
503        ];
504        for bad_diff1 in bad_diff1s {
505            assert_program_not_deserializable::<Core>(&bad_diff1, &Error::SharingNotMaximal);
506        }
507
508        #[rustfmt::skip]
509        let diff1s = vec![
510            (
511                // Sharing corrected
512                "
513                    -- Program which demands two 32-bit witnesses, the first one == the second + 1
514                    wit1 := witness : 1 -> 2^32
515                    wit2 := witness : 1 -> 2^32
516
517                    wit_diff := comp (comp (pair wit1 wit2) jet_subtract_32) (drop iden) : 1 -> 2^32
518                    diff_is_one := comp (pair wit_diff jet_one_32) jet_eq_32             : 1 -> 2
519                    main := comp diff_is_one jet_verify                                  : 1 -> 1
520                ",
521                vec![
522                    0xdc, 0xee, 0x28, 0xe6, 0x8c, 0x41, 0x08, 0x38,
523                    0x15, 0xc6, 0x22, 0x8d, 0xb7, 0x10, 0x46, 0x02,
524                    0x00,
525                ],
526                // CMR not checked against C code, since C won't give us any data without witnesses
527                "e7661630013b789c535146485c72001cad7eea92d04bc4135dc9c2d9e48093b7",
528                "3O4o5oxBCDgVxiKNtxBGAgA=",
529            ),
530            // Same program but with each `witness` replaced by `comp iden witness`.
531            (
532                "
533                    -- Program which demands two 32-bit witnesses, the first one == the second + 1
534                    wit1 := witness : 1 -> 2^32
535                    wit2 := witness : 1 -> 2^32
536                    compwit1 := comp iden wit1
537                    compwit2 := comp iden wit2
538
539                    wit_diff := comp (comp (pair compwit1 compwit2) jet_subtract_32) (drop iden)
540                    diff_is_one := comp (pair wit_diff jet_one_32) jet_eq_32             : 1 -> 2
541                    main := comp diff_is_one jet_verify                                  : 1 -> 1
542                ",
543                vec![
544                    0xe0, 0x28, 0x70, 0x43, 0x83, 0x00, 0xab, 0x9a,
545                    0x31, 0x04, 0x20, 0xe0, 0x57, 0x18, 0x8a, 0x36,
546                    0xdc, 0x41, 0x18, 0x08,
547                ],
548                // CMR not checked against C code, since C won't give us any data without witnesses
549                "774105cb723e00ab3ac48452aa2dd3744999605393f0343eedc8c6387aa5b3c8",
550                "4ChwQ4MAq5oxBCDgVxiKNtxBGAg=",
551            )
552        ];
553
554        for (prog_str, diff1, cmr, b64) in diff1s {
555            let diff1_prog = crate::node::commit::tests::assert_program_deserializable::<Core>(
556                prog_str, &diff1, cmr, b64,
557            );
558
559            // Attempt to finalize, providing 32-bit witnesses 0, 1, ..., and then
560            // counting how many were consumed afterward.
561            let mut counter = 0..100;
562            let witness_iter = (&mut counter).rev().map(Value::u32);
563            let diff1_final = diff1_prog
564                .finalize(&mut SimpleFinalizer::new(witness_iter))
565                .unwrap();
566            assert_eq!(counter, 0..98);
567
568            // Execute the program to confirm that it worked
569            let mut mac =
570                BitMachine::for_program(&diff1_final).expect("program has reasonable bounds");
571            mac.exec(&diff1_final, &CoreEnv::new()).unwrap();
572        }
573    }
574
575    #[test]
576    fn extra_nodes() {
577        // main = comp unit unit # but with an extra unconnected `unit` stuck on the beginning
578        // I created this unit test by hand
579        assert_program_not_deserializable::<Core>(&[0xa9, 0x48, 0x00], &Error::NotInCanonicalOrder);
580    }
581
582    #[test]
583    #[cfg(feature = "human_encoding")]
584    fn regression_177() {
585        // `case (drop iden) iden` from upstream occurs-check test. Has an infinitely sized
586        // input type. Will fail trying to unify the input type with the unit type before
587        // doing the occurs check, putting an infinitely-sized type into the error variant.
588        //
589        // The human-readable encoding will keep going and then also hit the occurs check.
590        //
591        // Check that both error types can be generated and printed in finite space/time.
592        let bad_prog = "
593            id := iden
594            main := case (drop id) id
595        ";
596        match Forest::parse::<Core>(bad_prog) {
597            Ok(_) => panic!("program should have failed"),
598            Err(set) => {
599                let mut errs_happened = (false, false);
600                for err in set.iter() {
601                    match err {
602                        crate::human_encoding::Error::TypeCheck(e @ types::Error::Bind { .. }) => {
603                            errs_happened.0 = true;
604                            e.to_string();
605                        }
606                        crate::human_encoding::Error::TypeCheck(
607                            e @ types::Error::OccursCheck { .. },
608                        ) => {
609                            errs_happened.1 = true;
610                            e.to_string();
611                        }
612                        x => panic!("unexpected error {x:?}"),
613                    }
614                }
615                assert_eq!(errs_happened, (true, true));
616            }
617        };
618    }
619}