Skip to content

Commit

Permalink
[Cosmos]: Add ability to sign direct with TxBody bytes only
Browse files Browse the repository at this point in the history
* Generate `AuthInfo` from `SigningInput` parameters if `SignDirect.auth_info_bytes` empty
  • Loading branch information
satoshiotomakan committed Jan 8, 2025
1 parent 1ce5282 commit dad27c9
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 21 deletions.
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

0 comments on commit dad27c9

Please sign in to comment.