From 02b348f8bea7b6aa355676c58d93c7696c31328a Mon Sep 17 00:00:00 2001 From: Rares <6453351+raress96@users.noreply.github.com> Date: Tue, 15 Oct 2024 16:40:42 +0300 Subject: [PATCH] Add code for execute data for Stacks. --- contracts/multisig-prover/src/encoding/mod.rs | 2 +- .../src/encoding/stacks/execute_data.rs | 166 ++++++++++++++++++ .../src/encoding/stacks/mod.rs | 95 +++++----- 3 files changed, 217 insertions(+), 46 deletions(-) create mode 100644 contracts/multisig-prover/src/encoding/stacks/execute_data.rs diff --git a/contracts/multisig-prover/src/encoding/mod.rs b/contracts/multisig-prover/src/encoding/mod.rs index 2f9350e8e..651bf463e 100644 --- a/contracts/multisig-prover/src/encoding/mod.rs +++ b/contracts/multisig-prover/src/encoding/mod.rs @@ -51,7 +51,7 @@ impl Encoder { Encoder::Bcs => bcs::encode_execute_data(domain_separator, verifier_set, sigs, payload), Encoder::StellarXdr => stellar_xdr::encode_execute_data(verifier_set, sigs, payload), Encoder::Stacks => { - todo!() + stacks::execute_data::encode(domain_separator, verifier_set, sigs, payload) } } } diff --git a/contracts/multisig-prover/src/encoding/stacks/execute_data.rs b/contracts/multisig-prover/src/encoding/stacks/execute_data.rs new file mode 100644 index 000000000..3f115a21f --- /dev/null +++ b/contracts/multisig-prover/src/encoding/stacks/execute_data.rs @@ -0,0 +1,166 @@ +use crate::encoding::stacks::{ecdsa_key, encode_messages, payload_digest, WeightedSigners}; +use crate::error::ContractError; +use crate::payload::Payload; +use axelar_wasm_std::hash::Hash; +use cosmwasm_std::HexBinary; +use error_stack::ResultExt; +use k256::ecdsa::RecoveryId; +use multisig::key::Signature; +use multisig::msg::SignerWithSig; +use multisig::verifier_set::VerifierSet; +use stacks_clarity::common::codec::StacksMessageCodec; +use stacks_clarity::common::types::StacksEpochId; +use stacks_clarity::vm::representations::ClarityName; +use stacks_clarity::vm::types::signatures::{ + BufferLength, ListTypeData, SequenceSubtype, TypeSignature, +}; +use stacks_clarity::vm::types::{TupleData, Value}; + +pub const APPROVE_MESSAGES_FUNCTION: &str = "approve-messages"; +pub const ROTATE_SIGNERS_FUNCTION: &str = "rotate-signers"; + +pub struct Proof { + pub signers: WeightedSigners, + pub signatures: Vec, +} + +impl Proof { + pub fn new( + verifier_set: &VerifierSet, + signers_with_sigs: Vec, + ) -> Result { + let signers = WeightedSigners::try_from(verifier_set)?; + + let mut signers_with_sigs: Vec<(Vec, Signature)> = signers_with_sigs + .into_iter() + .map(|signer| { + let key = ecdsa_key(&signer.signer.pub_key).expect("not ecdsa key"); + + (key, signer.signature) + }) + .collect::>(); + + signers_with_sigs.sort_by(|signer1, signer2| signer1.0.cmp(&signer2.0)); + + let signatures = signers_with_sigs + .into_iter() + .map(|signer| Value::buff_from(signer.1.as_ref().to_vec())) + .collect::>()?; + + Ok(Proof { + signers, + signatures, + }) + } + + pub fn try_into_value(self) -> Result { + let signers = self.signers.try_into_value()?; + let signatures = Value::list_with_type( + &StacksEpochId::latest(), + self.signatures, + ListTypeData::new_list( + TypeSignature::SequenceType(SequenceSubtype::BufferType( + BufferLength::try_from(65u32).map_err(|_| ContractError::InvalidMessage)?, + )), + 48, + )?, + ) + .map_err(|_| ContractError::InvalidMessage)?; + + let tuple_data = TupleData::from_data(vec![ + (ClarityName::from("signers"), signers), + (ClarityName::from("signatures"), signatures), + ])?; + + Ok(Value::from(tuple_data)) + } +} + +pub fn encode( + domain_separator: &Hash, + verifier_set: &VerifierSet, + signers: Vec, + payload: &Payload, +) -> error_stack::Result { + let signers = to_recoverable( + payload_digest(domain_separator, verifier_set, payload)?, + signers, + ); + + let proof = Proof::new(verifier_set, signers)?; + + let data = match payload { + Payload::Messages(messages) => { + let messages = encode_messages(messages)?.serialize_to_vec(); + let proof = proof.try_into_value()?.serialize_to_vec(); + + let tuple_data = TupleData::from_data(vec![ + ( + ClarityName::from("function"), + Value::string_ascii_from_bytes(APPROVE_MESSAGES_FUNCTION.as_bytes().to_vec()) + .map_err(|_| ContractError::InvalidMessage)?, + ), + ( + ClarityName::from("data"), + Value::buff_from(messages).map_err(|_| ContractError::InvalidMessage)?, + ), + ( + ClarityName::from("proof"), + Value::buff_from(proof).map_err(|_| ContractError::InvalidMessage)?, + ), + ]) + .change_context(ContractError::InvalidMessage)?; + + HexBinary::from(Value::from(tuple_data).serialize_to_vec()) + } + Payload::VerifierSet(new_verifier_set) => { + let new_verifier_set = WeightedSigners::try_from(new_verifier_set)? + .try_into_value()? + .serialize_to_vec(); + let proof = proof.try_into_value()?.serialize_to_vec(); + + let tuple_data = TupleData::from_data(vec![ + ( + ClarityName::from("function"), + Value::string_ascii_from_bytes(ROTATE_SIGNERS_FUNCTION.as_bytes().to_vec()) + .map_err(|_| ContractError::InvalidMessage)?, + ), + ( + ClarityName::from("data"), + Value::buff_from(new_verifier_set) + .map_err(|_| ContractError::InvalidMessage)?, + ), + ( + ClarityName::from("proof"), + Value::buff_from(proof).map_err(|_| ContractError::InvalidMessage)?, + ), + ]) + .change_context(ContractError::InvalidMessage)?; + + HexBinary::from(Value::from(tuple_data).serialize_to_vec()) + } + }; + + Ok(data) +} + +fn to_recoverable(msg: M, signers: Vec) -> Vec +where + M: AsRef<[u8]>, +{ + let recovery_transform = |recovery_byte: RecoveryId| -> u8 { recovery_byte.to_byte() }; + + signers + .into_iter() + .map(|mut signer| { + if let Signature::Ecdsa(nonrecoverable) = signer.signature { + signer.signature = nonrecoverable + .to_recoverable(msg.as_ref(), &signer.signer.pub_key, recovery_transform) + .map(Signature::EcdsaRecoverable) + .expect("failed to convert non-recoverable signature to recoverable"); + } + + signer + }) + .collect() +} diff --git a/contracts/multisig-prover/src/encoding/stacks/mod.rs b/contracts/multisig-prover/src/encoding/stacks/mod.rs index bf38b9c51..3f97c71e8 100644 --- a/contracts/multisig-prover/src/encoding/stacks/mod.rs +++ b/contracts/multisig-prover/src/encoding/stacks/mod.rs @@ -1,3 +1,5 @@ +pub mod execute_data; + use crate::error::ContractError; use crate::Payload; use axelar_wasm_std::hash::Hash; @@ -215,42 +217,7 @@ pub fn payload_digest( fn encode(payload: &Payload) -> Result, ContractError> { match payload { Payload::Messages(messages) => { - let messages: Vec = messages - .iter() - .map(Message::try_from) - .map(|message| message?.try_into_value()) - .collect::>()?; - - let message_type_signature = TupleTypeSignature::try_from(vec![ - ( - ClarityName::from("source-chain"), - TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( - BufferLength::try_from(32u32)?, - ))), - ), - ( - ClarityName::from("message-id"), - TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( - BufferLength::try_from(71u32)?, - ))), - ), - ( - ClarityName::from("source-address"), - TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( - BufferLength::try_from(48u32)?, - ))), - ), - ( - ClarityName::from("contract-address"), - TypeSignature::PrincipalType, - ), - ( - ClarityName::from("payload-hash"), - TypeSignature::SequenceType(SequenceSubtype::BufferType( - BufferLength::try_from(32u32)?, - )), - ), - ])?; + let message_value = encode_messages(messages)?; let tuple_data = TupleData::from_data(vec![ ( @@ -258,15 +225,7 @@ fn encode(payload: &Payload) -> Result, ContractError> { Value::string_ascii_from_bytes(TYPE_APPROVE_MESSAGES.as_bytes().to_vec()) .map_err(|_| ContractError::InvalidMessage)?, ), - ( - ClarityName::from("data"), - Value::list_with_type( - &StacksEpochId::latest(), - messages, - ListTypeData::new_list(TypeSignature::from(message_type_signature), 10)?, - ) - .map_err(|_| ContractError::InvalidMessage)?, - ), + (ClarityName::from("data"), message_value), ])?; Ok(Value::from(tuple_data).serialize_to_vec()) @@ -288,6 +247,52 @@ fn encode(payload: &Payload) -> Result, ContractError> { } } +fn encode_messages(messages: &Vec) -> Result { + let messages: Vec = messages + .iter() + .map(Message::try_from) + .map(|message| message?.try_into_value()) + .collect::>()?; + + let message_type_signature = TupleTypeSignature::try_from(vec![ + ( + ClarityName::from("source-chain"), + TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( + BufferLength::try_from(32u32)?, + ))), + ), + ( + ClarityName::from("message-id"), + TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( + BufferLength::try_from(71u32)?, + ))), + ), + ( + ClarityName::from("source-address"), + TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( + BufferLength::try_from(48u32)?, + ))), + ), + ( + ClarityName::from("contract-address"), + TypeSignature::PrincipalType, + ), + ( + ClarityName::from("payload-hash"), + TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( + 32u32, + )?)), + ), + ])?; + + Ok(Value::list_with_type( + &StacksEpochId::latest(), + messages, + ListTypeData::new_list(TypeSignature::from(message_type_signature), 10)?, + ) + .map_err(|_| ContractError::InvalidMessage)?) +} + #[cfg(test)] mod tests { use crate::encoding::stacks::WeightedSigner;