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(&self) -> Result<Arc<ConstructNode<J>>, types::Error> {
191        struct UnfinalizeTypes<J: Jet> {
192            inference_context: types::Context,
193            phantom: PhantomData<J>,
194        }
195
196        impl<J: Jet> Converter<Commit<J>, Construct<J>> for UnfinalizeTypes<J> {
197            type Error = types::Error;
198            fn convert_witness(
199                &mut self,
200                _: &PostOrderIterItem<&CommitNode<J>>,
201                _: &NoWitness,
202            ) -> Result<Option<Value>, Self::Error> {
203                Ok(None)
204            }
205
206            fn convert_disconnect(
207                &mut self,
208                _: &PostOrderIterItem<&CommitNode<J>>,
209                _: Option<&Arc<ConstructNode<J>>>,
210                _: &NoDisconnect,
211            ) -> Result<Option<Arc<ConstructNode<J>>>, Self::Error> {
212                Ok(None)
213            }
214
215            fn convert_data(
216                &mut self,
217                _: &PostOrderIterItem<&CommitNode<J>>,
218                inner: Inner<
219                    &Arc<ConstructNode<J>>,
220                    J,
221                    &Option<Arc<ConstructNode<J>>>,
222                    &Option<Value>,
223                >,
224            ) -> Result<ConstructData<J>, Self::Error> {
225                let inner = inner
226                    .map(|node| node.arrow())
227                    .map_disconnect(|maybe_node| maybe_node.as_ref().map(|node| node.arrow()));
228                let inner = inner.disconnect_as_ref(); // lol sigh rust
229                Ok(ConstructData::new(Arrow::from_inner(
230                    &self.inference_context,
231                    inner,
232                )?))
233            }
234        }
235
236        self.convert::<MaxSharing<Commit<J>>, _, _>(&mut UnfinalizeTypes {
237            inference_context: types::Context::new(),
238            phantom: PhantomData,
239        })
240    }
241
242    /// Decode a Simplicity program from bits, without witness data.
243    ///
244    /// # Usage
245    ///
246    /// Use this method only if the serialization **does not** include the witness data.
247    /// This means, the program simply has no witness during commitment,
248    /// or the witness is provided by other means.
249    ///
250    /// If the serialization contains the witness data, then use [`RedeemNode::decode()`].
251    pub fn decode<I: Iterator<Item = u8>>(bits: BitIter<I>) -> Result<Arc<Self>, DecodeError> {
252        use crate::decode;
253
254        // 1. Decode program with out witnesses.
255        let construct = crate::ConstructNode::decode(bits).map_err(DecodeError::Decode)?;
256        let program = construct.finalize_types().map_err(DecodeError::Type)?;
257        // 2. Do sharing check, using incomplete IHRs
258        if program.as_ref().is_shared_as::<MaxSharing<Commit<J>>>() {
259            Ok(program)
260        } else {
261            Err(DecodeError::Decode(decode::Error::SharingNotMaximal))
262        }
263    }
264
265    #[cfg(feature = "base64")]
266    #[allow(clippy::should_implement_trait)] // returns Arc<Self>
267    pub fn from_str(s: &str) -> Result<Arc<Self>, crate::ParseError> {
268        use crate::base64::engine::general_purpose;
269        use crate::base64::Engine as _;
270
271        let v = general_purpose::STANDARD
272            .decode(s)
273            .map_err(crate::ParseError::Base64)?;
274        let iter = crate::BitIter::new(v.into_iter());
275        Self::decode(iter).map_err(crate::ParseError::Decode)
276    }
277
278    /// Encode a Simplicity expression to bits without any witness data
279    #[deprecated(since = "0.5.0", note = "use Self::encode_without_witness instead")]
280    pub fn encode<W: io::Write>(&self, w: &mut BitWriter<W>) -> io::Result<usize> {
281        let program_bits = encode::encode_program(self, w)?;
282        w.flush_all()?;
283        Ok(program_bits)
284    }
285
286    /// Encode a Simplicity program to a vector of bytes, without any witness data.
287    #[deprecated(since = "0.5.0", note = "use Self::to_vec_without_witness instead")]
288    pub fn encode_to_vec(&self) -> Vec<u8> {
289        let mut program = Vec::<u8>::new();
290        self.encode_without_witness(&mut program)
291            .expect("write to vector never fails");
292        debug_assert!(!program.is_empty());
293
294        program
295    }
296}
297
298#[cfg(test)]
299mod tests {
300    use super::*;
301
302    use hex::DisplayHex;
303    use std::fmt;
304
305    use crate::decode::Error;
306    use crate::human_encoding::Forest;
307    use crate::jet::Core;
308    use crate::node::SimpleFinalizer;
309    use crate::{BitMachine, Value};
310
311    #[cfg_attr(not(feature = "base64"), allow(unused_variables))]
312    #[track_caller]
313    fn assert_program_deserializable<J: Jet>(
314        prog_str: &str,
315        prog_bytes: &[u8],
316        cmr_str: &str,
317        b64_str: &str,
318    ) -> Arc<CommitNode<J>> {
319        let forest = match Forest::<J>::parse(prog_str) {
320            Ok(forest) => forest,
321            Err(e) => panic!("Failed to parse program `{}`: {}", prog_str, e),
322        };
323        assert_eq!(
324            forest.roots().len(),
325            1,
326            "program `{}` has multiple roots",
327            prog_str
328        );
329        let main = match forest.roots().get("main") {
330            Some(root) => root,
331            None => panic!("Program `{}` has no main", prog_str),
332        };
333
334        let prog_hex = prog_bytes.as_hex();
335        let main_bytes = main.to_vec_without_witness();
336        assert_eq!(
337            prog_bytes,
338            main_bytes,
339            "Program string `{}` encoded to {} (expected {})",
340            prog_str,
341            main_bytes.as_hex(),
342            prog_hex,
343        );
344
345        let iter = BitIter::from(prog_bytes);
346        let prog = match CommitNode::<J>::decode(iter) {
347            Ok(prog) => prog,
348            Err(e) => panic!("program {} failed: {}", prog_hex, e),
349        };
350
351        assert_eq!(
352            prog.cmr().to_string(),
353            cmr_str,
354            "CMR mismatch (got {} expected {}) for program {}",
355            prog.cmr(),
356            cmr_str,
357            prog_hex,
358        );
359
360        let reser_sink = prog.to_vec_without_witness();
361        assert_eq!(
362            prog_bytes,
363            &reser_sink[..],
364            "program {} reserialized as {}",
365            prog_hex,
366            reser_sink.as_hex(),
367        );
368
369        #[cfg(feature = "base64")]
370        {
371            assert_eq!(prog.to_string(), b64_str);
372            assert_eq!(prog.display().program().to_string(), b64_str);
373            assert_eq!(prog, CommitNode::from_str(b64_str).unwrap());
374        }
375
376        prog
377    }
378
379    #[track_caller]
380    fn assert_program_not_deserializable<J: Jet>(prog: &[u8], err: &dyn fmt::Display) {
381        let prog_hex = prog.as_hex();
382        let err_str = err.to_string();
383
384        let iter = BitIter::from(prog);
385        match CommitNode::<J>::decode(iter) {
386            Ok(prog) => panic!(
387                "Program {} succeded (expected error {}). Program parsed as:\n{:?}",
388                prog_hex, err, prog
389            ),
390            Err(e) if e.to_string() == err_str => {} // ok
391            Err(e) => panic!(
392                "Program {} failed with error {} (expected error {})",
393                prog_hex, e, err
394            ),
395        };
396    }
397
398    #[test]
399    fn canonical_order() {
400        // "main = comp unit iden", but with the iden serialized before the unit
401        // To obtain this test vector I temporarily swapped `get_left` and `get_right`
402        // in the implementation of `PostOrderIter`
403        assert_program_not_deserializable::<Core>(&[0xa8, 0x48, 0x10], &Error::NotInCanonicalOrder);
404
405        // "main = iden", but prefixed by some unused nodes, the first of which is also iden.
406        assert_program_not_deserializable::<Core>(
407            &[0xc1, 0x00, 0x06, 0x20],
408            &Error::NotInCanonicalOrder,
409        );
410    }
411
412    #[test]
413    fn hidden_node() {
414        // main = hidden deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef
415        #[rustfmt::skip]
416        let hidden = [
417            0x36, 0xf5, 0x6d, 0xf7, 0x7e, 0xf5, 0x6d, 0xf7,
418            0x7e, 0xf5, 0x6d, 0xf7, 0x7e, 0xf5, 0x6d, 0xf7,
419            0x7e, 0xf5, 0x6d, 0xf7, 0x7e, 0xf5, 0x6d, 0xf7,
420            0x7e, 0xf5, 0x6d, 0xf7, 0x7e, 0xf5, 0x6d, 0xf7,
421            78,
422        ];
423        assert_program_not_deserializable::<Core>(&hidden, &Error::HiddenNode);
424
425        // main = comp witness hidden deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef
426        let hidden = [
427            0xae, 0xdb, 0xd5, 0xb7, 0xdd, 0xfb, 0xd5, 0xb7, 0xdd, 0xfb, 0xd5, 0xb7, 0xdd, 0xfb,
428            0xd5, 0xb7, 0xdd, 0xfb, 0xd5, 0xb7, 0xdd, 0xfb, 0xd5, 0xb7, 0xdd, 0xfb, 0xd5, 0xb7,
429            0xdd, 0xfb, 0xd5, 0xb7, 0xdd, 0xe0, 0x80,
430        ];
431        assert_program_not_deserializable::<Core>(&hidden, &Error::HiddenNode);
432    }
433
434    #[test]
435    fn case_both_children_hidden() {
436        // h1 = hidden deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef
437        // main = case h1 h1
438        #[rustfmt::skip]
439        let hidden = [
440            0x8d, 0xbd, 0x5b, 0x7d, 0xdf, 0xbd, 0x5b, 0x7d,
441            0xdf, 0xbd, 0x5b, 0x7d, 0xdf, 0xbd, 0x5b, 0x7d,
442            0xdf, 0xbd, 0x5b, 0x7d, 0xdf, 0xbd, 0x5b, 0x7d,
443            0xdf, 0xbd, 0x5b, 0x7d, 0xdf, 0xbd, 0x5b, 0x7d,
444            0xde, 0x10,
445        ];
446        assert_program_not_deserializable::<Core>(&hidden, &Error::BothChildrenHidden);
447    }
448
449    #[test]
450    fn unshared_hidden() {
451        // This program has a repeated hidden node, but all other sharing is correct
452        // and the order is canonical, etc.
453        #[rustfmt::skip]
454        let hidden = [
455            0xd6, 0xe9, 0x62, 0x56, 0x62, 0xc9, 0x38, 0x8a,
456            0x44, 0x31, 0x85, 0xee, 0xc2, 0x2b, 0x91, 0x48,
457            0x87, 0xe1, 0xfd, 0x18, 0x57, 0xc2, 0x8c, 0x4a,
458            0x28, 0x44, 0x2f, 0xa8, 0x61, 0x5c, 0xa7, 0x6e,
459            0x8c, 0xf9, 0x80, 0xc2, 0x18, 0x95, 0x98, 0xb2,
460            0x4e, 0x22, 0x91, 0x0c, 0x61, 0x7b, 0xb0, 0x8a,
461            0xe4, 0x52, 0x21, 0xf8, 0x7f, 0x46, 0x15, 0xf0,
462            0xa3, 0x12, 0x8a, 0x11, 0x0b, 0xea, 0x18, 0x57,
463            0x29, 0xdb, 0xa3, 0x3e, 0x60, 0x30, 0x2c, 0x00,
464            0xd0, 0x48, 0x20,
465        ];
466        assert_program_not_deserializable::<Core>(&hidden, &Error::SharingNotMaximal);
467    }
468
469    #[test]
470    fn shared_witnesses() {
471        assert_program_deserializable::<Core>(
472            "main := witness",
473            &[0x38],
474            "a0fc8debd6796917c86b77aded82e6c61649889ae8f2ed65b57b41aa9d90e375",
475            "OA==",
476        );
477
478        #[rustfmt::skip]
479        let bad_diff1s = vec![
480            // Above program, but with both witness nodes shared (note they have
481            // the same type and CMR)
482            vec![
483                0xda, 0xe2, 0x39, 0xa3, 0x10, 0x42, 0x0e, 0x05,
484                0x71, 0x88, 0xa3, 0x6d, 0xc4, 0x11, 0x80, 0x80
485            ],
486            // Same program but with each `witness` replaced by `comp iden witness`, which
487            // is semantically the same but buries the offending witness nodes a bit to
488            // trip up naive sharing logic.
489            vec![
490                0xde, 0x87, 0x04, 0x08, 0xe6, 0x8c, 0x41, 0x08,
491                0x38, 0x15, 0xc6, 0x22, 0x8d, 0xb7, 0x10, 0x46,
492                0x02, 0x00,
493            ],
494        ];
495        for bad_diff1 in bad_diff1s {
496            assert_program_not_deserializable::<Core>(&bad_diff1, &Error::SharingNotMaximal);
497        }
498
499        #[rustfmt::skip]
500        let diff1s = vec![
501            (
502                // Sharing corrected
503                "
504                    -- Program which demands two 32-bit witnesses, the first one == the second + 1
505                    wit1 := witness : 1 -> 2^32
506                    wit2 := witness : 1 -> 2^32
507
508                    wit_diff := comp (comp (pair wit1 wit2) jet_subtract_32) (drop iden) : 1 -> 2^32
509                    diff_is_one := comp (pair wit_diff jet_one_32) jet_eq_32             : 1 -> 2
510                    main := comp diff_is_one jet_verify                                  : 1 -> 1
511                ",
512                vec![
513                    0xdc, 0xee, 0x28, 0xe6, 0x8c, 0x41, 0x08, 0x38,
514                    0x15, 0xc6, 0x22, 0x8d, 0xb7, 0x10, 0x46, 0x02,
515                    0x00,
516                ],
517                // CMR not checked against C code, since C won't give us any data without witnesses
518                "e9339a0d715c721bff752aedc02710cdf3399f3f8d86e64456e85a1bc06ecb7c",
519                "3O4o5oxBCDgVxiKNtxBGAgA=",
520            ),
521            // Same program but with each `witness` replaced by `comp iden witness`.
522            (
523                "
524                    -- Program which demands two 32-bit witnesses, the first one == the second + 1
525                    wit1 := witness : 1 -> 2^32
526                    wit2 := witness : 1 -> 2^32
527                    compwit1 := comp iden wit1
528                    compwit2 := comp iden wit2
529
530                    wit_diff := comp (comp (pair compwit1 compwit2) jet_subtract_32) (drop iden)
531                    diff_is_one := comp (pair wit_diff jet_one_32) jet_eq_32             : 1 -> 2
532                    main := comp diff_is_one jet_verify                                  : 1 -> 1
533                ",
534                vec![
535                    0xe0, 0x28, 0x70, 0x43, 0x83, 0x00, 0xab, 0x9a,
536                    0x31, 0x04, 0x20, 0xe0, 0x57, 0x18, 0x8a, 0x36,
537                    0xdc, 0x41, 0x18, 0x08,
538                ],
539                // CMR not checked against C code, since C won't give us any data without witnesses
540                "d03bf350f406aef3af0d48e6533b3325ff86f18a36e0e73895a5cd6d6692b860",
541                "4ChwQ4MAq5oxBCDgVxiKNtxBGAg=",
542            )
543        ];
544
545        for (prog_str, diff1, cmr, b64) in diff1s {
546            let diff1_prog = crate::node::commit::tests::assert_program_deserializable::<Core>(
547                prog_str, &diff1, cmr, b64,
548            );
549
550            // Attempt to finalize, providing 32-bit witnesses 0, 1, ..., and then
551            // counting how many were consumed afterward.
552            let mut counter = 0..100;
553            let witness_iter = (&mut counter).rev().map(Value::u32);
554            let diff1_final = diff1_prog
555                .finalize(&mut SimpleFinalizer::new(witness_iter))
556                .unwrap();
557            assert_eq!(counter, 0..98);
558
559            // Execute the program to confirm that it worked
560            let mut mac =
561                BitMachine::for_program(&diff1_final).expect("program has reasonable bounds");
562            mac.exec(&diff1_final, &()).unwrap();
563        }
564    }
565
566    #[test]
567    fn extra_nodes() {
568        // main = comp unit unit # but with an extra unconnected `unit` stuck on the beginning
569        // I created this unit test by hand
570        assert_program_not_deserializable::<Core>(&[0xa9, 0x48, 0x00], &Error::NotInCanonicalOrder);
571    }
572
573    #[test]
574    fn regression_177() {
575        // `case (drop iden) iden` from upstream occurs-check test. Has an infinitely sized
576        // input type. Will fail trying to unify the input type with the unit type before
577        // doing the occurs check, putting an infinitely-sized type into the error variant.
578        //
579        // The human-readable encoding will keep going and then also hit the occurs check.
580        //
581        // Check that both error types can be generated and printed in finite space/time.
582        let bad_prog = "
583            id := iden
584            main := case (drop id) id
585        ";
586        match Forest::<Core>::parse(bad_prog) {
587            Ok(_) => panic!("program should have failed"),
588            Err(set) => {
589                let mut errs_happened = (false, false);
590                for err in set.iter() {
591                    match err {
592                        crate::human_encoding::Error::TypeCheck(e @ types::Error::Bind { .. }) => {
593                            errs_happened.0 = true;
594                            e.to_string();
595                        }
596                        crate::human_encoding::Error::TypeCheck(
597                            e @ types::Error::OccursCheck { .. },
598                        ) => {
599                            errs_happened.1 = true;
600                            e.to_string();
601                        }
602                        x => panic!("unexpected error {x:?}"),
603                    }
604                }
605                assert_eq!(errs_happened, (true, true));
606            }
607        };
608    }
609}