Skip to main content

amaru_kernel/cardano/
witness_set.rs

1// Copyright 2026 PRAGMA
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::BTreeMap;
16
17use crate::{
18    BootstrapWitness, HasScriptHash, Hash, MemoizedNativeScript, MemoizedPlutusData, NonEmptyVec, PlutusScript,
19    Redeemers, ScriptKind, VKeyWitness, cbor, size::SCRIPT,
20};
21
22/// FIXME: Accidentally not a set
23///
24///   NonEmptyVec below are supposed to be a NonEmptySet where duplicates would fail to decode. But it isn't.
25///   In the Haskell's codebsae, the default decoder for Set fails on duplicate starting from
26///   v9 and above:
27///
28///   <https://github.com/IntersectMBO/cardano-ledger/blob/fe0af09c8667bf8ffdd17dd1a387515b9b0533bf/libs/cardano-ledger-binary/src/Cardano/Ledger/Binary/Decoding/Decoder.hs#L906-L928>.
29///
30///   However, the decoders for witnesses fields were (accidentally) overridden and did not use the
31///   default `Set` implementation. So, duplicates were silently ignored instead of leading to
32///   decoder failure (while still allowing a set tag, and still expecting at least one element):
33///
34///   <https://github.com/IntersectMBO/cardano-ledger/blob/fe0af09c8667bf8ffdd17dd1a387515b9b0533bf/eras/alonzo/impl/src/Cardano/Ledger/Alonzo/TxWits.hs#L610-L624>
35///
36///   Importantly, this behaviour is changing again in v12, back to being a non-empty set / maps.
37#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize, cbor::Encode, cbor::Decode)]
38#[cbor(map)]
39pub struct WitnessSet {
40    #[n(0)]
41    pub vkeywitness: Option<NonEmptyVec<VKeyWitness>>,
42
43    #[n(1)]
44    pub native_script: Option<NonEmptyVec<MemoizedNativeScript>>,
45
46    /// FIXME: Accidentally not a set
47    ///
48    /// See note on vkeywitness.
49    #[n(2)]
50    pub bootstrap_witness: Option<NonEmptyVec<BootstrapWitness>>,
51
52    #[n(3)]
53    pub plutus_v1_script: Option<NonEmptyVec<PlutusScript<1>>>,
54
55    #[n(4)]
56    pub plutus_data: Option<NonEmptyVec<MemoizedPlutusData>>,
57
58    #[n(5)]
59    pub redeemer: Option<Redeemers>,
60
61    #[n(6)]
62    pub plutus_v2_script: Option<NonEmptyVec<PlutusScript<2>>>,
63
64    #[n(7)]
65    pub plutus_v3_script: Option<NonEmptyVec<PlutusScript<3>>>,
66}
67
68impl WitnessSet {
69    /// Collect provided scripts and compute each ScriptHash in a witness set
70    pub fn get_provided_scripts(&self) -> BTreeMap<Hash<SCRIPT>, ScriptKind> {
71        let mut provided_scripts = BTreeMap::new();
72
73        if let Some(native_scripts) = self.native_script.as_ref() {
74            provided_scripts
75                .extend(native_scripts.iter().map(|native_script| (native_script.script_hash(), ScriptKind::Native)))
76        };
77
78        collect_plutus_scripts(&mut provided_scripts, self.plutus_v1_script.as_ref(), ScriptKind::PlutusV1);
79
80        collect_plutus_scripts(&mut provided_scripts, self.plutus_v2_script.as_ref(), ScriptKind::PlutusV2);
81
82        collect_plutus_scripts(&mut provided_scripts, self.plutus_v3_script.as_ref(), ScriptKind::PlutusV3);
83
84        provided_scripts
85    }
86}
87
88// ----------------------------------------------------------------------------
89// Internals
90// ----------------------------------------------------------------------------
91
92/// A helper function, generic in the script VERSION, for collecting scripts from witnesses.
93fn collect_plutus_scripts<const VERSION: usize>(
94    accum: &mut BTreeMap<Hash<SCRIPT>, ScriptKind>,
95    scripts: Option<&NonEmptyVec<PlutusScript<VERSION>>>,
96    kind: ScriptKind,
97) {
98    if let Some(plutus_scripts) = scripts {
99        accum.extend(plutus_scripts.iter().map(|script| (script.script_hash(), kind)))
100    }
101}