Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,23 @@ class TestCoinType {
assertEquals(CoinType.TEZOS.value(), 1729)
assertEquals(CoinType.QTUM.value(), 2301)
assertEquals(CoinType.NEBULAS.value(), 2718)
assertEquals(CoinType.PACTUS.value(), 21888)
}

@Test
fun testCoinPurpose() {
assertEquals(Purpose.BIP84, CoinType.BITCOIN.purpose())
assertEquals(Purpose.BIP44, CoinType.PACTUS.purpose())
}

@Test
fun testCoinCurve() {
assertEquals(Curve.SECP256K1, CoinType.BITCOIN.curve())
assertEquals(Curve.ED25519, CoinType.PACTUS.curve())
}

@Test
fun testDerivationPath() {
fun testDerivationPathBitcoin() {
var res = CoinType.createFromValue(CoinType.BITCOIN.value()).derivationPath().toString()
assertEquals(res, "m/84'/0'/0'/0/0")
res = CoinType.createFromValue(CoinType.BITCOIN.value()).derivationPathWithDerivation(Derivation.BITCOINLEGACY).toString()
Expand All @@ -61,10 +64,31 @@ class TestCoinType {
}

@Test
fun testDeriveAddressFromPublicKeyAndDerivation() {
fun testDeriveAddressFromPublicKeyAndDerivationBitcoin() {
val publicKey = PublicKey("0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798".toHexByteArray(), PublicKeyType.SECP256K1)

val address = CoinType.BITCOIN.deriveAddressFromPublicKeyAndDerivation(publicKey, Derivation.BITCOINSEGWIT)
assertEquals(address, "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4")
}

@Test
fun testDerivationPathPactus() {
var res = CoinType.createFromValue(CoinType.PACTUS.value()).derivationPath().toString()
assertEquals(res, "m/44'/21888'/3'/0'")
res = CoinType.createFromValue(CoinType.PACTUS.value()).derivationPathWithDerivation(Derivation.PACTUSMAINNET).toString()
assertEquals(res, "m/44'/21888'/3'/0'")
res = CoinType.createFromValue(CoinType.PACTUS.value()).derivationPathWithDerivation(Derivation.PACTUSTESTNET).toString()
assertEquals(res, "m/44'/21777'/3'/0'")
}

@Test
fun testDeriveAddressFromPublicKeyAndDerivationPactus() {
val publicKey = PublicKey("95794161374b22c696dabb98e93f6ca9300b22f3b904921fbf560bb72145f4fa".toHexByteArray(), PublicKeyType.ED25519)

val mainnet_address = CoinType.PACTUS.deriveAddressFromPublicKeyAndDerivation(publicKey, Derivation.PACTUSMAINNET)
assertEquals(mainnet_address, "pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr")

val testnet_address = CoinType.PACTUS.deriveAddressFromPublicKeyAndDerivation(publicKey, Derivation.PACTUSTESTNET)
assertEquals(testnet_address, "tpc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymzqkcrg")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class TestPactusAddress {
}

@Test
fun testAddress() {
fun testMainnetAddress() {
val key = PrivateKey("4e51f1f3721f644ac7a193be7f5e7b8c2abaa3467871daf4eacb5d3af080e5d6".toHexByteArray())
val pubkey = key.publicKeyEd25519
val address = AnyAddress(pubkey, CoinType.PACTUS)
Expand All @@ -26,4 +26,15 @@ class TestPactusAddress {
assertEquals(pubkey.data().toHex(), "0x95794161374b22c696dabb98e93f6ca9300b22f3b904921fbf560bb72145f4fa")
assertEquals(address.description(), expected.description())
}

@Test
fun testTestnetAddress() {
val key = PrivateKey("4e51f1f3721f644ac7a193be7f5e7b8c2abaa3467871daf4eacb5d3af080e5d6".toHexByteArray())
val pubkey = key.publicKeyEd25519
val address = AnyAddress(pubkey, CoinType.PACTUS, Derivation.PACTUSTESTNET)
val expected = AnyAddress("tpc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymzqkcrg", CoinType.PACTUS)

assertEquals(pubkey.data().toHex(), "0x95794161374b22c696dabb98e93f6ca9300b22f3b904921fbf560bb72145f4fa")
assertEquals(address.description(), expected.description())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ class TestHDWallet {
}

@Test
fun testGetKeyForCoin() {
fun testGetKeyForCoinBitcoin() {
val coin = CoinType.BITCOIN
val wallet = HDWallet(words, password)
val key = wallet.getKeyForCoin(coin)
Expand All @@ -109,7 +109,7 @@ class TestHDWallet {
}

@Test
fun testGetKeyDerivation() {
fun testGetKeyDerivationBitcoin() {
val coin = CoinType.BITCOIN
val wallet = HDWallet(words, password)

Expand All @@ -127,7 +127,7 @@ class TestHDWallet {
}

@Test
fun testGetAddressForCoin() {
fun testGetAddressForCoinBitcoin() {
val coin = CoinType.BITCOIN
val wallet = HDWallet(words, password)

Expand All @@ -136,7 +136,7 @@ class TestHDWallet {
}

@Test
fun testGetAddressDerivation() {
fun testGetAddressDerivationBitcoin() {
val coin = CoinType.BITCOIN
val wallet = HDWallet(words, password)

Expand All @@ -153,6 +153,49 @@ class TestHDWallet {
assertEquals(address4, "bc1pgqks0cynn93ymve4x0jq3u7hne77908nlysp289hc44yc4cmy0hslyckrz")
}

@Test
fun testGetKeyForCoinPactus() {
val coin = CoinType.PACTUS
val wallet = HDWallet(words, password)
val key = wallet.getKeyForCoin(coin)

val address = coin.deriveAddress(key)
assertEquals(address, "pc1rjkzc23l7qkkenx6xwy04srwppzfk6m5t7q46ff")
}

@Test
fun testGetKeyDerivationPactus() {
val coin = CoinType.PACTUS
val wallet = HDWallet(words, password)

val key1 = wallet.getKeyDerivation(coin, Derivation.PACTUSMAINNET)
assertEquals(key1.data().toHex(), "0x153fefb8168f246f9f77c60ea10765c1c39828329e87284ddd316770717f3a5e")

val key2 = wallet.getKeyDerivation(coin, Derivation.PACTUSTESTNET)
assertEquals(key2.data().toHex(), "0x54f3c54dd6af5794bea1f86de05b8b9f164215e8deee896f604919046399e54d")
}

@Test
fun testGetAddressForCoinPactus() {
val coin = CoinType.PACTUS
val wallet = HDWallet(words, password)

val address = wallet.getAddressForCoin(coin)
assertEquals(address, "pc1rjkzc23l7qkkenx6xwy04srwppzfk6m5t7q46ff")
}

@Test
fun testGetAddressDerivationPactus() {
val coin = CoinType.PACTUS
val wallet = HDWallet(words, password)

val address1 = wallet.getAddressDerivation(coin, Derivation.PACTUSMAINNET)
assertEquals(address1, "pc1rjkzc23l7qkkenx6xwy04srwppzfk6m5t7q46ff")

val address2 = wallet.getAddressDerivation(coin, Derivation.PACTUSTESTNET)
assertEquals(address2, "tpc1rjtamyqp203j4367q4plkp4qt32d7sv34kfmj5e")
}

@Test
fun testDerive() {
val wallet = HDWallet(words, password)
Expand Down
8 changes: 8 additions & 0 deletions codegen-v2/manifest/TWDerivation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,11 @@ enums:
value: 5
- name: solanaSolana
value: 6
- name: stratisSegwit
value: 7
- name: bitcoinTaproot
value: 8
- name: pactusMainnet
value: 9
- name: pactusTestnet
value: 10
2 changes: 2 additions & 0 deletions include/TrustWalletCore/TWDerivation.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ enum TWDerivation {
TWDerivationSolanaSolana = 6,
TWDerivationStratisSegwit = 7,
TWDerivationBitcoinTaproot = 8,
TWDerivationPactusMainnet = 9,
TWDerivationPactusTestnet = 10,
// end_of_derivation_enum - USED TO GENERATE CODE
};

Expand Down
5 changes: 5 additions & 0 deletions registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -4827,7 +4827,12 @@
"blockchain": "Pactus",
"derivation": [
{
"name": "mainnet",
"path": "m/44'/21888'/3'/0'"
},
{
"name": "testnet",
"path": "m/44'/21777'/3'/0'"
}
],
"curve": "ed25519",
Expand Down
10 changes: 8 additions & 2 deletions rust/chains/tw_pactus/src/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use tw_proto::TxCompiler::Proto as CompilerProto;
use crate::compiler::PactusCompiler;
use crate::modules::transaction_util::PactusTransactionUtil;
use crate::signer::PactusSigner;
use crate::types::network::Network;
use crate::types::Address;

pub struct PactusEntry;
Expand Down Expand Up @@ -60,13 +61,18 @@ impl CoinEntry for PactusEntry {
&self,
_coin: &dyn CoinContext,
public_key: PublicKey,
_derivation: Derivation,
derivation: Derivation,
_prefix: Option<Self::AddressPrefix>,
) -> AddressResult<Self::Address> {
let public_key = public_key
.to_ed25519()
.ok_or(AddressError::PublicKeyTypeMismatch)?;
Address::from_public_key(public_key)

match derivation {
Derivation::Default => Address::from_public_key(public_key, Network::Mainnet),
Derivation::Testnet => Address::from_public_key(public_key, Network::Testnet),
_ => AddressResult::Err(AddressError::Unsupported),
}
}

#[inline]
Expand Down
42 changes: 29 additions & 13 deletions rust/chains/tw_pactus/src/types/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ use tw_memory::Data;
use crate::encoder::error::Error;
use crate::encoder::{Decodable, Encodable};

const ADDRESS_HRP: &str = "pc";
use super::network::Network;

const TREASURY_ADDRESS_STRING: &str = "000000000000000000000000000000000000000000";

/// Enum for Pactus address types.
Expand Down Expand Up @@ -66,18 +67,20 @@ impl Decodable for AddressType {
/// The hash is computed as RIPEMD160(Blake2b(public key)).
#[derive(Debug, Clone, PartialEq)]
pub struct Address {
network: Network,
addr_type: AddressType,
pub_hash: H160,
}

impl Address {
pub fn from_public_key(public_key: &PublicKey) -> Result<Self, AddressError> {
pub fn from_public_key(public_key: &PublicKey, network: Network) -> Result<Self, AddressError> {
let pud_data = public_key.to_bytes();
let pub_hash_data =
ripemd_160(&blake2_b(pud_data.as_ref(), 32).map_err(|_| AddressError::Internal)?);
let pub_hash = Address::vec_to_pub_hash(pub_hash_data)?;

Ok(Address {
network,
addr_type: AddressType::Ed25519Account,
pub_hash,
})
Expand Down Expand Up @@ -110,12 +113,12 @@ impl fmt::Display for Address {
return f.write_str(TREASURY_ADDRESS_STRING);
}

let hrp = self.network.address_hrp().map_err(|_| fmt::Error)?;
let mut b32 = Vec::with_capacity(33);

b32.push(bech32::u5::try_from_u8(self.addr_type.clone() as u8).map_err(|_| fmt::Error)?);
b32.extend_from_slice(&self.pub_hash.to_vec().to_base32());
bech32::encode_to_fmt(f, ADDRESS_HRP, &b32, bech32::Variant::Bech32m)
.map_err(|_| fmt::Error)?
bech32::encode_to_fmt(f, hrp, &b32, bech32::Variant::Bech32m).map_err(|_| fmt::Error)?
}
}

Expand Down Expand Up @@ -146,13 +149,15 @@ impl Decodable for Address {
let addr_type = AddressType::decode(r)?;
if addr_type == AddressType::Treasury {
return Ok(Address {
network: Network::Unknown,
addr_type,
pub_hash: H160::new(),
});
}

let pub_hash = H160::decode(r)?;
Ok(Address {
network: Network::Unknown,
addr_type,
pub_hash,
})
Expand All @@ -165,16 +170,14 @@ impl FromStr for Address {
fn from_str(s: &str) -> Result<Self, AddressError> {
if s == TREASURY_ADDRESS_STRING {
return Ok(Address {
network: Network::Unknown,
addr_type: AddressType::Treasury,
pub_hash: H160::new(),
});
}

let (hrp, b32, _variant) = bech32::decode(s).map_err(|_| AddressError::FromBech32Error)?;

if hrp != ADDRESS_HRP {
return Err(AddressError::InvalidHrp);
}
let network = Network::try_from_hrp(&hrp)?;

if b32.len() != 33 {
return Err(AddressError::InvalidInput);
Expand All @@ -185,6 +188,7 @@ impl FromStr for Address {
let pub_hash = Address::vec_to_pub_hash(b8)?;

Ok(Address {
network,
addr_type,
pub_hash,
})
Expand Down Expand Up @@ -241,12 +245,20 @@ mod test {
.decode_hex()
.unwrap();

let addr = deserialize::<Address>(&data).unwrap();
let mut addr = deserialize::<Address>(&data).unwrap();
assert!(!addr.is_treasury());

addr.network = Network::Mainnet;
assert_eq!(
addr.to_string(),
"pc1rqqqsyqcyq5rqwzqfpg9scrgwpuqqzqsr36kkra"
);

addr.network = Network::Testnet;
assert_eq!(
addr.to_string(),
"tpc1rqqqsyqcyq5rqwzqfpg9scrgwpuqqzqsrtuyllk"
);
}

#[test]
Expand Down Expand Up @@ -289,6 +301,7 @@ mod test {
for case in test_cases {
let pub_hash_data = case.pub_hash.decode_hex().unwrap();
let addr = Address {
network: Network::Mainnet,
addr_type: case.addr_type,
pub_hash: Address::vec_to_pub_hash(pub_hash_data).unwrap(),
};
Expand All @@ -307,7 +320,7 @@ mod test {
"afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5",
)
.unwrap();
let address = Address::from_public_key(&private_key.public()).unwrap();
let address = Address::from_public_key(&private_key.public(), Network::Mainnet).unwrap();
let mut w = Vec::new();

address.encode(&mut w).unwrap();
Expand All @@ -323,13 +336,16 @@ mod test {
.unwrap();
let private_key = PrivateKey::try_from(private_key_data.as_slice()).unwrap();
let public_key = private_key.public();
let address = Address::from_public_key(&public_key).unwrap();
let mainnet_address = Address::from_public_key(&public_key, Network::Mainnet).unwrap();
let testnet_address = Address::from_public_key(&public_key, Network::Testnet).unwrap();

let expected_public_key =
"95794161374b22c696dabb98e93f6ca9300b22f3b904921fbf560bb72145f4fa";
let expected_address = "pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr";
let expected_mainnet_address = "pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr";
let expected_testnet_address = "tpc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymzqkcrg";

assert_eq!(public_key.to_bytes().to_hex(), expected_public_key);
assert_eq!(address.to_string(), expected_address);
assert_eq!(mainnet_address.to_string(), expected_mainnet_address);
assert_eq!(testnet_address.to_string(), expected_testnet_address);
}
}
1 change: 1 addition & 0 deletions rust/chains/tw_pactus/src/types/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod address;
pub mod amount;
pub mod network;
pub mod validator_public_key;

pub use address::Address;
Expand Down
Loading
Loading