diff --git a/rust/tw_cosmos_sdk/src/modules/compiler.rs b/rust/tw_cosmos_sdk/src/modules/compiler.rs new file mode 100644 index 00000000000..6419678adfc --- /dev/null +++ b/rust/tw_cosmos_sdk/src/modules/compiler.rs @@ -0,0 +1,175 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::context::CosmosContext; +use crate::modules::broadcast_msg::{BroadcastMode, BroadcastMsg}; +use crate::modules::serializer::json_serializer::JsonSerializer; +use crate::modules::serializer::protobuf_serializer::ProtobufSerializer; +use crate::modules::signer::json_signer::JsonSigner; +use crate::modules::signer::protobuf_signer::ProtobufSigner; +use crate::modules::tx_builder::TxBuilder; +use crate::public_key::CosmosPublicKey; +use std::borrow::Cow; +use std::marker::PhantomData; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_coin_entry::signing_output_error; +use tw_proto::Cosmos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct TWTransactionCompiler { + _phantom: PhantomData, +} + +impl TWTransactionCompiler { + #[inline] + pub fn preimage_hashes( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> CompilerProto::PreSigningOutput<'static> { + match input.signing_mode { + Proto::SigningMode::JSON => Self::preimage_hashes_as_json(coin, input), + Proto::SigningMode::Protobuf => Self::preimage_hashes_as_protobuf(coin, input), + } + .unwrap_or_else(|e| signing_output_error!(CompilerProto::PreSigningOutput, e)) + } + + #[inline] + pub fn compile( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + signature_data: SignatureBytes, + public_key: PublicKeyBytes, + ) -> Proto::SigningOutput<'static> { + match input.signing_mode { + Proto::SigningMode::JSON => { + Self::compile_as_json(coin, input, signature_data, public_key) + }, + Proto::SigningMode::Protobuf => { + Self::compile_as_protobuf(coin, input, signature_data, public_key) + }, + } + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + pub fn preimage_hashes_as_protobuf( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + let preimage = match TxBuilder::::try_sign_direct_args(&input) { + // If there was a `SignDirect` message in the signing input, generate the tx preimage directly. + Ok(Some(sign_direct_args)) => { + ProtobufSigner::::preimage_hash_direct(&sign_direct_args)? + }, + // Otherwise, generate the tx preimage by using `TxBuilder`. + _ => { + // Please note the [`Proto::SigningInput::public_key`] should be set already. + let unsigned_tx = TxBuilder::::unsigned_tx_from_proto(coin, &input)?; + ProtobufSigner::::preimage_hash(&unsigned_tx)? + }, + }; + + Ok(CompilerProto::PreSigningOutput { + data: Cow::from(preimage.encoded_tx), + data_hash: Cow::from(preimage.tx_hash), + ..CompilerProto::PreSigningOutput::default() + }) + } + + pub fn preimage_hashes_as_json( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + // Please note the [`Proto::SigningInput::public_key`] should be set already. + let unsigned_tx = TxBuilder::::unsigned_tx_from_proto(coin, &input)?; + let preimage = JsonSigner::preimage_hash(&unsigned_tx)?; + + Ok(CompilerProto::PreSigningOutput { + data: Cow::from(preimage.encoded_tx.as_bytes().to_vec()), + data_hash: Cow::from(preimage.tx_hash), + ..CompilerProto::PreSigningOutput::default() + }) + } + + pub fn compile_as_protobuf( + coin: &dyn CoinContext, + mut input: Proto::SigningInput<'_>, + signature_data: SignatureBytes, + public_key: PublicKeyBytes, + ) -> SigningResult> { + let public_key = Context::PublicKey::from_bytes(coin, &public_key)?; + + let signed_tx_raw = match TxBuilder::::try_sign_direct_args(&input) { + // If there was a `SignDirect` message in the signing input, generate the `TxRaw` directly. + Ok(Some(sign_direct_args)) => ProtobufSerializer::::build_direct_signed_tx( + &sign_direct_args, + signature_data.clone(), + ), + // Otherwise, generate the `TxRaw` by using `TxBuilder`. + _ => { + // Set the public key. It will be used to construct a signer info. + input.public_key = Cow::from(public_key.to_bytes()); + let unsigned_tx = TxBuilder::::unsigned_tx_from_proto(coin, &input)?; + let signed_tx = unsigned_tx.into_signed(signature_data.clone()); + + ProtobufSerializer::build_signed_tx(&signed_tx)? + }, + }; + + let broadcast_mode = Self::broadcast_mode(input.mode); + let broadcast_tx = BroadcastMsg::raw(broadcast_mode, &signed_tx_raw).to_json_string(); + + let signature_json = + JsonSerializer::::serialize_signature(&public_key, signature_data.clone()); + let signature_json = serde_json::to_string(&[signature_json]) + .map_err(|_| SigningError(SigningErrorType::Error_internal))?; + + Ok(Proto::SigningOutput { + signature: Cow::from(signature_data), + signature_json: Cow::from(signature_json), + serialized: Cow::from(broadcast_tx), + ..Proto::SigningOutput::default() + }) + } + + pub fn compile_as_json( + coin: &dyn CoinContext, + mut input: Proto::SigningInput<'_>, + signature_data: SignatureBytes, + public_key: PublicKeyBytes, + ) -> SigningResult> { + let public_key = Context::PublicKey::from_bytes(coin, &public_key)?; + + // Set the public key. It will be used to construct a signer info. + input.public_key = Cow::from(public_key.to_bytes()); + let unsigned_tx = TxBuilder::::unsigned_tx_from_proto(coin, &input)?; + let signed_tx = unsigned_tx.into_signed(signature_data.clone()); + + let signed_tx_json = JsonSerializer::build_signed_tx(&signed_tx)?; + + let broadcast_mode = Self::broadcast_mode(input.mode); + let broadcast_tx = BroadcastMsg::json(broadcast_mode, &signed_tx_json)?.to_json_string(); + + let signature_json = serde_json::to_string(&signed_tx_json.signatures) + .map_err(|_| SigningError(SigningErrorType::Error_internal))?; + + Ok(Proto::SigningOutput { + signature: Cow::from(signature_data), + signature_json: Cow::from(signature_json), + serialized: Cow::from(broadcast_tx), + ..Proto::SigningOutput::default() + }) + } + + fn broadcast_mode(input: Proto::BroadcastMode) -> BroadcastMode { + match input { + Proto::BroadcastMode::BLOCK => BroadcastMode::Block, + Proto::BroadcastMode::SYNC => BroadcastMode::Sync, + Proto::BroadcastMode::ASYNC => BroadcastMode::Async, + } + } +} diff --git a/rust/tw_cosmos_sdk/src/modules/mod.rs b/rust/tw_cosmos_sdk/src/modules/mod.rs index cf1955eab41..2a667107a85 100644 --- a/rust/tw_cosmos_sdk/src/modules/mod.rs +++ b/rust/tw_cosmos_sdk/src/modules/mod.rs @@ -5,6 +5,7 @@ // file LICENSE at the root of the source code distribution tree. pub mod broadcast_msg; +pub mod compiler; pub mod serializer; pub mod signer; pub mod tx_builder; diff --git a/rust/tw_cosmos_sdk/src/modules/signer/json_signer.rs b/rust/tw_cosmos_sdk/src/modules/signer/json_signer.rs index 9853371428b..9f4cc907ff4 100644 --- a/rust/tw_cosmos_sdk/src/modules/signer/json_signer.rs +++ b/rust/tw_cosmos_sdk/src/modules/signer/json_signer.rs @@ -12,6 +12,12 @@ use crate::public_key::JsonPublicKey; use crate::transaction::{SignedTransaction, UnsignedTransaction}; use std::marker::PhantomData; use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_memory::Data; + +pub struct JsonTxPreimage { + pub encoded_tx: String, + pub tx_hash: Data, +} pub struct JsonSigner { _phantom: PhantomData, @@ -25,13 +31,21 @@ where private_key: &Context::PrivateKey, unsigned: UnsignedTransaction, ) -> SigningResult> { + let JsonTxPreimage { tx_hash, .. } = Self::preimage_hash(&unsigned)?; + let signature_data = private_key.sign_tx_hash(&tx_hash)?; + + Ok(unsigned.into_signed(signature_data)) + } + + pub fn preimage_hash(unsigned: &UnsignedTransaction) -> SigningResult { let tx_to_sign = JsonSerializer::build_unsigned_tx(&unsigned)?; let encoded_tx = serde_json::to_string(&tx_to_sign) .map_err(|_| SigningError(SigningErrorType::Error_internal))?; + let tx_hash = Context::TxHasher::hash_json_tx(&encoded_tx); - let hash_to_sign = Context::TxHasher::hash_json_tx(&encoded_tx); - let signature_data = private_key.sign_tx_hash(&hash_to_sign)?; - - Ok(unsigned.into_signed(signature_data)) + Ok(JsonTxPreimage { + encoded_tx, + tx_hash, + }) } } diff --git a/rust/tw_cosmos_sdk/src/modules/signer/protobuf_signer.rs b/rust/tw_cosmos_sdk/src/modules/signer/protobuf_signer.rs index a6164010183..800bb1a3467 100644 --- a/rust/tw_cosmos_sdk/src/modules/signer/protobuf_signer.rs +++ b/rust/tw_cosmos_sdk/src/modules/signer/protobuf_signer.rs @@ -11,8 +11,14 @@ use crate::private_key::{CosmosPrivateKey, SignatureData}; use crate::transaction::{SignedTransaction, UnsignedTransaction}; use std::marker::PhantomData; use tw_coin_entry::error::SigningResult; +use tw_memory::Data; use tw_proto::serialize; +pub struct ProtobufTxPreimage { + pub encoded_tx: Data, + pub tx_hash: Data, +} + pub struct ProtobufSigner { _phantom: PhantomData, } @@ -22,12 +28,8 @@ impl ProtobufSigner { private_key: &Context::PrivateKey, unsigned: UnsignedTransaction, ) -> SigningResult> { - let tx_to_sign = ProtobufSerializer::build_sign_doc(&unsigned)?; - let encoded_tx = serialize(&tx_to_sign)?; - - let hash_to_sign = Context::TxHasher::hash_sign_doc(&encoded_tx); - let signature_data = private_key.sign_tx_hash(&hash_to_sign)?; - + let ProtobufTxPreimage { tx_hash, .. } = Self::preimage_hash(&unsigned)?; + let signature_data = private_key.sign_tx_hash(&tx_hash)?; Ok(unsigned.into_signed(signature_data)) } @@ -35,10 +37,31 @@ impl ProtobufSigner { private_key: &Context::PrivateKey, args: &SignDirectArgs, ) -> SigningResult { + let ProtobufTxPreimage { tx_hash, .. } = Self::preimage_hash_direct(args)?; + private_key.sign_tx_hash(&tx_hash) + } + + pub fn preimage_hash( + unsigned: &UnsignedTransaction, + ) -> SigningResult { + let tx_to_sign = ProtobufSerializer::build_sign_doc(unsigned)?; + let encoded_tx = serialize(&tx_to_sign)?; + let tx_hash = Context::TxHasher::hash_sign_doc(&encoded_tx); + + Ok(ProtobufTxPreimage { + encoded_tx, + tx_hash, + }) + } + + pub fn preimage_hash_direct(args: &SignDirectArgs) -> SigningResult { let tx_to_sign = ProtobufSerializer::::build_direct_sign_doc(args); let encoded_tx = serialize(&tx_to_sign)?; + let tx_hash = Context::TxHasher::hash_sign_doc(&encoded_tx); - let hash_to_sign = Context::TxHasher::hash_sign_doc(&encoded_tx); - private_key.sign_tx_hash(&hash_to_sign) + Ok(ProtobufTxPreimage { + encoded_tx, + tx_hash, + }) } } diff --git a/rust/tw_cosmos_sdk/src/modules/signer/tw_signer.rs b/rust/tw_cosmos_sdk/src/modules/signer/tw_signer.rs index f6dc7b65303..cee28a9e47a 100644 --- a/rust/tw_cosmos_sdk/src/modules/signer/tw_signer.rs +++ b/rust/tw_cosmos_sdk/src/modules/signer/tw_signer.rs @@ -132,6 +132,7 @@ impl TWSigner { }) } + // TODO remove fn broadcast_mode(input: Proto::BroadcastMode) -> BroadcastMode { match input { Proto::BroadcastMode::BLOCK => BroadcastMode::Block, diff --git a/rust/tw_cosmos_sdk/src/private_key/secp256k1.rs b/rust/tw_cosmos_sdk/src/private_key/secp256k1.rs index c2ccbc3ef7a..617361711dd 100644 --- a/rust/tw_cosmos_sdk/src/private_key/secp256k1.rs +++ b/rust/tw_cosmos_sdk/src/private_key/secp256k1.rs @@ -12,6 +12,7 @@ use tw_keypair::tw::Curve; use tw_keypair::KeyPairError; use tw_memory::Data; +#[derive(Clone)] pub struct Secp256PrivateKey(tw::PrivateKey); impl AsRef for Secp256PrivateKey { diff --git a/rust/tw_cosmos_sdk/src/test_utils/sign_utils.rs b/rust/tw_cosmos_sdk/src/test_utils/sign_utils.rs index 6357b865497..63f7e135c70 100644 --- a/rust/tw_cosmos_sdk/src/test_utils/sign_utils.rs +++ b/rust/tw_cosmos_sdk/src/test_utils/sign_utils.rs @@ -5,10 +5,11 @@ // file LICENSE at the root of the source code distribution tree. use crate::context::CosmosContext; +use crate::modules::compiler::TWTransactionCompiler; use crate::modules::signer::tw_signer::TWSigner; use tw_coin_entry::coin_context::CoinContext; use tw_coin_entry::error::SigningErrorType; -use tw_encoding::hex::ToHex; +use tw_encoding::hex::{DecodeHex, ToHex}; use tw_proto::Common::Proto::SigningError; use tw_proto::Cosmos::Proto; @@ -24,6 +25,22 @@ pub struct TestInput<'a> { pub signature_json: &'a str, } +#[derive(Clone)] +pub struct TestCompileInput<'a> { + pub coin: &'a dyn CoinContext, + pub input: Proto::SigningInput<'a>, + /// Either a stringified JSON object or a hex-encoded serialzied `SignDoc`. + pub tx_preimage: &'a str, + /// Expected transaction preimage hash. + pub tx_prehash: &'a str, + /// Stringified JSON object. + pub tx: &'a str, + /// Signature hex-encoded. + pub signature: &'a str, + /// Stringified signature JSON object. + pub signature_json: &'a str, +} + #[derive(Clone)] pub struct TestErrorInput<'a> { pub coin: &'a dyn CoinContext, @@ -31,6 +48,18 @@ pub struct TestErrorInput<'a> { pub error: SigningErrorType, } +#[track_caller] +pub fn test_compile_protobuf(mut test_input: TestCompileInput<'_>) { + test_input.input.signing_mode = Proto::SigningMode::Protobuf; + test_compile_impl::(test_input); +} + +#[track_caller] +pub fn test_compile_json(mut test_input: TestCompileInput<'_>) { + test_input.input.signing_mode = Proto::SigningMode::JSON; + test_compile_impl::(test_input); +} + #[track_caller] pub fn test_sign_protobuf(mut test_input: TestInput<'_>) { test_input.input.signing_mode = Proto::SigningMode::Protobuf; @@ -80,3 +109,59 @@ fn test_sign_impl(test_input: TestInput<'_>) { "Unexpected signature JSON" ); } + +#[track_caller] +fn test_compile_impl(test_input: TestCompileInput<'_>) { + // First step - generate the preimage hashes. + let preimage_output = TWTransactionCompiler::::preimage_hashes( + test_input.coin, + test_input.input.clone(), + ); + assert_eq!(preimage_output.error, SigningError::OK); + assert!(preimage_output.error_message.is_empty()); + + let actual_str = match String::from_utf8(preimage_output.data.to_vec()) { + Ok(actual_tx) => actual_tx, + Err(_) => preimage_output.data.to_hex(), + }; + + assert_eq!( + actual_str, test_input.tx_preimage, + "Unexpected preimage transaction" + ); + assert_eq!( + preimage_output.data_hash.to_hex(), + test_input.tx_prehash, + "Unexpected preimage hash" + ); + + // Second step - Compile the transaction. + + let public_key = test_input.input.public_key.to_vec(); + let compile_output = TWTransactionCompiler::::compile( + test_input.coin, + test_input.input, + test_input.signature.decode_hex().unwrap(), + public_key, + ); + + assert_eq!(compile_output.error, SigningError::OK); + assert!(compile_output.error_message.is_empty()); + + let result_tx = if compile_output.serialized.is_empty() { + compile_output.json + } else { + compile_output.serialized + }; + assert_eq!(result_tx, test_input.tx, "Unexpected result transaction"); + + assert_eq!( + compile_output.signature.to_hex(), + test_input.signature, + "Unexpected signature" + ); + assert_eq!( + compile_output.signature_json, test_input.signature_json, + "Unexpected signature JSON" + ); +} diff --git a/rust/tw_cosmos_sdk/tests/compile.rs b/rust/tw_cosmos_sdk/tests/compile.rs new file mode 100644 index 00000000000..78dd632c4a8 --- /dev/null +++ b/rust/tw_cosmos_sdk/tests/compile.rs @@ -0,0 +1,101 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::test_utils::proto_utils::{make_amount, make_fee, make_message}; +use tw_cosmos_sdk::test_utils::sign_utils::{ + test_compile_json, test_compile_protobuf, TestCompileInput, +}; +use tw_encoding::hex::DecodeHex; +use tw_keypair::tw::PublicKeyType; +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + +#[test] +fn test_compile_with_signatures() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let send_msg = Proto::mod_Message::Send { + from_address: "cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx".into(), + to_address: "cosmos18s0hdnsllgcclweu9aymw4ngktr2k0rkygdzdp".into(), + amounts: vec![make_amount("uatom", "400000")], + ..Proto::mod_Message::Send::default() + }; + + let input = Proto::SigningInput { + account_number: 546179, + chain_id: "cosmoshub-4".into(), + sequence: 0, + fee: Some(make_fee(200000, make_amount("uatom", "1000"))), + public_key: "02ecef5ce437a302c67f95468de4b31f36e911f467d7e6a52b41c1e13e1d563649" + .decode_hex() + .unwrap() + .into(), + messages: vec![make_message(MessageEnum::send_coins_message(send_msg))], + ..Proto::SigningInput::default() + }; + + test_compile_protobuf::(TestCompileInput { + coin: &coin, + input: input.clone(), + tx_preimage: "0a92010a8f010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e64126f0a2d636f736d6f73316d6b793639636e38656b74777930383435766563397570736470686b7478743033676b776c78122d636f736d6f733138733068646e736c6c6763636c7765753961796d77346e676b7472326b30726b7967647a64701a0f0a057561746f6d120634303030303012650a4e0a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a2102ecef5ce437a302c67f95468de4b31f36e911f467d7e6a52b41c1e13e1d56364912040a02080112130a0d0a057561746f6d12043130303010c09a0c1a0b636f736d6f736875622d342083ab21", + tx_prehash: "fa7990e1814c900efaedf1bdbedba22c22336675befe0ae39974130fc204f3de", + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CpIBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLWNvc21vczFta3k2OWNuOGVrdHd5MDg0NXZlYzl1cHNkcGhrdHh0MDNna3dseBItY29zbW9zMThzMGhkbnNsbGdjY2x3ZXU5YXltdzRuZ2t0cjJrMHJreWdkemRwGg8KBXVhdG9tEgY0MDAwMDASZQpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJEgQKAggBEhMKDQoFdWF0b20SBDEwMDAQwJoMGkCvvVE6d29P30cO9/lnXyGunWMPxNY12NuqDcCnFkNM0H4CUQdl1Gc9+ogIJbro5nyzZzlv9rl2/GsZox/JXoCX"}"#, + signature: "afbd513a776f4fdf470ef7f9675f21ae9d630fc4d635d8dbaa0dc0a716434cd07e02510765d4673dfa880825bae8e67cb367396ff6b976fc6b19a31fc95e8097", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJ"},"signature":"r71ROndvT99HDvf5Z18hrp1jD8TWNdjbqg3ApxZDTNB+AlEHZdRnPfqICCW66OZ8s2c5b/a5dvxrGaMfyV6Alw=="}]"#, + }); + + test_compile_json::(TestCompileInput { + coin: &coin, + input: input.clone(), + tx_preimage: r#"{"account_number":"546179","chain_id":"cosmoshub-4","fee":{"amount":[{"amount":"1000","denom":"uatom"}],"gas":"200000"},"memo":"","msgs":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"400000","denom":"uatom"}],"from_address":"cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx","to_address":"cosmos18s0hdnsllgcclweu9aymw4ngktr2k0rkygdzdp"}}],"sequence":"0"}"#, + tx_prehash: "0a31f6cd50f1a5c514929ba68a977e222a7df2dc11e8470e93118cc3545e6b37", + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"1000","denom":"uatom"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"400000","denom":"uatom"}],"from_address":"cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx","to_address":"cosmos18s0hdnsllgcclweu9aymw4ngktr2k0rkygdzdp"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJ"},"signature":"tTyOrburrHEHa14qiw78e9StoZyyGmoku98IxYrWCmtN8Qo5mTeKa0BKKDfgG4LmmNdwYcrXtqQQ7F4dL3c26g=="}]}}"#, + signature: "b53c8eadbbabac71076b5e2a8b0efc7bd4ada19cb21a6a24bbdf08c58ad60a6b4df10a3999378a6b404a2837e01b82e698d77061cad7b6a410ec5e1d2f7736ea", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJ"},"signature":"tTyOrburrHEHa14qiw78e9StoZyyGmoku98IxYrWCmtN8Qo5mTeKa0BKKDfgG4LmmNdwYcrXtqQQ7F4dL3c26g=="}]"#, + }); +} + +#[test] +fn test_compile_with_signatures_direct() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let body_bytes = "0a89010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412690a2d636f736d6f733168736b366a727979716a6668703564686335357463396a74636b796778306570683664643032122d636f736d6f73317a743530617a7570616e716c66616d356166687633686578777975746e756b656834633537331a090a046d756f6e120131".decode_hex().unwrap(); + let auth_info_bytes = "0a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a210257286ec3f37d33557bbbaa000b27744ac9023aa9967cae75a181d1ff91fa9dc512040a020801180812110a0b0a046d756f6e120332303010c09a0c".decode_hex().unwrap(); + let sign_direct = Proto::mod_Message::SignDirect { + body_bytes: Cow::from(body_bytes), + auth_info_bytes: Cow::from(auth_info_bytes), + }; + let input = Proto::SigningInput { + account_number: 1037, + chain_id: "gaia-13003".into(), + public_key: "0257286ec3f37d33557bbbaa000b27744ac9023aa9967cae75a181d1ff91fa9dc5" + .decode_hex() + .unwrap() + .into(), + messages: vec![make_message(MessageEnum::sign_direct_message(sign_direct))], + ..Proto::SigningInput::default() + }; + + // real-world tx: https://www.mintscan.io/cosmos/txs/817101F3D96314AD028733248B28BAFAD535024D7D2C8875D3FE31DC159F096B + // curl -H 'Content-Type: application/json' --data-binary '{"tx_bytes": "Cr4BCr...1yKOU=", "mode": "BROADCAST_MODE_BLOCK"}' https://api.cosmos.network/cosmos/tx/v1beta1/txs + // also similar TX: BCDAC36B605576C8182C2829C808B30A69CAD4959D5ED1E6FF9984ABF280D603 + test_compile_protobuf::(TestCompileInput { + coin: &coin, + input: input.clone(), + tx_preimage: "0a8c010a89010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412690a2d636f736d6f733168736b366a727979716a6668703564686335357463396a74636b796778306570683664643032122d636f736d6f73317a743530617a7570616e716c66616d356166687633686578777975746e756b656834633537331a090a046d756f6e12013112650a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a210257286ec3f37d33557bbbaa000b27744ac9023aa9967cae75a181d1ff91fa9dc512040a020801180812110a0b0a046d756f6e120332303010c09a0c1a0a676169612d3133303033208d08", + tx_prehash: "8a6e6f74625fd39707843360120874853cc0c1d730b087f3939f4b187c75b907", + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H"}"#, + signature: "f9e1f4001657a42009c4eb6859625d2e41e961fc72efd2842909c898e439fc1f549916e4ecac676ee353c7d54c5ae30a29b4210b8bff0ebfdcb375e105002f47", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"+eH0ABZXpCAJxOtoWWJdLkHpYfxy79KEKQnImOQ5/B9UmRbk7KxnbuNTx9VMWuMKKbQhC4v/Dr/cs3XhBQAvRw=="}]"#, + }); +}