Skip to main content

amaru_kernel/cardano/
auxiliary_data.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 crate::{Hash, Hasher, KeyValuePairs, MemoizedNativeScript, Metadatum, NULL_HASH32, PlutusScript, cbor};
16
17#[derive(Debug, Clone, PartialEq, Eq, cbor::Encode, serde::Serialize, serde::Deserialize)]
18#[cbor(map)]
19pub struct AuxiliaryData {
20    #[cbor(skip)]
21    hash: Hash<{ AuxiliaryData::HASH_SIZE }>,
22
23    #[n(0)]
24    metadata: KeyValuePairs<u64, Metadatum>,
25
26    #[n(1)]
27    native_scripts: Vec<MemoizedNativeScript>,
28
29    #[n(2)]
30    plutus_v1_scripts: Vec<PlutusScript<1>>,
31
32    #[n(3)]
33    plutus_v2_scripts: Vec<PlutusScript<2>>,
34
35    #[n(4)]
36    plutus_v3_scripts: Vec<PlutusScript<3>>,
37}
38
39impl AuxiliaryData {
40    /// Hash digest size, in bytes.
41    pub const HASH_SIZE: usize = 32;
42
43    /// Obtain the blake2b-256 hash digest of the serialised AuxiliaryData.
44    pub fn hash(&self) -> Hash<{ Self::HASH_SIZE }> {
45        self.hash
46    }
47}
48
49impl Default for AuxiliaryData {
50    fn default() -> Self {
51        Self {
52            hash: NULL_HASH32,
53            metadata: KeyValuePairs::default(),
54            native_scripts: Vec::default(),
55            plutus_v1_scripts: Vec::default(),
56            plutus_v2_scripts: Vec::default(),
57            plutus_v3_scripts: Vec::default(),
58        }
59    }
60}
61
62// ```cddl
63// auxiliary_data = metadata / auxiliary_data_array / auxiliary_data_map
64//
65// metadata = {* metadatum_label => metadatum}
66//
67// metadatum_label = uint .size 8
68//
69// auxiliary_data_array =
70//   [ transaction_metadata : metadata
71//   , auxiliary_scripts : auxiliary_scripts
72//   ]
73//
74// auxiliary_scripts = [* native_script]
75//
76// auxiliary_data_map =
77//   #6.259(
78//     { ? 0 : metadata
79//     , ? 1 : [* native_script]
80//     , ? 2 : [* plutus_v1_script]
81//     , ? 3 : [* plutus_v2_script]
82//     , ? 4 : [* plutus_v3_script]
83//     }
84//
85//   )
86// ```
87impl<'b, C> cbor::Decode<'b, C> for AuxiliaryData {
88    // NOTE: AuxiliaryData post-Alonzo decoding
89    //
90    // Even when decoding post-Alonzo auxiliary data, the choice of decoder is determined
91    // dynamically based on the received format. Said differently, the Conway era decoding is
92    // backward-compatible, unlike many other data-types.
93    fn decode(d: &mut cbor::Decoder<'b>, ctx: &mut C) -> Result<Self, cbor::decode::Error> {
94        use cbor::data::Type::*;
95
96        let original_bytes = d.input();
97
98        let start_position = d.position();
99
100        #[allow(clippy::wildcard_enum_match_arm)]
101        let aux_data = match d.datatype()? {
102            Map | MapIndef => Self::decode_shelley(d, ctx),
103            Array | ArrayIndef => Self::decode_allegra(d, ctx),
104            Tag => Self::decode_alonzo(d, ctx),
105            any => Err(cbor::decode::Error::message(format!("unexpected type {any} when decoding auxiliary data"))),
106        }?;
107
108        let end_position = d.position();
109
110        Ok(Self { hash: Hasher::<256>::hash(&original_bytes[start_position..end_position]), ..aux_data })
111    }
112}
113
114// ----------------------------------------------------------------------------
115// Internals
116// ----------------------------------------------------------------------------
117
118impl AuxiliaryData {
119    /// Decode some auxiliary data using the Shelley-era codecs.
120    ///
121    /// /!\ Does not compute the underlying hash digest. This is a responsibility of the caller.
122    fn decode_shelley<'b, C>(d: &mut cbor::Decoder<'b>, ctx: &mut C) -> Result<Self, cbor::decode::Error> {
123        let metadata = d.decode_with(ctx)?;
124        Ok(Self { metadata, ..Self::default() })
125    }
126
127    /// Decode some auxiliary data using the Allegra-era codecs
128    ///
129    /// /!\ Does not compute the underlying hash digest. This is a responsibility of the caller.
130    fn decode_allegra<'b, C>(d: &mut cbor::Decoder<'b>, ctx: &mut C) -> Result<Self, cbor::decode::Error> {
131        cbor::heterogeneous_array(d, |d, assert_len| {
132            assert_len(2)?;
133            let metadata = d.decode_with(ctx)?;
134            let native_scripts = d.decode_with(ctx)?;
135            Ok(Self { metadata, native_scripts, ..Self::default() })
136        })
137    }
138
139    /// Decode some auxiliary data using the Alonzo-era codecs
140    ///
141    /// /!\ Does not compute the underlying hash digest. This is a responsibility of the caller.
142    fn decode_alonzo<'b, C>(d: &mut cbor::Decoder<'b>, ctx: &mut C) -> Result<Self, cbor::decode::Error> {
143        if d.tag()? != cbor::TAG_MAP_259 {
144            return Err(cbor::decode::Error::tag_mismatch(cbor::TAG_MAP_259));
145        }
146
147        let mut st = Self::default();
148
149        cbor::heterogeneous_map(
150            d,
151            &mut st,
152            |d| d.u64(),
153            |d, st, k| {
154                match k {
155                    0 => st.metadata = d.decode_with(ctx)?,
156                    1 => st.native_scripts = d.decode_with(ctx)?,
157                    2 => st.plutus_v1_scripts = d.decode_with(ctx)?,
158                    3 => st.plutus_v2_scripts = d.decode_with(ctx)?,
159                    4 => st.plutus_v3_scripts = d.decode_with(ctx)?,
160                    _ => {
161                        return Err(cbor::decode::Error::message(format!(
162                            "unexpected field key {k} in auxiliary data"
163                        ))
164                        .at(d.position()));
165                    }
166                };
167
168                Ok(())
169            },
170        )?;
171
172        Ok(st)
173    }
174}