ultrasonic/
codex.rs

1// UltraSONIC: transactional execution layer with capability-based memory access for zk-AluVM
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Designed in 2019-2025 by Dr Maxim Orlovsky <orlovsky@ubideco.org>
6// Written in 2024-2025 by Dr Maxim Orlovsky <orlovsky@ubideco.org>
7//
8// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland.
9// Copyright (C) 2024-2025 Laboratories for Ubiquitous Deterministic Computing (UBIDECO),
10//                         Institute for Distributed and Cognitive Systems (InDCS), Switzerland.
11// Copyright (C) 2019-2025 Dr Maxim Orlovsky.
12// All rights under the above copyrights are reserved.
13//
14// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
15// in compliance with the License. You may obtain a copy of the License at
16//
17//        http://www.apache.org/licenses/LICENSE-2.0
18//
19// Unless required by applicable law or agreed to in writing, software distributed under the License
20// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
21// or implied. See the License for the specific language governing permissions and limitations under
22// the License.
23
24use std::cmp::Ordering;
25use std::hash::{Hash, Hasher};
26
27use aluvm::alu::regs::Status;
28use aluvm::alu::{CoreConfig, CoreExt, Lib, LibId, LibSite, Vm};
29use aluvm::{fe256, GfaConfig, RegE};
30use amplify::confinement::{SmallVec, TinyOrdMap, TinyString};
31use amplify::num::u256;
32use amplify::Bytes32;
33use commit_verify::{CommitId, CommitmentId, DigestExt, ReservedBytes, Sha256};
34
35use crate::{
36    CellAddr, ContractId, Identity, Instr, Operation, StateCell, StateValue, VerifiedOperation,
37    VmContext, LIB_NAME_ULTRASONIC,
38};
39
40/// Identifier of a contract method call.
41pub type CallId = u16;
42
43/// Codex is a crucial part of a contract; it provides a set of commitments to the contract terms
44/// and conditions expressed as a deterministic program able to run in SONIC computer model.
45///
46/// The main (and the only) operation of the codex is verification of contract [`Operation`]s. It is
47/// done in [`Self::verify`] method.
48#[derive(Clone, Eq, Debug)]
49#[derive(CommitEncode)]
50#[commit_encode(strategy = strict, id = CodexId)]
51#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
52#[strict_type(lib = LIB_NAME_ULTRASONIC)]
53#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
54pub struct Codex {
55    /// Consensus version of the codex.
56    ///
57    /// # Future use
58    ///
59    /// For now, the only supported version is one; thus, a `ReservedBytes` is used.
60    ///
61    /// In the future, with more versions coming, this should be replaced with an enum, where the
62    /// first byte will encode (with standard strict encoding) a version number as an enum variant.
63    /// For instance,
64    ///
65    /// ```ignore
66    /// pub enum Codex {
67    ///     V0(CodexV0),
68    ///     V1(CodexV1)
69    /// }
70    /// pub struct CodexV0 { /*...*/ }
71    /// pub struct CodexV1 { /*...*/ }
72    /// ```
73    pub version: ReservedBytes<1>,
74    /// Human-readable name of the codex used in the UI.
75    pub name: TinyString,
76    /// Identity of the codex developer.
77    pub developer: Identity,
78    /// Timestamp of the codex creation.
79    ///
80    /// This field can be also used to "mine" a vanity codex id. While this feature is noa
81    /// necessary one, many people will try to do it, and it is better to provide them with a
82    /// standard way of doing this, rather to force them into abusing and misusing other fields of
83    /// the codex.
84    pub timestamp: i64,
85    /// A set of feature flags.
86    ///
87    /// RGB-I-0 consensus has no flags, which is enforced with [`ReservedBytes`] structure.
88    pub features: ReservedBytes<4>,
89    /// The order of the field used by VM for all scripts (operation verification and state access
90    /// condition satisfaction).
91    pub field_order: u256,
92    /// VM core configuration for the operation verification.
93    pub verification_config: CoreConfig,
94    /// The VM uses input config to verify the satisfaction of the lock conditions for
95    /// operation inputs.
96    ///
97    /// This configuration is given in the codex and not the contract, but contract issuers still
98    /// decide on it since they can just use a different codex or modify it here.
99    ///
100    /// Please keep in mind that this config may be used to effectively prohibit the use of custom
101    /// lock scripts in destructible state, via setting the maximal complexity level to zero.
102    pub input_config: CoreConfig,
103    /// List of verifiers for each of the calls supported by the codex.
104    pub verifiers: TinyOrdMap<CallId, LibSite>,
105}
106
107impl PartialOrd for Codex {
108    fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
109}
110impl Ord for Codex {
111    fn cmp(&self, other: &Self) -> Ordering { self.commit_id().cmp(&other.commit_id()) }
112}
113impl PartialEq for Codex {
114    fn eq(&self, other: &Self) -> bool { self.commit_id() == other.commit_id() }
115}
116impl Hash for Codex {
117    fn hash<H: Hasher>(&self, state: &mut H) { state.write(&self.commit_id().to_byte_array()); }
118}
119
120impl Codex {
121    /// The codex id holds a commitment to all codex data.
122    ///
123    /// The codex is encoded using strict encoding into the hasher, which is provided by the
124    /// `#[derive(CommitEncode)]` and `#[commit_encode(strategy = strict, id = CodexId)]` macros in
125    /// the structure definition.
126    ///
127    /// It is the same as the result of the [`CommitId::commit_id`] procedure.
128    pub fn codex_id(&self) -> CodexId { self.commit_id() }
129
130    /// The main purpose of the codex is to verify the operation under the contract. This is the
131    /// implementation of this verification procedure.
132    ///
133    /// # Arguments
134    ///
135    /// - `contract_id`: since the contract is external to the codex, this information must be
136    ///   provided to the verifier. While operation also commits to the contract id, this id must
137    ///   come not from the operation itself, but from the external knowledge of the contract id
138    ///   which is being verified; such that operation commitment to the contract is also checked.
139    /// - `operation`: the operation to verify.
140    /// - `memory`: an object holding an actual contract state (see [`Memory`] trait) and provides a
141    ///   read access to it.
142    /// - `repo`: a repository holding VM libraries used in the operation verification, calls to
143    ///   which are kept in the codex (see [`Codex::verifiers`]) _and_ may be called from by the
144    ///   access conditions of the inputs. See [`LibRepo`] for the details.
145    ///
146    /// # Returns
147    ///
148    /// On success, returns an operation wrapped as [`VerifiedOperation`] structure, which should be
149    /// used (1) for updating the contract state by applying the operation, and (2) for the
150    /// persistence of the contract history.
151    ///
152    /// # Errors
153    ///
154    /// On any verification failure, the method does not proceed with further certification and
155    /// instantly returns with one of [`CallError`] variants.
156    ///
157    /// # Panics
158    ///
159    /// Panics if the `repo` (library resolver) returns a library which id doesn't match the
160    /// requested one.
161    pub fn verify(
162        &self,
163        contract_id: ContractId,
164        operation: Operation,
165        memory: &impl Memory,
166        repo: &impl LibRepo,
167    ) -> Result<VerifiedOperation, CallError> {
168        let resolver = |lib_id: LibId| {
169            let lib = repo.get_lib(lib_id)?;
170            // We must have this verification to avoid hacking from the client libraries.
171            if lib.lib_id() != lib_id {
172                panic!(
173                    "The library returned by the `LibRepo` provided for the contract operation \
174                     verification doesn't match the requested library id. This error indicates \
175                     that the software using the consensus verification is invalid or compromised."
176                )
177            }
178            Some(lib)
179        };
180
181        if operation.contract_id != contract_id {
182            return Err(CallError::WrongContract {
183                expected: contract_id,
184                found: operation.contract_id,
185            });
186        }
187
188        // Phase 1: get inputs, verify their presence in the memory
189        let mut vm_inputs = Vm::<Instr<LibId>>::with(self.input_config, GfaConfig {
190            field_order: self.field_order,
191        });
192        let len = operation.destructible_in.len();
193        let mut destructible_inputs = SmallVec::with_capacity(len);
194        for input in &operation.destructible_in {
195            // Read memory
196            let cell = memory
197                .destructible(input.addr)
198                .ok_or(CallError::NoReadOnceInput(input.addr))?;
199
200            // We have same-sized arrays, so we happily skip the result returned by the confined
201            // collection.
202            let _res = destructible_inputs.push((*input, cell));
203            debug_assert!(_res.is_ok());
204        }
205
206        // Check that all read values are present in the memory.
207        let len = operation.immutable_in.len();
208        let mut immutable_inputs = SmallVec::with_capacity(len);
209        for addr in &operation.immutable_in {
210            let data = memory
211                .immutable(*addr)
212                .ok_or(CallError::NoImmutableInput(*addr))?;
213            // We have same-sized arrays, so we happily skip the result returned by the confined
214            // collection.
215            let _res = immutable_inputs.push(data);
216            debug_assert!(_res.is_ok());
217        }
218
219        // Phase 2: Verify operation integrity
220        let entry_point = self
221            .verifiers
222            .get(&operation.call_id)
223            .ok_or(CallError::NotFound(operation.call_id))?;
224        let context = VmContext {
225            witness: operation.witness,
226            destructible_input: destructible_inputs.as_slice(),
227            immutable_input: immutable_inputs.as_slice(),
228            destructible_output: operation.destructible_out.as_slice(),
229            immutable_output: operation.immutable_out.as_slice(),
230        };
231        let mut vm_main = Vm::<Instr<LibId>>::with(self.verification_config, GfaConfig {
232            field_order: self.field_order,
233        });
234        if vm_main.exec(*entry_point, &context, resolver) == Status::Fail {
235            if let Some(err_code) = vm_main.core.cx.get(RegE::E1) {
236                return Err(CallError::Script(err_code));
237            } else {
238                return Err(CallError::ScriptUnspecified);
239            }
240        }
241
242        // Phase 3: Verify input access conditions
243        // Initialize a virtual machine for the input access conditions.
244        // This machine sets the `UI` register to the input index, so the standard destructible
245        // state iterators start with it.
246        for (input_no, (_, cell)) in context.destructible_input.iter().enumerate() {
247            // Verify that the lock script conditions are satisfied.
248            // Please keep in mind that the lock scripts may be effectively prohibited by a codex
249            // just via defining `input_config` complexity level to be zero.
250            if let Some(lock) = cell.lock.and_then(|l| l.script) {
251                // Put also token of authority into a register
252                vm_inputs.core.cx.set_inro_index(input_no as u16);
253
254                if vm_inputs.exec(lock, &context, resolver) == Status::Fail {
255                    // Read error code from the output register
256                    return Err(CallError::Lock(vm_inputs.core.cx.get(RegE::E8)));
257                }
258                vm_inputs.reset();
259            }
260        }
261
262        Ok(VerifiedOperation::new_unchecked(operation.opid(), operation))
263    }
264}
265
266/// The trait, which must be implemented by a client library for a structure providing access to the
267/// valid and most recent contract state, consisting of two parts: *destructible* (also called
268/// *read-once*, or *owned*) and *immutable* (also called *read-only*, *append-only* or *global*).
269pub trait Memory {
270    /// Read a destructible memory cell created by a specific operation read-once output, which is
271    /// defined as a part of [`Operation::destructible`].
272    fn destructible(&self, addr: CellAddr) -> Option<StateCell>;
273    /// Read an immutable memory cell created by a specific operation immutable output, which is
274    /// defined as a part of [`Operation::immutable`].
275    fn immutable(&self, addr: CellAddr) -> Option<StateValue>;
276}
277
278/// The trait providing access to all the VM code libraries used by the contract, in both operation
279/// verification or state access conditions.
280pub trait LibRepo {
281    /// Get a specific library with the provided id.
282    ///
283    /// If the library is not known and this method returns `None`, but the library is called by the
284    /// operation verification or state access script, the verification will fail with
285    /// [`CallError::Script`].
286    fn get_lib(&self, lib_id: LibId) -> Option<&Lib>;
287}
288
289/// Contract operation verification errors returned by [`Codex::verify`].
290///
291/// The name of the error type is chosen so since the operation "calls" a contract method, and the
292/// codex verification verifies the integrity of the call.
293#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error)]
294#[display(doc_comments)]
295pub enum CallError {
296    /// operation doesn't belong to the current contract.
297    #[cfg_attr(
298        feature = "baid64",
299        display = "operation doesn't belong to the current contract {expected} (operation \
300                   contract is {found})."
301    )]
302    #[cfg_attr(
303        not(feature = "baid64"),
304        display = "operation doesn't belong to the current contract."
305    )]
306    WrongContract {
307        /// Expected contract id.
308        expected: ContractId,
309        /// The actual contract id of the operation.
310        found: ContractId,
311    },
312
313    /// operation verifier {0} is not present in the codex.
314    NotFound(CallId),
315
316    /// operation references destructible memory cell which was not defined.
317    #[cfg_attr(
318        feature = "baid64",
319        display = "operation references destructible memory cell {0} which was not defined."
320    )]
321    #[cfg_attr(
322        not(feature = "baid64"),
323        display = "operation references destructible memory cell {0:?} which was not defined."
324    )]
325    NoReadOnceInput(CellAddr),
326
327    /// operation references immutable memory cell which was not defined.
328    #[cfg_attr(
329        feature = "baid64",
330        display = "operation references immutable memory cell {0} which was not defined."
331    )]
332    #[cfg_attr(
333        not(feature = "baid64"),
334        display = "operation references immutable memory cell {0:?} which was not defined."
335    )]
336    /// operation references immutable memory cell {0} which was not defined.
337    NoImmutableInput(CellAddr),
338
339    /// operation input access conditions are unsatisfied.
340    Lock(Option<fe256>),
341
342    /// verification script failure with status code {0}.
343    Script(fe256),
344
345    /// verification script failure (no status code is returned from the verification script).
346    ScriptUnspecified,
347}
348
349/// Unique codex identifier - a commitment to all the [`Codex`] data.
350#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)]
351#[wrapper(AsSlice, Deref, BorrowSlice, Hex, Index, RangeOps)]
352#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
353#[strict_type(lib = LIB_NAME_ULTRASONIC)]
354#[cfg_attr(
355    all(feature = "serde", not(feature = "baid64")),
356    derive(Serialize, Deserialize),
357    serde(transparent)
358)]
359pub struct CodexId(
360    #[from]
361    #[from([u8; 32])]
362    Bytes32,
363);
364
365#[cfg(all(feature = "serde", feature = "baid64"))]
366impl_serde_str_bin_wrapper!(CodexId, Bytes32);
367
368impl From<Sha256> for CodexId {
369    fn from(hasher: Sha256) -> Self { hasher.finish().into() }
370}
371
372impl CommitmentId for CodexId {
373    const TAG: &'static str = "urn:ubideco:sonic:codex#2025-05-15";
374}
375
376#[cfg(feature = "baid64")]
377mod _baid4 {
378    use core::fmt::{self, Display, Formatter};
379    use core::str::FromStr;
380
381    use baid64::{Baid64ParseError, DisplayBaid64, FromBaid64Str};
382
383    use super::*;
384
385    impl DisplayBaid64 for CodexId {
386        const HRI: &'static str = "codex";
387        const CHUNKING: bool = true;
388        const PREFIX: bool = false;
389        const EMBED_CHECKSUM: bool = false;
390        const MNEMONIC: bool = true;
391        fn to_baid64_payload(&self) -> [u8; 32] { self.to_byte_array() }
392    }
393    impl FromBaid64Str for CodexId {}
394    impl FromStr for CodexId {
395        type Err = Baid64ParseError;
396        fn from_str(s: &str) -> Result<Self, Self::Err> { Self::from_baid64_str(s) }
397    }
398    impl Display for CodexId {
399        fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { self.fmt_baid64(f) }
400    }
401}
402
403#[cfg(test)]
404mod test {
405    #![cfg_attr(coverage_nightly, coverage(off))]
406
407    use core::str::FromStr;
408    use std::collections::HashMap;
409
410    use aluvm::alu::aluasm;
411    use aluvm::{zk_aluasm, FIELD_ORDER_SECP};
412    use amplify::ByteArray;
413    use commit_verify::Digest;
414    use strict_encoding::StrictDumb;
415
416    use super::*;
417    use crate::{uasm, AuthToken, CellLock, Input};
418
419    #[test]
420    fn codex_id_display() {
421        let id = CodexId::from_byte_array(Sha256::digest(b"test"));
422        assert_eq!(
423            format!("{id}"),
424            "n4bQgYhM-fWWaL_q-gxVrQFa-O~TxsrC-4Is0V1s-FbDwCgg#berlin-river-delta"
425        );
426        assert_eq!(
427            format!("{id:-}"),
428            "codex:n4bQgYhM-fWWaL_q-gxVrQFa-O~TxsrC-4Is0V1s-FbDwCgg#berlin-river-delta"
429        );
430        assert_eq!(format!("{id:#}"), "n4bQgYhM-fWWaL_q-gxVrQFa-O~TxsrC-4Is0V1s-FbDwCgg");
431    }
432
433    #[test]
434    fn codex_id_from_str() {
435        let id = CodexId::from_byte_array(Sha256::digest(b"test"));
436        assert_eq!(
437            CodexId::from_str(
438                "n4bQgYhM-fWWaL_q-gxVrQFa-O~TxsrC-4Is0V1s-FbDwCgg#berlin-river-delta"
439            )
440            .unwrap(),
441            id
442        );
443        assert_eq!(
444            CodexId::from_str(
445                "codex:n4bQgYhM-fWWaL_q-gxVrQFa-O~TxsrC-4Is0V1s-FbDwCgg#berlin-river-delta"
446            )
447            .unwrap(),
448            id
449        );
450        assert_eq!(
451            CodexId::from_str("codex:n4bQgYhM-fWWaL_q-gxVrQFa-O~TxsrC-4Is0V1s-FbDwCgg").unwrap(),
452            id
453        );
454        assert_eq!(
455            CodexId::from_str("n4bQgYhM-fWWaL_q-gxVrQFa-O~TxsrC-4Is0V1s-FbDwCgg").unwrap(),
456            id
457        );
458    }
459
460    #[derive(Clone, Eq, PartialEq, Debug, Default)]
461    pub struct DumbMemory {
462        pub destructible: HashMap<CellAddr, StateCell>,
463        pub immutable: HashMap<CellAddr, StateValue>,
464    }
465
466    impl Memory for DumbMemory {
467        fn destructible(&self, addr: CellAddr) -> Option<StateCell> {
468            self.destructible.get(&addr).copied()
469        }
470
471        fn immutable(&self, addr: CellAddr) -> Option<StateValue> {
472            self.immutable.get(&addr).copied()
473        }
474    }
475
476    impl LibRepo for Lib {
477        fn get_lib(&self, lib_id: LibId) -> Option<&Lib> {
478            if lib_id == self.lib_id() {
479                Some(self)
480            } else {
481                None
482            }
483        }
484    }
485
486    fn lib_success() -> Lib { Lib::assemble(&aluasm! { stop; }).unwrap() }
487    fn lib_failure_none() -> Lib {
488        Lib::assemble(&zk_aluasm! {
489            clr     E1;
490            test    E2;
491            chk     CO;
492        })
493        .unwrap()
494    }
495    fn lib_failure_one() -> Lib {
496        Lib::assemble(&zk_aluasm! {
497            put     E1, 1;
498            test    E2;
499            chk     CO;
500        })
501        .unwrap()
502    }
503    const SECRET: u8 = 48;
504    fn lib_lock() -> Lib {
505        assert_eq!(SECRET, 48);
506        Lib::assemble(&uasm! {
507            stop;
508            put     E1, 48; // Secret value
509
510            ldi     auth;
511            eq      EA, E1; // Must be provided as a token of authority
512            put     E8, 1;  // Failure #1
513            chk     CO;
514            put     E2, 1; // script is present
515            eq      EB, E2; // Must be provided in witness
516            chk     CO;
517
518            ldi     witness;
519            eq      EA, E1; // Must be provided in witness
520            put     E8, 2;  // Failure #2
521            chk     CO;
522
523            put     E8, 3;  // Failure #3
524            test    EB;     // The rest must be empty
525            not     CO;
526            chk     CO;
527            test    EC;     // The rest must be empty
528            not     CO;
529            chk     CO;
530            test    ED;
531            not     CO;
532            chk     CO;
533        })
534        .unwrap()
535    }
536
537    fn test_stand(modify: impl FnOnce(&mut Codex, &mut Operation, &mut DumbMemory)) {
538        test_stand_script(lib_success(), modify)
539    }
540
541    fn test_stand_script(
542        repo: Lib,
543        modify: impl FnOnce(&mut Codex, &mut Operation, &mut DumbMemory),
544    ) {
545        test_stand_repo(repo.lib_id(), repo, modify)
546    }
547
548    fn test_stand_repo(
549        lib_id: LibId,
550        repo: impl LibRepo,
551        modify: impl FnOnce(&mut Codex, &mut Operation, &mut DumbMemory),
552    ) {
553        let mut codex = Codex::strict_dumb();
554        codex.field_order = FIELD_ORDER_SECP;
555        codex.verification_config = CoreConfig { halt: true, complexity_lim: Some(10_000_000) };
556        codex.input_config = CoreConfig { halt: true, complexity_lim: Some(10_000_000) };
557        codex.verifiers = tiny_bmap! { 0 => LibSite::new(lib_id, 0) };
558
559        let contract_id = ContractId::from_byte_array(Sha256::digest(b"test"));
560        let mut operation = Operation::strict_dumb();
561        operation.contract_id = contract_id;
562        operation.call_id = 0;
563        let mut memory = DumbMemory::default();
564
565        modify(&mut codex, &mut operation, &mut memory);
566
567        codex
568            .verify(contract_id, operation, &memory, &repo)
569            .unwrap();
570    }
571
572    #[test]
573    fn verify_dumb() { test_stand(|_codex, _operation, _memory| {}); }
574
575    #[test]
576    #[should_panic(
577        expected = "WrongContract { expected: ContractId(Array<32>(9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08)), found: ContractId(Array<32>(8810ad581e59f2bc3928b261707a71308f7e139eb04820366dc4d5c18d980225))"
578    )]
579    fn verify_contract_id() {
580        test_stand(|_codex, operation, _memory| {
581            operation.contract_id = ContractId::from_byte_array(Sha256::digest(b"wrong"));
582        });
583    }
584
585    #[test]
586    #[should_panic(
587        expected = "NoImmutableInput(CellAddr { opid: Opid(Array<32>(0000000000000000000000000000000000000000000000000000000000000000)), pos: 0 })"
588    )]
589    fn verify_no_immutable() {
590        test_stand(|_codex, operation, _memory| {
591            operation.immutable_in = small_vec![CellAddr::strict_dumb()];
592        });
593    }
594
595    #[test]
596    #[should_panic(
597        expected = " NoReadOnceInput(CellAddr { opid: Opid(Array<32>(0000000000000000000000000000000000000000000000000000000000000000)), pos: 0 })"
598    )]
599    fn verify_no_destructible() {
600        test_stand(|_codex, operation, _memory| {
601            operation.destructible_in =
602                small_vec![Input { addr: CellAddr::strict_dumb(), witness: none!() }];
603        });
604    }
605
606    #[test]
607    fn verify_immutable() {
608        test_stand(|_codex, operation, memory| {
609            let addr = CellAddr::strict_dumb();
610            memory.immutable.insert(addr, StateValue::strict_dumb());
611            operation.immutable_in = small_vec![addr];
612        });
613    }
614
615    #[test]
616    fn verify_destructible() {
617        test_stand(|_codex, operation, memory| {
618            let addr = CellAddr::strict_dumb();
619            memory.destructible.insert(addr, StateCell::strict_dumb());
620            operation.destructible_in = small_vec![Input { addr, witness: none!() }];
621        });
622    }
623
624    #[test]
625    fn verify_protected_dumb() {
626        test_stand_script(lib_lock(), |_codex, operation, memory| {
627            let addr = CellAddr::strict_dumb();
628            memory.destructible.insert(addr, StateCell {
629                data: StateValue::None,
630                auth: AuthToken::strict_dumb(),
631                lock: Some(CellLock::with_script(lib_lock().lib_id(), 0)),
632            });
633            operation.destructible_in = small_vec![Input { addr, witness: none!() }];
634        });
635    }
636
637    #[test]
638    fn verify_protected() {
639        test_stand_script(lib_lock(), |_codex, operation, memory| {
640            let addr = CellAddr::strict_dumb();
641            memory.destructible.insert(addr, StateCell {
642                data: StateValue::None,
643                auth: AuthToken::from(fe256::from(SECRET)),
644                lock: Some(CellLock::with_script(lib_lock().lib_id(), 1)),
645            });
646            operation.destructible_in = small_vec![Input {
647                addr,
648                witness: StateValue::Single { first: fe256::from(SECRET) }
649            }];
650        });
651    }
652
653    #[test]
654    #[should_panic(
655        expected = "Lock(Some(fe256(0x0000000000000000000000000000000000000000000000000000000000000001)))"
656    )]
657    fn verify_protected_failure1() {
658        test_stand_script(lib_lock(), |_codex, operation, memory| {
659            let addr = CellAddr::strict_dumb();
660            memory.destructible.insert(addr, StateCell {
661                data: StateValue::None,
662                auth: AuthToken::strict_dumb(),
663                lock: Some(CellLock::with_script(lib_lock().lib_id(), 1)),
664            });
665            operation.destructible_in = small_vec![Input {
666                addr,
667                witness: StateValue::Single { first: fe256::from(SECRET) }
668            }];
669        });
670    }
671
672    #[test]
673    #[should_panic(
674        expected = "Lock(Some(fe256(0x0000000000000000000000000000000000000000000000000000000000000002)))"
675    )]
676    fn verify_protected_failure2() {
677        test_stand_script(lib_lock(), |_codex, operation, memory| {
678            let addr = CellAddr::strict_dumb();
679            memory.destructible.insert(addr, StateCell {
680                data: StateValue::None,
681                auth: AuthToken::from(fe256::from(SECRET)),
682                lock: Some(CellLock::with_script(lib_lock().lib_id(), 1)),
683            });
684            operation.destructible_in = small_vec![Input { addr, witness: StateValue::None }];
685        });
686    }
687
688    #[test]
689    #[should_panic(
690        expected = "Lock(Some(fe256(0x0000000000000000000000000000000000000000000000000000000000000003)))"
691    )]
692    fn verify_protected_failure3() {
693        test_stand_script(lib_lock(), |_codex, operation, memory| {
694            let addr = CellAddr::strict_dumb();
695            memory.destructible.insert(addr, StateCell {
696                data: StateValue::None,
697                auth: AuthToken::from(fe256::from(SECRET)),
698                lock: Some(CellLock::with_script(lib_lock().lib_id(), 1)),
699            });
700            operation.destructible_in = small_vec![Input {
701                addr,
702                witness: StateValue::Double {
703                    first: fe256::from(SECRET),
704                    second: fe256::from(SECRET)
705                }
706            }];
707        });
708    }
709
710    #[test]
711    #[should_panic(expected = "ScriptUnspecified")]
712    fn verify_script_failure_unspecified() {
713        test_stand_script(lib_failure_none(), |_codex, _operation, _memory| {});
714    }
715
716    #[test]
717    #[should_panic(
718        expected = "Script(fe256(0x0000000000000000000000000000000000000000000000000000000000000001))"
719    )]
720    fn verify_script_failure_code() {
721        test_stand_script(lib_failure_one(), |_codex, _operation, _memory| {});
722    }
723
724    #[test]
725    #[should_panic(expected = "NotFound(0)")]
726    fn verify_no_verifier() {
727        test_stand_script(lib_success(), |codex, _operation, _memory| {
728            codex.verifiers.clear();
729        });
730    }
731
732    #[test]
733    #[should_panic(expected = "ScriptUnspecified")]
734    fn verify_lib_absent() {
735        test_stand_script(lib_success(), |codex, _operation, _memory| {
736            codex
737                .verifiers
738                .insert(0, LibSite::new(lib_failure_one().lib_id(), 0))
739                .unwrap();
740        });
741    }
742
743    #[test]
744    #[should_panic(expected = "ScriptUnspecified")]
745    fn verify_lib_wrong_pos() {
746        test_stand_script(lib_success(), |codex, _operation, _memory| {
747            codex
748                .verifiers
749                .insert(0, LibSite::new(lib_success().lib_id(), 1))
750                .unwrap();
751        });
752    }
753
754    #[test]
755    #[should_panic(expected = "The library returned by the `LibRepo` provided for the contract \
756                               operation verification doesn't match the requested library id. \
757                               This error indicates that the software using the consensus \
758                               verification is invalid or compromised.")]
759    fn verify_wrong_lib_id() {
760        struct InvalidRepo(Lib);
761        impl LibRepo for InvalidRepo {
762            fn get_lib(&self, _lib_id: LibId) -> Option<&Lib> { Some(&self.0) }
763        }
764        let repo = InvalidRepo(lib_failure_one());
765        test_stand_repo(lib_success().lib_id(), repo, |_codex, _operation, _memory| {});
766    }
767}