diff --git a/Cargo.lock b/Cargo.lock index 84f9339c3..46cd7b101 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -997,6 +997,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base32" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" + [[package]] name = "base64" version = "0.13.1" @@ -1794,6 +1800,17 @@ dependencies = [ "libc", ] +[[package]] +name = "crate-git-revision" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c521bf1f43d31ed2f73441775ed31935d77901cb3451e44b38a1c1612fcbaf98" +dependencies = [ + "serde", + "serde_derive", + "serde_json", +] + [[package]] name = "crc32fast" version = "1.4.2" @@ -2526,6 +2543,12 @@ dependencies = [ "rustc_version 0.4.0", ] +[[package]] +name = "escape-bytes" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bfcf67fea2815c2fc3b90873fae90957be12ff417335dfadc7f52927feb03b2" + [[package]] name = "ethabi" version = "18.0.0" @@ -5004,6 +5027,7 @@ dependencies = [ "serde_json", "service-registry", "sha3", + "stellar", "sui-gateway", "thiserror", "voting-verifier", @@ -7741,6 +7765,49 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stellar" +version = "1.0.0" +dependencies = [ + "axelar-wasm-std", + "cosmwasm-std", + "error-stack", + "goldie", + "hex", + "multisig", + "rand", + "router-api", + "serde", + "serde_json", + "sha3", + "stellar-strkey", + "stellar-xdr", + "thiserror", +] + +[[package]] +name = "stellar-strkey" +version = "0.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12d2bf45e114117ea91d820a846fd1afbe3ba7d717988fee094ce8227a3bf8bd" +dependencies = [ + "base32", + "crate-git-revision", + "thiserror", +] + +[[package]] +name = "stellar-xdr" +version = "21.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2675a71212ed39a806e415b0dbf4702879ff288ec7f5ee996dda42a135512b50" +dependencies = [ + "crate-git-revision", + "escape-bytes", + "hex", + "stellar-strkey", +] + [[package]] name = "strsim" version = "0.10.0" diff --git a/Cargo.toml b/Cargo.toml index ebca95c66..a44a4558f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["ampd", "contracts/*", "integration-tests", "interchain-token-service", "packages/*"] +members = ["ampd", "contracts/*", "external-gateways/*", "integration-tests", "interchain-token-service", "packages/*"] resolver = "2" [workspace.package] @@ -19,6 +19,7 @@ events-derive = { version = "^1.0.0", path = "packages/events-derive" } evm-gateway = { version = "^1.0.0", path = "packages/evm-gateway" } sui-types = { version = "^1.0.0", path = "packages/sui-types" } sui-gateway = { version = "^1.0.0", path = "packages/sui-gateway" } +stellar = { version = "^1.0.0", path = "external-gateways/stellar" } axelar-wasm-std = { version = "^1.0.0", path = "packages/axelar-wasm-std" } axelar-wasm-std-derive = { version = "^1.0.0", path = "packages/axelar-wasm-std-derive" } hex = "0.4.3" diff --git a/contracts/multisig-prover/Cargo.toml b/contracts/multisig-prover/Cargo.toml index a6aa1bff2..396f28f74 100644 --- a/contracts/multisig-prover/Cargo.toml +++ b/contracts/multisig-prover/Cargo.toml @@ -57,6 +57,7 @@ router-api = { workspace = true } serde_json = "1.0.89" service-registry = { workspace = true } sha3 = { workspace = true } +stellar = { workspace = true } sui-gateway = { workspace = true } thiserror = { workspace = true } voting-verifier = { workspace = true, features = ["library"] } diff --git a/contracts/multisig-prover/src/encoding/mod.rs b/contracts/multisig-prover/src/encoding/mod.rs index ee545f86e..25941cd67 100644 --- a/contracts/multisig-prover/src/encoding/mod.rs +++ b/contracts/multisig-prover/src/encoding/mod.rs @@ -1,5 +1,6 @@ mod abi; mod bcs; +mod stellar_xdr; use axelar_wasm_std::hash::Hash; use cosmwasm_schema::cw_serde; @@ -17,6 +18,7 @@ use crate::payload::Payload; pub enum Encoder { Abi, Bcs, + StellarXdr, } impl Encoder { @@ -29,6 +31,9 @@ impl Encoder { match self { Encoder::Abi => abi::payload_digest(domain_separator, verifier_set, payload), Encoder::Bcs => bcs::payload_digest(domain_separator, verifier_set, payload), + Encoder::StellarXdr => { + stellar_xdr::payload_digest(domain_separator, verifier_set, payload) + } } } @@ -52,6 +57,7 @@ impl Encoder { &self.digest(domain_separator, verifier_set, payload)?, payload, ), + Encoder::StellarXdr => todo!(), } } } @@ -64,6 +70,7 @@ where let recovery_transform = match encoder { Encoder::Abi => add_27, Encoder::Bcs => no_op, + Encoder::StellarXdr => no_op, }; signers .into_iter() diff --git a/contracts/multisig-prover/src/encoding/stellar_xdr.rs b/contracts/multisig-prover/src/encoding/stellar_xdr.rs new file mode 100644 index 000000000..ffb0c55f0 --- /dev/null +++ b/contracts/multisig-prover/src/encoding/stellar_xdr.rs @@ -0,0 +1,196 @@ +use axelar_wasm_std::hash::Hash; +use axelar_wasm_std::FnExt; +use error_stack::{Result, ResultExt}; +use multisig::verifier_set::VerifierSet; +use sha3::{Digest, Keccak256}; +use stellar::{Message, Messages, WeightedSigners}; + +use crate::error::ContractError; +use crate::payload::Payload; + +pub fn payload_digest( + domain_separator: &Hash, + verifier_set: &VerifierSet, + payload: &Payload, +) -> Result { + let data_hash = match payload { + Payload::Messages(messages) => messages + .iter() + .map(Message::try_from) + .collect::, _>>() + .change_context(ContractError::InvalidMessage)? + .then(Messages::from) + .messages_approval_hash(), + Payload::VerifierSet(verifier_set) => WeightedSigners::try_from(verifier_set) + .change_context(ContractError::InvalidVerifierSet)? + .signers_rotation_hash(), + } + .change_context(ContractError::SerializeData)?; + + let signers_hash = WeightedSigners::try_from(verifier_set) + .change_context(ContractError::InvalidVerifierSet)? + .hash() + .change_context(ContractError::SerializeData)?; + + let unsigned = [ + domain_separator, + signers_hash.as_slice(), + data_hash.as_slice(), + ] + .concat(); + + Ok(Keccak256::digest(unsigned).into()) +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::{Addr, HexBinary, Uint128}; + use multisig::key::KeyType; + use multisig::msg::Signer; + use multisig::verifier_set::VerifierSet; + use router_api::{CrossChainId, Message}; + + use crate::encoding::stellar_xdr::payload_digest; + use crate::payload::Payload; + + #[test] + fn stellar_messages_payload_digest() { + let signers_data = vec![ + ( + "addr_1", + "508bcac3df50837e0b093aebc549211ba72bd1e7c1830a288b816b677d62a046", + 9u128, + ), + ( + "addr_2", + "5c186341e6392ff06b35b2b80a05f99cdd1dd7d5b436f2eef1a6dd08c07c9463", + 4u128, + ), + ( + "addr_3", + "78c860cbba0b74a728bdc2ae05feef5a14c8903f59d59525ed5bea9b52027d0e", + 3u128, + ), + ( + "addr_4", + "ac1276368dab35ecc413c5008f184df4005e8773ea44ce3c980bc3dbe45f7521", + 3u128, + ), + ( + "addr_4", + "856d2aedc159b543f3150fd9e013ed5cc4d5d32659595e7bedbec279c28ccbe0", + 5u128, + ), + ( + "addr_5", + "e2a6a040c4a31f8131651fb669d514066963e2fde91feb86350d494a6e02f0fa", + 6u128, + ), + ]; + let verifier_set = gen_veifier_set(signers_data, 22, 2024); + + let payload = Payload::Messages(vec![Message { + cc_id: CrossChainId { + source_chain: "source".parse().unwrap(), + message_id: "test".parse().unwrap(), + }, + source_address: "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + .parse() + .unwrap(), + destination_chain: "stellar".parse().unwrap(), + destination_address: "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + .parse() + .unwrap(), + payload_hash: HexBinary::from_hex( + "65ad329dc342a82bd1daedc42e183e6e2c272b8e2e3fd7c8f81d089736d0bc3c", + ) + .unwrap() + .to_array() + .unwrap(), + }]); + let domain_separator: [u8; 32] = + HexBinary::from_hex("2a15376c1277252b1bcce5a6ecd781bfbc2697dfd969ff58d8e2e116018b501e") + .unwrap() + .to_array() + .unwrap(); + goldie::assert!(hex::encode( + payload_digest(&domain_separator, &verifier_set, &payload).unwrap() + )); + } + + #[test] + fn stellar_verifier_set_payload_digest() { + let verifier_set = gen_veifier_set( + vec![( + "addr_1", + "bf95c447eb2e694974ee2cf5f17e7165bc884a0cb676bb4de50c604bb7a6ea77", + 4u128, + )], + 1, + 2024, + ); + let signers_data = vec![ + ( + "addr_1", + "5086d25f94b8c42faf7ef4325516864e179fcb2a1a9321720f0fc2b249105106", + 5u128, + ), + ( + "addr_2", + "57a446f70d8243b7d5e08edcd9c5774f3f0257940df7aa84bca5b1acfc0f3ba3", + 7u128, + ), + ( + "addr_3", + "5a3211139cca5cee83096e8009aadf6405d84f5137706bc1db68f53cbb202054", + 9u128, + ), + ( + "addr_4", + "9d8774a24acce628658dc93e41c56972ded010c07b731306b54282890113d60f", + 7u128, + ), + ( + "addr_5", + "a99083342953620013c9c61f8000a8778915337632ac601458c6c93387d963f5", + 7u128, + ), + ]; + let payload = Payload::VerifierSet(gen_veifier_set(signers_data, 27, 2024)); + let domain_separator: [u8; 32] = + HexBinary::from_hex("6773bd037510492f863cba62a0f3c55ac846883f33cae7266aff8be5eb9681e8") + .unwrap() + .to_array() + .unwrap(); + + goldie::assert!(hex::encode( + payload_digest(&domain_separator, &verifier_set, &payload).unwrap() + )); + } + + fn gen_veifier_set( + signers_data: Vec<(&str, &str, u128)>, + threshold: u128, + created_at: u64, + ) -> VerifierSet { + VerifierSet { + signers: signers_data + .into_iter() + .map(|(addr, pub_key, weight)| { + ( + addr.to_string(), + Signer { + address: Addr::unchecked(addr), + pub_key: (KeyType::Ed25519, HexBinary::from_hex(pub_key).unwrap()) + .try_into() + .unwrap(), + weight: Uint128::from(weight), + }, + ) + }) + .collect(), + threshold: threshold.into(), + created_at, + } + } +} diff --git a/contracts/multisig-prover/src/encoding/testdata/stellar_messages_payload_digest.golden b/contracts/multisig-prover/src/encoding/testdata/stellar_messages_payload_digest.golden new file mode 100644 index 000000000..82b3f830f --- /dev/null +++ b/contracts/multisig-prover/src/encoding/testdata/stellar_messages_payload_digest.golden @@ -0,0 +1 @@ +0d29098e3f5a7ead5c97c0f8e74051e1de9cb601105551f8d4a898187e0f4f0d \ No newline at end of file diff --git a/contracts/multisig-prover/src/encoding/testdata/stellar_verifier_set_payload_digest.golden b/contracts/multisig-prover/src/encoding/testdata/stellar_verifier_set_payload_digest.golden new file mode 100644 index 000000000..c10388061 --- /dev/null +++ b/contracts/multisig-prover/src/encoding/testdata/stellar_verifier_set_payload_digest.golden @@ -0,0 +1 @@ +80097627445437998d5e4b6689ec9b5dcba64cdd5f4580e490f173cefe78609f \ No newline at end of file diff --git a/contracts/multisig-prover/src/error.rs b/contracts/multisig-prover/src/error.rs index 4ed88fea3..cafef424e 100644 --- a/contracts/multisig-prover/src/error.rs +++ b/contracts/multisig-prover/src/error.rs @@ -74,4 +74,7 @@ pub enum ContractError { #[error("payload does not match the stored value")] PayloadMismatch, + + #[error("failed to serialize data for the external gateway")] + SerializeData, } diff --git a/external-gateways/stellar/Cargo.toml b/external-gateways/stellar/Cargo.toml new file mode 100644 index 000000000..3efc2cb84 --- /dev/null +++ b/external-gateways/stellar/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "stellar" +version = "1.0.0" +edition = { workspace = true } +rust-version = { workspace = true } + +[dependencies] +axelar-wasm-std = { workspace = true } +cosmwasm-std = { workspace = true } +error-stack = { workspace = true } +hex = "0.4.3" +multisig = { workspace = true, features = ["library"] } +router-api = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +sha3 = { workspace = true } +stellar-strkey = { version = "0.0.8" } +stellar-xdr = { version = "21.2.0" } +thiserror = { workspace = true } + +[lints] +workspace = true + +[dev-dependencies] +goldie = { workspace = true } +rand = "0.8.5" diff --git a/external-gateways/stellar/release.toml b/external-gateways/stellar/release.toml new file mode 100644 index 000000000..954564097 --- /dev/null +++ b/external-gateways/stellar/release.toml @@ -0,0 +1 @@ +release = false diff --git a/external-gateways/stellar/src/error.rs b/external-gateways/stellar/src/error.rs new file mode 100644 index 000000000..3722a04fe --- /dev/null +++ b/external-gateways/stellar/src/error.rs @@ -0,0 +1,11 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("unsupported type of public key")] + UnsupportedPublicKey, + #[error("invalid public key")] + InvalidPublicKey, + #[error("invalid destination address")] + InvalidDestinationAddress, +} diff --git a/external-gateways/stellar/src/lib.rs b/external-gateways/stellar/src/lib.rs new file mode 100644 index 000000000..279629793 --- /dev/null +++ b/external-gateways/stellar/src/lib.rs @@ -0,0 +1,341 @@ +pub mod error; + +use std::str::FromStr; + +use axelar_wasm_std::utils::TryMapExt; +use cosmwasm_std::Uint256; +use error_stack::{Report, ResultExt}; +use multisig::key::PublicKey; +use multisig::verifier_set::VerifierSet; +use sha3::{Digest, Keccak256}; +use stellar_strkey::Contract; +use stellar_xdr::curr::{ + BytesM, Error as XdrError, Hash, Limits, ScAddress, ScMapEntry, ScVal, StringM, VecM, WriteXdr, +}; + +use crate::error::Error; + +#[derive(Debug, Clone)] +pub enum CommandType { + ApproveMessages, + RotateSigners, +} + +impl CommandType { + pub fn as_str(&self) -> &'static str { + match self { + CommandType::ApproveMessages => "ApproveMessages", + CommandType::RotateSigners => "RotateSigners", + } + } +} + +impl TryFrom for ScVal { + type Error = XdrError; + + fn try_from(value: CommandType) -> Result { + let val: VecM = + vec![ScVal::Symbol(StringM::from_str(value.as_str())?.into())].try_into()?; + + Ok(ScVal::Vec(Some(val.into()))) + } +} + +#[derive(Debug, Clone)] +pub struct Message { + pub message_id: String, + pub source_chain: String, + pub source_address: String, + pub contract_address: Contract, + pub payload_hash: Hash, +} + +impl TryFrom<&router_api::Message> for Message { + type Error = Report; + + fn try_from(value: &router_api::Message) -> Result { + Ok(Self { + source_chain: value.cc_id.source_chain.to_string(), + message_id: value.cc_id.message_id.to_string(), + source_address: value.source_address.to_string(), + contract_address: Contract::from_string(value.destination_address.as_str()) + .change_context(Error::InvalidDestinationAddress) + .attach_printable(value.destination_address.to_string())?, + payload_hash: value.payload_hash.into(), + }) + } +} + +impl TryFrom for ScVal { + type Error = XdrError; + + fn try_from(value: Message) -> Result { + let keys: [&'static str; 5] = [ + "contract_address", + "message_id", + "payload_hash", + "source_address", + "source_chain", + ]; + + let vals: [ScVal; 5] = [ + ScVal::Address(ScAddress::Contract(Hash(value.contract_address.0))), + ScVal::String(StringM::from_str(&value.message_id)?.into()), + ScVal::Bytes(BytesM::try_from(AsRef::<[u8; 32]>::as_ref(&value.payload_hash))?.into()), + ScVal::String(StringM::from_str(&value.source_address)?.into()), + ScVal::String(StringM::from_str(&value.source_chain)?.into()), + ]; + + sc_map_from_slices(&keys, &vals) + } +} + +pub struct Messages(Vec); + +impl From> for Messages { + fn from(v: Vec) -> Self { + Messages(v) + } +} + +impl Messages { + pub fn messages_approval_hash(&self) -> Result<[u8; 32], XdrError> { + let messages = self + .0 + .iter() + .map(|message| message.clone().try_into()) + .collect::, _>>()?; + + let val: ScVal = (CommandType::ApproveMessages, messages) + .try_into() + .expect("must convert tuple of size 2 to ScVec"); + + let hash = Keccak256::digest(val.to_xdr(Limits::none())?); + + Ok(hash.into()) + } +} + +#[derive(Clone, Debug)] +pub struct WeightedSigner { + pub signer: BytesM<32>, + pub weight: u128, +} + +impl TryFrom for ScVal { + type Error = XdrError; + + fn try_from(value: WeightedSigner) -> Result { + let keys: [&'static str; 2] = ["signer", "weight"]; + + let vals: [ScVal; 2] = [ + ScVal::Bytes(value.signer.to_vec().try_into()?), + value.weight.into(), + ]; + + sc_map_from_slices(&keys, &vals) + } +} + +#[derive(Debug, Clone)] +pub struct WeightedSigners { + pub signers: Vec, + pub threshold: u128, + pub nonce: BytesM<32>, +} + +impl WeightedSigners { + pub fn hash(&self) -> Result<[u8; 32], XdrError> { + let val: ScVal = self.clone().try_into()?; + let hash = Keccak256::digest(val.to_xdr(Limits::none())?); + + Ok(hash.into()) + } + + pub fn signers_rotation_hash(&self) -> Result<[u8; 32], XdrError> { + let val: ScVal = (CommandType::RotateSigners, self.clone()) + .try_into() + .expect("must convert tuple of size 2 to ScVec"); + + let hash = Keccak256::digest(val.to_xdr(Limits::none())?); + + Ok(hash.into()) + } +} + +impl TryFrom for ScVal { + type Error = XdrError; + + fn try_from(value: WeightedSigners) -> Result { + let signers = value.signers.clone().try_map(|signer| signer.try_into())?; + + let keys: [&'static str; 3] = ["nonce", "signers", "threshold"]; + + let vals: [ScVal; 3] = [ + ScVal::Bytes(value.nonce.to_vec().try_into()?), + ScVal::Vec(Some(signers.try_into()?)), + value.threshold.into(), + ]; + + sc_map_from_slices(&keys, &vals) + } +} + +impl TryFrom<&VerifierSet> for WeightedSigners { + type Error = Report; + + fn try_from(value: &VerifierSet) -> Result { + let mut signers = value + .signers + .values() + .map(|signer| match &signer.pub_key { + PublicKey::Ed25519(key) => Ok(WeightedSigner { + signer: BytesM::try_from(key.as_ref()) + .change_context(Error::InvalidPublicKey) + .attach_printable(key.to_hex())?, + weight: signer.weight.into(), + }), + PublicKey::Ecdsa(_) => Err(Report::new(Error::UnsupportedPublicKey)), + }) + .collect::, _>>()?; + + signers.sort_by(|signer1, signer2| signer1.signer.cmp(&signer2.signer)); + + let nonce = Uint256::from(value.created_at) + .to_be_bytes() + .try_into() + .expect("must convert from 32 bytes"); + + Ok(Self { + signers, + threshold: value.threshold.into(), + nonce, + }) + } +} + +/// Form a new Map from a slice of symbol-names and a slice of values. Keys must be in sorted order. +fn sc_map_from_slices(keys: &[&str], vals: &[ScVal]) -> Result { + let vec: VecM = keys + .iter() + .zip(vals.iter()) + .map(|(key, val)| { + Ok(ScMapEntry { + key: ScVal::Symbol(StringM::from_str(key)?.into()), + val: val.clone(), + }) + }) + .collect::, XdrError>>()? + .try_into()?; + + Ok(ScVal::Map(Some(vec.into()))) +} + +#[cfg(test)] +mod test { + use axelar_wasm_std::FnExt; + use cosmwasm_std::HexBinary; + use serde::Serialize; + use stellar_xdr::curr::{Limits, ScVal, WriteXdr}; + + use crate::{CommandType, Message, Messages, WeightedSigner, WeightedSigners}; + + #[test] + fn command_type_encode() { + #[derive(Serialize)] + struct Encoded { + approve_messages: String, + rotate_signers: String, + } + let approve_messages = ScVal::try_from(CommandType::ApproveMessages) + .unwrap() + .to_xdr(Limits::none()) + .unwrap() + .then(HexBinary::from) + .to_hex(); + let rotate_signers = ScVal::try_from(CommandType::RotateSigners) + .unwrap() + .to_xdr(Limits::none()) + .unwrap() + .then(HexBinary::from) + .to_hex(); + + let encoded = Encoded { + approve_messages, + rotate_signers, + }; + + goldie::assert_json!(&encoded); + } + + #[test] + fn messages_approval_hash() { + let payload_hashes = [ + "cfa347779c9b646ddf628c4da721976ceb998f1ab2c097b52e66a575c3975a6c", + "fb5eb8245e3b8eb9d44f228ee142a3378f57d49fc95fa78d437ff8aa5dd564ba", + "90e3761c0794fbbd8b563a0d05d83395e7f88f64f30eebb7c5533329f6653e84", + "60e146cb9c548ba6e614a87910d8172c9d21279a3f8f4da256ff36e15b80ea30", + ]; + + let messages: Messages = (1..=4) + .map(|i| Message { + message_id: format!("test-{}", i), + source_chain: format!("source-{}", i), + source_address: "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + .to_string(), + contract_address: "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + .parse() + .unwrap(), + payload_hash: payload_hashes[i - 1].parse().expect("invalid hash"), + }) + .collect::>() + .into(); + goldie::assert!(HexBinary::from(messages.messages_approval_hash().unwrap()).to_hex()); + } + + #[test] + fn signers_rotation_hash() { + let weighted_signers = WeightedSigners { + signers: vec![ + WeightedSigner { + signer: "0a245a2a2a5e8ec439d1377579a08fc78ea55647ba6fcb1f5d8a360218e8a985" + .parse() + .unwrap(), + weight: 3, + }, + WeightedSigner { + signer: "0b422cf449d900f6f8eb97f62e35811c62eb75feb84dfccef44a5c1c3dbac2ad" + .parse() + .unwrap(), + weight: 2, + }, + WeightedSigner { + signer: "18c34bf01a11b5ba21ea11b1678f3035ef753f0bdb1d5014ec21037e8f99e2a2" + .parse() + .unwrap(), + weight: 4, + }, + WeightedSigner { + signer: "f683ca8a6d7fe55f25599bb64b01edcc5eeb85fe5b63d3a4f0b3c32405005518" + .parse() + .unwrap(), + weight: 4, + }, + WeightedSigner { + signer: "fbb4b870e800038f1379697fae3058938c59b696f38dd0fdf2659c0cf3a5b663" + .parse() + .unwrap(), + weight: 2, + }, + ], + threshold: 8, + nonce: "8784bf7be5a9baaeea47e12d9e8ad0dec29afcbc3617d97f771e3c24fa945dce" + .parse() + .unwrap(), + }; + + goldie::assert!( + HexBinary::from(weighted_signers.signers_rotation_hash().unwrap()).to_hex() + ); + } +} diff --git a/external-gateways/stellar/src/testdata/command_type_encode.golden b/external-gateways/stellar/src/testdata/command_type_encode.golden new file mode 100644 index 000000000..6db86d933 --- /dev/null +++ b/external-gateways/stellar/src/testdata/command_type_encode.golden @@ -0,0 +1,4 @@ +{ + "approve_messages": "0000001000000001000000010000000f0000000f417070726f76654d6573736167657300", + "rotate_signers": "0000001000000001000000010000000f0000000d526f746174655369676e657273000000" +} \ No newline at end of file diff --git a/external-gateways/stellar/src/testdata/messages_approval_hash.golden b/external-gateways/stellar/src/testdata/messages_approval_hash.golden new file mode 100644 index 000000000..4512173fc --- /dev/null +++ b/external-gateways/stellar/src/testdata/messages_approval_hash.golden @@ -0,0 +1 @@ +49f6a85aec4b4f72c667f2ba7950a692a59f462007abdbce0181e2982c0b602e \ No newline at end of file diff --git a/external-gateways/stellar/src/testdata/signers_rotation_hash.golden b/external-gateways/stellar/src/testdata/signers_rotation_hash.golden new file mode 100644 index 000000000..774e8446b --- /dev/null +++ b/external-gateways/stellar/src/testdata/signers_rotation_hash.golden @@ -0,0 +1 @@ +4ad8f3015146ac68334fd405f90e6ca75fbf2c276b333a8747c9ba83d9c3f1f6 \ No newline at end of file