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}