Skip to content

Commit

Permalink
Support compiling a transaction with partial signatures
Browse files Browse the repository at this point in the history
Fix issue #4142
  • Loading branch information
10gic committed Nov 30, 2024
1 parent 60ef068 commit 1c6b307
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 7 deletions.
7 changes: 0 additions & 7 deletions rust/chains/tw_solana/src/modules/tx_signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,6 @@ impl TxSigner {
) -> SigningResult<versioned::VersionedTransaction> {
let mut tx = versioned::VersionedTransaction::unsigned(unsigned_msg);

let actual_signatures = key_signs.len();
let expected_signatures = tx.message.num_required_signatures();
if actual_signatures != expected_signatures {
return SigningError::err(SigningErrorType::Error_signatures_count)
.with_context(|| format!("Expected '{expected_signatures}' signatures, provided '{actual_signatures}'"));
}

for (signing_pubkey, ed25519_signature) in key_signs {
// Find an index of the corresponding account.
let account_index = tx
Expand Down
53 changes: 53 additions & 0 deletions rust/tw_tests/tests/chains/solana/solana_compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
// Copyright © 2017 Trust Wallet.

use tw_any_coin::test_utils::sign_utils::{CompilerHelper, PreImageHelper};
use tw_any_coin::test_utils::transaction_decode_utils::TransactionDecoderHelper;
use tw_coin_registry::coin_type::CoinType;
use tw_encoding::base58::{self, Alphabet};
use tw_encoding::base64::{self, STANDARD};
use tw_encoding::hex::{DecodeHex, ToHex};
use tw_proto::Common::Proto::SigningError;
use tw_proto::Solana::Proto::{self, mod_SigningInput::OneOftransaction_type as TransactionType};
Expand Down Expand Up @@ -421,3 +423,54 @@ fn test_solana_compile_transfer_with_fake_signature() {
let output = compiler.compile(CoinType::Solana, &input, vec![signature], vec![public_key]);
assert_eq!(output.error, SigningError::Error_signing);
}

#[test]
fn test_solana_compile_with_partial_signature() {
// The following is an unsigned transaction generated by the Jito Staking DApp.
// This transaction requires two signatures; only the first one will be provided by the user.
// The DApp will add the second signature before submitting the transaction to Solana network.
let encoded_unsigned_tx = base64::decode("AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAGDqMrdVJnGyKZfDMMU6yR6mjQRynQy91x5Ik0QW5S7HjblEDFOjfYkcjeiZdpl9opTtGz1XgRDjyGqc4Z5VKuBHI5lg1U5Wnl5tFnrGEI9Y2rG1BNC7tYK5PXpURkpupeap6naP7fZEyKrpuOIYit0GvFUPv3Fsgiuc5jx3g9lS4fsU4N5V6fuoY5br/VSM/4ySAR6se3W6qbLZxqhvWhcUEJ5qP+7PmQMuHB32uXItyzY057jjRAk2vDSwzByOtSH/zRQemDLK8QrZF0lcoPJxtbKTzUcCfqc3AH7UDrOaC9BIo+CMO0lb4X9FQn2JvsW4DH4mlcGGTXZ0PbOb7TRtYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFTlniTAUaihSnsel5fw4Szfp0kCFmUxlaalEqbxZmArjJclj04kifG7PRApFI4NgwtaE5na/xCEBI572Nvp+FkDBkZv5SEXMv/srbpyw5vnvIzlu8X3EmssQ5s6QAAAAAaBTtTK9ooXRnL9rIYDGmPoTqFe+h1EtyKT9tvbABZQBt324ddloZPZy+FGzut5rBy0he1fWzeROoz1hX7/AKnLHFG8r27UznARMamsTdStWltOfL+TLpgyxe4Hfqm8xQYIAgABDAIAAACghgEAAAAAAAoGAAIABggNAQEMCgcJAwECBQIGCA0JDqCGAQAAAAAACwAFAkANAwALAAkDgDgBAAAAAAAIAgAEDAIAAAC0/gAAAAAAAA==", STANDARD).unwrap();

// Step 1: Decode the transaction, get the raw message, and prepare the signing input
let mut decoder = TransactionDecoderHelper::<Proto::DecodingTransactionOutput>::default();
let output = decoder.decode(CoinType::Solana, encoded_unsigned_tx);
assert_eq!(output.error, SigningError::OK);

let input = Proto::SigningInput {
raw_message: output.transaction,
..Proto::SigningInput::default()
};

// Step 2: Simulate signature (the first signature), normally obtained from signature server.
let signature1 = "daf56b31e3a97504bb5adb4c64709cdd8240700935d9e1cae9a05ab032de04b469cc1e02587f9c6d7e57192e4f9c77117897a85d15900730d90a7f55debf690a".decode_hex().unwrap();
let public_key1 = "a32b7552671b22997c330c53ac91ea68d04729d0cbdd71e48934416e52ec78db"
.decode_hex()
.unwrap();

// Step 3: Compile the transaction with partial signature
let mut compiler = CompilerHelper::<Proto::SigningOutput>::default();
let output = compiler.compile(
CoinType::Solana,
&input,
vec![signature1],
vec![public_key1],
);
assert_eq!(output.error, SigningError::OK);
assert_eq!(output.encoded, "YziLZ5ChTunpGLAAufaXrSpPrFZsL9fsiyo2NshjFMUXbyRrFbpTZpLdsWWULCvKeg4oSdxDuaDqazKZjNgdFjh7bDzq7HPzafzGRHEWy3Rub9DK8uUJrZe6EHpZSm1EKLsbFXYHaoRPPpuT7Pywufjdsk79qWGMHba6KnSaXbRm1tY2Fz88Hz25GUKvvKg7aSUJ5CZ3E2EEcWuX1VBDXPdzcgFkWtrW7JmjfNfhcxKQ5rHet8h3Cr1DueXoz12Sso2gKMgEjAdeHsMFpACercWtWW8M6B8VKp4KqJDvDUCRcJU9U6EbfiTt25dZEuDg4S7ceJG8nHJrYVEKajkPr5HZeU5zuxKQix1xUKBTz7Dx3U4ddFMSSb4xYb7Hv5oacUb3FRP7HwFYkuRocME8fr4wCxGnXg4Tkq1Fe9sVK1nD4YEiD77m6KD44bbXubqQJqkiLuXRJB28Jc3MUPJHrWRJhS5ezL1y89sL91cqZ9fpnLvih4MDC3VcCeuV35LxbigwHvcPPVxsHo2DdKraYMEf5kpR8eqbftAvQbpcUbxeraPcvhPKqU1GtkAgYCL6XG3Y44io5eTQwqMvLwRW9SsFhmbGCk1i7vBPf4p6EmVs3zbfKKSBXK7tg8RD4ojJiGBqCr3yuZkNeFoFXS1JhkggoS8Ury9xLrAB2mmCHEnyr9x5jEspyCcFUtxSyvtCGiyQVG9HxMEzJKVLX27vYncy1URzGyUS9TF35mpXRo3kUNVjTNuMXmYyMSGunt84atiCWSUMPWRB6sjJD9YBv35pLok589m5WvYvB1XFH9sSfkDQ8RCMpu762ekSJSQev3aH1F61hucKSkXNrkCvW8J2wLrLvwSjoPZ28u74AcvpzEa8o3ks33EYdNWTdY1aWWFzqRbCyQPE2s6JmRDJgX57QYdMqXs1KDkhgb7ttQrrkfgb5ki5EofcmNw");

// Step 4: Return the compiled transaction with partial signature to the DApp, which will then complete the remaining signatures.
let mut tx_with_partial_signature =
base58::decode(output.encoded.as_ref(), Alphabet::Bitcoin).unwrap();
// The following is the signature (the second signature) generated by the DApp.
let signature_generated_by_dapp = "c3207d277754a1a872bdc910136e917f9e902f025e493f0c1bf3d5b4512fffe03b250e86e4e68e20e96ab09beb24d7706ae338a2c5d11fbb23d779ae5b8c0701".decode_hex().unwrap();
// The following is a simple code to simulate the process of the DApp filling in the second signature.
let second_signature_offset = 1 + 64;
let second_signature_length = 64;
tx_with_partial_signature
[second_signature_offset..(second_signature_offset + second_signature_length)]
.copy_from_slice(&signature_generated_by_dapp);

let full_signed_tx = base64::encode(&tx_with_partial_signature, STANDARD);
// Successfully broadcasted: https://solscan.io/tx/5NuXtYpE58FbtCfEzgk2cTHZgEdNF69Z76bd8TQhBgmEN1RC98DNGNiWhvp1VSDMPudCgpE3z8jD7BNRuBztUbpM
assert_eq!(full_signed_tx, "Atr1azHjqXUEu1rbTGRwnN2CQHAJNdnhyumgWrAy3gS0acweAlh/nG1+VxkuT5x3EXiXqF0VkAcw2Qp/Vd6/aQrDIH0nd1ShqHK9yRATbpF/npAvAl5JPwwb89W0US//4DslDobk5o4g6Wqwm+sk13Bq4ziixdEfuyPXea5bjAcBAgAGDqMrdVJnGyKZfDMMU6yR6mjQRynQy91x5Ik0QW5S7HjblEDFOjfYkcjeiZdpl9opTtGz1XgRDjyGqc4Z5VKuBHI5lg1U5Wnl5tFnrGEI9Y2rG1BNC7tYK5PXpURkpupeap6naP7fZEyKrpuOIYit0GvFUPv3Fsgiuc5jx3g9lS4fsU4N5V6fuoY5br/VSM/4ySAR6se3W6qbLZxqhvWhcUEJ5qP+7PmQMuHB32uXItyzY057jjRAk2vDSwzByOtSH/zRQemDLK8QrZF0lcoPJxtbKTzUcCfqc3AH7UDrOaC9BIo+CMO0lb4X9FQn2JvsW4DH4mlcGGTXZ0PbOb7TRtYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFTlniTAUaihSnsel5fw4Szfp0kCFmUxlaalEqbxZmArjJclj04kifG7PRApFI4NgwtaE5na/xCEBI572Nvp+FkDBkZv5SEXMv/srbpyw5vnvIzlu8X3EmssQ5s6QAAAAAaBTtTK9ooXRnL9rIYDGmPoTqFe+h1EtyKT9tvbABZQBt324ddloZPZy+FGzut5rBy0he1fWzeROoz1hX7/AKnLHFG8r27UznARMamsTdStWltOfL+TLpgyxe4Hfqm8xQYIAgABDAIAAACghgEAAAAAAAoGAAIABggNAQEMCgcJAwECBQIGCA0JDqCGAQAAAAAACwAFAkANAwALAAkDgDgBAAAAAAAIAgAEDAIAAAC0/gAAAAAAAA==")
}

0 comments on commit 1c6b307

Please sign in to comment.