Skip to content
This repository was archived by the owner on Jun 1, 2026. It is now read-only.

Commit dc9e3af

Browse files
committed
check message inside tx
1 parent 5d3a011 commit dc9e3af

3 files changed

Lines changed: 68 additions & 16 deletions

File tree

‎crates/gem_solana/src/signer/chain_signer.rs‎

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use solana_primitives::{Pubkey, sign_message as sign_solana_message};
77
#[derive(Default)]
88
pub struct SolanaChainSigner;
99

10-
const SIGN_MESSAGE_TRANSACTION_PAYLOAD_ERROR: &str = "Serialized Solana transaction received in signMessage request; use signTransaction instead";
10+
const SIGN_MESSAGE_PAYLOAD_REJECTION: &str = "Serialized Solana transaction or transaction message received in signMessage request; use signTransaction instead";
1111

1212
impl ChainSigner for SolanaChainSigner {
1313
fn sign_transfer(&self, input: &SignerInput, private_key: &[u8]) -> Result<String, SignerError> {
@@ -41,7 +41,7 @@ impl ChainSigner for SolanaChainSigner {
4141

4242
fn sign_message(&self, message: &[u8], private_key: &[u8]) -> Result<String, SignerError> {
4343
if is_transaction_bytes(message) {
44-
return Err(SignerError::invalid_input(SIGN_MESSAGE_TRANSACTION_PAYLOAD_ERROR));
44+
return Err(SignerError::invalid_input(SIGN_MESSAGE_PAYLOAD_REJECTION));
4545
}
4646
let signature = sign_solana_message(private_key, message).map_err(|e| SignerError::signing_error(format!("sign: {e}")))?;
4747
Ok(bs58::encode(signature.as_bytes()).into_string())
@@ -74,7 +74,7 @@ impl ChainSigner for SolanaChainSigner {
7474
#[cfg(test)]
7575
mod tests {
7676
use super::*;
77-
use crate::signer::testkit::{DOUBLE_SIG_TX, EXPECTED_MESSAGE_HEX, SINGLE_SIG_TX};
77+
use crate::signer::testkit::{DOUBLE_SIG_TX, EXPECTED_MESSAGE_HEX, SINGLE_SIG_TX, mock_legacy_transaction};
7878
use gem_encoding::decode_base64;
7979
use primitives::testkit::signer_mock::TEST_PRIVATE_KEY;
8080
use primitives::{Chain, ChainSigner, SignerInput, TransactionLoadInput, TransferDataOutputType};
@@ -130,19 +130,34 @@ mod tests {
130130

131131
#[test]
132132
fn test_sign_message() {
133-
let signer = SolanaChainSigner;
134-
135-
let result = signer.sign_message(b"hello", &TEST_PRIVATE_KEY).unwrap();
133+
let result = SolanaChainSigner.sign_message(b"hello", &TEST_PRIVATE_KEY).unwrap();
136134

137135
assert_eq!(bs58::decode(result).into_vec().unwrap().len(), 64);
138136
}
139137

140138
#[test]
141139
fn test_sign_message_rejects_transaction_payload() {
142-
let signer = SolanaChainSigner;
143140
let bytes = decode_base64(SINGLE_SIG_TX).unwrap();
144-
let result = signer.sign_message(&bytes, &TEST_PRIVATE_KEY);
141+
let result = SolanaChainSigner.sign_message(&bytes, &TEST_PRIVATE_KEY);
142+
143+
assert_eq!(result.unwrap_err().to_string(), format!("Invalid input: {SIGN_MESSAGE_PAYLOAD_REJECTION}"));
144+
}
145+
146+
#[test]
147+
fn test_sign_message_rejects_bare_message_payload() {
148+
let bytes = decode_base64(SINGLE_SIG_TX).unwrap();
149+
let transaction = VersionedTransaction::deserialize_with_version(&bytes).unwrap();
150+
let message = transaction.serialize_message().unwrap();
151+
let result = SolanaChainSigner.sign_message(&message, &TEST_PRIVATE_KEY);
152+
153+
assert_eq!(result.unwrap_err().to_string(), format!("Invalid input: {SIGN_MESSAGE_PAYLOAD_REJECTION}"));
154+
}
155+
156+
#[test]
157+
fn test_sign_message_rejects_legacy_bare_message_payload() {
158+
let message = mock_legacy_transaction().serialize_message().unwrap();
159+
let result = SolanaChainSigner.sign_message(&message, &TEST_PRIVATE_KEY);
145160

146-
assert_eq!(result.unwrap_err().to_string(), format!("Invalid input: {SIGN_MESSAGE_TRANSACTION_PAYLOAD_ERROR}"));
161+
assert_eq!(result.unwrap_err().to_string(), format!("Invalid input: {SIGN_MESSAGE_PAYLOAD_REJECTION}"));
147162
}
148163
}

‎crates/gem_solana/src/signer/testkit.rs‎

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use gem_encoding::decode_base64;
22
use primitives::testkit::signer_mock::TEST_PRIVATE_KEY;
33
use primitives::{SolanaTokenProgramId, TransactionLoadMetadata};
4-
use solana_primitives::{Pubkey, VersionedTransaction, get_address};
4+
use solana_primitives::{CompiledInstruction, LegacyMessage, MessageHeader, Pubkey, VersionedTransaction, get_address};
55

66
pub const TEST_RECIPIENT: &str = "EN2sCsJ1WDV8UFqsiTXHcUPUxQ4juE71eCknHYYMifkd";
77
pub const TEST_SENDER_TOKEN_ADDRESS: &str = "HEeranxp3y7kVQKVSLdZW1rUmnbs7bAtUTMu8o88Jash";
@@ -26,6 +26,26 @@ pub fn base58_transaction(encoded_base64: &str) -> String {
2626
bs58::encode(decode_base64(encoded_base64).unwrap()).into_string()
2727
}
2828

29+
pub fn mock_legacy_transaction() -> VersionedTransaction {
30+
VersionedTransaction::Legacy {
31+
signatures: vec![],
32+
message: LegacyMessage {
33+
header: MessageHeader {
34+
num_required_signatures: 1,
35+
num_readonly_signed_accounts: 0,
36+
num_readonly_unsigned_accounts: 1,
37+
},
38+
account_keys: vec![Pubkey::new([1; 32]), Pubkey::new([2; 32])],
39+
recent_blockhash: [3; 32],
40+
instructions: vec![CompiledInstruction {
41+
program_id_index: 1,
42+
accounts: vec![0],
43+
data: vec![],
44+
}],
45+
},
46+
}
47+
}
48+
2949
pub fn program_id(transaction: &VersionedTransaction, index: usize) -> String {
3050
let instruction = &transaction.instructions()[index];
3151
transaction.account_keys()[instruction.program_id_index as usize].to_base58()

‎crates/gem_solana/src/transaction.rs‎

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,16 @@ pub fn try_decode_transaction_bytes(transaction: &[u8]) -> Option<VersionedTrans
1414
}
1515

1616
pub fn is_transaction_bytes(transaction: &[u8]) -> bool {
17-
try_decode_transaction_bytes(transaction).is_some()
17+
try_decode_transaction_bytes(transaction).is_some() || try_decode_transaction_message(transaction).is_some()
18+
}
19+
20+
fn try_decode_transaction_message(message: &[u8]) -> Option<VersionedTransaction> {
21+
let mut transaction = Vec::with_capacity(message.len() + 1);
22+
transaction.push(0);
23+
transaction.extend_from_slice(message);
24+
25+
let decoded = VersionedTransaction::deserialize_with_version(&transaction).ok()?;
26+
(decoded.serialize_message().ok()? == message).then_some(decoded)
1827
}
1928

2029
pub fn decode_transaction(transaction_base64: &str) -> Result<VersionedTransaction, String> {
@@ -60,7 +69,7 @@ pub fn instructions_from_primitives(instructions: Vec<SolanaInstruction>) -> Res
6069
mod tests {
6170
use super::*;
6271
#[cfg(feature = "signer")]
63-
use crate::signer::testkit::SINGLE_SIG_TX;
72+
use crate::signer::testkit::{SINGLE_SIG_TX, mock_legacy_transaction};
6473

6574
#[test]
6675
fn test_try_decode_blockhash() {
@@ -72,11 +81,19 @@ mod tests {
7281
#[cfg(feature = "signer")]
7382
#[test]
7483
fn test_is_transaction_bytes() {
75-
let mut bytes = gem_encoding::decode_base64(SINGLE_SIG_TX).unwrap();
76-
assert!(is_transaction_bytes(&bytes));
84+
let full_transaction = gem_encoding::decode_base64(SINGLE_SIG_TX).unwrap();
85+
let transaction = VersionedTransaction::deserialize_with_version(&full_transaction).unwrap();
86+
let mut v0_message = transaction.serialize_message().unwrap();
87+
let mut transaction_with_trailing_byte = full_transaction.clone();
88+
89+
assert!(is_transaction_bytes(&full_transaction));
90+
assert!(is_transaction_bytes(&v0_message));
91+
assert!(is_transaction_bytes(&mock_legacy_transaction().serialize_message().unwrap()));
7792

78-
bytes.push(0);
79-
assert!(!is_transaction_bytes(&bytes));
93+
transaction_with_trailing_byte.push(0);
94+
v0_message.push(0);
95+
assert!(!is_transaction_bytes(&transaction_with_trailing_byte));
96+
assert!(!is_transaction_bytes(&v0_message));
8097
assert!(!is_transaction_bytes(b"hello"));
8198
}
8299
}

0 commit comments

Comments
 (0)