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))
}
}