diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestCoinType.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestCoinType.kt index abbc87b7c2f..98f46092a88 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestCoinType.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestCoinType.kt @@ -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() @@ -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") + } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/pactus/TestPactusAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/pactus/TestPactusAddress.kt index b58b068b859..96e7217fc70 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/pactus/TestPactusAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/pactus/TestPactusAddress.kt @@ -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) @@ -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()) + } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestHDWallet.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestHDWallet.kt index 9d165d95142..b4f3d147fb6 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestHDWallet.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestHDWallet.kt @@ -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) @@ -109,7 +109,7 @@ class TestHDWallet { } @Test - fun testGetKeyDerivation() { + fun testGetKeyDerivationBitcoin() { val coin = CoinType.BITCOIN val wallet = HDWallet(words, password) @@ -127,7 +127,7 @@ class TestHDWallet { } @Test - fun testGetAddressForCoin() { + fun testGetAddressForCoinBitcoin() { val coin = CoinType.BITCOIN val wallet = HDWallet(words, password) @@ -136,7 +136,7 @@ class TestHDWallet { } @Test - fun testGetAddressDerivation() { + fun testGetAddressDerivationBitcoin() { val coin = CoinType.BITCOIN val wallet = HDWallet(words, password) @@ -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) diff --git a/codegen-v2/manifest/TWDerivation.yaml b/codegen-v2/manifest/TWDerivation.yaml index c2143b80431..b8d1cb6f0b9 100644 --- a/codegen-v2/manifest/TWDerivation.yaml +++ b/codegen-v2/manifest/TWDerivation.yaml @@ -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 diff --git a/include/TrustWalletCore/TWDerivation.h b/include/TrustWalletCore/TWDerivation.h index 3437313e934..263f2710f96 100644 --- a/include/TrustWalletCore/TWDerivation.h +++ b/include/TrustWalletCore/TWDerivation.h @@ -25,6 +25,8 @@ enum TWDerivation { TWDerivationSolanaSolana = 6, TWDerivationStratisSegwit = 7, TWDerivationBitcoinTaproot = 8, + TWDerivationPactusMainnet = 9, + TWDerivationPactusTestnet = 10, // end_of_derivation_enum - USED TO GENERATE CODE }; diff --git a/registry.json b/registry.json index e53e559ab61..faca84e65ad 100644 --- a/registry.json +++ b/registry.json @@ -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", diff --git a/rust/chains/tw_pactus/src/entry.rs b/rust/chains/tw_pactus/src/entry.rs index e9d12f3ac8c..1e0344221a7 100644 --- a/rust/chains/tw_pactus/src/entry.rs +++ b/rust/chains/tw_pactus/src/entry.rs @@ -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; @@ -60,13 +61,18 @@ impl CoinEntry for PactusEntry { &self, _coin: &dyn CoinContext, public_key: PublicKey, - _derivation: Derivation, + derivation: Derivation, _prefix: Option, ) -> AddressResult { 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] diff --git a/rust/chains/tw_pactus/src/types/address.rs b/rust/chains/tw_pactus/src/types/address.rs index 785e82c6639..aee9b1a6b7e 100644 --- a/rust/chains/tw_pactus/src/types/address.rs +++ b/rust/chains/tw_pactus/src/types/address.rs @@ -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. @@ -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 { + pub fn from_public_key(public_key: &PublicKey, network: Network) -> Result { 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, }) @@ -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)? } } @@ -146,6 +149,7 @@ 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(), }); @@ -153,6 +157,7 @@ impl Decodable for Address { let pub_hash = H160::decode(r)?; Ok(Address { + network: Network::Unknown, addr_type, pub_hash, }) @@ -165,16 +170,14 @@ impl FromStr for Address { fn from_str(s: &str) -> Result { 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); @@ -185,6 +188,7 @@ impl FromStr for Address { let pub_hash = Address::vec_to_pub_hash(b8)?; Ok(Address { + network, addr_type, pub_hash, }) @@ -241,12 +245,20 @@ mod test { .decode_hex() .unwrap(); - let addr = deserialize::
(&data).unwrap(); + let mut addr = deserialize::
(&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] @@ -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(), }; @@ -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(); @@ -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); } } diff --git a/rust/chains/tw_pactus/src/types/mod.rs b/rust/chains/tw_pactus/src/types/mod.rs index ff66b91c4f4..d7ea9b78918 100644 --- a/rust/chains/tw_pactus/src/types/mod.rs +++ b/rust/chains/tw_pactus/src/types/mod.rs @@ -1,5 +1,6 @@ pub mod address; pub mod amount; +pub mod network; pub mod validator_public_key; pub use address::Address; diff --git a/rust/chains/tw_pactus/src/types/network.rs b/rust/chains/tw_pactus/src/types/network.rs new file mode 100644 index 00000000000..230d531efe7 --- /dev/null +++ b/rust/chains/tw_pactus/src/types/network.rs @@ -0,0 +1,64 @@ +use tw_coin_entry::error::prelude::*; + +/// Represents the type of network (e.g., Mainnet or Testnet). +/// +/// The `CoinType` for Mainnet is defined as `21888`, and for Testnet, it is `21777`. +/// +/// The network type does not affect the decoding or encoding of addresses or transactions. +/// Instead, it is primarily used to facilitate the conversion of an address or public key +/// into its string representation (using bech32m). +/// +/// Note: TrustWallet Core does not provide an API for converting a public key directly +/// to its string representation; it only converts it to a hex representation. +/// However, it provides the API to convert an address to its string representation. +#[derive(Debug, Clone, PartialEq)] +pub enum Network { + /// The network type is either unknown or not explicitly set. + /// + /// Address raw bytes do not inherently carry the network type, + /// but the address string carries the network using an HRP (Human-Readable Part). + /// - Mainnet addresses start with `pc1...`. + /// - Testnet addresses start with `tpc1...`. + /// + /// When deriving an address from a string, the network type is inferred from the HRP. + /// When decoding an address from a public key or raw data, the network type must be explicitly set. + Unknown = 0, + + /// Represents the Mainnet network. + Mainnet = 1, + + /// Represents the Testnet network. + Testnet = 2, +} + +const MAINNET_ADDRESS_HRP: &str = "pc"; +const TESTNET_ADDRESS_HRP: &str = "tpc"; + +pub const MAINNET_PUBLIC_KEY_HRP: &str = "public"; +pub const TESTNET_PUBLIC_KEY_HRP: &str = "tpublic"; + +impl Network { + pub fn try_from_hrp(hrp: &str) -> Result { + match hrp { + MAINNET_ADDRESS_HRP => Ok(Network::Mainnet), + TESTNET_ADDRESS_HRP => Ok(Network::Testnet), + _ => Err(AddressError::InvalidHrp), + } + } + + pub fn address_hrp(&self) -> Result<&'static str, AddressError> { + match &self { + Network::Mainnet => Ok(MAINNET_ADDRESS_HRP), + Network::Testnet => Ok(TESTNET_ADDRESS_HRP), + Network::Unknown => Err(AddressError::InvalidHrp), + } + } + + pub fn public_key_hrp(&self) -> Result<&'static str, AddressError> { + match &self { + Network::Mainnet => Ok(MAINNET_PUBLIC_KEY_HRP), + Network::Testnet => Ok(TESTNET_PUBLIC_KEY_HRP), + Network::Unknown => Err(AddressError::InvalidHrp), + } + } +} diff --git a/rust/chains/tw_pactus/src/types/validator_public_key.rs b/rust/chains/tw_pactus/src/types/validator_public_key.rs index 84425774143..5818d72d56d 100644 --- a/rust/chains/tw_pactus/src/types/validator_public_key.rs +++ b/rust/chains/tw_pactus/src/types/validator_public_key.rs @@ -5,8 +5,9 @@ use bech32::FromBase32; use std::str::FromStr; use tw_keypair::KeyPairError; +use super::network::{MAINNET_PUBLIC_KEY_HRP, TESTNET_PUBLIC_KEY_HRP}; + pub const BLS_PUBLIC_KEY_SIZE: usize = 96; -pub const PUBLIC_KEY_HRP: &str = "public"; #[derive(Debug)] pub struct ValidatorPublicKey(pub [u8; BLS_PUBLIC_KEY_SIZE]); @@ -34,7 +35,7 @@ impl FromStr for ValidatorPublicKey { fn from_str(s: &str) -> Result { let (hrp, b32, _variant) = bech32::decode(s).map_err(|_| KeyPairError::InvalidPublicKey)?; - if hrp != PUBLIC_KEY_HRP { + if hrp != MAINNET_PUBLIC_KEY_HRP && hrp != TESTNET_PUBLIC_KEY_HRP { return Err(KeyPairError::InvalidPublicKey); } @@ -83,6 +84,11 @@ mod test { pub_key_str: "public1p4u8hfytl2pj6l9rj0t54gxcdmna4hq52ncqkkqjf3arha5mlk3x4mzpyjkhmdl20jae7f65aamjrvqcvf4sudcapz52ctcwc8r9wz3z2gwxs38880cgvfy49ta5ssyjut05myd4zgmjqstggmetyuyg7v5jhx47a", pub_key_data: "af0f74917f5065af94727ae9541b0ddcfb5b828a9e016b02498f477ed37fb44d5d882495afb6fd4f9773e4ea9deee436030c4d61c6e3a1151585e1d838cae1444a438d089ce77e10c492a55f6908125c5be9b236a246e4082d08de564e111e65", }, + TestCase { + name: "OK", + pub_key_str: "tpublic1p4u8hfytl2pj6l9rj0t54gxcdmna4hq52ncqkkqjf3arha5mlk3x4mzpyjkhmdl20jae7f65aamjrvqcvf4sudcapz52ctcwc8r9wz3z2gwxs38880cgvfy49ta5ssyjut05myd4zgmjqstggmetyuyg7v5fmv7tx", + pub_key_data: "af0f74917f5065af94727ae9541b0ddcfb5b828a9e016b02498f477ed37fb44d5d882495afb6fd4f9773e4ea9deee436030c4d61c6e3a1151585e1d838cae1444a438d089ce77e10c492a55f6908125c5be9b236a246e4082d08de564e111e65", + }, ]; for case in test_cases { diff --git a/rust/tw_coin_registry/src/tw_derivation.rs b/rust/tw_coin_registry/src/tw_derivation.rs index 67436f91371..21307edfbc0 100644 --- a/rust/tw_coin_registry/src/tw_derivation.rs +++ b/rust/tw_coin_registry/src/tw_derivation.rs @@ -18,6 +18,8 @@ pub enum TWDerivation { SolanaSolana = 6, StratisSegwit = 7, BitcoinTaproot = 8, + PactusMainnet = 9, + PactusTestnet = 10, // end_of_derivation_enum - USED TO GENERATE CODE #[default] Default = 0, @@ -32,6 +34,8 @@ impl From for Derivation { TWDerivation::BitcoinTestnet => Derivation::Testnet, TWDerivation::SolanaSolana => Derivation::Default, TWDerivation::BitcoinTaproot => Derivation::Taproot, + TWDerivation::PactusMainnet => Derivation::Default, + TWDerivation::PactusTestnet => Derivation::Testnet, } } } diff --git a/swift/Tests/CoinTypeTests.swift b/swift/Tests/CoinTypeTests.swift index 46812077e59..9a6120fd503 100644 --- a/swift/Tests/CoinTypeTests.swift +++ b/swift/Tests/CoinTypeTests.swift @@ -27,20 +27,34 @@ class CoinTypeTests: XCTestCase { XCTAssertEqual(CoinType.nebulas.rawValue, 2718) XCTAssertEqual(CoinType.avalancheCChain.rawValue, 10009000) XCTAssertEqual(CoinType.xdai.rawValue, 10000100) + XCTAssertEqual(CoinType.pactus.rawValue, 21888) } - + func testCoinDerivation() { XCTAssertEqual(CoinType.bitcoin.derivationPath(), "m/84'/0'/0'/0/0") XCTAssertEqual(CoinType.bitcoin.derivationPathWithDerivation(derivation: Derivation.bitcoinLegacy), "m/44'/0'/0'/0/0") XCTAssertEqual(CoinType.bitcoin.derivationPathWithDerivation(derivation: Derivation.bitcoinTaproot), "m/86'/0'/0'/0/0") XCTAssertEqual(CoinType.solana.derivationPathWithDerivation(derivation: Derivation.solanaSolana), "m/44'/501'/0'/0'") + XCTAssertEqual(CoinType.pactus.derivationPathWithDerivation(derivation: Derivation.pactusMainnet), "m/44'/21888'/3'/0'") + XCTAssertEqual(CoinType.pactus.derivationPathWithDerivation(derivation: Derivation.pactusTestnet), "m/44'/21777'/3'/0'") } - func testDeriveAddressFromPublicKeyAndDerivation() { + func testDeriveAddressFromPublicKeyAndDerivationBitcoin() { let pkData = Data(hexString: "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798")! let publicKey = PublicKey(data: pkData, type: .secp256k1)! let address = CoinType.bitcoin.deriveAddressFromPublicKeyAndDerivation(publicKey: publicKey, derivation: Derivation.bitcoinSegwit) XCTAssertEqual(address, "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4") } + + func testDeriveAddressFromPublicKeyAndDerivationPactus() { + let pkData = Data(hexString: "95794161374b22c696dabb98e93f6ca9300b22f3b904921fbf560bb72145f4fa")! + let publicKey = PublicKey(data: pkData, type: .ed25519)! + + let mainnet_address = CoinType.pactus.deriveAddressFromPublicKeyAndDerivation(publicKey: publicKey, derivation: Derivation.default) + XCTAssertEqual(mainnet_address, "pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr") + + let testnet_address = CoinType.pactus.deriveAddressFromPublicKeyAndDerivation(publicKey: publicKey, derivation: Derivation.pactusTestnet) + XCTAssertEqual(testnet_address, "tpc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymzqkcrg") + } } diff --git a/swift/Tests/HDWalletTests.swift b/swift/Tests/HDWalletTests.swift index c36a17c62e3..c3e885a36bc 100644 --- a/swift/Tests/HDWalletTests.swift +++ b/swift/Tests/HDWalletTests.swift @@ -10,19 +10,19 @@ extension HDWallet { } class HDWalletTests: XCTestCase { - + func testFromMnemonicImmutableXMainnetFromSignature() { let wallet = HDWallet(mnemonic: "obscure opera favorite shuffle mail tip age debate dirt pact cement loyal", passphrase: "")! let starkDerivationPath = Ethereum.eip2645GetPath(ethAddress: "0xd0972E2312518Ca15A2304D56ff9cc0b7ea0Ea37", layer: "starkex", application: "immutablex", index: "1") XCTAssertEqual(starkDerivationPath, "m/2645'/579218131'/211006541'/2124474935'/1609799702'/1") - + // Retrieve eth private key let ethPrivateKey = wallet.getKeyForCoin(coin: CoinType.ethereum) XCTAssertEqual(ethPrivateKey.data.hexString, "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d"); - + // StarkKey Derivation Path let derivationPath = DerivationPath(string: starkDerivationPath)! - + // Retrieve Stark Private key part let ethMsg = "Only sign this request if you’ve initiated an action with Immutable X." let ethSignature = EthereumMessageSigner.signMessageImmutableX(privateKey: ethPrivateKey, message: ethMsg) @@ -31,7 +31,7 @@ class HDWalletTests: XCTestCase { XCTAssertEqual(starkPrivateKey.data.hexString, "04be51a04e718c202e4dca60c2b72958252024cfc1070c090dd0f170298249de") let starkPublicKey = starkPrivateKey.getPublicKeyByType(pubkeyType: .starkex) XCTAssertEqual(starkPublicKey.data.hexString, "00e5b9b11f8372610ef35d647a1dcaba1a4010716588d591189b27bf3c2d5095") - + // Account Register let ethMsgToRegister = "Only sign this key linking request from Immutable X" let ethSignatureToRegister = EthereumMessageSigner.signMessageImmutableX(privateKey: ethPrivateKey, message: ethMsgToRegister) @@ -41,7 +41,7 @@ class HDWalletTests: XCTestCase { XCTAssertEqual(starkSignature, "04cf5f21333dd189ada3c0f2a51430d733501a9b1d5e07905273c1938cfb261e05b6013d74adde403e8953743a338c8d414bb96bf69d2ca1a91a85ed2700a528") XCTAssertTrue(StarkExMessageSigner.verifyMessage(pubKey: starkPublicKey, message: starkMsg, signature: starkSignature)) } - + func testCreateFromMnemonic() { let wallet = HDWallet.test @@ -79,7 +79,7 @@ class HDWalletTests: XCTestCase { XCTAssertEqual(masterKey.data.hexString, "e120fc1ef9d193a851926ebd937c3985dc2c4e642fb3d0832317884d5f18f3b3") } - func testGetKeyForCoin() { + func testGetKeyForCoinBitcoin() { let coin = CoinType.bitcoin let wallet = HDWallet.test let key = wallet.getKeyForCoin(coin: coin) @@ -88,7 +88,7 @@ class HDWalletTests: XCTestCase { XCTAssertEqual(address, "bc1qumwjg8danv2vm29lp5swdux4r60ezptzz7ce85") } - func testGetKeyDerivation() { + func testGetKeyDerivationBitcoin() { let coin = CoinType.bitcoin let wallet = HDWallet.test @@ -100,12 +100,12 @@ class HDWalletTests: XCTestCase { let key3 = wallet.getKeyDerivation(coin: coin, derivation: .bitcoinTestnet) XCTAssertEqual(key3.data.hexString, "ca5845e1b43e3adf577b7f110b60596479425695005a594c88f9901c3afe864f") - + let key4 = wallet.getKeyDerivation(coin: coin, derivation: .bitcoinTaproot) XCTAssertEqual(key4.data.hexString, "a2c4d6df786f118f20330affd65d248ffdc0750ae9cbc729d27c640302afd030") } - func testGetAddressForCoin() { + func testGetAddressForCoinBitcoin() { let coin = CoinType.bitcoin let wallet = HDWallet.test @@ -113,7 +113,7 @@ class HDWalletTests: XCTestCase { XCTAssertEqual(address, "bc1qumwjg8danv2vm29lp5swdux4r60ezptzz7ce85") } - func testGetAddressDerivation() { + func testGetAddressDerivationBitcoin() { let coin = CoinType.bitcoin let wallet = HDWallet.test @@ -125,11 +125,41 @@ class HDWalletTests: XCTestCase { let address3 = wallet.getAddressDerivation(coin: coin, derivation: .bitcoinTestnet) XCTAssertEqual(address3, "tb1qwgpxgwn33z3ke9s7q65l976pseh4edrzfmyvl0") - + let address4 = wallet.getAddressDerivation(coin: coin, derivation: .bitcoinTaproot) XCTAssertEqual(address4, "bc1pgqks0cynn93ymve4x0jq3u7hne77908nlysp289hc44yc4cmy0hslyckrz") } + func testGetKeyDerivationPactus() { + let coin = CoinType.pactus + let wallet = HDWallet.test + + let key1 = wallet.getKeyDerivation(coin: coin, derivation: .pactusMainnet) + XCTAssertEqual(key1.data.hexString, "153fefb8168f246f9f77c60ea10765c1c39828329e87284ddd316770717f3a5e") + + let key2 = wallet.getKeyDerivation(coin: coin, derivation: .pactusTestnet) + XCTAssertEqual(key2.data.hexString, "54f3c54dd6af5794bea1f86de05b8b9f164215e8deee896f604919046399e54d") + } + + func testGetAddressForCoinPactus() { + let coin = CoinType.pactus + let wallet = HDWallet.test + + let address = wallet.getAddressForCoin(coin: coin) + XCTAssertEqual(address, "pc1rjkzc23l7qkkenx6xwy04srwppzfk6m5t7q46ff") + } + + func testGetAddressDerivationPactus() { + let coin = CoinType.pactus + let wallet = HDWallet.test + + let address1 = wallet.getAddressDerivation(coin: coin, derivation: .pactusMainnet) + XCTAssertEqual(address1, "pc1rjkzc23l7qkkenx6xwy04srwppzfk6m5t7q46ff") + + let address2 = wallet.getAddressDerivation(coin: coin, derivation: .pactusTestnet) + XCTAssertEqual(address2, "tpc1rjtamyqp203j4367q4plkp4qt32d7sv34kfmj5e") + } + func testDerive() { let wallet = HDWallet.test diff --git a/swift/Tests/Keystore/KeyStoreTests.swift b/swift/Tests/Keystore/KeyStoreTests.swift index 99b2815b825..0b823afddf5 100755 --- a/swift/Tests/Keystore/KeyStoreTests.swift +++ b/swift/Tests/Keystore/KeyStoreTests.swift @@ -183,7 +183,7 @@ class KeyStoreTests: XCTestCase { XCTAssertNotNil(storedData) XCTAssertNotNil(PrivateKey(data: storedData!)) } - + func testImportPrivateKeyAES256() throws { let keyStore = try KeyStore(keyDirectory: keyDirectory) let privateKeyData = Data(hexString: "9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c")! @@ -222,7 +222,7 @@ class KeyStoreTests: XCTestCase { XCTAssertEqual(wallet.accounts.count, 1) XCTAssertNotNil(keyStore.hdWallet) } - + func testImportWalletAES256() throws { let keyStore = try KeyStore(keyDirectory: keyDirectory) let wallet = try keyStore.import(mnemonic: mnemonic, name: "name", encryptPassword: "newPassword", coins: [.ethereum], encryption: .aes256Ctr) @@ -437,7 +437,7 @@ class KeyStoreTests: XCTestCase { let btc2 = try wallet.getAccount(password: password, coin: .bitcoin, derivation: .bitcoinLegacy) XCTAssertEqual(btc2.address, "1NyRyFewhZcWMa9XCj3bBxSXPXyoSg8dKz") XCTAssertEqual(btc2.extendedPublicKey, "xpub6CR52eaUuVb4kXAVyHC2i5ZuqJ37oWNPZFtjXaazFPXZD45DwWBYEBLdrF7fmCR9pgBuCA9Q57zZfyJjDUBDNtWkhWuGHNYKLgDHpqrHsxV") - + let btc3 = try wallet.getAccount(password: password, coin: .bitcoin, derivation: .bitcoinTaproot) XCTAssertEqual(btc3.address, "bc1pyqkqf20fmmwmcxf98tv6k63e2sgnjy4zne6d0r32vxwm3au0hnksq6ec57") XCTAssertEqual(btc3.extendedPublicKey, "zpub6qNRYbLLXquaD1GKxHZWDs3moUFQfP4iqXiDPCd8aD3oNHZkCAusAw5raKQEWV8BkXBTXhWBkgZTxzjjnQ5cRjWa6LNcjmrVVNdUKvbKTgm") @@ -449,6 +449,14 @@ class KeyStoreTests: XCTestCase { let solana2 = try wallet.getAccount(password: password, coin: .solana, derivation: .solanaSolana) XCTAssertEqual(solana2.address, "CgWJeEWkiYqosy1ba7a3wn9HAQuHyK48xs3LM4SSDc1C") XCTAssertEqual(solana2.derivationPath, "m/44'/501'/0'/0'") + + let pactus_mainnet = try wallet.getAccount(password: password, coin: .pactus, derivation: .pactusMainnet) + XCTAssertEqual(pactus_mainnet.address, "pc1rzuswvfwde5hleqfemvpz4swlh6uud6nkukumdu") + XCTAssertEqual(pactus_mainnet.derivationPath, "m/44'/21888'/3'/0'") + + let pactus_testnet = try wallet.getAccount(password: password, coin: .pactus, derivation: .pactusTestnet) + XCTAssertEqual(pactus_testnet.address, "tpc1rxs9tperv58gvfwpn0vj5na7vrcffml40j2v6r9") + XCTAssertEqual(pactus_testnet.derivationPath, "m/44'/21777'/3'/0'") } func createTempDirURL() throws -> URL { diff --git a/tests/chains/Pactus/WalletTests.cpp b/tests/chains/Pactus/WalletTests.cpp index bcd3076f398..0a2f553b8e6 100644 --- a/tests/chains/Pactus/WalletTests.cpp +++ b/tests/chains/Pactus/WalletTests.cpp @@ -18,7 +18,7 @@ TEST(PactusWallet, DerivationPath) { assertStringsEqual(WRAPS(derivationPath), "m/44'/21888'/3'/0'"); } -TEST(PactusWallet, HDWallet) { +TEST(PactusWallet, HDWallet_MainnetDerivation) { auto mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon cactus"; auto passphrase = ""; auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(STRING(mnemonic).get(), STRING(passphrase).get())); @@ -41,4 +41,27 @@ TEST(PactusWallet, HDWallet) { TWStringDelete(derivationPath2); } +TEST(PactusWallet, HDWallet_TestnetDerivation) { + auto mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon cactus"; + auto passphrase = ""; + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(STRING(mnemonic).get(), STRING(passphrase).get())); + + auto derivationPath1 = TWStringCreateWithUTF8Bytes("m/44'/21777'/3'/0'"); + auto privateKey1 = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), TWCoinTypePactus, derivationPath1)); + auto publicKey1 = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519(privateKey1.get())); + auto address1 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKeyDerivation(publicKey1.get(), TWCoinTypePactus, TWDerivationPactusTestnet)); + auto addressStr1 = WRAPS(TWAnyAddressDescription(address1.get())); + + auto derivationPath2 = TWStringCreateWithUTF8Bytes("m/44'/21777'/3'/1'"); + auto privateKey2 = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), TWCoinTypePactus, derivationPath2)); + auto publicKey2 = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519(privateKey2.get())); + auto address2 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKeyDerivation(publicKey2.get(), TWCoinTypePactus, TWDerivationPactusTestnet)); + auto addressStr2 = WRAPS(TWAnyAddressDescription(address2.get())); + + assertStringsEqual(addressStr1, "tpc1r35xwz99uw2qrhz9wmdanaqcsge2nzsfegvv555"); + assertStringsEqual(addressStr2, "tpc1r34xj32k004j8v35fx6uqw4yaka54g6jdr58tvk"); + TWStringDelete(derivationPath1); + TWStringDelete(derivationPath2); +} + } \ No newline at end of file diff --git a/tests/common/Keystore/DerivationPathTests.cpp b/tests/common/Keystore/DerivationPathTests.cpp index a38fc23903a..3bdb3734f4d 100644 --- a/tests/common/Keystore/DerivationPathTests.cpp +++ b/tests/common/Keystore/DerivationPathTests.cpp @@ -92,6 +92,12 @@ TEST(Derivation, alternativeDerivation) { EXPECT_EQ(TW::derivationPath(TWCoinTypeSolana, TWDerivationDefault).string(), "m/44'/501'/0'"); EXPECT_EQ(TW::derivationPath(TWCoinTypeSolana, TWDerivationSolanaSolana).string(), "m/44'/501'/0'/0'"); EXPECT_EQ(std::string(TW::derivationName(TWCoinTypeSolana, TWDerivationSolanaSolana)), "solana"); + + EXPECT_EQ(TW::derivationPath(TWCoinTypePactus).string(), "m/44'/21888'/3'/0'"); + EXPECT_EQ(TW::derivationPath(TWCoinTypePactus, TWDerivationPactusMainnet).string(), "m/44'/21888'/3'/0'"); + EXPECT_EQ(TW::derivationPath(TWCoinTypePactus, TWDerivationPactusTestnet).string(), "m/44'/21777'/3'/0'"); + EXPECT_EQ(std::string(TW::derivationName(TWCoinTypePactus, TWDerivationPactusMainnet)), "mainnet"); + EXPECT_EQ(std::string(TW::derivationName(TWCoinTypePactus, TWDerivationPactusTestnet)), "testnet"); } } // namespace TW diff --git a/tests/common/Keystore/StoredKeyTests.cpp b/tests/common/Keystore/StoredKeyTests.cpp index 02c9bed047b..12c7fc7a740 100644 --- a/tests/common/Keystore/StoredKeyTests.cpp +++ b/tests/common/Keystore/StoredKeyTests.cpp @@ -159,25 +159,25 @@ TEST(StoredKey, AccountGetCreate) { // not exists, wallet nonnull, create std::optional acc3 = key.account(coinTypeBc, &wallet); EXPECT_TRUE(acc3.has_value()); - EXPECT_EQ(acc3->coin, coinTypeBc); + EXPECT_EQ(acc3->coin, coinTypeBc); EXPECT_EQ(key.accounts.size(), 1ul); // exists std::optional acc4 = key.account(coinTypeBc); EXPECT_TRUE(acc4.has_value()); - EXPECT_EQ(acc4->coin, coinTypeBc); + EXPECT_EQ(acc4->coin, coinTypeBc); EXPECT_EQ(key.accounts.size(), 1ul); // exists, wallet nonnull, not create std::optional acc5 = key.account(coinTypeBc, &wallet); EXPECT_TRUE(acc5.has_value()); - EXPECT_EQ(acc5->coin, coinTypeBc); + EXPECT_EQ(acc5->coin, coinTypeBc); EXPECT_EQ(key.accounts.size(), 1ul); // exists, wallet null, not create std::optional acc6 = key.account(coinTypeBc, nullptr); EXPECT_TRUE(acc6.has_value()); - EXPECT_EQ(acc6->coin, coinTypeBc); + EXPECT_EQ(acc6->coin, coinTypeBc); EXPECT_EQ(key.accounts.size(), 1ul); } @@ -435,7 +435,7 @@ TEST(StoredKey, CreateAccounts) { string mnemonicPhrase = "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; auto key = StoredKey::createWithMnemonic("name", gPassword, mnemonicPhrase, TWStoredKeyEncryptionLevelDefault); const auto wallet = key.wallet(gPassword); - + EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->address, "0x494f60cb6Ac2c8F5E1393aD9FdBdF4Ad589507F7"); EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->publicKey, "04cc32a479080d83fdcf69966713f0aad1bc1dc3ecf873b034894e84259841bc1c9b122717803e68905220ff54952d3f5ea2ab2698ca31f843addf94ae73fae9fd"); EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->extendedPublicKey, ""); @@ -596,7 +596,7 @@ TEST(StoredKey, CreateEncryptionParametersRandomSalt) { EXPECT_NE(salt1, salt2) << "salt must be random on every StoredKey creation"; } -TEST(StoredKey, CreateMultiAccounts) { // Multiple accounts for the same coin +TEST(StoredKey, CreateMultiAccounts) { // Multiple accounts from the same wallet auto key = StoredKey::createWithMnemonic("name", gPassword, gMnemonic, TWStoredKeyEncryptionLevelDefault); EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); const Data& mnemo2Data = key.payload.decrypt(gPassword); @@ -696,6 +696,31 @@ TEST(StoredKey, CreateMultiAccounts) { // Multiple accounts for the same coin EXPECT_EQ(key.getAccounts(coin)[2].address, expectedBtc3); EXPECT_EQ(key.getAccounts(coin)[2].derivationPath.string(), "m/44'/2'/0'/0/0"); } + + { // Create Pactus Accounts + const auto coin = TWCoinTypePactus; + + const auto pactusMainnet = key.account(coin, TWDerivationPactusMainnet, wallet); + const auto pactusTestnet = key.account(coin, TWDerivationPactusTestnet, wallet); + + const auto expectedMainnetAddr = "pc1rzuswvfwde5hleqfemvpz4swlh6uud6nkukumdu"; + const auto expectedTestnetAddr = "tpc1rxs9tperv58gvfwpn0vj5na7vrcffml40j2v6r9"; + + EXPECT_EQ(pactusMainnet.address, expectedMainnetAddr); + EXPECT_EQ(pactusTestnet.address, expectedTestnetAddr); + + EXPECT_EQ(pactusMainnet.derivationPath.string(), "m/44'/21888'/3'/0'"); + EXPECT_EQ(pactusTestnet.derivationPath.string(), "m/44'/21777'/3'/0'"); + + expectedAccounts += 2; + EXPECT_EQ(key.accounts.size(), expectedAccounts); + + EXPECT_EQ(key.account(coin)->address, expectedMainnetAddr); + EXPECT_EQ(key.account(coin, TWDerivationPactusMainnet, wallet).address, expectedMainnetAddr); + EXPECT_EQ(key.getAccounts(coin).size(), 2ul); + EXPECT_EQ(key.getAccounts(coin)[0].address, expectedMainnetAddr); + EXPECT_EQ(key.getAccounts(coin)[1].address, expectedTestnetAddr); + } } TEST(StoredKey, CreateWithMnemonicAlternativeDerivation) { diff --git a/tests/interface/TWAnyAddressTests.cpp b/tests/interface/TWAnyAddressTests.cpp index 1068efe40fb..212a1055c07 100644 --- a/tests/interface/TWAnyAddressTests.cpp +++ b/tests/interface/TWAnyAddressTests.cpp @@ -180,7 +180,7 @@ TEST(TWAnyAddress, createFromPubKey) { assertStringsEqual(WRAPS(TWAnyAddressDescription(addr.get())), "bc1qcj2vfjec3c3luf9fx9vddnglhh9gawmncmgxhz"); } -TEST(TWAnyAddress, createFromPubKeyDerivation) { +TEST(TWAnyAddress, createFromPubKeyDerivationBitcoin) { constexpr auto pubkey = "02753f5c275e1847ba4d2fd3df36ad00af2e165650b35fe3991e9c9c46f68b12bc"; const auto pubkey_twstring = STRING(pubkey); const auto pubkey_data = WRAPD(TWDataCreateWithHexString(pubkey_twstring.get())); @@ -200,6 +200,26 @@ TEST(TWAnyAddress, createFromPubKeyDerivation) { } } +TEST(TWAnyAddress, createFromPubKeyDerivationPactus) { + constexpr auto pubkey = "95794161374b22c696dabb98e93f6ca9300b22f3b904921fbf560bb72145f4fa"; + const auto pubkey_twstring = STRING(pubkey); + const auto pubkey_data = WRAPD(TWDataCreateWithHexString(pubkey_twstring.get())); + const auto pubkey_obj = WRAP(TWPublicKey, TWPublicKeyCreateWithData(pubkey_data.get(), TWPublicKeyTypeED25519)); + + { + const auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKeyDerivation(pubkey_obj.get(), TWCoinTypePactus, TWDerivationDefault)); + assertStringsEqual(WRAPS(TWAnyAddressDescription(addr.get())), "pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr"); + } + { + const auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKeyDerivation(pubkey_obj.get(), TWCoinTypePactus, TWDerivationPactusMainnet)); + assertStringsEqual(WRAPS(TWAnyAddressDescription(addr.get())), "pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr"); + } + { + const auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKeyDerivation(pubkey_obj.get(), TWCoinTypePactus, TWDerivationPactusTestnet)); + assertStringsEqual(WRAPS(TWAnyAddressDescription(addr.get())), "tpc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymzqkcrg"); + } +} + TEST(TWAnyAddress, createFromPubKeyFilecoinAddressType) { constexpr auto pubkey = "0419bf99082cf2fcdaa812d6eba1eba9036ff3a3d84c1817c84954d4e8ae283fec5313e427a0f5f68dec3169b2eda876b1d9f97b1ede7f958baee6a2ce78f6e94a"; const auto pubkey_twstring = STRING(pubkey); diff --git a/tests/interface/TWCoinTypeTests.cpp b/tests/interface/TWCoinTypeTests.cpp index b7547ae34a9..8d19ec12f86 100644 --- a/tests/interface/TWCoinTypeTests.cpp +++ b/tests/interface/TWCoinTypeTests.cpp @@ -174,3 +174,24 @@ TEST(TWCoinType, TWCoinTypeDerivationPathWithDerivationSolana) { ASSERT_EQ(result, "m/44'/501'/0'/0'"); TWStringDelete(res); } + +TEST(TWCoinType, TWCoinTypeDerivationPathPactus) { + auto res = TWCoinTypeDerivationPath(TWCoinTypePactus); + auto result = *reinterpret_cast(res); + ASSERT_EQ(result, "m/44'/21888'/3'/0'"); + TWStringDelete(res); +} + +TEST(TWCoinType, TWCoinTypeDerivationPathWithDerivationPactusMainnet) { + auto res = TWCoinTypeDerivationPathWithDerivation(TWCoinTypePactus, TWDerivationPactusMainnet); + auto result = *reinterpret_cast(res); + ASSERT_EQ(result, "m/44'/21888'/3'/0'"); + TWStringDelete(res); +} + +TEST(TWCoinType, TWCoinTypeDerivationPathWithDerivationPactusTestnet) { + auto res = TWCoinTypeDerivationPathWithDerivation(TWCoinTypePactus, TWDerivationPactusTestnet); + auto result = *reinterpret_cast(res); + ASSERT_EQ(result, "m/44'/21777'/3'/0'"); + TWStringDelete(res); +} diff --git a/wasm/tests/CoinType.test.ts b/wasm/tests/CoinType.test.ts index 75a0bed0408..84dc7da419d 100644 --- a/wasm/tests/CoinType.test.ts +++ b/wasm/tests/CoinType.test.ts @@ -16,6 +16,7 @@ describe("CoinType", () => { assert.equal(CoinType.binance.value, 714); assert.equal(CoinType.cosmos.value, 118); assert.equal(CoinType.solana.value, 501); + assert.equal(CoinType.pactus.value, 21888); }); it("test CoinTypeExt methods", () => { @@ -55,4 +56,25 @@ describe("CoinType", () => { const addr = CoinTypeExt.deriveAddressFromPublicKeyAndDerivation(CoinType.bitcoin, key, Derivation.bitcoinSegwit); assert.equal(addr, "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"); }); + + it("test CoinTypeExt methods for Pactus", () => { + const { CoinType, CoinTypeExt, Blockchain, Purpose, Curve, Derivation } = globalThis.core; + + assert.equal(CoinTypeExt.blockchain(CoinType.pactus), Blockchain.pactus); + assert.equal(CoinTypeExt.purpose(CoinType.pactus), Purpose.bip44); + assert.equal(CoinTypeExt.curve(CoinType.pactus), Curve.ed25519); + assert.isTrue(CoinTypeExt.validate(CoinType.pactus, "pc1rnvlc4wa73lc0rydmgfswz4j5wad4un376vv2d7")) + assert.equal(CoinTypeExt.derivationPath(CoinType.pactus), "m/44'/21888'/3'/0'"); + assert.equal(CoinTypeExt.derivationPathWithDerivation(CoinType.pactus, Derivation.pactusMainnet), "m/44'/21888'/3'/0'"); + assert.equal(CoinTypeExt.derivationPathWithDerivation(CoinType.pactus, Derivation.pactusTestnet), "m/44'/21777'/3'/0'"); + }); + + it("test deriveAddress for Pactus", () => { + const { CoinType, CoinTypeExt, PrivateKey, HexCoding } = globalThis.core; + + const data = HexCoding.decode("8778cc93c6596387e751d2dc693bbd93e434bd233bc5b68a826c56131821cb63"); + const key = PrivateKey.createWithData(data); + const addr = CoinTypeExt.deriveAddress(CoinType.pactus, key); + assert.equal(addr, "pc1rnvlc4wa73lc0rydmgfswz4j5wad4un376vv2d7") + }); });