Skip to content

Commit

Permalink
Merge branch 'master' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
satoshiotomakan authored Dec 17, 2024
2 parents c007547 + b37b780 commit d4af84a
Show file tree
Hide file tree
Showing 25 changed files with 583 additions and 54 deletions.
8 changes: 4 additions & 4 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ let package = Package(
targets: [
.binaryTarget(
name: "WalletCore",
url: "https://github.com/trustwallet/wallet-core/releases/download/4.1.12/WalletCore.xcframework.zip",
checksum: "1632bbbab1c6a588689eec77a24e1468d9a6746968652cf0a7e669e996c3d24d"
url: "https://github.com/trustwallet/wallet-core/releases/download/4.1.21/WalletCore.xcframework.zip",
checksum: "503937e1013bb7c1f610a8a4ec74a6ccdedb06bdec9fa9126ac47e25a90da06a"
),
.binaryTarget(
name: "SwiftProtobuf",
url: "https://github.com/trustwallet/wallet-core/releases/download/4.1.12/SwiftProtobuf.xcframework.zip",
checksum: "33d80c20428c9db4fcf99d1272ba19655f7c6ee7e5b1809fa8a7e7d4aa1b222b"
url: "https://github.com/trustwallet/wallet-core/releases/download/4.1.21/SwiftProtobuf.xcframework.zip",
checksum: "f6da2b8fafdce5e8d46ea305972f1ad942cc796f63026a32c883331dd3813285"
)
]
)
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,19 @@ class TestSolanaTransaction {
val expectedString = "AVUye82Mv+/aWeU2G+B6Nes365mUU2m8iqcGZn/8kFJvw4wY6AgKGG+vJHaknHlCDwE1yi1SIMVUUtNCOm3kHg8BAAIEODI+iWe7g68B9iwCy8bFkJKvsIEj350oSOpcv4gNnv/st+6qmqipl9lwMK6toB9TiL7LrJVfij+pKwr+pUKxfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAAAboZ09wD3Y5HNUl7aN8bwpCqowev1hMu7fSgziLswUSgMDAAUCECcAAAICAAEMAgAAAOgDAAAAAAAAAwAJA+gDAAAAAAAA"
assertEquals(output.encoded, expectedString)
}

@Test
fun testSetFeePayer() {
val originalTx = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQABA2uEKrOPvZNBtdUtSFXcg8+kj4O/Z1Ht/hwvnaqq5s6mTXd3KtwUyJFfRs2PBfeQW8xCEZvNr/5J/Tx8ltbn0pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACo+QRbvXWNKoOfaOL4cSpfYrmn/2TV+dBmct+HsmmwdAQICAAEMAgAAAACcnwYAAAAAAA=="

// Step 1 - Add fee payer to the transaction.
val updatedTx = SolanaTransaction.setFeePayer(originalTx, "Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ")
assertEquals(updatedTx, "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAIAAQTLKvCJtWpVdze8Fxjgy/Iyz1sC4U7gqnxmdSM/X2+bV2uEKrOPvZNBtdUtSFXcg8+kj4O/Z1Ht/hwvnaqq5s6mTXd3KtwUyJFfRs2PBfeQW8xCEZvNr/5J/Tx8ltbn0pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACo+QRbvXWNKoOfaOL4cSpfYrmn/2TV+dBmct+HsmmwdAQMCAQIMAgAAAACcnwYAAAAAAA==")

// This case originates from a test case in C++. Here, only the most critical function is verified for correctness,
// while the remaining steps have been omitted.
// Step 2 - Decode transaction into a `RawMessage` Protobuf.
// Step 3 - Obtain preimage hash.
// Step 4 - Compile transaction info.
}
}
2 changes: 1 addition & 1 deletion docs/registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ This list is generated from [./registry.json](../registry.json)
| 10004689 | IoTeX EVM | IOTX | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/iotexevm/info/logo.png" width="32" /> | <https://iotex.io/> |
| 10007000 | NativeZetaChain | ZETA | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/zetachain/info/logo.png" width="32" /> | <https://www.zetachain.com/> |
| 10007700 | NativeCanto | CANTO | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/nativecanto/info/logo.png" width="32" /> | <https://canto.io/> |
| 10008217 | Kaia | KLAY | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/kaia/info/logo.png" width="32" /> | <https://kaia.io> |
| 10008217 | Kaia | KAIA | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/kaia/info/logo.png" width="32" /> | <https://kaia.io> |
| 10009000 | Avalanche C-Chain | AVAX | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/avalanchec/info/logo.png" width="32" /> | <https://www.avalabs.org/> |
| 10009001 | Evmos | EVMOS | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/evmos/info/logo.png" width="32" /> | <https://evmos.org/> |
| 10042170 | Arbitrum Nova | ETH | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/arbitrumnova/info/logo.png" width="32" /> | <https://nova.arbitrum.io> |
Expand Down
12 changes: 10 additions & 2 deletions include/TrustWalletCore/TWSolanaTransaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ TWString *_Nullable TWSolanaTransactionGetComputeUnitLimit(TWString *_Nonnull en
/// and returns the updated transaction.
///
/// \param encodedTx base64 encoded Solana transaction.
/// \price Unit Price as a decimal string.
/// \param price Unit Price as a decimal string.
/// \return base64 encoded Solana transaction. Null if an error occurred.
TW_EXPORT_STATIC_METHOD
TWString *_Nullable TWSolanaTransactionSetComputeUnitPrice(TWString *_Nonnull encodedTx, TWString *_Nonnull price);
Expand All @@ -59,9 +59,17 @@ TWString *_Nullable TWSolanaTransactionSetComputeUnitPrice(TWString *_Nonnull en
/// and returns the updated transaction.
///
/// \param encodedTx base64 encoded Solana transaction.
/// \limit Unit Limit as a decimal string.
/// \param limit Unit Limit as a decimal string.
/// \return base64 encoded Solana transaction. Null if an error occurred.
TW_EXPORT_STATIC_METHOD
TWString *_Nullable TWSolanaTransactionSetComputeUnitLimit(TWString *_Nonnull encodedTx, TWString *_Nonnull limit);

/// Adds fee payer to the given transaction and returns the updated transaction.
///
/// \param encodedTx base64 encoded Solana transaction.
/// \param feePayer fee payer account address. Must be a base58 encoded public key. It must NOT be in the account list yet.
/// \return base64 encoded Solana transaction. Null if an error occurred.
TW_EXPORT_STATIC_METHOD
TWString *_Nullable TWSolanaTransactionSetFeePayer(TWString *_Nonnull encodedTx, TWString *_Nonnull feePayer);

TW_EXTERN_C_END
2 changes: 1 addition & 1 deletion registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -4066,7 +4066,7 @@
"id": "kaia",
"name": "Kaia",
"coinId": 10008217,
"symbol": "KLAY",
"symbol": "KAIA",
"decimals": 18,
"blockchain": "Ethereum",
"derivation": [
Expand Down
31 changes: 31 additions & 0 deletions rust/chains/tw_solana/src/modules/insert_instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,37 @@ pub trait InsertInstruction {
Ok(account_added_at)
}

/// Adds a fee payer account to the message.
/// Note: The fee payer must NOT be in the account list yet.
fn set_fee_payer(&mut self, account: SolanaAddress) -> SigningResult<()> {
if self.account_keys_mut().contains(&account) {
// For security reasons, we don't allow adding a fee payer if it's already in the account list.
//
// If the fee payer is already in the transaction and there is a malicious instruction to
// transfer tokens from the fee payer to another account, The fee payer may have inadvertently
// signed off on such transactions, which is not what they would expect.
//
// Such examples may be difficult to exploit, but we still took precautionary measures to prohibit
// the new fee payer from appearing in the account list of the transaction out of caution
return SigningError::err(SigningErrorType::Error_internal)
.context("Fee payer account is already in the account list");
}

// Insert the fee payer account at the beginning of the account list.
self.account_keys_mut().insert(0, account);
self.message_header_mut().num_required_signatures += 1;

// Update `program id indexes` and `account id indexes` in every instruction as we inserted the account at the beginning of the list.
self.instructions_mut().iter_mut().for_each(|ix| {
ix.program_id_index += 1; // Update `program id indexes`
ix.accounts
.iter_mut()
.for_each(|account_id| *account_id += 1); // Update `account id indexes`
});

Ok(())
}

/// Returns ALT (Address Lookup Tables) if supported by the message version.
fn address_table_lookups(&self) -> Option<&[MessageAddressTableLookup]>;

Expand Down
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
15 changes: 15 additions & 0 deletions rust/chains/tw_solana/src/modules/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//
// Copyright © 2017 Trust Wallet.

use crate::address::SolanaAddress;
use crate::defined_addresses::{COMPUTE_BUDGET_ADDRESS, SYSTEM_PROGRAM_ID_ADDRESS};
use crate::modules::insert_instruction::InsertInstruction;
use crate::modules::instruction_builder::compute_budget_instruction::{
Expand Down Expand Up @@ -158,6 +159,20 @@ impl SolanaTransaction {

tx.to_base64().tw_err(|_| SigningErrorType::Error_internal)
}

pub fn set_fee_payer(encoded_tx: &str, fee_payer: SolanaAddress) -> SigningResult<String> {
let tx_bytes = base64::decode(encoded_tx, STANDARD)?;
let mut tx: VersionedTransaction =
bincode::deserialize(&tx_bytes).map_err(|_| SigningErrorType::Error_input_parse)?;

tx.message.set_fee_payer(fee_payer)?;

// Set the correct number of zero signatures
let unsigned_tx = VersionedTransaction::unsigned(tx.message);
unsigned_tx
.to_base64()
.tw_err(|_| SigningErrorType::Error_internal)
}
}

fn try_instruction_as_compute_budget(
Expand Down
14 changes: 8 additions & 6 deletions rust/frameworks/tw_utxo/src/modules/sighash_computer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,11 @@ where
.input_args()
.iter()
.enumerate()
.map(|(input_index, utxo)| {
.map(|(signing_input_index, utxo)| {
let signing_method = utxo.signing_method;

let utxo_args = UtxoPreimageArgs {
input_index,
input_index: signing_input_index,
script_pubkey: utxo.script_pubkey.clone(),
amount: utxo.amount,
// TODO move `leaf_hash_code_separator` to `UtxoTaprootPreimageArgs`.
Expand All @@ -90,12 +90,14 @@ where
let tr_spent_script_pubkeys: Vec<Script> = unsigned_tx
.input_args()
.iter()
.map(|utxo| {
if utxo.signing_method == SigningMethod::Taproot {
// Taproot UTXOs scriptPubkeys should be signed as is.
.enumerate()
.map(|(i, utxo)| {
if i == signing_input_index {
// Use the scriptPubkey required to spend this UTXO.
utxo.script_pubkey.clone()
} else {
// Use the original scriptPubkey declared in the unspent output.
// Use the original scriptPubkey declared in the unspent output for other UTXOs
// (different from that we sign at this iteration).
utxo.prevout_script_pubkey.clone()
}
})
Expand Down
2 changes: 1 addition & 1 deletion rust/tw_keypair/src/ed25519/keypair.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ impl<H: Hasher512> SigningKeyTrait for KeyPair<H> {
type Signature = Signature;

fn sign(&self, message: Self::SigningMessage) -> KeyPairResult<Self::Signature> {
self.private().sign_with_public_key(self.public(), &message)
self.private().sign(message)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ impl<H: Hasher512> SigningKeyTrait for ExtendedKeyPair<H> {
type Signature = Signature;

fn sign(&self, message: Self::SigningMessage) -> KeyPairResult<Self::Signature> {
self.private()
.sign_with_public_key(self.public(), message.as_slice())
self.private().sign(message)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,25 +43,16 @@ impl<H: Hasher512> ExtendedPrivateKey<H> {

ExtendedPublicKey::new(key, second_key)
}

/// `ed25519` signing uses a public key associated with the private key.
pub(crate) fn sign_with_public_key(
&self,
public: &ExtendedPublicKey<H>,
message: &[u8],
) -> KeyPairResult<Signature> {
self.key
.expanded_key
.sign_with_pubkey(public.key_for_signing(), message)
}
}

impl<H: Hasher512> SigningKeyTrait for ExtendedPrivateKey<H> {
type SigningMessage = Vec<u8>;
type Signature = Signature;

fn sign(&self, message: Self::SigningMessage) -> KeyPairResult<Self::Signature> {
self.sign_with_public_key(&self.public(), message.as_slice())
self.key
.expanded_key
.dangerous_sign_with_pubkey(self.public().key_for_signing(), message.as_slice())
}
}

Expand Down
13 changes: 2 additions & 11 deletions rust/tw_keypair/src/ed25519/private.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,24 +36,15 @@ impl<H: Hasher512> PrivateKey<H> {
pub fn public(&self) -> PublicKey<H> {
PublicKey::with_expanded_secret(&self.expanded_key)
}

/// `ed25519` signing uses a public key associated with the private key.
pub(crate) fn sign_with_public_key(
&self,
public: &PublicKey<H>,
message: &[u8],
) -> KeyPairResult<Signature> {
self.expanded_key
.sign_with_pubkey(public.to_bytes(), message)
}
}

impl<H: Hasher512> SigningKeyTrait for PrivateKey<H> {
type SigningMessage = Vec<u8>;
type Signature = Signature;

fn sign(&self, message: Self::SigningMessage) -> KeyPairResult<Self::Signature> {
self.sign_with_public_key(&self.public(), &message)
self.expanded_key
.dangerous_sign_with_pubkey(self.public().to_bytes(), &message)
}
}

Expand Down
12 changes: 10 additions & 2 deletions rust/tw_keypair/src/ed25519/secret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,14 @@ impl<H: Hasher512> ExpandedSecretKey<H> {
/// Signs a message with this `ExpandedSecretKey`.
/// Source: https://github.com/dalek-cryptography/ed25519-dalek/blob/1.0.1/src/secret.rs#L389-L412
/// Ported: https://github.com/trustwallet/wallet-core/blob/423f0e34725f69c0a9d535e1a32534c99682edea/trezor-crypto/crypto/ed25519-donna/ed25519.c#L97-L130
///
/// # Important
///
/// Ensure that the public key is always correctly paired with the private key,
/// preventing scenarios where an arbitrary public key could be introduced into the signing process.
/// Security report: https://github.com/trustwallet/wallet-core/security/advisories/GHSA-7g72-jxww-q9vq
#[allow(non_snake_case)]
pub(crate) fn sign_with_pubkey(
pub(crate) fn dangerous_sign_with_pubkey(
&self,
pubkey: H256,
message: &[u8],
Expand Down Expand Up @@ -122,7 +128,9 @@ mod tests {
let message = hex::decode("f0").unwrap();

// Anyway, the result signature has an expected value.
let sign = secret_key.sign_with_pubkey(public, &message).unwrap();
let sign = secret_key
.dangerous_sign_with_pubkey(public, &message)
.unwrap();
let expected = H512::from("ed55bce14a845a275e7a3a7242420ed1eeaba79dc3141bebf42ca0d12169e209a6e56b6981a336f711ae3aaea8d063b72b0e79a8808311d08cb42cabfdd0450d");
assert_eq!(sign.to_bytes(), expected);
}
Expand Down
74 changes: 74 additions & 0 deletions rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/brc20.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,77 @@ fn test_bitcoin_sign_brc20_transfer() {
fee: 3000,
});
}

/// Fixes `{"error":"-26: non-mandatory-script-verify-flag (Invalid Schnorr signature)"}` error.
#[test]
fn test_bitcoin_sign_brc20_reveal_with_extra_p2tr_input() {
// bc1puq428nh4eynlqph8gynwdtqg4je0hc03gp2ptgsf4c5ylxz0ll2sd34gk7
let alice_pk_bytes = "8efa479919269076eb331c304fff187b9d7aa60d1f6cd3d6b12a151a52f22582"
.decode_hex()
.unwrap();
let alice_private_key = schnorr::PrivateKey::try_from(alice_pk_bytes.as_slice()).unwrap();
let alice_pubkey = alice_private_key.public().compressed();
let my_address = "bc1puq428nh4eynlqph8gynwdtqg4je0hc03gp2ptgsf4c5ylxz0ll2sd34gk7";

let commit_txid = "164b459a49c5e3a817028df7f4545585874feff3985e48d6ff6792989a4823a8";
let commit_utxo = Proto::Input {
out_point: input::out_point(commit_txid, 0),
value: 546,
sighash_type: SIGHASH_ALL,
claiming_script: input::brc20_inscribe(alice_pubkey.to_vec(), "duna", "0.001"),
..Default::default()
};

// Extra P2TR UTXO is used to cover transaction fee.
let p2tr_utxo = "164b459a49c5e3a817028df7f4545585874feff3985e48d6ff6792989a4823a8";
let extra_p2tr = Proto::Input {
out_point: input::out_point(p2tr_utxo, 1),
value: 11_210,
sighash_type: SIGHASH_ALL,
claiming_script: input::receiver_address(my_address),
..Default::default()
};

let out1 = Proto::Output {
value: 546,
to_recipient: output::to_address(my_address),
};
let change_output = Proto::Output {
value: 0,
to_recipient: output::to_address(my_address),
};

let builder = Proto::TransactionBuilder {
version: Proto::TransactionVersion::V2,
inputs: vec![commit_utxo, extra_p2tr],
outputs: vec![out1],
change_output: Some(change_output),
input_selector: Proto::InputSelector::UseAll,
dust_policy: dust_threshold(DUST),
fee_per_vb: 9,
..Default::default()
};

let signing = Proto::SigningInput {
private_keys: vec![alice_pk_bytes.into()],
chain_info: btc_info(),
// We enable deterministic Schnorr signatures here
dangerous_use_fixed_schnorr_rng: true,
transaction: TransactionOneof::builder(builder),
..Default::default()
};

// https://www.blockchain.com/explorer/transactions/btc/113dfc827e4535dccc6aa7fcff5482b4de0fb2ab70f52c44c12c12bca3be5847
sign::BitcoinSignHelper::new(&signing)
.coin(CoinType::Bitcoin)
.sign(sign::Expected {
encoded: "02000000000102a823489a989267ffd6485e98f3ef4f87855554f4f78d0217a8e3c5499a454b160000000000ffffffffa823489a989267ffd6485e98f3ef4f87855554f4f78d0217a8e3c5499a454b160100000000ffffffff022202000000000000225120e02aa3cef5c927f006e74126e6ac08acb2fbe1f1405415a209ae284f984fffd52d23000000000000225120e02aa3cef5c927f006e74126e6ac08acb2fbe1f1405415a209ae284f984fffd50340b90b099a8facd5d4e6008990d180f9afeeb07d62452570a6e700ca0f7968577da6113cb201e9ac3584caa04898cdc7113cce4df06604b8f294a3959d216d364f5e0063036f7264010118746578742f706c61696e3b636861727365743d7574662d38003a7b2270223a226272632d3230222c226f70223a227472616e73666572222c227469636b223a2264756e61222c22616d74223a22302e303031227d6821c02146f58256fcc00ef86a0e53fc14e943bbea2c7972b598b58178fdd6fa3ef79201401c5e54a0ead877e52146e42f8d197f4c7be84c7d2479a75f33128f15777584bfec410c3e130b4504fe061991a78365add223a3dfb5ca79a988f9ffcf46039b3100000000",
txid: "113dfc827e4535dccc6aa7fcff5482b4de0fb2ab70f52c44c12c12bca3be5847",
inputs: vec![546, 11_210],
outputs: vec![546, 9_005],
// `vsize` is different from the estimated value due to the signatures der serialization.
vsize: 244,
weight: 975,
fee: 2_205,
});
}
Loading

0 comments on commit d4af84a

Please sign in to comment.