1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
use alloc::vec::Vec;

use assembly::ast::AstSerdeOptions;
use miden_crypto::Felt;

use super::{Assembler, AssemblyContext, CodeBlock, Digest, NoteError, ProgramAst};
use crate::utils::serde::{
    ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
};

// CONSTANTS
// ================================================================================================

/// Default serialization options for script code AST.
const CODE_SERDE_OPTIONS: AstSerdeOptions = AstSerdeOptions::new(true);

// NOTE SCRIPT
// ================================================================================================

/// An executable program of a note.
///
/// A note's script represents a program which must be executed for a note to be consumed. As such
/// it defines the rules and side effects of consuming a given note.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NoteScript {
    hash: Digest,
    code: ProgramAst,
}

impl NoteScript {
    // CONSTRUCTORS
    // --------------------------------------------------------------------------------------------

    /// Returns a new [NoteScript] instantiated from the provided program and compiled with the
    /// provided assembler. The compiled code block is also returned.
    ///
    /// # Errors
    /// Returns an error if the compilation of the provided program fails.
    pub fn new(code: ProgramAst, assembler: &Assembler) -> Result<(Self, CodeBlock), NoteError> {
        let code_block = assembler
            .compile_in_context(&code, &mut AssemblyContext::for_program(Some(&code)))
            .map_err(NoteError::ScriptCompilationError)?;
        Ok((Self { hash: code_block.hash(), code }, code_block))
    }

    /// Returns a new [NoteScript] instantiated from the provided components.
    ///
    /// **Note**: this function assumes that the specified hash results from the compilation of the
    /// provided program, but this is not checked.
    pub fn from_parts(code: ProgramAst, hash: Digest) -> Self {
        Self { code, hash }
    }

    // PUBLIC ACCESSORS
    // --------------------------------------------------------------------------------------------

    /// Returns MAST root of this note script.
    pub fn hash(&self) -> Digest {
        self.hash
    }

    /// Returns the AST of this note script.
    pub fn code(&self) -> &ProgramAst {
        &self.code
    }
}

// CONVERSIONS INTO NOTE SCRIPT
// ================================================================================================

impl From<&NoteScript> for Vec<Felt> {
    fn from(value: &NoteScript) -> Self {
        let mut bytes = value.code.to_bytes(AstSerdeOptions { serialize_imports: true });
        let len = bytes.len();

        // Pad the data so that it can be encoded with u32
        let missing = if len % 4 > 0 { 4 - (len % 4) } else { 0 };
        bytes.resize(bytes.len() + missing, 0);

        let final_size = 5 + bytes.len();
        let mut result = Vec::with_capacity(final_size);

        // Push the length, this is used to remove the padding later
        result.extend(value.hash);
        result.push(Felt::new(len as u64));

        // A Felt can not represent all u64 values, so the data is encoded using u32.
        let mut encoded: &[u8] = &bytes;
        while encoded.len() >= 4 {
            let (data, rest) =
                encoded.split_first_chunk::<4>().expect("The length has been checked");
            let number = u32::from_le_bytes(*data);
            result.push(Felt::new(number.into()));

            encoded = rest;
        }

        result
    }
}

impl From<NoteScript> for Vec<Felt> {
    fn from(value: NoteScript) -> Self {
        (&value).into()
    }
}

// CONVERSIONS FROM NOTE SCRIPT
// ================================================================================================

impl TryFrom<&[Felt]> for NoteScript {
    type Error = DeserializationError;

    fn try_from(value: &[Felt]) -> Result<Self, Self::Error> {
        if value.len() < 5 {
            return Err(DeserializationError::UnexpectedEOF);
        }

        let hash = Digest::new([value[0], value[1], value[2], value[3]]);
        let len = value[4].as_int();
        let mut data = Vec::with_capacity(value.len() * 4);

        for felt in &value[5..] {
            let v = u32::try_from(felt.as_int())
                .map_err(|v| DeserializationError::InvalidValue(format!("{v}")))?;
            data.extend(v.to_le_bytes())
        }
        data.shrink_to(len as usize);

        // TODO: validate the hash matches the code
        let code = ProgramAst::from_bytes(&data)?;
        Ok(NoteScript::from_parts(code, hash))
    }
}

impl TryFrom<Vec<Felt>> for NoteScript {
    type Error = DeserializationError;

    fn try_from(value: Vec<Felt>) -> Result<Self, Self::Error> {
        value.as_slice().try_into()
    }
}

// SERIALIZATION
// ================================================================================================

impl Serializable for NoteScript {
    fn write_into<W: ByteWriter>(&self, target: &mut W) {
        self.hash.write_into(target);
        self.code.write_into(target, CODE_SERDE_OPTIONS);
    }
}

impl Deserializable for NoteScript {
    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
        let hash = Digest::read_from(source)?;
        let code = ProgramAst::read_from(source)?;

        Ok(Self::from_parts(code, hash))
    }
}