Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Cosmos]: Add ability to sign direct with TxBody bytes only #4202

Merged
merged 1 commit into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions rust/tw_cosmos_sdk/src/modules/compiler/tw_compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ impl<Context: CosmosContext> TWTransactionCompiler<Context> {
input: Proto::SigningInput<'_>,
) -> SigningResult<CompilerProto::PreSigningOutput<'static>> {
let tx_hasher = TxBuilder::<Context>::tx_hasher_from_proto(&input);
let preimage = match TxBuilder::<Context>::try_sign_direct_args(&input) {
let preimage = match TxBuilder::<Context>::try_sign_direct_args(coin, &input) {
// If there was a `SignDirect` message in the signing input, generate the tx preimage directly.
Ok(Some(sign_direct_args)) => {
ProtobufPreimager::<Context>::preimage_hash_direct(&sign_direct_args, tx_hasher)?
Expand Down Expand Up @@ -128,7 +128,7 @@ impl<Context: CosmosContext> TWTransactionCompiler<Context> {
let params = TxBuilder::<Context>::public_key_params_from_proto(&input);
let public_key = Context::PublicKey::from_bytes(coin, &public_key, params)?;

let signed_tx_raw = match TxBuilder::<Context>::try_sign_direct_args(&input) {
let signed_tx_raw = match TxBuilder::<Context>::try_sign_direct_args(coin, &input) {
// If there was a `SignDirect` message in the signing input, generate the `TxRaw` directly.
Ok(Some(sign_direct_args)) => ProtobufSerializer::<Context>::build_direct_signed_tx(
&sign_direct_args,
Expand Down
53 changes: 34 additions & 19 deletions rust/tw_cosmos_sdk/src/modules/tx_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use crate::address::Address;
use crate::context::CosmosContext;
use crate::modules::serializer::protobuf_serializer::SignDirectArgs;
use crate::modules::serializer::protobuf_serializer::{ProtobufSerializer, SignDirectArgs};
use crate::public_key::{CosmosPublicKey, PublicKeyParams};
use crate::transaction::message::cosmos_generic_message::JsonRawMessage;
use crate::transaction::message::{CosmosMessage, CosmosMessageBox};
Expand Down Expand Up @@ -34,16 +34,10 @@ where
coin: &dyn CoinContext,
input: &Proto::SigningInput<'_>,
) -> SigningResult<UnsignedTransaction<Context>> {
let fee = input
.fee
.as_ref()
.or_tw_err(SigningErrorType::Error_wrong_fee)
.context("No fee specified")?;
let signer = Self::signer_info_from_proto(coin, input)?;

Ok(UnsignedTransaction {
signer,
fee: Self::fee_from_proto(fee)?,
fee: Self::fee_from_proto(&input.fee)?,
chain_id: input.chain_id.to_string(),
account_number: input.account_number,
tx_body: Self::tx_body_from_proto(coin, input)?,
Expand Down Expand Up @@ -86,15 +80,20 @@ where
}
}

fn fee_from_proto(input: &Proto::Fee) -> SigningResult<Fee<Context::Address>> {
let amounts = input
fn fee_from_proto(input: &Option<Proto::Fee>) -> SigningResult<Fee<Context::Address>> {
let fee_input = input
.as_ref()
.or_tw_err(SigningErrorType::Error_wrong_fee)
.context("No fee specified")?;

let amounts = fee_input
.amounts
.iter()
.map(Self::coin_from_proto)
.collect::<SigningResult<_>>()?;
Ok(Fee {
amounts,
gas_limit: input.gas,
gas_limit: fee_input.gas,
payer: None,
granter: None,
})
Expand Down Expand Up @@ -133,6 +132,7 @@ where
}

pub fn try_sign_direct_args(
context: &dyn CoinContext,
input: &Proto::SigningInput<'_>,
) -> SigningResult<Option<SignDirectArgs>> {
use Proto::mod_Message::OneOfmessage_oneof as MessageEnum;
Expand All @@ -141,15 +141,30 @@ where
return Ok(None);
};

match msg.message_oneof {
MessageEnum::sign_direct_message(ref direct) => Ok(Some(SignDirectArgs {
tx_body: direct.body_bytes.to_vec(),
auth_info: direct.auth_info_bytes.to_vec(),
chain_id: input.chain_id.to_string(),
account_number: input.account_number,
})),
_ => Ok(None),
let MessageEnum::sign_direct_message(ref direct) = msg.message_oneof else {
return Ok(None);
};

if direct.body_bytes.is_empty() {
return SigningError::err(SigningErrorType::Error_invalid_params)
.context("`body_bytes` must not be empty");
}

let auth_info = if direct.auth_info_bytes.is_empty() {
let signer = Self::signer_info_from_proto(context, input)?;
let fee = Self::fee_from_proto(&input.fee)?;
let auth_info = ProtobufSerializer::<Context>::build_auth_info(&signer, &fee);
serialize(&auth_info)?
} else {
direct.auth_info_bytes.to_vec()
};

Ok(Some(SignDirectArgs {
tx_body: direct.body_bytes.to_vec(),
auth_info,
chain_id: input.chain_id.to_string(),
account_number: input.account_number,
}))
}

pub fn tx_message(
Expand Down
39 changes: 39 additions & 0 deletions rust/tw_cosmos_sdk/tests/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,45 @@ fn test_sign_direct() {
});
}

/// Build and sign a transaction with `TxBody` bytes only,
/// and `AuthInfo` will be generated from `SigningInput` parameters.
#[test]
fn test_sign_direct_with_body_bytes() {
use tw_cosmos_sdk::proto::cosmos::tx::v1beta1 as tx_proto;

let coin = TestCoinContext::default()
.with_public_key_type(PublicKeyType::Secp256k1)
.with_hrp("cosmos");

let body_bytes = "0a89010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412690a2d636f736d6f733168736b366a727979716a6668703564686335357463396a74636b796778306570683664643032122d636f736d6f73317a743530617a7570616e716c66616d356166687633686578777975746e756b656834633537331a090a046d756f6e120131".decode_hex().unwrap();

let sign_direct = Proto::mod_Message::SignDirect {
body_bytes: Cow::from(body_bytes),
// Do not specify the `AuthInfo` bytes.
auth_info_bytes: Cow::default(),
};
let mut input = Proto::SigningInput {
account_number: 1037,
chain_id: "gaia-13003".into(),
fee: Some(make_fee(200000, make_amount("muon", "200"))),
sequence: 8,
private_key: account_1037_private_key(),
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_sign_protobuf::<StandardCosmosContext>(TestInput {
coin: &coin,
input: input.clone(),
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=="}]"#,
});
}

#[test]
fn test_sign_direct_0a90010a() {
let coin = TestCoinContext::default()
Expand Down
2 changes: 2 additions & 0 deletions src/proto/Cosmos.proto
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,10 @@ message Message {
// For signing an already serialized transaction. Account number and chain ID must be set outside.
message SignDirect {
// The prepared serialized TxBody
// Required
bytes body_bytes = 1;
// The prepared serialized AuthInfo
// Optional. If not provided, will be generated from `SigningInput` parameters.
bytes auth_info_bytes = 2;
}

Expand Down
Loading