From ec19583977d2d8e0eb488b4485a8c8c5155351be Mon Sep 17 00:00:00 2001 From: Alexander Guryanov Date: Mon, 9 Feb 2026 15:16:03 +0300 Subject: [PATCH 01/15] Replace CreateProgram.bytecode with CreateProgram.program_module --- build.rs | 5 ++++- proto | 2 +- src/main.rs | 4 ++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/build.rs b/build.rs index 75f14dd..ee14a5e 100644 --- a/build.rs +++ b/build.rs @@ -1,4 +1,7 @@ fn main() -> Result<(), Box> { - tonic_prost_build::compile_protos("proto/libernet.proto")?; + tonic_prost_build::configure().compile_protos( + &["proto/libernet.proto", "proto/libernet_wasm.proto"], + &["proto"], + )?; Ok(()) } diff --git a/proto b/proto index 173baaa..43e0689 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 173baaa7876335272a0305159563ffe48d84b665 +Subproject commit 43e0689069e33148b60ac1ed391d9577c4c35071 diff --git a/src/main.rs b/src/main.rs index 90d31a4..edaaa4a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,6 +35,10 @@ mod testing; pub mod libernet { tonic::include_proto!("libernet"); + + pub mod wasm { + tonic::include_proto!("libernet.wasm"); + } } #[derive(Parser, Debug)] From f99d33df6fa9bebf59c9afd1613d8d8b06f432f5 Mon Sep 17 00:00:00 2001 From: Alexander Guryanov Date: Mon, 9 Feb 2026 16:28:59 +0300 Subject: [PATCH 02/15] Create struct Program + EncodeToAny + DecodeFromAny --- CODEOWNERS | 1 + src/data.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ src/proto.rs | 1 + 3 files changed, 45 insertions(+) diff --git a/CODEOWNERS b/CODEOWNERS index bb82316..260b507 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1,2 @@ * @71104 +* @caiiiycuk diff --git a/src/data.rs b/src/data.rs index 7385196..99488e8 100644 --- a/src/data.rs +++ b/src/data.rs @@ -253,6 +253,24 @@ impl AccountProof { } } +#[derive(Debug, Default, PartialEq)] +pub struct Program { + module: libernet::wasm::ProgramModule, +} + +impl proto::EncodeToAny for Program { + fn encode_to_any(&self) -> Result { + Ok(prost_types::Any::from_msg(&self.module)?) + } +} + +impl proto::DecodeFromAny for Program { + fn decode_from_any(proto: &prost_types::Any) -> Result { + let proto = proto.to_msg::()?; + Ok(Self { module: proto }) + } +} + #[derive(Debug, Clone, PartialEq)] pub struct Transaction { payload: prost_types::Any, @@ -873,4 +891,29 @@ mod tests { assert_eq!(storage.get_u8(0x1234567eu32), 0); assert_eq!(storage.get_u8(0x1234567fu32), 0); } + + #[test] + fn test_encode_decode_empty_program() { + let program = Program { + module: libernet::wasm::ProgramModule { + protocol_version: Some(1), + version: Some(libernet::wasm::Version { + num: Some(1), + encoding: Some(libernet::wasm::Encoding::Module as i32), + }), + sections: vec![], + }, + }; + let proto = program.encode_to_any().unwrap(); + let decoded = Program::decode_from_any(&proto).unwrap(); + assert_eq!(program, decoded); + let proto_module = proto.to_msg::().unwrap(); + assert_eq!(proto_module.protocol_version, Some(1)); + assert_eq!(proto_module.version.as_ref().unwrap().num, Some(1)); + assert_eq!( + proto_module.version.as_ref().unwrap().encoding, + Some(libernet::wasm::Encoding::Module as i32) + ); + assert_eq!(proto_module.sections.len(), 0); + } } diff --git a/src/proto.rs b/src/proto.rs index ab1e607..241944c 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -38,6 +38,7 @@ liber_proto_name!(libernet::Scalar, "Scalar"); liber_proto_name!(libernet::Transaction, "Transaction"); liber_proto_name!(libernet::node_identity::Payload, "NodeIdentity.Payload"); liber_proto_name!(libernet::transaction::Payload, "Transaction.Payload"); +liber_proto_name!(libernet::wasm::ProgramModule, "wasm.ProgramModule"); /// Makes a type encodable to `google.protobuf.Any`. pub trait EncodeToAny: Sized { From 915897d72e3f1cd7a284ec7e0c2d5c50375cfcff Mon Sep 17 00:00:00 2001 From: Alexander Guryanov Date: Tue, 17 Feb 2026 14:22:51 +0300 Subject: [PATCH 03/15] Update proto; remove PartialEq requirment --- proto | 2 +- src/data.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/proto b/proto index 43e0689..310a23e 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 43e0689069e33148b60ac1ed391d9577c4c35071 +Subproject commit 310a23ed4fdff91921dca047e237e384566459e6 diff --git a/src/data.rs b/src/data.rs index 99488e8..5da547f 100644 --- a/src/data.rs +++ b/src/data.rs @@ -253,7 +253,8 @@ impl AccountProof { } } -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, Default)] +#[cfg_attr(test, derive(PartialEq))] pub struct Program { module: libernet::wasm::ProgramModule, } @@ -898,10 +899,10 @@ mod tests { module: libernet::wasm::ProgramModule { protocol_version: Some(1), version: Some(libernet::wasm::Version { - num: Some(1), + number: Some(1), encoding: Some(libernet::wasm::Encoding::Module as i32), }), - sections: vec![], + ..Default::default() }, }; let proto = program.encode_to_any().unwrap(); @@ -909,11 +910,10 @@ mod tests { assert_eq!(program, decoded); let proto_module = proto.to_msg::().unwrap(); assert_eq!(proto_module.protocol_version, Some(1)); - assert_eq!(proto_module.version.as_ref().unwrap().num, Some(1)); + assert_eq!(proto_module.version.as_ref().unwrap().number, Some(1)); assert_eq!( proto_module.version.as_ref().unwrap().encoding, Some(libernet::wasm::Encoding::Module as i32) ); - assert_eq!(proto_module.sections.len(), 0); } } From 174cbc2f07b6b7ce56bc2b4ee247cc42c0daa50a Mon Sep 17 00:00:00 2001 From: Alexander Guryanov Date: Tue, 17 Feb 2026 14:52:23 +0300 Subject: [PATCH 04/15] Turn module: programModule into Option<> --- src/data.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/data.rs b/src/data.rs index 5da547f..9b86748 100644 --- a/src/data.rs +++ b/src/data.rs @@ -256,19 +256,19 @@ impl AccountProof { #[derive(Debug, Default)] #[cfg_attr(test, derive(PartialEq))] pub struct Program { - module: libernet::wasm::ProgramModule, + module: Option, } impl proto::EncodeToAny for Program { fn encode_to_any(&self) -> Result { - Ok(prost_types::Any::from_msg(&self.module)?) + Ok(prost_types::Any::from_msg(self.module.as_ref().context("missing program module")?)?) } } impl proto::DecodeFromAny for Program { fn decode_from_any(proto: &prost_types::Any) -> Result { let proto = proto.to_msg::()?; - Ok(Self { module: proto }) + Ok(Self { module: Some(proto) }) } } @@ -896,14 +896,14 @@ mod tests { #[test] fn test_encode_decode_empty_program() { let program = Program { - module: libernet::wasm::ProgramModule { + module: Some(libernet::wasm::ProgramModule { protocol_version: Some(1), version: Some(libernet::wasm::Version { number: Some(1), encoding: Some(libernet::wasm::Encoding::Module as i32), }), ..Default::default() - }, + }), }; let proto = program.encode_to_any().unwrap(); let decoded = Program::decode_from_any(&proto).unwrap(); From aac32e01d455c84a4523618a1714f4ec552186e0 Mon Sep 17 00:00:00 2001 From: Alexander Guryanov Date: Thu, 19 Feb 2026 17:12:15 +0300 Subject: [PATCH 05/15] Add AsScalar for wasm operators --- src/data.rs | 8 +- src/main.rs | 1 + src/program.rs | 440 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 447 insertions(+), 2 deletions(-) create mode 100644 src/program.rs diff --git a/src/data.rs b/src/data.rs index 9b86748..a5a625d 100644 --- a/src/data.rs +++ b/src/data.rs @@ -261,14 +261,18 @@ pub struct Program { impl proto::EncodeToAny for Program { fn encode_to_any(&self) -> Result { - Ok(prost_types::Any::from_msg(self.module.as_ref().context("missing program module")?)?) + Ok(prost_types::Any::from_msg( + self.module.as_ref().context("missing program module")?, + )?) } } impl proto::DecodeFromAny for Program { fn decode_from_any(proto: &prost_types::Any) -> Result { let proto = proto.to_msg::()?; - Ok(Self { module: Some(proto) }) + Ok(Self { + module: Some(proto), + }) } } diff --git a/src/main.rs b/src/main.rs index edaaa4a..59e94f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,6 +21,7 @@ mod constants; mod data; mod db; mod net; +mod program; mod proto; mod service; mod ssl; diff --git a/src/program.rs b/src/program.rs new file mode 100644 index 0000000..2fa2763 --- /dev/null +++ b/src/program.rs @@ -0,0 +1,440 @@ +use crate::libernet::wasm::{self, CatchElement, PlainType, RefType}; +use crate::libernet::wasm::{OpCode, Operator, operator::Operator::*}; +use anyhow::{Result, anyhow, bail}; +use blstrs::Scalar; +use crypto::{merkle::AsScalar, poseidon::hash_t4}; + +#[macro_export] +macro_rules! opmatch { + ($expr:expr, $pat:pat => $body:block, $msg:expr $(,)?) => {{ + match $expr { + Some($pat) => $body, + _ => anyhow::bail!($msg), + } + }}; +} + +trait ToScalar { + fn to_scalar(&self) -> Result; +} + +impl ToScalar for u32 { + fn to_scalar(&self) -> Result { + Ok(self.as_scalar()) + } +} + +impl ToScalar for Vec +where + T: ToScalar, +{ + fn to_scalar(&self) -> Result { + let mut scalar = (self.len() as u64).as_scalar(); + for elem in self { + scalar = hash_t4(&[scalar, elem.to_scalar()?]); + } + Ok(scalar) + } +} + +impl ToScalar for wasm::ValueType { + fn to_scalar(&self) -> Result { + let value_code = self + .value_type + .ok_or_else(|| anyhow!("Value type is required"))?; + let plain_type = PlainType::try_from(value_code)?; + let body = match plain_type { + PlainType::ValueTypeI32 + | PlainType::ValueTypeI64 + | PlainType::ValueTypeF32 + | PlainType::ValueTypeF64 + | PlainType::ValueTypeV128 => { + if self.reference_type.is_some() { + bail!("Reference type is set for primitive value type"); + } + 0.into() + } + PlainType::ValueTypeRef => { + let ref_code = self + .reference_type + .ok_or_else(|| anyhow!("Reference type is required"))?; + if RefType::try_from(ref_code).is_err() { + bail!("Invalid reference type"); + } + ref_code.as_scalar() + } + }; + Ok(hash_t4(&[value_code.as_scalar(), body])) + } +} + +impl ToScalar for wasm::block_type::BlockType { + fn to_scalar(&self) -> Result { + Ok(match self { + wasm::block_type::BlockType::Empty(_) => -1.as_scalar(), + wasm::block_type::BlockType::ValueType(vt) => vt.to_scalar()?, + wasm::block_type::BlockType::FunctionType(_) => todo!(), + }) + } +} + +impl ToScalar for CatchElement { + fn to_scalar(&self) -> Result { + let catch_element = self + .catch_element + .ok_or(anyhow!("Catch element is required"))?; + Ok(match catch_element { + wasm::catch_element::CatchElement::One(one) => hash_t4(&[ + one.tag.ok_or(anyhow!("One: Tag is required"))?.as_scalar(), + one.label + .ok_or(anyhow!("One: Label is required"))? + .as_scalar(), + ]), + wasm::catch_element::CatchElement::OneRef(one_ref) => hash_t4(&[ + one_ref + .tag + .ok_or(anyhow!("OneRef: Tag is required"))? + .as_scalar(), + one_ref + .label + .ok_or(anyhow!("OneRef: Label is required"))? + .as_scalar(), + ]), + wasm::catch_element::CatchElement::All(all) => hash_t4(&[all + .label + .ok_or(anyhow!("All: Label is required"))? + .as_scalar()]), + wasm::catch_element::CatchElement::AllRef(all_ref) => hash_t4(&[all_ref + .label + .ok_or(anyhow!("AllRef: Label is required"))? + .as_scalar()]), + }) + } +} + +impl ToScalar for Operator { + fn to_scalar(&self) -> Result { + let opcode_value = self.opcode.ok_or_else(|| anyhow!("Opcode is required"))?; + let opcode = OpCode::try_from(opcode_value)?; + let operator = &self.operator; + + let body: Scalar = match opcode { + OpCode::Unreachable + | OpCode::Nop + | OpCode::Else + | OpCode::End + | OpCode::Return + | OpCode::Drop + | OpCode::Select + | OpCode::I32Eqz + | OpCode::I32Eq + | OpCode::I32Ne + | OpCode::I32LtSigned + | OpCode::I32LtUnsigned + | OpCode::I32GtSigned + | OpCode::I32GtUnsigned + | OpCode::I32LeSigned + | OpCode::I32LeUnsigned + | OpCode::I32GeSigned + | OpCode::I32GeUnsigned + | OpCode::I64Eqz + | OpCode::I64Eq + | OpCode::I64Ne + | OpCode::I64LtSigned + | OpCode::I64LtUnsigned + | OpCode::I64GtSigned + | OpCode::I64GtUnsigned + | OpCode::I64LeSigned + | OpCode::I64LeUnsigned + | OpCode::I64GeSigned + | OpCode::I64GeUnsigned + | OpCode::F32Eq + | OpCode::F32Ne + | OpCode::F32Lt + | OpCode::F32Gt + | OpCode::F32Le + | OpCode::F32Ge + | OpCode::F64Eq + | OpCode::F64Ne + | OpCode::F64Lt + | OpCode::F64Gt + | OpCode::F64Le + | OpCode::F64Ge + | OpCode::I32Clz + | OpCode::I32Ctz + | OpCode::I32Popcnt + | OpCode::I32Add + | OpCode::I32Sub + | OpCode::I32Mul + | OpCode::I32DivSigned + | OpCode::I32DivUnsigned + | OpCode::I32RemSigned + | OpCode::I32RemUnsigned + | OpCode::I32And + | OpCode::I32Or + | OpCode::I32Xor + | OpCode::I32Shl + | OpCode::I32ShrSigned + | OpCode::I32ShrUnsigned + | OpCode::I32Rotl + | OpCode::I32Rotr + | OpCode::I64Clz + | OpCode::I64Ctz + | OpCode::I64Popcnt + | OpCode::I64Add + | OpCode::I64Sub + | OpCode::I64Mul + | OpCode::I64DivSigned + | OpCode::I64DivUnsigned + | OpCode::I64RemSigned + | OpCode::I64RemUnsigned + | OpCode::I64And + | OpCode::I64Or + | OpCode::I64Xor + | OpCode::I64Shl + | OpCode::I64ShrSigned + | OpCode::I64ShrUnsigned + | OpCode::I64Rotl + | OpCode::I64Rotr + | OpCode::F32Abs + | OpCode::F32Neg + | OpCode::F32Ceil + | OpCode::F32Floor + | OpCode::F32Trunc + | OpCode::F32Nearest + | OpCode::F32Sqrt + | OpCode::F32Add + | OpCode::F32Sub + | OpCode::F32Mul + | OpCode::F32Div + | OpCode::F32Min + | OpCode::F32Max + | OpCode::F32Copysign + | OpCode::F64Abs + | OpCode::F64Neg + | OpCode::F64Ceil + | OpCode::F64Floor + | OpCode::F64Trunc + | OpCode::F64Nearest + | OpCode::F64Sqrt + | OpCode::F64Add + | OpCode::F64Sub + | OpCode::F64Mul + | OpCode::F64Div + | OpCode::F64Min + | OpCode::F64Max + | OpCode::F64Copysign + | OpCode::I32WrapI64 + | OpCode::I32TruncF32Signed + | OpCode::I32TruncF32Unsigned + | OpCode::I32TruncF64Signed + | OpCode::I32TruncF64Unsigned + | OpCode::I64ExtendI32Signed + | OpCode::I64ExtendI32Unsigned + | OpCode::I64TruncF32Signed + | OpCode::I64TruncF32Unsigned + | OpCode::I64TruncF64Signed + | OpCode::I64TruncF64Unsigned + | OpCode::F32ConvertI32Signed + | OpCode::F32ConvertI32Unsigned + | OpCode::F32ConvertI64Signed + | OpCode::F32ConvertI64Unsigned + | OpCode::F32DemoteF64 + | OpCode::F64ConvertI32Signed + | OpCode::F64ConvertI32Unsigned + | OpCode::F64ConvertI64Signed + | OpCode::F64ConvertI64Unsigned + | OpCode::F64PromoteF32 + | OpCode::I32ReinterpretF32 + | OpCode::I64ReinterpretF64 + | OpCode::F32ReinterpretI32 + | OpCode::F64ReinterpretI64 + | OpCode::SignExtI32Extend8Signed + | OpCode::SignExtI32Extend16Signed + | OpCode::SignExtI64Extend8Signed + | OpCode::SignExtI64Extend16Signed + | OpCode::SignExtI64Extend32Signed + | OpCode::SaturatingFloatToIntExtI32TruncSatF32Signed + | OpCode::SaturatingFloatToIntExtI32TruncSatF32Unsigned + | OpCode::SaturatingFloatToIntExtI32TruncSatF64Signed + | OpCode::SaturatingFloatToIntExtI32TruncSatF64Unsigned + | OpCode::SaturatingFloatToIntExtI64TruncSatF32Signed + | OpCode::SaturatingFloatToIntExtI64TruncSatF32Unsigned + | OpCode::SaturatingFloatToIntExtI64TruncSatF64Signed + | OpCode::SaturatingFloatToIntExtI64TruncSatF64Unsigned + | OpCode::ExceptionsExtThrowRef + | OpCode::LegacyExceptionsExtCatchAll => { + if operator.is_some() { + bail!("Operator is not allowed for this opcode"); + } + 0.into() + } + OpCode::Block | OpCode::Loop | OpCode::If | OpCode::LegacyExceptionsExtTry => { + opmatch!(operator, BlockType(block_type) => { + match block_type.block_type { + Some(block_type) => block_type.to_scalar()?, + _ => bail!("Block type is required"), + } + }, "Block type is required") + } + OpCode::Br + | OpCode::BrIf + | OpCode::LegacyExceptionsExtRethrow + | OpCode::LegacyExceptionsExtDelegate => { + opmatch!(operator, RelativeDepth(relative_depth) => { + relative_depth.as_scalar() + }, "Relative depth is required") + } + OpCode::BrTable => { + opmatch!(operator, Targets(targets) => { + let default = targets.default.ok_or_else(|| anyhow!("Default target is required"))?; + hash_t4(&[default.as_scalar(), targets.targets.to_scalar()?]) + }, "Type index is required") + } + OpCode::Call => { + opmatch!(operator, FunctionIndex(function_index) => { + function_index.as_scalar() + }, "Function index is required") + } + OpCode::CallIndirect => { + opmatch!(operator, CallIndirect(call_indirect) => { + let type_index = call_indirect.type_index.ok_or_else(|| anyhow!("Type index is required"))?; + let table_index = call_indirect.table_index.ok_or_else(|| anyhow!("Table index is required"))?; + hash_t4(&[type_index.as_scalar(), table_index.as_scalar()]) + }, "Type index and table index are required") + } + OpCode::LocalGet | OpCode::LocalSet | OpCode::LocalTee => { + opmatch!(operator, LocalIndex(local_index) => { + local_index.as_scalar() + }, "Local index is required") + } + OpCode::GlobalGet | OpCode::GlobalSet => { + opmatch!(operator, GlobalIndex(global_index) => { + global_index.as_scalar() + }, "Global index is required") + } + OpCode::I32Load + | OpCode::I64Load + | OpCode::F32Load + | OpCode::F64Load + | OpCode::I32Load8Signed + | OpCode::I32Load8Unsigned + | OpCode::I32Load16Signed + | OpCode::I32Load16Unsigned + | OpCode::I64Load8Signed + | OpCode::I64Load8Unsigned + | OpCode::I64Load16Signed + | OpCode::I64Load16Unsigned + | OpCode::I64Load32Signed + | OpCode::I64Load32Unsigned + | OpCode::I32Store + | OpCode::I64Store + | OpCode::F32Store + | OpCode::F64Store + | OpCode::I32Store8 + | OpCode::I32Store16 + | OpCode::I64Store8 + | OpCode::I64Store16 + | OpCode::I64Store32 => { + opmatch!(operator, Memarg(memarg) => { + let align = memarg.align.ok_or_else(|| anyhow!("Align is required"))?; + let max_align = memarg.max_align.ok_or_else(|| anyhow!("Max align is required"))?; + let offset = memarg.offset.ok_or_else(|| anyhow!("Offset is required"))?; + let memory = memarg.memory.ok_or_else(|| anyhow!("Memory is required"))?; + hash_t4(&[align.as_scalar(), max_align.as_scalar(), offset.as_scalar(), memory.as_scalar()]) + }, "Mem arg is required") + } + OpCode::MemorySize | OpCode::MemoryGrow => { + opmatch!(operator, Mem(mem) => { + mem.as_scalar() + }, "Mem is required") + } + OpCode::I32Constant => { + opmatch!(operator, I32Value(i32_value) => { + i32_value.as_scalar() + }, "I32 value is required") + } + OpCode::I64Constant => { + opmatch!(operator, I64Value(i64_value) => { + i64_value.as_scalar() + }, "I64 value is required") + } + OpCode::F32Constant => { + opmatch!(operator, F32Value(f32_value) => { + f32_value.as_scalar() + }, "F32 value is required") + } + OpCode::F64Constant => { + opmatch!(operator, F64Value(f64_value) => { + f64_value.as_scalar() + }, "F64 value is required") + } + OpCode::BulkMemoryExtMemoryInit => { + opmatch!(operator, MemoryInit(memory_init) => { + let data_index = memory_init.data_index.ok_or_else(|| anyhow!("Data index is required"))?; + let address = memory_init.address.ok_or_else(|| anyhow!("Address is required"))?; + hash_t4(&[data_index.as_scalar(), address.as_scalar()]) + }, "Data index and address are required") + } + OpCode::BulkMemoryExtDataDrop => { + opmatch!(operator, DataIndex(data_index) => { + data_index.as_scalar() + }, "Data index is required") + } + OpCode::BulkMemoryExtMemoryCopy => { + opmatch!(operator, MemoryCopy(memory_copy) => { + let destination_address = memory_copy.destination_address.ok_or_else(|| anyhow!("Destination address is required"))?; + let source_address = memory_copy.source_address.ok_or_else(|| anyhow!("Source address is required"))?; + hash_t4(&[destination_address.as_scalar(), source_address.as_scalar()]) + }, "Destination address and source address are required") + } + OpCode::BulkMemoryExtMemoryFill => { + opmatch!(operator, Mem(mem) => { + mem.as_scalar() + }, "Mem is required") + } + OpCode::BulkMemoryExtTableInit => { + opmatch!(operator, TableInit(table_init) => { + let element_index = table_init.element_index.ok_or_else(|| anyhow!("Element index is required"))?; + let table = table_init.table.ok_or_else(|| anyhow!("Table is required"))?; + hash_t4(&[element_index.as_scalar(), table.as_scalar()]) + }, "Table index is required") + } + OpCode::BulkMemoryExtElemDrop => { + opmatch!(operator, ElementIndex(element_index) => { + element_index.as_scalar() + }, "Element index is required") + } + OpCode::BulkMemoryExtTableCopy => { + opmatch!(operator, TableCopy(table_copy) => { + let dst_table = table_copy.dst_table.ok_or_else(|| anyhow!("Dst table is required"))?; + let src_table = table_copy.src_table.ok_or_else(|| anyhow!("Src table is required"))?; + hash_t4(&[dst_table.as_scalar(), src_table.as_scalar()]) + }, "Dst table and src table are required") + } + OpCode::ExceptionsExtTryTable => { + opmatch!(operator, TryTable(try_table) => { + let block_type = try_table.r#type.ok_or_else(|| anyhow!("Block type is required"))?.block_type.ok_or_else(|| anyhow!("Block type is required"))?.to_scalar()?; + let catches = try_table.catches.to_scalar()?; + hash_t4(&[block_type, catches]) + }, "Type index and catches are required") + } + OpCode::ExceptionsExtThrow | OpCode::LegacyExceptionsExtCatch => { + opmatch!(operator, TagIndex(tag_index) => { + tag_index.as_scalar() + }, "Tag index is required") + } + }; + + Ok(hash_t4(&[opcode_value.as_scalar(), body])) + } +} + +impl AsScalar for Operator { + fn as_scalar(&self) -> Scalar { + hash_t4(&match self.to_scalar() { + Ok(scalar) => [1.as_scalar(), scalar], + Err(_) => [0.as_scalar(), 0.as_scalar()], + }) + } +} From 487b17ca9003991a06e8f2e410c8a3230968a6ad Mon Sep 17 00:00:00 2001 From: Alexander Guryanov Date: Thu, 19 Feb 2026 17:50:37 +0300 Subject: [PATCH 06/15] Add tests for operator AsScalar conversion --- src/program.rs | 398 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 373 insertions(+), 25 deletions(-) diff --git a/src/program.rs b/src/program.rs index 2fa2763..0ff12f1 100644 --- a/src/program.rs +++ b/src/program.rs @@ -5,7 +5,7 @@ use blstrs::Scalar; use crypto::{merkle::AsScalar, poseidon::hash_t4}; #[macro_export] -macro_rules! opmatch { +macro_rules! some { ($expr:expr, $pat:pat => $body:block, $msg:expr $(,)?) => {{ match $expr { Some($pat) => $body, @@ -52,7 +52,7 @@ impl ToScalar for wasm::ValueType { if self.reference_type.is_some() { bail!("Reference type is set for primitive value type"); } - 0.into() + 0.as_scalar() } PlainType::ValueTypeRef => { let ref_code = self @@ -73,7 +73,7 @@ impl ToScalar for wasm::block_type::BlockType { Ok(match self { wasm::block_type::BlockType::Empty(_) => -1.as_scalar(), wasm::block_type::BlockType::ValueType(vt) => vt.to_scalar()?, - wasm::block_type::BlockType::FunctionType(_) => todo!(), + wasm::block_type::BlockType::FunctionType(v) => v.as_scalar(), }) } } @@ -270,7 +270,7 @@ impl ToScalar for Operator { 0.into() } OpCode::Block | OpCode::Loop | OpCode::If | OpCode::LegacyExceptionsExtTry => { - opmatch!(operator, BlockType(block_type) => { + some!(operator, BlockType(block_type) => { match block_type.block_type { Some(block_type) => block_type.to_scalar()?, _ => bail!("Block type is required"), @@ -281,35 +281,35 @@ impl ToScalar for Operator { | OpCode::BrIf | OpCode::LegacyExceptionsExtRethrow | OpCode::LegacyExceptionsExtDelegate => { - opmatch!(operator, RelativeDepth(relative_depth) => { + some!(operator, RelativeDepth(relative_depth) => { relative_depth.as_scalar() }, "Relative depth is required") } OpCode::BrTable => { - opmatch!(operator, Targets(targets) => { + some!(operator, Targets(targets) => { let default = targets.default.ok_or_else(|| anyhow!("Default target is required"))?; hash_t4(&[default.as_scalar(), targets.targets.to_scalar()?]) }, "Type index is required") } OpCode::Call => { - opmatch!(operator, FunctionIndex(function_index) => { + some!(operator, FunctionIndex(function_index) => { function_index.as_scalar() }, "Function index is required") } OpCode::CallIndirect => { - opmatch!(operator, CallIndirect(call_indirect) => { + some!(operator, CallIndirect(call_indirect) => { let type_index = call_indirect.type_index.ok_or_else(|| anyhow!("Type index is required"))?; let table_index = call_indirect.table_index.ok_or_else(|| anyhow!("Table index is required"))?; hash_t4(&[type_index.as_scalar(), table_index.as_scalar()]) }, "Type index and table index are required") } OpCode::LocalGet | OpCode::LocalSet | OpCode::LocalTee => { - opmatch!(operator, LocalIndex(local_index) => { + some!(operator, LocalIndex(local_index) => { local_index.as_scalar() }, "Local index is required") } OpCode::GlobalGet | OpCode::GlobalSet => { - opmatch!(operator, GlobalIndex(global_index) => { + some!(operator, GlobalIndex(global_index) => { global_index.as_scalar() }, "Global index is required") } @@ -336,7 +336,7 @@ impl ToScalar for Operator { | OpCode::I64Store8 | OpCode::I64Store16 | OpCode::I64Store32 => { - opmatch!(operator, Memarg(memarg) => { + some!(operator, Memarg(memarg) => { let align = memarg.align.ok_or_else(|| anyhow!("Align is required"))?; let max_align = memarg.max_align.ok_or_else(|| anyhow!("Max align is required"))?; let offset = memarg.offset.ok_or_else(|| anyhow!("Offset is required"))?; @@ -345,82 +345,82 @@ impl ToScalar for Operator { }, "Mem arg is required") } OpCode::MemorySize | OpCode::MemoryGrow => { - opmatch!(operator, Mem(mem) => { + some!(operator, Mem(mem) => { mem.as_scalar() }, "Mem is required") } OpCode::I32Constant => { - opmatch!(operator, I32Value(i32_value) => { + some!(operator, I32Value(i32_value) => { i32_value.as_scalar() }, "I32 value is required") } OpCode::I64Constant => { - opmatch!(operator, I64Value(i64_value) => { + some!(operator, I64Value(i64_value) => { i64_value.as_scalar() }, "I64 value is required") } OpCode::F32Constant => { - opmatch!(operator, F32Value(f32_value) => { + some!(operator, F32Value(f32_value) => { f32_value.as_scalar() }, "F32 value is required") } OpCode::F64Constant => { - opmatch!(operator, F64Value(f64_value) => { + some!(operator, F64Value(f64_value) => { f64_value.as_scalar() }, "F64 value is required") } OpCode::BulkMemoryExtMemoryInit => { - opmatch!(operator, MemoryInit(memory_init) => { + some!(operator, MemoryInit(memory_init) => { let data_index = memory_init.data_index.ok_or_else(|| anyhow!("Data index is required"))?; let address = memory_init.address.ok_or_else(|| anyhow!("Address is required"))?; hash_t4(&[data_index.as_scalar(), address.as_scalar()]) }, "Data index and address are required") } OpCode::BulkMemoryExtDataDrop => { - opmatch!(operator, DataIndex(data_index) => { + some!(operator, DataIndex(data_index) => { data_index.as_scalar() }, "Data index is required") } OpCode::BulkMemoryExtMemoryCopy => { - opmatch!(operator, MemoryCopy(memory_copy) => { + some!(operator, MemoryCopy(memory_copy) => { let destination_address = memory_copy.destination_address.ok_or_else(|| anyhow!("Destination address is required"))?; let source_address = memory_copy.source_address.ok_or_else(|| anyhow!("Source address is required"))?; hash_t4(&[destination_address.as_scalar(), source_address.as_scalar()]) }, "Destination address and source address are required") } OpCode::BulkMemoryExtMemoryFill => { - opmatch!(operator, Mem(mem) => { + some!(operator, Mem(mem) => { mem.as_scalar() }, "Mem is required") } OpCode::BulkMemoryExtTableInit => { - opmatch!(operator, TableInit(table_init) => { + some!(operator, TableInit(table_init) => { let element_index = table_init.element_index.ok_or_else(|| anyhow!("Element index is required"))?; let table = table_init.table.ok_or_else(|| anyhow!("Table is required"))?; hash_t4(&[element_index.as_scalar(), table.as_scalar()]) }, "Table index is required") } OpCode::BulkMemoryExtElemDrop => { - opmatch!(operator, ElementIndex(element_index) => { + some!(operator, ElementIndex(element_index) => { element_index.as_scalar() }, "Element index is required") } OpCode::BulkMemoryExtTableCopy => { - opmatch!(operator, TableCopy(table_copy) => { + some!(operator, TableCopy(table_copy) => { let dst_table = table_copy.dst_table.ok_or_else(|| anyhow!("Dst table is required"))?; let src_table = table_copy.src_table.ok_or_else(|| anyhow!("Src table is required"))?; hash_t4(&[dst_table.as_scalar(), src_table.as_scalar()]) }, "Dst table and src table are required") } OpCode::ExceptionsExtTryTable => { - opmatch!(operator, TryTable(try_table) => { + some!(operator, TryTable(try_table) => { let block_type = try_table.r#type.ok_or_else(|| anyhow!("Block type is required"))?.block_type.ok_or_else(|| anyhow!("Block type is required"))?.to_scalar()?; let catches = try_table.catches.to_scalar()?; hash_t4(&[block_type, catches]) }, "Type index and catches are required") } OpCode::ExceptionsExtThrow | OpCode::LegacyExceptionsExtCatch => { - opmatch!(operator, TagIndex(tag_index) => { + some!(operator, TagIndex(tag_index) => { tag_index.as_scalar() }, "Tag index is required") } @@ -438,3 +438,351 @@ impl AsScalar for Operator { }) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::libernet::wasm::operator::Operator as OperatorVariant; + use crate::libernet::wasm::{ + BreakTargets, CallIndirectOp, CatchAllElements, CatchElement, CatchOne, MemArg, ValueType, + block_type, catch_element, + }; + use crate::libernet::wasm::{OpCode, Operator}; + use crypto::merkle::AsScalar; + + #[test] + fn test_some_macro_success() { + let result: Result = (|| { + let opt: Option = Some(42); + let x = some!(opt, x => { Ok(x) }, "expected some"); + x + })(); + assert_eq!(result.unwrap(), 42); + } + + #[test] + fn test_some_macro_failure() { + let result: Result = (|| { + let opt: Option = None; + some!(opt, x => { Ok(x) }, "expected some") + })(); + assert!(result.is_err()); + assert_eq!(result.unwrap_err().to_string(), "expected some"); + } + + #[test] + fn test_u32_to_scalar() { + let scalar = 42u32.to_scalar().unwrap(); + assert_eq!(scalar, 42u32.as_scalar()); + } + + #[test] + fn test_vec_to_scalar() { + let empty: Vec = vec![]; + let empty_scalar = empty.to_scalar().unwrap(); + assert_eq!(empty_scalar, 0u64.as_scalar()); + + let vec = vec![1u32, 2u32]; + let scalar = vec.to_scalar().unwrap(); + assert_eq!( + scalar, + hash_t4(&[ + hash_t4(&[2u64.as_scalar(), 1u32.as_scalar()]), + 2u32.as_scalar() + ]) + ); + } + + #[test] + fn test_value_type_i32_to_scalar() { + let vt = ValueType { + value_type: Some(PlainType::ValueTypeI32 as i32), + reference_type: None, + }; + let scalar = vt.to_scalar().unwrap(); + assert_eq!( + scalar, + hash_t4(&[(PlainType::ValueTypeI32 as i32).as_scalar(), 0.as_scalar()]) + ); + } + + #[test] + fn test_value_type_missing_value_type_fails() { + let vt = ValueType { + value_type: None, + reference_type: None, + }; + assert!(vt.to_scalar().is_err()); + } + + #[test] + fn test_value_type_ref_type_with_primitive_fails() { + let vt = ValueType { + value_type: Some(PlainType::ValueTypeI32 as i32), + reference_type: Some(RefType::RefFunc as i32), + }; + assert!(vt.to_scalar().is_err()); + } + + #[test] + fn test_catch_element_one_to_scalar() { + let ce = CatchElement { + catch_element: Some(catch_element::CatchElement::One(CatchOne { + tag: Some(1), + label: Some(2), + })), + }; + let scalar = ce.to_scalar().unwrap(); + assert_ne!(scalar, 0u64.as_scalar()); + } + + #[test] + fn test_catch_element_all_to_scalar() { + let ce = CatchElement { + catch_element: Some(catch_element::CatchElement::All(CatchAllElements { + label: Some(5), + })), + }; + let scalar = ce.to_scalar().unwrap(); + assert_ne!(scalar, 0u64.as_scalar()); + } + + #[test] + fn test_catch_element_missing_fails() { + let ce = CatchElement { + catch_element: None, + }; + assert!(ce.to_scalar().is_err()); + } + + #[test] + fn test_operator_nop_to_scalar() { + let op = Operator { + opcode: Some(OpCode::Nop as i32), + operator: None, + }; + let scalar = op.to_scalar().unwrap(); + assert_ne!(scalar, 0u64.as_scalar()); + } + + #[test] + fn test_operator_nop_with_operator_fails() { + let op = Operator { + opcode: Some(OpCode::Nop as i32), + operator: Some(OperatorVariant::LocalIndex(0)), + }; + assert!(op.to_scalar().is_err()); + } + + #[test] + fn test_operator_call_to_scalar() { + let op = Operator { + opcode: Some(OpCode::Call as i32), + operator: Some(OperatorVariant::FunctionIndex(42)), + }; + let scalar = op.to_scalar().unwrap(); + assert_ne!(scalar, 0u64.as_scalar()); + } + + #[test] + fn test_operator_call_missing_function_index_fails() { + let op = Operator { + opcode: Some(OpCode::Call as i32), + operator: None, + }; + assert!(op.to_scalar().is_err()); + } + + #[test] + fn test_operator_i32_constant_to_scalar() { + let op = Operator { + opcode: Some(OpCode::I32Constant as i32), + operator: Some(OperatorVariant::I32Value(100)), + }; + let scalar = op.to_scalar().unwrap(); + assert_eq!( + scalar, + hash_t4(&[(OpCode::I32Constant as i32).as_scalar(), 100u64.as_scalar()]) + ); + } + + #[test] + fn test_operator_block_empty_to_scalar() { + let op = Operator { + opcode: Some(OpCode::Block as i32), + operator: Some(OperatorVariant::BlockType(wasm::BlockType { + block_type: Some(block_type::BlockType::Empty(110)), + })), + }; + let scalar = op.to_scalar().unwrap(); + assert_eq!( + scalar, + hash_t4(&[(OpCode::Block as i32).as_scalar(), -1.as_scalar()]) + ); + } + + #[test] + fn test_operator_block_value_type_to_scalar() { + let value_type = ValueType { + value_type: Some(PlainType::ValueTypeI32 as i32), + reference_type: None, + }; + let op = Operator { + opcode: Some(OpCode::Block as i32), + operator: Some(OperatorVariant::BlockType(wasm::BlockType { + block_type: Some(block_type::BlockType::ValueType(value_type)), + })), + }; + let scalar = op.to_scalar().unwrap(); + assert_eq!( + scalar, + hash_t4(&[ + (OpCode::Block as i32).as_scalar(), + value_type.to_scalar().unwrap() + ]) + ); + } + + #[test] + fn test_operator_block_function_type_to_scalar() { + let op = Operator { + opcode: Some(OpCode::Block as i32), + operator: Some(OperatorVariant::BlockType(wasm::BlockType { + block_type: Some(block_type::BlockType::FunctionType(110)), + })), + }; + let scalar = op.to_scalar().unwrap(); + assert_eq!( + scalar, + hash_t4(&[(OpCode::Block as i32).as_scalar(), 110u64.as_scalar()]) + ); + } + + #[test] + fn test_operator_local_get_to_scalar() { + let op = Operator { + opcode: Some(OpCode::LocalGet as i32), + operator: Some(OperatorVariant::LocalIndex(3)), + }; + let scalar = op.to_scalar().unwrap(); + assert_eq!( + scalar, + hash_t4(&[(OpCode::LocalGet as i32).as_scalar(), 3u64.as_scalar()]) + ); + } + + #[test] + fn test_operator_memarg_to_scalar() { + let op = Operator { + opcode: Some(OpCode::I32Load as i32), + operator: Some(OperatorVariant::Memarg(MemArg { + align: Some(1), + max_align: Some(2), + offset: Some(3), + memory: Some(4), + })), + }; + let scalar = op.to_scalar().unwrap(); + assert_eq!( + scalar, + hash_t4(&[ + (OpCode::I32Load as i32).as_scalar(), + hash_t4(&[ + 1u64.as_scalar(), + 2u64.as_scalar(), + 3u64.as_scalar(), + 4u64.as_scalar() + ]) + ]) + ); + } + + #[test] + fn test_operator_br_table_to_scalar() { + let op = Operator { + opcode: Some(OpCode::BrTable as i32), + operator: Some(OperatorVariant::Targets(BreakTargets { + default: Some(5), + targets: vec![1, 2, 3], + })), + }; + let scalar = op.to_scalar().unwrap(); + assert_eq!( + scalar, + hash_t4(&[ + (OpCode::BrTable as i32).as_scalar(), + hash_t4(&[5.as_scalar(), vec![1, 2, 3].to_scalar().unwrap()]) + ]) + ); + } + + #[test] + fn test_operator_call_indirect_to_scalar() { + let op = Operator { + opcode: Some(OpCode::CallIndirect as i32), + operator: Some(OperatorVariant::CallIndirect(CallIndirectOp { + type_index: Some(1), + table_index: Some(2), + })), + }; + let scalar = op.to_scalar().unwrap(); + assert_eq!( + scalar, + hash_t4(&[ + (OpCode::CallIndirect as i32).as_scalar(), + hash_t4(&[1.as_scalar(), 2.as_scalar()]) + ]) + ); + } + + #[test] + fn test_operator_as_scalar_deterministic() { + let op = Operator { + opcode: Some(OpCode::Nop as i32), + operator: None, + }; + let s1 = op.as_scalar(); + let s2 = op.as_scalar(); + assert_eq!(s1, s2); + } + + #[test] + fn test_operator_as_scalar_different_operators_different_scalars() { + let nop = Operator { + opcode: Some(OpCode::Nop as i32), + operator: None, + }; + let ret = Operator { + opcode: Some(OpCode::Return as i32), + operator: None, + }; + assert_ne!(nop.as_scalar(), ret.as_scalar()); + } + + #[test] + fn test_operator_as_scalar_invalid_returns_error_scalar() { + let invalid = Operator { + opcode: None, + operator: None, + }; + let scalar = invalid.as_scalar(); + let valid = Operator { + opcode: Some(OpCode::Nop as i32), + operator: None, + }; + assert_ne!(scalar, valid.as_scalar()); + } + + #[test] + fn test_operator_same_op_same_scalar() { + let op1 = Operator { + opcode: Some(OpCode::I32Constant as i32), + operator: Some(OperatorVariant::I32Value(42)), + }; + let op2 = Operator { + opcode: Some(OpCode::I32Constant as i32), + operator: Some(OperatorVariant::I32Value(42)), + }; + assert_eq!(op1.as_scalar(), op2.as_scalar()); + } +} From 98f1ddc3714a31e1dfec66eecfc7a494a1ac42f0 Mon Sep 17 00:00:00 2001 From: Alexander Guryanov Date: Thu, 26 Feb 2026 11:24:14 +0300 Subject: [PATCH 07/15] Update ToScalar -> Sha3Hash --- Cargo.lock | 2 + Cargo.toml | 4 + src/data.rs | 5 +- src/program.rs | 636 ++++++++++++++++++++++++++++++++----------------- 4 files changed, 419 insertions(+), 228 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c86c100..0ddcd08 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1327,6 +1327,7 @@ dependencies = [ "ed25519-dalek", "ff", "futures", + "hex", "hyper", "oid-registry", "p256", @@ -1335,6 +1336,7 @@ dependencies = [ "prost-types", "rustls", "serde_json", + "sha3", "time", "tokio", "tokio-rustls", diff --git a/Cargo.toml b/Cargo.toml index bddbc76..a7e3e96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,10 @@ tonic-prost = "0.14.2" tower = "0.5.3" x509-parser = "0.18.1" zeroize = "1.8.2" +sha3 = "0.10.8" + +[dev-dependencies] +hex = "0.4" [build-dependencies] tonic-prost-build = "*" diff --git a/src/data.rs b/src/data.rs index a5a625d..2a22b60 100644 --- a/src/data.rs +++ b/src/data.rs @@ -261,9 +261,8 @@ pub struct Program { impl proto::EncodeToAny for Program { fn encode_to_any(&self) -> Result { - Ok(prost_types::Any::from_msg( - self.module.as_ref().context("missing program module")?, - )?) + prost_types::Any::from_msg(self.module.as_ref().context("missing program module")?) + .map_err(|e| anyhow!(e.to_string())) } } diff --git a/src/program.rs b/src/program.rs index 0ff12f1..5a33669 100644 --- a/src/program.rs +++ b/src/program.rs @@ -1,10 +1,11 @@ -use crate::libernet::wasm::{self, CatchElement, PlainType, RefType}; +use crate::libernet::wasm::{ + self, CatchElement, FuncType, ImportSection, PlainType, ProgramModule, RefType, SubType, + TypeRefFunc, TypeSection, Version, +}; use crate::libernet::wasm::{OpCode, Operator, operator::Operator::*}; use anyhow::{Result, anyhow, bail}; -use blstrs::Scalar; -use crypto::{merkle::AsScalar, poseidon::hash_t4}; +use sha3::Digest; -#[macro_export] macro_rules! some { ($expr:expr, $pat:pat => $body:block, $msg:expr $(,)?) => {{ match $expr { @@ -14,36 +15,56 @@ macro_rules! some { }}; } -trait ToScalar { - fn to_scalar(&self) -> Result; +trait Sha3Hash { + fn sha3_hash(&self, hasher: &mut D) -> Result<()>; } -impl ToScalar for u32 { - fn to_scalar(&self) -> Result { - Ok(self.as_scalar()) +impl Sha3Hash for u32 { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + hasher.update(self.to_le_bytes()); + Ok(()) } } -impl ToScalar for Vec -where - T: ToScalar, -{ - fn to_scalar(&self) -> Result { - let mut scalar = (self.len() as u64).as_scalar(); +impl Sha3Hash for u64 { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + hasher.update(self.to_le_bytes()); + Ok(()) + } +} + +impl Sha3Hash for i32 { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + hasher.update(self.to_le_bytes()); + Ok(()) + } +} + +impl Sha3Hash for i64 { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + hasher.update(self.to_le_bytes()); + Ok(()) + } +} + +impl Sha3Hash for Vec { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + (self.len() as u64).sha3_hash(hasher)?; for elem in self { - scalar = hash_t4(&[scalar, elem.to_scalar()?]); + elem.sha3_hash(hasher)?; } - Ok(scalar) + Ok(()) } } -impl ToScalar for wasm::ValueType { - fn to_scalar(&self) -> Result { +impl Sha3Hash for wasm::ValueType { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { let value_code = self .value_type .ok_or_else(|| anyhow!("Value type is required"))?; let plain_type = PlainType::try_from(value_code)?; - let body = match plain_type { + value_code.sha3_hash(hasher)?; + match plain_type { PlainType::ValueTypeI32 | PlainType::ValueTypeI64 | PlainType::ValueTypeF32 @@ -52,7 +73,7 @@ impl ToScalar for wasm::ValueType { if self.reference_type.is_some() { bail!("Reference type is set for primitive value type"); } - 0.as_scalar() + hasher.update([0]); } PlainType::ValueTypeRef => { let ref_code = self @@ -61,64 +82,77 @@ impl ToScalar for wasm::ValueType { if RefType::try_from(ref_code).is_err() { bail!("Invalid reference type"); } - ref_code.as_scalar() + ref_code.sha3_hash(hasher)?; } }; - Ok(hash_t4(&[value_code.as_scalar(), body])) + Ok(()) } } -impl ToScalar for wasm::block_type::BlockType { - fn to_scalar(&self) -> Result { - Ok(match self { - wasm::block_type::BlockType::Empty(_) => -1.as_scalar(), - wasm::block_type::BlockType::ValueType(vt) => vt.to_scalar()?, - wasm::block_type::BlockType::FunctionType(v) => v.as_scalar(), - }) +impl Sha3Hash for wasm::block_type::BlockType { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + match self { + wasm::block_type::BlockType::Empty(_) => hasher.update([0]), + wasm::block_type::BlockType::ValueType(vt) => vt.sha3_hash(hasher)?, + wasm::block_type::BlockType::FunctionType(v) => v.sha3_hash(hasher)?, + }; + Ok(()) } } -impl ToScalar for CatchElement { - fn to_scalar(&self) -> Result { +impl Sha3Hash for CatchElement { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { let catch_element = self .catch_element .ok_or(anyhow!("Catch element is required"))?; - Ok(match catch_element { - wasm::catch_element::CatchElement::One(one) => hash_t4(&[ - one.tag.ok_or(anyhow!("One: Tag is required"))?.as_scalar(), + match catch_element { + wasm::catch_element::CatchElement::One(one) => { + hasher.update([0]); + one.tag + .ok_or(anyhow!("One: Tag is required"))? + .sha3_hash(hasher)?; one.label .ok_or(anyhow!("One: Label is required"))? - .as_scalar(), - ]), - wasm::catch_element::CatchElement::OneRef(one_ref) => hash_t4(&[ + .sha3_hash(hasher)?; + } + wasm::catch_element::CatchElement::OneRef(one_ref) => { + hasher.update([1]); one_ref .tag .ok_or(anyhow!("OneRef: Tag is required"))? - .as_scalar(), + .sha3_hash(hasher)?; one_ref .label .ok_or(anyhow!("OneRef: Label is required"))? - .as_scalar(), - ]), - wasm::catch_element::CatchElement::All(all) => hash_t4(&[all - .label - .ok_or(anyhow!("All: Label is required"))? - .as_scalar()]), - wasm::catch_element::CatchElement::AllRef(all_ref) => hash_t4(&[all_ref - .label - .ok_or(anyhow!("AllRef: Label is required"))? - .as_scalar()]), - }) + .sha3_hash(hasher)?; + } + wasm::catch_element::CatchElement::All(all) => { + hasher.update([2]); + all.label + .ok_or(anyhow!("All: Label is required"))? + .sha3_hash(hasher)?; + } + wasm::catch_element::CatchElement::AllRef(all_ref) => { + hasher.update([3]); + all_ref + .label + .ok_or(anyhow!("AllRef: Label is required"))? + .sha3_hash(hasher)?; + } + }; + Ok(()) } } -impl ToScalar for Operator { - fn to_scalar(&self) -> Result { +impl Sha3Hash for Operator { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { let opcode_value = self.opcode.ok_or_else(|| anyhow!("Opcode is required"))?; let opcode = OpCode::try_from(opcode_value)?; let operator = &self.operator; - let body: Scalar = match opcode { + opcode_value.sha3_hash(hasher)?; + + match opcode { OpCode::Unreachable | OpCode::Nop | OpCode::Else @@ -267,12 +301,12 @@ impl ToScalar for Operator { if operator.is_some() { bail!("Operator is not allowed for this opcode"); } - 0.into() + hasher.update([0]); } OpCode::Block | OpCode::Loop | OpCode::If | OpCode::LegacyExceptionsExtTry => { some!(operator, BlockType(block_type) => { match block_type.block_type { - Some(block_type) => block_type.to_scalar()?, + Some(block_type) => block_type.sha3_hash(hasher)?, _ => bail!("Block type is required"), } }, "Block type is required") @@ -282,35 +316,34 @@ impl ToScalar for Operator { | OpCode::LegacyExceptionsExtRethrow | OpCode::LegacyExceptionsExtDelegate => { some!(operator, RelativeDepth(relative_depth) => { - relative_depth.as_scalar() + relative_depth.sha3_hash(hasher)?; }, "Relative depth is required") } OpCode::BrTable => { some!(operator, Targets(targets) => { - let default = targets.default.ok_or_else(|| anyhow!("Default target is required"))?; - hash_t4(&[default.as_scalar(), targets.targets.to_scalar()?]) + targets.default.ok_or_else(|| anyhow!("Default target is required"))?.sha3_hash(hasher)?; + targets.targets.sha3_hash(hasher)?; }, "Type index is required") } OpCode::Call => { some!(operator, FunctionIndex(function_index) => { - function_index.as_scalar() + function_index.sha3_hash(hasher)?; }, "Function index is required") } OpCode::CallIndirect => { some!(operator, CallIndirect(call_indirect) => { - let type_index = call_indirect.type_index.ok_or_else(|| anyhow!("Type index is required"))?; - let table_index = call_indirect.table_index.ok_or_else(|| anyhow!("Table index is required"))?; - hash_t4(&[type_index.as_scalar(), table_index.as_scalar()]) + call_indirect.type_index.ok_or_else(|| anyhow!("Type index is required"))?.sha3_hash(hasher)?; + call_indirect.table_index.ok_or_else(|| anyhow!("Table index is required"))?.sha3_hash(hasher)?; }, "Type index and table index are required") } OpCode::LocalGet | OpCode::LocalSet | OpCode::LocalTee => { some!(operator, LocalIndex(local_index) => { - local_index.as_scalar() + local_index.sha3_hash(hasher)?; }, "Local index is required") } OpCode::GlobalGet | OpCode::GlobalSet => { some!(operator, GlobalIndex(global_index) => { - global_index.as_scalar() + global_index.sha3_hash(hasher)?; }, "Global index is required") } OpCode::I32Load @@ -337,105 +370,195 @@ impl ToScalar for Operator { | OpCode::I64Store16 | OpCode::I64Store32 => { some!(operator, Memarg(memarg) => { - let align = memarg.align.ok_or_else(|| anyhow!("Align is required"))?; - let max_align = memarg.max_align.ok_or_else(|| anyhow!("Max align is required"))?; - let offset = memarg.offset.ok_or_else(|| anyhow!("Offset is required"))?; - let memory = memarg.memory.ok_or_else(|| anyhow!("Memory is required"))?; - hash_t4(&[align.as_scalar(), max_align.as_scalar(), offset.as_scalar(), memory.as_scalar()]) + memarg.align.ok_or_else(|| anyhow!("Align is required"))?.sha3_hash(hasher)?; + memarg.max_align.ok_or_else(|| anyhow!("Max align is required"))?.sha3_hash(hasher)?; + memarg.offset.ok_or_else(|| anyhow!("Offset is required"))?.sha3_hash(hasher)?; + memarg.memory.ok_or_else(|| anyhow!("Memory is required"))?.sha3_hash(hasher)?; }, "Mem arg is required") } OpCode::MemorySize | OpCode::MemoryGrow => { some!(operator, Mem(mem) => { - mem.as_scalar() + mem.sha3_hash(hasher)?; }, "Mem is required") } OpCode::I32Constant => { some!(operator, I32Value(i32_value) => { - i32_value.as_scalar() + i32_value.sha3_hash(hasher)?; }, "I32 value is required") } OpCode::I64Constant => { some!(operator, I64Value(i64_value) => { - i64_value.as_scalar() + i64_value.sha3_hash(hasher)?; }, "I64 value is required") } OpCode::F32Constant => { some!(operator, F32Value(f32_value) => { - f32_value.as_scalar() + f32_value.sha3_hash(hasher)?; }, "F32 value is required") } OpCode::F64Constant => { some!(operator, F64Value(f64_value) => { - f64_value.as_scalar() + f64_value.sha3_hash(hasher)?; }, "F64 value is required") } OpCode::BulkMemoryExtMemoryInit => { some!(operator, MemoryInit(memory_init) => { - let data_index = memory_init.data_index.ok_or_else(|| anyhow!("Data index is required"))?; - let address = memory_init.address.ok_or_else(|| anyhow!("Address is required"))?; - hash_t4(&[data_index.as_scalar(), address.as_scalar()]) + memory_init.data_index.ok_or_else(|| anyhow!("Data index is required"))?.sha3_hash(hasher)?; + memory_init.address.ok_or_else(|| anyhow!("Address is required"))?.sha3_hash(hasher)?; }, "Data index and address are required") } OpCode::BulkMemoryExtDataDrop => { some!(operator, DataIndex(data_index) => { - data_index.as_scalar() + data_index.sha3_hash(hasher)?; }, "Data index is required") } OpCode::BulkMemoryExtMemoryCopy => { some!(operator, MemoryCopy(memory_copy) => { - let destination_address = memory_copy.destination_address.ok_or_else(|| anyhow!("Destination address is required"))?; - let source_address = memory_copy.source_address.ok_or_else(|| anyhow!("Source address is required"))?; - hash_t4(&[destination_address.as_scalar(), source_address.as_scalar()]) + memory_copy.destination_address.ok_or_else(|| anyhow!("Destination address is required"))?.sha3_hash(hasher)?; + memory_copy.source_address.ok_or_else(|| anyhow!("Source address is required"))?.sha3_hash(hasher)?; }, "Destination address and source address are required") } OpCode::BulkMemoryExtMemoryFill => { some!(operator, Mem(mem) => { - mem.as_scalar() + mem.sha3_hash(hasher)?; }, "Mem is required") } OpCode::BulkMemoryExtTableInit => { some!(operator, TableInit(table_init) => { - let element_index = table_init.element_index.ok_or_else(|| anyhow!("Element index is required"))?; - let table = table_init.table.ok_or_else(|| anyhow!("Table is required"))?; - hash_t4(&[element_index.as_scalar(), table.as_scalar()]) + table_init.element_index.ok_or_else(|| anyhow!("Element index is required"))?.sha3_hash(hasher)?; + table_init.table.ok_or_else(|| anyhow!("Table is required"))?.sha3_hash(hasher)?; }, "Table index is required") } OpCode::BulkMemoryExtElemDrop => { some!(operator, ElementIndex(element_index) => { - element_index.as_scalar() + element_index.sha3_hash(hasher)?; }, "Element index is required") } OpCode::BulkMemoryExtTableCopy => { some!(operator, TableCopy(table_copy) => { - let dst_table = table_copy.dst_table.ok_or_else(|| anyhow!("Dst table is required"))?; - let src_table = table_copy.src_table.ok_or_else(|| anyhow!("Src table is required"))?; - hash_t4(&[dst_table.as_scalar(), src_table.as_scalar()]) + table_copy.dst_table.ok_or_else(|| anyhow!("Dst table is required"))?.sha3_hash(hasher)?; + table_copy.src_table.ok_or_else(|| anyhow!("Src table is required"))?.sha3_hash(hasher)?; }, "Dst table and src table are required") } OpCode::ExceptionsExtTryTable => { some!(operator, TryTable(try_table) => { - let block_type = try_table.r#type.ok_or_else(|| anyhow!("Block type is required"))?.block_type.ok_or_else(|| anyhow!("Block type is required"))?.to_scalar()?; - let catches = try_table.catches.to_scalar()?; - hash_t4(&[block_type, catches]) + try_table.r#type.ok_or_else(|| anyhow!("Block type is required"))?.block_type.ok_or_else(|| anyhow!("Block type is required"))?.sha3_hash(hasher)?; + try_table.catches.sha3_hash(hasher)?; }, "Type index and catches are required") } OpCode::ExceptionsExtThrow | OpCode::LegacyExceptionsExtCatch => { some!(operator, TagIndex(tag_index) => { - tag_index.as_scalar() + tag_index.sha3_hash(hasher)?; }, "Tag index is required") } }; - Ok(hash_t4(&[opcode_value.as_scalar(), body])) + Ok(()) + } +} + +impl Sha3Hash for Version { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.number + .ok_or_else(|| anyhow!("Version number is required"))? + .sha3_hash(hasher)?; + self.encoding + .ok_or_else(|| anyhow!("Version encoding is required"))? + .sha3_hash(hasher)?; + Ok(()) + } +} + +impl Sha3Hash for FuncType { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.params.sha3_hash(hasher)?; + self.results.sha3_hash(hasher)?; + Ok(()) } } -impl AsScalar for Operator { - fn as_scalar(&self) -> Scalar { - hash_t4(&match self.to_scalar() { - Ok(scalar) => [1.as_scalar(), scalar], - Err(_) => [0.as_scalar(), 0.as_scalar()], - }) +impl Sha3Hash for SubType { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + match &self.kind { + Some(wasm::sub_type::Kind::Func(func_type)) => func_type.sha3_hash(hasher)?, + _ => bail!("Sub type kind is required"), + } + Ok(()) + } +} + +impl Sha3Hash for TypeSection { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.types.sha3_hash(hasher)?; + Ok(()) + } +} + +impl Sha3Hash for TypeRefFunc { + fn sha3_hash(&self, _hasher: &mut D) -> Result<()> { + // let module = self + // .module + // .as_ref() + // .ok_or_else(|| anyhow!("Module is required"))?; + // let name = self + // .name + // .as_ref() + // .ok_or_else(|| anyhow!("Name is required"))?; + // let function_type = self + // .function_type + // .ok_or_else(|| anyhow!("Function type is required"))?; + //TODO + Ok(()) + } +} + +impl Sha3Hash for ImportSection { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.imports.sha3_hash(hasher)?; + Ok(()) + } +} + +impl Sha3Hash for ProgramModule { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.protocol_version + .ok_or_else(|| anyhow!("Protocol version is required"))? + .sha3_hash(hasher)?; + self.version + .ok_or_else(|| anyhow!("Version is required"))? + .sha3_hash(hasher)?; + + // pub import_section: ::core::option::Option, + // #[prost(message, optional, tag = "5")] + // pub function_section: ::core::option::Option, + // #[prost(message, optional, tag = "6")] + // pub table_section: ::core::option::Option, + // #[prost(message, optional, tag = "7")] + // pub memory_section: ::core::option::Option, + // #[prost(message, optional, tag = "8")] + // pub tag_section: ::core::option::Option, + // #[prost(message, optional, tag = "9")] + // pub global_section: ::core::option::Option, + // #[prost(message, optional, tag = "10")] + // pub export_section: ::core::option::Option, + // #[prost(message, optional, tag = "11")] + // pub element_section: ::core::option::Option, + // #[prost(message, optional, tag = "12")] + // pub code_section: ::core::option::Option, + // #[prost(message, optional, tag = "13")] + // pub data_section: ::core::option::Option, + + macro_rules! hash_section { + ($opt:expr $(,)?) => {{ + match &$opt { + Some(v) => v.sha3_hash(hasher)?, + None => hasher.update([0]), + } + }}; + } + + hash_section!(self.type_section); + Ok(()) } } @@ -444,11 +567,39 @@ mod tests { use super::*; use crate::libernet::wasm::operator::Operator as OperatorVariant; use crate::libernet::wasm::{ - BreakTargets, CallIndirectOp, CatchAllElements, CatchElement, CatchOne, MemArg, ValueType, - block_type, catch_element, + BreakTargets, CallIndirectOp, CatchAllElements, CatchAllRef, CatchElement, CatchOne, + CatchOneRef, MemArg, ValueType, block_type, catch_element, }; use crate::libernet::wasm::{OpCode, Operator}; - use crypto::merkle::AsScalar; + + fn sha512, T: AsRef<[u8]>>(parts: I) -> String { + let mut hasher = sha3::Sha3_512::new(); + for p in parts { + hasher.update(p.as_ref()); + } + hex::encode(hasher.finalize()) + } + + fn hash, T: Sha3Hash>(parts: I) -> String { + let mut hasher = sha3::Sha3_512::new(); + for p in parts { + p.sha3_hash(&mut hasher).unwrap(); + } + hex::encode(hasher.finalize()) + } + + macro_rules! hash_eq { + ($left:expr, $right:expr) => { + assert_eq!(hash($left), sha512($right)); + }; + } + + macro_rules! hash_err { + ($left:expr) => { + let mut hasher = sha3::Sha3_512::new(); + assert!($left.sha3_hash(&mut hasher).is_err()); + }; + } #[test] fn test_some_macro_success() { @@ -471,38 +622,46 @@ mod tests { } #[test] - fn test_u32_to_scalar() { - let scalar = 42u32.to_scalar().unwrap(); - assert_eq!(scalar, 42u32.as_scalar()); + fn test_u32_to_hash() { + hash_eq!([42u32], [42u32.to_le_bytes()]); + } + + #[test] + fn test_u64_to_hash() { + hash_eq!([42u64], [42u64.to_le_bytes()]); + } + + #[test] + fn test_i32_to_hash() { + hash_eq!([42i32], [42i32.to_le_bytes()]); } #[test] - fn test_vec_to_scalar() { - let empty: Vec = vec![]; - let empty_scalar = empty.to_scalar().unwrap(); - assert_eq!(empty_scalar, 0u64.as_scalar()); - - let vec = vec![1u32, 2u32]; - let scalar = vec.to_scalar().unwrap(); - assert_eq!( - scalar, - hash_t4(&[ - hash_t4(&[2u64.as_scalar(), 1u32.as_scalar()]), - 2u32.as_scalar() - ]) + fn test_i64_to_hash() { + hash_eq!([42i64], [42i64.to_le_bytes()]); + } + + #[test] + fn test_vec_to_hash() { + hash_eq!([Vec::::new()], [0u64.to_le_bytes()]); + hash_eq!( + [vec![1u32, 2u32]], + [ + &2u64.to_le_bytes()[..], + &1u32.to_le_bytes()[..], + &2u32.to_le_bytes()[..] + ] ); } #[test] - fn test_value_type_i32_to_scalar() { - let vt = ValueType { - value_type: Some(PlainType::ValueTypeI32 as i32), - reference_type: None, - }; - let scalar = vt.to_scalar().unwrap(); - assert_eq!( - scalar, - hash_t4(&[(PlainType::ValueTypeI32 as i32).as_scalar(), 0.as_scalar()]) + fn test_value_type_i32_to_hash() { + hash_eq!( + [ValueType { + value_type: Some(PlainType::ValueTypeI32 as i32), + reference_type: None, + }], + [&(PlainType::ValueTypeI32 as i32).to_le_bytes()[..], &[0]] ); } @@ -512,7 +671,7 @@ mod tests { value_type: None, reference_type: None, }; - assert!(vt.to_scalar().is_err()); + hash_err!(vt); } #[test] @@ -521,30 +680,55 @@ mod tests { value_type: Some(PlainType::ValueTypeI32 as i32), reference_type: Some(RefType::RefFunc as i32), }; - assert!(vt.to_scalar().is_err()); + hash_err!(vt); } #[test] - fn test_catch_element_one_to_scalar() { + fn test_catch_element_one_to_hash() { let ce = CatchElement { catch_element: Some(catch_element::CatchElement::One(CatchOne { tag: Some(1), label: Some(2), })), }; - let scalar = ce.to_scalar().unwrap(); - assert_ne!(scalar, 0u64.as_scalar()); + hash_eq!( + [ce], + [&[0], &1u32.to_le_bytes()[..], &2u32.to_le_bytes()[..]] + ); } #[test] - fn test_catch_element_all_to_scalar() { + fn test_catch_element_one_ref_to_hash() { + let ce = CatchElement { + catch_element: Some(catch_element::CatchElement::OneRef(CatchOneRef { + tag: Some(1), + label: Some(2), + })), + }; + hash_eq!( + [ce], + [&[1], &1u32.to_le_bytes()[..], &2u32.to_le_bytes()[..]] + ); + } + + #[test] + fn test_catch_element_all_to_hash() { let ce = CatchElement { catch_element: Some(catch_element::CatchElement::All(CatchAllElements { label: Some(5), })), }; - let scalar = ce.to_scalar().unwrap(); - assert_ne!(scalar, 0u64.as_scalar()); + hash_eq!([ce], [&[2], &5u32.to_le_bytes()[..]]); + } + + #[test] + fn test_catch_element_all_ref_to_hash() { + let ce = CatchElement { + catch_element: Some(catch_element::CatchElement::AllRef(CatchAllRef { + label: Some(5), + })), + }; + hash_eq!([ce], [&[3], &5u32.to_le_bytes()[..]]); } #[test] @@ -552,17 +736,16 @@ mod tests { let ce = CatchElement { catch_element: None, }; - assert!(ce.to_scalar().is_err()); + hash_err!(ce); } #[test] - fn test_operator_nop_to_scalar() { + fn test_operator_nop_to_hash() { let op = Operator { opcode: Some(OpCode::Nop as i32), operator: None, }; - let scalar = op.to_scalar().unwrap(); - assert_ne!(scalar, 0u64.as_scalar()); + hash_eq!([op], [&(OpCode::Nop as i32).to_le_bytes()[..], &[0]]); } #[test] @@ -571,17 +754,22 @@ mod tests { opcode: Some(OpCode::Nop as i32), operator: Some(OperatorVariant::LocalIndex(0)), }; - assert!(op.to_scalar().is_err()); + hash_err!(op); } #[test] - fn test_operator_call_to_scalar() { + fn test_operator_call_to_hash() { let op = Operator { opcode: Some(OpCode::Call as i32), operator: Some(OperatorVariant::FunctionIndex(42)), }; - let scalar = op.to_scalar().unwrap(); - assert_ne!(scalar, 0u64.as_scalar()); + hash_eq!( + [op], + [ + &(OpCode::Call as i32).to_le_bytes()[..], + &42u32.to_le_bytes()[..] + ] + ); } #[test] @@ -590,39 +778,37 @@ mod tests { opcode: Some(OpCode::Call as i32), operator: None, }; - assert!(op.to_scalar().is_err()); + hash_err!(op); } #[test] - fn test_operator_i32_constant_to_scalar() { + fn test_operator_i32_constant_to_hash() { let op = Operator { opcode: Some(OpCode::I32Constant as i32), operator: Some(OperatorVariant::I32Value(100)), }; - let scalar = op.to_scalar().unwrap(); - assert_eq!( - scalar, - hash_t4(&[(OpCode::I32Constant as i32).as_scalar(), 100u64.as_scalar()]) + hash_eq!( + [op], + [ + &(OpCode::I32Constant as i32).to_le_bytes()[..], + &100u32.to_le_bytes()[..] + ] ); } #[test] - fn test_operator_block_empty_to_scalar() { + fn test_operator_block_empty_to_hash() { let op = Operator { opcode: Some(OpCode::Block as i32), operator: Some(OperatorVariant::BlockType(wasm::BlockType { block_type: Some(block_type::BlockType::Empty(110)), })), }; - let scalar = op.to_scalar().unwrap(); - assert_eq!( - scalar, - hash_t4(&[(OpCode::Block as i32).as_scalar(), -1.as_scalar()]) - ); + hash_eq!([op], [&(OpCode::Block as i32).to_le_bytes()[..], &[0]]); } #[test] - fn test_operator_block_value_type_to_scalar() { + fn test_operator_block_value_type_to_hash() { let value_type = ValueType { value_type: Some(PlainType::ValueTypeI32 as i32), reference_type: None, @@ -633,46 +819,50 @@ mod tests { block_type: Some(block_type::BlockType::ValueType(value_type)), })), }; - let scalar = op.to_scalar().unwrap(); - assert_eq!( - scalar, - hash_t4(&[ - (OpCode::Block as i32).as_scalar(), - value_type.to_scalar().unwrap() - ]) + hash_eq!( + [op], + [ + &(OpCode::Block as i32).to_le_bytes()[..], + &(PlainType::ValueTypeI32 as i32).to_le_bytes()[..], + &[0] + ] ); } #[test] - fn test_operator_block_function_type_to_scalar() { + fn test_operator_block_function_type_to_hash() { let op = Operator { opcode: Some(OpCode::Block as i32), operator: Some(OperatorVariant::BlockType(wasm::BlockType { block_type: Some(block_type::BlockType::FunctionType(110)), })), }; - let scalar = op.to_scalar().unwrap(); - assert_eq!( - scalar, - hash_t4(&[(OpCode::Block as i32).as_scalar(), 110u64.as_scalar()]) + hash_eq!( + [op], + [ + &(OpCode::Block as i32).to_le_bytes()[..], + &110u32.to_le_bytes()[..] + ] ); } #[test] - fn test_operator_local_get_to_scalar() { + fn test_operator_local_get_to_hash() { let op = Operator { opcode: Some(OpCode::LocalGet as i32), operator: Some(OperatorVariant::LocalIndex(3)), }; - let scalar = op.to_scalar().unwrap(); - assert_eq!( - scalar, - hash_t4(&[(OpCode::LocalGet as i32).as_scalar(), 3u64.as_scalar()]) + hash_eq!( + [op], + [ + &(OpCode::LocalGet as i32).to_le_bytes()[..], + &3u32.to_le_bytes()[..] + ] ); } #[test] - fn test_operator_memarg_to_scalar() { + fn test_operator_memarg_to_hash() { let op = Operator { opcode: Some(OpCode::I32Load as i32), operator: Some(OperatorVariant::Memarg(MemArg { @@ -682,23 +872,20 @@ mod tests { memory: Some(4), })), }; - let scalar = op.to_scalar().unwrap(); - assert_eq!( - scalar, - hash_t4(&[ - (OpCode::I32Load as i32).as_scalar(), - hash_t4(&[ - 1u64.as_scalar(), - 2u64.as_scalar(), - 3u64.as_scalar(), - 4u64.as_scalar() - ]) - ]) + hash_eq!( + [op], + [ + &(OpCode::I32Load as i32).to_le_bytes()[..], + &1u32.to_le_bytes()[..], + &2u32.to_le_bytes()[..], + &3u64.to_le_bytes()[..], + &4u32.to_le_bytes()[..] + ] ); } #[test] - fn test_operator_br_table_to_scalar() { + fn test_operator_br_table_to_hash() { let op = Operator { opcode: Some(OpCode::BrTable as i32), operator: Some(OperatorVariant::Targets(BreakTargets { @@ -706,18 +893,21 @@ mod tests { targets: vec![1, 2, 3], })), }; - let scalar = op.to_scalar().unwrap(); - assert_eq!( - scalar, - hash_t4(&[ - (OpCode::BrTable as i32).as_scalar(), - hash_t4(&[5.as_scalar(), vec![1, 2, 3].to_scalar().unwrap()]) - ]) + hash_eq!( + [op], + [ + &(OpCode::BrTable as i32).to_le_bytes()[..], + &5u32.to_le_bytes()[..], + &3u64.to_le_bytes()[..], + &1u32.to_le_bytes()[..], + &2u32.to_le_bytes()[..], + &3u32.to_le_bytes()[..] + ] ); } #[test] - fn test_operator_call_indirect_to_scalar() { + fn test_operator_call_indirect_to_hash() { let op = Operator { opcode: Some(OpCode::CallIndirect as i32), operator: Some(OperatorVariant::CallIndirect(CallIndirectOp { @@ -725,29 +915,33 @@ mod tests { table_index: Some(2), })), }; - let scalar = op.to_scalar().unwrap(); - assert_eq!( - scalar, - hash_t4(&[ - (OpCode::CallIndirect as i32).as_scalar(), - hash_t4(&[1.as_scalar(), 2.as_scalar()]) - ]) + hash_eq!( + [op], + [ + &(OpCode::CallIndirect as i32).to_le_bytes()[..], + &1u32.to_le_bytes()[..], + &2u32.to_le_bytes()[..] + ] ); } #[test] - fn test_operator_as_scalar_deterministic() { + fn test_operator_hash_deterministic() { let op = Operator { opcode: Some(OpCode::Nop as i32), operator: None, }; - let s1 = op.as_scalar(); - let s2 = op.as_scalar(); - assert_eq!(s1, s2); + let mut hasher1 = sha3::Sha3_512::new(); + let mut hasher2 = sha3::Sha3_512::new(); + op.sha3_hash(&mut hasher1).unwrap(); + op.sha3_hash(&mut hasher2).unwrap(); + assert_eq!(hasher1.finalize(), hasher2.finalize()); } #[test] - fn test_operator_as_scalar_different_operators_different_scalars() { + fn test_operator_hash_different_operators_different_scalars() { + let mut hasher1 = sha3::Sha3_512::new(); + let mut hasher2 = sha3::Sha3_512::new(); let nop = Operator { opcode: Some(OpCode::Nop as i32), operator: None, @@ -756,25 +950,15 @@ mod tests { opcode: Some(OpCode::Return as i32), operator: None, }; - assert_ne!(nop.as_scalar(), ret.as_scalar()); - } - - #[test] - fn test_operator_as_scalar_invalid_returns_error_scalar() { - let invalid = Operator { - opcode: None, - operator: None, - }; - let scalar = invalid.as_scalar(); - let valid = Operator { - opcode: Some(OpCode::Nop as i32), - operator: None, - }; - assert_ne!(scalar, valid.as_scalar()); + nop.sha3_hash(&mut hasher1).unwrap(); + ret.sha3_hash(&mut hasher2).unwrap(); + assert_ne!(hasher1.finalize(), hasher2.finalize()); } #[test] - fn test_operator_same_op_same_scalar() { + fn test_operator_same_op_same_hash() { + let mut hasher1 = sha3::Sha3_512::new(); + let mut hasher2 = sha3::Sha3_512::new(); let op1 = Operator { opcode: Some(OpCode::I32Constant as i32), operator: Some(OperatorVariant::I32Value(42)), @@ -783,6 +967,8 @@ mod tests { opcode: Some(OpCode::I32Constant as i32), operator: Some(OperatorVariant::I32Value(42)), }; - assert_eq!(op1.as_scalar(), op2.as_scalar()); + op1.sha3_hash(&mut hasher1).unwrap(); + op2.sha3_hash(&mut hasher2).unwrap(); + assert_eq!(hasher1.finalize(), hasher2.finalize()); } } From 9c072e292dcb71ca0349d69c3468d94464716791 Mon Sep 17 00:00:00 2001 From: Alexander Guryanov Date: Thu, 26 Feb 2026 11:32:23 +0300 Subject: [PATCH 08/15] ok_or_else -> context --- src/program.rs | 58 +++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/src/program.rs b/src/program.rs index 5a33669..e5e8d47 100644 --- a/src/program.rs +++ b/src/program.rs @@ -3,7 +3,7 @@ use crate::libernet::wasm::{ TypeRefFunc, TypeSection, Version, }; use crate::libernet::wasm::{OpCode, Operator, operator::Operator::*}; -use anyhow::{Result, anyhow, bail}; +use anyhow::{Context, Result, anyhow, bail}; use sha3::Digest; macro_rules! some { @@ -59,9 +59,7 @@ impl Sha3Hash for Vec { impl Sha3Hash for wasm::ValueType { fn sha3_hash(&self, hasher: &mut D) -> Result<()> { - let value_code = self - .value_type - .ok_or_else(|| anyhow!("Value type is required"))?; + let value_code = self.value_type.context("Value type is required")?; let plain_type = PlainType::try_from(value_code)?; value_code.sha3_hash(hasher)?; match plain_type { @@ -76,9 +74,7 @@ impl Sha3Hash for wasm::ValueType { hasher.update([0]); } PlainType::ValueTypeRef => { - let ref_code = self - .reference_type - .ok_or_else(|| anyhow!("Reference type is required"))?; + let ref_code = self.reference_type.context("Reference type is required")?; if RefType::try_from(ref_code).is_err() { bail!("Invalid reference type"); } @@ -146,7 +142,7 @@ impl Sha3Hash for CatchElement { impl Sha3Hash for Operator { fn sha3_hash(&self, hasher: &mut D) -> Result<()> { - let opcode_value = self.opcode.ok_or_else(|| anyhow!("Opcode is required"))?; + let opcode_value = self.opcode.context("Opcode is required")?; let opcode = OpCode::try_from(opcode_value)?; let operator = &self.operator; @@ -321,7 +317,7 @@ impl Sha3Hash for Operator { } OpCode::BrTable => { some!(operator, Targets(targets) => { - targets.default.ok_or_else(|| anyhow!("Default target is required"))?.sha3_hash(hasher)?; + targets.default.context("Default target is required")?.sha3_hash(hasher)?; targets.targets.sha3_hash(hasher)?; }, "Type index is required") } @@ -332,8 +328,8 @@ impl Sha3Hash for Operator { } OpCode::CallIndirect => { some!(operator, CallIndirect(call_indirect) => { - call_indirect.type_index.ok_or_else(|| anyhow!("Type index is required"))?.sha3_hash(hasher)?; - call_indirect.table_index.ok_or_else(|| anyhow!("Table index is required"))?.sha3_hash(hasher)?; + call_indirect.type_index.context("Type index is required")?.sha3_hash(hasher)?; + call_indirect.table_index.context("Table index is required")?.sha3_hash(hasher)?; }, "Type index and table index are required") } OpCode::LocalGet | OpCode::LocalSet | OpCode::LocalTee => { @@ -370,10 +366,10 @@ impl Sha3Hash for Operator { | OpCode::I64Store16 | OpCode::I64Store32 => { some!(operator, Memarg(memarg) => { - memarg.align.ok_or_else(|| anyhow!("Align is required"))?.sha3_hash(hasher)?; - memarg.max_align.ok_or_else(|| anyhow!("Max align is required"))?.sha3_hash(hasher)?; - memarg.offset.ok_or_else(|| anyhow!("Offset is required"))?.sha3_hash(hasher)?; - memarg.memory.ok_or_else(|| anyhow!("Memory is required"))?.sha3_hash(hasher)?; + memarg.align.context("Align is required")?.sha3_hash(hasher)?; + memarg.max_align.context("Max align is required")?.sha3_hash(hasher)?; + memarg.offset.context("Offset is required")?.sha3_hash(hasher)?; + memarg.memory.context("Memory is required")?.sha3_hash(hasher)?; }, "Mem arg is required") } OpCode::MemorySize | OpCode::MemoryGrow => { @@ -403,8 +399,8 @@ impl Sha3Hash for Operator { } OpCode::BulkMemoryExtMemoryInit => { some!(operator, MemoryInit(memory_init) => { - memory_init.data_index.ok_or_else(|| anyhow!("Data index is required"))?.sha3_hash(hasher)?; - memory_init.address.ok_or_else(|| anyhow!("Address is required"))?.sha3_hash(hasher)?; + memory_init.data_index.context("Data index is required")?.sha3_hash(hasher)?; + memory_init.address.context("Address is required")?.sha3_hash(hasher)?; }, "Data index and address are required") } OpCode::BulkMemoryExtDataDrop => { @@ -414,8 +410,8 @@ impl Sha3Hash for Operator { } OpCode::BulkMemoryExtMemoryCopy => { some!(operator, MemoryCopy(memory_copy) => { - memory_copy.destination_address.ok_or_else(|| anyhow!("Destination address is required"))?.sha3_hash(hasher)?; - memory_copy.source_address.ok_or_else(|| anyhow!("Source address is required"))?.sha3_hash(hasher)?; + memory_copy.destination_address.context("Destination address is required")?.sha3_hash(hasher)?; + memory_copy.source_address.context("Source address is required")?.sha3_hash(hasher)?; }, "Destination address and source address are required") } OpCode::BulkMemoryExtMemoryFill => { @@ -425,8 +421,8 @@ impl Sha3Hash for Operator { } OpCode::BulkMemoryExtTableInit => { some!(operator, TableInit(table_init) => { - table_init.element_index.ok_or_else(|| anyhow!("Element index is required"))?.sha3_hash(hasher)?; - table_init.table.ok_or_else(|| anyhow!("Table is required"))?.sha3_hash(hasher)?; + table_init.element_index.context("Element index is required")?.sha3_hash(hasher)?; + table_init.table.context("Table is required")?.sha3_hash(hasher)?; }, "Table index is required") } OpCode::BulkMemoryExtElemDrop => { @@ -436,13 +432,13 @@ impl Sha3Hash for Operator { } OpCode::BulkMemoryExtTableCopy => { some!(operator, TableCopy(table_copy) => { - table_copy.dst_table.ok_or_else(|| anyhow!("Dst table is required"))?.sha3_hash(hasher)?; - table_copy.src_table.ok_or_else(|| anyhow!("Src table is required"))?.sha3_hash(hasher)?; + table_copy.dst_table.context("Dst table is required")?.sha3_hash(hasher)?; + table_copy.src_table.context("Src table is required")?.sha3_hash(hasher)?; }, "Dst table and src table are required") } OpCode::ExceptionsExtTryTable => { some!(operator, TryTable(try_table) => { - try_table.r#type.ok_or_else(|| anyhow!("Block type is required"))?.block_type.ok_or_else(|| anyhow!("Block type is required"))?.sha3_hash(hasher)?; + try_table.r#type.context("Block type is required")?.block_type.context("Block type is required")?.sha3_hash(hasher)?; try_table.catches.sha3_hash(hasher)?; }, "Type index and catches are required") } @@ -460,10 +456,10 @@ impl Sha3Hash for Operator { impl Sha3Hash for Version { fn sha3_hash(&self, hasher: &mut D) -> Result<()> { self.number - .ok_or_else(|| anyhow!("Version number is required"))? + .context("Version number is required")? .sha3_hash(hasher)?; self.encoding - .ok_or_else(|| anyhow!("Version encoding is required"))? + .context("Version encoding is required")? .sha3_hash(hasher)?; Ok(()) } @@ -499,14 +495,14 @@ impl Sha3Hash for TypeRefFunc { // let module = self // .module // .as_ref() - // .ok_or_else(|| anyhow!("Module is required"))?; + // .context("Module is required")?; // let name = self // .name // .as_ref() - // .ok_or_else(|| anyhow!("Name is required"))?; + // .context("Name is required")?; // let function_type = self // .function_type - // .ok_or_else(|| anyhow!("Function type is required"))?; + // .context("Function type is required")?; //TODO Ok(()) } @@ -522,10 +518,10 @@ impl Sha3Hash for ImportSection { impl Sha3Hash for ProgramModule { fn sha3_hash(&self, hasher: &mut D) -> Result<()> { self.protocol_version - .ok_or_else(|| anyhow!("Protocol version is required"))? + .context("Protocol version is required")? .sha3_hash(hasher)?; self.version - .ok_or_else(|| anyhow!("Version is required"))? + .context("Version is required")? .sha3_hash(hasher)?; // pub import_section: ::core::option::Option, From 10e8fe1eb209c3045b60767e462e9aa301a74c09 Mon Sep 17 00:00:00 2001 From: Alexander Guryanov Date: Thu, 26 Feb 2026 11:48:52 +0300 Subject: [PATCH 09/15] Rename function_type to type_index in BlockType --- proto | 2 +- src/program.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/proto b/proto index 310a23e..e8544d9 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 310a23ed4fdff91921dca047e237e384566459e6 +Subproject commit e8544d9d26f489e9b1db197e831f4cb1ab8fd611 diff --git a/src/program.rs b/src/program.rs index e5e8d47..d640cc8 100644 --- a/src/program.rs +++ b/src/program.rs @@ -90,7 +90,7 @@ impl Sha3Hash for wasm::block_type::BlockType { match self { wasm::block_type::BlockType::Empty(_) => hasher.update([0]), wasm::block_type::BlockType::ValueType(vt) => vt.sha3_hash(hasher)?, - wasm::block_type::BlockType::FunctionType(v) => v.sha3_hash(hasher)?, + wasm::block_type::BlockType::TypeIndex(v) => v.sha3_hash(hasher)?, }; Ok(()) } @@ -830,7 +830,7 @@ mod tests { let op = Operator { opcode: Some(OpCode::Block as i32), operator: Some(OperatorVariant::BlockType(wasm::BlockType { - block_type: Some(block_type::BlockType::FunctionType(110)), + block_type: Some(block_type::BlockType::TypeIndex(110)), })), }; hash_eq!( From fc3c8f91d65e72b15defc8f814f75f6156eaf0db Mon Sep 17 00:00:00 2001 From: Alexander Guryanov Date: Thu, 26 Feb 2026 11:58:05 +0300 Subject: [PATCH 10/15] Implement AsScalar for Program --- src/data.rs | 39 +++++++++++++++++++++++++++++++-------- src/program.rs | 13 +++++++++++++ 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/data.rs b/src/data.rs index 2a22b60..7989ad5 100644 --- a/src/data.rs +++ b/src/data.rs @@ -257,6 +257,23 @@ impl AccountProof { #[cfg_attr(test, derive(PartialEq))] pub struct Program { module: Option, + hash: Scalar, +} + +impl Program { + pub fn new(module: libernet::wasm::ProgramModule) -> Self { + let hash = module.as_scalar(); + Self { + module: Some(module), + hash, + } + } +} + +impl AsScalar for Program { + fn as_scalar(&self) -> Scalar { + self.hash + } } impl proto::EncodeToAny for Program { @@ -269,8 +286,10 @@ impl proto::EncodeToAny for Program { impl proto::DecodeFromAny for Program { fn decode_from_any(proto: &prost_types::Any) -> Result { let proto = proto.to_msg::()?; + let hash = proto.as_scalar(); Ok(Self { module: Some(proto), + hash, }) } } @@ -521,6 +540,7 @@ mod tests { use super::*; use crate::proto::{DecodeFromAny, EncodeToAny}; use crate::testing::parse_scalar; + use core::hash; use crypto::utils; use std::time::Duration; @@ -898,15 +918,18 @@ mod tests { #[test] fn test_encode_decode_empty_program() { - let program = Program { - module: Some(libernet::wasm::ProgramModule { - protocol_version: Some(1), - version: Some(libernet::wasm::Version { - number: Some(1), - encoding: Some(libernet::wasm::Encoding::Module as i32), - }), - ..Default::default() + let module = libernet::wasm::ProgramModule { + protocol_version: Some(1), + version: Some(libernet::wasm::Version { + number: Some(1), + encoding: Some(libernet::wasm::Encoding::Module as i32), }), + ..Default::default() + }; + let hash = module.as_scalar(); + let program = Program { + module: Some(module), + hash, }; let proto = program.encode_to_any().unwrap(); let decoded = Program::decode_from_any(&proto).unwrap(); diff --git a/src/program.rs b/src/program.rs index d640cc8..df7c36e 100644 --- a/src/program.rs +++ b/src/program.rs @@ -4,6 +4,10 @@ use crate::libernet::wasm::{ }; use crate::libernet::wasm::{OpCode, Operator, operator::Operator::*}; use anyhow::{Context, Result, anyhow, bail}; +use blstrs::Scalar; +use crypto::merkle::AsScalar; +use crypto::utils::{self, h512_to_scalar}; +use primitive_types::H512; use sha3::Digest; macro_rules! some { @@ -558,6 +562,15 @@ impl Sha3Hash for ProgramModule { } } +impl AsScalar for ProgramModule { + fn as_scalar(&self) -> Scalar { + let mut hasher = sha3::Sha3_512::new(); + self.sha3_hash(&mut hasher).unwrap(); + let hash = hasher.finalize(); + h512_to_scalar(H512::from_slice(hash.as_slice())) + } +} + #[cfg(test)] mod tests { use super::*; From 0f72e948e75214622aa6473494446f47c98bfecb Mon Sep 17 00:00:00 2001 From: Alexander Guryanov Date: Thu, 26 Feb 2026 12:28:19 +0300 Subject: [PATCH 11/15] Implement hashers for some sections --- src/program.rs | 340 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 310 insertions(+), 30 deletions(-) diff --git a/src/program.rs b/src/program.rs index df7c36e..f61e678 100644 --- a/src/program.rs +++ b/src/program.rs @@ -1,12 +1,14 @@ use crate::libernet::wasm::{ - self, CatchElement, FuncType, ImportSection, PlainType, ProgramModule, RefType, SubType, - TypeRefFunc, TypeSection, Version, + self, CatchElement, ElementSection, Export, ExportSection, FuncType, FunctionSection, Global, + GlobalSection, GlobalType, ImportSection, MemorySection, MemoryType, PlainType, ProgramModule, + RefType, SubType, TableSection, TableType, TagSection, TagType, TypeRefFunc, TypeSection, + Version, }; use crate::libernet::wasm::{OpCode, Operator, operator::Operator::*}; use anyhow::{Context, Result, anyhow, bail}; use blstrs::Scalar; use crypto::merkle::AsScalar; -use crypto::utils::{self, h512_to_scalar}; +use crypto::utils::h512_to_scalar; use primitive_types::H512; use sha3::Digest; @@ -51,6 +53,34 @@ impl Sha3Hash for i64 { } } +impl Sha3Hash for String { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + (self.len() as u64).sha3_hash(hasher)?; + hasher.update(self.as_bytes()); + Ok(()) + } +} + +impl Sha3Hash for bool { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + hasher.update([if *self { 1 } else { 0 }]); + Ok(()) + } +} + +impl Sha3Hash for Option { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + match self { + Some(v) => { + hasher.update([1, 1]); + v.sha3_hash(hasher)?; + } + None => hasher.update([1, 0]), + } + Ok(()) + } +} + impl Sha3Hash for Vec { fn sha3_hash(&self, hasher: &mut D) -> Result<()> { (self.len() as u64).sha3_hash(hasher)?; @@ -495,19 +525,18 @@ impl Sha3Hash for TypeSection { } impl Sha3Hash for TypeRefFunc { - fn sha3_hash(&self, _hasher: &mut D) -> Result<()> { - // let module = self - // .module - // .as_ref() - // .context("Module is required")?; - // let name = self - // .name - // .as_ref() - // .context("Name is required")?; - // let function_type = self - // .function_type - // .context("Function type is required")?; - //TODO + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.module + .as_ref() + .context("Module is required")? + .sha3_hash(hasher)?; + self.name + .as_ref() + .context("Name is required")? + .sha3_hash(hasher)?; + self.function_type + .context("Function type is required")? + .sha3_hash(hasher)?; Ok(()) } } @@ -519,6 +548,131 @@ impl Sha3Hash for ImportSection { } } +impl Sha3Hash for FunctionSection { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.type_idxs.sha3_hash(hasher)?; + Ok(()) + } +} + +impl Sha3Hash for TableSection { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.types.sha3_hash(hasher)?; + Ok(()) + } +} + +impl Sha3Hash for TableType { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.reference_type + .context("Reference type is required")? + .sha3_hash(hasher)?; + self.table64 + .context("Table64 is required")? + .sha3_hash(hasher)?; + self.initial + .context("Initial is required")? + .sha3_hash(hasher)?; + self.maximum.sha3_hash(hasher)?; + self.shared + .context("Shared is required")? + .sha3_hash(hasher)?; + Ok(()) + } +} + +impl Sha3Hash for MemorySection { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.memory_types.sha3_hash(hasher)?; + Ok(()) + } +} + +impl Sha3Hash for MemoryType { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.memory64 + .context("Memory64 is required")? + .sha3_hash(hasher)?; + self.shared + .context("Shared is required")? + .sha3_hash(hasher)?; + self.initial + .context("Initial is required")? + .sha3_hash(hasher)?; + self.maximum.sha3_hash(hasher)?; + self.page_size_log2.sha3_hash(hasher)?; + Ok(()) + } +} + +impl Sha3Hash for TagSection { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.tags.sha3_hash(hasher)?; + Ok(()) + } +} + +impl Sha3Hash for TagType { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.kind.sha3_hash(hasher)?; + self.function_type_idx.sha3_hash(hasher)?; + Ok(()) + } +} + +impl Sha3Hash for GlobalSection { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.globals.sha3_hash(hasher)?; + Ok(()) + } +} + +impl Sha3Hash for Global { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.r#type.context("Type is required")?.sha3_hash(hasher)?; + self.init_expr + .as_ref() + .context("Init expr is required")? + .operators + .sha3_hash(hasher)?; + Ok(()) + } +} + +impl Sha3Hash for GlobalType { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.content_type + .context("Content type is required")? + .sha3_hash(hasher)?; + self.mutable + .context("Mutable is required")? + .sha3_hash(hasher)?; + self.shared + .context("Shared is required")? + .sha3_hash(hasher)?; + Ok(()) + } +} + +impl Sha3Hash for ExportSection { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.exports.sha3_hash(hasher)?; + Ok(()) + } +} + +impl Sha3Hash for Export { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.name + .as_ref() + .context("Name is required")? + .sha3_hash(hasher)?; + self.kind.context("Kind is required")?.sha3_hash(hasher)?; + self.index.context("Index is required")?.sha3_hash(hasher)?; + Ok(()) + } +} + impl Sha3Hash for ProgramModule { fn sha3_hash(&self, hasher: &mut D) -> Result<()> { self.protocol_version @@ -528,19 +682,6 @@ impl Sha3Hash for ProgramModule { .context("Version is required")? .sha3_hash(hasher)?; - // pub import_section: ::core::option::Option, - // #[prost(message, optional, tag = "5")] - // pub function_section: ::core::option::Option, - // #[prost(message, optional, tag = "6")] - // pub table_section: ::core::option::Option, - // #[prost(message, optional, tag = "7")] - // pub memory_section: ::core::option::Option, - // #[prost(message, optional, tag = "8")] - // pub tag_section: ::core::option::Option, - // #[prost(message, optional, tag = "9")] - // pub global_section: ::core::option::Option, - // #[prost(message, optional, tag = "10")] - // pub export_section: ::core::option::Option, // #[prost(message, optional, tag = "11")] // pub element_section: ::core::option::Option, // #[prost(message, optional, tag = "12")] @@ -558,6 +699,13 @@ impl Sha3Hash for ProgramModule { } hash_section!(self.type_section); + hash_section!(self.import_section); + hash_section!(self.function_section); + hash_section!(self.table_section); + hash_section!(self.memory_section); + hash_section!(self.tag_section); + hash_section!(self.global_section); + hash_section!(self.export_section); Ok(()) } } @@ -577,7 +725,8 @@ mod tests { use crate::libernet::wasm::operator::Operator as OperatorVariant; use crate::libernet::wasm::{ BreakTargets, CallIndirectOp, CatchAllElements, CatchAllRef, CatchElement, CatchOne, - CatchOneRef, MemArg, ValueType, block_type, catch_element, + CatchOneRef, Expression, Global, GlobalSection, GlobalType, MemArg, MemoryType, RefType, + TableType, TagKind, TagType, TypeRefFunc, ValueType, block_type, catch_element, }; use crate::libernet::wasm::{OpCode, Operator}; @@ -650,6 +799,26 @@ mod tests { hash_eq!([42i64], [42i64.to_le_bytes()]); } + #[test] + fn test_bool_to_hash() { + hash_eq!([true], [&[1]]); + hash_eq!([false], [&[0]]); + } + + #[test] + fn test_option_to_hash() { + hash_eq!([Some(42u32)], [&[1, 1], &42u32.to_le_bytes()[..]]); + hash_eq!([None::], [&[1, 0]]); + } + + #[test] + fn test_string_to_hash() { + hash_eq!( + ["hello".to_string()], + [&("hello".len() as u64).to_le_bytes()[..], &b"hello"[..]] + ); + } + #[test] fn test_vec_to_hash() { hash_eq!([Vec::::new()], [0u64.to_le_bytes()]); @@ -692,6 +861,68 @@ mod tests { hash_err!(vt); } + #[test] + fn test_table_type_to_hash() { + let table = TableType { + reference_type: Some(RefType::RefFunc as i32), + table64: Some(true), + initial: Some(100), + maximum: Some(200), + shared: Some(false), + }; + hash_eq!( + [table], + [ + &(RefType::RefFunc as i32).to_le_bytes()[..], + &[1], + &100u64.to_le_bytes()[..], + &[1, 1], + &200u64.to_le_bytes()[..], + &[0] + ] + ); + } + + #[test] + fn test_memory_type_to_hash() { + let memory = MemoryType { + memory64: Some(true), + shared: Some(false), + initial: Some(1), + maximum: Some(256), + page_size_log2: Some(16), + }; + hash_eq!( + [memory], + [ + &[1], + &[0], + &1u64.to_le_bytes()[..], + &[1, 1], + &256u64.to_le_bytes()[..], + &[1, 1], + &16u32.to_le_bytes()[..] + ] + ); + } + + #[test] + fn test_tag_type_to_hash() { + let tag = TagType { + kind: Some(TagKind::Exception as i32), + function_type_idx: Some(42), + }; + hash_eq!( + [tag], + [ + &[1, 1], + &(TagKind::Exception as i32).to_le_bytes()[..], + &[1, 1], + &42u32.to_le_bytes()[..] + ] + ); + } + #[test] fn test_catch_element_one_to_hash() { let ce = CatchElement { @@ -934,6 +1165,55 @@ mod tests { ); } + #[test] + fn test_type_ref_func_to_hash() { + let trf = TypeRefFunc { + module: Some("env".to_string()), + name: Some("foo".to_string()), + function_type: Some(42), + }; + hash_eq!( + [trf], + [ + &3u64.to_le_bytes()[..], + b"env", + &3u64.to_le_bytes()[..], + b"foo", + &42u32.to_le_bytes()[..] + ] + ); + } + + #[test] + fn test_type_ref_func_missing_module_fails() { + let trf = TypeRefFunc { + module: None, + name: Some("foo".to_string()), + function_type: Some(42), + }; + hash_err!(trf); + } + + #[test] + fn test_type_ref_func_missing_name_fails() { + let trf = TypeRefFunc { + module: Some("env".to_string()), + name: None, + function_type: Some(42), + }; + hash_err!(trf); + } + + #[test] + fn test_type_ref_func_missing_function_type_fails() { + let trf = TypeRefFunc { + module: Some("env".to_string()), + name: Some("foo".to_string()), + function_type: None, + }; + hash_err!(trf); + } + #[test] fn test_operator_hash_deterministic() { let op = Operator { From 93fbaed5a7fd7aee86c21245340da52efd1bd47b Mon Sep 17 00:00:00 2001 From: Alexander Guryanov Date: Thu, 26 Feb 2026 16:47:42 +0300 Subject: [PATCH 12/15] cover all sections with hash --- src/program.rs | 161 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 144 insertions(+), 17 deletions(-) diff --git a/src/program.rs b/src/program.rs index f61e678..e736d9d 100644 --- a/src/program.rs +++ b/src/program.rs @@ -1,8 +1,10 @@ +use crate::libernet::wasm::element::Items; use crate::libernet::wasm::{ - self, CatchElement, ElementSection, Export, ExportSection, FuncType, FunctionSection, Global, - GlobalSection, GlobalType, ImportSection, MemorySection, MemoryType, PlainType, ProgramModule, - RefType, SubType, TableSection, TableType, TagSection, TagType, TypeRefFunc, TypeSection, - Version, + self, CatchElement, CodeSection, CodeSectionEntry, Data, DataKind, DataSection, Element, + ElementExpressions, ElementFunctions, ElementKind, ElementSection, Export, ExportSection, + Expression, FuncType, FunctionSection, Global, GlobalSection, GlobalType, ImportSection, + Locals, MemorySection, MemoryType, PlainType, ProgramModule, RefType, SubType, TableSection, + TableType, TagSection, TagType, TypeRefFunc, TypeSection, Version, }; use crate::libernet::wasm::{OpCode, Operator, operator::Operator::*}; use anyhow::{Context, Result, anyhow, bail}; @@ -68,6 +70,14 @@ impl Sha3Hash for bool { } } +impl Sha3Hash for Vec { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + (self.len() as u64).sha3_hash(hasher)?; + hasher.update(self); + Ok(()) + } +} + impl Sha3Hash for Option { fn sha3_hash(&self, hasher: &mut D) -> Result<()> { match self { @@ -630,11 +640,14 @@ impl Sha3Hash for GlobalSection { impl Sha3Hash for Global { fn sha3_hash(&self, hasher: &mut D) -> Result<()> { self.r#type.context("Type is required")?.sha3_hash(hasher)?; - self.init_expr - .as_ref() - .context("Init expr is required")? - .operators - .sha3_hash(hasher)?; + self.init_expr.sha3_hash(hasher)?; + Ok(()) + } +} + +impl Sha3Hash for Expression { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.operators.sha3_hash(hasher)?; Ok(()) } } @@ -673,6 +686,117 @@ impl Sha3Hash for Export { } } +impl Sha3Hash for ElementSection { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.elements.sha3_hash(hasher)?; + Ok(()) + } +} +impl Sha3Hash for Element { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.kind + .as_ref() + .context("Kind is required")? + .sha3_hash(hasher)?; + self.items.sha3_hash(hasher)?; + Ok(()) + } +} + +impl Sha3Hash for ElementKind { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.r#type.context("Type is required")?.sha3_hash(hasher)?; + self.table_index.sha3_hash(hasher)?; + self.expression.sha3_hash(hasher)?; + Ok(()) + } +} + +impl Sha3Hash for Items { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + match self { + Items::Functions(functions) => functions.sha3_hash(hasher)?, + Items::Expressions(expressions) => expressions.sha3_hash(hasher)?, + } + Ok(()) + } +} + +impl Sha3Hash for ElementFunctions { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.functions.sha3_hash(hasher)?; + Ok(()) + } +} + +impl Sha3Hash for ElementExpressions { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.reference_type + .context("Reference type is required")? + .sha3_hash(hasher)?; + self.expressions.sha3_hash(hasher)?; + Ok(()) + } +} + +impl Sha3Hash for CodeSection { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.code_section_entry.sha3_hash(hasher)?; + Ok(()) + } +} + +impl Sha3Hash for CodeSectionEntry { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.locals.sha3_hash(hasher)?; + self.body.sha3_hash(hasher)?; + Ok(()) + } +} + +impl Sha3Hash for Locals { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.count.context("Count is required")?.sha3_hash(hasher)?; + self.value_type + .context("Value type is required")? + .sha3_hash(hasher)?; + Ok(()) + } +} + +impl Sha3Hash for DataSection { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.datas.sha3_hash(hasher)?; + Ok(()) + } +} + +impl Sha3Hash for Data { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.kind + .as_ref() + .context("Kind is required")? + .sha3_hash(hasher)?; + self.data + .as_ref() + .context("Data is required")? + .sha3_hash(hasher)?; + Ok(()) + } +} + +impl Sha3Hash for DataKind { + fn sha3_hash(&self, hasher: &mut D) -> Result<()> { + self.r#type + .as_ref() + .context("Type is required")? + .sha3_hash(hasher)?; + self.memory_index.sha3_hash(hasher)?; + self.expression.sha3_hash(hasher)?; + Ok(()) + } +} + impl Sha3Hash for ProgramModule { fn sha3_hash(&self, hasher: &mut D) -> Result<()> { self.protocol_version @@ -682,13 +806,6 @@ impl Sha3Hash for ProgramModule { .context("Version is required")? .sha3_hash(hasher)?; - // #[prost(message, optional, tag = "11")] - // pub element_section: ::core::option::Option, - // #[prost(message, optional, tag = "12")] - // pub code_section: ::core::option::Option, - // #[prost(message, optional, tag = "13")] - // pub data_section: ::core::option::Option, - macro_rules! hash_section { ($opt:expr $(,)?) => {{ match &$opt { @@ -706,6 +823,9 @@ impl Sha3Hash for ProgramModule { hash_section!(self.tag_section); hash_section!(self.global_section); hash_section!(self.export_section); + hash_section!(self.element_section); + hash_section!(self.code_section); + hash_section!(self.data_section); Ok(()) } } @@ -725,7 +845,7 @@ mod tests { use crate::libernet::wasm::operator::Operator as OperatorVariant; use crate::libernet::wasm::{ BreakTargets, CallIndirectOp, CatchAllElements, CatchAllRef, CatchElement, CatchOne, - CatchOneRef, Expression, Global, GlobalSection, GlobalType, MemArg, MemoryType, RefType, + CatchOneRef, MemArg, MemoryType, RefType, TableType, TagKind, TagType, TypeRefFunc, ValueType, block_type, catch_element, }; use crate::libernet::wasm::{OpCode, Operator}; @@ -818,6 +938,13 @@ mod tests { [&("hello".len() as u64).to_le_bytes()[..], &b"hello"[..]] ); } + #[test] + fn test_bytes_to_hash() { + hash_eq!( + [b"hello".to_vec()], + [&("hello".len() as u64).to_le_bytes()[..], &b"hello"[..]] + ); + } #[test] fn test_vec_to_hash() { From 5db2d2f8e089c0c8688c75de19800c68333766e3 Mon Sep 17 00:00:00 2001 From: Alexander Guryanov Date: Thu, 26 Feb 2026 17:35:05 +0300 Subject: [PATCH 13/15] add more unit tests --- src/program.rs | 813 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 810 insertions(+), 3 deletions(-) diff --git a/src/program.rs b/src/program.rs index e736d9d..cacea9f 100644 --- a/src/program.rs +++ b/src/program.rs @@ -520,7 +520,10 @@ impl Sha3Hash for FuncType { impl Sha3Hash for SubType { fn sha3_hash(&self, hasher: &mut D) -> Result<()> { match &self.kind { - Some(wasm::sub_type::Kind::Func(func_type)) => func_type.sha3_hash(hasher)?, + Some(wasm::sub_type::Kind::Func(func_type)) => { + hasher.update([1]); + func_type.sha3_hash(hasher)?; + } _ => bail!("Sub type kind is required"), } Ok(()) @@ -845,8 +848,8 @@ mod tests { use crate::libernet::wasm::operator::Operator as OperatorVariant; use crate::libernet::wasm::{ BreakTargets, CallIndirectOp, CatchAllElements, CatchAllRef, CatchElement, CatchOne, - CatchOneRef, MemArg, MemoryType, RefType, - TableType, TagKind, TagType, TypeRefFunc, ValueType, block_type, catch_element, + CatchOneRef, DataKindType, ElementKindType, Encoding, ExternalKind, MemArg, MemoryType, + RefType, TableType, TagKind, TagType, TypeRefFunc, ValueType, block_type, catch_element, }; use crate::libernet::wasm::{OpCode, Operator}; @@ -1387,4 +1390,808 @@ mod tests { op2.sha3_hash(&mut hasher2).unwrap(); assert_eq!(hasher1.finalize(), hasher2.finalize()); } + + #[test] + fn test_value_type_ref_to_hash() { + hash_eq!( + [ValueType { + value_type: Some(PlainType::ValueTypeRef as i32), + reference_type: Some(RefType::RefFunc as i32), + }], + [ + &(PlainType::ValueTypeRef as i32).to_le_bytes()[..], + &(RefType::RefFunc as i32).to_le_bytes()[..] + ] + ); + } + + #[test] + fn test_value_type_ref_missing_reference_type_fails() { + let vt = ValueType { + value_type: Some(PlainType::ValueTypeRef as i32), + reference_type: None, + }; + hash_err!(vt); + } + + #[test] + fn test_version_to_hash() { + let version = Version { + number: Some(1), + encoding: Some(Encoding::Module as i32), + }; + hash_eq!( + [version], + [ + &1u32.to_le_bytes()[..], + &(Encoding::Module as i32).to_le_bytes()[..] + ] + ); + } + + #[test] + fn test_version_missing_number_fails() { + let v = Version { + number: None, + encoding: Some(Encoding::Module as i32), + }; + hash_err!(v); + } + + #[test] + fn test_version_missing_encoding_fails() { + let v = Version { + number: Some(1), + encoding: None, + }; + hash_err!(v); + } + + #[test] + fn test_func_type_empty_to_hash() { + let ft = FuncType { + params: vec![], + results: vec![], + }; + hash_eq!([ft], [0u64.to_le_bytes(), 0u64.to_le_bytes()]); + } + + #[test] + fn test_func_type_with_params_and_results_to_hash() { + let i32_vt = ValueType { + value_type: Some(PlainType::ValueTypeI32 as i32), + reference_type: None, + }; + let ft = FuncType { + params: vec![i32_vt], + results: vec![i32_vt], + }; + hash_eq!( + [ft], + [ + &1u64.to_le_bytes()[..], + &(PlainType::ValueTypeI32 as i32).to_le_bytes()[..], + &[0], + &1u64.to_le_bytes()[..], + &(PlainType::ValueTypeI32 as i32).to_le_bytes()[..], + &[0] + ] + ); + } + + #[test] + fn test_sub_type_func_to_hash() { + let ft = FuncType { + params: vec![], + results: vec![], + }; + let st = SubType { + kind: Some(wasm::sub_type::Kind::Func(ft)), + }; + hash_eq!( + [st], + [&[1], &0u64.to_le_bytes()[..], &0u64.to_le_bytes()[..]] + ); + } + + #[test] + fn test_sub_type_missing_kind_fails() { + let st = SubType { kind: None }; + hash_err!(st); + } + + #[test] + fn test_type_section_empty_to_hash() { + let ts = TypeSection { types: vec![] }; + hash_eq!([ts], [0u64.to_le_bytes()]); + } + + #[test] + fn test_type_section_with_types_to_hash() { + let ft = FuncType { + params: vec![], + results: vec![], + }; + let st = SubType { + kind: Some(wasm::sub_type::Kind::Func(ft)), + }; + let ts = TypeSection { types: vec![st] }; + hash_eq!( + [ts], + [ + &1u64.to_le_bytes()[..], + &[1], + &0u64.to_le_bytes()[..], + &0u64.to_le_bytes()[..] + ] + ); + } + + #[test] + fn test_import_section_empty_to_hash() { + let is = ImportSection { imports: vec![] }; + hash_eq!([is], [0u64.to_le_bytes()]); + } + + #[test] + fn test_function_section_to_hash() { + let fs = FunctionSection { + type_idxs: vec![0, 1], + }; + hash_eq!( + [fs], + [ + &2u64.to_le_bytes()[..], + &0u32.to_le_bytes()[..], + &1u32.to_le_bytes()[..] + ] + ); + } + + #[test] + fn test_function_section_empty_to_hash() { + let fs = FunctionSection { type_idxs: vec![] }; + hash_eq!([fs], [0u64.to_le_bytes()]); + } + + #[test] + fn test_table_section_empty_to_hash() { + let ts = TableSection { types: vec![] }; + hash_eq!([ts], [0u64.to_le_bytes()]); + } + + #[test] + fn test_memory_section_empty_to_hash() { + let ms = MemorySection { + memory_types: vec![], + }; + hash_eq!([ms], [0u64.to_le_bytes()]); + } + + #[test] + fn test_tag_section_empty_to_hash() { + let ts = TagSection { tags: vec![] }; + hash_eq!([ts], [0u64.to_le_bytes()]); + } + + #[test] + fn test_global_type_to_hash() { + let i32_vt = ValueType { + value_type: Some(PlainType::ValueTypeI32 as i32), + reference_type: None, + }; + let gt = GlobalType { + content_type: Some(i32_vt), + mutable: Some(true), + shared: Some(false), + }; + hash_eq!( + [gt], + [ + &(PlainType::ValueTypeI32 as i32).to_le_bytes()[..], + &[0], + &[1], + &[0] + ] + ); + } + + #[test] + fn test_global_type_missing_content_type_fails() { + let gt = GlobalType { + content_type: None, + mutable: Some(true), + shared: Some(false), + }; + hash_err!(gt); + } + + #[test] + fn test_global_type_missing_mutable_fails() { + let i32_vt = ValueType { + value_type: Some(PlainType::ValueTypeI32 as i32), + reference_type: None, + }; + let gt = GlobalType { + content_type: Some(i32_vt), + mutable: None, + shared: Some(false), + }; + hash_err!(gt); + } + + #[test] + fn test_global_type_missing_shared_fails() { + let i32_vt = ValueType { + value_type: Some(PlainType::ValueTypeI32 as i32), + reference_type: None, + }; + let gt = GlobalType { + content_type: Some(i32_vt), + mutable: Some(true), + shared: None, + }; + hash_err!(gt); + } + + #[test] + fn test_expression_empty_to_hash() { + let expr = Expression { operators: vec![] }; + hash_eq!([expr], [0u64.to_le_bytes()]); + } + + #[test] + fn test_expression_with_operators_to_hash() { + let nop = Operator { + opcode: Some(OpCode::Nop as i32), + operator: None, + }; + let expr = Expression { + operators: vec![nop], + }; + hash_eq!( + [expr], + [ + &1u64.to_le_bytes()[..], + &(OpCode::Nop as i32).to_le_bytes()[..], + &[0] + ] + ); + } + + #[test] + fn test_global_to_hash() { + let i32_vt = ValueType { + value_type: Some(PlainType::ValueTypeI32 as i32), + reference_type: None, + }; + let gt = GlobalType { + content_type: Some(i32_vt), + mutable: Some(false), + shared: Some(false), + }; + let init = Expression { operators: vec![] }; + let g = Global { + r#type: Some(gt), + init_expr: Some(init), + }; + hash_eq!( + [g], + [ + &(PlainType::ValueTypeI32 as i32).to_le_bytes()[..], + &[0], + &[0], + &[0], + &[1, 1], + &0u64.to_le_bytes()[..] + ] + ); + } + + #[test] + fn test_global_missing_type_fails() { + let g = Global { + r#type: None, + init_expr: Some(Expression { operators: vec![] }), + }; + hash_err!(g); + } + + #[test] + fn test_global_section_empty_to_hash() { + let gs = GlobalSection { globals: vec![] }; + hash_eq!([gs], [0u64.to_le_bytes()]); + } + + #[test] + fn test_export_to_hash() { + let e = Export { + name: Some("foo".to_string()), + kind: Some(ExternalKind::ExtFunc as i32), + index: Some(0), + }; + hash_eq!( + [e], + [ + &3u64.to_le_bytes()[..], + b"foo", + &(ExternalKind::ExtFunc as i32).to_le_bytes()[..], + &0u32.to_le_bytes()[..] + ] + ); + } + + #[test] + fn test_export_missing_name_fails() { + let e = Export { + name: None, + kind: Some(ExternalKind::ExtFunc as i32), + index: Some(0), + }; + hash_err!(e); + } + + #[test] + fn test_export_missing_kind_fails() { + let e = Export { + name: Some("foo".to_string()), + kind: None, + index: Some(0), + }; + hash_err!(e); + } + + #[test] + fn test_export_missing_index_fails() { + let e = Export { + name: Some("foo".to_string()), + kind: Some(ExternalKind::ExtFunc as i32), + index: None, + }; + hash_err!(e); + } + + #[test] + fn test_export_section_empty_to_hash() { + let es = ExportSection { exports: vec![] }; + hash_eq!([es], [0u64.to_le_bytes()]); + } + + #[test] + fn test_element_kind_active_to_hash() { + let ek = ElementKind { + r#type: Some(ElementKindType::ElActive as i32), + table_index: Some(0), + expression: Some(Expression { operators: vec![] }), + }; + hash_eq!( + [ek], + [ + &(ElementKindType::ElActive as i32).to_le_bytes()[..], + &[1, 1], + &0u32.to_le_bytes()[..], + &[1, 1], + &0u64.to_le_bytes()[..] + ] + ); + } + + #[test] + fn test_element_kind_passive_to_hash() { + let ek = ElementKind { + r#type: Some(ElementKindType::ElPassive as i32), + table_index: None, + expression: None, + }; + hash_eq!( + [ek], + [ + &(ElementKindType::ElPassive as i32).to_le_bytes()[..], + &[1, 0], + &[1, 0] + ] + ); + } + + #[test] + fn test_element_kind_missing_type_fails() { + let ek = ElementKind { + r#type: None, + table_index: None, + expression: None, + }; + hash_err!(ek); + } + + #[test] + fn test_element_functions_to_hash() { + let ef = ElementFunctions { + functions: vec![1, 2, 3], + }; + hash_eq!( + [ef], + [ + &3u64.to_le_bytes()[..], + &1u32.to_le_bytes()[..], + &2u32.to_le_bytes()[..], + &3u32.to_le_bytes()[..] + ] + ); + } + + #[test] + fn test_element_functions_empty_to_hash() { + let ef = ElementFunctions { functions: vec![] }; + hash_eq!([ef], [0u64.to_le_bytes()]); + } + + #[test] + fn test_element_expressions_to_hash() { + let ee = ElementExpressions { + reference_type: Some(RefType::RefFunc as i32), + expressions: vec![], + }; + hash_eq!( + [ee], + [ + &(RefType::RefFunc as i32).to_le_bytes()[..], + &0u64.to_le_bytes()[..] + ] + ); + } + + #[test] + fn test_element_expressions_missing_ref_type_fails() { + let ee = ElementExpressions { + reference_type: None, + expressions: vec![], + }; + hash_err!(ee); + } + + #[test] + fn test_items_functions_to_hash() { + let items = Items::Functions(ElementFunctions { functions: vec![1] }); + hash_eq!([items], [&1u64.to_le_bytes()[..], &1u32.to_le_bytes()[..]]); + } + + #[test] + fn test_items_expressions_to_hash() { + let items = Items::Expressions(ElementExpressions { + reference_type: Some(RefType::RefFunc as i32), + expressions: vec![], + }); + hash_eq!( + [items], + [ + &(RefType::RefFunc as i32).to_le_bytes()[..], + &0u64.to_le_bytes()[..] + ] + ); + } + + #[test] + fn test_element_to_hash() { + let ek = ElementKind { + r#type: Some(ElementKindType::ElPassive as i32), + table_index: None, + expression: None, + }; + let el = Element { + kind: Some(ek), + items: Some(Items::Functions(ElementFunctions { functions: vec![] })), + }; + hash_eq!( + [el], + [ + &(ElementKindType::ElPassive as i32).to_le_bytes()[..], + &[1, 0], + &[1, 0], + &[1, 1], + &0u64.to_le_bytes()[..] + ] + ); + } + + #[test] + fn test_element_none_items_to_hash() { + let ek = ElementKind { + r#type: Some(ElementKindType::ElPassive as i32), + table_index: None, + expression: None, + }; + let el = Element { + kind: Some(ek), + items: None, + }; + hash_eq!( + [el], + [ + &(ElementKindType::ElPassive as i32).to_le_bytes()[..], + &[1, 0], + &[1, 0], + &[1, 0] + ] + ); + } + + #[test] + fn test_element_missing_kind_fails() { + let el = Element { + kind: None, + items: None, + }; + hash_err!(el); + } + + #[test] + fn test_element_section_empty_to_hash() { + let es = ElementSection { elements: vec![] }; + hash_eq!([es], [0u64.to_le_bytes()]); + } + + #[test] + fn test_code_section_entry_empty_to_hash() { + let cse = CodeSectionEntry { + locals: vec![], + body: vec![], + }; + hash_eq!([cse], [0u64.to_le_bytes(), 0u64.to_le_bytes()]); + } + + #[test] + fn test_locals_to_hash() { + let i32_vt = ValueType { + value_type: Some(PlainType::ValueTypeI32 as i32), + reference_type: None, + }; + let l = Locals { + count: Some(3), + value_type: Some(i32_vt), + }; + hash_eq!( + [l], + [ + &3u32.to_le_bytes()[..], + &(PlainType::ValueTypeI32 as i32).to_le_bytes()[..], + &[0] + ] + ); + } + + #[test] + fn test_locals_missing_count_fails() { + let i32_vt = ValueType { + value_type: Some(PlainType::ValueTypeI32 as i32), + reference_type: None, + }; + let l = Locals { + count: None, + value_type: Some(i32_vt), + }; + hash_err!(l); + } + + #[test] + fn test_locals_missing_value_type_fails() { + let l = Locals { + count: Some(3), + value_type: None, + }; + hash_err!(l); + } + + #[test] + fn test_code_section_empty_to_hash() { + let cs = CodeSection { + code_section_entry: vec![], + }; + hash_eq!([cs], [0u64.to_le_bytes()]); + } + + #[test] + fn test_data_kind_passive_to_hash() { + let dk = DataKind { + r#type: Some(DataKindType::Passive as i32), + memory_index: None, + expression: None, + }; + hash_eq!( + [dk], + [ + &(DataKindType::Passive as i32).to_le_bytes()[..], + &[1, 0], + &[1, 0] + ] + ); + } + + #[test] + fn test_data_kind_active_to_hash() { + let dk = DataKind { + r#type: Some(DataKindType::Active as i32), + memory_index: Some(0), + expression: Some(Expression { operators: vec![] }), + }; + hash_eq!( + [dk], + [ + &(DataKindType::Active as i32).to_le_bytes()[..], + &[1, 1], + &0u32.to_le_bytes()[..], + &[1, 1], + &0u64.to_le_bytes()[..] + ] + ); + } + + #[test] + fn test_data_kind_missing_type_fails() { + let dk = DataKind { + r#type: None, + memory_index: None, + expression: None, + }; + hash_err!(dk); + } + + #[test] + fn test_data_to_hash() { + let dk = DataKind { + r#type: Some(DataKindType::Passive as i32), + memory_index: None, + expression: None, + }; + let d = Data { + kind: Some(dk), + data: Some(b"hi".to_vec()), + }; + hash_eq!( + [d], + [ + &(DataKindType::Passive as i32).to_le_bytes()[..], + &[1, 0], + &[1, 0], + &2u64.to_le_bytes()[..], + b"hi" + ] + ); + } + + #[test] + fn test_data_missing_kind_fails() { + let d = Data { + kind: None, + data: Some(b"hi".to_vec()), + }; + hash_err!(d); + } + + #[test] + fn test_data_missing_data_fails() { + let dk = DataKind { + r#type: Some(DataKindType::Passive as i32), + memory_index: None, + expression: None, + }; + let d = Data { + kind: Some(dk), + data: None, + }; + hash_err!(d); + } + + #[test] + fn test_data_section_empty_to_hash() { + let ds = DataSection { datas: vec![] }; + hash_eq!([ds], [0u64.to_le_bytes()]); + } + + #[test] + fn test_program_module_minimal_to_hash() { + let pm = ProgramModule { + protocol_version: Some(1), + version: Some(Version { + number: Some(1), + encoding: Some(Encoding::Module as i32), + }), + type_section: None, + import_section: None, + function_section: None, + table_section: None, + memory_section: None, + tag_section: None, + global_section: None, + export_section: None, + element_section: None, + code_section: None, + data_section: None, + }; + hash_eq!( + [pm], + [ + &1u32.to_le_bytes()[..], + &1u32.to_le_bytes()[..], + &(Encoding::Module as i32).to_le_bytes()[..], + &[0u8; 11][..] + ] + ); + } + + #[test] + fn test_program_module_missing_protocol_version_fails() { + let pm = ProgramModule { + protocol_version: None, + version: Some(Version { + number: Some(1), + encoding: Some(Encoding::Module as i32), + }), + type_section: None, + import_section: None, + function_section: None, + table_section: None, + memory_section: None, + tag_section: None, + global_section: None, + export_section: None, + element_section: None, + code_section: None, + data_section: None, + }; + hash_err!(pm); + } + + #[test] + fn test_program_module_missing_version_fails() { + let pm = ProgramModule { + protocol_version: Some(1), + version: None, + type_section: None, + import_section: None, + function_section: None, + table_section: None, + memory_section: None, + tag_section: None, + global_section: None, + export_section: None, + element_section: None, + code_section: None, + data_section: None, + }; + hash_err!(pm); + } + + #[test] + fn test_program_module_with_sections_to_hash() { + let pm = ProgramModule { + protocol_version: Some(1), + version: Some(Version { + number: Some(1), + encoding: Some(Encoding::Module as i32), + }), + type_section: Some(TypeSection { types: vec![] }), + import_section: Some(ImportSection { imports: vec![] }), + function_section: Some(FunctionSection { type_idxs: vec![] }), + table_section: None, + memory_section: None, + tag_section: None, + global_section: None, + export_section: None, + element_section: None, + code_section: None, + data_section: None, + }; + hash_eq!( + [pm], + [ + &1u32.to_le_bytes()[..], + &1u32.to_le_bytes()[..], + &(Encoding::Module as i32).to_le_bytes()[..], + &0u64.to_le_bytes()[..], + &0u64.to_le_bytes()[..], + &0u64.to_le_bytes()[..], + &[0u8; 8][..] + ] + ); + } } From 8c19becdc338c5b9fc21037d2b6663010b34ea29 Mon Sep 17 00:00:00 2001 From: Alexander Guryanov Date: Thu, 26 Feb 2026 22:06:59 +0300 Subject: [PATCH 14/15] refactor unit tests --- src/program.rs | 743 ++++++++++++++++++++----------------------------- 1 file changed, 297 insertions(+), 446 deletions(-) diff --git a/src/program.rs b/src/program.rs index cacea9f..70458e5 100644 --- a/src/program.rs +++ b/src/program.rs @@ -847,11 +847,10 @@ mod tests { use super::*; use crate::libernet::wasm::operator::Operator as OperatorVariant; use crate::libernet::wasm::{ - BreakTargets, CallIndirectOp, CatchAllElements, CatchAllRef, CatchElement, CatchOne, - CatchOneRef, DataKindType, ElementKindType, Encoding, ExternalKind, MemArg, MemoryType, - RefType, TableType, TagKind, TagType, TypeRefFunc, ValueType, block_type, catch_element, + BreakTargets, CallIndirectOp, CatchAllElements, CatchAllRef, CatchOne, CatchOneRef, + DataKindType, ElementKindType, Encoding, ExternalKind, MemArg, TagKind, ValueType, + block_type, catch_element, }; - use crate::libernet::wasm::{OpCode, Operator}; fn sha512, T: AsRef<[u8]>>(parts: I) -> String { let mut hasher = sha3::Sha3_512::new(); @@ -882,6 +881,44 @@ mod tests { }; } + fn i32_vt() -> ValueType { + ValueType { + value_type: Some(PlainType::ValueTypeI32 as i32), + reference_type: None, + } + } + + fn empty_expr() -> Expression { + Expression { operators: vec![] } + } + + fn default_version() -> Version { + Version { + number: Some(1), + encoding: Some(Encoding::Module as i32), + } + } + + fn default_pm() -> ProgramModule { + ProgramModule { + protocol_version: Some(1), + version: Some(default_version()), + type_section: None, + import_section: None, + function_section: None, + table_section: None, + memory_section: None, + tag_section: None, + global_section: None, + export_section: None, + element_section: None, + code_section: None, + data_section: None, + } + } + + // --- some! macro --- + #[test] fn test_some_macro_success() { let result: Result = (|| { @@ -902,6 +939,8 @@ mod tests { assert_eq!(result.unwrap_err().to_string(), "expected some"); } + // --- primitives --- + #[test] fn test_u32_to_hash() { hash_eq!([42u32], [42u32.to_le_bytes()]); @@ -941,6 +980,7 @@ mod tests { [&("hello".len() as u64).to_le_bytes()[..], &b"hello"[..]] ); } + #[test] fn test_bytes_to_hash() { hash_eq!( @@ -962,96 +1002,55 @@ mod tests { ); } + // --- ValueType --- + #[test] fn test_value_type_i32_to_hash() { + hash_eq!( + [i32_vt()], + [&(PlainType::ValueTypeI32 as i32).to_le_bytes()[..], &[0]] + ); + } + + #[test] + fn test_value_type_ref_to_hash() { hash_eq!( [ValueType { - value_type: Some(PlainType::ValueTypeI32 as i32), - reference_type: None, + value_type: Some(PlainType::ValueTypeRef as i32), + reference_type: Some(RefType::RefFunc as i32), }], - [&(PlainType::ValueTypeI32 as i32).to_le_bytes()[..], &[0]] + [ + &(PlainType::ValueTypeRef as i32).to_le_bytes()[..], + &(RefType::RefFunc as i32).to_le_bytes()[..] + ] ); } #[test] fn test_value_type_missing_value_type_fails() { - let vt = ValueType { + hash_err!(ValueType { value_type: None, reference_type: None, - }; - hash_err!(vt); + }); } #[test] fn test_value_type_ref_type_with_primitive_fails() { - let vt = ValueType { + hash_err!(ValueType { value_type: Some(PlainType::ValueTypeI32 as i32), reference_type: Some(RefType::RefFunc as i32), - }; - hash_err!(vt); - } - - #[test] - fn test_table_type_to_hash() { - let table = TableType { - reference_type: Some(RefType::RefFunc as i32), - table64: Some(true), - initial: Some(100), - maximum: Some(200), - shared: Some(false), - }; - hash_eq!( - [table], - [ - &(RefType::RefFunc as i32).to_le_bytes()[..], - &[1], - &100u64.to_le_bytes()[..], - &[1, 1], - &200u64.to_le_bytes()[..], - &[0] - ] - ); + }); } #[test] - fn test_memory_type_to_hash() { - let memory = MemoryType { - memory64: Some(true), - shared: Some(false), - initial: Some(1), - maximum: Some(256), - page_size_log2: Some(16), - }; - hash_eq!( - [memory], - [ - &[1], - &[0], - &1u64.to_le_bytes()[..], - &[1, 1], - &256u64.to_le_bytes()[..], - &[1, 1], - &16u32.to_le_bytes()[..] - ] - ); + fn test_value_type_ref_missing_reference_type_fails() { + hash_err!(ValueType { + value_type: Some(PlainType::ValueTypeRef as i32), + reference_type: None, + }); } - #[test] - fn test_tag_type_to_hash() { - let tag = TagType { - kind: Some(TagKind::Exception as i32), - function_type_idx: Some(42), - }; - hash_eq!( - [tag], - [ - &[1, 1], - &(TagKind::Exception as i32).to_le_bytes()[..], - &[1, 1], - &42u32.to_le_bytes()[..] - ] - ); - } + // --- CatchElement --- #[test] fn test_catch_element_one_to_hash() { @@ -1103,12 +1102,13 @@ mod tests { #[test] fn test_catch_element_missing_fails() { - let ce = CatchElement { + hash_err!(CatchElement { catch_element: None, - }; - hash_err!(ce); + }); } + // --- Operator --- + #[test] fn test_operator_nop_to_hash() { let op = Operator { @@ -1120,11 +1120,10 @@ mod tests { #[test] fn test_operator_nop_with_operator_fails() { - let op = Operator { + hash_err!(Operator { opcode: Some(OpCode::Nop as i32), operator: Some(OperatorVariant::LocalIndex(0)), - }; - hash_err!(op); + }); } #[test] @@ -1144,11 +1143,10 @@ mod tests { #[test] fn test_operator_call_missing_function_index_fails() { - let op = Operator { + hash_err!(Operator { opcode: Some(OpCode::Call as i32), operator: None, - }; - hash_err!(op); + }); } #[test] @@ -1179,14 +1177,10 @@ mod tests { #[test] fn test_operator_block_value_type_to_hash() { - let value_type = ValueType { - value_type: Some(PlainType::ValueTypeI32 as i32), - reference_type: None, - }; let op = Operator { opcode: Some(OpCode::Block as i32), operator: Some(OperatorVariant::BlockType(wasm::BlockType { - block_type: Some(block_type::BlockType::ValueType(value_type)), + block_type: Some(block_type::BlockType::ValueType(i32_vt())), })), }; hash_eq!( @@ -1295,133 +1289,12 @@ mod tests { ); } - #[test] - fn test_type_ref_func_to_hash() { - let trf = TypeRefFunc { - module: Some("env".to_string()), - name: Some("foo".to_string()), - function_type: Some(42), - }; - hash_eq!( - [trf], - [ - &3u64.to_le_bytes()[..], - b"env", - &3u64.to_le_bytes()[..], - b"foo", - &42u32.to_le_bytes()[..] - ] - ); - } - - #[test] - fn test_type_ref_func_missing_module_fails() { - let trf = TypeRefFunc { - module: None, - name: Some("foo".to_string()), - function_type: Some(42), - }; - hash_err!(trf); - } - - #[test] - fn test_type_ref_func_missing_name_fails() { - let trf = TypeRefFunc { - module: Some("env".to_string()), - name: None, - function_type: Some(42), - }; - hash_err!(trf); - } - - #[test] - fn test_type_ref_func_missing_function_type_fails() { - let trf = TypeRefFunc { - module: Some("env".to_string()), - name: Some("foo".to_string()), - function_type: None, - }; - hash_err!(trf); - } - - #[test] - fn test_operator_hash_deterministic() { - let op = Operator { - opcode: Some(OpCode::Nop as i32), - operator: None, - }; - let mut hasher1 = sha3::Sha3_512::new(); - let mut hasher2 = sha3::Sha3_512::new(); - op.sha3_hash(&mut hasher1).unwrap(); - op.sha3_hash(&mut hasher2).unwrap(); - assert_eq!(hasher1.finalize(), hasher2.finalize()); - } - - #[test] - fn test_operator_hash_different_operators_different_scalars() { - let mut hasher1 = sha3::Sha3_512::new(); - let mut hasher2 = sha3::Sha3_512::new(); - let nop = Operator { - opcode: Some(OpCode::Nop as i32), - operator: None, - }; - let ret = Operator { - opcode: Some(OpCode::Return as i32), - operator: None, - }; - nop.sha3_hash(&mut hasher1).unwrap(); - ret.sha3_hash(&mut hasher2).unwrap(); - assert_ne!(hasher1.finalize(), hasher2.finalize()); - } - - #[test] - fn test_operator_same_op_same_hash() { - let mut hasher1 = sha3::Sha3_512::new(); - let mut hasher2 = sha3::Sha3_512::new(); - let op1 = Operator { - opcode: Some(OpCode::I32Constant as i32), - operator: Some(OperatorVariant::I32Value(42)), - }; - let op2 = Operator { - opcode: Some(OpCode::I32Constant as i32), - operator: Some(OperatorVariant::I32Value(42)), - }; - op1.sha3_hash(&mut hasher1).unwrap(); - op2.sha3_hash(&mut hasher2).unwrap(); - assert_eq!(hasher1.finalize(), hasher2.finalize()); - } - - #[test] - fn test_value_type_ref_to_hash() { - hash_eq!( - [ValueType { - value_type: Some(PlainType::ValueTypeRef as i32), - reference_type: Some(RefType::RefFunc as i32), - }], - [ - &(PlainType::ValueTypeRef as i32).to_le_bytes()[..], - &(RefType::RefFunc as i32).to_le_bytes()[..] - ] - ); - } - - #[test] - fn test_value_type_ref_missing_reference_type_fails() { - let vt = ValueType { - value_type: Some(PlainType::ValueTypeRef as i32), - reference_type: None, - }; - hash_err!(vt); - } + // --- Version --- #[test] fn test_version_to_hash() { - let version = Version { - number: Some(1), - encoding: Some(Encoding::Module as i32), - }; hash_eq!( - [version], + [default_version()], [ &1u32.to_le_bytes()[..], &(Encoding::Module as i32).to_le_bytes()[..] @@ -1431,40 +1304,27 @@ mod tests { #[test] fn test_version_missing_number_fails() { - let v = Version { + hash_err!(Version { number: None, encoding: Some(Encoding::Module as i32), - }; - hash_err!(v); + }); } #[test] fn test_version_missing_encoding_fails() { - let v = Version { + hash_err!(Version { number: Some(1), encoding: None, - }; - hash_err!(v); + }); } - #[test] - fn test_func_type_empty_to_hash() { - let ft = FuncType { - params: vec![], - results: vec![], - }; - hash_eq!([ft], [0u64.to_le_bytes(), 0u64.to_le_bytes()]); - } + // --- FuncType --- #[test] fn test_func_type_with_params_and_results_to_hash() { - let i32_vt = ValueType { - value_type: Some(PlainType::ValueTypeI32 as i32), - reference_type: None, - }; let ft = FuncType { - params: vec![i32_vt], - results: vec![i32_vt], + params: vec![i32_vt()], + results: vec![i32_vt()], }; hash_eq!( [ft], @@ -1479,14 +1339,15 @@ mod tests { ); } + // --- SubType --- + #[test] fn test_sub_type_func_to_hash() { - let ft = FuncType { - params: vec![], - results: vec![], - }; let st = SubType { - kind: Some(wasm::sub_type::Kind::Func(ft)), + kind: Some(wasm::sub_type::Kind::Func(FuncType { + params: vec![], + results: vec![], + })), }; hash_eq!( [st], @@ -1496,24 +1357,97 @@ mod tests { #[test] fn test_sub_type_missing_kind_fails() { - let st = SubType { kind: None }; - hash_err!(st); + hash_err!(SubType { kind: None }); } + // --- TypeRefFunc --- + #[test] - fn test_type_section_empty_to_hash() { - let ts = TypeSection { types: vec![] }; - hash_eq!([ts], [0u64.to_le_bytes()]); + fn test_type_ref_func_to_hash() { + let trf = TypeRefFunc { + module: Some("env".to_string()), + name: Some("foo".to_string()), + function_type: Some(42), + }; + hash_eq!( + [trf], + [ + &3u64.to_le_bytes()[..], + b"env", + &3u64.to_le_bytes()[..], + b"foo", + &42u32.to_le_bytes()[..] + ] + ); + } + + #[test] + fn test_type_ref_func_missing_module_fails() { + hash_err!(TypeRefFunc { + module: None, + name: Some("foo".to_string()), + function_type: Some(42), + }); + } + + #[test] + fn test_type_ref_func_missing_name_fails() { + hash_err!(TypeRefFunc { + module: Some("env".to_string()), + name: None, + function_type: Some(42), + }); + } + + #[test] + fn test_type_ref_func_missing_function_type_fails() { + hash_err!(TypeRefFunc { + module: Some("env".to_string()), + name: Some("foo".to_string()), + function_type: None, + }); } + // --- Sections (empty) --- + + #[test] + fn test_empty_sections_to_hash() { + let empty_vec_hash = sha512([0u64.to_le_bytes()]); + assert_eq!(hash([TypeSection { types: vec![] }]), empty_vec_hash); + assert_eq!(hash([ImportSection { imports: vec![] }]), empty_vec_hash); + assert_eq!( + hash([FunctionSection { type_idxs: vec![] }]), + empty_vec_hash + ); + assert_eq!(hash([TableSection { types: vec![] }]), empty_vec_hash); + assert_eq!( + hash([MemorySection { + memory_types: vec![] + }]), + empty_vec_hash + ); + assert_eq!(hash([TagSection { tags: vec![] }]), empty_vec_hash); + assert_eq!(hash([GlobalSection { globals: vec![] }]), empty_vec_hash); + assert_eq!(hash([ExportSection { exports: vec![] }]), empty_vec_hash); + assert_eq!(hash([ElementSection { elements: vec![] }]), empty_vec_hash); + assert_eq!( + hash([CodeSection { + code_section_entry: vec![] + }]), + empty_vec_hash + ); + assert_eq!(hash([DataSection { datas: vec![] }]), empty_vec_hash); + } + + // --- TypeSection (non-empty) --- + #[test] fn test_type_section_with_types_to_hash() { - let ft = FuncType { - params: vec![], - results: vec![], - }; let st = SubType { - kind: Some(wasm::sub_type::Kind::Func(ft)), + kind: Some(wasm::sub_type::Kind::Func(FuncType { + params: vec![], + results: vec![], + })), }; let ts = TypeSection { types: vec![st] }; hash_eq!( @@ -1527,11 +1461,7 @@ mod tests { ); } - #[test] - fn test_import_section_empty_to_hash() { - let is = ImportSection { imports: vec![] }; - hash_eq!([is], [0u64.to_le_bytes()]); - } + // --- FunctionSection (non-empty) --- #[test] fn test_function_section_to_hash() { @@ -1548,40 +1478,80 @@ mod tests { ); } - #[test] - fn test_function_section_empty_to_hash() { - let fs = FunctionSection { type_idxs: vec![] }; - hash_eq!([fs], [0u64.to_le_bytes()]); - } + // --- TableType --- #[test] - fn test_table_section_empty_to_hash() { - let ts = TableSection { types: vec![] }; - hash_eq!([ts], [0u64.to_le_bytes()]); + fn test_table_type_to_hash() { + let table = TableType { + reference_type: Some(RefType::RefFunc as i32), + table64: Some(true), + initial: Some(100), + maximum: Some(200), + shared: Some(false), + }; + hash_eq!( + [table], + [ + &(RefType::RefFunc as i32).to_le_bytes()[..], + &[1], + &100u64.to_le_bytes()[..], + &[1, 1], + &200u64.to_le_bytes()[..], + &[0] + ] + ); } + // --- MemoryType --- + #[test] - fn test_memory_section_empty_to_hash() { - let ms = MemorySection { - memory_types: vec![], + fn test_memory_type_to_hash() { + let memory = MemoryType { + memory64: Some(true), + shared: Some(false), + initial: Some(1), + maximum: Some(256), + page_size_log2: Some(16), }; - hash_eq!([ms], [0u64.to_le_bytes()]); + hash_eq!( + [memory], + [ + &[1], + &[0], + &1u64.to_le_bytes()[..], + &[1, 1], + &256u64.to_le_bytes()[..], + &[1, 1], + &16u32.to_le_bytes()[..] + ] + ); } + // --- TagType --- + #[test] - fn test_tag_section_empty_to_hash() { - let ts = TagSection { tags: vec![] }; - hash_eq!([ts], [0u64.to_le_bytes()]); + fn test_tag_type_to_hash() { + let tag = TagType { + kind: Some(TagKind::Exception as i32), + function_type_idx: Some(42), + }; + hash_eq!( + [tag], + [ + &[1, 1], + &(TagKind::Exception as i32).to_le_bytes()[..], + &[1, 1], + &42u32.to_le_bytes()[..] + ] + ); } + // --- GlobalType --- + #[test] fn test_global_type_to_hash() { - let i32_vt = ValueType { - value_type: Some(PlainType::ValueTypeI32 as i32), - reference_type: None, - }; let gt = GlobalType { - content_type: Some(i32_vt), + content_type: Some(i32_vt()), mutable: Some(true), shared: Some(false), }; @@ -1598,47 +1568,32 @@ mod tests { #[test] fn test_global_type_missing_content_type_fails() { - let gt = GlobalType { + hash_err!(GlobalType { content_type: None, mutable: Some(true), shared: Some(false), - }; - hash_err!(gt); + }); } #[test] fn test_global_type_missing_mutable_fails() { - let i32_vt = ValueType { - value_type: Some(PlainType::ValueTypeI32 as i32), - reference_type: None, - }; - let gt = GlobalType { - content_type: Some(i32_vt), + hash_err!(GlobalType { + content_type: Some(i32_vt()), mutable: None, shared: Some(false), - }; - hash_err!(gt); + }); } #[test] fn test_global_type_missing_shared_fails() { - let i32_vt = ValueType { - value_type: Some(PlainType::ValueTypeI32 as i32), - reference_type: None, - }; - let gt = GlobalType { - content_type: Some(i32_vt), + hash_err!(GlobalType { + content_type: Some(i32_vt()), mutable: Some(true), shared: None, - }; - hash_err!(gt); + }); } - #[test] - fn test_expression_empty_to_hash() { - let expr = Expression { operators: vec![] }; - hash_eq!([expr], [0u64.to_le_bytes()]); - } + // --- Expression --- #[test] fn test_expression_with_operators_to_hash() { @@ -1659,21 +1614,18 @@ mod tests { ); } + // --- Global --- + #[test] fn test_global_to_hash() { - let i32_vt = ValueType { - value_type: Some(PlainType::ValueTypeI32 as i32), - reference_type: None, - }; let gt = GlobalType { - content_type: Some(i32_vt), + content_type: Some(i32_vt()), mutable: Some(false), shared: Some(false), }; - let init = Expression { operators: vec![] }; let g = Global { r#type: Some(gt), - init_expr: Some(init), + init_expr: Some(empty_expr()), }; hash_eq!( [g], @@ -1690,18 +1642,13 @@ mod tests { #[test] fn test_global_missing_type_fails() { - let g = Global { + hash_err!(Global { r#type: None, - init_expr: Some(Expression { operators: vec![] }), - }; - hash_err!(g); + init_expr: Some(empty_expr()), + }); } - #[test] - fn test_global_section_empty_to_hash() { - let gs = GlobalSection { globals: vec![] }; - hash_eq!([gs], [0u64.to_le_bytes()]); - } + // --- Export --- #[test] fn test_export_to_hash() { @@ -1723,46 +1670,39 @@ mod tests { #[test] fn test_export_missing_name_fails() { - let e = Export { + hash_err!(Export { name: None, kind: Some(ExternalKind::ExtFunc as i32), index: Some(0), - }; - hash_err!(e); + }); } #[test] fn test_export_missing_kind_fails() { - let e = Export { + hash_err!(Export { name: Some("foo".to_string()), kind: None, index: Some(0), - }; - hash_err!(e); + }); } #[test] fn test_export_missing_index_fails() { - let e = Export { + hash_err!(Export { name: Some("foo".to_string()), kind: Some(ExternalKind::ExtFunc as i32), index: None, - }; - hash_err!(e); + }); } - #[test] - fn test_export_section_empty_to_hash() { - let es = ExportSection { exports: vec![] }; - hash_eq!([es], [0u64.to_le_bytes()]); - } + // --- ElementKind --- #[test] fn test_element_kind_active_to_hash() { let ek = ElementKind { r#type: Some(ElementKindType::ElActive as i32), table_index: Some(0), - expression: Some(Expression { operators: vec![] }), + expression: Some(empty_expr()), }; hash_eq!( [ek], @@ -1795,14 +1735,15 @@ mod tests { #[test] fn test_element_kind_missing_type_fails() { - let ek = ElementKind { + hash_err!(ElementKind { r#type: None, table_index: None, expression: None, - }; - hash_err!(ek); + }); } + // --- ElementFunctions / ElementExpressions / Items --- + #[test] fn test_element_functions_to_hash() { let ef = ElementFunctions { @@ -1819,12 +1760,6 @@ mod tests { ); } - #[test] - fn test_element_functions_empty_to_hash() { - let ef = ElementFunctions { functions: vec![] }; - hash_eq!([ef], [0u64.to_le_bytes()]); - } - #[test] fn test_element_expressions_to_hash() { let ee = ElementExpressions { @@ -1842,11 +1777,10 @@ mod tests { #[test] fn test_element_expressions_missing_ref_type_fails() { - let ee = ElementExpressions { + hash_err!(ElementExpressions { reference_type: None, expressions: vec![], - }; - hash_err!(ee); + }); } #[test] @@ -1870,6 +1804,8 @@ mod tests { ); } + // --- Element --- + #[test] fn test_element_to_hash() { let ek = ElementKind { @@ -1917,18 +1853,13 @@ mod tests { #[test] fn test_element_missing_kind_fails() { - let el = Element { + hash_err!(Element { kind: None, items: None, - }; - hash_err!(el); + }); } - #[test] - fn test_element_section_empty_to_hash() { - let es = ElementSection { elements: vec![] }; - hash_eq!([es], [0u64.to_le_bytes()]); - } + // --- CodeSectionEntry / Locals --- #[test] fn test_code_section_entry_empty_to_hash() { @@ -1941,13 +1872,9 @@ mod tests { #[test] fn test_locals_to_hash() { - let i32_vt = ValueType { - value_type: Some(PlainType::ValueTypeI32 as i32), - reference_type: None, - }; let l = Locals { count: Some(3), - value_type: Some(i32_vt), + value_type: Some(i32_vt()), }; hash_eq!( [l], @@ -1961,33 +1888,21 @@ mod tests { #[test] fn test_locals_missing_count_fails() { - let i32_vt = ValueType { - value_type: Some(PlainType::ValueTypeI32 as i32), - reference_type: None, - }; - let l = Locals { + hash_err!(Locals { count: None, - value_type: Some(i32_vt), - }; - hash_err!(l); + value_type: Some(i32_vt()), + }); } #[test] fn test_locals_missing_value_type_fails() { - let l = Locals { + hash_err!(Locals { count: Some(3), value_type: None, - }; - hash_err!(l); + }); } - #[test] - fn test_code_section_empty_to_hash() { - let cs = CodeSection { - code_section_entry: vec![], - }; - hash_eq!([cs], [0u64.to_le_bytes()]); - } + // --- DataKind / Data --- #[test] fn test_data_kind_passive_to_hash() { @@ -2011,7 +1926,7 @@ mod tests { let dk = DataKind { r#type: Some(DataKindType::Active as i32), memory_index: Some(0), - expression: Some(Expression { operators: vec![] }), + expression: Some(empty_expr()), }; hash_eq!( [dk], @@ -2027,12 +1942,11 @@ mod tests { #[test] fn test_data_kind_missing_type_fails() { - let dk = DataKind { + hash_err!(DataKind { r#type: None, memory_index: None, expression: None, - }; - hash_err!(dk); + }); } #[test] @@ -2060,11 +1974,10 @@ mod tests { #[test] fn test_data_missing_kind_fails() { - let d = Data { + hash_err!(Data { kind: None, data: Some(b"hi".to_vec()), - }; - hash_err!(d); + }); } #[test] @@ -2074,41 +1987,18 @@ mod tests { memory_index: None, expression: None, }; - let d = Data { + hash_err!(Data { kind: Some(dk), data: None, - }; - hash_err!(d); + }); } - #[test] - fn test_data_section_empty_to_hash() { - let ds = DataSection { datas: vec![] }; - hash_eq!([ds], [0u64.to_le_bytes()]); - } + // --- ProgramModule --- #[test] fn test_program_module_minimal_to_hash() { - let pm = ProgramModule { - protocol_version: Some(1), - version: Some(Version { - number: Some(1), - encoding: Some(Encoding::Module as i32), - }), - type_section: None, - import_section: None, - function_section: None, - table_section: None, - memory_section: None, - tag_section: None, - global_section: None, - export_section: None, - element_section: None, - code_section: None, - data_section: None, - }; hash_eq!( - [pm], + [default_pm()], [ &1u32.to_le_bytes()[..], &1u32.to_le_bytes()[..], @@ -2120,66 +2010,27 @@ mod tests { #[test] fn test_program_module_missing_protocol_version_fails() { - let pm = ProgramModule { + hash_err!(ProgramModule { protocol_version: None, - version: Some(Version { - number: Some(1), - encoding: Some(Encoding::Module as i32), - }), - type_section: None, - import_section: None, - function_section: None, - table_section: None, - memory_section: None, - tag_section: None, - global_section: None, - export_section: None, - element_section: None, - code_section: None, - data_section: None, - }; - hash_err!(pm); + ..default_pm() + }); } #[test] fn test_program_module_missing_version_fails() { - let pm = ProgramModule { - protocol_version: Some(1), + hash_err!(ProgramModule { version: None, - type_section: None, - import_section: None, - function_section: None, - table_section: None, - memory_section: None, - tag_section: None, - global_section: None, - export_section: None, - element_section: None, - code_section: None, - data_section: None, - }; - hash_err!(pm); + ..default_pm() + }); } #[test] fn test_program_module_with_sections_to_hash() { let pm = ProgramModule { - protocol_version: Some(1), - version: Some(Version { - number: Some(1), - encoding: Some(Encoding::Module as i32), - }), type_section: Some(TypeSection { types: vec![] }), import_section: Some(ImportSection { imports: vec![] }), function_section: Some(FunctionSection { type_idxs: vec![] }), - table_section: None, - memory_section: None, - tag_section: None, - global_section: None, - export_section: None, - element_section: None, - code_section: None, - data_section: None, + ..default_pm() }; hash_eq!( [pm], From 29d77f0bb3dde067b1ae3fb5b8c041ee4d3af33d Mon Sep 17 00:00:00 2001 From: Alexander Guryanov Date: Thu, 26 Feb 2026 22:07:57 +0300 Subject: [PATCH 15/15] update proto submodule --- proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proto b/proto index e8544d9..da97b15 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit e8544d9d26f489e9b1db197e831f4cb1ab8fd611 +Subproject commit da97b154dc3128d82f4759ca5a9611d6597ef2b7