rgbcore/
verify.rs

1// RGB Core Library: consensus layer for RGB smart contracts.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Designed in 2019-2025 by Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
6// Written in 2024-2025 by Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
7//
8// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland.
9// Copyright (C) 2024-2025 LNP/BP Laboratories,
10//                         Institute for Distributed and Cognitive Systems (InDCS), Switzerland.
11// Copyright (C) 2025 RGB Consortium, Switzerland.
12// Copyright (C) 2019-2025 Dr Maxim Orlovsky.
13// All rights under the above copyrights are reserved.
14//
15// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
16// in compliance with the License. You may obtain a copy of the License at
17//
18//        http://www.apache.org/licenses/LICENSE-2.0
19//
20// Unless required by applicable law or agreed to in writing, software distributed under the License
21// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
22// or implied. See the License for the specific language governing permissions and limitations under
23// the License.
24
25use alloc::collections::{BTreeMap, BTreeSet};
26use alloc::vec::Vec;
27use core::error::Error;
28use core::fmt::{Debug, Formatter};
29
30use amplify::confinement::SmallOrdMap;
31use amplify::ByteArray;
32use single_use_seals::{PublishedWitness, SealError, SealWitness};
33use ultrasonic::{
34    AuthToken, CallError, CellAddr, Codex, ContractId, LibRepo, Memory, Operation, Opid, VerifiedOperation,
35};
36
37use crate::{RgbSeal, RgbSealDef, LIB_NAME_RGB};
38
39/// Combination of an operation with operation-defined seals.
40///
41/// An operation contains only [`AuthToken`]'s, which are commitments to seal definitions.
42/// Hence, we have to separately include a full seal definition next to the operation data.
43#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
44#[strict_type(lib = LIB_NAME_RGB)]
45#[cfg_attr(
46    feature = "serde",
47    derive(Serialize, Deserialize),
48    serde(
49        rename_all = "camelCase",
50        bound = "Seal::Definition: serde::Serialize + for<'d> serde::Deserialize<'d>, Seal::PubWitness: \
51                 serde::Serialize + for<'d> serde::Deserialize<'d>, Seal::CliWitness: serde::Serialize + for<'d> \
52                 serde::Deserialize<'d>"
53    )
54)]
55pub struct OperationSeals<Seal: RgbSeal> {
56    /// The operation.
57    pub operation: Operation,
58    /// Seals defined by an operation.
59    pub defined_seals: SmallOrdMap<u16, Seal::Definition>,
60    /// An optional witness for the closing of the operation input seals.
61    pub witness: Option<SealWitness<Seal>>,
62}
63
64impl<Seal: RgbSeal> Clone for OperationSeals<Seal>
65where
66    Seal::PubWitness: Clone,
67    Seal::CliWitness: Clone,
68{
69    fn clone(&self) -> Self {
70        Self {
71            operation: self.operation.clone(),
72            defined_seals: self.defined_seals.clone(),
73            witness: self.witness.clone(),
74        }
75    }
76}
77
78/// Provider which reads an operation and its seals from a consignment stream.
79pub trait ReadOperation: Sized {
80    /// Seal definition type used by operations.
81    type Seal: RgbSeal;
82
83    /// Reads an operation and its seals from a consignment stream and initialize the witness
84    /// reader.
85    fn read_operation(&mut self) -> Result<Option<OperationSeals<Self::Seal>>, impl Error + 'static>;
86}
87
88/// API exposed by the contract required for evaluating and verifying the contract state (see
89/// [`ContractVerify`]).
90///
91/// NB: `apply_operation` is called only after `apply_witness`.
92pub trait ContractApi<Seal: RgbSeal> {
93    /// Returns contract id for the processed contract.
94    ///
95    /// Called only once during the operation verification.
96    fn contract_id(&self) -> ContractId;
97
98    /// Returns a codex against which the contract must be verified.
99    ///
100    /// Called only once during the operation verification.
101    fn codex(&self) -> &Codex;
102
103    /// Returns repository providing script libraries used during the verification.
104    ///
105    /// Called only once during the operation verification.
106    fn repo(&self) -> &impl LibRepo;
107
108    /// Returns a memory implementation providing read access to all the contract state cells,
109    /// including immutable and destructible memory.
110    fn memory(&self) -> &impl Memory;
111
112    /// Detects whether an operation with a given id is already known as a _valid_ operation for the
113    /// ledger.
114    ///
115    /// The method MUST return `true` for genesis operation.
116    fn is_known(&self, opid: Opid) -> bool;
117
118    /// # Nota bene:
119    ///
120    /// The method is called only for those operations which are not known (i.e. [`Self::is_known`]
121    /// returns `false` for the operation id).
122    ///
123    /// The method is NOT called for the genesis operation.
124    fn apply_operation(&mut self, op: VerifiedOperation);
125
126    /// # Nota bene:
127    ///
128    /// The method is called for all operations, including known ones, for which the consignment
129    /// provides at least single seal definition information (thus, it may be called for the genesis
130    /// operation as well).
131    fn apply_seals(&mut self, opid: Opid, seals: SmallOrdMap<u16, Seal::Definition>);
132
133    /// # Nota bene:
134    ///
135    /// The method is called for all operations, including known ones, which have a witness (i.e.,
136    /// except genesis or operations with no destroyed state).
137    fn apply_witness(&mut self, opid: Opid, witness: SealWitness<Seal>);
138}
139
140/// Main implementation of the contract verification procedure.
141///
142/// # Nota bene
143///
144/// This trait cannot be manually implemented; it is always accessible as a blanked implementation
145/// for all types implementing [`ContractApi`] trait.
146///
147/// The purpose of the trait is to prevent overriding of the implementation in client libraries.
148pub trait ContractVerify<Seal: RgbSeal>: ContractApi<Seal> {
149    /// Evaluate contract state by verifying and applying contract operations coming from a
150    /// consignment `reader`.
151    fn evaluate<R: ReadOperation<Seal = Seal>>(&mut self, mut reader: R) -> Result<(), VerificationError<Seal>> {
152        let contract_id = self.contract_id();
153        let codex_id = self.codex().codex_id();
154
155        let mut is_genesis = true;
156        let mut seals = BTreeMap::<CellAddr, Seal>::new();
157
158        while let Some(mut block) = reader
159            .read_operation()
160            .map_err(|e| VerificationError::Stream(Box::new(e)))?
161        {
162            // Genesis cannot commit to the contract id since the contract does not exist yet;
163            // thus, we have to apply this little trick
164            if is_genesis {
165                if block.operation.contract_id.to_byte_array() != codex_id.to_byte_array() {
166                    return Err(VerificationError::NoCodexCommitment);
167                }
168                block.operation.contract_id = contract_id;
169            }
170            let opid = block.operation.opid();
171
172            // We need to check that all seal definitions strictly match operation-defined destructible cells
173            let defined = block
174                .operation
175                .destructible_out
176                .iter()
177                .map(|cell| cell.auth)
178                .collect::<BTreeSet<_>>();
179            let reported = block
180                .defined_seals
181                .values()
182                .map(|seal| seal.auth_token())
183                .collect::<BTreeSet<_>>();
184            // It is a subset and not an equal set since some seals might be unknown to us:
185            // we know their commitment auth token but do not know the definition.
186            if !reported.is_subset(&defined) {
187                let sources = block
188                    .defined_seals
189                    .iter()
190                    .map(|(pos, seal)| (*pos, seal.to_string()))
191                    .collect();
192                return Err(VerificationError::SealsDefinitionMismatch { opid, reported, defined, sources });
193            }
194
195            // Collect single-use seal closings by the operation
196            let mut closed_seals = Vec::<Seal>::new();
197            for input in &block.operation.destructible_in {
198                let seal = seals
199                    .remove(&input.addr)
200                    .ok_or(VerificationError::SealUnknown(input.addr))?;
201                closed_seals.push(seal);
202            }
203
204            // If the operation was validated before, we need to skip its validation, since its inputs are not a
205            // part of the state anymore.
206            let operation = if self.is_known(opid) {
207                None
208            } else {
209                // Verify the operation
210                let verified = self
211                    .codex()
212                    .verify(contract_id, block.operation, self.memory(), self.repo())?;
213                Some(verified)
214            };
215
216            // This convoluted logic happens since we use a state machine which ensures the client can't lie to
217            // the verifier
218            // Now we can add operation-defined seals to the set of known seals
219            let mut seal_sources: BTreeSet<_> = block
220                .defined_seals
221                .iter()
222                .filter_map(|(pos, seal)| seal.to_src().map(|seal| (CellAddr::new(opid, *pos), seal)))
223                .collect();
224
225            if let Some(witness) = block.witness {
226                let msg = opid.to_byte_array();
227                witness
228                    .verify_seals_closing(&closed_seals, msg.into())
229                    .map_err(|e| VerificationError::SealsNotClosed(witness.published.pub_id(), opid, e))?;
230
231                //  Each witness actually produces its own set of witness-output-based seal sources.
232                let pub_id = witness.published.pub_id();
233                let iter = block
234                    .defined_seals
235                    .iter()
236                    .filter(|(_, seal)| seal.to_src().is_none())
237                    .map(|(pos, seal)| (CellAddr::new(opid, *pos), seal.resolve(pub_id)));
238                seal_sources.extend(iter);
239
240                self.apply_witness(opid, witness);
241            } else if !closed_seals.is_empty() {
242                return Err(VerificationError::NoWitness(opid));
243            }
244
245            seals.extend(seal_sources);
246            if is_genesis {
247                is_genesis = false
248            } else if let Some(operation) = operation {
249                self.apply_operation(operation);
250            }
251
252            if !block.defined_seals.is_empty() {
253                self.apply_seals(opid, block.defined_seals);
254            }
255        }
256
257        Ok(())
258    }
259}
260
261impl<Seal: RgbSeal, C: ContractApi<Seal>> ContractVerify<Seal> for C {}
262
263/// Errors returned from the verification.
264#[derive(Display, Error, From)]
265#[display(doc_comments)]
266pub enum VerificationError<Seal: RgbSeal> {
267    /// error reading the consignment stream.
268    ///
269    /// Details: {0}
270    Stream(Box<dyn Error>),
271
272    /// genesis does not commit to the codex id; a wrong contract genesis is used.
273    NoCodexCommitment,
274
275    /// no witness known for the operation {0}.
276    NoWitness(Opid),
277
278    /// single-use seals are not closed properly with witness {0} for operation {1}.
279    ///
280    /// Details: {2}
281    SealsNotClosed(<Seal::PubWitness as PublishedWitness<Seal>>::PubId, Opid, SealError<Seal>),
282
283    /// unknown seal definition for cell address {0}.
284    SealUnknown(CellAddr),
285
286    /// seals, reported to be defined by the operation {opid}, do match the assignments in the
287    /// operation.
288    ///
289    /// Actual operation seals from the assignments: {defined:#?}
290    ///
291    /// Reported seals: {reported:#?}
292    ///
293    /// Sources for the reported seals: {sources:#?}
294    #[allow(missing_docs)]
295    SealsDefinitionMismatch {
296        opid: Opid,
297        reported: BTreeSet<AuthToken>,
298        defined: BTreeSet<AuthToken>,
299        sources: BTreeMap<u16, String>,
300    },
301
302    /// Eror returned by the virtual machine script.
303    #[from]
304    #[display(inner)]
305    Vm(CallError),
306}
307
308// We need manual implementation since otherwise we get an unneeded `Seal::PubWitness: Debug` bound
309impl<Seal: RgbSeal> Debug for VerificationError<Seal> {
310    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { write!(f, "{self}") }
311}
312
313#[cfg(test)]
314mod test {
315    #![cfg_attr(coverage_nightly, coverage(off))]
316
317    use std::collections::HashMap;
318    use std::convert::Infallible;
319    use std::vec;
320
321    use bp::seals::{TxoSeal, TxoSealExt, WOutpoint, WTxoSeal};
322    use bp::{Outpoint, Sats, ScriptPubkey, SeqNo, Tx, TxIn, TxOut, Vout};
323    use strict_encoding::StrictDumb;
324    use ultrasonic::aluvm::alu::{aluasm, CoreConfig, Lib, LibId, LibSite};
325    use ultrasonic::aluvm::FIELD_ORDER_SECP;
326    use ultrasonic::{fe256, CodexId, Genesis, Identity, Input, StateCell, StateData, StateValue};
327
328    use super::*;
329
330    #[derive(Clone)]
331    struct TestReader(vec::IntoIter<OperationSeals<TxoSeal>>);
332    impl ReadOperation for TestReader {
333        type Seal = TxoSeal;
334        fn read_operation(&mut self) -> Result<Option<OperationSeals<Self::Seal>>, impl Error + 'static> {
335            Result::<_, Infallible>::Ok(self.0.next())
336        }
337    }
338    impl TestReader {
339        pub fn new(vec: Vec<OperationSeals<TxoSeal>>) -> Self { Self(vec.into_iter()) }
340    }
341
342    struct TestContract {
343        pub codex: Codex,
344        pub contract_id: ContractId,
345        pub libs: HashMap<LibId, Lib>,
346        pub global: HashMap<CellAddr, StateValue>,
347        pub owned: HashMap<CellAddr, StateCell>,
348        pub known_ops: BTreeMap<Opid, Operation>,
349        pub seal_definitions: BTreeMap<Opid, HashMap<u16, WTxoSeal>>,
350        pub witnesses: BTreeMap<Opid, Vec<SealWitness<TxoSeal>>>,
351    }
352    impl Memory for TestContract {
353        fn destructible(&self, addr: CellAddr) -> Option<StateCell> { self.owned.get(&addr).cloned() }
354        fn immutable(&self, addr: CellAddr) -> Option<StateValue> { self.global.get(&addr).cloned() }
355    }
356    impl LibRepo for TestContract {
357        fn get_lib(&self, lib_id: LibId) -> Option<&Lib> { self.libs.get(&lib_id) }
358    }
359    impl ContractApi<TxoSeal> for TestContract {
360        fn contract_id(&self) -> ContractId { self.contract_id }
361        fn codex(&self) -> &Codex { &self.codex }
362        fn repo(&self) -> &impl LibRepo { self }
363        fn memory(&self) -> &impl Memory { self }
364        fn is_known(&self, opid: Opid) -> bool { self.known_ops.contains_key(&opid) }
365        fn apply_operation(&mut self, op: VerifiedOperation) {
366            let opid = op.opid();
367            let op = op.into_operation();
368            for (no, inp) in op.immutable_out.iter().enumerate() {
369                self.global
370                    .insert(CellAddr::new(opid, no as u16), inp.value);
371            }
372            for (no, inp) in op.destructible_out.iter().enumerate() {
373                self.owned.insert(CellAddr::new(opid, no as u16), *inp);
374            }
375            self.known_ops.insert(opid, op);
376        }
377        fn apply_seals(&mut self, opid: Opid, seals: SmallOrdMap<u16, WTxoSeal>) {
378            self.seal_definitions.entry(opid).or_default().extend(seals);
379        }
380        fn apply_witness(&mut self, opid: Opid, witness: SealWitness<TxoSeal>) {
381            self.witnesses.entry(opid).or_default().push(witness);
382        }
383    }
384
385    fn lib() -> Lib {
386        let code = aluasm! {
387            stop;
388        };
389        Lib::assemble(&code).unwrap()
390    }
391
392    fn codex() -> Codex {
393        let lib_id = lib().lib_id();
394        Codex {
395            name: tiny_s!("TestCodex"),
396            developer: Identity::default(),
397            version: default!(),
398            features: default!(),
399            timestamp: 1732529307,
400            field_order: FIELD_ORDER_SECP,
401            input_config: CoreConfig::default(),
402            verification_config: CoreConfig::default(),
403            verifiers: tiny_bmap! {
404                0 => LibSite::new(lib_id, 0),
405            },
406        }
407    }
408
409    const SEAL_WOUT: WTxoSeal = WTxoSeal {
410        primary: WOutpoint::Wout(Vout::from_u32(0)),
411        secondary: TxoSealExt::Fallback(Outpoint::coinbase()),
412    };
413
414    const SEAL_1: WTxoSeal = WTxoSeal {
415        primary: WOutpoint::Extern(Outpoint::coinbase()),
416        secondary: TxoSealExt::Fallback(Outpoint::coinbase()),
417    };
418
419    fn genesis() -> Genesis {
420        let mut genesis = Genesis::strict_dumb();
421        genesis.codex_id = codex().codex_id();
422        genesis.immutable_out = small_vec![StateData::new(0u64, 1000u64)];
423        genesis.destructible_out = small_vec![StateCell {
424            data: StateValue::None,
425            auth: SEAL_1.auth_token(),
426            lock: None
427        }];
428        genesis
429    }
430
431    fn contract() -> TestContract {
432        let lib = lib();
433        let lib_id = lib.lib_id();
434        let genesis = genesis();
435        let genesis_op = genesis.to_operation(ContractId::strict_dumb());
436        let genesis_opid = genesis_op.opid();
437        TestContract {
438            codex: codex(),
439            contract_id: ContractId::strict_dumb(),
440            libs: map! { lib_id => lib },
441            global: none!(),
442            owned: map! { CellAddr::new(genesis_opid, 0) => genesis_op.destructible_out[0] },
443            known_ops: bmap! { genesis_opid => genesis_op },
444            seal_definitions: bmap! { genesis_opid => none!() },
445            witnesses: bmap! { genesis_opid => none!() },
446        }
447    }
448
449    fn operation() -> Operation {
450        let genesis = genesis();
451        let contract = contract();
452        let genesis_op = genesis.to_operation(contract.contract_id);
453        let genesis_opid = genesis_op.opid();
454        Operation {
455            version: default!(),
456            contract_id: contract.contract_id,
457            call_id: 0,
458            nonce: fe256::ZERO,
459            witness: StateValue::None,
460            destructible_in: small_vec![Input {
461                addr: CellAddr::new(genesis_opid, 0),
462                witness: StateValue::None
463            }],
464            immutable_in: Default::default(),
465            destructible_out: Default::default(),
466            immutable_out: Default::default(),
467        }
468    }
469
470    #[allow(clippy::result_large_err)]
471    fn run(reader: TestReader) -> Result<(), VerificationError<TxoSeal>> {
472        let mut contract = contract();
473        contract.evaluate(reader.clone())?;
474
475        // Check contract values
476        let mut ops = bmap! {};
477        let mut seals = bmap! {};
478        let mut witnesses = bmap! {};
479        for entry in reader.0 {
480            let opid = entry.operation.opid();
481            ops.insert(opid, entry.operation);
482            seals.insert(opid, entry.defined_seals.into_iter().collect());
483            witnesses.insert(opid, entry.witness.into_iter().collect());
484        }
485
486        ops.pop_first();
487        let (genesis_opid, genesis_op) = contract.known_ops.first_key_value().unwrap();
488        ops.insert(*genesis_opid, genesis_op.clone());
489        seals.pop_first();
490        let (genesis_opid, definitions) = contract.seal_definitions.first_key_value().unwrap();
491        seals.insert(*genesis_opid, definitions.clone());
492        witnesses.pop_first();
493        let (genesis_opid, definitions) = contract.witnesses.first_key_value().unwrap();
494        witnesses.insert(*genesis_opid, definitions.clone());
495
496        assert_eq!(ops, contract.known_ops);
497        assert_eq!(seals, contract.seal_definitions);
498        assert_eq!(witnesses, contract.witnesses);
499        Ok(())
500    }
501
502    #[test]
503    fn empty() {
504        let reader = TestReader::new(vec![]);
505        run(reader).unwrap();
506    }
507
508    #[test]
509    fn genesis_only() {
510        let genesis = genesis();
511        let genesis_op = genesis.to_operation(genesis.codex_id.to_byte_array().into());
512
513        let reader =
514            TestReader::new(vec![OperationSeals { operation: genesis_op, defined_seals: none!(), witness: None }]);
515        run(reader).unwrap();
516    }
517
518    #[test]
519    #[should_panic(expected = "genesis does not commit to the codex id; a wrong contract genesis is used.")]
520    fn invalid_genesis() {
521        let mut genesis = genesis();
522        genesis.codex_id = CodexId::from_byte_array([0xAD; 32]);
523        let genesis_op = genesis.to_operation(genesis.codex_id.to_byte_array().into());
524
525        let reader =
526            TestReader::new(vec![OperationSeals { operation: genesis_op, defined_seals: none!(), witness: None }]);
527        run(reader).unwrap();
528    }
529
530    #[test]
531    #[should_panic(expected = "seals, reported to be defined by the operation \
532                               k7fHvPyBlnM8m1n0QUaqNhB0I8kTwWXmi7nB_ZjTGVc, do match the assignments in the \
533                               operation.
534Actual operation seals from the assignments: {
535    AuthToken(
536        fe256(
537            0x0000141b74832b85ca7bc7e2899cc3e5617a29ac4340f09b105524a6f62bd597,
538        ),
539    ),
540}
541Reported seals: {
542    AuthToken(
543        fe256(
544            0x000046c31ad97975e90e4ab2ee247f0e2f39ec8461823023e977cc14bcda14f5,
545        ),
546    ),
547}
548Sources for the reported seals: {
549    0: \"~:0/00000000000000000000000000000000000000000000000000000000000000000000000000000000\",
550}")]
551    fn invalid_seals() {
552        let genesis = genesis();
553        let genesis_op = genesis.to_operation(genesis.codex_id.to_byte_array().into());
554
555        let reader = TestReader::new(vec![OperationSeals {
556            operation: genesis_op,
557            defined_seals: small_bmap! { 0 => WTxoSeal::strict_dumb() },
558            witness: None,
559        }]);
560        run(reader).unwrap();
561    }
562
563    #[test]
564    #[should_panic(expected = "unknown seal definition for cell address k7fHvPyBlnM8m1n0QUaqNhB0I8kTwWXmi7nB_ZjTGVc:0.")]
565    fn seal_unknown() {
566        let genesis = genesis();
567        let genesis_op = genesis.to_operation(genesis.codex_id.to_byte_array().into());
568        let operation = operation();
569
570        let reader = TestReader::new(vec![
571            OperationSeals { operation: genesis_op, defined_seals: none!(), witness: None },
572            OperationSeals { operation, defined_seals: none!(), witness: None },
573        ]);
574        run(reader).unwrap();
575    }
576
577    #[test]
578    #[should_panic(expected = "unknown seal definition for cell address k7fHvPyBlnM8m1n0QUaqNhB0I8kTwWXmi7nB_ZjTGVc:0.")]
579    fn genesis_with_wout() {
580        let mut genesis = genesis();
581        genesis.destructible_out[0].auth = SEAL_WOUT.auth_token();
582        let genesis_op = genesis.to_operation(genesis.codex_id.to_byte_array().into());
583        let operation = operation();
584
585        let reader = TestReader::new(vec![
586            OperationSeals {
587                operation: genesis_op,
588                defined_seals: small_bmap! { 0 => SEAL_WOUT},
589                witness: None,
590            },
591            OperationSeals { operation, defined_seals: none!(), witness: None },
592        ]);
593        run(reader).unwrap();
594    }
595
596    #[test]
597    #[should_panic(expected = "no witness known for the operation KAPb7ikgqk_ofCp1fZWm_T6XfKjuCwNf9BlZfPpoR0E.")]
598    fn no_witness() {
599        let genesis = genesis();
600        let genesis_op = genesis.to_operation(genesis.codex_id.to_byte_array().into());
601        let operation = operation();
602
603        let reader = TestReader::new(vec![
604            OperationSeals {
605                operation: genesis_op,
606                defined_seals: small_bmap! { 0 => SEAL_1 },
607                witness: None,
608            },
609            OperationSeals { operation, defined_seals: none!(), witness: None },
610        ]);
611        run(reader).unwrap();
612    }
613
614    #[test]
615    #[should_panic(expected = "single-use seals are not closed properly with witness \
616                               4ebd325a4b394cff8c57e8317ccf5a8d0e2bdf1b8526f8aad6c8e43d8240621a for operation \
617                               KAPb7ikgqk_ofCp1fZWm_T6XfKjuCwNf9BlZfPpoR0E.
618Details: seal \
619                               0000000000000000000000000000000000000000000000000000000000000000:0/\
620                               0000000000000000000000000000000000000000000000000000000000000000:0 is not included in \
621                               the public witness 4ebd325a4b394cff8c57e8317ccf5a8d0e2bdf1b8526f8aad6c8e43d8240621a")]
622    fn seals_unclosed() {
623        let genesis = genesis();
624        let genesis_op = genesis.to_operation(genesis.codex_id.to_byte_array().into());
625        let operation = operation();
626
627        let reader = TestReader::new(vec![
628            OperationSeals {
629                operation: genesis_op,
630                defined_seals: small_bmap! { 0 => SEAL_1 },
631                witness: None,
632            },
633            OperationSeals {
634                operation,
635                defined_seals: none!(),
636                witness: Some(SealWitness::new(strict_dumb!(), strict_dumb!())),
637            },
638        ]);
639        run(reader).unwrap();
640    }
641
642    #[test]
643    #[should_panic(expected = "ingle-use seals are not closed properly with witness \
644                               1692606e775129a6733b6dc48ec7f5771f8e30d8c5304c0949d36efad2411812 for operation \
645                               KAPb7ikgqk_ofCp1fZWm_T6XfKjuCwNf9BlZfPpoR0E.
646Details: seal \
647                               0000000000000000000000000000000000000000000000000000000000000000:0/\
648                               0000000000000000000000000000000000000000000000000000000000000000:0 is not included in \
649                               the public witness 1692606e775129a6733b6dc48ec7f5771f8e30d8c5304c0949d36efad2411812")]
650    fn not_spending_utxo() {
651        let genesis = genesis();
652        let genesis_op = genesis.to_operation(genesis.codex_id.to_byte_array().into());
653        let operation = operation();
654
655        let mut witness = Tx::strict_dumb();
656        witness
657            .outputs
658            .push(TxOut {
659                value: Sats::ZERO,
660                script_pubkey: ScriptPubkey::op_return(&[]),
661            })
662            .unwrap();
663
664        let reader = TestReader::new(vec![
665            OperationSeals {
666                operation: genesis_op,
667                defined_seals: small_bmap! { 0 => SEAL_1 },
668                witness: None,
669            },
670            OperationSeals {
671                operation,
672                defined_seals: none!(),
673                witness: Some(SealWitness::new(witness, strict_dumb!())),
674            },
675        ]);
676        run(reader).unwrap();
677    }
678
679    #[test]
680    #[should_panic(expected = "single-use seals are not closed properly with witness \
681                               0520b790b442e9c023e2ea0e0e284fbe60086d64f01037082f19464b44f9642e for operation \
682                               KAPb7ikgqk_ofCp1fZWm_T6XfKjuCwNf9BlZfPpoR0E.
683Details: seal \
684                               0000000000000000000000000000000000000000000000000000000000000000:0/\
685                               0000000000000000000000000000000000000000000000000000000000000000:0 is not included in \
686                               the public witness 0520b790b442e9c023e2ea0e0e284fbe60086d64f01037082f19464b44f9642e")]
687    fn missing_commitment() {
688        let genesis = genesis();
689        let genesis_op = genesis.to_operation(genesis.codex_id.to_byte_array().into());
690        let operation = operation();
691
692        let mut witness = Tx::strict_dumb();
693        witness
694            .inputs
695            .push(TxIn {
696                prev_output: Outpoint::coinbase(),
697                sig_script: none!(),
698                sequence: SeqNo::from_consensus_u32(0),
699                witness: none!(),
700            })
701            .unwrap();
702        witness
703            .outputs
704            .push(TxOut {
705                value: Sats::ZERO,
706                script_pubkey: ScriptPubkey::op_return(&[]),
707            })
708            .unwrap();
709
710        let reader = TestReader::new(vec![
711            OperationSeals {
712                operation: genesis_op,
713                defined_seals: small_bmap! { 0 => SEAL_1 },
714                witness: None,
715            },
716            OperationSeals {
717                operation,
718                defined_seals: none!(),
719                witness: Some(SealWitness::new(witness, strict_dumb!())),
720            },
721        ]);
722        run(reader).unwrap();
723    }
724}