diff --git a/rust/chains/tw_solana/src/modules/tx_signer.rs b/rust/chains/tw_solana/src/modules/tx_signer.rs index 5fb421e6876..754fa372efe 100644 --- a/rust/chains/tw_solana/src/modules/tx_signer.rs +++ b/rust/chains/tw_solana/src/modules/tx_signer.rs @@ -44,13 +44,6 @@ impl TxSigner { ) -> SigningResult { 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 diff --git a/rust/tw_tests/tests/chains/solana/solana_compile.rs b/rust/tw_tests/tests/chains/solana/solana_compile.rs index 592d5bee322..d57c136f067 100644 --- a/rust/tw_tests/tests/chains/solana/solana_compile.rs +++ b/rust/tw_tests/tests/chains/solana/solana_compile.rs @@ -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}; @@ -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::::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::::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==") +}