1#![no_std]
14#![deny(warnings)]
15
16extern crate alloc;
17
18use alloc::vec::Vec;
19
20use miden_field::{Felt, Word};
21use miden_field_repr::{FeltReader, FeltReprError, FromFeltRepr, ToFeltRepr};
22
23const WORD_FELTS: usize = Word::NUM_ELEMENTS;
25
26#[derive(Clone, Debug, PartialEq, Eq)]
28pub enum EncodedScriptArgs {
29 Word(Word),
32 Preimage(Vec<Felt>),
37}
38
39#[derive(Debug, Clone, PartialEq, Eq)]
41#[non_exhaustive]
42pub enum ScriptArgsError {
43 Decode(FeltReprError),
45 NonZeroPadding,
47 TrailingData,
49 NonWordMultipleLength,
51 AdviceProviderUnavailable,
54}
55
56impl From<FeltReprError> for ScriptArgsError {
57 fn from(err: FeltReprError) -> Self {
58 Self::Decode(err)
59 }
60}
61
62impl core::fmt::Display for ScriptArgsError {
63 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
64 match self {
65 Self::Decode(err) => write!(f, "failed to decode tx script args: {err}"),
66 Self::NonZeroPadding => f.write_str("non-zero padding felt in tx script args"),
67 Self::TrailingData => f.write_str("trailing data after tx script args"),
68 Self::NonWordMultipleLength => {
69 f.write_str("tx script args advice value is not a whole number of words")
70 }
71 Self::AdviceProviderUnavailable => {
72 f.write_str("commitment-mode tx script args can only be decoded on the Miden VM")
73 }
74 }
75 }
76}
77
78pub type ScriptArgsResult<T> = core::result::Result<T, ScriptArgsError>;
80
81mod sealed {
82 pub trait Sealed {}
84 impl<T: super::FromFeltRepr + super::ToFeltRepr> Sealed for T {}
85}
86
87pub trait ScriptArgs: Sized + sealed::Sealed {
107 const FIXED_LEN: Option<usize>;
109
110 fn decode(arg: Word) -> ScriptArgsResult<Self>;
117
118 fn encode(&self) -> EncodedScriptArgs;
120}
121
122const fn is_word_mode(fixed_len: Option<usize>) -> bool {
124 match fixed_len {
125 Some(len) => len <= WORD_FELTS,
126 None => false,
127 }
128}
129
130#[inline(always)]
132fn check_zero_padding(reader: &mut FeltReader<'_>) -> ScriptArgsResult<()> {
133 while reader.remaining() > 0 {
134 if reader.read()? != Felt::ZERO {
135 return Err(ScriptArgsError::NonZeroPadding);
136 }
137 }
138 Ok(())
139}
140
141#[cfg(all(target_family = "wasm", miden, feature = "miden-vm-guest"))]
146#[inline(always)]
147fn decode_commitment<T: FromFeltRepr>(arg: Word) -> ScriptArgsResult<T> {
148 use miden_stdlib_sys::{adv_load_preimage, intrinsics::advice::adv_push_mapvaln};
149
150 let num_felts = adv_push_mapvaln(arg).as_canonical_u64();
151 if num_felts % WORD_FELTS as u64 != 0 {
152 return Err(ScriptArgsError::NonWordMultipleLength);
153 }
154 let num_words = Felt::new(num_felts / WORD_FELTS as u64).unwrap();
155 let preimage = adv_load_preimage(num_words, arg);
156 decode_preimage(&preimage)
157}
158
159#[cfg(not(all(target_family = "wasm", miden, feature = "miden-vm-guest")))]
161fn decode_commitment<T: FromFeltRepr>(_arg: Word) -> ScriptArgsResult<T> {
162 Err(ScriptArgsError::AdviceProviderUnavailable)
163}
164
165#[inline(always)]
171pub fn decode_preimage<T: FromFeltRepr>(preimage: &[Felt]) -> ScriptArgsResult<T> {
172 if !preimage.len().is_multiple_of(WORD_FELTS) {
175 return Err(ScriptArgsError::NonWordMultipleLength);
176 }
177 let mut reader = FeltReader::new(preimage);
178 let value = T::from_felt_repr(&mut reader)?;
179 check_decoded_len::<T>(&reader)?;
180 if reader.remaining() >= WORD_FELTS {
182 return Err(ScriptArgsError::TrailingData);
183 }
184 check_zero_padding(&mut reader)?;
185 Ok(value)
186}
187
188#[inline(always)]
192fn check_decoded_len<T: FromFeltRepr>(reader: &FeltReader<'_>) -> ScriptArgsResult<()> {
193 if let Some(fixed_len) = T::FIXED_LEN {
194 assert!(reader.pos() == fixed_len, "decoded length must match FIXED_LEN");
195 }
196 Ok(())
197}
198
199impl<T: FromFeltRepr + ToFeltRepr> ScriptArgs for T {
200 const FIXED_LEN: Option<usize> = <T as FromFeltRepr>::FIXED_LEN;
201
202 #[inline(always)]
205 fn decode(arg: Word) -> ScriptArgsResult<Self> {
206 if const { is_word_mode(Self::FIXED_LEN) } {
208 let felts = [arg[0], arg[1], arg[2], arg[3]];
209 let mut reader = FeltReader::new(&felts);
210 let value = Self::from_felt_repr(&mut reader)?;
211 check_decoded_len::<Self>(&reader)?;
212 check_zero_padding(&mut reader)?;
213 Ok(value)
214 } else {
215 decode_commitment(arg)
216 }
217 }
218
219 fn encode(&self) -> EncodedScriptArgs {
220 let mut felts = self.to_felt_repr();
221 if let Some(fixed_len) = Self::FIXED_LEN {
224 assert!(felts.len() == fixed_len, "encoding length must match FIXED_LEN");
225 }
226 if const { is_word_mode(Self::FIXED_LEN) } {
227 felts.resize(WORD_FELTS, Felt::ZERO);
228 EncodedScriptArgs::Word(Word::new([felts[0], felts[1], felts[2], felts[3]]))
229 } else {
230 felts.resize(felts.len().next_multiple_of(WORD_FELTS), Felt::ZERO);
232 EncodedScriptArgs::Preimage(felts)
233 }
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use alloc::vec;
240
241 use super::*;
242
243 fn felt(value: u64) -> Felt {
244 Felt::new(value).unwrap()
245 }
246
247 #[test]
249 fn word_mode_encode_pads_with_zeros() {
250 let EncodedScriptArgs::Word(word) = felt(7).encode() else {
251 panic!("expected word mode for a single felt");
252 };
253
254 assert_eq!(word, Word::new([felt(7), felt(0), felt(0), felt(0)]));
255 }
256
257 #[test]
259 fn word_mode_roundtrip() {
260 let value = felt(7);
261 let EncodedScriptArgs::Word(word) = value.encode() else {
262 panic!("expected word mode for a single felt");
263 };
264
265 assert_eq!(<Felt as ScriptArgs>::decode(word), Ok(value));
266 }
267
268 #[test]
270 fn word_args_are_transported_verbatim() {
271 let word = Word::new([felt(1), felt(2), felt(3), felt(4)]);
272 let EncodedScriptArgs::Word(encoded) = word.encode() else {
273 panic!("expected word mode for a word");
274 };
275
276 assert_eq!(encoded, word);
277 assert_eq!(<Word as ScriptArgs>::decode(encoded), Ok(word));
278 }
279
280 #[test]
282 fn word_mode_decode_rejects_nonzero_padding() {
283 let word = Word::new([felt(7), felt(0), felt(0), felt(1)]);
284
285 assert_eq!(<Felt as ScriptArgs>::decode(word), Err(ScriptArgsError::NonZeroPadding));
286 }
287
288 #[test]
290 fn word_mode_decode_surfaces_felt_repr_errors() {
291 let word = Word::new([felt(2), felt(0), felt(0), felt(0)]);
292
293 assert_eq!(
294 <bool as ScriptArgs>::decode(word),
295 Err(ScriptArgsError::Decode(FeltReprError::InvalidBool {
296 pos: 0,
297 len: 4,
298 value: 2
299 }))
300 );
301 }
302
303 #[test]
305 fn commitment_mode_encode_pads_to_word_multiple() {
306 let values = vec![felt(5), felt(6)];
307 let EncodedScriptArgs::Preimage(felts) = values.encode() else {
308 panic!("expected commitment mode for a variable-length encoding");
309 };
310
311 assert_eq!(felts, vec![felt(2), felt(5), felt(6), felt(0)]);
313 }
314
315 struct LyingFixedLen;
317
318 impl FromFeltRepr for LyingFixedLen {
319 const FIXED_LEN: Option<usize> = Some(1);
320
321 fn from_felt_repr(reader: &mut FeltReader<'_>) -> miden_field_repr::FeltReprResult<Self> {
322 reader.read()?;
323 reader.read()?;
324 Ok(Self)
325 }
326 }
327
328 impl ToFeltRepr for LyingFixedLen {
329 fn write_felt_repr(&self, writer: &mut miden_field_repr::FeltWriter<'_>) {
330 writer.write(felt(1));
331 writer.write(felt(2));
332 }
333 }
334
335 #[test]
337 #[should_panic(expected = "must match FIXED_LEN")]
338 fn encode_rejects_wrong_manual_fixed_len() {
339 let _ = LyingFixedLen.encode();
340 }
341
342 #[test]
345 #[should_panic(expected = "decoded length must match FIXED_LEN")]
346 fn decode_rejects_wrong_manual_fixed_len() {
347 struct LyingDecoder;
349
350 impl FromFeltRepr for LyingDecoder {
351 const FIXED_LEN: Option<usize> = Some(2);
352
353 fn from_felt_repr(
354 reader: &mut FeltReader<'_>,
355 ) -> miden_field_repr::FeltReprResult<Self> {
356 reader.read()?;
357 Ok(Self)
358 }
359 }
360
361 impl ToFeltRepr for LyingDecoder {
362 fn write_felt_repr(&self, writer: &mut miden_field_repr::FeltWriter<'_>) {
363 writer.write(felt(1));
364 writer.write(felt(2));
365 }
366 }
367
368 let _ = LyingDecoder::decode(Word::new([felt(1), felt(2), felt(0), felt(0)]));
369 }
370
371 #[test]
373 fn commitment_mode_decode_reports_missing_advice_provider() {
374 let word = Word::new([felt(1), felt(2), felt(3), felt(4)]);
375
376 assert_eq!(
377 <Vec<Felt> as ScriptArgs>::decode(word),
378 Err(ScriptArgsError::AdviceProviderUnavailable)
379 );
380 }
381
382 #[test]
384 fn decode_preimage_accepts_canonical_padding() {
385 let decoded: ScriptArgsResult<Vec<Felt>> =
386 decode_preimage(&[felt(2), felt(5), felt(6), felt(0)]);
387
388 assert_eq!(decoded, Ok(vec![felt(5), felt(6)]));
389 }
390
391 #[test]
393 fn decode_preimage_rejects_nonzero_padding() {
394 let decoded: ScriptArgsResult<Vec<Felt>> =
395 decode_preimage(&[felt(2), felt(5), felt(6), felt(9)]);
396
397 assert_eq!(decoded, Err(ScriptArgsError::NonZeroPadding));
398 }
399
400 #[test]
403 fn decode_preimage_rejects_non_word_multiple_length() {
404 let decoded: ScriptArgsResult<Vec<Felt>> = decode_preimage(&[felt(2), felt(5), felt(6)]);
405
406 assert_eq!(decoded, Err(ScriptArgsError::NonWordMultipleLength));
407 }
408
409 #[test]
411 fn decode_preimage_rejects_extra_word() {
412 let decoded: ScriptArgsResult<Vec<Felt>> = decode_preimage(&[
413 felt(2),
414 felt(5),
415 felt(6),
416 felt(0),
417 felt(0),
418 felt(0),
419 felt(0),
420 felt(0),
421 ]);
422
423 assert_eq!(decoded, Err(ScriptArgsError::TrailingData));
424 }
425}