From 226c2408d2b066e2948c7b2a10cd136ea0a9ae92 Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Fri, 25 Jul 2025 08:51:46 +0200 Subject: [PATCH 1/6] Tezlink: move verify_signature to operation.rs --- .../kernel_latest/tezos/src/operation.rs | 30 +++++++++++++++ .../tezos_execution/src/validate.rs | 37 ++----------------- 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/etherlink/kernel_latest/tezos/src/operation.rs b/etherlink/kernel_latest/tezos/src/operation.rs index 2cfedb72a3b3..c5350e9f2215 100644 --- a/etherlink/kernel_latest/tezos/src/operation.rs +++ b/etherlink/kernel_latest/tezos/src/operation.rs @@ -12,6 +12,7 @@ use primitive_types::H256; use rlp::Decodable; use tezos_crypto_rs::blake2b::digest_256; use tezos_crypto_rs::hash::{BlsSignature, UnknownSignature}; +use tezos_crypto_rs::PublicKeySignatureVerifier; use tezos_data_encoding::types::Narith; use tezos_data_encoding::{ enc::{BinError, BinWriter}, @@ -207,6 +208,35 @@ impl From for ManagerOperation { } } +pub fn verify_signature( + pk: &PublicKey, + branch: &BlockHash, + operation: &ManagerOperationContent, + signature: UnknownSignature, +) -> Result { + // Watermark comes from `src/lib_crypto/signature_v2.ml` + // The watermark for a ManagerOperation is always `Generic_operation` + // encoded with `0x03` + let watermark = 3_u8; + + let mut serialized_unsigned_operation = vec![watermark]; + + let branch: [u8; 32] = branch.0.to_fixed_bytes(); + tezos_data_encoding::enc::put_bytes(&branch, &mut serialized_unsigned_operation); + operation.bin_write(&mut serialized_unsigned_operation)?; + + let signature = &signature.into(); + + // The verify_signature function never returns false. If the verification + // is incorrect the function will return an Error and it's up to us to + // transform that into a `false` boolean if we want. + let check = pk + .verify_signature(signature, &serialized_unsigned_operation) + .unwrap_or(false); + + Ok(check) +} + #[cfg(test)] fn make_dummy_operation( operation: OperationContent, diff --git a/etherlink/kernel_latest/tezos_execution/src/validate.rs b/etherlink/kernel_latest/tezos_execution/src/validate.rs index 681cd2f54eee..6681cc00585f 100644 --- a/etherlink/kernel_latest/tezos_execution/src/validate.rs +++ b/etherlink/kernel_latest/tezos_execution/src/validate.rs @@ -2,16 +2,14 @@ // // SPDX-License-Identifier: MIT -use tezos_crypto_rs::{hash::UnknownSignature, PublicKeySignatureVerifier}; -use tezos_data_encoding::enc::BinWriter; -use tezos_data_encoding::{enc::BinError, types::Narith}; +use tezos_crypto_rs::hash::UnknownSignature; +use tezos_data_encoding::types::Narith; use tezos_evm_logging::log; use tezos_evm_runtime::runtime::Runtime; use tezos_smart_rollup::types::PublicKey; use tezos_tezlink::enc_wrappers::BlockHash; -use tezos_tezlink::operation::ManagerOperationContent; use tezos_tezlink::{ - operation::{ManagerOperation, OperationContent, RevealContent}, + operation::{verify_signature, ManagerOperation, OperationContent, RevealContent}, operation_result::{CounterError, ValidityError}, }; @@ -93,35 +91,6 @@ fn get_revealed_key( } } -fn verify_signature( - pk: &PublicKey, - branch: &BlockHash, - operation: &ManagerOperationContent, - signature: UnknownSignature, -) -> Result { - // Watermark comes from `src/lib_crypto/signature_v2.ml` - // The watermark for a ManagerOperation is always `Generic_operation` - // encoded with `0x03` - let watermark = 3_u8; - - let mut serialized_unsigned_operation = vec![watermark]; - - let branch: [u8; 32] = branch.0.to_fixed_bytes(); - tezos_data_encoding::enc::put_bytes(&branch, &mut serialized_unsigned_operation); - operation.bin_write(&mut serialized_unsigned_operation)?; - - let signature = &signature.into(); - - // The verify_signature function never returns false. If the verification - // is incorrect the function will return an Error and it's up to us to - // transform that into a `false` boolean if we want. - let check = pk - .verify_signature(signature, &serialized_unsigned_operation) - .unwrap_or(false); - - Ok(check) -} - // Inspired from `check_gas_limit` in `src/proto_alpha/lib_protocol/gas_limit_repr.ml` fn check_gas_limit( hard_gas_limit_per_operation: &Narith, -- GitLab From 168a8db45390ad6a96a5b0a6ea6bcedd17817cc3 Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Fri, 25 Jul 2025 09:29:45 +0200 Subject: [PATCH 2/6] Tezlink: refacto unserialize_unsigned_operation --- .../kernel_latest/tezos/src/operation.rs | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/etherlink/kernel_latest/tezos/src/operation.rs b/etherlink/kernel_latest/tezos/src/operation.rs index c5350e9f2215..4d429fabb1b6 100644 --- a/etherlink/kernel_latest/tezos/src/operation.rs +++ b/etherlink/kernel_latest/tezos/src/operation.rs @@ -208,12 +208,10 @@ impl From for ManagerOperation { } } -pub fn verify_signature( - pk: &PublicKey, +pub fn serialize_unsigned_operation( branch: &BlockHash, - operation: &ManagerOperationContent, - signature: UnknownSignature, -) -> Result { + content: &ManagerOperationContent, +) -> Result, BinError> { // Watermark comes from `src/lib_crypto/signature_v2.ml` // The watermark for a ManagerOperation is always `Generic_operation` // encoded with `0x03` @@ -223,7 +221,18 @@ pub fn verify_signature( let branch: [u8; 32] = branch.0.to_fixed_bytes(); tezos_data_encoding::enc::put_bytes(&branch, &mut serialized_unsigned_operation); - operation.bin_write(&mut serialized_unsigned_operation)?; + content.bin_write(&mut serialized_unsigned_operation)?; + + Ok(serialized_unsigned_operation) +} + +pub fn verify_signature( + pk: &PublicKey, + branch: &BlockHash, + operation: &ManagerOperationContent, + signature: UnknownSignature, +) -> Result { + let serialized_unsigned_operation = serialize_unsigned_operation(branch, operation)?; let signature = &signature.into(); -- GitLab From ca8ec13c927d9e48e6cb3b0c0b5bed46d0d709ec Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Fri, 25 Jul 2025 16:16:33 +0200 Subject: [PATCH 3/6] Tezlink: use serialization on sign_operation --- etherlink/kernel_latest/kernel/src/block.rs | 23 ++++++++----------- .../kernel_latest/tezos_execution/src/lib.rs | 16 +++++-------- 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/etherlink/kernel_latest/kernel/src/block.rs b/etherlink/kernel_latest/kernel/src/block.rs index 167405842431..be971cf86f99 100644 --- a/etherlink/kernel_latest/kernel/src/block.rs +++ b/etherlink/kernel_latest/kernel/src/block.rs @@ -618,7 +618,6 @@ mod tests { use primitive_types::{H160, U256}; use std::str::FromStr; use tezos_crypto_rs::hash::UnknownSignature; - use tezos_data_encoding::enc::BinWriter; use tezos_data_encoding::types::Narith; use tezos_ethereum::block::BlockFees; use tezos_ethereum::transaction::{ @@ -692,19 +691,17 @@ mod tests { branch: &H256, content: &ManagerOperationContent, ) -> UnknownSignature { - // Watermark comes from `src/lib_crypto/signature_v2.ml` - // The watermark for a ManagerOperation is always `Generic_operation` - // encoded with `0x03` - let mut serialized_unsigned_operation = vec![3_u8]; - - let branch = branch.as_fixed_bytes(); - tezos_data_encoding::enc::put_bytes(branch, &mut serialized_unsigned_operation); - content - .bin_write(&mut serialized_unsigned_operation) + let serialized_unsigned_operation = + tezos_tezlink::operation::serialize_unsigned_operation( + &(*branch).into(), + content, + ) .unwrap(); + let signature = sk .sign(serialized_unsigned_operation) .expect("Signature should have succeeded"); + signature.into() } @@ -1096,8 +1093,8 @@ mod tests { let chain_config = dummy_tez_config(); let mut config = dummy_configuration(); - let boostrap = bootstrap1(); - let src = boostrap.pkh.clone(); + let bootstrap = bootstrap1(); + let src = bootstrap.pkh.clone(); let context = context::Context::from(&TEZLINK_SAFE_STORAGE_ROOT_PATH) .expect("Context creation should have succeeded"); @@ -1123,7 +1120,7 @@ mod tests { assert_eq!(Manager::NotRevealed(src), manager); // Reveal bootstrap 1 manager - let reveal = make_reveal_operation(0, 1, 0, 0, boostrap); + let reveal = make_reveal_operation(0, 1, 0, 0, bootstrap); store_blueprints::<_, MichelsonChainConfig>( &mut host, diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 63e8825bcf22..2fe0dca3e04e 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -455,7 +455,6 @@ mod tests { use crate::{TezlinkImplicitAccount, TezlinkOriginatedAccount}; use primitive_types::H256; use tezos_crypto_rs::hash::{ContractKt1Hash, SecretKeyEd25519, UnknownSignature}; - use tezos_data_encoding::enc::BinWriter; use tezos_data_encoding::types::Narith; use tezos_evm_runtime::runtime::{MockKernelHost, Runtime}; use tezos_smart_rollup::types::{Contract, PublicKey, PublicKeyHash}; @@ -520,16 +519,13 @@ mod tests { branch: &H256, content: &ManagerOperationContent, ) -> UnknownSignature { - // Watermark comes from `src/lib_crypto/signature_v2.ml` - // The watermark for a ManagerOperation is always `Generic_operation` - // encoded with `0x03` - let mut serialized_unsigned_operation = vec![3_u8]; - - let branch = branch.as_fixed_bytes(); - tezos_data_encoding::enc::put_bytes(branch, &mut serialized_unsigned_operation); - content - .bin_write(&mut serialized_unsigned_operation) + let serialized_unsigned_operation = + tezos_tezlink::operation::serialize_unsigned_operation( + &(*branch).into(), + content, + ) .unwrap(); + let signature = sk .sign(serialized_unsigned_operation) .expect("Signature should have succeeded"); -- GitLab From 9668a4b03637b63d02f7eeed67a958e27113b639 Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Fri, 25 Jul 2025 15:59:14 +0200 Subject: [PATCH 4/6] Tezlink: define signature errors --- etherlink/kernel_latest/tezos/src/operation.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/etherlink/kernel_latest/tezos/src/operation.rs b/etherlink/kernel_latest/tezos/src/operation.rs index 4d429fabb1b6..40d1b4df4105 100644 --- a/etherlink/kernel_latest/tezos/src/operation.rs +++ b/etherlink/kernel_latest/tezos/src/operation.rs @@ -19,6 +19,7 @@ use tezos_data_encoding::{ nom::{error::DecodeError, NomError, NomReader}, }; use tezos_smart_rollup::types::{Contract, PublicKey, PublicKeyHash}; +use thiserror::Error; #[derive(PartialEq, Debug, Clone, NomReader, BinWriter)] pub struct Parameter { @@ -226,6 +227,14 @@ pub fn serialize_unsigned_operation( Ok(serialized_unsigned_operation) } +#[derive(Error, Debug)] +pub enum SignatureErrors { + #[error("Signing failed with encoding error {0}")] + BinError(#[from] BinError), + #[error("Signing failed with cryptographic error {0}")] + CryptoError(#[from] tezos_crypto_rs::CryptoError), +} + pub fn verify_signature( pk: &PublicKey, branch: &BlockHash, -- GitLab From 6a2305df9368497b68304a351a72f252620af7fb Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Fri, 25 Jul 2025 16:24:49 +0200 Subject: [PATCH 5/6] Tezlink: change types on sign_operation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Raphaƫl Cauderlier --- etherlink/kernel_latest/kernel/src/block.rs | 19 ++++++++---------- .../kernel_latest/tezos_execution/src/lib.rs | 20 ++++++++----------- 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/etherlink/kernel_latest/kernel/src/block.rs b/etherlink/kernel_latest/kernel/src/block.rs index be971cf86f99..db6f4ecfa6b8 100644 --- a/etherlink/kernel_latest/kernel/src/block.rs +++ b/etherlink/kernel_latest/kernel/src/block.rs @@ -688,21 +688,18 @@ mod tests { fn sign_operation( sk: &SecretKeyEd25519, - branch: &H256, + branch: &BlockHash, content: &ManagerOperationContent, - ) -> UnknownSignature { + ) -> Result { let serialized_unsigned_operation = - tezos_tezlink::operation::serialize_unsigned_operation( - &(*branch).into(), - content, - ) - .unwrap(); + tezos_tezlink::operation::serialize_unsigned_operation(branch, content) + .unwrap(); let signature = sk .sign(serialized_unsigned_operation) .expect("Signature should have succeeded"); - signature.into() + Ok(signature.into()) } fn make_operation( @@ -713,7 +710,7 @@ mod tests { source: Bootstrap, content: OperationContent, ) -> Operation { - let branch = TezBlock::genesis_block_hash(); + let branch = TezBlock::genesis_block_hash().into(); let manager_op = ManagerOperation { source: source.pkh, fee: fee.into(), @@ -724,10 +721,10 @@ mod tests { } .into(); - let signature = sign_operation(&source.sk, &branch, &manager_op); + let signature = sign_operation(&source.sk, &branch, &manager_op).unwrap(); Operation { - branch: BlockHash::from(branch), + branch, content: manager_op, signature, } diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 2fe0dca3e04e..f2b9b1b23a02 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -453,7 +453,6 @@ pub fn apply_operation( #[cfg(test)] mod tests { use crate::{TezlinkImplicitAccount, TezlinkOriginatedAccount}; - use primitive_types::H256; use tezos_crypto_rs::hash::{ContractKt1Hash, SecretKeyEd25519, UnknownSignature}; use tezos_data_encoding::types::Narith; use tezos_evm_runtime::runtime::{MockKernelHost, Runtime}; @@ -516,20 +515,17 @@ mod tests { fn sign_operation( sk: &SecretKeyEd25519, - branch: &H256, + branch: &BlockHash, content: &ManagerOperationContent, - ) -> UnknownSignature { + ) -> Result { let serialized_unsigned_operation = - tezos_tezlink::operation::serialize_unsigned_operation( - &(*branch).into(), - content, - ) - .unwrap(); + tezos_tezlink::operation::serialize_unsigned_operation(branch, content) + .unwrap(); let signature = sk .sign(serialized_unsigned_operation) .expect("Signature should have succeeded"); - signature.into() + Ok(signature.into()) } const CONTRACT_1: &str = "KT1EFxv88KpjxzGNu1ozh9Vta4BaV3psNknp"; @@ -551,7 +547,7 @@ mod tests { source: Bootstrap, content: OperationContent, ) -> Operation { - let branch = TezBlock::genesis_block_hash(); + let branch = TezBlock::genesis_block_hash().into(); let manager_op = ManagerOperation { source: source.pkh, fee: fee.into(), @@ -562,10 +558,10 @@ mod tests { } .into(); - let signature = sign_operation(&source.sk, &branch, &manager_op); + let signature = sign_operation(&source.sk, &branch, &manager_op).unwrap(); Operation { - branch: BlockHash::from(branch), + branch, content: manager_op, signature, } -- GitLab From 517911e9dd6cb7e9632cd3fc538773b8403c515e Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Fri, 25 Jul 2025 15:32:36 +0200 Subject: [PATCH 6/6] Tezlink: move sign_operation to operation Tezlink: proper error handling on sign operation --- etherlink/kernel_latest/kernel/src/block.rs | 20 +---------------- .../kernel_latest/tezos/src/operation.rs | 14 +++++++++++- .../kernel_latest/tezos_execution/src/lib.rs | 22 +++---------------- 3 files changed, 17 insertions(+), 39 deletions(-) diff --git a/etherlink/kernel_latest/kernel/src/block.rs b/etherlink/kernel_latest/kernel/src/block.rs index db6f4ecfa6b8..4f0ed189cfad 100644 --- a/etherlink/kernel_latest/kernel/src/block.rs +++ b/etherlink/kernel_latest/kernel/src/block.rs @@ -587,8 +587,7 @@ mod tests { use super::*; use tezos_crypto_rs::hash::SecretKeyEd25519; use tezos_execution::account_storage::TezlinkAccount; - use tezos_tezlink::enc_wrappers::BlockHash; - use tezos_tezlink::operation::ManagerOperationContent; + use tezos_tezlink::operation::sign_operation; use tezos_tezlink::operation::Parameter; use crate::block_storage; @@ -617,7 +616,6 @@ mod tests { use evm_execution::precompiles::precompile_set; use primitive_types::{H160, U256}; use std::str::FromStr; - use tezos_crypto_rs::hash::UnknownSignature; use tezos_data_encoding::types::Narith; use tezos_ethereum::block::BlockFees; use tezos_ethereum::transaction::{ @@ -686,22 +684,6 @@ mod tests { } } - fn sign_operation( - sk: &SecretKeyEd25519, - branch: &BlockHash, - content: &ManagerOperationContent, - ) -> Result { - let serialized_unsigned_operation = - tezos_tezlink::operation::serialize_unsigned_operation(branch, content) - .unwrap(); - - let signature = sk - .sign(serialized_unsigned_operation) - .expect("Signature should have succeeded"); - - Ok(signature.into()) - } - fn make_operation( fee: u64, counter: u64, diff --git a/etherlink/kernel_latest/tezos/src/operation.rs b/etherlink/kernel_latest/tezos/src/operation.rs index 40d1b4df4105..a0ac1dfa2b61 100644 --- a/etherlink/kernel_latest/tezos/src/operation.rs +++ b/etherlink/kernel_latest/tezos/src/operation.rs @@ -11,7 +11,7 @@ use nom::Finish; use primitive_types::H256; use rlp::Decodable; use tezos_crypto_rs::blake2b::digest_256; -use tezos_crypto_rs::hash::{BlsSignature, UnknownSignature}; +use tezos_crypto_rs::hash::{BlsSignature, SecretKeyEd25519, UnknownSignature}; use tezos_crypto_rs::PublicKeySignatureVerifier; use tezos_data_encoding::types::Narith; use tezos_data_encoding::{ @@ -235,6 +235,18 @@ pub enum SignatureErrors { CryptoError(#[from] tezos_crypto_rs::CryptoError), } +pub fn sign_operation( + sk: &SecretKeyEd25519, + branch: &BlockHash, + content: &ManagerOperationContent, +) -> Result { + let serialized_unsigned_operation = serialize_unsigned_operation(branch, content)?; + + let signature = sk.sign(serialized_unsigned_operation)?; + + Ok(signature.into()) +} + pub fn verify_signature( pk: &PublicKey, branch: &BlockHash, diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index f2b9b1b23a02..3934eecc62cf 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -453,16 +453,15 @@ pub fn apply_operation( #[cfg(test)] mod tests { use crate::{TezlinkImplicitAccount, TezlinkOriginatedAccount}; - use tezos_crypto_rs::hash::{ContractKt1Hash, SecretKeyEd25519, UnknownSignature}; + use tezos_crypto_rs::hash::{ContractKt1Hash, SecretKeyEd25519}; use tezos_data_encoding::types::Narith; use tezos_evm_runtime::runtime::{MockKernelHost, Runtime}; use tezos_smart_rollup::types::{Contract, PublicKey, PublicKeyHash}; use tezos_tezlink::{ block::TezBlock, - enc_wrappers::BlockHash, operation::{ - ManagerOperation, ManagerOperationContent, Operation, OperationContent, - Parameter, RevealContent, TransferContent, + sign_operation, ManagerOperation, Operation, OperationContent, Parameter, + RevealContent, TransferContent, }, operation_result::{ Balance, BalanceTooLow, BalanceUpdate, ContentResult, CounterError, @@ -513,21 +512,6 @@ mod tests { } } - fn sign_operation( - sk: &SecretKeyEd25519, - branch: &BlockHash, - content: &ManagerOperationContent, - ) -> Result { - let serialized_unsigned_operation = - tezos_tezlink::operation::serialize_unsigned_operation(branch, content) - .unwrap(); - - let signature = sk - .sign(serialized_unsigned_operation) - .expect("Signature should have succeeded"); - Ok(signature.into()) - } - const CONTRACT_1: &str = "KT1EFxv88KpjxzGNu1ozh9Vta4BaV3psNknp"; static SCRIPT: &str = r#" -- GitLab