Skip to content

Commit

Permalink
[Pactus]: Support Pactus Blockchain (#4057)
Browse files Browse the repository at this point in the history
* feat: support Pactus

* fix: fix compile_impl implementation

* update Pactus address tests

* encoding treasury addresss

* update tests for HD wallet

* define Decodable trait and implement decode

* encode and decode for amount and address

* decode transaction and calculate id

* test(pactus): add compiler and signer tests

* add C++ integration tests

* extend central derivation and validation tests

* revert changes on non-pactus files

* fix compile error

* update tests for Kotlin and Swift

* use H160 for PubKeyHash

* update VarInt test

* fix: resolve PR review comments

* fix compile error

* update C++ tests

* fix formatting issue

* update Java and Swift tests

* update swift test

* fix broken swift test

* fix: add successfully transfer broadcasted in mainnet

* add bls validator public key

* add bond payload encodable and decodable

* add generic for decode_fix_slice

* add bond payload for sign transaction

* add bond payload as public use

* add pub use validator public key

* add original test case for bond and transfer

* refactor transfer in test compile

* add bond sign test

* refactor transfer sign payload

* refactor change to bond module name

* fix rust tests

* add transfer and bond test for swift

* add test sign bond without public key

* use broadcasted transaction data for tests

* fix mistake on Android test

---------

Co-authored-by: Javad <[email protected]>
  • Loading branch information
b00f and Ja7ad authored Nov 7, 2024
1 parent c4907d4 commit 6be89c9
Show file tree
Hide file tree
Showing 56 changed files with 3,142 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -154,5 +154,6 @@ class CoinAddressDerivationTests {
TIA -> assertEquals("celestia142j9u5eaduzd7faumygud6ruhdwme98qpwmfv7", address)
NATIVEZETACHAIN -> assertEquals("zeta13u6g7vqgw074mgmf2ze2cadzvkz9snlwywj304", address)
DYDX -> assertEquals("dydx142j9u5eaduzd7faumygud6ruhdwme98qeayaky", address)
PACTUS -> assertEquals("pc1r7ys2g5a4xc2qtm0t4q987m4mvs57w5g0v4pvzg", address)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright © 2017 Trust Wallet.

package com.trustwallet.core.app.blockchains.pactus

import com.trustwallet.core.app.utils.toHex
import com.trustwallet.core.app.utils.toHexByteArray
import org.junit.Assert.assertEquals
import org.junit.Test
import wallet.core.jni.*

class TestPactusAddress {

init {
System.loadLibrary("TrustWalletCore")
}

@Test
fun testAddress() {
val key = PrivateKey("4e51f1f3721f644ac7a193be7f5e7b8c2abaa3467871daf4eacb5d3af080e5d6".toHexByteArray())
val pubkey = key.publicKeyEd25519
val address = AnyAddress(pubkey, CoinType.PACTUS)
val expected = AnyAddress("pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr", CoinType.PACTUS)

assertEquals(pubkey.data().toHex(), "0x95794161374b22c696dabb98e93f6ca9300b22f3b904921fbf560bb72145f4fa")
assertEquals(address.description(), expected.description())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright © 2017 Trust Wallet.

package com.trustwallet.core.app.blockchains.pactus

import com.google.protobuf.ByteString
import com.trustwallet.core.app.utils.toHexByteArray
import org.junit.Assert.assertEquals
import org.junit.Test
import wallet.core.jni.PrivateKey
import wallet.core.java.AnySigner
import wallet.core.jni.CoinType
import wallet.core.jni.CoinType.PACTUS
import wallet.core.jni.proto.Pactus
import wallet.core.jni.proto.Pactus.SigningOutput
import com.trustwallet.core.app.utils.Numeric
import org.junit.Assert.assertArrayEquals

class TestPactusSigner {

init {
System.loadLibrary("TrustWalletCore")
}

@Test
fun testPactusTransferSigning() {
// Successfully broadcasted transaction:
// https://pacviewer.com/transaction/1b6b7226f7935a15f05371d1a1fefead585a89704ce464b7cc1d453d299d235f
//
val signingInput = Pactus.SigningInput.newBuilder()
signingInput.apply {
privateKey = ByteString.copyFrom(
PrivateKey("4e51f1f3721f644ac7a193be7f5e7b8c2abaa3467871daf4eacb5d3af080e5d6"
.toHexByteArray()).data()
)
transaction = Pactus.TransactionMessage.newBuilder().apply {
lockTime = 2335524
fee = 10000000
memo = "wallet-core"
transfer = Pactus.TransferPayload.newBuilder().apply {
sender = "pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr"
receiver = "pc1r0g22ufzn8qtw0742dmfglnw73e260hep0k3yra"
amount = 200000000
}.build()
}.build()
}

val output = AnySigner.sign(signingInput.build(), PACTUS, SigningOutput.parser())

assertEquals(
"0x1b6b7226f7935a15f05371d1a1fefead585a89704ce464b7cc1d453d299d235f",
Numeric.toHexString(output.transactionId.toByteArray())
)

assertEquals(
"0x4ed8fee3d8992e82660dd05bbe8608fc56ceabffdeeee61e3213b9b49d33a0fc8dea6d79ee7ec60f66433f189ed9b3c50b2ad6fa004e26790ee736693eda8506",
Numeric.toHexString(output.signature.toByteArray())
)

assertEquals(
"0x000124a3230080ade2040b77616c6c65742d636f726501037098338e0b6808119dfd4457ab806b9c2059b89b037a14ae24533816e7faaa6ed28fcdde8e55a7df218084af5f4ed8fee3d8992e82660dd05bbe8608fc56ceabffdeeee61e3213b9b49d33a0fc8dea6d79ee7ec60f66433f189ed9b3c50b2ad6fa004e26790ee736693eda850695794161374b22c696dabb98e93f6ca9300b22f3b904921fbf560bb72145f4fa",
Numeric.toHexString(output.signedTransactionData.toByteArray())
)
}

@Test
fun testPactusBondWithPublicKeySigning() {
// Successfully broadcasted transaction:
// https://pacviewer.com/transaction/d194b445642a04ec78ced4448696e50b733f2f0b517a23871882c0eefaf1c28f
//
val signingInput = Pactus.SigningInput.newBuilder()
signingInput.apply {
privateKey = ByteString.copyFrom(
PrivateKey("4e51f1f3721f644ac7a193be7f5e7b8c2abaa3467871daf4eacb5d3af080e5d6"
.toHexByteArray()).data()
)
transaction = Pactus.TransactionMessage.newBuilder().apply {
lockTime = 2339009
fee = 10000000
memo = "wallet-core"
bond = Pactus.BondPayload.newBuilder().apply {
sender = "pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr"
receiver = "pc1p9y5gmu9l002tt60wak9extgvwm69rq3a9ackrl"
stake = 1000000000
publicKey = "public1pnz75msstqdrq5eguvcwanug0zauhqjw2cc4flmez3qethnp68y64ehc4k69amapj7x4na2uda0snqz4yxujgx3jsse4f64fgy7jkh0xauvhrc5ts09vfk48g85t0js66hvajm6xruemsvlxqv3xvkyur8v9v0mtn"
}.build()
}.build()
}

val output = AnySigner.sign(signingInput.build(), PACTUS, SigningOutput.parser())

assertEquals(
"0xd194b445642a04ec78ced4448696e50b733f2f0b517a23871882c0eefaf1c28f",
Numeric.toHexString(output.transactionId.toByteArray())
)

assertEquals(
"0x0d7bc6d94927534b89e2f53bcfc9fc849e0e2982438955eda55b4338328adac79d4ee3216d143f0e1629764ab650734f8ba188e716d71f9eff65e39ce7006300",
Numeric.toHexString(output.signature.toByteArray())
)

assertEquals(
"0x0001c1b0230080ade2040b77616c6c65742d636f726502037098338e0b6808119dfd4457ab806b9c2059b89b0129288df0bf7bd4b5e9eeed8b932d0c76f451823d6098bd4dc20b03460a651c661dd9f10f17797049cac62a9fef228832bbcc3a39355cdf15b68bddf432f1ab3eab8debe1300aa43724834650866a9d552827a56bbcdde32e3c517079589b54e83d16f9435abb3b2de8c3e677067cc0644ccb13833b8094ebdc030d7bc6d94927534b89e2f53bcfc9fc849e0e2982438955eda55b4338328adac79d4ee3216d143f0e1629764ab650734f8ba188e716d71f9eff65e39ce700630095794161374b22c696dabb98e93f6ca9300b22f3b904921fbf560bb72145f4fa",
Numeric.toHexString(output.signedTransactionData.toByteArray())
)
}

@Test
fun testPactusBondWithoutPublicKeySigning() {
// Successfully broadcasted transaction:
// https://pacviewer.com/transaction/f83f583a5c40adf93a90ea536a7e4b467d30ca4f308d5da52624d80c42adec80
//
val signingInput = Pactus.SigningInput.newBuilder()
signingInput.apply {
privateKey = ByteString.copyFrom(
PrivateKey("4e51f1f3721f644ac7a193be7f5e7b8c2abaa3467871daf4eacb5d3af080e5d6"
.toHexByteArray()).data()
)
transaction = Pactus.TransactionMessage.newBuilder().apply {
lockTime = 2335580
fee = 10000000
memo = "wallet-core"
bond = Pactus.BondPayload.newBuilder().apply {
sender = "pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr"
receiver = "pc1p6taz5l2kq5ppnxv4agnqj48svnvsy797xpe6wd"
stake = 1000000000
}.build()
}.build()
}

val output = AnySigner.sign(signingInput.build(), PACTUS, SigningOutput.parser())

assertEquals(
"0xf83f583a5c40adf93a90ea536a7e4b467d30ca4f308d5da52624d80c42adec80",
Numeric.toHexString(output.transactionId.toByteArray())
)

assertEquals(
"0x9e6279fb64067c7d7316ac74630bbb8589df268aa4548f1c7d85c087a8748ff0715b9149afbd94c5d8ee6b37c787ec63e963cbb38be513ebc436aa58f9a8f00d",
Numeric.toHexString(output.signature.toByteArray())
)

assertEquals(
"0x00015ca3230080ade2040b77616c6c65742d636f726502037098338e0b6808119dfd4457ab806b9c2059b89b01d2fa2a7d560502199995ea260954f064d90278be008094ebdc039e6279fb64067c7d7316ac74630bbb8589df268aa4548f1c7d85c087a8748ff0715b9149afbd94c5d8ee6b37c787ec63e963cbb38be513ebc436aa58f9a8f00d95794161374b22c696dabb98e93f6ca9300b22f3b904921fbf560bb72145f4fa",
Numeric.toHexString(output.signedTransactionData.toByteArray())
)
}
}
1 change: 1 addition & 0 deletions docs/registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ This list is generated from [./registry.json](../registry.json)
| 14001 | WAX | WAXP | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/wax/info/logo.png" width="32" /> | <http://wax.io> |
| 18000 | Meter | MTR | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/meter/info/logo.png" width="32" /> | <https://meter.io/> |
| 19167 | Flux | FLUX | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/zelcash/info/logo.png" width="32" /> | <https://runonflux.io> |
| 21888 | Pactus | PAC | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/pactus/info/logo.png" width="32" /> | <https://pactus.org> |
| 52752 | Celo | CELO | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/celo/info/logo.png" width="32" /> | <https://celo.org> |
| 59144 | Linea | ETH | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/linea/info/logo.png" width="32" /> | <https://linea.build> |
| 81457 | Blast | ETH | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/blast/info/logo.png" width="32" /> | <https://blast.io> |
Expand Down
1 change: 1 addition & 0 deletions include/TrustWalletCore/TWBlockchain.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ enum TWBlockchain {
TWBlockchainNativeEvmos = 53, // Cosmos
TWBlockchainNativeInjective = 54, // Cosmos
TWBlockchainBitcoinCash = 55,
TWBlockchainPactus = 56,
};

TW_EXTERN_C_END
1 change: 1 addition & 0 deletions include/TrustWalletCore/TWCoinType.h
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ enum TWCoinType {
TWCoinTypeBlast = 81457,
TWCoinTypeBounceBit = 6001,
TWCoinTypeZkLinkNova = 810180,
TWCoinTypePactus = 21888,
// end_of_tw_coin_type_marker_do_not_modify
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,5 +147,6 @@ class CoinAddressDerivationTests {
Tia -> "celestia142j9u5eaduzd7faumygud6ruhdwme98qpwmfv7"
NativeZetaChain -> "zeta13u6g7vqgw074mgmf2ze2cadzvkz9snlwywj304"
Dydx -> "dydx142j9u5eaduzd7faumygud6ruhdwme98qeayaky"
Pactus -> "pc1r7ys2g5a4xc2qtm0t4q987m4mvs57w5g0v4pvzg"
}
}
27 changes: 27 additions & 0 deletions registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -4787,5 +4787,32 @@
"rpc": "https://rpc.zklink.io",
"documentation": "https://docs.zklink.io"
}
},
{
"id": "pactus",
"name": "Pactus",
"coinId": 21888,
"symbol": "PAC",
"decimals": 9,
"blockchain": "Pactus",
"derivation": [
{
"path": "m/44'/21888'/3'/0'"
}
],
"curve": "ed25519",
"publicKeyType": "ed25519",
"hrp": "pc",
"explorer": {
"url": "https://pacviewer.com",
"txPath": "/transaction/",
"accountPath": "/address/"
},
"info": {
"url": "https://pactus.org",
"source": "https://github.com/pactus-project/pactus",
"rpc": "https://docs.pactus.org/api/http",
"documentation": "https://docs.pactus.org"
}
}
]
15 changes: 15 additions & 0 deletions rust/Cargo.lock

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

1 change: 1 addition & 0 deletions rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ members = [
"chains/tw_internet_computer",
"chains/tw_native_evmos",
"chains/tw_native_injective",
"chains/tw_pactus",
"chains/tw_ronin",
"chains/tw_solana",
"chains/tw_sui",
Expand Down
18 changes: 18 additions & 0 deletions rust/chains/tw_pactus/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "tw_pactus"
version = "0.1.0"
edition = "2021"

[dependencies]
bech32 = "0.9.1"
byteorder = "1.4"
tw_coin_entry = { path = "../../tw_coin_entry" }
tw_keypair = { path = "../../tw_keypair" }
tw_memory = { path = "../../tw_memory" }
tw_proto = { path = "../../tw_proto" }
tw_hash = { path = "../../tw_hash" }
tw_encoding = { path = "../../tw_encoding" }

[dev-dependencies]
tw_encoding = { path = "../../tw_encoding" }

84 changes: 84 additions & 0 deletions rust/chains/tw_pactus/src/compiler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright © 2017 Trust Wallet.

use tw_coin_entry::coin_context::CoinContext;
use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes};
use tw_coin_entry::error::prelude::*;
use tw_coin_entry::signing_output_error;
use tw_keypair::ed25519;
use tw_proto::Pactus::Proto;
use tw_proto::TxCompiler::Proto as CompilerProto;

use crate::modules::tx_builder::TxBuilder;

pub struct PactusCompiler;

impl PactusCompiler {
#[inline]
pub fn preimage_hashes(
coin: &dyn CoinContext,
input: Proto::SigningInput<'_>,
) -> CompilerProto::PreSigningOutput<'static> {
Self::preimage_hashes_impl(coin, input)
.unwrap_or_else(|e| signing_output_error!(CompilerProto::PreSigningOutput, e))
}

fn preimage_hashes_impl(
_coin: &dyn CoinContext,
input: Proto::SigningInput<'_>,
) -> SigningResult<CompilerProto::PreSigningOutput<'static>> {
let trx = TxBuilder::from_proto(&input)?;
let sign_bytes = trx.sign_bytes()?;

let output = CompilerProto::PreSigningOutput {
data_hash: trx.id().into(),
data: sign_bytes.into(),
..CompilerProto::PreSigningOutput::default()
};

Ok(output)
}

#[inline]
pub fn compile(
coin: &dyn CoinContext,
input: Proto::SigningInput<'_>,
signatures: Vec<SignatureBytes>,
public_keys: Vec<PublicKeyBytes>,
) -> Proto::SigningOutput<'static> {
Self::compile_impl(coin, input, signatures, public_keys)
.unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e))
}

fn compile_impl(
_coin: &dyn CoinContext,
input: Proto::SigningInput<'_>,
signatures: Vec<SignatureBytes>,
public_keys: Vec<PublicKeyBytes>,
) -> SigningResult<Proto::SigningOutput<'static>> {
let signature_bytes = signatures
.first()
.or_tw_err(SigningErrorType::Error_signatures_count)?;
let public_key_bytes = public_keys
.first()
.or_tw_err(SigningErrorType::Error_signatures_count)?;

let public_key = ed25519::sha512::PublicKey::try_from(public_key_bytes.as_slice())?;
let signature = ed25519::Signature::try_from(signature_bytes.as_slice())?;

let mut trx = TxBuilder::from_proto(&input)?;
trx.set_signatory(public_key.to_owned(), signature.to_owned());

let data = trx.to_bytes()?;

let output = Proto::SigningOutput {
transaction_id: trx.id().into(),
signed_transaction_data: data.into(),
signature: signature.to_bytes().to_vec().into(),
..Proto::SigningOutput::default()
};

Ok(output)
}
}
Loading

0 comments on commit 6be89c9

Please sign in to comment.