Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Zcash]: Migrate Zcash blockchain to Rust #4232

Merged
merged 26 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a1f78dc
[Zcash]: Add `google::protobuf::Any chain_specific` parameters to Bi…
satoshiotomakan Jan 16, 2025
6cc8f49
Merge branch 'master' into s/rust-zcash
satoshiotomakan Jan 16, 2025
87a6954
[BitcoinV2]: Add `TransactionBuilder::fee_policy`
satoshiotomakan Jan 17, 2025
000ac37
Merge branch 'master' into s/rust-zcash
satoshiotomakan Jan 17, 2025
e40718e
[BitcoinV2]: Fix compile errors
satoshiotomakan Jan 17, 2025
ac6bab6
[BitcoinV2]: Add `tw_zcash` blockchain skeleton
satoshiotomakan Jan 20, 2025
729db11
[BitcoinV2]: Add `ZcashSigningRequestBuilder`
satoshiotomakan Jan 20, 2025
9b7b0d8
[BitcoinV2]: Make Bitcoin's Signer, Compiler and Planner generic
satoshiotomakan Jan 22, 2025
a4b0644
[BitcoinV2]: Add `TAddress`
satoshiotomakan Jan 22, 2025
08fdb25
[Zcash]: Add `preImageHashes` test
satoshiotomakan Jan 22, 2025
b0c4e6f
refactor(zcash): Add signing, compiling, planning tests
satoshiotomakan Jan 23, 2025
1ab7537
refactor(zcash): Add Komodo Rust blockchain
satoshiotomakan Jan 23, 2025
a701bb7
refactor(komodo): Add integration tests
satoshiotomakan Jan 23, 2025
a37e197
refactor(komodo): Add Compiling test
satoshiotomakan Jan 23, 2025
f1f3c00
refactor(komodo): Add Signing test
satoshiotomakan Jan 23, 2025
0d3cb0c
refactor(zcash): Add PSBT not-supported test
satoshiotomakan Jan 23, 2025
466d681
refactor(zcash): Add support for BitcoinV2 protocol for Zcash impleme…
satoshiotomakan Jan 23, 2025
5f05e3f
refactor(zcash): Add Android test
satoshiotomakan Jan 23, 2025
779760b
refactor(zcash): Add TS test
satoshiotomakan Jan 23, 2025
b46ddb6
refactor(zcash): Add last tests
satoshiotomakan Jan 23, 2025
f0f9386
Merge branch 'master' into s/rust-zcash
satoshiotomakan Jan 23, 2025
04e0ebc
[CI] Trigger CI
satoshiotomakan Jan 23, 2025
e0d7928
refactor(zcash): Resolve review comments
satoshiotomakan Jan 27, 2025
87b0012
refactor(zcash): Fix Rust sample
satoshiotomakan Jan 27, 2025
cc98404
Merge branch 'master' into s/rust-zcash
satoshiotomakan Jan 27, 2025
100638f
Merge branch 'master' into s/rust-zcash
satoshiotomakan Jan 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/linux-ci-rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ jobs:
run: |
tools/check-coverage rust/coverage.stats rust/coverage.info

- name: Run Doc tests
run: |
tools/rust-test doc

# Run Rust tests in WASM.
test-wasm:
runs-on: ubuntu-24.04
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import wallet.core.jni.PublicKeyType
import wallet.core.jni.proto.Bitcoin
import wallet.core.jni.proto.Bitcoin.SigningOutput
import wallet.core.jni.proto.BitcoinV2
import wallet.core.jni.proto.Utxo
import wallet.core.jni.proto.Common.SigningError

class TestBitcoinSigning {
Expand Down Expand Up @@ -172,7 +173,7 @@ class TestBitcoinSigning {
val publicKey = ByteString.copyFrom(privateKey.getPublicKeySecp256k1(true).data())

val utxo0 = BitcoinV2.Input.newBuilder()
.setOutPoint(BitcoinV2.OutPoint.newBuilder().apply {
.setOutPoint(Utxo.OutPoint.newBuilder().apply {
hash = ByteString.copyFrom(txId)
vout = 1
})
Expand Down Expand Up @@ -238,7 +239,7 @@ class TestBitcoinSigning {
val publicKey = ByteString.copyFrom(privateKey.getPublicKeySecp256k1(true).data())

val utxo0 = BitcoinV2.Input.newBuilder()
.setOutPoint(BitcoinV2.OutPoint.newBuilder().apply {
.setOutPoint(Utxo.OutPoint.newBuilder().apply {
hash = ByteString.copyFrom(txIdCommit)
vout = 0
})
Expand Down Expand Up @@ -299,7 +300,7 @@ class TestBitcoinSigning {
val bobAddress = "bc1qazgc2zhu2kmy42py0vs8d7yff67l3zgpwfzlpk"

val utxo0 = BitcoinV2.Input.newBuilder()
.setOutPoint(BitcoinV2.OutPoint.newBuilder().apply {
.setOutPoint(Utxo.OutPoint.newBuilder().apply {
hash = ByteString.copyFrom(txIdInscription)
vout = 0
})
Expand All @@ -310,7 +311,7 @@ class TestBitcoinSigning {
})

val utxo1 = BitcoinV2.Input.newBuilder()
.setOutPoint(BitcoinV2.OutPoint.newBuilder().apply {
.setOutPoint(Utxo.OutPoint.newBuilder().apply {
hash = ByteString.copyFrom(txIdForFees)
vout = 1
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import org.junit.Test
import wallet.core.java.AnySigner
import wallet.core.jni.BitcoinSigHashType
import wallet.core.jni.CoinType
import wallet.core.jni.proto.Utxo
import wallet.core.jni.proto.Bitcoin
import wallet.core.jni.proto.BitcoinV2
import wallet.core.jni.proto.Common.SigningError
Expand All @@ -22,7 +23,7 @@ class TestBitcoinCashSigning {
val privateKey = "7fdafb9db5bc501f2096e7d13d331dc7a75d9594af3d251313ba8b6200f4e384".toHexBytesInByteString()

val utxo1 = BitcoinV2.Input.newBuilder().apply {
outPoint = BitcoinV2.OutPoint.newBuilder().apply {
outPoint = Utxo.OutPoint.newBuilder().apply {
hash = "e28c2b955293159898e34c6840d99bf4d390e2ee1c6f606939f18ee1e2000d05".toHexBytesInByteString()
vout = 2
}.build()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright © 2017 Trust Wallet.

package com.trustwallet.core.app.blockchains.zcash

import com.google.protobuf.ByteString
import com.trustwallet.core.app.utils.Numeric
import org.junit.Assert.assertEquals
import org.junit.Test

import wallet.core.java.AnySigner
import wallet.core.jni.BitcoinSigHashType
import wallet.core.jni.CoinType.ZCASH
import wallet.core.jni.proto.Common.SigningError
import wallet.core.jni.proto.Bitcoin
import wallet.core.jni.proto.Bitcoin.SigningOutput
import wallet.core.jni.proto.BitcoinV2
import wallet.core.jni.proto.Utxo
import wallet.core.jni.proto.Zcash

class TestZcashSigner {

init {
System.loadLibrary("TrustWalletCore")
}

@Test
fun testSignZcashV2() {
// Successfully broadcasted: https://explorer.zcha.in/transactions/ec9033381c1cc53ada837ef9981c03ead1c7c41700ff3a954389cfaddc949256
val privateKeyData = Numeric.hexStringToByteArray("a9684f5bebd0e1208aae2e02bc9e9163bd1965ad23d8538644e1df8b99b99559")
val dustSatoshis = 546.toLong()
val senderAddress = "t1gWVE2uyrET2CxSmCaBiKzmWxQdHhnvMSz"
val toAddress = "t1QahNjDdibyE4EdYkawUSKBBcVTSqv64CS"
val sapplingBranchId = Numeric.hexStringToByteArray("0xbb09b876")

val txid0 = Numeric.hexStringToByteArray("3a19dd44032dfed61bfca5ba5751aab8a107b30609cbd5d70dc5ef09885b6853").reversedArray()
val utxo0 = BitcoinV2.Input.newBuilder()
.setOutPoint(Utxo.OutPoint.newBuilder().apply {
hash = ByteString.copyFrom(txid0)
vout = 0
})
.setValue(494_000)
.setSighashType(BitcoinSigHashType.ALL.value())
.setReceiverAddress(senderAddress)

val out0 = BitcoinV2.Output.newBuilder()
.setValue(488_000)
.setToAddress(toAddress)

val builder = BitcoinV2.TransactionBuilder.newBuilder()
.setVersion(BitcoinV2.TransactionVersion.UseDefault)
.addInputs(utxo0)
.addOutputs(out0)
.setInputSelector(BitcoinV2.InputSelector.UseAll)
.setFixedDustThreshold(dustSatoshis)
// Set ZCash specific extra parameters.
.setZcashExtraData(Zcash.TransactionBuilderExtraData.newBuilder().apply {
branchId = ByteString.copyFrom(sapplingBranchId)
})
val signingInput = BitcoinV2.SigningInput.newBuilder()
.setBuilder(builder)
.addPrivateKeys(ByteString.copyFrom(privateKeyData))
.setChainInfo(BitcoinV2.ChainInfo.newBuilder().apply {
p2PkhPrefix = 184
p2ShPrefix = 189
})
.build()

val legacySigningInput = Bitcoin.SigningInput.newBuilder().apply {
signingV2 = signingInput
coinType = ZCASH.value()
}

val output = AnySigner.sign(legacySigningInput.build(), ZCASH, SigningOutput.parser())

assertEquals(output.error, SigningError.OK)
assertEquals(output.signingResultV2.errorMessage, "")
assertEquals(output.signingResultV2.error, SigningError.OK)
assertEquals(Numeric.toHexString(output.signingResultV2.encoded.toByteArray()), "0x0400008085202f890153685b8809efc50dd7d5cb0906b307a1b8aa5157baa5fc1bd6fe2d0344dd193a000000006b483045022100ca0be9f37a4975432a52bb65b25e483f6f93d577955290bb7fb0060a93bfc92002203e0627dff004d3c72a957dc9f8e4e0e696e69d125e4d8e275d119001924d3b48012103b243171fae5516d1dc15f9178cfcc5fdc67b0a883055c117b01ba8af29b953f6ffffffff0140720700000000001976a91449964a736f3713d64283fd0018626ba50091c7e988ac00000000000000000000000000000000000000")
assertEquals(Numeric.toHexString(output.signingResultV2.txid.toByteArray()), "0xec9033381c1cc53ada837ef9981c03ead1c7c41700ff3a954389cfaddc949256")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@

use tw_any_coin::test_utils::address_utils::{
test_address_derive, test_address_get_data, test_address_invalid, test_address_normalization,
test_address_valid,
test_address_valid, KeyType,
};
use tw_coin_registry::coin_type::CoinType;

#[test]
fn test_{COIN_ID}_address_derive() {
test_address_derive(CoinType::{COIN_TYPE}, "PRIVATE_KEY", "EXPECTED ADDRESS");
test_address_derive(
CoinType::{COIN_TYPE},
KeyType::PrivateKey("PRIVATE_KEY"),
"EXPECTED ADDRESS"
);
}

#[test]
Expand Down
1 change: 1 addition & 0 deletions include/TrustWalletCore/TWBlockchain.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ enum TWBlockchain {
TWBlockchainNativeInjective = 54, // Cosmos
TWBlockchainBitcoinCash = 55,
TWBlockchainPactus = 56,
TWBlockchainKomodo = 57,
};

TW_EXTERN_C_END
2 changes: 1 addition & 1 deletion registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -1397,7 +1397,7 @@
"coinId": 141,
"symbol": "KMD",
"decimals": 8,
"blockchain": "Zcash",
"blockchain": "Komodo",
"derivation": [
{
"path": "m/44'/141'/0'/0/0",
Expand Down
29 changes: 29 additions & 0 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ members = [
"chains/tw_ethereum",
"chains/tw_greenfield",
"chains/tw_internet_computer",
"chains/tw_komodo",
"chains/tw_native_evmos",
"chains/tw_native_injective",
"chains/tw_pactus",
Expand All @@ -18,6 +19,7 @@ members = [
"chains/tw_sui",
"chains/tw_thorchain",
"chains/tw_ton",
"chains/tw_zcash",
"frameworks/tw_substrate",
"frameworks/tw_ton_sdk",
"frameworks/tw_utxo",
Expand Down
25 changes: 25 additions & 0 deletions rust/chains/tw_bitcoin/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,35 @@
//
// Copyright © 2017 Trust Wallet.

use crate::modules::protobuf_builder::standard_protobuf_builder::StandardProtobufBuilder;
use crate::modules::protobuf_builder::ProtobufBuilder;
use crate::modules::psbt_request::standard_psbt_request_builder::StandardPsbtRequestBuilder;
use crate::modules::psbt_request::PsbtRequestBuilder;
use crate::modules::signing_request::standard_signing_request::StandardSigningRequestBuilder;
use crate::modules::signing_request::SigningRequestBuilder;
use tw_coin_entry::error::prelude::SigningResult;
use tw_utxo::address::standard_bitcoin::StandardBitcoinAddress;
use tw_utxo::context::{AddressPrefixes, UtxoContext};
use tw_utxo::fee::fee_estimator::StandardFeeEstimator;
use tw_utxo::script::Script;
use tw_utxo::transaction::standard_transaction::Transaction;

/// A set of associated modules different from a chain to chain.
/// The modules are mainly used to generate a signing request from a [`BitcoinV2::Proto::SigningInput`],
/// and generate a [`Utxo::Proto::Transaction`] output when the transaction is signed.
pub trait BitcoinSigningContext: UtxoContext + Sized {
type SigningRequestBuilder: SigningRequestBuilder<Self>;
type ProtobufBuilder: ProtobufBuilder<Self>;
type PsbtRequestBuilder: PsbtRequestBuilder<Self>;
}

#[derive(Default)]
pub struct StandardBitcoinContext;

impl UtxoContext for StandardBitcoinContext {
type Address = StandardBitcoinAddress;
type Transaction = Transaction;
type FeeEstimator = StandardFeeEstimator<Self::Transaction>;

fn addr_to_script_pubkey(
addr: &Self::Address,
Expand All @@ -26,3 +45,9 @@ impl UtxoContext for StandardBitcoinContext {
}
}
}

impl BitcoinSigningContext for StandardBitcoinContext {
type SigningRequestBuilder = StandardSigningRequestBuilder;
type ProtobufBuilder = StandardProtobufBuilder;
type PsbtRequestBuilder = StandardPsbtRequestBuilder;
}
20 changes: 10 additions & 10 deletions rust/chains/tw_bitcoin/src/modules/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
//
// Copyright © 2017 Trust Wallet.

use crate::context::BitcoinSigningContext;
use crate::modules::protobuf_builder::ProtobufBuilder;
use crate::modules::psbt_request::PsbtRequest;
use crate::modules::psbt_request::{PsbtRequest, PsbtRequestBuilder};
use crate::modules::signing_request::SigningRequestBuilder;
use std::borrow::Cow;
use std::marker::PhantomData;
Expand All @@ -17,6 +18,7 @@ use tw_proto::BitcoinV2::Proto::mod_PreSigningOutput::{
};
use tw_proto::BitcoinV2::Proto::mod_SigningInput::OneOftransaction as TransactionType;
use tw_utxo::context::UtxoContext;
use tw_utxo::encode::Encodable;
use tw_utxo::modules::sighash_computer::{SighashComputer, TaprootTweak, TxPreimage};
use tw_utxo::modules::sighash_verifier::SighashVerifier;
use tw_utxo::modules::tx_compiler::TxCompiler;
Expand All @@ -29,7 +31,7 @@ pub struct BitcoinCompiler<Context: UtxoContext> {
_phantom: PhantomData<Context>,
}

impl<Context: UtxoContext> BitcoinCompiler<Context> {
impl<Context: BitcoinSigningContext> BitcoinCompiler<Context> {
/// Please note that [`Proto::SigningInput::public_key`] must be set.
/// If the public key should be derived from a private key, please do it before this method is called.
#[inline]
Expand All @@ -47,11 +49,11 @@ impl<Context: UtxoContext> BitcoinCompiler<Context> {
) -> SigningResult<Proto::PreSigningOutput<'static>> {
let unsigned_tx = match input.transaction {
TransactionType::builder(ref tx_builder) => {
let request = SigningRequestBuilder::<Context>::build(coin, &input, tx_builder)?;
let request = Context::SigningRequestBuilder::build(coin, &input, tx_builder)?;
TxPlanner::plan(request)?.unsigned_tx
},
TransactionType::psbt(ref psbt) => {
PsbtRequest::<Context>::build(&input, psbt)?.unsigned_tx
Context::PsbtRequestBuilder::build(&input, psbt)?.unsigned_tx
},
TransactionType::None => {
return SigningError::err(SigningErrorType::Error_invalid_params)
Expand Down Expand Up @@ -110,15 +112,14 @@ impl<Context: UtxoContext> BitcoinCompiler<Context> {
tx_builder_input: &Proto::TransactionBuilder,
signatures: Vec<SignatureBytes>,
) -> SigningResult<Proto::SigningOutput<'static>> {
let request = SigningRequestBuilder::<Context>::build(coin, input, tx_builder_input)?;
let request = Context::SigningRequestBuilder::build(coin, input, tx_builder_input)?;
let SelectResult { unsigned_tx, plan } = TxPlanner::plan(request)?;

SighashVerifier::verify_signatures(&unsigned_tx, &signatures)?;
let signed_tx = TxCompiler::compile(unsigned_tx, &signatures)?;
let tx_proto = ProtobufBuilder::tx_to_proto(&signed_tx);

Ok(Proto::SigningOutput {
transaction: Some(tx_proto),
transaction: Context::ProtobufBuilder::tx_to_proto(&signed_tx),
encoded: Cow::from(signed_tx.encode_out()),
txid: Cow::from(signed_tx.txid()),
// `vsize` could have been changed after the transaction being signed.
Expand All @@ -136,15 +137,14 @@ impl<Context: UtxoContext> BitcoinCompiler<Context> {
psbt: &Proto::Psbt,
signatures: Vec<SignatureBytes>,
) -> SigningResult<Proto::SigningOutput<'static>> {
let PsbtRequest { unsigned_tx, .. } = PsbtRequest::<Context>::build(input, psbt)?;
let PsbtRequest { unsigned_tx, .. } = Context::PsbtRequestBuilder::build(input, psbt)?;
let fee = unsigned_tx.fee()?;

SighashVerifier::verify_signatures(&unsigned_tx, &signatures)?;
let signed_tx = TxCompiler::compile(unsigned_tx, &signatures)?;
let tx_proto = ProtobufBuilder::tx_to_proto(&signed_tx);

Ok(Proto::SigningOutput {
transaction: Some(tx_proto),
transaction: Context::ProtobufBuilder::tx_to_proto(&signed_tx),
encoded: Cow::from(signed_tx.encode_out()),
txid: Cow::from(signed_tx.txid()),
// `vsize` could have been changed after the transaction being signed.
Expand Down
Loading
Loading