From 94392b1dcc7d759445bbdaf11c127238f2c3039e Mon Sep 17 00:00:00 2001 From: CJ Cobb Date: Wed, 20 Sep 2023 15:41:00 -0400 Subject: [PATCH 1/5] feat(multisig-prover): encode bcs proof --- Cargo.lock | 18 +++ contracts/multisig-prover/Cargo.toml | 2 + contracts/multisig-prover/src/encoding/bcs.rs | 129 ++++++++++++++++++ contracts/multisig-prover/src/encoding/mod.rs | 1 + contracts/multisig-prover/src/error.rs | 3 + 5 files changed, 153 insertions(+) create mode 100644 contracts/multisig-prover/src/encoding/bcs.rs diff --git a/Cargo.lock b/Cargo.lock index da834117a..e283f8e40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -428,6 +428,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "bcs" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd3ffe8b19a604421a5d461d4a70346223e535903fbc3067138bddbebddcf77" +dependencies = [ + "serde", + "thiserror", +] + [[package]] name = "bech32" version = "0.9.1" @@ -3046,6 +3056,7 @@ dependencies = [ "anyhow", "axelar-wasm-std", "axelar-wasm-std-derive", + "bcs", "connection-router", "cosmwasm-schema", "cosmwasm-std", @@ -3069,6 +3080,7 @@ dependencies = [ "service-registry", "sha3", "thiserror", + "tuple-transpose", "voting-verifier", ] @@ -5404,6 +5416,12 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tuple-transpose" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1873fa6d09975af3b118126092a3988c4900b84026eb9c926e3c225cee70d18d" + [[package]] name = "typenum" version = "1.16.0" diff --git a/contracts/multisig-prover/Cargo.toml b/contracts/multisig-prover/Cargo.toml index 6ab434dd5..f735d4020 100644 --- a/contracts/multisig-prover/Cargo.toml +++ b/contracts/multisig-prover/Cargo.toml @@ -30,6 +30,7 @@ optimize = """docker run --rm -v "$(pwd)":/code \ [dependencies] axelar-wasm-std = { workspace = true } axelar-wasm-std-derive = { workspace = true } +bcs = { version = "0.1.5"} connection-router = { workspace = true, features = ["library"] } cosmwasm-schema = "1.1.3" cosmwasm-std = "1.1.3" @@ -52,6 +53,7 @@ serde_json = "1.0.89" service-registry = { workspace = true } sha3 = { version = "0.10", default-features = false, features = [] } thiserror = { workspace = true } +tuple-transpose = { version = "0.1.0" } voting-verifier = { workspace = true, features = ["library"] } [dev-dependencies] diff --git a/contracts/multisig-prover/src/encoding/bcs.rs b/contracts/multisig-prover/src/encoding/bcs.rs new file mode 100644 index 000000000..c2149f528 --- /dev/null +++ b/contracts/multisig-prover/src/encoding/bcs.rs @@ -0,0 +1,129 @@ +use bcs::to_bytes; +use cosmwasm_std::{HexBinary, Uint256}; +use itertools::Itertools; +use multisig::{key::Signature, msg::Signer}; + +use crate::{error::ContractError, types::Operator}; + +#[allow(dead_code)] +fn encode_proof( + quorum: Uint256, + signers: Vec<(Signer, Option)>, +) -> Result { + let mut operators = make_operators_with_sigs(signers)?; + operators.sort(); + + let (addresses, weights, signatures): (Vec<_>, Vec, Vec<_>) = operators + .iter() + .map(|op| { + ( + op.address.to_vec(), + u128::from_le_bytes( + op.weight.to_le_bytes()[..16] + .try_into() + .expect("couldn't convert u256 to u128"), + ) + .to_le(), + op.signature.as_ref().map(|sig| sig.as_ref().to_vec()), + ) + }) + .multiunzip(); + + let signatures: Vec> = signatures.into_iter().flatten().collect(); + let quorum = &u128::from_le_bytes( + quorum.to_le_bytes()[..16] + .try_into() + .expect("couldn't convert u256 to u128"), + ); + Ok(to_bytes(&(addresses, weights, quorum, signatures))?.into()) +} + +#[allow(dead_code)] +fn make_operators_with_sigs( + signers_with_sigs: Vec<(Signer, Option)>, +) -> Result, ContractError> { + Ok(signers_with_sigs + .into_iter() + .map(|(signer, sig)| Operator { + address: signer.pub_key.into(), + weight: signer.weight, + signature: sig, + }) + .collect()) +} + +#[cfg(test)] +mod test { + + use std::vec; + + use bcs::from_bytes; + use cosmwasm_std::{Addr, HexBinary, Uint256}; + use multisig::{ + key::{PublicKey, Signature}, + msg::Signer, + }; + + use super::encode_proof; + + #[test] + fn test_encode_proof() { + let signers = vec![ + (Signer { + address: Addr::unchecked("axelarvaloper1ff675m593vve8yh82lzhdnqfpu7m23cxstr6h4"), + weight: Uint256::from(10u128), + pub_key: PublicKey::Ecdsa( + HexBinary::from_hex( + "03c6ddb0fcee7b528da1ef3c9eed8d51eeacd7cc28a8baa25c33037c5562faa6e4", + ) + .unwrap(), + ), + }, + Some(Signature::Ecdsa( + HexBinary::from_hex("283786d844a7c4d1d424837074d0c8ec71becdcba4dd42b5307cb543a0e2c8b81c10ad541defd5ce84d2a608fc454827d0b65b4865c8192a2ea1736a5c4b72021b").unwrap()))), + (Signer { + address: Addr::unchecked("axelarvaloper1x86a8prx97ekkqej2x636utrdu23y8wupp9gk5"), + weight: Uint256::from(10u128), + pub_key: PublicKey::Ecdsa( + HexBinary::from_hex( + "03d123ce370b163acd576be0e32e436bb7e63262769881d35fa3573943bf6c6f81", + ) + .unwrap(), + ), + }, + Some(Signature::Ecdsa( + HexBinary::from_hex("283786d844a7c4d1d424837074d0c8ec71becdcba4dd42b5307cb543a0e2c8b81c10ad541defd5ce84d2a608fc454827d0b65b4865c8192a2ea1736a5c4b72021b").unwrap())))]; + + let quorum = Uint256::from(10u128); + let proof = encode_proof(quorum, signers.clone()); + + assert!(proof.is_ok()); + let proof = proof.unwrap(); + let decoded_proof: Result<(Vec>, Vec, u128, Vec>), _> = + from_bytes(&proof); + assert!(decoded_proof.is_ok()); + let (operators, weights, quorum_decoded, signatures): ( + Vec>, + Vec, + u128, + Vec>, + ) = decoded_proof.unwrap(); + + assert_eq!(operators.len(), signers.len()); + assert_eq!(weights.len(), signers.len()); + assert_eq!(signatures.len(), signers.len()); + assert_eq!(quorum_decoded, 10u128); + + for i in 0..signers.len() { + assert_eq!( + operators[i], + HexBinary::from(signers[i].0.pub_key.clone()).to_vec() + ); + assert_eq!(weights[i], 10u128); + assert_eq!( + signatures[i], + HexBinary::from(signers[i].1.clone().unwrap()).to_vec() + ); + } + } +} diff --git a/contracts/multisig-prover/src/encoding/mod.rs b/contracts/multisig-prover/src/encoding/mod.rs index 4236e38a5..4e41d312e 100644 --- a/contracts/multisig-prover/src/encoding/mod.rs +++ b/contracts/multisig-prover/src/encoding/mod.rs @@ -1,4 +1,5 @@ mod abi; +mod bcs; use axelar_wasm_std::operators::Operators; use cosmwasm_schema::cw_serde; diff --git a/contracts/multisig-prover/src/error.rs b/contracts/multisig-prover/src/error.rs index e236b6025..e269465b3 100644 --- a/contracts/multisig-prover/src/error.rs +++ b/contracts/multisig-prover/src/error.rs @@ -35,6 +35,9 @@ pub enum ContractError { #[error(transparent)] NonEmptyError(#[from] nonempty::Error), + #[error(transparent)] + BcsError(#[from] bcs::Error), + #[error("worker set has not changed sufficiently since last update")] WorkerSetUnchanged, From 01979db0f5a2914be634426725dc6e80b50d01e9 Mon Sep 17 00:00:00 2001 From: CJ Cobb Date: Wed, 20 Sep 2023 19:44:30 -0400 Subject: [PATCH 2/5] fix lint --- Cargo.lock | 7 ------- contracts/multisig-prover/Cargo.toml | 3 +-- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e283f8e40..5d22c6fdb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3080,7 +3080,6 @@ dependencies = [ "service-registry", "sha3", "thiserror", - "tuple-transpose", "voting-verifier", ] @@ -5416,12 +5415,6 @@ dependencies = [ "utf-8", ] -[[package]] -name = "tuple-transpose" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1873fa6d09975af3b118126092a3988c4900b84026eb9c926e3c225cee70d18d" - [[package]] name = "typenum" version = "1.16.0" diff --git a/contracts/multisig-prover/Cargo.toml b/contracts/multisig-prover/Cargo.toml index f735d4020..27d76c5d0 100644 --- a/contracts/multisig-prover/Cargo.toml +++ b/contracts/multisig-prover/Cargo.toml @@ -30,7 +30,7 @@ optimize = """docker run --rm -v "$(pwd)":/code \ [dependencies] axelar-wasm-std = { workspace = true } axelar-wasm-std-derive = { workspace = true } -bcs = { version = "0.1.5"} +bcs = "0.1.5" connection-router = { workspace = true, features = ["library"] } cosmwasm-schema = "1.1.3" cosmwasm-std = "1.1.3" @@ -53,7 +53,6 @@ serde_json = "1.0.89" service-registry = { workspace = true } sha3 = { version = "0.10", default-features = false, features = [] } thiserror = { workspace = true } -tuple-transpose = { version = "0.1.0" } voting-verifier = { workspace = true, features = ["library"] } [dev-dependencies] From 3cdb4c797f2e925657297c840ca3d10acc990749 Mon Sep 17 00:00:00 2001 From: CJ Cobb Date: Thu, 21 Sep 2023 15:09:40 -0400 Subject: [PATCH 3/5] review feedback --- contracts/multisig-prover/src/encoding/bcs.rs | 47 ++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/contracts/multisig-prover/src/encoding/bcs.rs b/contracts/multisig-prover/src/encoding/bcs.rs index c2149f528..34418a8ea 100644 --- a/contracts/multisig-prover/src/encoding/bcs.rs +++ b/contracts/multisig-prover/src/encoding/bcs.rs @@ -5,12 +5,22 @@ use multisig::{key::Signature, msg::Signer}; use crate::{error::ContractError, types::Operator}; + +fn u256_to_u128(val: Uint256) -> u128 { + assert!(val <= Uint256::from(u128::MAX), "couldn't convert u256 ({}) to u128", val); + u128::from_le_bytes( + val.to_le_bytes()[..16] + .try_into() + .expect("couldn't convert u256 to u128"), + ) +} + #[allow(dead_code)] fn encode_proof( quorum: Uint256, signers: Vec<(Signer, Option)>, ) -> Result { - let mut operators = make_operators_with_sigs(signers)?; + let mut operators = make_operators_with_sigs(signers); operators.sort(); let (addresses, weights, signatures): (Vec<_>, Vec, Vec<_>) = operators @@ -18,38 +28,28 @@ fn encode_proof( .map(|op| { ( op.address.to_vec(), - u128::from_le_bytes( - op.weight.to_le_bytes()[..16] - .try_into() - .expect("couldn't convert u256 to u128"), - ) - .to_le(), + u256_to_u128(op.weight), op.signature.as_ref().map(|sig| sig.as_ref().to_vec()), ) }) .multiunzip(); let signatures: Vec> = signatures.into_iter().flatten().collect(); - let quorum = &u128::from_le_bytes( - quorum.to_le_bytes()[..16] - .try_into() - .expect("couldn't convert u256 to u128"), - ); + let quorum = u256_to_u128(quorum); Ok(to_bytes(&(addresses, weights, quorum, signatures))?.into()) } -#[allow(dead_code)] fn make_operators_with_sigs( signers_with_sigs: Vec<(Signer, Option)>, -) -> Result, ContractError> { - Ok(signers_with_sigs +) -> Vec { + signers_with_sigs .into_iter() .map(|(signer, sig)| Operator { address: signer.pub_key.into(), weight: signer.weight, signature: sig, }) - .collect()) + .collect() } #[cfg(test)] @@ -64,8 +64,23 @@ mod test { msg::Signer, }; + use crate::encoding::bcs::u256_to_u128; + use super::encode_proof; + #[test] + fn test_u256_to_u128() { + let val = u128::MAX; + assert_eq!(val, u256_to_u128(Uint256::from(val))); + } + + + #[test] + #[should_panic] + fn test_u256_to_u128_fails() { + let _ = u256_to_u128(Uint256::MAX); + } + #[test] fn test_encode_proof() { let signers = vec![ From f4068c7b377eaf42267dbc7734b5d005d1d55684 Mon Sep 17 00:00:00 2001 From: CJ Cobb Date: Thu, 21 Sep 2023 15:22:10 -0400 Subject: [PATCH 4/5] lint --- contracts/multisig-prover/src/encoding/bcs.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/multisig-prover/src/encoding/bcs.rs b/contracts/multisig-prover/src/encoding/bcs.rs index 34418a8ea..fcbb67fde 100644 --- a/contracts/multisig-prover/src/encoding/bcs.rs +++ b/contracts/multisig-prover/src/encoding/bcs.rs @@ -5,9 +5,12 @@ use multisig::{key::Signature, msg::Signer}; use crate::{error::ContractError, types::Operator}; - fn u256_to_u128(val: Uint256) -> u128 { - assert!(val <= Uint256::from(u128::MAX), "couldn't convert u256 ({}) to u128", val); + assert!( + val <= Uint256::from(u128::MAX), + "couldn't convert u256 ({}) to u128", + val + ); u128::from_le_bytes( val.to_le_bytes()[..16] .try_into() @@ -39,9 +42,7 @@ fn encode_proof( Ok(to_bytes(&(addresses, weights, quorum, signatures))?.into()) } -fn make_operators_with_sigs( - signers_with_sigs: Vec<(Signer, Option)>, -) -> Vec { +fn make_operators_with_sigs(signers_with_sigs: Vec<(Signer, Option)>) -> Vec { signers_with_sigs .into_iter() .map(|(signer, sig)| Operator { @@ -74,7 +75,6 @@ mod test { assert_eq!(val, u256_to_u128(Uint256::from(val))); } - #[test] #[should_panic] fn test_u256_to_u128_fails() { From 47591cfce5e60eb9ea59c4bd20d2624a124b5ad1 Mon Sep 17 00:00:00 2001 From: CJ Cobb Date: Fri, 22 Sep 2023 11:04:19 -0400 Subject: [PATCH 5/5] review --- contracts/multisig-prover/src/encoding/bcs.rs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/contracts/multisig-prover/src/encoding/bcs.rs b/contracts/multisig-prover/src/encoding/bcs.rs index fcbb67fde..33e132ecb 100644 --- a/contracts/multisig-prover/src/encoding/bcs.rs +++ b/contracts/multisig-prover/src/encoding/bcs.rs @@ -6,16 +6,7 @@ use multisig::{key::Signature, msg::Signer}; use crate::{error::ContractError, types::Operator}; fn u256_to_u128(val: Uint256) -> u128 { - assert!( - val <= Uint256::from(u128::MAX), - "couldn't convert u256 ({}) to u128", - val - ); - u128::from_le_bytes( - val.to_le_bytes()[..16] - .try_into() - .expect("couldn't convert u256 to u128"), - ) + val.to_string().parse().expect("value is larger than u128") } #[allow(dead_code)] @@ -24,9 +15,9 @@ fn encode_proof( signers: Vec<(Signer, Option)>, ) -> Result { let mut operators = make_operators_with_sigs(signers); - operators.sort(); + operators.sort(); // gateway requires operators to be sorted - let (addresses, weights, signatures): (Vec<_>, Vec, Vec<_>) = operators + let (addresses, weights, signatures): (Vec<_>, Vec<_>, Vec<_>) = operators .iter() .map(|op| { (