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