pallas_primitives/alonzo/
native_script.rs1use super::NativeScript;
5use crate::native_script::impl_native_script;
6
7impl_native_script!(NativeScript);
8
9#[cfg(test)]
10mod tests {
11 use super::*;
12 use pallas_codec::minicbor::{self, Decoder, Encoder};
13 use proptest::prelude::*;
14
15 fn scripts() -> impl Strategy<Value = NativeScript> {
16 prop_oneof![
17 any::<[u8; 28]>().prop_map(|x| NativeScript::ScriptPubkey(x.into())),
18 any::<u64>().prop_map(NativeScript::InvalidBefore),
19 any::<u64>().prop_map(NativeScript::InvalidHereafter),
20 ]
21 .prop_recursive(4, 64, 8, |inner| {
22 prop_oneof![
23 prop::collection::vec(inner.clone(), 0..5).prop_map(NativeScript::ScriptAll),
24 prop::collection::vec(inner.clone(), 0..5).prop_map(NativeScript::ScriptAny),
25 (
26 prop_oneof![
27 Just(i64::MIN),
28 Just(-1i64),
29 Just(0i64),
30 Just(i64::MAX),
31 any::<i64>(),
32 ],
33 prop::collection::vec(inner, 0..5),
34 )
35 .prop_map(|(n, xs)| NativeScript::ScriptNOfK(n, xs)),
36 ]
37 })
38 }
39
40 proptest! {
41 #[test]
42 fn round_trips_through_cbor(script in scripts()) {
43 let bytes = minicbor::to_vec(&script).unwrap();
44 let decoded: NativeScript = minicbor::decode(&bytes).unwrap();
45 prop_assert!(decoded == script);
46 prop_assert_eq!(minicbor::to_vec(&decoded).unwrap(), bytes);
47 prop_assert!(script.clone() == script);
48 }
49 }
50
51 #[test]
52 fn matches_ledger_cddl_wire_format() {
53 let key = [0xab; 28];
54 let key_hex = "ab".repeat(28);
55 let cases = [
56 (
57 NativeScript::ScriptPubkey(key.into()),
58 format!("8200581c{key_hex}"),
59 ),
60 (
61 NativeScript::ScriptAll(vec![
62 NativeScript::InvalidBefore(1),
63 NativeScript::InvalidHereafter(2),
64 ]),
65 "820182820401820502".to_string(),
66 ),
67 (NativeScript::ScriptAny(vec![]), "820280".to_string()),
68 (
69 NativeScript::ScriptNOfK(
70 2,
71 vec![
72 NativeScript::ScriptPubkey(key.into()),
73 NativeScript::ScriptAny(vec![NativeScript::InvalidBefore(0)]),
74 ],
75 ),
76 format!("830302828200581c{key_hex}820281820400"),
77 ),
78 (NativeScript::InvalidBefore(1000), "82041903e8".to_string()),
79 (
80 NativeScript::InvalidHereafter(1 << 32),
81 "82051b0000000100000000".to_string(),
82 ),
83 ];
84 for (script, hex) in cases {
85 let bytes = hex::decode(&hex).unwrap();
86 assert_eq!(minicbor::to_vec(&script).unwrap(), bytes, "{hex}");
87 let decoded: NativeScript = minicbor::decode(&bytes).unwrap();
88 assert!(decoded == script, "{hex}");
89 }
90 }
91
92 fn deep_cbor(depth: usize) -> Vec<u8> {
93 let mut bytes = Vec::new();
94 for i in 0..depth {
95 match i % 3 {
96 0 => bytes.extend_from_slice(&[0x82, 1, 0x81]),
97 1 => bytes.extend_from_slice(&[0x82, 2, 0x81]),
98 _ => bytes.extend_from_slice(&[0x83, 3, 1, 0x81]),
99 }
100 }
101 bytes.extend_from_slice(&[0x82, 4, 0]);
102 bytes
103 }
104
105 fn small_stack(f: impl FnOnce() + Send + 'static) {
106 std::thread::Builder::new()
107 .stack_size(128 * 1024)
108 .spawn(f)
109 .unwrap()
110 .join()
111 .unwrap();
112 }
113
114 #[test]
115 fn deep_script_lifecycle_on_small_stack() {
116 small_stack(|| {
117 let bytes = deep_cbor(20_000);
118 let script: NativeScript = minicbor::decode(&bytes).unwrap();
119 let cloned = script.clone();
120 assert!(script == cloned);
121 assert_eq!(minicbor::to_vec(&cloned).unwrap(), bytes);
122 let mut different_bytes = bytes.clone();
123 *different_bytes.last_mut().unwrap() = 1;
124 let different: NativeScript = minicbor::decode(&different_bytes).unwrap();
125 assert!(script != different);
126
127 let mut short_buffer = [0u8; 16];
128 assert!(
129 Encoder::new(short_buffer.as_mut_slice())
130 .encode(&script)
131 .is_err()
132 );
133 });
135 }
136
137 #[test]
138 fn malformed_deep_script_cleans_up_completed_children() {
139 small_stack(|| {
140 let mut bytes = vec![0x82, 1, 0x82];
143 bytes.extend(deep_cbor(20_000));
144 bytes.extend_from_slice(&[0x82, 0, 0x40]); assert!(minicbor::decode::<NativeScript>(&bytes).is_err());
146
147 let mut bytes = deep_cbor(20_000);
149 bytes.pop();
150 assert!(minicbor::decode::<NativeScript>(&bytes).is_err());
151 });
152 }
153
154 #[test]
155 fn preserves_permissive_array_decoding() {
156 use NativeScript::*;
157 let cases = [
158 ("820180", ScriptAll(vec![]), "820180"),
159 ("82029fff", ScriptAny(vec![]), "820280"),
160 ("83030080", ScriptNOfK(0, vec![]), "83030080"),
161 (
162 "82019f820400820501ff",
163 ScriptAll(vec![InvalidBefore(0), InvalidHereafter(1)]),
164 "820182820400820501",
165 ),
166 ("830400f6", InvalidBefore(0), "820400"),
167 (
168 "830181830501f68100",
169 ScriptAll(vec![InvalidHereafter(1)]),
170 "820181820501",
171 ),
172 ];
173 for (input, expected, canonical) in cases {
174 let bytes = hex::decode(input).unwrap();
175 let mut d = Decoder::new(&bytes);
176 let script: NativeScript = d.decode().unwrap();
177 assert_eq!(d.position(), bytes.len(), "{input}");
178 assert!(script == expected, "{input}");
179 assert_eq!(
180 minicbor::to_vec(&script).unwrap(),
181 hex::decode(canonical).unwrap(),
182 "{input}"
183 );
184 }
185 }
186
187 #[test]
188 fn n_of_k_threshold_is_signed() {
189 let bytes = hex::decode(
192 "830320828200581c3118644aa21ba172c82732ce80d1c94cdcb5f2e8891e1ad2645707188200581ce07caf4bf751495f75774ace30552441e4df84d141e5d1f5029cb04d",
193 )
194 .unwrap();
195 let script: NativeScript = minicbor::decode(&bytes).unwrap();
196 let NativeScript::ScriptNOfK(n, scripts) = &script else {
197 panic!("expected ScriptNOfK, got {script:?}");
198 };
199 assert_eq!(*n, -1);
200 assert_eq!(scripts.len(), 2);
201 assert_eq!(minicbor::to_vec(&script).unwrap(), bytes);
202 }
203
204 #[test]
205 fn malformed_arrays_are_rejected_without_reserving_claimed_length() {
206 for bytes in [
207 "80",
208 "8100",
209 "820301",
210 "9f0400ff", "820680",
212 "822080", "82019bffffffffffffffff", "82019f820400", "830181820400", ] {
217 let bytes = hex::decode(bytes).unwrap();
218 assert!(minicbor::decode::<NativeScript>(&bytes).is_err());
219 }
220 }
221
222 #[test]
223 fn equality_checks_variant_payload_order_and_arity() {
224 let a = NativeScript::InvalidBefore(1);
225 let b = NativeScript::InvalidHereafter(1);
226 assert!(a != b);
227 assert!(NativeScript::ScriptAll(vec![]) != NativeScript::ScriptAny(vec![]));
228 assert!(NativeScript::ScriptNOfK(1, vec![]) != NativeScript::ScriptNOfK(2, vec![]));
229 assert!(
230 NativeScript::ScriptAll(vec![a.clone(), b.clone()])
231 != NativeScript::ScriptAll(vec![b, a.clone()])
232 );
233 assert!(NativeScript::ScriptAll(vec![a]) != NativeScript::ScriptAll(vec![]));
234 assert!(
235 NativeScript::ScriptPubkey([0; 28].into())
236 != NativeScript::ScriptPubkey([1; 28].into())
237 );
238 }
239}