diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt index f9779f246aa..7d616505844 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt @@ -34,7 +34,9 @@ class CoinAddressDerivationTests { private fun runDerivationChecks(coin: CoinType, address: String?) = when (coin) { BINANCE -> assertEquals("bnb12vtaxl9952zm6rwf7v8jerq74pvaf77fcmvzhw", address) + TBINANCE -> assertEquals("tbnb12vtaxl9952zm6rwf7v8jerq74pvaf77fkw9xhl", address) BITCOIN -> assertEquals("bc1quvuarfksewfeuevuc6tn0kfyptgjvwsvrprk9d", address) + BITCOINDIAMOND -> assertEquals("1KaRW9xPPtCTZ9FdaTHduCPck4YvSeEWNn", address) BITCOINCASH -> assertEquals("bitcoincash:qpzl3jxkzgvfd9flnd26leud5duv795fnv7vuaha70", address) BITCOINGOLD -> assertEquals("btg1qwz9sed0k4neu6ycrudzkca6cnqe3zweq35hvtg", address) CALLISTO -> assertEquals("0x3E6FFC80745E6669135a76F4A7ce6BCF02436e04", address) @@ -58,7 +60,9 @@ class CoinAddressDerivationTests { TRON -> assertEquals("TQ5NMqJjhpQGK7YJbESKtNCo86PJ89ujio", address) VECHAIN -> assertEquals("0x1a553275dF34195eAf23942CB7328AcF9d48c160", address) WANCHAIN -> assertEquals("0xD5ca90b928279FE5D06144136a25DeD90127aC15", address) + KOMODO -> assertEquals("RCWJLXE5CSXydxdSnwcghzPgkFswERegyb", address) ZCASH -> assertEquals("t1YYnByMzdGhQv3W3rnjHMrJs6HH4Y231gy", address) + ZEN -> assertEquals("znUmzvod1f4P9LYsBhNxjqCDQvNSStAmYEX", address) FIRO -> assertEquals("aEd5XFChyXobvEics2ppAqgK3Bgusjxtik", address) NIMIQ -> assertEquals("NQ76 7AVR EHDA N05U X7SY XB14 XJU7 8ERV GM6H", address) STELLAR -> assertEquals("GA3H6I4C5XUBYGVB66KXR27JV5KS3APSTKRUWOIXZ5MVWZKVTLXWKZ2P", address) @@ -72,6 +76,7 @@ class CoinAddressDerivationTests { DOGECOIN -> assertEquals("DJRFZNg8jkUtjcpo2zJd92FUAzwRjitw6f", address) KIN -> assertEquals("GBL3MT2ICHHM5OJ2QJ44CAHGDK6MLPINVXBKOKLHGBWQDVRWTWQ7U2EA", address) VIACOIN -> assertEquals("via1qnmsgjd6cvfprnszdgmyg9kewtjfgqflz67wwhc", address) + VERGE -> assertEquals("DPb3Xz4vjB6QGLKDmrbprrtv4XzNqkADc2", address) QTUM -> assertEquals("QhceuaTdeCZtcxmVc6yyEDEJ7Riu5gWFoF", address) NULS -> assertEquals("NULSd6HgU8MoRnNjBgvJpa9tqvGxYdv5ne4en", address) EOS -> assertEquals("EOS6hs8sRvGSzuQtq223zwJipMzqTJpXUVjyvHPvPwBSZWWrJTJkg", address) @@ -91,6 +96,7 @@ class CoinAddressDerivationTests { ALGORAND -> assertEquals("JTJWO524JXIHVPGBDWFLJE7XUIA32ECOZOBLF2QP3V5TQBT3NKZSCG67BQ", address) KUSAMA -> assertEquals("G9xV2EatmrjRC1FLPexc3ddqNRRzCsAdURU8RFiAAJX6ppY", address) POLKADOT -> assertEquals("13nN6BGAoJwd7Nw1XxeBCx5YcBXuYnL94Mh7i3xBprqVSsFk", address) + PIVX -> assertEquals("D81AqC8zKma3Cht4TbVuh4jyVVyLkZULCm", address) KAVA -> assertEquals("kava1drpa0x9ptz0fql3frv562rcrhj2nstuz3pas87", address) CARDANO -> assertEquals("addr1qyr8jjfnypp95eq74aqzn7ss687ehxclgj7mu6gratmg3mul2040vt35dypp042awzsjk5xm3zr3zm5qh7454uwdv08s84ray2", address) NEO -> assertEquals("AT6w7PJvwPcSqHvtbNBY2aHPDv12eW5Uuf", address) @@ -100,6 +106,9 @@ class CoinAddressDerivationTests { SMARTCHAINLEGACY -> assertEquals("0x49784f90176D8D9d4A3feCDE7C1373dAAb5b13b8", address) OASIS -> assertEquals("oasis1qzcpavvmuw280dk0kd4lxjhtpf0u3ll27yf7sqps", address) THORCHAIN -> assertEquals("thor1c8jd7ad9pcw4k3wkuqlkz4auv95mldr2kyhc65", address) + IOST -> assertEquals("4av8w81EyzUgHonsVWqfs15WM4Vrpgox4BYYQWhNQDVu", address) + SYSCOIN -> assertEquals("sys1qkl640se3mwpt666e3lyywnwh09e9jquvx9x8qj", address) + STRATIS -> assertEquals("strax1q0caanaw4nkf6fzwnzq2p7yum680e57pdg05zkm", address) BLUZELLE -> assertEquals("bluzelle1xccvees6ev4wm2r49rc6ptulsdxa8x8jfpmund", address) CRYPTOORG -> assertEquals("cro16fdf785ejm00jf9a24d23pzqzjh2h05klxjwu8", address) OSMOSIS -> assertEquals("osmo142j9u5eaduzd7faumygud6ruhdwme98qclefqp", address) @@ -109,6 +118,7 @@ class CoinAddressDerivationTests { EVERSCALE -> assertEquals("0:0c39661089f86ec5926ea7d4ee4223d634ba4ed6dcc2e80c7b6a8e6d59f79b04", address) TON -> assertEquals("EQDgEMqToTacHic7SnvnPFmvceG5auFkCcAw0mSCvzvKUfk9", address) APTOS -> assertEquals("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", address) + NEBL -> assertEquals("NgDVaXAwNgBwb88xLiFKomfBmPkEh9F2d7", address) SUI -> assertEquals("0xada112cfb90b44ba889cc5d39ac2bf46281e4a91f7919c693bcd9b8323e81ed2", address) HEDERA -> assertEquals("0.0.302a300506032b657003210049eba62f64d0d941045595d9433e65d84ecc46bcdb1421de55e05fcf2d8357d5", address) SECRET -> assertEquals("secret1f69sk5033zcdr2p2yf3xjehn7xvgdeq09d2llh", address) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoindiamond/TestBitcoinDiamondAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoindiamond/TestBitcoinDiamondAddress.kt new file mode 100644 index 00000000000..d8f768eeaec --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoindiamond/TestBitcoinDiamondAddress.kt @@ -0,0 +1,31 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +package com.trustwallet.core.app.blockchains.bitcoindiamond + +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 TestBitcoinDiamondAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee".toHexByteArray()) + val pubkey = key.getPublicKeySecp256k1(true); + val address = AnyAddress(pubkey, CoinType.BITCOINDIAMOND) + val expected = AnyAddress("1G15VvshDxwFTnahZZECJfFwEkq9fP79o8", CoinType.BITCOINDIAMOND) + + assertEquals(pubkey.data().toHex(), "0x02485a209514cc896f8ed736e205bc4c35bd5299ef3f9e84054475336b964c02a3") + assertEquals(address.description(), expected.description()) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoindiamond/TestBitcoinDiamondSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoindiamond/TestBitcoinDiamondSigner.kt new file mode 100644 index 00000000000..eea39e3a269 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoindiamond/TestBitcoinDiamondSigner.kt @@ -0,0 +1,88 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +package com.trustwallet.core.app.blockchains.bitcoindiamond + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHexByteArray +import com.trustwallet.core.app.utils.toHexBytes +import com.trustwallet.core.app.utils.toHexBytesInByteString +import org.junit.Assert.assertEquals +import org.junit.Test + +import wallet.core.java.AnySigner +import wallet.core.jni.BitcoinScript +import wallet.core.jni.BitcoinSigHashType +import wallet.core.jni.CoinType +import wallet.core.jni.proto.Bitcoin +import wallet.core.jni.proto.Common.SigningError + +class TestBitcoinDiamondSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun BitcoinDiamondTransactionSigning() { + val toScript = BitcoinScript.lockScriptForAddress("1HevQVTSEc8cEpDm65UpfCdj5erd4xxBhx", CoinType.BITCOINDIAMOND); + assertEquals(Numeric.toHexString(toScript.data()), "0x76a914b6adfbbf15c8f6fa53f1edb37054dce5c7c145c688ac"); + + // prepare SigningInput + val input = Bitcoin.SigningInput.newBuilder() + .setHashType(BitcoinScript.hashTypeForCoin(CoinType.BITCOINDIAMOND)) + .setAmount(17615) + .setByteFee(1) + .setToAddress("1HevQVTSEc8cEpDm65UpfCdj5erd4xxBhx") + .setChangeAddress("1G15VvshDxwFTnahZZECJfFwEkq9fP79o8") + .setCoinType(CoinType.BITCOINDIAMOND.value()) + + val utxoKey0 = + (Numeric.hexStringToByteArray("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee")) + input.addPrivateKey(ByteString.copyFrom(utxoKey0)) + + // build utxo + val txHash0 = (Numeric.hexStringToByteArray("034f4667301711e8a69236a93476ed798f9c11aaae472da5b315191a0453461d")) + val outpoint0 = Bitcoin.OutPoint.newBuilder() + .setHash(ByteString.copyFrom(txHash0)) + .setIndex(0) + .setSequence(Long.MAX_VALUE.toInt()) + .build() + + val utxo0 = Bitcoin.UnspentTransaction.newBuilder() + .setAmount(27615) + .setOutPoint(outpoint0) + .setScript(ByteString.copyFrom("76a914a48da46386ce52cccad178de900c71f06130c31088ac".toHexBytes())) + .build() + + input.addUtxo(utxo0) + + val plan = AnySigner.plan(input.build(), CoinType.BITCOINDIAMOND, Bitcoin.TransactionPlan.parser()) + + input.plan = Bitcoin.TransactionPlan.newBuilder() + .mergeFrom(plan) + .setAmount(17615) + .setFee(10000) + .setChange(0) + .setPreblockhash(ByteString.copyFrom("99668daf7c77c6462f3ba8fab61b4f62abaabd62911e7e59729ee9e1929dfa4b".toHexBytes())) + .build() + + + val output = AnySigner.sign(input.build(), CoinType.BITCOINDIAMOND, Bitcoin.SigningOutput.parser()) + + assertEquals(output.error, SigningError.OK) + val signedTransaction = output.transaction + assert(signedTransaction.isInitialized) + assertEquals(12, signedTransaction.version) + assertEquals(1, signedTransaction.inputsCount) + assertEquals(1, signedTransaction.outputsCount) + + val encoded = output.encoded + assertEquals("0x0c00000099668daf7c77c6462f3ba8fab61b4f62abaabd62911e7e59729ee9e1929dfa4b01034f4667301711e8a69236a93476ed798f9c11aaae472da5b315191a0453461d000000006a473044022078e0d3a9e1eb270ab02c15f8fcf1d3bfc95a324839690b7de4f011a4266132ff02204679e8103c4d3f0bb5192a5f53cc273732fd0e8392ab3b00dc708fd24d0160b3012102485a209514cc896f8ed736e205bc4c35bd5299ef3f9e84054475336b964c02a3ffffffff01cf440000000000001976a914b6adfbbf15c8f6fa53f1edb37054dce5c7c145c688ac00000000", + Numeric.toHexString(encoded.toByteArray())); + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoSigning.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoSigning.kt index 524ddb2e96f..675a3e63e5f 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoSigning.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoSigning.kt @@ -114,7 +114,7 @@ class TestCardanoSigning { .setAmount(8_051_373) val token3 = Cardano.TokenAmount.newBuilder() .setPolicyId("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77") - .setAssetName("CUBY") + .setAssetNameHex("43554259") .setAmount(ByteString.copyFrom(Numeric.hexStringToByteArray("2dc6c0"))) // 3000000 .build() utxo1.addTokenAmount(token3) @@ -130,7 +130,7 @@ class TestCardanoSigning { .setAmount(2_000_000) val token1 = Cardano.TokenAmount.newBuilder() .setPolicyId("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77") - .setAssetName("SUNDAE") + .setAssetNameHex("53554e444145") .setAmount(ByteString.copyFrom(Numeric.hexStringToByteArray("04d3e8d9"))) // 80996569 .build() utxo2.addTokenAmount(token1) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cosmos/TestCosmosTransactions.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cosmos/TestCosmosTransactions.kt index 7267138e1da..ed829d1ce76 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cosmos/TestCosmosTransactions.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cosmos/TestCosmosTransactions.kt @@ -69,7 +69,7 @@ class TestCosmosTransactions { output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczEzazBxMGw3bGcya3IzMmt2dDdseTIzNnBwbGR5OHY5ZHp3aDNnZBItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjoSNgo0Y29zbW9zdmFsb3BlcjFnanR2bHk5bGVsNnpza3Z3dHZsZzV2aHdwdTljOXdhdzdzeHp3eCABEgYI4LD6pgYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAUSEwoNCgV1YXRvbRIEMjQxOBCp8wUaQIFyfuijGKf87Hz61ZqxasfLI1PZnNge4RDq/tRyB/tZI6p80iGRqHecoV6+84EQkc9GTlNRQOSlApRCsivT9XI=\"}" ) - assertEquals(output.error, "") + assertEquals(output.errorMessage, "") } @Test @@ -114,7 +114,7 @@ class TestCosmosTransactions { output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CqoBCqcBCh8vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnUmV2b2tlEoMBCi1jb3Ntb3MxM2swcTBsN2xnMmtyMzJrdnQ3bHkyMzZwcGxkeTh2OWR6d2gzZ2QSLWNvc21vczFmczdsdTI4aHg1bTlha203cnAwYzI0MjJjbjhyMmY3Z3VydWpoZhojL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuTXNnRGVsZWdhdGUSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAQSEwoNCgV1YXRvbRIEMjE5NBC3rQUaQI7K+W7MMBoD6FbFZxRBqs9VTjErztjWTy57+fvrLaTCIZ+eBs7CuaKqfUZdSN8otjubSHVTQID3k9DpPAX0yDo=\"}" ) - assertEquals(output.error, "") + assertEquals(output.errorMessage, "") } @Test @@ -166,7 +166,7 @@ class TestCosmosTransactions { output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H\"}" ) - assertEquals(output.error, "") + assertEquals(output.errorMessage, "") } @Test diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cryptoorg/TestCryptoorgSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cryptoorg/TestCryptoorgSigner.kt index 98c568d9bef..25197f82a53 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cryptoorg/TestCryptoorgSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cryptoorg/TestCryptoorgSigner.kt @@ -73,6 +73,6 @@ class TestCryptoorgSigner { // https://crypto.org/explorer/tx/BCB213B0A121F0CF11BECCF52475F1C8328D6070F3CFDA9E14C42E6DB30E847E assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CpABCo0BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm0KKmNybzFjdHd0Y3dwZ2tza3k5ODhkaHRoNmpzbHh2ZXVtZ3UwZDQ1emdmMBIqY3JvMXhwYWh5NmM3d2xkeGFjdjZsZDk5aDQzNW1odmZuc3VwMjR2Y3VzGhMKB2Jhc2Vjcm8SCDUwMDAwMDAwEmkKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQOIMbBhYj5+i+WdiIxxCEpFyNCJMHy/XsVdxiwde9Vr4xIECgIIARgCEhUKDwoHYmFzZWNybxIENTAwMBDAmgwaQAcxK9xk6r69gmz+1UWaCnYxNuXPXZdp59YcqKPJE5d6fp+IICTBOwd2rs8MiApcf8kNSrbZ6oECxcGQAdxF0SI=\"}") - assertEquals(output.error, "") + assertEquals(output.errorMessage, "") } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/eos/TestEOSSigning.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/eos/TestEOSSigning.kt index 27fb4ce0b11..d5d073b1840 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/eos/TestEOSSigning.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/eos/TestEOSSigning.kt @@ -82,7 +82,7 @@ class TestEOSSigning { val signatureValue: String = signatures.get(0) as String; assertNotNull("Error parsing JSON result", signatureValue) assertEquals( - "SIG_K1_KfCdjsrTnx5cBpbA5cUdHZAsRYsnC9uKzuS1shFeqfMCfdZwX4PBm9pfHwGRT6ffz3eavhtkyNci5GoFozQAx8P8PBnDmj", + "SIG_K1_K9RdLC7DEDWjTfR64GU8BtDHcAjzR1ntcT651JMcfHNTpdsvDrUwfyzF1FkvL9fxEi2UCtGJZ9zYoNbJoMF1fbU64cRiJ7", signatureValue ) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nuls/TestNULSSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nuls/TestNULSSigner.kt index 44284c35e02..f9399034258 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nuls/TestNULSSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nuls/TestNULSSigner.kt @@ -38,4 +38,84 @@ class TestNULSSigner { "0200f885885d00008c0117010001f7ec6473df12e751d64cf20a8baa7edd50810f8101000100201d9a0000000000000000000000000000000000000000000000000000000000080000000000000000000117010001f05e7878971f3374515eabb6f16d75219d8873120100010080969800000000000000000000000000000000000000000000000000000000000000000000000000692103958b790c331954ed367d37bac901de5c2f06ac8368b37d7bd6cd5ae143c1d7e3463044022028019c0099e2233c7adb84bb03a9a5666ece4a5b65a026a090fa460f3679654702204df0fcb8762b5944b3aba033fa1a287ccb098150035dd8b66f52dc58d3d0843a" ) } + + @Test + fun NULSTokenTransactionSigning() { + val signingInput = NULS.SigningInput.newBuilder() + .setPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("9ce21dad67e0f0af2599b41b515a7f7018059418bab892a7b68f283d489abc4b"))) + .setFrom("NULSd6Hgj7ZoVgsPN9ybB4C1N2TbvkgLc8Z9H") + .setTo("NULSd6Hgied7ym6qMEfVzZanMaa9qeqA6TZSe") + .setAmount(ByteString.copyFrom("0x989680".toHexByteArray())) + .setChainId(9) + .setIdassetsId(1) + .setNonce(ByteString.copyFrom("0000000000000000".toByteArray())) + .setRemark("") + .setBalance(ByteString.copyFrom("0x5F5E100".toHexByteArray())) + .setTimestamp(0x5D8885F8) + .setFeePayer("NULSd6Hgj7ZoVgsPN9ybB4C1N2TbvkgLc8Z9H") + .setFeePayerBalance(ByteString.copyFrom("0xF4240".toHexByteArray())) + .setFeePayerNonce(ByteString.copyFrom("0000000000000000".toByteArray())) + .build() + + val output = AnySigner.sign(signingInput, CoinType.NULS, SigningOutput.parser()) + val encoded = output.encoded.toByteArray() + val hex = Numeric.toHexString(encoded, 0, encoded.size, false) + assertEquals(hex, + "0200f885885d0000d20217010001f7ec6473df12e751d64cf20a8baa7edd50810f810900010080969800000000000000000000000000000000000000000000000000000000000800000000000000000017010001f7ec6473df12e751d64cf20a8baa7edd50810f8101000100a086010000000000000000000000000000000000000000000000000000000000080000000000000000000117010001f05e7878971f3374515eabb6f16d75219d8873120900010080969800000000000000000000000000000000000000000000000000000000000000000000000000692103958b790c331954ed367d37bac901de5c2f06ac8368b37d7bd6cd5ae143c1d7e346304402204629e7e39708468a405f8d8dd208d0133a686beb5d3ae829b7a2f5867c74480902203d0dffac8189b1caa9087e480fd57581e8c382cc4e17034b492dd2178dac851d" + ) + } + + @Test + fun NULSTransactionWithFeePayerSigning() { + val signingInput = NULS.SigningInput.newBuilder() + .setPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("48c91cd24a27a1cdc791022ff39316444229db1c466b3b1841b40c919dee3002"))) + .setFrom("NULSd6HgYx7bdWWv7PxYhYeTRBhD6kZs1o5Ac") + .setTo("NULSd6HgaHjzhMEUjd4T5DFnLz9EvV4TntrdV") + .setAmount(ByteString.copyFrom("0x186A0".toHexByteArray())) + .setChainId(1) + .setIdassetsId(1) + .setNonce(ByteString.copyFrom("0000000000000000".toByteArray())) + .setRemark("") + .setBalance(ByteString.copyFrom("0xF4240".toHexByteArray())) + .setTimestamp(0x62FB3F9F) + .setFeePayer("NULSd6HgYj81NrQBFZYXvyQhHCJCkGYWDTNeA") + .setFeePayerNonce(ByteString.copyFrom("0000000000000000".toByteArray())) + .setFeePayerBalance(ByteString.copyFrom("0xF4240".toHexByteArray())) + .setFeePayerPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("9401fd554cb700777e57b05338f9ff47597add8b23ce9f1c8e041e9b4e2116b6"))) + .build() + + val output = AnySigner.sign(signingInput, CoinType.NULS, SigningOutput.parser()) + val encoded = output.encoded.toByteArray() + val hex = Numeric.toHexString(encoded, 0, encoded.size, false) + assertEquals(hex, + "02009f3ffb620000d202170100014f019a4227bff89d51590fbf53fbd98d994485f801000100a086010000000000000000000000000000000000000000000000000000000000080000000000000000001701000152a6388c8bf54e8fcd73cc824813bfef0912299b01000100a086010000000000000000000000000000000000000000000000000000000000080000000000000000000117010001686a3c9cd2ac45aee0ef825b0443d1eb209368b701000100a0860100000000000000000000000000000000000000000000000000000000000000000000000000d22102764370693450d654d6fc061d1d4dbfbe0c95715fd3cde7e15df073ab0983b8c8463044022040b5820b93fd5e272f2a00518af45a722571834934ba20d9a866de8e6d6409ab022003c610c647648444c1e2193634b2c51817a5a6ac3fe781da1a9ea773506afd8221025910df09ca768909cce9224d182401044c7b5bd44b0adee2ec5f2e64446573ff4630440220140e46b260942a8475f38df39bf54a2eea56c77199fc7ba775aa4b7f147d0d2102206c82705cba509f37ba0e35520a97bccb71a9e35cadcb8d95dd7fde5c8aa9e428" + ) + } + + @Test + fun NULSTokenTransactionWithFeePayerSigning() { + val signingInput = NULS.SigningInput.newBuilder() + .setPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("48c91cd24a27a1cdc791022ff39316444229db1c466b3b1841b40c919dee3002"))) + .setFrom("NULSd6HgYx7bdWWv7PxYhYeTRBhD6kZs1o5Ac") + .setTo("NULSd6HgaHjzhMEUjd4T5DFnLz9EvV4TntrdV") + .setAmount(ByteString.copyFrom("0x186A0".toHexByteArray())) + .setChainId(9) + .setIdassetsId(1) + .setNonce(ByteString.copyFrom("0000000000000000".toByteArray())) + .setRemark("") + .setBalance(ByteString.copyFrom("0xDBBA0".toHexByteArray())) + .setTimestamp(0x62FB4E4C) + .setFeePayer("NULSd6HgYj81NrQBFZYXvyQhHCJCkGYWDTNeA") + .setFeePayerBalance(ByteString.copyFrom("0xF4240".toHexByteArray())) + .setFeePayerNonce(ByteString.copyFrom("e05d03df6ede0e22".toByteArray())) + .setFeePayerPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("9401fd554cb700777e57b05338f9ff47597add8b23ce9f1c8e041e9b4e2116b6"))) + .build() + + val output = AnySigner.sign(signingInput, CoinType.NULS, SigningOutput.parser()) + val encoded = output.encoded.toByteArray() + val hex = Numeric.toHexString(encoded, 0, encoded.size, false) + assertEquals(hex, + "02004c4efb620000d2021701000152a6388c8bf54e8fcd73cc824813bfef0912299b09000100a08601000000000000000000000000000000000000000000000000000000000008000000000000000000170100014f019a4227bff89d51590fbf53fbd98d994485f801000100a08601000000000000000000000000000000000000000000000000000000000008e05d03df6ede0e22000117010001686a3c9cd2ac45aee0ef825b0443d1eb209368b709000100a0860100000000000000000000000000000000000000000000000000000000000000000000000000d32102764370693450d654d6fc061d1d4dbfbe0c95715fd3cde7e15df073ab0983b8c8463044022025cb5b40bda4e6fc0ba719bb0c1a907b2a0faa91316ef2c836310d49f906b6a802207d530a56a6c56d974036c86125da06d6e47f9d8ba1544ac3e620cebd883a707821025910df09ca768909cce9224d182401044c7b5bd44b0adee2ec5f2e64446573ff473045022100ff6f45a1c3856f9ea954baca6b2988295bbb22c958f87f0d3baf9989930549530220426ecb1520513710b99ab50e1f6c7e21b0175adef08aa05070bb9bfca8a001d8" + ) + } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisSigner.kt index 97f075947ba..e8498fb0ab6 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisSigner.kt @@ -73,6 +73,6 @@ class TestOsmosisSigner { val output = AnySigner.sign(signingInput, OSMOSIS, SigningOutput.parser()) assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"Co0BCooBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmoKK29zbW8xbWt5Njljbjhla3R3eTA4NDV2ZWM5dXBzZHBoa3R4dDBlbjk3ZjUSK29zbW8xOHMwaGRuc2xsZ2NjbHdldTlheW13NG5na3RyMmswcmt2bjdqbW4aDgoFdW9zbW8SBTk5ODAwEmQKTgpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQLs71zkN6MCxn+VRo3ksx826RH0Z9fmpStBweE+HVY2SRIECgIIARISCgwKBXVvc21vEgMyMDAQwJoMGkAMY//Md5GRUR4lVZhk558hFS3kii9QZYoYKfg4+ac/xgNeyoiEweVDhcmEvlH1orVwjLUOnYs4ly2a/yIurYVj\"}") - assertEquals(output.error, "") + assertEquals(output.errorMessage, "") } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/secret/TestSecretSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/secret/TestSecretSigner.kt index 2c25e3601da..d9286ccd4f2 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/secret/TestSecretSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/secret/TestSecretSigner.kt @@ -72,6 +72,6 @@ class TestSecretSigner { val output = AnySigner.sign(signingInput, SECRET, SigningOutput.parser()) assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CpIBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLXNlY3JldDE4bWRyamE0MGdmdWZ0dDV5eDZ0Z2owZm41bHVycGxlenlwODk0eRItc2VjcmV0MXJucTZoamZuYWx4ZWVmODdybWRleWEzbnU5ZGhwYzdrOXB1anMzGg8KBXVzY3J0EgYxMDAwMDASZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAkZqxdKMtPq2w0kGDGwWGejTAed0H7azPMHtrCX0XYZGEgQKAggBGAESEwoNCgV1c2NydBIEMjUwMBCowwEaQOcHd2gHpa5WKZ/5RRerEtrHlyDlojIEzUGhC9xMFgs7UQMWy+kTTN+NRf7zQ8rx3cPkIKeZhv0u1KRc8uRCc4o=\"}") - assertEquals(output.error, "") + assertEquals(output.errorMessage, "") } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stargaze/TestStargazeSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stargaze/TestStargazeSigner.kt index 36a4410be82..94a2fbfc7c2 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stargaze/TestStargazeSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stargaze/TestStargazeSigner.kt @@ -65,7 +65,7 @@ class TestStargazeSigner { output.serialized, expected ) - assertEquals(output.error, "") + assertEquals(output.errorMessage, "") } @Test @@ -118,6 +118,6 @@ class TestStargazeSigner { output.serialized, expected ) - assertEquals(output.error, "") + assertEquals(output.errorMessage, "") } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraClassicTxs.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraClassicTxs.kt index 2c67c665dd8..538d0efb76b 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraClassicTxs.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraClassicTxs.kt @@ -110,7 +110,7 @@ class TestTerraClassicTxs { val output = AnySigner.sign(signingInput, TERRA, SigningOutput.parser()) assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CucBCuQBCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBK5AQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2Glt7InRyYW5zZmVyIjp7ImFtb3VudCI6IjI1MDAwMCIsInJlY2lwaWVudCI6InRlcnJhMWpsZ2FxeTludm4yaGY1dDJzcmE5eWN6OHM3N3duZjlsMGttZ2NwIn19EmcKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQNwZjrHsPmJKW/rXOWfukpQ1+lOHOJW3/IlFFnKLNmsABIECgIIARgDEhMKDQoFdWx1bmESBDMwMDAQwJoMGkAaprIEMLPH2HmFdwFGoaipb2GIyhXt6ombz+WMnG2mORBI6gFt0M+IymYgzZz6w1SW52R922yafDnn7yXfutRw\"}") - assertEquals(output.error, "") + assertEquals(output.errorMessage, "") } @Test @@ -154,7 +154,7 @@ class TestTerraClassicTxs { val output = AnySigner.sign(signingInput, TERRA, SigningOutput.parser()) assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"Cu4BCusBCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBLAAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2GmJ7InRyYW5zZmVyIjogeyAiYW1vdW50IjogIjI1MDAwMCIsICJyZWNpcGllbnQiOiAidGVycmExZDcwNDhjc2FwNHd6Y3Y1em03ejZ0ZHFlbTJhZ3lwOTY0N3ZkeWoiIH0gfRJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYBxITCg0KBXVsdW5hEgQzMDAwEMCaDBpAkPsS7xlSng2LMc9KiD1soN5NLaDcUh8I9okPmsdJN3le1B7yxRGNB4aQfhaRl/8Z0r5vitRT0AWuxDasd8wcFw==\"}") - assertEquals(output.error, "") + assertEquals(output.errorMessage, "") } @Test @@ -204,6 +204,6 @@ class TestTerraClassicTxs { val output = AnySigner.sign(signingInput, TERRA, SigningOutput.parser()) assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CrIBCq8BCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBKEAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMXNlcGZqN3MwYWVnNTk2N3V4bmZrNHRoemxlcnJza3RrcGVsbTVzGhh7ICJkZXBvc2l0X3N0YWJsZSI6IHt9IH0qDAoEdXVzZBIEMTAwMBJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYCRITCg0KBXVsdW5hEgQ3MDAwEMDPJBpAGyi7f1ioY8XV6pjFq1s86Om4++CIUnd3rLHif2iopCcYvX0mLkTlQ6NUERg8nWTYgXcj6fOTO/ptgPuAtv0NWg==\"}") - assertEquals(output.error, "") + assertEquals(output.errorMessage, "") } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraTransactions.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraTransactions.kt index 6ee4574001a..e4591ff57e3 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraTransactions.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraTransactions.kt @@ -64,7 +64,7 @@ class TestTerraTransactions { val jsonPayload = output.json assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CpEBCo4BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm4KLHRlcnJhMWhzazZqcnl5cWpmaHA1ZGhjNTV0YzlqdGNreWd4MGVwMzdoZGQyEix0ZXJyYTFqbGdhcXk5bnZuMmhmNXQyc3JhOXljejhzNzd3bmY5bDBrbWdjcBoQCgV1bHVuYRIHMTAwMDAwMBJoClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECVyhuw/N9M1V7u6oACyd0SskCOqmWfK51oYHR/5H6ncUSBAoCCAEYARIUCg4KBXVsdW5hEgUzMDAwMBDAmgwaQPh0C3rjzdixIUiyPx3FlWAxzbKILNAcSRVeQnaTl1vsI5DEfYa2oYlUBLqyilcMCcU/iaJLhex30No2ak0Zn1Q=\"}") - assertEquals(output.error, "") + assertEquals(output.errorMessage, "") } @Test @@ -109,6 +109,6 @@ class TestTerraTransactions { val output = AnySigner.sign(signingInput, TERRAV2, SigningOutput.parser()) assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CuUBCuIBCiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QSuQEKLHRlcnJhMTh3dWtwODRkcTIyN3d1NG1naDBqbTZuOW5sbmo2cnM4MnBwOXdmEix0ZXJyYTE0ejU2bDBmcDJsc2Y4Nnp5M2h0eTJ6NDdlemtobnRodHI5eXE3NhpbeyJ0cmFuc2ZlciI6eyJhbW91bnQiOiIyNTAwMDAiLCJyZWNpcGllbnQiOiJ0ZXJyYTFqbGdhcXk5bnZuMmhmNXQyc3JhOXljejhzNzd3bmY5bDBrbWdjcCJ9fRJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYAxITCg0KBXVsdW5hEgQzMDAwEMCaDBpAiBGbQaj+jsXE6/FssD3fC77QOxpli9GqsPea+KoNyMIEgVj89Hii+oU1bAEQS4qV0SaE2V6RNy24uCcFTIRbcQ==\"}") - assertEquals(output.error, "") + assertEquals(output.errorMessage, "") } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORChainSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORChainSigner.kt index 2314586d13e..c12ad29b2f1 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORChainSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORChainSigner.kt @@ -74,7 +74,7 @@ class TestTHORChainSigner { val output = AnySigner.sign(signingInput, THORCHAIN, SigningOutput.parser()) assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"ClIKUAoOL3R5cGVzLk1zZ1NlbmQSPgoUFSLnZ9tusZcIsAOAKb+9YHvJvQ4SFMqGRZ+wBVHH30JUDF54aRksgzrbGhAKBHJ1bmUSCDM4MDAwMDAwEmYKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQPtmX45bPQpL1/OWkK7pBWZzNXZbjExVKfJ6nBJ3jF8dxIECgIIARgVEhIKCwoEcnVuZRIDMjAwEKDLmAEaQKZtS3ATa26OOGvqdKm14ZbHeNfkPtIajXi5MkZ5XaX2SWOeX+YnCPZ9TxF9Jj5cVIo71m55xq4hVL3yDbRe89g=\"}") - assertEquals(output.error, "") + assertEquals(output.errorMessage, "") assertEquals(output.json, "") } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/zen/TestZenAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/zen/TestZenAddress.kt new file mode 100644 index 00000000000..bd7fcdfed69 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/zen/TestZenAddress.kt @@ -0,0 +1,31 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +package com.trustwallet.core.app.blockchains.zen + +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 TestZenAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a".toHexByteArray()) + val pubkey = key.getPublicKeySecp256k1(true) + val address = AnyAddress(pubkey, CoinType.ZEN) + val expected = AnyAddress("znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg", CoinType.ZEN) + + assertEquals(pubkey.data().toHex(), "0x02b4ac9056d20c52ac11b0d7e83715dd3eac851cfc9cb64b8546d9ea0d4bb3bdfe") + assertEquals(address.description(), expected.description()) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/zen/TestZenSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/zen/TestZenSigner.kt new file mode 100644 index 00000000000..0b42106409a --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/zen/TestZenSigner.kt @@ -0,0 +1,78 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +package com.trustwallet.core.app.blockchains.zen + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHexByteArray +import com.trustwallet.core.app.utils.toHexBytes +import com.trustwallet.core.app.utils.toHexBytesInByteString +import org.junit.Assert.assertEquals +import org.junit.Test + +import wallet.core.java.AnySigner +import wallet.core.jni.BitcoinScript +import wallet.core.jni.BitcoinSigHashType +import wallet.core.jni.CoinType +import wallet.core.jni.proto.Bitcoin +import wallet.core.jni.proto.Common.SigningError + +class TestZenSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun ZenTransactionSigning() { + // prepare SigningInput + val input = Bitcoin.SigningInput.newBuilder() + .setHashType(BitcoinScript.hashTypeForCoin(CoinType.ZEN)) + .setAmount(10000) + .setByteFee(1) + .setToAddress("zngBGZGKaaBamofSuFw5WMnvU2HQAtwGeb5") + .setChangeAddress("znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg") + .setCoinType(CoinType.ZEN.value()) + + val utxoKey0 = + (Numeric.hexStringToByteArray("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a")) + input.addPrivateKey(ByteString.copyFrom(utxoKey0)) + + // build utxo + val txHash0 = (Numeric.hexStringToByteArray("a39e13b5ab406547e31284cd96fb40ed271813939c195ae7a86cd67fb8a4de62")) + val outpoint0 = Bitcoin.OutPoint.newBuilder() + .setHash(ByteString.copyFrom(txHash0)) + .setIndex(0) + .setSequence(Long.MAX_VALUE.toInt()) + .build() + + val utxo0 = Bitcoin.UnspentTransaction.newBuilder() + .setAmount(17600) + .setOutPoint(outpoint0) + .setScript(ByteString.copyFrom("76a914cf83669620de8bbdf2cefcdc5b5113195603c56588ac2081dc725fd33fada1062323802eefb54d3325d924d4297a69221456040000000003e88211b4".toHexBytes())) + .build() + + input.addUtxo(utxo0) + + val plan = AnySigner.plan(input.build(), CoinType.ZEN, Bitcoin.TransactionPlan.parser()) + + input.plan = Bitcoin.TransactionPlan.newBuilder() + .mergeFrom(plan) + .setPreblockhash(ByteString.copyFrom("81dc725fd33fada1062323802eefb54d3325d924d4297a692214560400000000".toHexBytes())) + .setPreblockheight(1147624) + .build() + + + val output = AnySigner.sign(input.build(), CoinType.ZEN, Bitcoin.SigningOutput.parser()) + + assertEquals(output.error, SigningError.OK) + + val encoded = output.encoded + assertEquals("0x0100000001a39e13b5ab406547e31284cd96fb40ed271813939c195ae7a86cd67fb8a4de62000000006a473044022014d687c0bee0b7b584db2eecbbf73b545ee255c42b8edf0944665df3fa882cfe02203bce2412d93c5a56cb4806ddd8297ff05f8fc121306e870bae33377a58a02f05012102b4ac9056d20c52ac11b0d7e83715dd3eac851cfc9cb64b8546d9ea0d4bb3bdfeffffffff0210270000000000003f76a914a58d22659b1082d1fa8698fc51996b43281bfce788ac2081dc725fd33fada1062323802eefb54d3325d924d4297a69221456040000000003e88211b4ce1c0000000000003f76a914cf83669620de8bbdf2cefcdc5b5113195603c56588ac2081dc725fd33fada1062323802eefb54d3325d924d4297a69221456040000000003e88211b400000000", + Numeric.toHexString(encoded.toByteArray())); + } +} diff --git a/codegen/lib/coin_skeleton_gen.rb b/codegen/lib/coin_skeleton_gen.rb index c8adec9d8be..29335e4fdb0 100755 --- a/codegen/lib/coin_skeleton_gen.rb +++ b/codegen/lib/coin_skeleton_gen.rb @@ -129,6 +129,7 @@ def generate_skeleton(coin_id, mode) generate_file("newcoin/AddressTests.cpp.erb", "tests/chains/#{name}", "AddressTests.cpp", coin) generate_file("newcoin/SignerTests.cpp.erb", "tests/chains/#{name}", "SignerTests.cpp", coin) + generate_file("newcoin/TransactionCompilerTests.cpp.erb", "tests/chains/#{name}", "TransactionCompilerTests.cpp", coin) generate_file("newcoin/TWAddressTests.cpp.erb", "tests/chains/#{name}", "TWAnyAddressTests.cpp", coin) generate_file("newcoin/TWSignerTests.cpp.erb", "tests/chains/#{name}", "TWAnySignerTests.cpp", coin) generate_file("newcoin/AddressTests.kt.erb", "android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/#{format_name_lowercase(coin)}", "Test#{name}Address.kt", coin) diff --git a/codegen/lib/templates/newcoin/Entry.cpp.erb b/codegen/lib/templates/newcoin/Entry.cpp.erb index e078c291122..5e4c4aaf25d 100644 --- a/codegen/lib/templates/newcoin/Entry.cpp.erb +++ b/codegen/lib/templates/newcoin/Entry.cpp.erb @@ -25,4 +25,12 @@ void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) con signTemplate(dataIn, dataOut); } +TW::Data Entry::preImageHashes(TWCoinType coin, const Data& txInputData) const { + return TW::Data(); +} + +void Entry::compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + +} + } // namespace TW::<%= format_name(coin) %> diff --git a/codegen/lib/templates/newcoin/Entry.h.erb b/codegen/lib/templates/newcoin/Entry.h.erb index 03196bf2039..29ba0783596 100644 --- a/codegen/lib/templates/newcoin/Entry.h.erb +++ b/codegen/lib/templates/newcoin/Entry.h.erb @@ -19,6 +19,9 @@ public: void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; // normalizeAddress(): implement this if needed, e.g. Ethereum address is EIP55 checksummed // plan(): implement this if the blockchain is UTXO based + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::<%= format_name(coin) %> diff --git a/codegen/lib/templates/newcoin/Signer.h.erb b/codegen/lib/templates/newcoin/Signer.h.erb index d96e144345a..7d660796723 100644 --- a/codegen/lib/templates/newcoin/Signer.h.erb +++ b/codegen/lib/templates/newcoin/Signer.h.erb @@ -19,7 +19,10 @@ public: Signer() = delete; /// Signs a Proto::SigningInput transaction - static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; + static Proto::SigningOutput sign(const Proto::SigningInput& input, std::optional optionalExternalSigs = {}) noexcept; + + /// Collect pre-image hashes to be signed + static PreSigningOutput preImageHashes(const SigningInput& input) noexcept; }; } // namespace TW::<%= name %> diff --git a/codegen/lib/templates/newcoin/TransactionCompilerTests.cpp.erb b/codegen/lib/templates/newcoin/TransactionCompilerTests.cpp.erb new file mode 100644 index 00000000000..8150081c49e --- /dev/null +++ b/codegen/lib/templates/newcoin/TransactionCompilerTests.cpp.erb @@ -0,0 +1,30 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "<%= format_name(coin) %>/Signer.h" +#include "<%= format_name(coin) %>/Address.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TestUtilities.h" +#include "TransactionCompiler.h" + +#include "proto/<%= format_name(coin) %>.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include + +#include + +using namespace TW; + +namespace TW::<%= format_name(coin) %> { + +TEST(<%= format_name(coin) %>Compiler, CompileWithSignatures) { + // TODO: Finalize test implementation +} + +} // namespace TW::<%= format_name(coin) %> diff --git a/docs/registry.md b/docs/registry.md index adb90c124ca..ba11ab6fc96 100644 --- a/docs/registry.md +++ b/docs/registry.md @@ -13,15 +13,21 @@ This list is generated from [./registry.json](../registry.json) | 20 | DigiByte | DGB | | | | 22 | Monacoin | MONA | | | | 42 | Decred | DCR | | | +| 57 | Syscoin | SYS | | | | 60 | Ethereum | ETH | | | | 61 | Ethereum Classic | ETC | | | | 74 | ICON | ICX | | | +| 77 | Verge | XVG | | | | 118 | Cosmos Hub | ATOM | | | +| 119 | Pivx | PIVX | | | +| 121 | Zen | ZEN | | | | 133 | Zcash | ZEC | | | | 136 | Firo | FIRO | | | | 137 | Rootstock | RBTC | | | +| 141 | Komodo | KMD | | | | 144 | XRP | XRP | | | | 145 | Bitcoin Cash | BCH | | | +| 146 | Nebl | NEBL | | | | 148 | Stellar | XLM | | | | 156 | Bitcoin Gold | BTG | | | | 165 | Nano | XNO | | | @@ -32,6 +38,7 @@ This list is generated from [./registry.json](../registry.json) | 235 | FIO | FIO | | | | 242 | Nimiq | NIM | | | | 283 | Algorand | ALGO | | | +| 291 | IOST | IOST | | | | 304 | IoTeX | IOTX | | | | 309 | Nervos | CKB | | | | 313 | Zilliqa | ZIL | | | @@ -56,7 +63,7 @@ This list is generated from [./registry.json](../registry.json) | 564 | Agoric | BLD | | | | 607 | TON | TON | | | | 637 | Aptos | APT | | | -| 714 | BNB Beacon Chain | BNB | | | +| 714 | BNB Beacon Chain | BNB | | | | 784 | Sui | SUI | | | | 818 | VeChain | VET | | | | 820 | Callisto | CLO | | | @@ -66,6 +73,7 @@ This list is generated from [./registry.json](../registry.json) | 931 | THORChain | RUNE | | | | 966 | Polygon | MATIC | | | | 996 | OKX Chain | OKT | | | +| 999 | Bitcoin Diamond | BCD | | | | 1001 | ThunderCore | TT | | | | 1023 | Harmony | ONE | | | | 1024 | Ontology | ONT | | | @@ -80,6 +88,7 @@ This list is generated from [./registry.json](../registry.json) | 18000 | Meter | MTR | | | | 19167 | Flux | FLUX | | | | 52752 | Celo | CELO | | | +| 105105 | Stratis | STRAX | | | | 534353 | Scroll | ETH | | | | 5718350 | Wanchain | WAN | | | | 5741564 | Waves | WAVES | | | @@ -120,6 +129,7 @@ This list is generated from [./registry.json](../registry.json) | 20000714 | BNB Smart Chain | BNB | | | | 20009001 | Native Evmos | EVMOS | | | | 30000118 | Juno | JUNO | | | +| 30000714 | TBNB | BNB | | | | 40000118 | Stride | STRD | | | | 50000118 | Axelar | AXL | | | | 60000118 | Crescent | CRE | | | diff --git a/include/TrustWalletCore/TWBitcoinScript.h b/include/TrustWalletCore/TWBitcoinScript.h index e8a8ba2411f..f8db46847d6 100644 --- a/include/TrustWalletCore/TWBitcoinScript.h +++ b/include/TrustWalletCore/TWBitcoinScript.h @@ -202,6 +202,10 @@ struct TWBitcoinScript* _Nonnull TWBitcoinScriptBuildPayToWitnessScriptHash(TWDa TW_EXPORT_STATIC_METHOD struct TWBitcoinScript* _Nonnull TWBitcoinScriptLockScriptForAddress(TWString* _Nonnull address, enum TWCoinType coin); +/// Builds a appropriate lock script for the given address with replay. +TW_EXPORT_STATIC_METHOD +struct TWBitcoinScript *_Nonnull TWBitcoinScriptLockScriptForAddressReplay(TWString *_Nonnull address, enum TWCoinType coin, TWData *_Nonnull blockHash, int64_t blockHeight); + /// Return the default HashType for the given coin, such as TWBitcoinSigHashTypeAll. /// /// \param coinType coin type diff --git a/include/TrustWalletCore/TWBlockchain.h b/include/TrustWalletCore/TWBlockchain.h index ebcdca72d89..d48fb3c14d8 100644 --- a/include/TrustWalletCore/TWBlockchain.h +++ b/include/TrustWalletCore/TWBlockchain.h @@ -40,6 +40,7 @@ enum TWBlockchain { TWBlockchainHarmony = 25, TWBlockchainNEAR = 26, TWBlockchainAlgorand = 27, + TWBlockchainIOST = 28, TWBlockchainPolkadot = 29, TWBlockchainCardano = 30, TWBlockchainNEO = 31, @@ -52,12 +53,16 @@ enum TWBlockchain { TWBlockchainThorchain = 38, // Cosmos TWBlockchainRonin = 39, // Ethereum TWBlockchainKusama = 40, // Polkadot - TWBlockchainNervos = 41, - TWBlockchainEverscale = 42, - TWBlockchainAptos = 43, // Aptos - TWBlockchainHedera = 44, // Hedera - TWBlockchainTheOpenNetwork = 45, - TWBlockchainSui = 46, + TWBlockchainZen = 41, // Bitcoin + TWBlockchainBitcoinDiamond = 42, // Bitcoin + TWBlockchainVerge = 43, // Bitcoin + TWBlockchainNervos = 44, + TWBlockchainEverscale = 45, + TWBlockchainAptos = 46, // Aptos + TWBlockchainNebl = 47, // Bitcoin + TWBlockchainHedera = 48, // Hedera + TWBlockchainTheOpenNetwork = 49, + TWBlockchainSui = 50, }; TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWCoinType.h b/include/TrustWalletCore/TWCoinType.h index 7b13940d4e1..d521c20b543 100644 --- a/include/TrustWalletCore/TWCoinType.h +++ b/include/TrustWalletCore/TWCoinType.h @@ -9,6 +9,7 @@ #include "TWBase.h" #include "TWBlockchain.h" #include "TWCurve.h" +#include "TWDerivation.h" #include "TWHDVersion.h" #include "TWHRP.h" #include "TWPurpose.h" @@ -38,6 +39,7 @@ enum TWCoinType { TWCoinTypeCallisto = 820, TWCoinTypeCardano = 1815, // Note: Cardano Shelley testnet uses purpose 1852 (not 44) 1852/1815 TWCoinTypeCosmos = 118, + TWCoinTypePivx = 119, TWCoinTypeDash = 5, TWCoinTypeDecred = 42, TWCoinTypeDigiByte = 20, @@ -92,6 +94,7 @@ enum TWCoinType { TWCoinTypeBandChain = 494, TWCoinTypeSmartChainLegacy = 10000714, TWCoinTypeSmartChain = 20000714, + TWCoinTypeTBinance = 30000714, TWCoinTypeOasis = 474, TWCoinTypePolygon = 966, TWCoinTypeTHORChain = 931, @@ -108,10 +111,15 @@ enum TWCoinType { TWCoinTypeRonin = 10002020, TWCoinTypeOsmosis = 10000118, TWCoinTypeECash = 899, + TWCoinTypeIOST = 291, TWCoinTypeCronosChain = 10000025, TWCoinTypeSmartBitcoinCash = 10000145, TWCoinTypeKuCoinCommunityChain = 10000321, + TWCoinTypeBitcoinDiamond = 999, TWCoinTypeBoba = 10000288, + TWCoinTypeSyscoin = 57, + TWCoinTypeVerge = 77, + TWCoinTypeZen = 121, TWCoinTypeMetis = 1001088, TWCoinTypeAurora = 1323161554, TWCoinTypeEvmos = 10009001, @@ -122,9 +130,12 @@ enum TWCoinType { TWCoinTypeKlaytn = 10008217, TWCoinTypeMeter = 18000, TWCoinTypeOKXChain = 996, + TWCoinTypeStratis = 105105, + TWCoinTypeKomodo = 141, TWCoinTypeNervos = 309, TWCoinTypeEverscale = 396, TWCoinTypeAptos = 637, + TWCoinTypeNebl = 146, TWCoinTypeHedera = 3030, TWCoinTypeSecret = 529, TWCoinTypeNativeInjective = 10000060, @@ -232,6 +243,12 @@ TW_EXPORT_METHOD TWString* _Nonnull TWCoinTypeDeriveAddressFromPublicKey(enum TWCoinType coin, struct TWPublicKey* _Nonnull publicKey); +/// Derives the address for a particular coin from the public key with the derivation. +TW_EXPORT_METHOD +TWString* _Nonnull TWCoinTypeDeriveAddressFromPublicKeyAndDerivation(enum TWCoinType coin, + struct TWPublicKey* _Nonnull publicKey, + enum TWDerivation derivation); + /// HRP for this coin type /// /// \param coin A coin type diff --git a/include/TrustWalletCore/TWHDVersion.h b/include/TrustWalletCore/TWHDVersion.h index 1ccf682e61e..6e726bef840 100644 --- a/include/TrustWalletCore/TWHDVersion.h +++ b/include/TrustWalletCore/TWHDVersion.h @@ -24,12 +24,18 @@ enum TWHDVersion { TWHDVersionYPRV = 0x049d7878, TWHDVersionZPUB = 0x04b24746, TWHDVersionZPRV = 0x04b2430c, + TWHDVersionVPUB = 0x045f1cf6, + TWHDVersionVPRV = 0x045f18bc, + TWHDVersionTPUB = 0x043587cf, + TWHDVersionTPRV = 0x04358394, // Litecoin TWHDVersionLTUB = 0x019da462, TWHDVersionLTPV = 0x019d9cfe, TWHDVersionMTUB = 0x01b26ef6, TWHDVersionMTPV = 0x01b26792, + TWHDVersionTTUB = 0x0436f6e1, + TWHDVersionTTPV = 0x0436ef7d, // Decred TWHDVersionDPUB = 0x2fda926, diff --git a/include/TrustWalletCore/TWHDWallet.h b/include/TrustWalletCore/TWHDWallet.h index c47c28bf066..e48e9bd7050 100644 --- a/include/TrustWalletCore/TWHDWallet.h +++ b/include/TrustWalletCore/TWHDWallet.h @@ -13,6 +13,7 @@ #include "TWDerivation.h" #include "TWDerivationPath.h" #include "TWHDVersion.h" +#include "TWDerivation.h" #include "TWPrivateKey.h" #include "TWPublicKey.h" #include "TWPurpose.h" diff --git a/include/TrustWalletCore/TWHash.h b/include/TrustWalletCore/TWHash.h index 3d9fd6a314f..f8f4a9743e2 100644 --- a/include/TrustWalletCore/TWHash.h +++ b/include/TrustWalletCore/TWHash.h @@ -103,6 +103,9 @@ TWData *_Nonnull TWHashBlake2b(TWData *_Nonnull data, size_t size); /// /// \param data Non-null block of data /// \return Non-null computed Groestl512 block of data +TW_EXPORT_STATIC_METHOD +TWData *_Nonnull TWHashBlake2bPersonal(TWData *_Nonnull data, TWData * _Nonnull personal, size_t outlen); + TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashGroestl512(TWData *_Nonnull data); diff --git a/include/TrustWalletCore/TWSubstrateAddress.h b/include/TrustWalletCore/TWSubstrateAddress.h new file mode 100644 index 00000000000..9b059b04861 --- /dev/null +++ b/include/TrustWalletCore/TWSubstrateAddress.h @@ -0,0 +1,48 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "TWBase.h" +#include "TWData.h" +#include "TWHRP.h" +#include "TWString.h" + +// TODO: Adjust definitions below + +TW_EXTERN_C_BEGIN + +struct TWPublicKey; + +/// Represents a Substrate address. +TW_EXPORT_CLASS +struct TWSubstrateAddress; + +/// Compares two addresses for equality. +TW_EXPORT_STATIC_METHOD +bool TWSubstrateAddressEqual(struct TWSubstrateAddress *_Nonnull lhs, struct TWSubstrateAddress *_Nonnull rhs); + +/// Determines if the string is a valid Substrate address. +TW_EXPORT_STATIC_METHOD +bool TWSubstrateAddressIsValidString(TWString *_Nonnull string, int32_t network); + +/// Creates an address from a string representaion. +TW_EXPORT_STATIC_METHOD +struct TWSubstrateAddress *_Nullable TWSubstrateAddressCreateWithString(TWString *_Nonnull string, int32_t network); + +/// Creates an address from a public key. +TW_EXPORT_STATIC_METHOD +struct TWSubstrateAddress *_Nonnull TWSubstrateAddressCreateWithPublicKey(struct TWPublicKey *_Nonnull publicKey, int32_t network); + +/// Delete address object +TW_EXPORT_METHOD +void TWSubstrateAddressDelete(struct TWSubstrateAddress *_Nonnull address); + +/// Returns the address string representation. +TW_EXPORT_PROPERTY +TWString *_Nonnull TWSubstrateAddressDescription(struct TWSubstrateAddress *_Nonnull address); + +TW_EXTERN_C_END \ No newline at end of file diff --git a/include/TrustWalletCore/TWSubstrateSigner.h b/include/TrustWalletCore/TWSubstrateSigner.h new file mode 100644 index 00000000000..cac91c11ba8 --- /dev/null +++ b/include/TrustWalletCore/TWSubstrateSigner.h @@ -0,0 +1,26 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "TWBase.h" +#include "TWData.h" +#include "TWSubstrateSigner.h" + +TW_EXTERN_C_BEGIN + +TW_EXPORT_CLASS +struct TWSubstrateSigner; + +/// Builds a message to be signed +TW_EXPORT_STATIC_METHOD +TWData *_Nonnull TWSubstrateSignerMessage(TWData *_Nonnull data); + +/// Builds a transaction to be broadcasted +TW_EXPORT_STATIC_METHOD +TWData *_Nonnull TWSubstrateSignerTransaction(TWData *_Nonnull data, TWData *_Nonnull publicKey, TWData *_Nonnull signature); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWTransactionCompiler.h b/include/TrustWalletCore/TWTransactionCompiler.h index d016b5f2681..529bc12e302 100644 --- a/include/TrustWalletCore/TWTransactionCompiler.h +++ b/include/TrustWalletCore/TWTransactionCompiler.h @@ -60,4 +60,10 @@ TWData* _Nonnull TWTransactionCompilerCompileWithSignatures( enum TWCoinType coinType, TWData* _Nonnull txInputData, const struct TWDataVector* _Nonnull signatures, const struct TWDataVector* _Nonnull publicKeys); +TW_EXPORT_STATIC_METHOD +TWData *_Nonnull TWTransactionCompilerCompileWithSignaturesAndPubKeyType( + enum TWCoinType coinType, TWData *_Nonnull txInputData, + const struct TWDataVector *_Nonnull signatures, const struct TWDataVector *_Nonnull publicKeys, + enum TWPublicKeyType pubKeyType); + TW_EXTERN_C_END diff --git a/registry.json b/registry.json index eef88ef7fae..14512c99802 100644 --- a/registry.json +++ b/registry.json @@ -315,6 +315,41 @@ "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" } }, + { + "id": "syscoin", + "name": "Syscoin", + "coinId": 57, + "symbol": "SYS", + "decimals": 8, + "blockchain": "Bitcoin", + "derivation": [ + { + "path": "m/84'/57'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 63, + "p2shPrefix": 5, + "hrp": "sys", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://sys1.bcfn.ca", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "19e043f76f6ffc960f5fe93ecec37bc37a58ae7525d7e9cd6ed40f71f0da60eb", + "sampleAccount": "sys1qh3gvhnzq2ch7w8g04x8zksr2mz7r90x7ksmu40" + }, + "info": { + "url": "https://syscoin.org", + "source": "https://github.com/syscoin", + "rpc": "https://sys1.bcfn.ca", + "documentation": "https://docs.syscoin.org" + } + }, { "id": "ethereum", "name": "Ethereum", @@ -364,7 +399,9 @@ "explorer": { "url": "https://blockscout.com/etc/mainnet", "txPath": "/tx/", - "accountPath": "/address/" + "accountPath": "/address/", + "sampleTx": "0x66004165d3901819dc22e568931591d2e4287bda54995f4ce2701a12016f5997", + "sampleAccount": "0x9eab4b0fc468a7f5d46228bf5a76cb52370d068d" }, "info": { "url": "https://ethereumclassic.org", @@ -399,6 +436,108 @@ "documentation": "https://www.icondev.io/docs/icon-json-rpc-v3" } }, + { + "id": "verge", + "name": "Verge", + "coinId": 77, + "symbol": "XVG", + "decimals": 6, + "blockchain": "Verge", + "derivation": [ + { + "path": "m/84'/77'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 30, + "p2shPrefix": 33, + "hrp": "vg", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://verge-blockchain.info", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "8c99979a2b25a46659bff35b238aab1c3158f736f215d99526429c7c96203581", + "sampleAccount": "DFre88gd87bAZQdnS7dbBLwT6GWiGFMQB6" + }, + "info": { + "url": "https://vergecurrency.com", + "source": "https://github.com/vergecurrency/verge", + "rpc": "", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "pivx", + "name": "Pivx", + "coinId": 119, + "symbol": "PIVX", + "decimals": 8, + "blockchain": "Bitcoin", + "derivation": [ + { + "path": "m/44'/119'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 30, + "p2shPrefix": 13, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://pivx.ccore.online", + "txPath": "/transaction/", + "accountPath": "/address/" + }, + "info": { + "url": "https://pivx.org", + "source": "https://github.com/trezor/blockbook", + "rpc": "", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "zen", + "name": "Zen", + "coinId": 121, + "symbol": "ZEN", + "decimals": 8, + "blockchain": "Zen", + "derivation": [ + { + "path": "m/44'/121'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "staticPrefix": 32, + "p2pkhPrefix": 137, + "p2shPrefix": 150, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://explorer.horizen.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "b7f548640766fb024247accf4e01bec37d88d49c4900357edc84d49a09ff4430", + "sampleAccount": "znRchPtvEyJJUwGbCALqyjwHJb1Gx6z4H4j" + }, + "info": { + "url": "https://www.horizen.io", + "source": "https://github.com/trezor/blockbook", + "rpc": "", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, { "id": "aptos", "name": "Aptos", @@ -478,7 +617,9 @@ "explorer": { "url": "https://mintscan.io/cosmos", "txPath": "/txs/", - "accountPath": "/account/" + "accountPath": "/account/", + "sampleTx": "541FA06FB37AC1BF61922143783DD76FECA361830F9876D0342536EE8A87A790", + "sampleAccount": "cosmos1gu6y2a0ffteesyeyeesk23082c6998xyzmt9mz" }, "info": { "url": "https://cosmos.network", @@ -1019,7 +1160,9 @@ "explorer": { "url": "https://blockchair.com/zcash", "txPath": "/transaction/", - "accountPath": "/address/" + "accountPath": "/address/", + "sampleTx": "f2438a93039faf08d39bd3df1f7b5f19a2c29ffe8753127e2956ab4461adab35", + "sampleAccount": "t1Yfrf1dssDLmaMBsq2LFKWPbS5vH3nGpa2" }, "info": { "url": "https://z.cash", @@ -1051,7 +1194,9 @@ "explorer": { "url": "https://explorer.firo.org", "txPath": "/tx/", - "accountPath": "/address/" + "accountPath": "/address/", + "sampleTx": "09a60d58b3d17519a42a8eca60750c33b710ca8f3ca71994192e05c248a2a111", + "sampleAccount": "a8ULhhDgfdSiXJhSZVdhb8EuDc6R3ogsaM" }, "info": { "url": "https://firo.org/", @@ -1060,6 +1205,40 @@ "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" } }, + { + "id": "komodo", + "name": "Komodo", + "coinId": 141, + "symbol": "KMD", + "decimals": 8, + "blockchain": "Zcash", + "derivation": [ + { + "path": "m/44'/141'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 60, + "p2shPrefix": 85, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://kmdexplorer.io/", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "f53bd1a5c0f5dc4b60ba9a1882742ea96faa996e1b870795812a29604dd7829e", + "sampleAccount": "RWvfkt8UjbPWXgeZEcgYmKw2vA1bbAx5t2" + }, + "info": { + "url": "https://komodoplatform.com", + "source": "https://github.com/KomodoPlatform/komodo", + "rpc": "", + "documentation": "https://developers.komodoplatform.com" + } + }, { "id": "ripple", "name": "XRP", @@ -1138,7 +1317,9 @@ "explorer": { "url": "https://blockchair.com/stellar", "txPath": "/transaction/", - "accountPath": "/account/" + "accountPath": "/account/", + "sampleTx": "d9aeabfa9d24df8c5755125f8af243b74cd3ff878656cfa72c566a8824bf6e84", + "sampleAccount": "GCILJZQ3CKBKBUJWW4TAM6Q37LJA5MQX6GMSFSQN75BPLWIZ33OPRG52" }, "info": { "url": "https://stellar.org", @@ -1171,7 +1352,9 @@ "explorer": { "url": "https://explorer.bitcoingold.org/insight", "txPath": "/tx/", - "accountPath": "/address/" + "accountPath": "/address/", + "sampleTx": "2f807d7734de35d2236a1b3d8704eb12954f5f82ea66987949b10e94d9999b23", + "sampleAccount": "GJjz2Du9BoJQ3CPcoyVTHUJZSj62i1693U" }, "info": { "url": "https://bitcoingold.org", @@ -1365,7 +1548,9 @@ "explorer": { "url": "https://explorer.fioprotocol.io", "txPath": "/transaction/", - "accountPath": "/account/" + "accountPath": "/account/", + "sampleTx": "930d1d3cf8988b39b5f64b64e9d61314a3e05a155d9e3505bdf863aab1adddf3", + "sampleAccount": "f5axfpgffiqz" }, "info": { "url": "https://fioprotocol.io", @@ -1616,7 +1801,9 @@ "explorer": { "url": "https://polkadot.subscan.io", "txPath": "/extrinsic/", - "accountPath": "/account/" + "accountPath": "/account/", + "sampleTx": "0xb96f97d8ee508f420e606e1a6dcc74b88844713ddec2bd7cf4e3aa6b1d6beef4", + "sampleAccount": "13hJFqnkqQbmgnGQteGntjMjTdmTBRE8Z93JqxsrpgT7Yjd2" }, "info": { "url": "https://polkadot.network/", @@ -1670,7 +1857,9 @@ "explorer": { "url": "https://explorer.near.org", "txPath": "/transactions/", - "accountPath": "/accounts/" + "accountPath": "/accounts/", + "sampleTx": "FPQAMaVnvFHNwNBJWnTttXfdJhp5FvMGGDJEesB8gvbL", + "sampleAccount": "test-trust.vlad.near" }, "info": { "url": "https://nearprotocol.com", @@ -1783,7 +1972,7 @@ "txPath": "/txs/", "accountPath": "/account/", "sampleTx": "2988DF83FCBFAA38179D583A96734CBD071541D6768221BB23111BC8136D5E6A", - "sampleAccount": "kava1jf9aaj9myrzsnmpdr7twecnaftzmku2mdpy2a7" + "sampleAccount": "kava1xd39avn2f008jmvua0eupg39zsp2xn3wf802vn" }, "info": { "url": "https://kava.io", @@ -2021,12 +2210,43 @@ "sampleAccount": "bnb1u7jm0cll5h3224y0tapwn6gf6pr49ytewx4gsz" }, "info": { - "url": "https://binance.org", - "source": "https://github.com/binance-chain/node-binary", + "url": "https://www.bnbchain.org", + "source": "https://github.com/bnb-chain/node-binary", "rpc": "https://dex.binance.org", "documentation": "https://docs.bnbchain.org/docs/beaconchain/develop/api-reference/dex-api/paths" } }, + { + "id": "tbinance", + "name": "TBinance", + "displayName": "TBNB", + "coinId": 30000714, + "slip44": 714, + "symbol": "BNB", + "decimals": 8, + "blockchain": "Binance", + "derivation": [ + { + "path": "m/44'/714'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "tbnb", + "explorer": { + "url": "https://testnet-explorer.binance.org", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "92E9DA1B6D603667E2DE83C0AC0C1D9E6D65405AD642DA794421C64A82A078D0", + "sampleAccount": "tbnb1c2cxgv3cklswxlvqr9anm6mlp6536qnd36txgr" + }, + "info": { + "url": "https://www.bnbchain.org", + "source": "https://github.com/bnb-chain/node-binary", + "rpc": "https://testnet-dex.binance.org", + "documentation": "https://docs.bnbchain.org/docs/beaconchain/develop/api-reference/dex-api/paths-testnet" + } + }, { "id": "vechain", "name": "VeChain", @@ -2045,7 +2265,9 @@ "explorer": { "url": "https://explore.vechain.org", "txPath": "/transactions/", - "accountPath": "/accounts/" + "accountPath": "/accounts/", + "sampleTx": "0xa424053be0063555aee73a595ca69968c2e4d90d36f280753e503b92b11a655d", + "sampleAccount": "0x8a0a035a33173601bfbec8b6ae7c4a6557a55103" }, "info": { "url": "https://vechain.org", @@ -2129,7 +2351,9 @@ "explorer": { "url": "https://tomoscan.io", "txPath": "/tx/", - "accountPath": "/address/" + "accountPath": "/address/", + "sampleTx": "0x35a8d3ab06c94d5b7d27221b7c9a24ba3f1710dd0fcfd75c5d59b3a885fd709b", + "sampleAccount": "0x86cCbD9bfb371c355202086882bC644A7D0b024B" }, "info": { "url": "https://tomochain.com", @@ -2138,6 +2362,41 @@ "documentation": "https://eth.wiki/json-rpc/API" } }, + { + "id": "bitcoindiamond", + "name": "Bitcoin Diamond", + "coinId": 999, + "symbol": "BCD", + "decimals": 7, + "blockchain": "BitcoinDiamond", + "derivation": [ + { + "path": "m/84'/999'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 0, + "p2shPrefix": 5, + "hrp": "bcd", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "http://explorer.btcd.io/#", + "txPath": "/tx?tx=", + "accountPath": "/address?address=", + "sampleTx": "ec564fe8993ba77f3f5c8b7f6ebb4cbc08e564a54612d6f4584cd1017cf723d4", + "sampleAccount": "1HNTyntGXNhy4WxNzWfffPqp7LHb8bGJ9R" + }, + "info": { + "url": "https://www.bitcoindiamond.org", + "source": "https://github.com/trezor/blockbook", + "rpc": "", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, { "id": "thundertoken", "name": "ThunderCore", @@ -2265,7 +2524,9 @@ "explorer": { "url": "https://tzstats.com", "txPath": "/", - "accountPath": "/" + "accountPath": "/", + "sampleTx": "onk3Z6V4StyfiXTPSHwZFvTKVAaws37cHmZacmULPr3VbVHpKrg", + "sampleAccount": "tz1SiPXX4MYGNJNDsRc7n8hkvUqFzg8xqF9m" }, "info": { "url": "https://tezos.com", @@ -2294,7 +2555,7 @@ "txPath": "/transaction/", "accountPath": "/address/", "sampleTx": "b7a6c5cadab0f64bdc89c77ee4a351463aba5c33f2cef6bbd6542a74a90a3af3", - "sampleAccount": "addr1s3xuxwfetyfe7q9u3rfn6je9stlvcgmj8rezd87qjjegdtxm3y3f2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59jz29g8j" + "sampleAccount": "DdzFFzCqrhstpwKc8WMvPwwBb5oabcTW9zc5ykA37wJR4tYQucvsR9dXb2kEGNXkFJz2PtrpzfRiZkx8R1iNo8NYqdsukVmv7EAybFwC" }, "info": { "url": "https://www.cardano.org", @@ -2434,7 +2695,9 @@ "explorer": { "url": "https://nulscan.io", "txPath": "/transaction/info?hash=", - "accountPath": "/address/info?address=" + "accountPath": "/address/info?address=", + "sampleTx": "303e0e42c28acc37ba952a1effd43daa1caec79928054f7abefb21c32e6fdc02", + "sampleAccount": "NULSd6HgdSjUZy7jKMZfvQ5QU6Z97oufGTGcF" }, "info": { "url": "https://nuls.io", @@ -2498,7 +2761,7 @@ "txPath": "/tx/", "accountPath": "/address/", "sampleTx": "0x180ea96a3218b82b9b35d796823266d8a425c182507adfe5eeffc96e6a14d856", - "sampleAccount": "0xc6D3DBf8dF90BA3f957A9634677805eee0e43bBe" + "sampleAccount": "0x69B492D57bb777e97aa7044D0575228434e2E8B1" }, "info": { "url": "https://wanchain.org", @@ -2710,7 +2973,9 @@ "explorer": { "url": "https://optimistic.etherscan.io", "txPath": "/tx/", - "accountPath": "/address/" + "accountPath": "/address/", + "sampleTx": "0x6fd99288be9bf71eb002bb31da10a4fb0fbbb3c45ae73693b212f49c9db7df8f", + "sampleAccount": "0x1f932361e31d206b4f6b2478123a9d0f8c761031" }, "info": { "url": "https://optimism.io/", @@ -2831,7 +3096,9 @@ "explorer": { "url": "https://arbiscan.io", "txPath": "/tx/", - "accountPath": "/address/" + "accountPath": "/address/", + "sampleTx": "0xa1e319be22c08155e5904aa211fb87df5f4ade48de79c6578b1cf3dfda9e498c", + "sampleAccount": "0xecf9ffa7f51e1194f89c25ad8c484f6bfd04e1ac" }, "info": { "url": "https://arbitrum.io", @@ -3144,7 +3411,9 @@ "explorer": { "url": "https://explorer.bitcoinabc.org", "txPath": "/tx/", - "accountPath": "/address/" + "accountPath": "/address/", + "sampleTx": "6bc767e69cfacffd954c9e5acd178d3140bf00d094b92c6f6052b517500c553b", + "sampleAccount": "ecash:pqnqv9lt7e5vjyp0w88zf2af0l92l8rxdg2jj94l5j" }, "info": { "url": "https://e.cash", @@ -3153,6 +3422,40 @@ "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" } }, + { + "id": "iost", + "name": "IOST", + "coinId": 291, + "symbol": "IOST", + "decimals": 2, + "blockchain": "IOST", + "derivation": [ + { + "path": "m/44'/899'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], + "curve": "ed25519", + "publicKeyType": "ed25519", + "p2pkhPrefix": 0, + "p2shPrefix": 5, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://explorer.iost.io", + "txPath": "/tx/", + "accountPath": "/account/", + "sampleTx": "7dKQzgTkPBNrZqrQ2Bqhqq132CHGPKANFDtzRsjHRCqx", + "sampleAccount": "4av8w81EyzUgHonsVWqfs15WM4Vrpgox4BYYQWhNQDVu" + }, + "info": { + "url": "https://iost.io", + "source": "https://github.com/iost-official/go-iost", + "rpc": "", + "documentation": "https://developers.iost.io/docs/en/6-reference/API.html" + } + }, { "id": "cronos", "name": "Cronos Chain", @@ -3566,6 +3869,75 @@ "documentation": "https://okc-docs.readthedocs.io/en/latest" } }, + { + "id": "stratis", + "name": "Stratis", + "coinId": 105105, + "symbol": "STRAX", + "decimals": 8, + "blockchain": "Bitcoin", + "derivation": [ + { + "path": "m/44'/105105'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "strax", + "p2pkhPrefix": 75, + "p2shPrefix": 140, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://explorer.rutanio.com/strax/explorer", + "txPath": "/transaction/", + "accountPath": "/address/", + "sampleTx": "3923df87e83859dec8b87a414cbb1529113788c512a4d0c283e1394c274f0bfb", + "sampleAccount": "XWqnSWzQA5kDAS727UTYtXkdcbKc8mEvyN" + }, + "info": { + "url": "https://www.stratisplatform.com/", + "source": "https://github.com/stratisproject", + "rpc": "", + "documentation": "https://academy.stratisplatform.com/index.html" + } + }, + { + "id": "Nebl", + "name": "Nebl", + "coinId": 146, + "symbol": "NEBL", + "decimals": 8, + "blockchain": "Verge", + "derivation": [ + { + "path": "m/44'/146'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 53, + "p2shPrefix": 112, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://explorer.nebl.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "1e56c745ab87d702c98eddc6bc2475b2eb292ed4af92d170b2362c8a089272a4", + "sampleAccount": "NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc" + }, + "info": { + "url": "https://nebl.io", + "source": "https://github.com/NeblioTeam", + "rpc": "", + "documentation": "https://github.com/NeblioTeam" + } + }, { "id": "hedera", "name": "Hedera", diff --git a/samples/kmp/shared/build.gradle.kts b/samples/kmp/shared/build.gradle.kts index 6a2d4a3d721..f2d587627a9 100644 --- a/samples/kmp/shared/build.gradle.kts +++ b/samples/kmp/shared/build.gradle.kts @@ -35,7 +35,7 @@ kotlin { sourceSets { val commonMain by getting { dependencies { - implementation("com.trustwallet:wallet-core-kotlin:3.1.38") + implementation("com.trustwallet:wallet-core-kotlin:3.1.39") } } val commonTest by getting { diff --git a/src/Aion/Entry.cpp b/src/Aion/Entry.cpp index 73ce5a91e23..55bbacc128d 100644 --- a/src/Aion/Entry.cpp +++ b/src/Aion/Entry.cpp @@ -8,6 +8,7 @@ #include "Address.h" #include "Signer.h" +#include "proto/TransactionCompiler.pb.h" using namespace TW; using namespace std; @@ -28,4 +29,31 @@ void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::D signTemplate(dataIn, dataOut); } +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + auto preImage = Signer::signaturePreimage(input); + auto preImageHash = Hash::blake2b(preImage, 32); + output.set_data(preImage.data(), preImage.size()); + output.set_data_hash(preImageHash.data(), preImageHash.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() == 0 || publicKeys.size() == 0) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return; + } + if (signatures.size() > 1 || publicKeys.size() > 1) { + output.set_error(Common::Proto::Error_no_support_n2n); + output.set_error_message(Common::Proto::SigningError_Name(Common::Proto::Error_no_support_n2n)); + return; + } + output = Signer::compile(signatures[0], publicKeys[0], input); + }); +} + } // namespace TW::Aion diff --git a/src/Aion/Entry.h b/src/Aion/Entry.h index 7c46b5a06e7..896081c7981 100644 --- a/src/Aion/Entry.h +++ b/src/Aion/Entry.h @@ -17,6 +17,9 @@ class Entry final : public CoinEntry { bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Aion diff --git a/src/Aion/Signer.cpp b/src/Aion/Signer.cpp index 179dc86a898..7cc526196c4 100644 --- a/src/Aion/Signer.cpp +++ b/src/Aion/Signer.cpp @@ -13,17 +13,8 @@ using namespace TW; namespace TW::Aion { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - using boost::multiprecision::uint128_t; - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - auto transaction = Transaction( - /* nonce: */ static_cast(load(input.nonce())), - /* gasPrice: */ static_cast(load(input.gas_price())), - /* gasLimit: */ static_cast(load(input.gas_limit())), - /* to: */ Address(input.to_address()), - /* amount: */ static_cast(load(input.amount())), - /* timestamp */ static_cast(input.timestamp()), - /* payload: */ Data(input.payload().begin(), input.payload().end())); + auto transaction = Signer::buildTransaction(input); Signer::sign(key, transaction); auto output = Proto::SigningOutput(); @@ -46,4 +37,42 @@ void Signer::sign(const PrivateKey& privateKey, Transaction& transaction) noexce transaction.signature = result; } +TW::Data Signer::signaturePreimage(const Proto::SigningInput& input) noexcept { + auto transaction = Signer::buildTransaction(input); + auto encoded = transaction.encode(); + return transaction.encode(); +} + +Proto::SigningOutput Signer::compile(const Data& signature, const PublicKey& publicKey, const Proto::SigningInput& input) noexcept { + auto transaction = Signer::buildTransaction(input); + + // Aion signature = pubKeyBytes + signatureBytes + Data result(publicKey.bytes.begin(), publicKey.bytes.end()); + result.insert(result.end(), signature.begin(), signature.end()); + + transaction.signature = result; + + auto output = Proto::SigningOutput(); + auto encoded = transaction.encode(); + output.set_encoded(encoded.data(), encoded.size()); + output.set_signature(transaction.signature.data(), transaction.signature.size()); + + return output; +} + +Transaction Signer::buildTransaction(const Proto::SigningInput& input) noexcept { + using boost::multiprecision::uint128_t; + + auto transaction = Transaction( + /* nonce: */ static_cast(load(input.nonce())), + /* gasPrice: */ static_cast(load(input.gas_price())), + /* gasLimit: */ static_cast(load(input.gas_limit())), + /* to: */ Address(input.to_address()), + /* amount: */ static_cast(load(input.amount())), + /* timestamp */ static_cast(input.timestamp()), + /* payload: */ Data(input.payload().begin(), input.payload().end())); + + return transaction; +} + } // namespace TW::Aion diff --git a/src/Aion/Signer.h b/src/Aion/Signer.h index 4cf833cb2e3..341150aee73 100644 --- a/src/Aion/Signer.h +++ b/src/Aion/Signer.h @@ -28,6 +28,14 @@ class Signer { /// Signs the given transaction. static void sign(const PrivateKey& privateKey, Transaction& transaction) noexcept; + + /// Get transaction data to be signed + static TW::Data signaturePreimage(const Proto::SigningInput& input) noexcept; + static Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey, const Proto::SigningInput& input) noexcept; + + private: + /// Builds an Aion transaction from the given `Proto::SigningInput`. + static Transaction buildTransaction(const Proto::SigningInput& input) noexcept; }; } // namespace TW::Aion diff --git a/src/Algorand/Entry.cpp b/src/Algorand/Entry.cpp index becd692cf6f..a9afe1a8975 100644 --- a/src/Algorand/Entry.cpp +++ b/src/Algorand/Entry.cpp @@ -8,6 +8,7 @@ #include "Address.h" #include "Signer.h" +#include "proto/TransactionCompiler.pb.h" namespace TW::Algorand { @@ -29,4 +30,30 @@ std::string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& return Signer::signJSON(json, key); } +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + // Algo has no preImageHash + auto preImage = Signer::signaturePreimage(input); + output.set_data(preImage.data(), preImage.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() == 0 || publicKeys.size() == 0) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return; + } + if (signatures.size() > 1 || publicKeys.size() > 1) { + output.set_error(Common::Proto::Error_no_support_n2n); + output.set_error_message(Common::Proto::SigningError_Name(Common::Proto::Error_no_support_n2n)); + return; + } + output = Signer::compile(signatures[0], publicKeys[0], input); + }); +} + } // namespace TW::Algorand diff --git a/src/Algorand/Entry.h b/src/Algorand/Entry.h index 5f60a8e6849..1d54006d86d 100644 --- a/src/Algorand/Entry.h +++ b/src/Algorand/Entry.h @@ -19,6 +19,9 @@ class Entry final : public CoinEntry { void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; bool supportsJSONSigning() const { return true; } std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Algorand diff --git a/src/Algorand/Signer.cpp b/src/Algorand/Signer.cpp index 0362f2e5e27..e849e18df7d 100644 --- a/src/Algorand/Signer.cpp +++ b/src/Algorand/Signer.cpp @@ -22,7 +22,83 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto protoOutput = Proto::SigningOutput(); auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); auto pubkey = key.getPublicKey(TWPublicKeyTypeED25519); - auto from = Address(pubkey); + + auto preImageData = Signer::preImage(pubkey, input); + auto signature = key.sign(preImageData, TWCurveED25519); + return Signer::encodeTransaction(signature, pubkey, input); +} + +std::string Signer::signJSON(const std::string& json, const Data& key) { + auto input = Proto::SigningInput(); + google::protobuf::util::JsonStringToMessage(json, &input); + input.set_private_key(key.data(), key.size()); + return hex(Signer::sign(input).encoded()); +} + +Data Signer::sign(const PrivateKey& privateKey, const BaseTransaction& transaction) noexcept { + Data data; + append(data, TRANSACTION_TAG); + append(data, transaction.serialize()); + auto signature = privateKey.sign(data, TWCurveED25519); + return {signature.begin(), signature.end()}; +} + +TW::Data Signer::signaturePreimage(const Proto::SigningInput& input) noexcept { + auto pubKey = input.public_key(); + return Signer::preImage(PublicKey(Data(pubKey.begin(), pubKey.end()), TWPublicKeyTypeED25519), input); +} + +Proto::SigningOutput Signer::compile(const Data& signature, const PublicKey& publicKey, const Proto::SigningInput& input) noexcept { + return Signer::encodeTransaction(signature, publicKey, input); +} + +TW::Data Signer::preImage(const TW::PublicKey& pubKey, const Proto::SigningInput& input) noexcept { + auto from = Address(pubKey); + auto firstRound = input.first_round(); + auto lastRound = input.last_round(); + auto fee = input.fee(); + + auto note = Data(input.note().begin(), input.note().end()); + auto genesisId = input.genesis_id(); + auto genesisHash = Data(input.genesis_hash().begin(), input.genesis_hash().end()); + + TW::Data transactionData; + if (input.has_transfer()) { + auto message = input.transfer(); + auto to = Address(message.to_address()); + + auto transaction = Transfer(from, to, fee, message.amount(), firstRound, + lastRound, note, TRANSACTION_PAY, genesisId, genesisHash); + transactionData = transaction.serialize(); + } else if (input.has_asset_transfer()) { + auto message = input.asset_transfer(); + auto to = Address(message.to_address()); + + auto transaction = + AssetTransfer(from, to, fee, message.amount(), + message.asset_id(), firstRound, lastRound, note, + ASSET_TRANSACTION, genesisId, genesisHash); + transactionData = transaction.serialize(); + } else if (input.has_asset_opt_in()) { + auto message = input.asset_opt_in(); + auto transaction = OptInAssetTransaction(from, fee, message.asset_id(), + firstRound, lastRound, note, + ASSET_TRANSACTION, genesisId, genesisHash); + transactionData = transaction.serialize(); + } else { + return {Data{}}; + } + + Data data; + append(data, TRANSACTION_TAG); + append(data, transactionData); + return data; +} + +Proto::SigningOutput Signer::encodeTransaction(const Data& signature, const TW::PublicKey& pubKey, const Proto::SigningInput& input) noexcept { + auto protoOutput = Proto::SigningOutput(); + + auto from = Address(pubKey); auto firstRound = input.first_round(); auto lastRound = input.last_round(); auto fee = input.fee(); @@ -36,7 +112,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto transaction = Transfer(from, to, fee, message.amount(), firstRound, lastRound, note, TRANSACTION_PAY, genesisId, genesisHash); - auto signature = sign(key, transaction); + auto serialized = transaction.BaseTransaction::serialize(signature); protoOutput.set_encoded(serialized.data(), serialized.size()); protoOutput.set_signature(Base64::encode(signature)); @@ -48,7 +124,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { AssetTransfer(from, to, fee, message.amount(), message.asset_id(), firstRound, lastRound, note, ASSET_TRANSACTION, genesisId, genesisHash); - auto signature = sign(key, transaction); + auto serialized = transaction.BaseTransaction::serialize(signature); protoOutput.set_encoded(serialized.data(), serialized.size()); protoOutput.set_signature(Base64::encode(signature)); @@ -58,28 +134,12 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto transaction = OptInAssetTransaction(from, fee, message.asset_id(), firstRound, lastRound, note, ASSET_TRANSACTION, genesisId, genesisHash); - auto signature = sign(key, transaction); + auto serialized = transaction.BaseTransaction::serialize(signature); protoOutput.set_encoded(serialized.data(), serialized.size()); protoOutput.set_signature(Base64::encode(signature)); } - return protoOutput; } -std::string Signer::signJSON(const std::string& json, const Data& key) { - auto input = Proto::SigningInput(); - google::protobuf::util::JsonStringToMessage(json, &input); - input.set_private_key(key.data(), key.size()); - return hex(Signer::sign(input).encoded()); -} - -Data Signer::sign(const PrivateKey& privateKey, const BaseTransaction& transaction) noexcept { - Data data; - append(data, TRANSACTION_TAG); - append(data, transaction.serialize()); - auto signature = privateKey.sign(data, TWCurveED25519); - return {signature.begin(), signature.end()}; -} - } // namespace TW::Algorand diff --git a/src/Algorand/Signer.h b/src/Algorand/Signer.h index c25a4487835..ba41661170f 100644 --- a/src/Algorand/Signer.h +++ b/src/Algorand/Signer.h @@ -7,6 +7,7 @@ #pragma once #include "AssetTransfer.h" +#include "proto/Common.pb.h" #include "OptInAssetTransaction.h" #include "Transfer.h" @@ -28,6 +29,14 @@ class Signer { /// Signs the given transaction. static Data sign(const PrivateKey& privateKey, const BaseTransaction& transaction) noexcept; + + /// Get transaction data to be signed + static TW::Data signaturePreimage(const Proto::SigningInput& input) noexcept; + static Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey, const Proto::SigningInput& input) noexcept; + + private: + static TW::Data preImage(const TW::PublicKey& pubKey, const Proto::SigningInput& input) noexcept; + static Proto::SigningOutput encodeTransaction(const Data& signature, const TW::PublicKey& pubKey, const Proto::SigningInput& input) noexcept; }; } // namespace TW::Algorand diff --git a/src/Binance/Address.cpp b/src/Binance/Address.cpp index 1230b2362a4..00d8ab95dea 100644 --- a/src/Binance/Address.cpp +++ b/src/Binance/Address.cpp @@ -6,28 +6,41 @@ // file LICENSE at the root of the source code distribution tree. #include "Address.h" +#include "Coin.h" #include #include namespace TW::Binance { -const std::string Address::_hrp = HRP_BINANCE; const std::string Address::hrpValidator = "bva"; -const std::vector validHrps = {Address::_hrp, Address::hrpValidator, "bnbp", "bvap", "bca", "bcap"}; -bool Address::isValid(const std::string& addr) { - Address addrNotUsed; +Address::Address(TWCoinType coin): + Bech32Address(stringForHRP(TW::hrp(coin))) { +} + +Address::Address(const Data& keyHash, TWCoinType coin): + Bech32Address(stringForHRP(TW::hrp(coin)), keyHash) { +} + +Address::Address(const PublicKey& publicKey, TWCoinType coin): + Bech32Address(stringForHRP(TW::hrp(coin)), Hash::HasherSha256ripemd, publicKey) { +} + +bool Address::isValid(TWCoinType coin, const std::string& addr) { + const auto* const hrp = stringForHRP(TW::hrp(coin)); + Address addrNotUsed(hrp); return decode(addr, addrNotUsed); } bool Address::isValid(const std::string& addr, const std::string& hrp) { - Address addrNotUsed; - return Bech32Address::decode(addr, addrNotUsed, hrp); + Address addrNotUsed(hrp); + return decode(addr, addrNotUsed); } bool Address::decode(const std::string& addr, Address& obj_out) { - for (const auto& hrp : validHrps) { + std::vector validHrps = {obj_out.getHrp(), Address::hrpValidator, "bnbp", "bvap", "bca", "bcap"}; + for (const auto& hrp: validHrps) { if (Bech32Address::decode(addr, obj_out, hrp)) { return true; } diff --git a/src/Binance/Address.h b/src/Binance/Address.h index ca25df2089c..ac2a0616e90 100644 --- a/src/Binance/Address.h +++ b/src/Binance/Address.h @@ -8,6 +8,7 @@ #include "../Bech32Address.h" +#include #include namespace TW::Binance { @@ -15,20 +16,28 @@ namespace TW::Binance { /// Binance address is a Bech32Address, with "bnb" prefix and sha256ripemd hash class Address: public Bech32Address { public: - static const std::string _hrp; // HRP_BINANCE - static const std::string hrpValidator; // HRP_BINANCE + static const std::string hrpValidator; - static bool isValid(const std::string& addr); - static bool isValid(const std::string& addr, const std::string& hrp); + /// Checks if the given `addr` is a valid Binance address and has a known hrp. + static bool isValid(TWCoinType coin, const std::string& addr); + /// Checks if the given `addr` is a valid Binance address and has the given `chainHrp`. + static bool isValid(const std::string& addr, const std::string& chainHrp); - Address() : Bech32Address(_hrp) {} + explicit Address(TWCoinType coin = TWCoinTypeBinance); - /// Initializes an address with a key hash. - Address(const Data& keyHash) : Bech32Address(_hrp, keyHash) {} + explicit Address(const std::string& chainHrp) : Bech32Address(chainHrp) {} - /// Initializes an address with a public key. - Address(const PublicKey& publicKey) : Bech32Address(_hrp, Hash::HasherSha256ripemd, publicKey) {} - Address(const PublicKey& publicKey, const std::string hrp) : Bech32Address(hrp, Hash::HasherSha256ripemd, publicKey) {} + /// Initializes an address with a key hash and hrp. + explicit Address(const Data& keyHash, const std::string& chainHrp) : Bech32Address(chainHrp, keyHash) {} + + /// Initializes an address with a key hash and coin type. + explicit Address(const Data& keyHash, TWCoinType coin = TWCoinTypeBinance); + + /// Initializes an address with a public key and hrp. + explicit Address(const PublicKey& publicKey, const std::string& chainHrp) : Bech32Address(chainHrp, Hash::HasherSha256ripemd, publicKey) {} + + /// Initializes an address with a public key and coin type. + explicit Address(const PublicKey& publicKey, TWCoinType coin = TWCoinTypeBinance); static bool decode(const std::string& addr, Address& obj_out); }; diff --git a/src/Binance/Entry.cpp b/src/Binance/Entry.cpp index e2c22118e2c..75ed2a72e9e 100644 --- a/src/Binance/Entry.cpp +++ b/src/Binance/Entry.cpp @@ -8,46 +8,53 @@ #include "../proto/TransactionCompiler.pb.h" #include "Address.h" +#include "Coin.h" #include "Signer.h" namespace TW::Binance { -bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { +bool Entry::validateAddress(TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { if (std::holds_alternative(addressPrefix)) { - if (const auto hrp = std::get(addressPrefix); hrp) { + if (const auto* hrp = std::get(addressPrefix); hrp) { return Address::isValid(address, hrp); } } - - // Use the default validation, which handles a specific set of valid HRPs. - return Address::isValid(address); + return Address::isValid(coin, address); } -std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { +std::string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, const PrefixVariant& addressPrefix) const { const std::string hrp = getFromPrefixHrpOrDefault(addressPrefix, coin); return Address(publicKey, hrp).string(); } -Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { - Address addr; - if (!Address::decode(address, addr)) { - return {}; +Data Entry::addressToData(TWCoinType coin, const std::string& address) const { + const char* hrp = stringForHRP(TW::hrp(coin)); + Address addr(hrp); + if (Address::decode(address, addr)) { + return addr.getKeyHash(); } - return addr.getKeyHash(); + return {}; } -void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - signTemplate(dataIn, dataOut); +void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + auto input = Proto::SigningInput(); + input.ParseFromArray(dataIn.data(), (int)dataIn.size()); + const char* hrp = stringForHRP(TW::hrp(coin)); + auto serializedOut = Signer::sign(input, hrp).SerializeAsString(); + dataOut.insert(dataOut.end(), serializedOut.begin(), serializedOut.end()); } -std::string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { - return Signer::signJSON(json, key); +std::string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { + const char* hrp = stringForHRP(TW::hrp(coin)); + return Signer::signJSON(json, key, hrp); } Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + const char* hrp = stringForHRP(TW::hrp(coin)); + return txCompilerTemplate( - txInputData, [](const auto& input, auto& output) { - Signer signer(input); + txInputData, [hrp](const auto& input, auto& output) { + Signer signer(input, hrp); auto preImageHash = signer.preImageHash(); auto preImage = signer.signaturePreimage(); @@ -57,23 +64,16 @@ Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInput } void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { - dataOut = txCompilerTemplate( - txInputData, [&](const auto& input, auto& output) { - if (signatures.size() == 0 || publicKeys.size() == 0) { - output.set_error(Common::Proto::Error_invalid_params); - output.set_error_message("empty signatures or publickeys"); - return; - } - if (signatures.size() > 1 || publicKeys.size() > 1) { - output.set_error(Common::Proto::Error_no_support_n2n); - output.set_error_message(Common::Proto::SigningError_Name(Common::Proto::Error_no_support_n2n)); - return; - } - output = Signer(input).compile(signatures[0], publicKeys[0]); + const char* hrp = stringForHRP(TW::hrp(coin)); + + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [hrp](const auto& input, auto& output, const auto& signature, const auto& publicKey) { + output = Signer(input, hrp).compile(signature, publicKey); }); } -Data Entry::buildTransactionInput([[maybe_unused]] TWCoinType coinType, const std::string& from, const std::string& to, const uint256_t& amount, const std::string& asset, const std::string& memo, const std::string& chainId) const { +Data Entry::buildTransactionInput(TWCoinType coinType, const std::string& from, const std::string& to, const uint256_t& amount, const std::string& asset, const std::string& memo, const std::string& chainId) const { auto input = Proto::SigningInput(); input.set_chain_id(chainId); input.set_account_number(0); @@ -85,16 +85,22 @@ Data Entry::buildTransactionInput([[maybe_unused]] TWCoinType coinType, const st auto& order = *input.mutable_send_order(); - Address fromAddress; + const char* hrp = stringForHRP(TW::hrp(coinType)); + + Data fromKeyhash; + Data toKeyhash; + + Address fromAddress(hrp); if (!Address::decode(from, fromAddress)) { throw std::invalid_argument("Invalid from address"); } - const auto fromKeyhash = fromAddress.getKeyHash(); - Address toAddress; + fromKeyhash = fromAddress.getKeyHash(); + + Address toAddress(hrp); if (!Address::decode(to, toAddress)) { throw std::invalid_argument("Invalid to address"); } - const auto toKeyhash = toAddress.getKeyHash(); + toKeyhash = toAddress.getKeyHash(); { auto* sendOrderInputs = order.add_inputs(); diff --git a/src/Binance/Serialization.cpp b/src/Binance/Serialization.cpp index 580eb6970ac..2ceeaca9df7 100644 --- a/src/Binance/Serialization.cpp +++ b/src/Binance/Serialization.cpp @@ -15,9 +15,9 @@ namespace TW::Binance { using json = nlohmann::json; -static inline std::string addressString(const std::string& bytes) { +static inline std::string addressString(const std::string& bytes, const std::string& chainHrp) { auto data = Data(bytes.begin(), bytes.end()); - return Address(data).string(); + return Address(data, chainHrp).string(); } static inline std::string validatorAddress(const std::string& bytes) { @@ -25,47 +25,47 @@ static inline std::string validatorAddress(const std::string& bytes) { return Bech32Address(Address::hrpValidator, data).string(); } -json signatureJSON(const Proto::SigningInput& input) { +json signatureJSON(const Proto::SigningInput& input, const std::string& chainHrp) { json j; j["account_number"] = std::to_string(input.account_number()); j["chain_id"] = input.chain_id(); j["data"] = nullptr; j["memo"] = input.memo(); - j["msgs"] = json::array({orderJSON(input)}); + j["msgs"] = json::array({orderJSON(input, chainHrp)}); j["sequence"] = std::to_string(input.sequence()); j["source"] = std::to_string(input.source()); return j; } -json orderJSON(const Proto::SigningInput& input) { +json orderJSON(const Proto::SigningInput& input, const std::string& chainHrp) { json j; if (input.has_trade_order()) { j["id"] = input.trade_order().id(); j["ordertype"] = 2; j["price"] = input.trade_order().price(); j["quantity"] = input.trade_order().quantity(); - j["sender"] = addressString(input.trade_order().sender()); + j["sender"] = addressString(input.trade_order().sender(), chainHrp); j["side"] = input.trade_order().side(); j["symbol"] = input.trade_order().symbol(); j["timeinforce"] = input.trade_order().timeinforce(); } else if (input.has_cancel_trade_order()) { j["refid"] = input.cancel_trade_order().refid(); - j["sender"] = addressString(input.cancel_trade_order().sender()); + j["sender"] = addressString(input.cancel_trade_order().sender(), chainHrp); j["symbol"] = input.cancel_trade_order().symbol(); } else if (input.has_send_order()) { - j["inputs"] = inputsJSON(input.send_order()); - j["outputs"] = outputsJSON(input.send_order()); + j["inputs"] = inputsJSON(input.send_order(), chainHrp); + j["outputs"] = outputsJSON(input.send_order(), chainHrp); } else if (input.has_freeze_order()) { - j["from"] = addressString(input.freeze_order().from()); + j["from"] = addressString(input.freeze_order().from(), chainHrp); j["symbol"] = input.freeze_order().symbol(); j["amount"] = input.freeze_order().amount(); } else if (input.has_unfreeze_order()) { - j["from"] = addressString(input.unfreeze_order().from()); + j["from"] = addressString(input.unfreeze_order().from(), chainHrp); j["symbol"] = input.unfreeze_order().symbol(); j["amount"] = input.unfreeze_order().amount(); } else if (input.has_htlt_order()) { - j["from"] = addressString(input.htlt_order().from()); - j["to"] = addressString(input.htlt_order().to()); + j["from"] = addressString(input.htlt_order().from(), chainHrp); + j["to"] = addressString(input.htlt_order().to(), chainHrp); j["recipient_other_chain"] = input.htlt_order().recipient_other_chain(); j["sender_other_chain"] = input.htlt_order().sender_other_chain(); j["random_number_hash"] = hex(input.htlt_order().random_number_hash()); @@ -75,35 +75,49 @@ json orderJSON(const Proto::SigningInput& input) { j["height_span"] = input.htlt_order().height_span(); j["cross_chain"] = input.htlt_order().cross_chain(); } else if (input.has_deposithtlt_order()) { - j["from"] = addressString(input.deposithtlt_order().from()); + j["from"] = addressString(input.deposithtlt_order().from(), chainHrp); j["swap_id"] = hex(input.deposithtlt_order().swap_id()); j["amount"] = tokensJSON(input.deposithtlt_order().amount()); } else if (input.has_claimhtlt_order()) { - j["from"] = addressString(input.claimhtlt_order().from()); + j["from"] = addressString(input.claimhtlt_order().from(), chainHrp); j["swap_id"] = hex(input.claimhtlt_order().swap_id()); j["random_number"] = hex(input.claimhtlt_order().random_number()); } else if (input.has_refundhtlt_order()) { - j["from"] = addressString(input.refundhtlt_order().from()); + j["from"] = addressString(input.refundhtlt_order().from(), chainHrp); j["swap_id"] = hex(input.refundhtlt_order().swap_id()); + } else if (input.has_issue_order()) { + j["from"] = addressString(input.issue_order().from(), chainHrp); + j["total_supply"] = input.issue_order().total_supply(); + j["name"] = input.issue_order().name(); + j["symbol"] = input.issue_order().symbol(); + j["mintable"] = input.issue_order().mintable(); + } else if (input.has_mint_order()) { + j["from"] = addressString(input.mint_order().from(), chainHrp); + j["amount"] = input.mint_order().amount(); + j["symbol"] = input.mint_order().symbol(); + } else if (input.has_burn_order()) { + j["from"] = addressString(input.burn_order().from(), chainHrp); + j["amount"] = input.burn_order().amount(); + j["symbol"] = input.burn_order().symbol(); } else if (input.has_transfer_out_order()) { auto to = input.transfer_out_order().to(); auto addr = Ethereum::Address(Data(to.begin(), to.end())); - j["from"] = addressString(input.transfer_out_order().from()); + j["from"] = addressString(input.transfer_out_order().from(), chainHrp); j["to"] = addr.string(); j["amount"] = tokenJSON(input.transfer_out_order().amount()); j["expire_time"] = input.transfer_out_order().expire_time(); } else if (input.has_side_delegate_order()) { j["type"] = "cosmos-sdk/MsgSideChainDelegate"; j["value"] = { - {"delegator_addr", addressString(input.side_delegate_order().delegator_addr())}, - {"validator_addr", validatorAddress(input.side_delegate_order().validator_addr())}, + {"delegator_addr", addressString(input.side_delegate_order().delegator_addr(), chainHrp)}, + {"validator_addr",validatorAddress(input.side_delegate_order().validator_addr())}, {"delegation", tokenJSON(input.side_delegate_order().delegation(), true)}, {"side_chain_id", input.side_delegate_order().chain_id()}, }; } else if (input.has_side_redelegate_order()) { j["type"] = "cosmos-sdk/MsgSideChainRedelegate"; j["value"] = { - {"delegator_addr", addressString(input.side_redelegate_order().delegator_addr())}, + {"delegator_addr", addressString(input.side_redelegate_order().delegator_addr(), chainHrp)}, {"validator_src_addr", validatorAddress(input.side_redelegate_order().validator_src_addr())}, {"validator_dst_addr", validatorAddress(input.side_redelegate_order().validator_dst_addr())}, {"amount", tokenJSON(input.side_redelegate_order().amount(), true)}, @@ -112,19 +126,19 @@ json orderJSON(const Proto::SigningInput& input) { } else if (input.has_side_undelegate_order()) { j["type"] = "cosmos-sdk/MsgSideChainUndelegate"; j["value"] = { - {"delegator_addr", addressString(input.side_undelegate_order().delegator_addr())}, + {"delegator_addr", addressString(input.side_undelegate_order().delegator_addr(), chainHrp)}, {"validator_addr", validatorAddress(input.side_undelegate_order().validator_addr())}, {"amount", tokenJSON(input.side_undelegate_order().amount(), true)}, {"side_chain_id", input.side_undelegate_order().chain_id()}, }; } else if (input.has_time_lock_order()) { - j["from"] = addressString(input.time_lock_order().from_address()); + j["from"] = addressString(input.time_lock_order().from_address(), chainHrp); j["description"] = input.time_lock_order().description(); j["amount"] = tokensJSON(input.time_lock_order().amount()); j["lock_time"] = input.time_lock_order().lock_time(); } else if (input.has_time_relock_order()) { const auto amount = input.time_relock_order().amount(); - j["from"] = addressString(input.time_relock_order().from_address()); + j["from"] = addressString(input.time_relock_order().from_address(), chainHrp); j["time_lock_id"] = input.time_relock_order().id(); j["description"] = input.time_relock_order().description(); // if amount is empty or omitted, set null to avoid signature verification error @@ -134,26 +148,31 @@ json orderJSON(const Proto::SigningInput& input) { } j["lock_time"] = input.time_relock_order().lock_time(); } else if (input.has_time_unlock_order()) { - j["from"] = addressString(input.time_unlock_order().from_address()); + j["from"] = addressString(input.time_unlock_order().from_address(), chainHrp); j["time_lock_id"] = input.time_unlock_order().id(); } + return j; } -json inputsJSON(const Proto::SendOrder& order) { +json inputsJSON(const Proto::SendOrder& order, const std::string& chainHrp) { json j = json::array(); for (auto& input : order.inputs()) { - j.push_back({{"address", addressString(input.address())}, - {"coins", tokensJSON(input.coins())}}); + j.push_back({ + {"address", addressString(input.address(), chainHrp)}, + {"coins", tokensJSON(input.coins())} + }); } return j; } -json outputsJSON(const Proto::SendOrder& order) { +json outputsJSON(const Proto::SendOrder& order, const std::string& chainHrp) { json j = json::array(); for (auto& output : order.outputs()) { - j.push_back({{"address", addressString(output.address())}, - {"coins", tokensJSON(output.coins())}}); + j.push_back({ + {"address", addressString(output.address(), chainHrp)}, + {"coins", tokensJSON(output.coins())} + }); } return j; } diff --git a/src/Binance/Serialization.h b/src/Binance/Serialization.h index 35ed5b32d63..a49046eb035 100644 --- a/src/Binance/Serialization.h +++ b/src/Binance/Serialization.h @@ -11,10 +11,10 @@ namespace TW::Binance { -nlohmann::json signatureJSON(const Proto::SigningInput& input); -nlohmann::json orderJSON(const Proto::SigningInput& input); -nlohmann::json inputsJSON(const Proto::SendOrder& order); -nlohmann::json outputsJSON(const Proto::SendOrder& order); +nlohmann::json signatureJSON(const Proto::SigningInput& input, const std::string& chainHrp); +nlohmann::json orderJSON(const Proto::SigningInput& input, const std::string& chainHrp); +nlohmann::json inputsJSON(const Proto::SendOrder& order, const std::string& chainHrp); +nlohmann::json outputsJSON(const Proto::SendOrder& order, const std::string& chainHrp); nlohmann::json tokenJSON(const Proto::SendOrder_Token& token, bool stringAmount = false); nlohmann::json tokensJSON(const ::google::protobuf::RepeatedPtrField& tokens); diff --git a/src/Binance/Signer.cpp b/src/Binance/Signer.cpp index 2f04e18e4e3..3b0856428d3 100644 --- a/src/Binance/Signer.cpp +++ b/src/Binance/Signer.cpp @@ -8,6 +8,9 @@ #include "Serialization.h" #include "../HexCoding.h" #include "../PrivateKey.h" +#include "../Coin.h" + +#include #include #include @@ -41,22 +44,27 @@ static const auto timeLockOrderPrefix = Data{0x07, 0x92, 0x15, 0x31}; static const auto timeRelockOrderPrefix = Data{0x50, 0x47, 0x11, 0xDA}; static const auto timeUnlockOrderPrefix = Data{0xC4, 0x05, 0x0C, 0x6C}; -Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - auto signer = Signer(input); +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input, const std::string& chainHrp) noexcept { + auto signer = Signer(input, chainHrp); auto encoded = signer.build(); auto output = Proto::SigningOutput(); output.set_encoded(encoded.data(), encoded.size()); return output; } -std::string Signer::signJSON(const std::string& json, const Data& key) { +std::string Signer::signJSON(const std::string& json, const Data& key, const std::string& chainHrp) { auto input = Proto::SigningInput(); google::protobuf::util::JsonStringToMessage(json, &input); input.set_private_key(key.data(), key.size()); - auto output = Signer::sign(input); + auto output = Signer::sign(input, chainHrp); return hex(output.encoded()); } +Signer::Signer(Proto::SigningInput input, TWCoinType coin): + input(std::move(input)), + chainHrp(stringForHRP(TW::hrp(coin))) { +} + Data Signer::build() const { auto signature = encodeSignature(sign()); return encodeTransaction(signature); @@ -92,7 +100,7 @@ Proto::SigningOutput Signer::compile(const Data& signature, const PublicKey& pub } std::string Signer::signaturePreimage() const { - auto json = signatureJSON(input); + auto json = signatureJSON(input, this->chainHrp); return json.dump(); } diff --git a/src/Binance/Signer.h b/src/Binance/Signer.h index 0b35b4c36a3..88d462f1f6f 100644 --- a/src/Binance/Signer.h +++ b/src/Binance/Signer.h @@ -10,6 +10,7 @@ #include "PublicKey.h" #include "../proto/Binance.pb.h" +#include #include #include @@ -19,14 +20,21 @@ namespace TW::Binance { class Signer { public: /// Signs a Proto::SigningInput transaction - static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; + static Proto::SigningOutput sign(const Proto::SigningInput& input, const std::string& chainHrp) noexcept; /// Signs a json Proto::SigningInput with private key - static std::string signJSON(const std::string& json, const Data& key); + static std::string signJSON(const std::string& json, const Data& key, const std::string& chainHrp); public: Proto::SigningInput input; + std::string chainHrp; /// Initializes a transaction signer. - explicit Signer(Proto::SigningInput input) : input(std::move(input)) {} + explicit Signer(Proto::SigningInput input, std::string chainHrp): + input(std::move(input)), + chainHrp(std::move(chainHrp)) { + } + + /// Initializes a transaction signer. + explicit Signer(Proto::SigningInput input, TWCoinType coin = TWCoinTypeBinance); /// Builds a signed transaction. /// diff --git a/src/Bitcoin/Entry.cpp b/src/Bitcoin/Entry.cpp index 94734c3f7be..27473c893fc 100644 --- a/src/Bitcoin/Entry.cpp +++ b/src/Bitcoin/Entry.cpp @@ -27,6 +27,8 @@ bool Entry::validateAddress(TWCoinType coin, const std::string& address, const P case TWCoinTypeQtum: case TWCoinTypeViacoin: case TWCoinTypeBitcoinGold: + case TWCoinTypeSyscoin: + case TWCoinTypeStratis: return isValidBase58 || isValidHrp; case TWCoinTypeBitcoinCash: return base58Prefix ? isValidBase58 : BitcoinCashAddress::isValid(address); @@ -34,6 +36,7 @@ bool Entry::validateAddress(TWCoinType coin, const std::string& address, const P return base58Prefix ? isValidBase58 : ECashAddress::isValid(address); case TWCoinTypeDash: case TWCoinTypeDogecoin: + case TWCoinTypePivx: case TWCoinTypeRavencoin: case TWCoinTypeFiro: default: @@ -70,6 +73,11 @@ std::string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW switch (coin) { case TWCoinTypeBitcoin: case TWCoinTypeLitecoin: + case TWCoinTypeDigiByte: + case TWCoinTypeViacoin: + case TWCoinTypeBitcoinGold: + case TWCoinTypeSyscoin: + case TWCoinTypeStratis: switch (derivation) { case TWDerivationBitcoinLegacy: case TWDerivationLitecoinLegacy: @@ -84,11 +92,6 @@ std::string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW return SegwitAddress(publicKey, hrp).string(); } - case TWCoinTypeDigiByte: - case TWCoinTypeViacoin: - case TWCoinTypeBitcoinGold: - return SegwitAddress(publicKey, hrp).string(); - case TWCoinTypeBitcoinCash: return BitcoinCashAddress(publicKey).string(); @@ -98,6 +101,7 @@ std::string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW case TWCoinTypeDash: case TWCoinTypeDogecoin: case TWCoinTypeMonacoin: + case TWCoinTypePivx: case TWCoinTypeQtum: case TWCoinTypeRavencoin: case TWCoinTypeFiro: @@ -113,37 +117,24 @@ inline Data cashAddressToData(const CashAddress&& addr) { Data Entry::addressToData(TWCoinType coin, const std::string& address) const { switch (coin) { - case TWCoinTypeBitcoin: - case TWCoinTypeBitcoinGold: - case TWCoinTypeDigiByte: - case TWCoinTypeGroestlcoin: - case TWCoinTypeLitecoin: - case TWCoinTypeViacoin: { - const auto decoded = SegwitAddress::decode(address); - if (!std::get<2>(decoded)) { - return {}; - } - return std::get<0>(decoded).witnessProgram; - } - case TWCoinTypeBitcoinCash: return cashAddressToData(BitcoinCashAddress(address)); case TWCoinTypeECash: return cashAddressToData(ECashAddress(address)); - case TWCoinTypeDash: - case TWCoinTypeDogecoin: - case TWCoinTypeMonacoin: - case TWCoinTypeQtum: - case TWCoinTypeRavencoin: - case TWCoinTypeFiro: { - const auto addr = Address(address); - return {addr.bytes.begin() + 1, addr.bytes.end()}; + default: { + const auto decoded = SegwitAddress::decode(address); + if (!std::get<2>(decoded)) { + // check if it is a legacy address + if (Address::isValid(address)) { + const auto addr = Address(address); + return {addr.bytes.begin() + 1, addr.bytes.end()}; + } + return {Data()}; + } + return std::get<0>(decoded).witnessProgram; } - - default: - return {}; } } diff --git a/src/Bitcoin/FeeCalculator.cpp b/src/Bitcoin/FeeCalculator.cpp index 8b0c6eaa191..bc8b023131d 100644 --- a/src/Bitcoin/FeeCalculator.cpp +++ b/src/Bitcoin/FeeCalculator.cpp @@ -29,18 +29,35 @@ int64_t LinearFeeCalculator::calculateSingleInput(int64_t byteFee) const noexcep } class DecredFeeCalculator : public LinearFeeCalculator { +private: + bool disableDustFilter = false; + public: - constexpr DecredFeeCalculator() noexcept - : LinearFeeCalculator(gDecredBytesPerInput, gDecredBytesPerOutput, gDecredBytesBase) {} + constexpr DecredFeeCalculator(bool disableFilter = false) noexcept + : LinearFeeCalculator(gDecredBytesPerInput, gDecredBytesPerOutput, gDecredBytesBase) + , disableDustFilter(disableFilter) {} + + int64_t calculateSingleInput(int64_t byteFee) const noexcept override { + if (disableDustFilter) { + return 0; + } + return LinearFeeCalculator::calculateSingleInput(byteFee); + } }; static constexpr DefaultFeeCalculator defaultFeeCalculator{}; +static constexpr DefaultFeeCalculator defaultFeeCalculatorNoDustFilter(true); static constexpr DecredFeeCalculator decredFeeCalculator{}; +static constexpr DecredFeeCalculator decredFeeCalculatorNoDustFilter(true); static constexpr SegwitFeeCalculator segwitFeeCalculator{}; +static constexpr SegwitFeeCalculator segwitFeeCalculatorNoDustFilter(true); -const FeeCalculator& getFeeCalculator(TWCoinType coinType) noexcept { +const FeeCalculator& getFeeCalculator(TWCoinType coinType, bool disableFilter) noexcept { switch (coinType) { case TWCoinTypeDecred: + if (disableFilter) { + return decredFeeCalculatorNoDustFilter; + } return decredFeeCalculator; case TWCoinTypeBitcoin: @@ -49,9 +66,17 @@ const FeeCalculator& getFeeCalculator(TWCoinType coinType) noexcept { case TWCoinTypeLitecoin: case TWCoinTypeViacoin: case TWCoinTypeGroestlcoin: + case TWCoinTypeSyscoin: + case TWCoinTypeStratis: + if (disableFilter) { + return segwitFeeCalculatorNoDustFilter; + } return segwitFeeCalculator; default: + if (disableFilter) { + return defaultFeeCalculatorNoDustFilter; + } return defaultFeeCalculator; } } diff --git a/src/Bitcoin/FeeCalculator.h b/src/Bitcoin/FeeCalculator.h index 229964ebc1a..cbfa0d5d00d 100644 --- a/src/Bitcoin/FeeCalculator.h +++ b/src/Bitcoin/FeeCalculator.h @@ -56,19 +56,41 @@ class ConstantFeeCalculator : public FeeCalculator { /// Default Bitcoin transaction fee calculator, non-segwit. class DefaultFeeCalculator : public LinearFeeCalculator { +private: + bool disableDustFilter = false; + public: - constexpr DefaultFeeCalculator() noexcept - : LinearFeeCalculator(gDefaultBytesPerInput, gDefaultBytesPerOutput, gDefaultBytesBase) {} + constexpr DefaultFeeCalculator(bool disableFilter = false) noexcept + : LinearFeeCalculator(gDefaultBytesPerInput, gDefaultBytesPerOutput, gDefaultBytesBase) + , disableDustFilter(disableFilter) {} + + [[nodiscard]] int64_t calculateSingleInput(int64_t byteFee) const noexcept override { + if (disableDustFilter) { + return 0; + } + return LinearFeeCalculator::calculateSingleInput(byteFee); + } }; /// Bitcoin Segwit transaction fee calculator class SegwitFeeCalculator : public LinearFeeCalculator { +private: + bool disableDustFilter = false; + public: - constexpr SegwitFeeCalculator() noexcept - : LinearFeeCalculator(gSegwitBytesPerInput, gSegwitBytesPerOutput, gSegwitBytesBase) {} + constexpr SegwitFeeCalculator(bool disableFilter = false) noexcept + : LinearFeeCalculator(gSegwitBytesPerInput, gSegwitBytesPerOutput, gSegwitBytesBase) + , disableDustFilter(disableFilter) {} + + [[nodiscard]] int64_t calculateSingleInput(int64_t byteFee) const noexcept override { + if (disableDustFilter) { + return 0; + } + return LinearFeeCalculator::calculateSingleInput(byteFee); + } }; /// Return the fee calculator for the given coin. -const FeeCalculator& getFeeCalculator(TWCoinType coinType) noexcept; +const FeeCalculator& getFeeCalculator(TWCoinType coinType, bool disableFilter = false) noexcept; } // namespace TW::Bitcoin diff --git a/src/Bitcoin/OpCodes.h b/src/Bitcoin/OpCodes.h index 1e5cd03726a..592ca986e6f 100644 --- a/src/Bitcoin/OpCodes.h +++ b/src/Bitcoin/OpCodes.h @@ -136,6 +136,7 @@ enum OpCode { OP_NOP3 [[maybe_unused]] = OP_CHECKSEQUENCEVERIFY, OP_NOP4 [[maybe_unused]] = 0xb3, OP_NOP5 [[maybe_unused]] = 0xb4, + OP_CHECKBLOCKATHEIGHT = OP_NOP5, OP_NOP6 [[maybe_unused]] = 0xb5, OP_NOP7 [[maybe_unused]] = 0xb6, OP_NOP8 [[maybe_unused]] = 0xb7, diff --git a/src/Bitcoin/OutPoint.cpp b/src/Bitcoin/OutPoint.cpp index c4107bb1fc2..557a66c96cb 100644 --- a/src/Bitcoin/OutPoint.cpp +++ b/src/Bitcoin/OutPoint.cpp @@ -14,6 +14,7 @@ void OutPoint::encode(Data& data) const noexcept { std::copy(std::begin(hash), std::end(hash), std::back_inserter(data)); encode32LE(index, data); // sequence is encoded in TransactionInputs + // tree is only for DCR } } // namespace TW::Bitcoin diff --git a/src/Bitcoin/OutPoint.h b/src/Bitcoin/OutPoint.h index bbfe148289b..81473b40d2f 100644 --- a/src/Bitcoin/OutPoint.h +++ b/src/Bitcoin/OutPoint.h @@ -28,16 +28,19 @@ struct OutPoint { /// TransactionInput.sequence) uint32_t sequence; + /// The tree in utxo, only works for DCR + int8_t tree; + OutPoint() noexcept = default; /// Initializes an out-point reference with hash, index. template - OutPoint(const T& h, uint32_t index, uint32_t sequence = 0) noexcept - : hash(to_array(h)), index(index), sequence(sequence) {} + OutPoint(const T& h, uint32_t index, uint32_t sequence = 0, int8_t tree = 0) noexcept + : hash(to_array(h)), index(index), sequence(sequence), tree(tree) {} /// Initializes an out-point from a Protobuf out-point. OutPoint(const Proto::OutPoint& other) noexcept - : OutPoint(other.hash(), other.index(), other.sequence()) { + : OutPoint(other.hash(), other.index(), other.sequence(), int8_t(other.tree())) { assert(other.hash().size() == 32); } @@ -49,6 +52,7 @@ struct OutPoint { op.set_hash(std::string(hash.begin(), hash.end())); op.set_index(index); op.set_sequence(sequence); + op.set_tree(int32_t(tree)); return op; } }; diff --git a/src/Bitcoin/Script.cpp b/src/Bitcoin/Script.cpp index 2b94835ae72..367061872af 100644 --- a/src/Bitcoin/Script.cpp +++ b/src/Bitcoin/Script.cpp @@ -16,6 +16,7 @@ #include "../Decred/Address.h" #include "../Groestlcoin/Address.h" #include "../Zcash/TAddress.h" +#include "../Zen/Address.h" #include #include @@ -33,6 +34,12 @@ bool Script::isPayToScriptHash() const { bytes[22] == OP_EQUAL; } +bool Script::isPayToScriptHashReplay() const { + // Extra-fast test for pay-to-script-hash-replay + return bytes.size() == 61 && bytes[0] == OP_HASH160 && bytes[1] == 0x14 && + bytes[22] == OP_EQUAL && bytes.back() == OP_CHECKBLOCKATHEIGHT; +} + bool Script::isPayToWitnessScriptHash() const { // Extra-fast test for pay-to-witness-script-hash return bytes.size() == 34 && bytes[0] == OP_0 && bytes[1] == 0x20; @@ -81,6 +88,17 @@ bool Script::matchPayToPublicKeyHash(Data& result) const { return false; } +bool Script::matchPayToPublicKeyHashReplay(Data& result) const { + if (bytes.size() == 63 && bytes[0] == OP_DUP && bytes[1] == OP_HASH160 && bytes[2] == 20 && + bytes[23] == OP_EQUALVERIFY && bytes[24] == OP_CHECKSIG && bytes[25] == 32 && + bytes.back() == OP_CHECKBLOCKATHEIGHT) { + result.clear(); + std::copy(std::begin(bytes) + 3, std::begin(bytes) + 3 + 20, std::back_inserter(result)); + return true; + } + return false; +} + bool Script::matchPayToScriptHash(Data& result) const { if (!isPayToScriptHash()) { return false; @@ -90,6 +108,16 @@ bool Script::matchPayToScriptHash(Data& result) const { return true; } +bool Script::matchPayToScriptHashReplay(Data& result) const { + if (!isPayToScriptHashReplay()) { + return false; + } + result.clear(); + std::copy(std::begin(bytes) + 2, std::begin(bytes) + 22, std::back_inserter(result)); + return true; +} + + bool Script::matchPayToWitnessPublicKeyHash(Data& result) const { if (!isPayToWitnessPublicKeyHash()) { return false; @@ -219,6 +247,32 @@ Script Script::buildPayToPublicKeyHash(const Data& hash) { return script; } +Script Script::buildPayToPublicKeyHashReplay(const Data& hash, const Data& blockHash, int64_t blockHeight) { + assert(hash.size() == 20); + assert(blockHash.size() == 32); + Script script; + script.bytes.push_back(OP_DUP); + script.bytes.push_back(OP_HASH160); + script.bytes.push_back(20); + append(script.bytes, hash); + script.bytes.push_back(OP_EQUALVERIFY); + script.bytes.push_back(OP_CHECKSIG); + + // blockhash + script.bytes.push_back(32); + append(script.bytes, blockHash); + + // blockheight + auto blockHeightData = encodeNumber(blockHeight); + // blockHeight size will never beyond 1 byte size + script.bytes.push_back(static_cast(blockHeightData.size())); + append(script.bytes, blockHeightData); + script.bytes.push_back(OP_CHECKBLOCKATHEIGHT); + + return script; +} + + Script Script::buildPayToScriptHash(const Data& scriptHash) { assert(scriptHash.size() == 20); Script script; @@ -229,6 +283,30 @@ Script Script::buildPayToScriptHash(const Data& scriptHash) { return script; } +Script Script::buildPayToScriptHashReplay(const Data& scriptHash, const Data& blockHash, int64_t blockHeight) { + assert(scriptHash.size() == 20); + assert(blockHash.size() == 32); + Script script; + script.bytes.push_back(OP_HASH160); + script.bytes.push_back(20); + append(script.bytes, scriptHash); + script.bytes.push_back(OP_EQUAL); + + // blockhash + script.bytes.push_back(32); + append(script.bytes, blockHash); + + // blockheight + auto blockHeightData = encodeNumber(blockHeight); + // blockHeight size will never beyond 1 byte size + script.bytes.push_back(static_cast(blockHeightData.size())); + append(script.bytes, blockHeightData); + script.bytes.push_back(OP_CHECKBLOCKATHEIGHT); + + return script; +} + + // Append to the buffer the length for the upcoming data (push). Supported length range: 0-75 bytes void pushDataLength(Data& buffer, size_t len) { assert(len <= 255); @@ -291,6 +369,44 @@ void Script::encode(Data& data) const { std::copy(std::begin(bytes), std::end(bytes), std::back_inserter(data)); } +Data Script::encodeNumber(int64_t n) { + Data result; + // check bitcoin Script::push_int64 + if (n == -1 || (n >= 1 && n <= 16)) { + result.push_back(OP_1 + uint8_t(n - 1)); + return result; + } + if (n == 0) { + result.push_back(OP_0); + return result; + } + + const bool neg = n < 0; + uint64_t absvalue = neg ? -n : n; + + while (absvalue) { + result.push_back(absvalue & 0xff); + absvalue >>= 8; + } + + if (result.back() & 0x80) { + result.push_back(neg ? 0x80 : 0); + } else if (neg) { + result.back() |= 0x80; + } + return result; +} + +bool isLtcP2sh(enum TWCoinType coin, byte start) { + // For ltc, we need to support legacy p2sh which starts with 5. + // Here we check prefix 5 and 50 in case of wallet-core changing its config value. + // Ref: https://github.com/litecoin-project/litecoin/blob/0.21/src/chainparams.cpp#L128 + if (TWCoinTypeLitecoin == coin && (5 == start || 50 == start)) { + return true; + } + return false; +} + Script Script::lockScriptForAddress(const std::string& string, enum TWCoinType coin) { // First try legacy address, for all coins if (Address::isValid(string)) { @@ -303,8 +419,8 @@ Script Script::lockScriptForAddress(const std::string& string, enum TWCoinType c data.reserve(Address::size - 1); std::copy(address.bytes.begin() + 1, address.bytes.end(), std::back_inserter(data)); return buildPayToPublicKeyHash(data); - } - if (p2sh == address.bytes[0]) { + } else if (p2sh == address.bytes[0] + || isLtcP2sh(coin, address.bytes[0])) { // address starts with 3/M auto data = Data(); data.reserve(Address::size - 1); @@ -395,4 +511,21 @@ Script Script::lockScriptForAddress(const std::string& string, enum TWCoinType c } } +Script Script::lockScriptForAddress(const std::string& string, enum TWCoinType coin, const Data& blockHash, int64_t blockHeight) { + if (Zen::Address::isValid(string)) { + auto address = Zen::Address(string); + auto data = Data(); + data.reserve(Address::size - 2); + std::copy(address.bytes.begin() + 2, address.bytes.end(), std::back_inserter(data)); + if (address.bytes[1] == TW::p2pkhPrefix(TWCoinTypeZen)) { + return buildPayToPublicKeyHashReplay(data, blockHash, blockHeight); + } else if (address.bytes[1] == TW::p2shPrefix(TWCoinTypeZen)) { + return buildPayToScriptHashReplay(data, blockHash, blockHeight); + } + } + + return lockScriptForAddress(string, coin); +} + + } // namespace TW::Bitcoin diff --git a/src/Bitcoin/Script.h b/src/Bitcoin/Script.h index 4244ed8d552..100a7ada6df 100644 --- a/src/Bitcoin/Script.h +++ b/src/Bitcoin/Script.h @@ -45,6 +45,10 @@ class Script { /// Determines whether this is a pay-to-script-hash (P2SH) script. bool isPayToScriptHash() const; + /// Determines whether this is a pay-to-script-hash-replay (P2SH) script. + /// Only apply for zen + bool isPayToScriptHashReplay() const; + /// Determines whether this is a pay-to-witness-script-hash (P2WSH) script. bool isPayToWitnessScriptHash() const; @@ -60,9 +64,17 @@ class Script { /// Matches the script to a pay-to-public-key-hash (P2PKH). bool matchPayToPublicKeyHash(Data& keyHash) const; + /// Matches the script to a pay-to-public-key-hash-replay (P2PKH). + /// Only apply for zen + bool matchPayToPublicKeyHashReplay(Data& keyHash) const; + /// Matches the script to a pay-to-script-hash (P2SH). bool matchPayToScriptHash(Data& scriptHash) const; + /// Matches the script to a pay-to-script-hash-replay (P2SH). + /// Only apply for zen + bool matchPayToScriptHashReplay(Data& scriptHash) const; + /// Matches the script to a pay-to-witness-public-key-hash (P2WPKH). bool matchPayToWitnessPublicKeyHash(Data& keyHash) const; @@ -78,9 +90,17 @@ class Script { /// Builds a pay-to-public-key-hash (P2PKH) script from a public key hash. static Script buildPayToPublicKeyHash(const Data& hash); + /// Builds a pay-to-public-key-hash-replay (P2PKH) script from a public key hash. + /// This will apply for zen + static Script buildPayToPublicKeyHashReplay(const Data& hash, const Data& blockHash, int64_t blockHeight); + /// Builds a pay-to-script-hash (P2SH) script from a script hash. static Script buildPayToScriptHash(const Data& scriptHash); + /// Builds a pay-to-script-hash-replay (P2SH) script from a script hash. + /// This will apply for zen + static Script buildPayToScriptHashReplay(const Data& scriptHash, const Data& blockHash, int64_t blockHeight); + /// Builds a pay-to-witness-public-key-hash (P2WPKH) script from a public /// key hash. static Script buildPayToWitnessPublicKeyHash(const Data& hash); @@ -101,6 +121,10 @@ class Script { /// address. static Script lockScriptForAddress(const std::string& address, enum TWCoinType coin); + /// Builds a appropriate lock script for the given + /// address with blockhash and blockheight. + static Script lockScriptForAddress(const std::string& address, enum TWCoinType coin, const Data& blockHash, int64_t blockHeight); + /// Encodes the script. void encode(Data& data) const; @@ -113,6 +137,9 @@ class Script { return OP_1 + uint8_t(n - 1); } + /// Encodes an integer + static Data encodeNumber(int64_t n); + /// Decodes a small integer static inline int decodeNumber(uint8_t opcode) { if (opcode == OP_0) { diff --git a/src/Bitcoin/SignatureBuilder.cpp b/src/Bitcoin/SignatureBuilder.cpp index 0622413f3ec..55ca7938c20 100644 --- a/src/Bitcoin/SignatureBuilder.cpp +++ b/src/Bitcoin/SignatureBuilder.cpp @@ -11,7 +11,10 @@ #include "../BinaryCoding.h" #include "../HexCoding.h" + +#include "../BitcoinDiamond/Transaction.h" #include "../Groestlcoin/Transaction.h" +#include "../Verge/Transaction.h" #include "../Zcash/Transaction.h" #include "../Zcash/TransactionBuilder.h" @@ -130,7 +133,7 @@ Result, Common::Proto::SigningError> SignatureBuilder keys; int required; - if (script.matchPayToScriptHash(data)) { + if (script.matchPayToScriptHash(data) || script.matchPayToScriptHashReplay(data)) { auto redeemScript = scriptForScriptHash(data); if (redeemScript.empty()) { // Error: Missing redeem script @@ -190,7 +193,7 @@ Result, Common::Proto::SigningError> SignatureBuilder, Common::Proto::SigningError>::success({signature}); } - if (script.matchPayToPublicKeyHash(data)) { + if (script.matchPayToPublicKeyHash(data) || script.matchPayToPublicKeyHashReplay(data)) { // obtain public key auto pair = keyPairForPubKeyHash(data); Data pubkey; @@ -340,6 +343,8 @@ Data SignatureBuilder::scriptForScriptHash(const Data& hash) const // Explicitly instantiate a Signers for compatible transactions. template class SignatureBuilder; +template class SignatureBuilder; +template class SignatureBuilder; template class SignatureBuilder; template class SignatureBuilder; diff --git a/src/Bitcoin/Signer.h b/src/Bitcoin/Signer.h index 4a8b2218c47..c881b821f4c 100644 --- a/src/Bitcoin/Signer.h +++ b/src/Bitcoin/Signer.h @@ -15,8 +15,6 @@ namespace TW::Bitcoin { -typedef std::vector> SignaturePubkeyList; - class Signer { public: Signer() = delete; diff --git a/src/Bitcoin/SigningInput.cpp b/src/Bitcoin/SigningInput.cpp index b3d53fb40ca..bf9cf7c4c87 100644 --- a/src/Bitcoin/SigningInput.cpp +++ b/src/Bitcoin/SigningInput.cpp @@ -24,12 +24,21 @@ SigningInput::SigningInput(const Proto::SigningInput& input) { utxos.emplace_back(u); } useMaxAmount = input.use_max_amount(); + useMaxUtxo = input.use_max_utxo(); + disableDustFilter = input.disable_dust_filter(); coinType = static_cast(input.coin_type()); if (input.has_plan()) { plan = TransactionPlan(input.plan()); } outputOpReturn = data(input.output_op_return()); lockTime = input.lock_time(); + time = input.time(); + + totalAmount = amount; + for (auto& output: input.extra_outputs()) { + totalAmount += output.amount(); + extraOutputs.push_back(std::make_pair(output.to_address(), output.amount())); + } } } // namespace TW::Bitcoin diff --git a/src/Bitcoin/SigningInput.h b/src/Bitcoin/SigningInput.h index b30944884d4..53328a1a3c3 100644 --- a/src/Bitcoin/SigningInput.h +++ b/src/Bitcoin/SigningInput.h @@ -51,6 +51,12 @@ class SigningInput { // If sending max amount bool useMaxAmount = false; + // If all input utxos + bool useMaxUtxo = false; + + // If disable dust filter + bool disableDustFilter = false; + // Coin type (forks) TWCoinType coinType = TWCoinTypeBitcoin; @@ -60,6 +66,14 @@ class SigningInput { Data outputOpReturn; uint32_t lockTime = 0; + uint32_t time = 0; + + // Besides to_address and change_addres, + // we have other outputs that include address and value + std::vector> extraOutputs; + + // Total amount to send, including all outputs amount + Amount totalAmount = 0; public: SigningInput() = default; diff --git a/src/Bitcoin/TransactionBuilder.cpp b/src/Bitcoin/TransactionBuilder.cpp index 1f097edb615..6cad21e6b52 100644 --- a/src/Bitcoin/TransactionBuilder.cpp +++ b/src/Bitcoin/TransactionBuilder.cpp @@ -79,7 +79,7 @@ int64_t estimateSegwitFee(const FeeCalculator& feeCalculator, const TransactionP int extraOutputCount(const SigningInput& input) { int count = int(input.outputOpReturn.size() > 0); - return count; + return count + int(input.extraOutputs.size()); } TransactionPlan TransactionBuilder::plan(const SigningInput& input) { @@ -89,21 +89,21 @@ TransactionPlan TransactionBuilder::plan(const SigningInput& input) { } bool maxAmount = input.useMaxAmount; - if (input.amount == 0 && !maxAmount) { + if (input.totalAmount == 0 && !maxAmount) { plan.error = Common::Proto::Error_zero_amount_requested; } else if (input.utxos.empty()) { plan.error = Common::Proto::Error_missing_input_utxos; } else { - const auto& feeCalculator = getFeeCalculator(static_cast(input.coinType)); + const auto& feeCalculator = getFeeCalculator(static_cast(input.coinType), input.disableDustFilter); auto inputSelector = InputSelector(input.utxos, feeCalculator); auto inputSum = InputSelector::sum(input.utxos); // select UTXOs - plan.amount = input.amount; + plan.amount = input.totalAmount; // if amount requested is the same or more than available amount, it cannot be satisfied, but // treat this case as MaxAmount, and send maximum available (which will be less) - if (!maxAmount && static_cast(input.amount) >= inputSum) { + if (!maxAmount && static_cast(input.totalAmount) >= inputSum) { maxAmount = true; } @@ -112,10 +112,16 @@ TransactionPlan TransactionBuilder::plan(const SigningInput& input) { UTXOs selectedInputs; if (!maxAmount) { output_size = 2 + extraOutputs; // output + change - if (input.utxos.size() <= SimpleModeLimit && input.utxos.size() <= MaxUtxosHardLimit) { - selectedInputs = inputSelector.select(plan.amount, input.byteFee, output_size); + if (input.useMaxUtxo) { + selectedInputs = inputSelector.selectMaxAmount(input.byteFee); } else { - selectedInputs = inputSelector.selectSimple(plan.amount, input.byteFee, output_size); + if (input.utxos.size() <= SimpleModeLimit && + input.utxos.size() <= MaxUtxosHardLimit) { + selectedInputs = inputSelector.select(plan.amount, input.byteFee, output_size); + } else { + selectedInputs = + inputSelector.selectSimple(plan.amount, input.byteFee, output_size); + } } } else { output_size = 1 + extraOutputs; // output, no change @@ -140,8 +146,8 @@ TransactionPlan TransactionBuilder::plan(const SigningInput& input) { // Compute fee. // must preliminary set change so that there is a second output if (!maxAmount) { - assert(input.amount <= plan.availableAmount); - plan.amount = input.amount; + assert(input.totalAmount <= plan.availableAmount); + plan.amount = input.totalAmount; plan.fee = 0; plan.change = plan.availableAmount - plan.amount; } else { @@ -161,6 +167,7 @@ TransactionPlan TransactionBuilder::plan(const SigningInput& input) { } else { // max available amount plan.amount = std::max(Amount(0), plan.availableAmount - plan.fee); + plan.useMaxAmount = true; } assert(plan.amount >= 0 && plan.amount <= plan.availableAmount); diff --git a/src/Bitcoin/TransactionBuilder.h b/src/Bitcoin/TransactionBuilder.h index d14cff43c50..75331b8d860 100644 --- a/src/Bitcoin/TransactionBuilder.h +++ b/src/Bitcoin/TransactionBuilder.h @@ -26,20 +26,23 @@ class TransactionBuilder { /// Builds a transaction with the selected input UTXOs, and one main output and an optional change output. template - static Result build(const TransactionPlan& plan, const std::string& toAddress, - const std::string& changeAddress, enum TWCoinType coin, uint32_t lockTime) { + static Result build(const TransactionPlan& plan, const SigningInput& input) { Transaction tx; - tx.lockTime = lockTime; + tx.lockTime = input.lockTime; - auto outputTo = prepareOutputWithScript(toAddress, plan.amount, coin); - if (!outputTo.has_value()) { + auto outputToAmount = input.amount; + if (plan.useMaxAmount) { + outputToAmount = plan.amount; + } + auto outputTo = prepareOutputWithScript(input.toAddress, outputToAmount, input.coinType); + if (!outputTo.has_value()) { return Result::failure(Common::Proto::Error_invalid_address); } tx.outputs.push_back(outputTo.value()); if (plan.change > 0) { - auto outputChange = prepareOutputWithScript(changeAddress, plan.change, coin); - if (!outputChange.has_value()) { + auto outputChange = prepareOutputWithScript(input.changeAddress, plan.change, input.coinType); + if (!outputChange.has_value()) { return Result::failure(Common::Proto::Error_invalid_address); } tx.outputs.push_back(outputChange.value()); @@ -59,6 +62,15 @@ class TransactionBuilder { tx.outputs.emplace_back(0, lockingScriptOpReturn); } + // extra outputs + for (auto& o : input.extraOutputs) { + auto output = prepareOutputWithScript(o.first, o.second, input.coinType); + if (!output.has_value()) { + return Result::failure(Common::Proto::Error_invalid_address); + } + tx.outputs.push_back(output.value()); + } + return Result(tx); } diff --git a/src/Bitcoin/TransactionPlan.h b/src/Bitcoin/TransactionPlan.h index 3849656bcc9..1bc01a57a49 100644 --- a/src/Bitcoin/TransactionPlan.h +++ b/src/Bitcoin/TransactionPlan.h @@ -33,8 +33,17 @@ struct TransactionPlan { /// Zcash branch id Data branchId; + /// zen & bitcoin diamond preblockhash + Data preBlockHash; + + /// zen preblockheight + int64_t preBlockHeight = 0; + Data outputOpReturn; + /// Check if we use max amount for output address + bool useMaxAmount = false; + Common::Proto::SigningError error = Common::Proto::SigningError::OK; TransactionPlan() = default; @@ -46,6 +55,8 @@ struct TransactionPlan { , change(plan.change()) , utxos(std::vector(plan.utxos().begin(), plan.utxos().end())) , branchId(plan.branch_id().begin(), plan.branch_id().end()) + , preBlockHash(plan.preblockhash().begin(), plan.preblockhash().end()) + , preBlockHeight(plan.preblockheight()) , outputOpReturn(plan.output_op_return().begin(), plan.output_op_return().end()) , error(plan.error()) {} @@ -60,6 +71,8 @@ struct TransactionPlan { *plan.add_utxos() = utxo.proto(); } plan.set_branch_id(branchId.data(), branchId.size()); + plan.set_preblockhash(preBlockHash.data(), preBlockHash.size()); + plan.set_preblockheight(preBlockHeight); plan.set_output_op_return(outputOpReturn.data(), outputOpReturn.size()); plan.set_error(error); return plan; diff --git a/src/Bitcoin/TransactionSigner.cpp b/src/Bitcoin/TransactionSigner.cpp index e23f5c32f61..94655ddbadf 100644 --- a/src/Bitcoin/TransactionSigner.cpp +++ b/src/Bitcoin/TransactionSigner.cpp @@ -8,9 +8,14 @@ #include "SignatureBuilder.h" +#include "../BitcoinDiamond/Transaction.h" +#include "../BitcoinDiamond/TransactionBuilder.h" #include "../Groestlcoin/Transaction.h" +#include "../Verge/Transaction.h" +#include "../Verge/TransactionBuilder.h" #include "../Zcash/Transaction.h" #include "../Zcash/TransactionBuilder.h" +#include "../Zen/TransactionBuilder.h" namespace TW::Bitcoin { @@ -27,7 +32,7 @@ Result TransactionSigner(plan, input.toAddress, input.changeAddress, input.coinType, input.lockTime); + auto tx_result = TransactionBuilder::template build(plan, input); if (!tx_result) { return Result::failure(tx_result.error()); } @@ -47,7 +52,7 @@ Result TransactionSigner(plan, input.toAddress, input.changeAddress, input.coinType, input.lockTime); + auto tx_result = TransactionBuilder::template build(plan, input); if (!tx_result) { return Result::failure(tx_result.error()); } @@ -63,6 +68,9 @@ Result TransactionSigner; template class Bitcoin::TransactionSigner; +template class Bitcoin::TransactionSigner; template class Bitcoin::TransactionSigner; +template class Bitcoin::TransactionSigner; +template class Bitcoin::TransactionSigner; } // namespace TW::Bitcoin diff --git a/src/BitcoinDiamond/Entry.cpp b/src/BitcoinDiamond/Entry.cpp new file mode 100644 index 00000000000..6463565d77e --- /dev/null +++ b/src/BitcoinDiamond/Entry.cpp @@ -0,0 +1,95 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Entry.h" + +#include "Signer.h" +#include "proto/Bitcoin.pb.h" +#include "../Bitcoin/Address.h" +#include "../Bitcoin/SegwitAddress.h" + +using namespace TW; +using namespace std; + +namespace TW::BitcoinDiamond { + +// Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. + +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const string& address, const PrefixVariant& addressPrefix) const { + auto* base58Prefix = std::get_if(&addressPrefix); + auto* hrp = std::get_if(&addressPrefix); + bool isValidBase58 = base58Prefix ? Bitcoin::Address::isValid(address) : false; + bool isValidHrp = hrp ? Bitcoin::SegwitAddress::isValid(address, *hrp) : false; + return isValidBase58 || isValidHrp; +} + +string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const { + byte p2pkh = getFromPrefixPkhOrDefault(addressPrefix, coin); + const char* hrp = getFromPrefixHrpOrDefault(addressPrefix, coin); + + switch (derivation) { + case TWDerivationBitcoinLegacy: + case TWDerivationDefault: + return Bitcoin::Address(publicKey, p2pkh).string(); + default: + return TW::Bitcoin::SegwitAddress(publicKey, hrp).string(); + } +} + +TW::Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + const auto decoded = Bitcoin::SegwitAddress::decode(address); + if (!std::get<2>(decoded)) { + // check if it is a legacy address + if (Bitcoin::Address::isValid(address)) { + const auto addr = Bitcoin::Address(address); + return {addr.bytes.begin() + 1, addr.bytes.end()}; + } + return {Data()}; + } + return std::get<0>(decoded).witnessProgram; +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + signTemplate(dataIn, dataOut); +} + +void Entry::plan([[maybe_unused]] TWCoinType coin, const Data& dataIn, Data& dataOut) const { + planTemplate(dataIn, dataOut); +} + +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, + [](const auto& input, auto& output) { output = Signer::preImageHashes(input); }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() == 0 || publicKeys.size() == 0) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return; + } + + if (signatures.size() != publicKeys.size()) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("signatures size and publickeys size not equal"); + return; + } + + HashPubkeyList externalSignatures; + auto n = signatures.size(); + for (auto i = 0ul; i < n; ++i) { + externalSignatures.push_back(std::make_pair(signatures[i], publicKeys[i].bytes)); + } + + output = Signer::sign(input, externalSignatures); + }); +} + +} // namespace TW::BitcoinDiamond diff --git a/src/BitcoinDiamond/Entry.h b/src/BitcoinDiamond/Entry.h new file mode 100644 index 00000000000..d3d4768c6ad --- /dev/null +++ b/src/BitcoinDiamond/Entry.h @@ -0,0 +1,27 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "../CoinEntry.h" + +namespace TW::BitcoinDiamond { + +/// Entry point for implementation of BitcoinDiamond coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file +class Entry final: public CoinEntry { +public: + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const override; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override; + Data addressToData(TWCoinType coin, const std::string& address) const override; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; +}; + +} // namespace TW::BitcoinDiamond diff --git a/src/BitcoinDiamond/Signer.cpp b/src/BitcoinDiamond/Signer.cpp new file mode 100644 index 00000000000..75d43bd2c81 --- /dev/null +++ b/src/BitcoinDiamond/Signer.cpp @@ -0,0 +1,67 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Signer.h" +#include "Bitcoin/TransactionSigner.h" +#include "Hash.h" +#include "HexCoding.h" +#include "PublicKey.h" +#include "Transaction.h" +#include "TransactionBuilder.h" + +using namespace TW; +namespace TW::BitcoinDiamond { + +TransactionPlan Signer::plan(const SigningInput& input) noexcept { + auto plan = Bitcoin::TransactionSigner::plan(input); + return plan.proto(); +} + +SigningOutput Signer::sign(const SigningInput& input, std::optional optionalExternalSigs) noexcept { + SigningOutput output; + auto result = Bitcoin::TransactionSigner::sign(input, false, optionalExternalSigs); + if (!result) { + output.set_error(result.error()); + return output; + } + const auto& tx = result.payload(); + *output.mutable_transaction() = tx.proto(); + + Data encoded; + tx.encode(encoded); + output.set_encoded(encoded.data(), encoded.size()); + + Data txHashData = encoded; + if (tx.hasWitness()) { + txHashData.clear(); + tx.encode(txHashData, Transaction::SegwitFormatMode::NonSegwit); + } + auto txHash = Hash::sha256(txHashData.data(), txHashData.size()); + std::reverse(txHash.begin(), txHash.end()); + output.set_transaction_id(hex(txHash)); + return output; +} + +PreSigningOutput Signer::preImageHashes(const SigningInput& input) noexcept { + PreSigningOutput output; + auto result = Bitcoin::TransactionSigner::preImageHashes(input); + if (!result) { + output.set_error(result.error()); + output.set_error_message(Common::Proto::SigningError_Name(result.error())); + return output; + } + + auto hashList = result.payload(); + auto* hashPubKeys = output.mutable_hash_public_keys(); + for (auto& h : hashList) { + auto* hpk = hashPubKeys->Add(); + hpk->set_data_hash(h.first.data(), h.first.size()); + hpk->set_public_key_hash(h.second.data(), h.second.size()); + } + return output; +} + +} // namespace TW::BitcoinDiamond diff --git a/src/BitcoinDiamond/Signer.h b/src/BitcoinDiamond/Signer.h new file mode 100644 index 00000000000..3a835ca4b7a --- /dev/null +++ b/src/BitcoinDiamond/Signer.h @@ -0,0 +1,38 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "../Data.h" +#include "../PrivateKey.h" +#include "../proto/Bitcoin.pb.h" + +#include + +namespace TW::BitcoinDiamond { + +using SigningInput = Bitcoin::Proto::SigningInput; +using SigningOutput = Bitcoin::Proto::SigningOutput; +using TransactionPlan = Bitcoin::Proto::TransactionPlan; +using PreSigningOutput = Bitcoin::Proto::PreSigningOutput; + +/// Helper class that performs BitcoinDiamond transaction signing. +class Signer { +public: + /// Hide default constructor + Signer() = delete; + + /// Returns a transaction plan (utxo selection, fee estimation) + static TransactionPlan plan(const SigningInput& input) noexcept; + + /// Signs a Proto::SigningInput transaction + static SigningOutput sign(const SigningInput& input, std::optional optionalExternalSigs = {}) noexcept; + + /// Collect pre-image hashes to be signed + static PreSigningOutput preImageHashes(const SigningInput& input) noexcept; +}; + +} // namespace TW::BitcoinDiamond diff --git a/src/BitcoinDiamond/Transaction.cpp b/src/BitcoinDiamond/Transaction.cpp new file mode 100644 index 00000000000..cf662a0ac2f --- /dev/null +++ b/src/BitcoinDiamond/Transaction.cpp @@ -0,0 +1,209 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Transaction.h" +#include "../Bitcoin/SigHashType.h" +#include "../BinaryCoding.h" +#include "../Hash.h" +#include "../Data.h" + +#include + +using namespace TW; +namespace TW::BitcoinDiamond { + +Data Transaction::getPreImage(const Bitcoin::Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType, uint64_t amount) const { + assert(index < inputs.size()); + + Data data; + + // Version + encode32LE(_version, data); + + // see: https://github.com/eveybcd/BitcoinDiamond/blob/master/src/script/interpreter.cpp#L1267 + if (_version == CURRENT_VERSION_FORK) { + std::copy(std::begin(preBlockHash), std::end(preBlockHash), std::back_inserter(data)); + } + + // Input prevouts (none/all, depending on flags) + if ((hashType & TWBitcoinSigHashTypeAnyoneCanPay) == 0) { + auto hashPrevouts = getPrevoutHash(); + std::copy(std::begin(hashPrevouts), std::end(hashPrevouts), std::back_inserter(data)); + } else { + std::fill_n(back_inserter(data), 32, 0); + } + + // Input nSequence (none/all, depending on flags) + if ((hashType & TWBitcoinSigHashTypeAnyoneCanPay) == 0 && + !Bitcoin::hashTypeIsSingle(hashType) && !Bitcoin::hashTypeIsNone(hashType)) { + auto hashSequence = getSequenceHash(); + std::copy(std::begin(hashSequence), std::end(hashSequence), std::back_inserter(data)); + } else { + std::fill_n(back_inserter(data), 32, 0); + } + + // The input being signed (replacing the scriptSig with scriptCode + amount) + // The prevout may already be contained in hashPrevout, and the nSequence + // may already be contain in hashSequence. + inputs[index].previousOutput.encode(data); + scriptCode.encode(data); + + encode64LE(amount, data); + encode32LE(inputs[index].sequence, data); + + // Outputs (none/one/all, depending on flags) + if (!Bitcoin::hashTypeIsSingle(hashType) && !Bitcoin::hashTypeIsNone(hashType)) { + auto hashOutputs = getOutputsHash(); + copy(begin(hashOutputs), end(hashOutputs), back_inserter(data)); + } else if (Bitcoin::hashTypeIsSingle(hashType) && index < outputs.size()) { + Data outputData; + outputs[index].encode(outputData); + auto hashOutputs = Hash::hash(hasher, outputData); + copy(begin(hashOutputs), end(hashOutputs), back_inserter(data)); + } else { + fill_n(back_inserter(data), 32, 0); + } + + // Locktime + encode32LE(lockTime, data); + + // Sighash type + encode32LE(hashType, data); + + return data; +} + + +void Transaction::encode(Data& data, enum SegwitFormatMode segwitFormat) const { + bool useWitnessFormat = true; + switch (segwitFormat) { + case NonSegwit: useWitnessFormat = false; break; + case IfHasWitness: useWitnessFormat = hasWitness(); break; + case Segwit: useWitnessFormat = true; break; + } + + encode32LE(_version, data); + + // see: https://github.com/eveybcd/BitcoinDiamond/blob/master/src/primitives/transaction.h#L344 + if (_version == CURRENT_VERSION_FORK) { + std::copy(std::begin(preBlockHash), std::end(preBlockHash), std::back_inserter(data)); + } + + if (useWitnessFormat) { + // Use extended format in case witnesses are to be serialized. + data.push_back(0); // marker + data.push_back(1); // flag + } + + // txins + encodeVarInt(inputs.size(), data); + for (auto& input : inputs) { + input.encode(data); + } + + // txouts + encodeVarInt(outputs.size(), data); + for (auto& output : outputs) { + output.encode(data); + } + + if (useWitnessFormat) { + encodeWitness(data); + } + + encode32LE(lockTime, data); // nLockTime +} + + +Data Transaction::getSignatureHash(const Bitcoin::Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType, uint64_t amount, + enum Bitcoin::SignatureVersion version) const { + switch (version) { + case Bitcoin::BASE: + return getSignatureHashBase(scriptCode, index, hashType); + case Bitcoin::WITNESS_V0: + return getSignatureHashWitnessV0(scriptCode, index, hashType, amount); + } +} + +/// Generates the signature hash for Witness version 0 scripts. +Data Transaction::getSignatureHashWitnessV0(const Bitcoin::Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType, + uint64_t amount) const { + auto preimage = getPreImage(scriptCode, index, hashType, amount); + auto hash = Hash::hash(hasher, preimage); + return hash; +} + +/// Generates the signature hash for for scripts other than witness scripts. +Data Transaction::getSignatureHashBase(const Bitcoin::Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType) const { + assert(index < inputs.size()); + + Data data; + + encode32LE(_version, data); + + // see: https://github.com/eveybcd/BitcoinDiamond/blob/master/src/script/interpreter.cpp#L1170 + if (_version == CURRENT_VERSION_FORK) { + std::copy(std::begin(preBlockHash), std::end(preBlockHash), std::back_inserter(data)); + } + + auto serializedInputCount = + (hashType & TWBitcoinSigHashTypeAnyoneCanPay) != 0 ? 1 : inputs.size(); + encodeVarInt(serializedInputCount, data); + for (auto subindex = 0ul; subindex < serializedInputCount; subindex += 1) { + serializeInput(subindex, scriptCode, index, hashType, data); + } + + auto hashNone = Bitcoin::hashTypeIsNone(hashType); + auto hashSingle = Bitcoin::hashTypeIsSingle(hashType); + auto serializedOutputCount = hashNone ? 0 : (hashSingle ? index + 1 : outputs.size()); + encodeVarInt(serializedOutputCount, data); + for (auto subindex = 0ul; subindex < serializedOutputCount; subindex += 1) { + if (hashSingle && subindex != index) { + auto output = Bitcoin::TransactionOutput(-1, {}); + output.encode(data); + } else { + outputs[subindex].encode(data); + } + } + + // Locktime + encode32LE(lockTime, data); + + // Sighash type + encode32LE(hashType, data); + + auto hash = Hash::hash(hasher, data); + return hash; +} + +Bitcoin::Proto::Transaction Transaction::proto() const { + auto protoTx = Bitcoin::Proto::Transaction(); + protoTx.set_version(_version); + protoTx.set_locktime(lockTime); + + for (const auto& input : inputs) { + auto* protoInput = protoTx.add_inputs(); + protoInput->mutable_previousoutput()->set_hash(input.previousOutput.hash.data(), + input.previousOutput.hash.size()); + protoInput->mutable_previousoutput()->set_index(input.previousOutput.index); + protoInput->set_sequence(input.sequence); + protoInput->set_script(input.script.bytes.data(), input.script.bytes.size()); + } + + for (const auto& output : outputs) { + auto* protoOutput = protoTx.add_outputs(); + protoOutput->set_value(output.value); + protoOutput->set_script(output.script.bytes.data(), output.script.bytes.size()); + } + + return protoTx; +} + +} // namespace TW::BitcoinDiamond diff --git a/src/BitcoinDiamond/Transaction.h b/src/BitcoinDiamond/Transaction.h new file mode 100644 index 00000000000..a01950ca641 --- /dev/null +++ b/src/BitcoinDiamond/Transaction.h @@ -0,0 +1,55 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include + +#include "../Bitcoin/Script.h" +#include "../Bitcoin/Transaction.h" +#include "../Bitcoin/TransactionInput.h" +#include "../Bitcoin/TransactionOutput.h" +#include "../proto/Bitcoin.pb.h" +#include "Data.h" + +namespace TW::BitcoinDiamond { + +struct Transaction : public Bitcoin::Transaction { +public: + // see: https://github.com/eveybcd/BitcoinDiamond/blob/master/src/primitives/transaction.h#L209 + static const int32_t CURRENT_VERSION_FORK = 12; + static const int32_t CURRENT_VERSION = CURRENT_VERSION_FORK; + + Data preBlockHash; + +public: + Transaction() : Bitcoin::Transaction(CURRENT_VERSION, 0, TW::Hash::HasherSha256d) {} + Transaction(const Data& blockHash, int32_t version = CURRENT_VERSION, uint32_t lockTime = 0) + : Bitcoin::Transaction(version, lockTime, TW::Hash::HasherSha256d) + , preBlockHash(blockHash) {} + + Data getPreImage(const Bitcoin::Script& scriptCode, size_t index, enum TWBitcoinSigHashType hashType, uint64_t amount) const; + + /// Encodes the transaction into the provided buffer. + void encode(Data& data, enum SegwitFormatMode segwitFormat) const; + void encode(Data& data) const { encode(data, SegwitFormatMode::IfHasWitness); } + + Data getSignatureHash(const Bitcoin::Script& scriptCode, size_t index, enum TWBitcoinSigHashType hashType, + uint64_t amount, enum Bitcoin::SignatureVersion version) const; + /// Converts to Protobuf model + Bitcoin::Proto::Transaction proto() const; + +private: + /// Generates the signature hash for Witness version 0 scripts. + Data getSignatureHashWitnessV0(const Bitcoin::Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType, uint64_t amount) const; + + /// Generates the signature hash for for scripts other than witness scripts. + Data getSignatureHashBase(const Bitcoin::Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType) const; +}; + +} // namespace TW::BitcoinDiamond diff --git a/src/BitcoinDiamond/TransactionBuilder.h b/src/BitcoinDiamond/TransactionBuilder.h new file mode 100644 index 00000000000..651e7e79262 --- /dev/null +++ b/src/BitcoinDiamond/TransactionBuilder.h @@ -0,0 +1,39 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Transaction.h" +#include "../Bitcoin/TransactionBuilder.h" +#include "../Bitcoin/TransactionPlan.h" +#include "../proto/Bitcoin.pb.h" +#include "../HexCoding.h" +#include + +#include + +namespace TW::BitcoinDiamond { + +struct TransactionBuilder { + /// Plans a transaction by selecting UTXOs and calculating fees. + static Bitcoin::TransactionPlan plan(const Bitcoin::SigningInput& input) { + return Bitcoin::TransactionBuilder::plan(input); + } + + /// Builds a transaction by selecting UTXOs and calculating fees. + template + static Result build(const Bitcoin::TransactionPlan& plan, + const Bitcoin::SigningInput& input) { + auto tx_result = Bitcoin::TransactionBuilder::build(plan, input); + if (!tx_result) { return Result::failure(tx_result.error()); } + Transaction tx = tx_result.payload(); + std::copy(plan.preBlockHash.begin(), plan.preBlockHash.end(), + std::back_inserter(tx.preBlockHash)); + return Result(tx); + } +}; + +} // namespace TW::BitcoinDiamond diff --git a/src/Cardano/AddressV2.cpp b/src/Cardano/AddressV2.cpp index 896ceeba8f5..f0ff6ef546d 100644 --- a/src/Cardano/AddressV2.cpp +++ b/src/Cardano/AddressV2.cpp @@ -40,7 +40,7 @@ bool AddressV2::parseAndCheck(const std::string& addr, Data& root_out, Data& att } root_out = payloadElems[0].getBytes(); attrs_out = payloadElems[1].encoded(); // map, but encoded as bytes - type_out = (TW::byte)payloadElems[2].getValue(); + type_out = (byte)payloadElems[2].getValue(); return true; } diff --git a/src/Cardano/AddressV3.cpp b/src/Cardano/AddressV3.cpp index d146d08b14e..f1f349a4f01 100644 --- a/src/Cardano/AddressV3.cpp +++ b/src/Cardano/AddressV3.cpp @@ -163,7 +163,7 @@ AddressV3::AddressV3(const Data& data) { AddressV3::AddressV3(const AddressV3& other) = default; uint8_t AddressV3::firstByte(NetworkId networkId, Kind kind) { - byte first = (byte)(((byte)kind << 4) + networkId); + auto first = (byte)(((byte)kind << 4) + networkId); return first; } diff --git a/src/Cardano/Entry.cpp b/src/Cardano/Entry.cpp index a5eef847112..03b11551b51 100644 --- a/src/Cardano/Entry.cpp +++ b/src/Cardano/Entry.cpp @@ -8,6 +8,8 @@ #include "AddressV3.h" #include "Signer.h" +#include "../proto/Cardano.pb.h" +#include "../proto/TransactionCompiler.pb.h" namespace TW::Cardano { @@ -33,4 +35,31 @@ void Entry::plan([[maybe_unused]] TWCoinType coin, const Data& dataIn, Data& dat planTemplate(dataIn, dataOut); } +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + Transaction tx; + const auto buildRet = Signer::buildTx(tx, input); + if (buildRet != Common::Proto::OK) { + output.set_error(buildRet); + output.set_error_message(Common::Proto::SigningError_Name(buildRet)); + return; + } + auto hash = tx.getId(); + auto encoded = tx.encode(); + output.set_data_hash(hash.data(), hash.size()); + output.set_data(encoded.data(), encoded.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [](const auto& input, auto& output, const auto& signature, const auto& publicKey) { + auto encoded = Signer::encodeTransactionWithSig(input, publicKey, signature); + output.set_encoded(encoded.data(), encoded.size()); + return; + }); +} + } // namespace TW::Cardano diff --git a/src/Cardano/Entry.h b/src/Cardano/Entry.h index 2a4a1d81277..e81e85aa16e 100644 --- a/src/Cardano/Entry.h +++ b/src/Cardano/Entry.h @@ -15,10 +15,13 @@ namespace TW::Cardano { class Entry final : public CoinEntry { public: bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; - Data addressToData(TWCoinType coin, const std::string& address) const; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Cardano diff --git a/src/Cardano/Signer.cpp b/src/Cardano/Signer.cpp index 0e0ed85e895..1879016e26d 100644 --- a/src/Cardano/Signer.cpp +++ b/src/Cardano/Signer.cpp @@ -47,6 +47,12 @@ Common::Proto::SigningError Signer::buildTransactionAux(Transaction& tx, const P } const auto toAddress = AddressV3(input.transfer_message().to_address()); tx.outputs.emplace_back(toAddress.data(), plan.amount, plan.outputTokens); + + for (auto& output: plan.extraOutputs) { + const auto extraToAddress = AddressV3(output.address); + tx.outputs.emplace_back(extraToAddress.data(), output.amount, output.tokenBundle); + } + // Change bool hasChangeToken = any_of(plan.changeTokens.bundle.begin(), plan.changeTokens.bundle.end(), [](auto&& t) { return t.second.amount > 0; }); if (plan.change > 0 || hasChangeToken) { @@ -304,8 +310,8 @@ std::vector Signer::selectInputsWithTokens(const std::vector& } // Create a simple plan, used for estimation -TransactionPlan simplePlan(Amount amount, const TokenBundle& requestedTokens, const std::vector& selectedInputs, bool maxAmount, uint64_t deposit, uint64_t undeposit) { - TransactionPlan plan{.utxos = selectedInputs, .amount = amount, .deposit = deposit, .undeposit = undeposit}; +TransactionPlan simplePlan(Amount amount, const TokenBundle& requestedTokens, const std::vector& selectedInputs, bool maxAmount, uint64_t deposit, uint64_t undeposit, const std::vector& extraOutputs) { + TransactionPlan plan{.utxos = selectedInputs, .amount = amount, .deposit = deposit, .undeposit = undeposit, .extraOutputs = extraOutputs}; // Sum availableAmount plan.availableAmount = 0; for (auto& u : plan.utxos) { @@ -362,14 +368,14 @@ uint64_t sumUndeposits(const Proto::SigningInput& input) { } // Estimates size of transaction in bytes. -uint64_t estimateTxSize(const Proto::SigningInput& input, Amount amount, const TokenBundle& requestedTokens, const std::vector& selectedInputs) { +uint64_t estimateTxSize(const Proto::SigningInput& input, Amount amount, const TokenBundle& requestedTokens, const std::vector& selectedInputs, const std::vector& extraOutputs) { auto inputs = std::vector(); for (auto i = 0; i < input.utxos_size(); ++i) { inputs.emplace_back(TxInput::fromProto(input.utxos(i))); } const auto deposits = sumDeposits(input); const uint64_t undeposits = sumUndeposits(input); - const auto _simplePlan = simplePlan(amount, requestedTokens, selectedInputs, input.transfer_message().use_max_amount(), deposits, undeposits); + const auto _simplePlan = simplePlan(amount, requestedTokens, selectedInputs, input.transfer_message().use_max_amount(), deposits, undeposits, extraOutputs); Data encoded; Data txId; @@ -390,8 +396,8 @@ Amount txFeeFunction(uint64_t txSizeInBytes) { return fee; } -Amount Signer::estimateFee(const Proto::SigningInput& input, Amount amount, const TokenBundle& requestedTokens, const std::vector& selectedInputs) { - return txFeeFunction(estimateTxSize(input, amount, requestedTokens, selectedInputs)); +Amount Signer::estimateFee(const Proto::SigningInput& input, Amount amount, const TokenBundle& requestedTokens, const std::vector& selectedInputs, const std::vector& extraOutputs) { + return txFeeFunction(estimateTxSize(input, amount, requestedTokens, selectedInputs, extraOutputs)); } TransactionPlan Signer::doPlan() const { @@ -422,12 +428,22 @@ TransactionPlan Signer::doPlan() const { // Amounts requested plan.amount = input.transfer_message().amount(); + + uint64_t extraAmountSum = 0; + auto extraOutputs = std::vector(); + for (auto& output: input.extra_outputs()) { + const auto extraToAddress = AddressV3(output.address()); + extraOutputs.emplace_back(extraToAddress.data(), output.amount()); + extraAmountSum = extraAmountSum + output.amount(); + } + plan.extraOutputs = extraOutputs; + TokenBundle requestedTokens; for (auto i = 0; i < input.transfer_message().token_amount().token_size(); ++i) { const auto token = TokenAmount::fromProto(input.transfer_message().token_amount().token(i)); requestedTokens.add(token); } - assert(plan.amount > 0 || maxAmount); + assert(plan.amount > 0 || maxAmount || input.transfer_message().token_amount().token_size() > 0); if (requestedTokens.size() > 1) { // We support transfer of only one coin (for simplicity; inputs may contain more coins which are preserved) plan.error = Common::Proto::Error_invalid_requested_token_amount; @@ -483,28 +499,28 @@ TransactionPlan Signer::doPlan() const { // compute fee if (input.transfer_message().force_fee() == 0) { - plan.fee = estimateFee(input, plan.amount, requestedTokens, plan.utxos); + plan.fee = estimateFee(input, plan.amount, requestedTokens, plan.utxos, plan.extraOutputs); } else { // fee provided, use it (capped) - plan.fee = std::max(Amount(0), std::min(availableAmountAfterDeposit - plan.amount, input.transfer_message().force_fee())); + plan.fee = std::max(Amount(0), std::min(availableAmountAfterDeposit - plan.amount - extraAmountSum, input.transfer_message().force_fee())); } assert(plan.fee >= 0 && plan.fee < availableAmountAfterDeposit); // adjust/compute output amount if (!maxAmount) { // reduce amount if needed - plan.amount = std::max(Amount(0), std::min(plan.amount, availableAmountAfterDeposit - plan.fee)); + plan.amount = std::max(Amount(0), std::min(plan.amount, availableAmountAfterDeposit - plan.fee - extraAmountSum)); } else { // max available amount - plan.amount = std::max(Amount(0), availableAmountAfterDeposit - plan.fee); + plan.amount = std::max(Amount(0), availableAmountAfterDeposit - plan.fee - extraAmountSum); } assert(plan.amount >= 0 && plan.amount <= availableAmountAfterDeposit); - if (plan.amount + plan.fee > availableAmountAfterDeposit) { + if (plan.amount + extraAmountSum + plan.fee > availableAmountAfterDeposit) { plan.error = Common::Proto::Error_low_balance; return plan; } - assert(plan.amount + plan.fee <= availableAmountAfterDeposit); + assert(plan.amount + extraAmountSum + plan.fee <= availableAmountAfterDeposit); // compute output token amounts if (!maxAmount) { @@ -514,7 +530,7 @@ TransactionPlan Signer::doPlan() const { } // compute change - plan.change = availableAmountAfterDeposit - (plan.amount + plan.fee); + plan.change = availableAmountAfterDeposit - (plan.amount + extraAmountSum + plan.fee); for (auto iter = plan.availableTokens.bundle.begin(); iter != plan.availableTokens.bundle.end(); ++iter) { const auto key = iter->second.key(); const auto changeAmount = iter->second.amount - plan.outputTokens.getAmount(key); @@ -526,10 +542,40 @@ TransactionPlan Signer::doPlan() const { assert(plan.change >= 0 && plan.change <= availableAmountAfterDeposit); assert(!maxAmount || plan.change == 0); // change is 0 in max amount case - assert(plan.amount + plan.change + plan.fee == availableAmountAfterDeposit); - assert(plan.amount + plan.change + plan.fee + plan.deposit == plan.availableAmount + plan.undeposit); + assert(plan.amount + extraAmountSum+ plan.change + plan.fee == availableAmountAfterDeposit); + assert(plan.amount + extraAmountSum+ plan.change + plan.fee + plan.deposit == plan.availableAmount + plan.undeposit); return plan; } + +Data Signer::encodeTransactionWithSig(const Proto::SigningInput &input, const PublicKey &publicKey, const Data &signature) { + Transaction txAux; + auto buildRet = buildTx(txAux, input); + if (buildRet != Common::Proto::OK) { + throw Common::Proto::SigningError(buildRet); + } + + std::vector> signatures; + signatures.emplace_back(subData(publicKey.bytes, 0, 32), signature); + const auto sigsCbor = cborizeSignatures(signatures); + + // Cbor-encode txAux & signatures + const auto cbor = Cbor::Encode::array({ + // txaux + Cbor::Encode::fromRaw(txAux.encode()), + // signatures + sigsCbor, + // aux data + Cbor::Encode::null(), + }); + + return cbor.encoded(); +} + +Common::Proto::SigningError Signer::buildTx(Transaction& tx, const Proto::SigningInput& input) { + auto plan = Signer(input).doPlan(); + return buildTransactionAux(tx, input, plan); +} + } // namespace TW::Cardano diff --git a/src/Cardano/Signer.h b/src/Cardano/Signer.h index fd4df36825a..7cfc086aa9e 100644 --- a/src/Cardano/Signer.h +++ b/src/Cardano/Signer.h @@ -43,9 +43,11 @@ class Signer { } // Build encoded transaction static Common::Proto::SigningError encodeTransaction(Data& encoded, Data& txId, const Proto::SigningInput& input, const TransactionPlan& plan, bool sizeEstimationOnly = false); + static Data encodeTransactionWithSig(const Proto::SigningInput &input, const PublicKey &publicKey, const Data &signature); // Build aux transaction object, using input and plan static Common::Proto::SigningError buildTransactionAux(Transaction& tx, const Proto::SigningInput& input, const TransactionPlan& plan); - static Amount estimateFee(const Proto::SigningInput& input, Amount amount, const TokenBundle& requestedTokens, const std::vector& selectedInputs); + static Common::Proto::SigningError buildTx(Transaction& tx, const Proto::SigningInput& input); + static Amount estimateFee(const Proto::SigningInput& input, Amount amount, const TokenBundle& requestedTokens, const std::vector& selectedInputs, const std::vector& extraOutputs); static std::vector selectInputsWithTokens(const std::vector& inputs, Amount amount, const TokenBundle& requestedTokens); // Build list of public keys + signature static Common::Proto::SigningError assembleSignatures(std::vector>& signatures, const Proto::SigningInput& input, const TransactionPlan& plan, const Data& txId, bool sizeEstimationOnly = false); diff --git a/src/Cardano/Transaction.cpp b/src/Cardano/Transaction.cpp index 034e153e3ee..dc04539f937 100644 --- a/src/Cardano/Transaction.cpp +++ b/src/Cardano/Transaction.cpp @@ -5,6 +5,7 @@ // file LICENSE at the root of the source code distribution tree. #include "Transaction.h" +#include "AddressV3.h" #include "Cbor.h" #include "Hash.h" @@ -14,13 +15,24 @@ namespace TW::Cardano { TokenAmount TokenAmount::fromProto(const Proto::TokenAmount& proto) { - return {proto.policy_id(), proto.asset_name(), load(proto.amount())}; + std::string assetName; + if (!proto.asset_name().empty()) { + assetName = proto.asset_name(); + } else if (!proto.asset_name_hex().empty()) { + auto assetNameData = parse_hex(proto.asset_name_hex()); + assetName.assign(assetNameData.data(), assetNameData.data() + assetNameData.size()); + } + + return {proto.policy_id(), std::move(assetName), load(proto.amount())}; } Proto::TokenAmount TokenAmount::toProto() const { + auto assetNameHex = hex(assetName); + Proto::TokenAmount tokenAmount; tokenAmount.set_policy_id(policyId.data(), policyId.size()); tokenAmount.set_asset_name(assetName.data(), assetName.size()); + tokenAmount.set_asset_name_hex(assetNameHex.data(), assetNameHex.size()); const auto amountData = store(amount); tokenAmount.set_amount(amountData.data(), amountData.size()); return tokenAmount; @@ -106,9 +118,11 @@ uint64_t TokenBundle::minAdaAmount() const { assetNameRegistry.emplace(t.second.assetName); } } + auto numPids = uint64_t(policyIdRegistry.size()); auto numAssets = uint64_t(assetNameRegistry.size()); - for_each(assetNameRegistry.begin(), assetNameRegistry.end(), [&sumAssetNameLengths](auto&& a) { sumAssetNameLengths += a.length(); }); + for_each(assetNameRegistry.begin(), assetNameRegistry.end(), [&sumAssetNameLengths](auto&& a){ sumAssetNameLengths += a.length(); }); + return minAdaAmountHelper(numPids, numAssets, sumAssetNameLengths); } @@ -137,6 +151,28 @@ Proto::TxInput TxInput::toProto() const { return txInput; } +TxOutput TxOutput::fromProto(const Cardano::Proto::TxOutput& proto) { + auto ret = TxOutput(); + ret.address = data(proto.address()); + ret.amount = proto.amount(); + for (auto i = 0; i < proto.token_amount_size(); ++i) { + auto ta = TokenAmount::fromProto(proto.token_amount(i)); + ret.tokenBundle.add(ta); + } + return ret; +} + +Proto::TxOutput TxOutput::toProto() const { + Proto::TxOutput txOutput; + const auto toAddress = AddressV3(address); + txOutput.set_address(toAddress.string()); + txOutput.set_amount(amount); + for (const auto& token : tokenBundle.bundle) { + *txOutput.add_token_amount() = token.second.toProto(); + } + return txOutput; +} + bool operator==(const TxInput& i1, const TxInput& i2) { return i1.outputIndex == i2.outputIndex && i1.txHash == i2.txHash; } @@ -161,6 +197,9 @@ TransactionPlan TransactionPlan::fromProto(const Proto::TransactionPlan& proto) for (auto i = 0; i < proto.utxos_size(); ++i) { ret.utxos.emplace_back(TxInput::fromProto(proto.utxos(i))); } + for (auto i = 0; i < proto.extra_outputs_size(); ++i) { + ret.extraOutputs.emplace_back(TxOutput::fromProto(proto.extra_outputs(i))); + } ret.error = proto.error(); return ret; } @@ -185,6 +224,9 @@ Proto::TransactionPlan TransactionPlan::toProto() const { for (const auto& u : utxos) { *plan.add_utxos() = u.toProto(); } + for (const auto& u : extraOutputs) { + *plan.add_extra_outputs() = u.toProto(); + } plan.set_error(error); return plan; } diff --git a/src/Cardano/Transaction.h b/src/Cardano/Transaction.h index 7848ae51caf..dd34c84ad23 100644 --- a/src/Cardano/Transaction.h +++ b/src/Cardano/Transaction.h @@ -17,6 +17,8 @@ #include #include #include +#include +#include namespace TW::Cardano { @@ -111,11 +113,15 @@ class TxOutput { : address(std::move(address)), amount(amount) {} TxOutput(Data address, Amount amount, TokenBundle tokenBundle) : address(std::move(address)), amount(amount), tokenBundle(std::move(tokenBundle)) {} + + static TxOutput fromProto(const Proto::TxOutput& proto); + Proto::TxOutput toProto() const; }; class TransactionPlan { public: std::vector utxos; + std::vector extraOutputs; Amount availableAmount = 0; // total coins in the input utxos Amount amount = 0; // coins in the output UTXO Amount fee = 0; // coin amount deducted as fee diff --git a/src/Coin.cpp b/src/Coin.cpp index 22930188aaa..125b9f04228 100644 --- a/src/Coin.cpp +++ b/src/Coin.cpp @@ -19,6 +19,7 @@ #include "Aptos/Entry.h" #include "Binance/Entry.h" #include "Bitcoin/Entry.h" +#include "BitcoinDiamond/Entry.h" #include "Cardano/Entry.h" #include "Cosmos/Entry.h" #include "Decred/Entry.h" @@ -31,6 +32,7 @@ #include "Groestlcoin/Entry.h" #include "Harmony/Entry.h" #include "Icon/Entry.h" +#include "IOST/Entry.h" #include "IoTeX/Entry.h" #include "Kusama/Entry.h" #include "NEAR/Entry.h" @@ -43,6 +45,7 @@ #include "Oasis/Entry.h" #include "Ontology/Entry.h" #include "Polkadot/Entry.h" +#include "XRP/Entry.h" #include "Ronin/Entry.h" #include "Solana/Entry.h" #include "Stellar/Entry.h" @@ -51,10 +54,13 @@ #include "Theta/Entry.h" #include "Tron/Entry.h" #include "VeChain/Entry.h" +#include "Verge/Entry.h" #include "Waves/Entry.h" #include "XRP/Entry.h" #include "Zcash/Entry.h" #include "Zilliqa/Entry.h" +#include "Zen/Entry.h" +#include "Everscale/Entry.h" #include "Hedera/Entry.h" #include "TheOpenNetwork/Entry.h" #include "Sui/Entry.h" @@ -81,6 +87,7 @@ FIO::Entry fioDP; Groestlcoin::Entry groestlcoinDP; Harmony::Entry harmonyDP; Icon::Entry iconDP; +IOST::Entry iostDP; IoTeX::Entry iotexDP; Kusama::Entry kusamaDP; Nano::Entry nanoDP; @@ -101,9 +108,12 @@ Theta::Entry thetaDP; THORChain::Entry thorchainDP; Tron::Entry tronDP; VeChain::Entry vechainDP; +Verge::Entry vergeDP; Waves::Entry wavesDP; Zcash::Entry zcashDP; Zilliqa::Entry zilliqaDP; +BitcoinDiamond::Entry bcdDP; +Zen::Entry zenDP; Nervos::Entry NervosDP; Everscale::Entry EverscaleDP; Hedera::Entry HederaDP; @@ -118,6 +128,7 @@ CoinEntry* coinDispatcher(TWCoinType coinType) { switch (blockchain) { // #coin-list# case TWBlockchainBitcoin: entry = &bitcoinDP; break; + case TWBlockchainBitcoinDiamond: entry = &bcdDP; break; case TWBlockchainEthereum: entry = ðereumDP; break; case TWBlockchainVechain: entry = &vechainDP; break; case TWBlockchainTron: entry = &tronDP; break; @@ -153,6 +164,9 @@ CoinEntry* coinDispatcher(TWCoinType coinType) { case TWBlockchainDecred: entry = &decredDP; break; case TWBlockchainGroestlcoin: entry = &groestlcoinDP; break; case TWBlockchainZcash: entry = &zcashDP; break; + case TWBlockchainZen: entry = &zenDP; break; + case TWBlockchainVerge: entry = &vergeDP; break; + case TWBlockchainIOST: entry = &iostDP; break; case TWBlockchainThorchain: entry = &thorchainDP; break; case TWBlockchainRonin: entry = &roninDP; break; case TWBlockchainKusama: entry = &kusamaDP; break; diff --git a/src/CoinEntry.h b/src/CoinEntry.h index 043dd4da016..c8b0b81bbaf 100644 --- a/src/CoinEntry.h +++ b/src/CoinEntry.h @@ -101,7 +101,7 @@ Data txCompilerTemplate(const Data& dataIn, Func&& fnHandler) { if (!input.ParseFromArray(dataIn.data(), (int)dataIn.size())) { output.set_error(Common::Proto::Error_input_parse); output.set_error_message("failed to parse input data"); - return TW::data(output.SerializeAsString());; + return TW::data(output.SerializeAsString()); } try { @@ -114,6 +114,39 @@ Data txCompilerTemplate(const Data& dataIn, Func&& fnHandler) { return TW::data(output.SerializeAsString()); } +// This template will be used for compile in each coin's Entry.cpp. +// It is a helper function to simplify exception handle that validates if there is only one `signatures` and one `publicKeys`. +template +Data txCompilerSingleTemplate(const Data& dataIn, const std::vector& signatures, const std::vector& publicKeys, Func&& fnHandler) { + auto input = Input(); + auto output = Output(); + if (!input.ParseFromArray(dataIn.data(), (int)dataIn.size())) { + output.set_error(Common::Proto::Error_input_parse); + output.set_error_message("failed to parse input data"); + return TW::data(output.SerializeAsString()); + } + + if (signatures.empty() || publicKeys.empty()) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return TW::data(output.SerializeAsString()); + } + if (signatures.size() != 1 || publicKeys.size() != 1) { + output.set_error(Common::Proto::Error_no_support_n2n); + output.set_error_message("signatures and publickeys size can only be one"); + return TW::data(output.SerializeAsString()); + } + + try { + // each coin function handler + fnHandler(input, output, signatures[0], publicKeys[0]); + } catch (const std::exception& e) { + output.set_error(Common::Proto::Error_internal); + output.set_error_message(e.what()); + } + return TW::data(output.SerializeAsString()); +} + // Get the hrp from the prefix variant, or the coin-default if it is empty or it is not an hrp const char* getFromPrefixHrpOrDefault(const PrefixVariant &prefix, TWCoinType coin); diff --git a/src/Cosmos/Entry.cpp b/src/Cosmos/Entry.cpp index 7fb6b034a6e..753df4990f8 100644 --- a/src/Cosmos/Entry.cpp +++ b/src/Cosmos/Entry.cpp @@ -5,7 +5,7 @@ // file LICENSE at the root of the source code distribution tree. #include "Entry.h" - +#include #include "Address.h" #include "Signer.h" @@ -50,4 +50,24 @@ string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key return Signer::signJSON(json, key, coin); } +Data Entry::preImageHashes(TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [&coin](const auto& input, auto& output) { + auto pkVec = Data(input.public_key().begin(), input.public_key().end()); + auto preimage = Signer().signaturePreimage(input, pkVec, coin); + auto imageHash = Hash::sha256(preimage); + output.set_data(preimage.data(), preimage.size()); + output.set_data_hash(imageHash.data(), imageHash.size()); + }); +} + +void Entry::compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [coin](const auto& input, auto& output, const auto& signature, const auto& publicKey) { + auto signedTx = Signer().encodeTransaction(input, signature, publicKey, coin); + output.set_serialized(signedTx.data(), signedTx.size()); + }); +} + } // namespace TW::Cosmos diff --git a/src/Cosmos/Entry.h b/src/Cosmos/Entry.h index f95c48fe334..4bc3261f9c5 100644 --- a/src/Cosmos/Entry.h +++ b/src/Cosmos/Entry.h @@ -20,6 +20,9 @@ class Entry : public CoinEntry { void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; bool supportsJSONSigning() const final { return true; } std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const override; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; }; } // namespace TW::Cosmos diff --git a/src/Cosmos/JsonSerialization.cpp b/src/Cosmos/JsonSerialization.cpp index 20020731958..d08c4fa362a 100644 --- a/src/Cosmos/JsonSerialization.cpp +++ b/src/Cosmos/JsonSerialization.cpp @@ -6,7 +6,6 @@ #include "JsonSerialization.h" #include "ProtobufSerialization.h" - #include "../Cosmos/Address.h" #include "../proto/Cosmos.pb.h" #include "Base64.h" @@ -23,6 +22,7 @@ const string TYPE_PREFIX_MSG_SEND = "cosmos-sdk/MsgSend"; const string TYPE_PREFIX_MSG_DELEGATE = "cosmos-sdk/MsgDelegate"; const string TYPE_PREFIX_MSG_UNDELEGATE = "cosmos-sdk/MsgUndelegate"; const string TYPE_PREFIX_MSG_REDELEGATE = "cosmos-sdk/MsgBeginRedelegate"; +const string TYPE_PREFIX_MSG_SET_WITHDRAW_ADDRESS = "cosmos-sdk/MsgSetWithdrawAddress"; const string TYPE_PREFIX_MSG_WITHDRAW_REWARD = "cosmos-sdk/MsgWithdrawDelegationReward"; const string TYPE_PREFIX_PUBLIC_KEY = "tendermint/PubKeySecp256k1"; const string TYPE_EVMOS_PREFIX_PUBLIC_KEY = "ethermint/PubKeyEthSecp256k1"; @@ -145,6 +145,35 @@ static json messageWithdrawReward(const Proto::Message_WithdrawDelegationReward& }; } +static json messageSetWithdrawAddress(const Proto::Message_SetWithdrawAddress& message) { + auto typePrefix = message.type_prefix().empty() ? TYPE_PREFIX_MSG_SET_WITHDRAW_ADDRESS : message.type_prefix(); + + return { + {"type", typePrefix}, + {"value", { + {"delegator_address", message.delegator_address()}, + {"withdraw_address", message.withdraw_address()} + }} + }; +} + + +// This method not only support token transfer, but also support all other types of contract call. +// https://docs.terra.money/Tutorials/Smart-contracts/Manage-CW20-tokens.html#interacting-with-cw20-contract +static json messageExecuteContract(const Proto::Message_ExecuteContract& message) { + auto typePrefix = message.type_prefix().empty() ? TYPE_PREFIX_WASM_MSG_EXECUTE : message.type_prefix(); + + return { + {"type", typePrefix}, + {"value", { + {"sender", message.sender()}, + {"contract", message.contract()}, + {"execute_msg", message.execute_msg()}, + {"coins", amountsJSON(message.coins())} + }} + }; +} + json messageWasmTerraTransfer(const Proto::Message_WasmTerraExecuteContractTransfer& msg) { return { {"type", TYPE_PREFIX_WASM_MSG_EXECUTE}, @@ -177,10 +206,17 @@ static json messagesJSON(const Proto::SigningInput& input) { j.push_back(messageUndelegate(msg.unstake_message())); } else if (msg.has_withdraw_stake_reward_message()) { j.push_back(messageWithdrawReward(msg.withdraw_stake_reward_message())); + } else if (msg.has_set_withdraw_address_message()) { + j.push_back(messageSetWithdrawAddress(msg.set_withdraw_address_message())); } else if (msg.has_restake_message()) { j.push_back(messageRedelegate(msg.restake_message())); } else if (msg.has_raw_json_message()) { j.push_back(messageRawJSON(msg.raw_json_message())); + } else if (msg.has_execute_contract_message()) { + j.push_back(messageExecuteContract(msg.execute_contract_message())); + } else if (msg.has_transfer_tokens_message()) { + assert(false); // not suppored, use protobuf serialization + return json::array(); } else if ((msg.has_wasm_terra_execute_contract_transfer_message())) { j.push_back(messageWasmTerraTransfer(msg.wasm_terra_execute_contract_transfer_message())); } else if (msg.has_transfer_tokens_message() || msg.has_wasm_terra_execute_contract_generic()) { @@ -212,9 +248,8 @@ json signaturePreimageJSON(const Proto::SigningInput& input) { }; } -json transactionJSON(const Proto::SigningInput& input, const Data& signature, TWCoinType coin) { - auto privateKey = PrivateKey(input.private_key()); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + +json transactionJSON(const Proto::SigningInput& input, const PublicKey& publicKey, const Data& signature, TWCoinType coin) { json tx = { {"fee", feeJSON(input.fee())}, {"memo", input.memo()}, @@ -226,4 +261,15 @@ json transactionJSON(const Proto::SigningInput& input, const Data& signature, TW return broadcastJSON(tx, input.mode()); } -} // namespace TW::Cosmos +json transactionJSON(const Proto::SigningInput& input, const Data& signature, TWCoinType coin) { + auto privateKey = PrivateKey(input.private_key()); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + + return transactionJSON(input, publicKey, signature, coin); +} + +std::string buildJsonTxRaw(const Proto::SigningInput& input, const PublicKey& publicKey, const Data& signature, TWCoinType coin) { + return transactionJSON(input, publicKey, signature, coin).dump(); +} + +} // namespace TW::Cosmos diff --git a/src/Cosmos/JsonSerialization.h b/src/Cosmos/JsonSerialization.h index 714b62a3498..c8458bddf6b 100644 --- a/src/Cosmos/JsonSerialization.h +++ b/src/Cosmos/JsonSerialization.h @@ -7,6 +7,7 @@ #pragma once #include "Data.h" +#include "PublicKey.h" #include "../proto/Cosmos.pb.h" #include #include @@ -16,6 +17,7 @@ extern const std::string TYPE_PREFIX_MSG_TRANSFER; extern const std::string TYPE_PREFIX_MSG_DELEGATE; extern const std::string TYPE_PREFIX_MSG_UNDELEGATE; extern const std::string TYPE_PREFIX_MSG_REDELEGATE; +extern const std::string TYPE_PREFIX_MSG_SET_WITHDRAW_ADDRESS; extern const std::string TYPE_PREFIX_MSG_WITHDRAW_REWARD; extern const std::string TYPE_PREFIX_PUBLIC_KEY; @@ -26,6 +28,8 @@ using json = nlohmann::json; json signaturePreimageJSON(const Proto::SigningInput& input); json transactionJSON(const Proto::SigningInput& input, const Data& signature, TWCoinType coin); +json transactionJSON(const Proto::SigningInput& input, const PublicKey& publicKey, const Data& signature, TWCoinType coin); +std::string buildJsonTxRaw(const Proto::SigningInput& input, const PublicKey& publicKey, const Data& signature, TWCoinType coin); json signatureJSON(const Data& signature, const Data& pubkey, TWCoinType coin); } // namespace TW::Cosmos::json diff --git a/src/Cosmos/Protobuf/distribution_tx.proto b/src/Cosmos/Protobuf/distribution_tx.proto index 1182c562140..36a8c7df74b 100644 --- a/src/Cosmos/Protobuf/distribution_tx.proto +++ b/src/Cosmos/Protobuf/distribution_tx.proto @@ -3,6 +3,13 @@ package cosmos.distribution.v1beta1; // Src: https://github.com/cosmos/cosmos-sdk/blob/master/proto/cosmos/distribution/v1beta1/tx.proto +// MsgSetWithdrawAddress sets the withdraw address for +// a delegator (or validator self-delegation). +message MsgSetWithdrawAddress { + string delegator_address = 1; + string withdraw_address = 2; +} + // MsgWithdrawDelegatorReward represents delegation withdrawal to a delegator // from a single validator. message MsgWithdrawDelegatorReward { diff --git a/src/Cosmos/ProtobufSerialization.cpp b/src/Cosmos/ProtobufSerialization.cpp index 87c3ccf60af..da1369b96e1 100644 --- a/src/Cosmos/ProtobufSerialization.cpp +++ b/src/Cosmos/ProtobufSerialization.cpp @@ -17,8 +17,8 @@ #include "Protobuf/stride_liquid_staking.pb.h" #include "Protobuf/gov_tx.pb.h" #include "Protobuf/crypto_secp256k1_keys.pb.h" -#include "Protobuf/ibc_applications_transfer_tx.pb.h" #include "Protobuf/terra_wasm_v1beta1_tx.pb.h" +#include "Protobuf/ibc_applications_transfer_tx.pb.h" #include "Protobuf/thorchain_bank_tx.pb.h" #include "Protobuf/ethermint_keys.pb.h" #include "Protobuf/injective_keys.pb.h" @@ -39,6 +39,23 @@ using json = nlohmann::json; using string = std::string; const auto ProtobufAnyNamespacePrefix = ""; // to override default 'type.googleapis.com' +static string broadcastMode(Proto::BroadcastMode mode) { + switch (mode) { + case Proto::BroadcastMode::BLOCK: + return "BROADCAST_MODE_BLOCK"; + case Proto::BroadcastMode::ASYNC: + return "BROADCAST_MODE_ASYNC"; + default: return "BROADCAST_MODE_SYNC"; + } +} + +static json broadcastJSON(std::string data, Proto::BroadcastMode mode) { + return { + {"tx_bytes", data}, + {"mode", broadcastMode(mode)} + }; +} + cosmos::base::v1beta1::Coin convertCoin(const Proto::Amount& amount) { cosmos::base::v1beta1::Coin coin; coin.set_denom(amount.denom()); @@ -129,6 +146,32 @@ google::protobuf::Any convertMessage(const Proto::Message& msg) { return any; } + case Proto::Message::kSetWithdrawAddressMessage: + { + assert(msg.has_set_withdraw_address_message()); + const auto& withdraw = msg.set_withdraw_address_message(); + auto msgWithdraw = cosmos::distribution::v1beta1::MsgSetWithdrawAddress(); + msgWithdraw.set_delegator_address(withdraw.delegator_address()); + msgWithdraw.set_withdraw_address(withdraw.withdraw_address()); + any.PackFrom(msgWithdraw, ProtobufAnyNamespacePrefix); + return any; + } + + case Proto::Message::kExecuteContractMessage: + { + assert(msg.has_execute_contract_message()); + const auto& execContract = msg.execute_contract_message(); + auto executeContractMsg = terra::wasm::v1beta1::MsgExecuteContract(); + executeContractMsg.set_sender(execContract.sender()); + executeContractMsg.set_contract(execContract.contract()); + executeContractMsg.set_execute_msg(execContract.execute_msg()); + for (auto i = 0; i < execContract.coins_size(); ++i){ + *executeContractMsg.add_coins() = convertCoin(execContract.coins(i)); + } + any.PackFrom(executeContractMsg, ProtobufAnyNamespacePrefix); + return any; + } + case Proto::Message::kWasmTerraExecuteContractTransferMessage: { assert(msg.has_wasm_terra_execute_contract_transfer_message()); @@ -337,13 +380,17 @@ std::string buildProtoTxBody(const Proto::SigningInput& input) { } std::string buildAuthInfo(const Proto::SigningInput& input, TWCoinType coin) { + // AuthInfo + const auto privateKey = PrivateKey(input.private_key()); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + return buildAuthInfo(input, publicKey, coin); +} + +std::string buildAuthInfo(const Proto::SigningInput& input, const PublicKey& publicKey, TWCoinType coin) { if (input.messages_size() >= 1 && input.messages(0).has_sign_direct_message()) { return input.messages(0).sign_direct_message().auth_info_bytes(); } - // AuthInfo - const auto privateKey = PrivateKey(input.private_key()); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); auto authInfo = cosmos::AuthInfo(); auto* signerInfo = authInfo.add_signer_infos(); @@ -416,15 +463,29 @@ std::string buildProtoTxRaw(const std::string& serializedTxBody, const std::stri return txRaw.SerializeAsString(); } -static string broadcastMode(Proto::BroadcastMode mode) { - switch (mode) { - case Proto::BroadcastMode::BLOCK: - return "BROADCAST_MODE_BLOCK"; - case Proto::BroadcastMode::ASYNC: - return "BROADCAST_MODE_ASYNC"; - case Proto::BroadcastMode::SYNC: - default: return "BROADCAST_MODE_SYNC"; - } +std::string signaturePreimageProto(const Proto::SigningInput& input, const PublicKey& publicKey, TWCoinType coin) { + // SignDoc Preimage + const auto serializedTxBody = buildProtoTxBody(input); + const auto serializedAuthInfo = buildAuthInfo(input, publicKey, coin); + + auto signDoc = cosmos::SignDoc(); + signDoc.set_body_bytes(serializedTxBody); + signDoc.set_auth_info_bytes(serializedAuthInfo); + signDoc.set_chain_id(input.chain_id()); + signDoc.set_account_number(input.account_number()); + return signDoc.SerializeAsString(); +} + +std::string buildProtoTxRaw(const Proto::SigningInput& input, const PublicKey& publicKey, const Data& signature, TWCoinType coin) { + const auto serializedTxBody = buildProtoTxBody(input); + const auto serializedAuthInfo = buildAuthInfo(input, publicKey, coin); + + auto txRaw = cosmos::TxRaw(); + txRaw.set_body_bytes(serializedTxBody); + txRaw.set_auth_info_bytes(serializedAuthInfo); + *txRaw.add_signatures() = std::string(signature.begin(), signature.end()); + auto data = txRaw.SerializeAsString(); + return broadcastJSON(Base64::encode(Data(data.begin(), data.end())), input.mode()).dump(); } std::string buildProtoTxJson(const Proto::SigningInput& input, const std::string& serializedTx) { diff --git a/src/Cosmos/ProtobufSerialization.h b/src/Cosmos/ProtobufSerialization.h index 47e25e84d76..6eae28e431f 100644 --- a/src/Cosmos/ProtobufSerialization.h +++ b/src/Cosmos/ProtobufSerialization.h @@ -7,6 +7,7 @@ #pragma once #include "Data.h" +#include "PublicKey.h" #include "../proto/Cosmos.pb.h" #include @@ -19,13 +20,18 @@ namespace TW::Cosmos::Protobuf { std::string buildProtoTxBody(const Proto::SigningInput& input); std::string buildAuthInfo(const Proto::SigningInput& input, TWCoinType coin); +std::string buildAuthInfo(const Proto::SigningInput& input, const PublicKey& publicKey, TWCoinType coin); -Data buildSignature(const Proto::SigningInput& input, const std::string& serializedTxBody, const std::string& serializedAuthInfo, TWCoinType coin); +std::string signaturePreimageProto(const Proto::SigningInput& input, const PublicKey& publicKey, TWCoinType coin); +std::string buildProtoTxRaw(const Proto::SigningInput& input, const PublicKey& publicKey, const Data& signature, TWCoinType coin); std::string buildProtoTxRaw(const std::string& serializedTxBody, const std::string& serializedAuthInfo, const Data& signature); +Data buildSignature(const Proto::SigningInput& input, const std::string& serializedTxBody, const std::string& serializedAuthInfo, TWCoinType coin); std::string buildProtoTxJson(const Proto::SigningInput& input, const std::string& serializedTx); +nlohmann::json wasmExecuteSendPayload(const Proto::Message_WasmExecuteContractSend& msg); + nlohmann::json wasmExecuteTransferPayload(const Proto::Message_WasmExecuteContractTransfer& msg); nlohmann::json wasmExecuteSendPayload(const Proto::Message_WasmExecuteContractSend& msg); diff --git a/src/Cosmos/Signer.cpp b/src/Cosmos/Signer.cpp index 4c35170ba9f..c27bc984045 100644 --- a/src/Cosmos/Signer.cpp +++ b/src/Cosmos/Signer.cpp @@ -25,6 +25,18 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input, TWCoinType c } } +std::string Signer::signaturePreimage(const Proto::SigningInput& input, const Data& publicKey, TWCoinType coin) const { + switch (input.signing_mode()) { + case Proto::JSON: + return Json::signaturePreimageJSON(input).dump(); + + case Proto::Protobuf: + default: + auto pbk = PublicKey(publicKey, TWPublicKeyTypeSECP256k1); + return Protobuf::signaturePreimageProto(input, pbk, coin); + } +} + Proto::SigningOutput Signer::signJsonSerialized(const Proto::SigningInput& input, TWCoinType coin) noexcept { auto key = PrivateKey(input.private_key()); auto preimage = Json::signaturePreimageJSON(input).dump(); @@ -37,7 +49,7 @@ Proto::SigningOutput Signer::signJsonSerialized(const Proto::SigningInput& input output.set_json(txJson.dump()); output.set_signature(signature.data(), signature.size()); output.set_serialized(""); - output.set_error(""); + output.set_error_message(""); output.set_signature_json(txJson["tx"]["signatures"].dump()); return output; } @@ -52,18 +64,19 @@ Proto::SigningOutput Signer::signProtobuf(const Proto::SigningInput& input, TWCo auto serializedTxRaw = buildProtoTxRaw(serializedTxBody, serializedAuthInfo, signature); auto output = Proto::SigningOutput(); - const std::string jsonSerialized = buildProtoTxJson(input, serializedTxRaw); + const std::string jsonSerialized = Protobuf::buildProtoTxJson(input, serializedTxRaw); auto publicKey = PrivateKey(input.private_key()).getPublicKey(TWPublicKeyTypeSECP256k1); auto signatures = nlohmann::json::array({signatureJSON(signature, publicKey.bytes, coin)}); output.set_serialized(jsonSerialized); output.set_signature(signature.data(), signature.size()); output.set_json(""); - output.set_error(""); + output.set_error_message(""); output.set_signature_json(signatures.dump()); return output; } catch (const std::exception& ex) { auto output = Proto::SigningOutput(); - output.set_error(std::string("Error: ") + ex.what()); + output.set_error(Common::Proto::Error_internal); + output.set_error_message(std::string("Error: ") + ex.what()); return output; } } @@ -76,4 +89,14 @@ std::string Signer::signJSON(const std::string& json, const Data& key, TWCoinTyp return output.json(); } +std::string Signer::encodeTransaction(const Proto::SigningInput& input, const Data& signature, const PublicKey& publicKey, TWCoinType coin) const { + switch (input.signing_mode()) { + case Proto::JSON: + return Json::buildJsonTxRaw(input, publicKey, signature, coin); + + case Proto::Protobuf: + default: + return Protobuf::buildProtoTxRaw(input, publicKey, signature, coin); + } +} } // namespace TW::Cosmos diff --git a/src/Cosmos/Signer.h b/src/Cosmos/Signer.h index e21f368241c..b63836fc2e7 100644 --- a/src/Cosmos/Signer.h +++ b/src/Cosmos/Signer.h @@ -7,6 +7,7 @@ #pragma once #include "Data.h" +#include "PublicKey.h" #include "../proto/Cosmos.pb.h" #include @@ -19,6 +20,9 @@ class Signer { /// Signs a Proto::SigningInput transaction static Proto::SigningOutput sign(const Proto::SigningInput& input, TWCoinType coin) noexcept; + std::string encodeTransaction(const Proto::SigningInput& input, const Data& signature, const PublicKey& publicKey, TWCoinType coin) const; + std::string signaturePreimage(const Proto::SigningInput& input, const Data& publicKey, TWCoinType coin) const; + /// Signs a Proto::SigningInput transaction, using Json serialization static Proto::SigningOutput signJsonSerialized(const Proto::SigningInput& input, TWCoinType coin) noexcept; diff --git a/src/Data.h b/src/Data.h index c6e58596922..697037ef78f 100644 --- a/src/Data.h +++ b/src/Data.h @@ -16,6 +16,9 @@ namespace TW { using byte = std::uint8_t; using Data = std::vector; +typedef std::vector> HashPubkeyList; +typedef std::vector> SignaturePubkeyList; + inline void pad_left(Data& data, const uint32_t size) { data.insert(data.begin(), size - data.size(), 0); } diff --git a/src/Decred/Address.cpp b/src/Decred/Address.cpp index 203ad0711c2..4f04ef1e0f8 100644 --- a/src/Decred/Address.cpp +++ b/src/Decred/Address.cpp @@ -8,6 +8,7 @@ #include "../Base58.h" #include "../Coin.h" +#include "../Hash.h" #include diff --git a/src/Decred/Entry.cpp b/src/Decred/Entry.cpp index f2993dd7300..3f7a6c9f1d0 100644 --- a/src/Decred/Entry.cpp +++ b/src/Decred/Entry.cpp @@ -32,4 +32,36 @@ void Entry::plan([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::D planTemplate(dataIn, dataOut); } +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + output = Signer::preImageHashes(input); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() == 0 || publicKeys.size() == 0) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return; + } + + if (signatures.size() != publicKeys.size()) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("signatures size and publickeys size not equal"); + return; + } + + HashPubkeyList externalSignatures; + auto n = signatures.size(); + for (auto i = 0ul; i < n; ++i) { + externalSignatures.push_back(std::make_pair(signatures[i], publicKeys[i].bytes)); + } + + output = Signer::sign(input, externalSignatures); + }); +} + } // namespace TW::Decred diff --git a/src/Decred/Entry.h b/src/Decred/Entry.h index 90c78c3dff2..322e5d091c8 100644 --- a/src/Decred/Entry.h +++ b/src/Decred/Entry.h @@ -19,6 +19,9 @@ class Entry final : public CoinEntry { Data addressToData(TWCoinType coin, const std::string& address) const; void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Decred diff --git a/src/Decred/OutPoint.h b/src/Decred/OutPoint.h index 69dacea4712..a75ca419b7e 100644 --- a/src/Decred/OutPoint.h +++ b/src/Decred/OutPoint.h @@ -44,14 +44,14 @@ class OutPoint { OutPoint(const Bitcoin::Proto::OutPoint& other) { std::copy(other.hash().begin(), other.hash().begin() + hash.size(), hash.begin()); index = other.index(); - tree = 0; + tree = int8_t(other.tree()); } /// Initializes an out-point from a Protobuf out-point. OutPoint(const Bitcoin::OutPoint& other) { hash = other.hash; index = other.index; - tree = 0; + tree = other.tree; } /// Encodes the out-point into the provided buffer. diff --git a/src/Decred/Signer.cpp b/src/Decred/Signer.cpp index 363e7a957db..c208bb3f04d 100644 --- a/src/Decred/Signer.cpp +++ b/src/Decred/Signer.cpp @@ -20,12 +20,14 @@ Bitcoin::Proto::TransactionPlan Signer::plan(const Bitcoin::Proto::SigningInput& return signer.txPlan.proto(); } -Proto::SigningOutput Signer::sign(const Bitcoin::Proto::SigningInput& input) noexcept { - auto signer = Signer(input); +Proto::SigningOutput Signer::sign(const Bitcoin::Proto::SigningInput& input, std::optional optionalExternalSigs) noexcept { + SigningMode signingMode = optionalExternalSigs.has_value() ? SigningMode_External : SigningMode_Normal; + auto signer = Signer(std::move(input), signingMode, optionalExternalSigs); auto result = signer.sign(); auto output = Proto::SigningOutput(); if (!result) { output.set_error(result.error()); + output.set_error_message(Common::Proto::SigningError_Name(result.error())); return output; } @@ -41,6 +43,33 @@ Proto::SigningOutput Signer::sign(const Bitcoin::Proto::SigningInput& input) noe return output; } +Bitcoin::Proto::PreSigningOutput Signer::preImageHashes(const Bitcoin::Proto::SigningInput& input) noexcept { + Bitcoin::Proto::PreSigningOutput output; + + auto signer = Signer(std::move(input), SigningMode_HashOnly); + auto result = signer.sign(); + if (!result) { + output.set_error(result.error()); + output.set_error_message(Common::Proto::SigningError_Name(result.error())); + return output; + } + + auto hashes = signer.getHashesForSigning(); + if (hashes.size() == 0) { + output.set_error(Common::Proto::Error_signing); + output.set_error_message("got empty preImage hashes"); + return output; + } + + auto* hashPubKeys = output.mutable_hash_public_keys(); + for (auto& h : hashes) { + auto* hpk = hashPubKeys->Add(); + hpk->set_data_hash(h.first.data(), h.first.size()); + hpk->set_public_key_hash(h.second.data(), h.second.size()); + } + return output; +} + Result Signer::sign() { if (txPlan.utxos.empty() || _transaction.inputs.empty()) { return Result::failure(Common::Proto::Error_missing_input_utxos); @@ -110,11 +139,11 @@ Result, Common::Proto::SigningError> Signer::signStep(Bitcoin: if (script.matchPayToPublicKey(data)) { auto keyHash = TW::Hash::ripemd(TW::Hash::blake256(data)); auto key = keyForPublicKeyHash(keyHash); - if (key.empty()) { + if (key.empty() && signingMode == SigningMode_Normal) { // Error: Missing key return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_missing_private_key); } - auto signature = createSignature(transactionToSign, script, key, index); + auto signature = createSignature(transactionToSign, script, key, keyHash, index); if (signature.empty()) { // Error: Failed to sign return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_signing); @@ -122,18 +151,32 @@ Result, Common::Proto::SigningError> Signer::signStep(Bitcoin: return Result, Common::Proto::SigningError>::success({signature}); } else if (script.matchPayToPublicKeyHash(data)) { auto key = keyForPublicKeyHash(data); + Data pubkey; if (key.empty()) { - // Error: Missing keyxs - return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_missing_private_key); + if (signingMode == SigningMode_HashOnly) { + // estimation mode, key is missing: use placeholder for public key + pubkey = Data(PublicKey::secp256k1Size); + } else if (signingMode == SigningMode_External) { + size_t hashSize = hashesForSigning.size(); + if (!externalSignatures.has_value() || externalSignatures.value().size() <= hashSize) { + // Error: no or not enough signatures provided + return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_signing); + } + pubkey = std::get<1>(externalSignatures.value()[hashSize]); + } else { + // Error: Missing keyxs + return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_missing_private_key); + } + } else { + pubkey = PrivateKey(key).getPublicKey(TWPublicKeyTypeSECP256k1).bytes; } - auto pubkey = PrivateKey(key).getPublicKey(TWPublicKeyTypeSECP256k1); - auto signature = createSignature(transactionToSign, script, key, index); + auto signature = createSignature(transactionToSign, script, key, data, index); if (signature.empty()) { // Error: Failed to sign return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_signing); } - return Result, Common::Proto::SigningError>::success({signature, pubkey.bytes}); + return Result, Common::Proto::SigningError>::success({signature, pubkey}); } else if (script.matchPayToScriptHash(data)) { auto redeemScript = scriptForScriptHash(data); if (redeemScript.empty()) { @@ -149,11 +192,11 @@ Result, Common::Proto::SigningError> Signer::signStep(Bitcoin: } auto keyHash = TW::Hash::ripemd(TW::Hash::blake256(pubKey)); auto key = keyForPublicKeyHash(keyHash); - if (key.empty()) { + if (key.empty() && signingMode == SigningMode_Normal) { // Error: missing key return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_missing_private_key); } - auto signature = createSignature(transactionToSign, script, key, index); + auto signature = createSignature(transactionToSign, script, key, keyHash, index); if (signature.empty()) { // Error: Failed to sign return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_signing); @@ -169,8 +212,44 @@ Result, Common::Proto::SigningError> Signer::signStep(Bitcoin: } Data Signer::createSignature(const Transaction& transaction, const Bitcoin::Script& script, - const Data& key, size_t index) { + const Data& key, const Data& publicKeyHash, size_t index) { auto sighash = transaction.computeSignatureHash(script, index, static_cast(input.hash_type())); + + if (signingMode == SigningMode_HashOnly) { + // Don't sign, only store hash-to-be-signed + pubkeyhash. Return placeholder. + hashesForSigning.push_back(std::make_pair(sighash, publicKeyHash)); + return Data(72); + } + + if (signingMode == SigningMode_External) { + // Use externally-provided signature + // Store hash, only for counting + size_t hashSize = hashesForSigning.size(); + hashesForSigning.push_back(std::make_pair(sighash, publicKeyHash)); + + if (!externalSignatures.has_value() || externalSignatures.value().size() <= hashSize) { + // Error: no or not enough signatures provided + return Data(); + } + + Data externalSignature = std::get<0>(externalSignatures.value()[hashSize]); + const Data publicKey = std::get<1>(externalSignatures.value()[hashSize]); + + // Verify provided signature + if (!PublicKey::isValid(publicKey, TWPublicKeyTypeSECP256k1)) { + // Error: invalid public key + return Data(); + } + const auto publicKeyObj = PublicKey(publicKey, TWPublicKeyTypeSECP256k1); + if (!publicKeyObj.verifyAsDER(externalSignature, sighash)) { + // Error: Signature does not match publickey+hash + return Data(); + } + externalSignature.push_back(static_cast(input.hash_type())); + + return externalSignature; + } + auto pk = PrivateKey(key); auto signature = pk.signAsDER(Data(begin(sighash), end(sighash))); if (script.empty()) { diff --git a/src/Decred/Signer.h b/src/Decred/Signer.h index 5f69deb2afc..665cccb9076 100644 --- a/src/Decred/Signer.h +++ b/src/Decred/Signer.h @@ -24,6 +24,13 @@ namespace TW::Decred { +/// Normal and special signature modes +enum SigningMode { + SigningMode_Normal = 0, // normal signing + SigningMode_HashOnly, // no signing, only collect hash to be signed + SigningMode_External, // no signing, signatures are provided +}; + /// Helper class that performs Decred transaction signing. class Signer { public: @@ -31,11 +38,24 @@ class Signer { static Bitcoin::Proto::TransactionPlan plan(const Bitcoin::Proto::SigningInput& input) noexcept; /// Signs a Proto::SigningInput transaction - static Proto::SigningOutput sign(const Bitcoin::Proto::SigningInput& input) noexcept; + static Proto::SigningOutput sign(const Bitcoin::Proto::SigningInput& input, std::optional optionalExternalSigs = {}) noexcept; + + /// Collect pre-image hashes to be signed + static Bitcoin::Proto::PreSigningOutput preImageHashes(const Bitcoin::Proto::SigningInput& input) noexcept; + private: /// Private key and redeem script provider for signing. Bitcoin::Proto::SigningInput input; + /// Determine sign strategy + SigningMode signingMode = SigningMode_Normal; + + /// For SigningMode_HashOnly, collect hashes (plus corresponding publickey hashes) here + HashPubkeyList hashesForSigning; + + /// For SigningMode_External, signatures are provided here + std::optional externalSignatures; + public: /// Transaction plan. Bitcoin::TransactionPlan txPlan; @@ -52,14 +72,16 @@ class Signer { Signer() = default; /// Initializes a transaction signer with signing input. - explicit Signer(const Bitcoin::Proto::SigningInput& input) - : input(input) { + explicit Signer(const Bitcoin::Proto::SigningInput& input, + SigningMode mode = SigningMode_Normal, + std::optional externalSignatures = {}) + : input(input), signingMode(mode), externalSignatures(externalSignatures) { if (input.has_plan()) { - txPlan = Bitcoin::TransactionPlan(input.plan()); + txPlan = Bitcoin::TransactionPlan(input.plan()); } else { - txPlan = TransactionBuilder::plan(input); + txPlan = TransactionBuilder::plan(input); } - _transaction = TransactionBuilder::build(txPlan, input.to_address(), input.change_address()); + _transaction = TransactionBuilder::build(txPlan, input); } /// Signs the transaction. @@ -72,10 +94,12 @@ class Signer { /// \returns the signed transaction script. Result sign(Bitcoin::Script script, size_t index); + HashPubkeyList getHashesForSigning() const { return hashesForSigning; } + private: Result, Common::Proto::SigningError> signStep(Bitcoin::Script script, size_t index); Data createSignature(const Transaction& transaction, const Bitcoin::Script& script, - const Data& key, size_t index); + const Data& key, const Data& publicKeyHash, size_t index); /// Returns the private key for the given public key hash. Data keyForPublicKeyHash(const Data& hash) const; diff --git a/src/Decred/TransactionBuilder.h b/src/Decred/TransactionBuilder.h index 57ceef91b95..4d0257e6fdc 100644 --- a/src/Decred/TransactionBuilder.h +++ b/src/Decred/TransactionBuilder.h @@ -7,6 +7,7 @@ #pragma once #include "Transaction.h" +#include "../Bitcoin/SigningInput.h" #include "../Bitcoin/TransactionPlan.h" #include "../Bitcoin/TransactionBuilder.h" #include "../proto/Bitcoin.pb.h" @@ -25,29 +26,41 @@ struct TransactionBuilder { } /// Builds a transaction by selecting UTXOs and calculating fees. - static Transaction build(const Bitcoin::TransactionPlan& plan, const std::string& toAddress, - const std::string& changeAddress) { - auto coin = TWCoinTypeDecred; - auto lockingScriptTo = Bitcoin::Script::lockScriptForAddress(toAddress, coin); + static Transaction build(const Bitcoin::TransactionPlan& plan, const Bitcoin::SigningInput& input) { + auto coin = TWCoinTypeDecred; + + auto outputToAmount = input.amount; + if (plan.useMaxAmount) { + outputToAmount = plan.amount; + } + + auto lockingScriptTo = Bitcoin::Script::lockScriptForAddress(input.toAddress, coin); if (lockingScriptTo.empty()) { return {}; } Transaction tx; - tx.outputs.emplace_back(TransactionOutput(plan.amount, /* version: */ 0, lockingScriptTo)); + tx.outputs.emplace_back(TransactionOutput(outputToAmount, /* version: */ 0, lockingScriptTo)); if (plan.change > 0) { - auto lockingScriptChange = Bitcoin::Script::lockScriptForAddress(changeAddress, coin); + auto lockingScriptChange = Bitcoin::Script::lockScriptForAddress(input.changeAddress, coin); tx.outputs.emplace_back( TransactionOutput(plan.change, /* version: */ 0, lockingScriptChange)); } const auto emptyScript = Bitcoin::Script(); for (auto& utxo : plan.utxos) { - auto input = TransactionInput(); - input.previousOutput = utxo.outPoint; - input.sequence = utxo.outPoint.sequence; - tx.inputs.push_back(std::move(input)); + auto txInput = TransactionInput(); + txInput.previousOutput = utxo.outPoint; + txInput.sequence = utxo.outPoint.sequence; + tx.inputs.push_back(std::move(txInput)); + } + + // extra outputs + for (auto& o : input.extraOutputs) { + auto lockingScriptOther = Bitcoin::Script::lockScriptForAddress(o.first, coin); + tx.outputs.emplace_back( + TransactionOutput(o.second, /* version: */ 0, lockingScriptOther)); } return tx; diff --git a/src/Decred/TransactionInput.h b/src/Decred/TransactionInput.h index 30629605d4d..9c600f1a8ad 100644 --- a/src/Decred/TransactionInput.h +++ b/src/Decred/TransactionInput.h @@ -27,13 +27,19 @@ class TransactionInput { /// before inclusion into a block. uint32_t sequence = std::numeric_limits::max(); - int64_t valueIn; - uint32_t blockHeight; + int64_t valueIn = 0; + uint32_t blockHeight = 0; uint32_t blockIndex = std::numeric_limits::max(); /// Computational Script for confirming transaction authorization. Bitcoin::Script script; + TransactionInput() = default; + /// Initializes a transaction input with a previous output, a script and a + /// sequence number. + TransactionInput(OutPoint previousOutput, Bitcoin::Script script, uint32_t sequence) + : previousOutput(std::move(previousOutput)), sequence(sequence), script(std::move(script)) {} + /// Encodes the transaction into the provided buffer. void encode(Data& data) const; diff --git a/src/EOS/Entry.cpp b/src/EOS/Entry.cpp index 84d9acab9eb..7b3f92a9da3 100644 --- a/src/EOS/Entry.cpp +++ b/src/EOS/Entry.cpp @@ -5,6 +5,8 @@ // file LICENSE at the root of the source code distribution tree. #include "Entry.h" +#include "../proto/EOS.pb.h" +#include #include "Address.h" #include "Signer.h" @@ -25,4 +27,30 @@ void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::D signTemplate(dataIn, dataOut); } +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const TW::Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + auto chainId = Data(input.chain_id().begin(), input.chain_id().end()); + auto unsignedTxBytes = Signer(chainId).buildUnsignedTx(input); + auto imageHash = Hash::sha256(unsignedTxBytes); + output.set_data(unsignedTxBytes.data(), unsignedTxBytes.size()); + output.set_data_hash(imageHash.data(), imageHash.size()); + }); } + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, [[maybe_unused]] const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() != 1) { + output.set_error(Common::Proto::Error_signatures_count); + output.set_error_message(Common::Proto::SigningError_Name(Common::Proto::Error_signatures_count)); + return; + } + + auto chainId = Data(input.chain_id().begin(), input.chain_id().end()); + auto signedTx = Signer(chainId).buildSignedTx(input, signatures[0]); + output.set_json_encoded(signedTx.data(), signedTx.size()); + }); +} + +} // namespace TW::EOS diff --git a/src/EOS/Entry.h b/src/EOS/Entry.h index d544cd9a3f0..f1ea5b07f8a 100644 --- a/src/EOS/Entry.h +++ b/src/EOS/Entry.h @@ -17,6 +17,9 @@ class Entry final : public CoinEntry { bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::EOS diff --git a/src/EOS/Signer.cpp b/src/EOS/Signer.cpp index 46ba6468e0d..00b79865783 100644 --- a/src/EOS/Signer.cpp +++ b/src/EOS/Signer.cpp @@ -15,19 +15,9 @@ namespace TW::EOS { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { Proto::SigningOutput output; try { - // create an asset object - const auto& assetData = input.asset(); - auto asset = Asset(assetData.amount(), static_cast(assetData.decimals()), - assetData.symbol()); - - // create a transfer action - auto action = TransferAction(input.currency(), input.sender(), input.recipient(), asset, - input.memo()); - - // create a Transaction and add the transfer action - auto tx = Transaction(Data(input.reference_block_id().begin(), input.reference_block_id().end()), - input.reference_block_time()); - tx.actions.push_back(action); + auto chainId = Data(input.chain_id().begin(), input.chain_id().end()); + auto signer = Signer(chainId); + auto tx = signer.buildTx(input); // get key type EOS::Type type = Type::Legacy; @@ -49,8 +39,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { // sign the transaction with a Signer auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - auto chainId = Data(input.chain_id().begin(), input.chain_id().end()); - Signer(chainId).sign(key, type, tx); + signer.sign(key, type, tx); // Pack the transaction and add the json encoding to Signing outputput PackedTransaction ptx{tx, CompressionType::None}; @@ -84,6 +73,10 @@ void Signer::sign(const PrivateKey& privateKey, Type type, Transaction& transact } TW::Data Signer::hash(const Transaction& transaction) const noexcept { + return Hash::sha256(serializeTx(transaction)); +} + +TW::Data Signer::serializeTx(const Transaction& transaction) const noexcept { Data hashInput(chainID); transaction.serialize(hashInput); @@ -93,7 +86,7 @@ TW::Data Signer::hash(const Transaction& transaction) const noexcept { } append(hashInput, cfdHash); - return Hash::sha256(hashInput); + return hashInput; } // canonical check for EOS @@ -106,4 +99,57 @@ int Signer::isCanonical([[maybe_unused]] uint8_t by, uint8_t sig[64]) { // clang-format on } +Transaction Signer::buildTx(const Proto::SigningInput& input) const { + // create an asset object + auto assetData = input.asset(); + auto asset = + Asset(assetData.amount(), static_cast(assetData.decimals()), assetData.symbol()); + + // create a transfer action + auto action = + TransferAction(input.currency(), input.sender(), input.recipient(), asset, input.memo()); + + // create a Transaction and add the transfer action + auto tx = + Transaction(Data(input.reference_block_id().begin(), input.reference_block_id().end()), + input.reference_block_time()); + if (input.expiration() > 0) { + tx.expiration = input.expiration(); + } + tx.actions.push_back(action); + return tx; +} + +Data Signer::buildUnsignedTx(const Proto::SigningInput& input) noexcept { + auto tx = buildTx(input); + return serializeTx(tx); +} + +std::string Signer::buildSignedTx(const Proto::SigningInput& input, const Data& signature) noexcept { + auto tx = buildTx(input); + + // get key type + EOS::Type type = Type::Legacy; + switch (input.private_key_type()) { + case Proto::KeyType::LEGACY: + type = Type::Legacy; + break; + + case Proto::KeyType::MODERNK1: + type = Type::ModernK1; + break; + + case Proto::KeyType::MODERNR1: + type = Type::ModernR1; + break; + default: + break; + } + + tx.signatures.emplace_back(Signature(signature, type)); + PackedTransaction ptx{tx, CompressionType::None}; + auto stx = ptx.serialize().dump(); + return stx; +} + } // namespace TW::EOS diff --git a/src/EOS/Signer.h b/src/EOS/Signer.h index 034dac0f6e9..fb211b7ebba 100644 --- a/src/EOS/Signer.h +++ b/src/EOS/Signer.h @@ -35,7 +35,14 @@ class Signer { /// Computes the transaction hash. Data hash(const Transaction& transaction) const noexcept; + /// Serialize the transaction. + Data serializeTx(const Transaction& transaction) const noexcept; + static int isCanonical(uint8_t by, uint8_t sig[64]); + + Transaction buildTx(const Proto::SigningInput& input) const; + Data buildUnsignedTx(const Proto::SigningInput& input) noexcept; + std::string buildSignedTx(const Proto::SigningInput& input, const Data& signature) noexcept; }; } // namespace TW::EOS diff --git a/src/EOS/Transaction.h b/src/EOS/Transaction.h index 174800ec8a8..70bd1154f3e 100644 --- a/src/EOS/Transaction.h +++ b/src/EOS/Transaction.h @@ -67,7 +67,7 @@ class Transaction { void setReferenceBlock(const Data& referenceBlockId); - static const int32_t ExpirySeconds = 30; + static const int32_t ExpirySeconds = 3600; /// Get formatted date static std::string formatDate(int32_t date); }; diff --git a/src/Ethereum/Entry.cpp b/src/Ethereum/Entry.cpp index 2baaaea6ebf..901ec3fa8a5 100644 --- a/src/Ethereum/Entry.cpp +++ b/src/Ethereum/Entry.cpp @@ -7,6 +7,8 @@ #include "Entry.h" #include "Address.h" +#include "proto/Common.pb.h" +#include "proto/TransactionCompiler.pb.h" #include "Signer.h" #include "proto/TransactionCompiler.pb.h" diff --git a/src/Ethereum/Entry.h b/src/Ethereum/Entry.h index a320d1d50be..e5d3d41c30d 100644 --- a/src/Ethereum/Entry.h +++ b/src/Ethereum/Entry.h @@ -22,8 +22,8 @@ class Entry : public CoinEntry { bool supportsJSONSigning() const final { return true; } std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const final; - Data preImageHashes(TWCoinType coin, const Data& txInputData) const final; - void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const final; + Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; Data buildTransactionInput(TWCoinType coinType, const std::string& from, const std::string& to, const uint256_t& amount, const std::string& asset, const std::string& memo, const std::string& chainId) const final; }; diff --git a/src/Ethereum/Signer.h b/src/Ethereum/Signer.h index e8addda1935..3a28793b2f9 100644 --- a/src/Ethereum/Signer.h +++ b/src/Ethereum/Signer.h @@ -37,7 +37,6 @@ class Signer { /// Compiles a Proto::SigningInput transaction, with external signature static Proto::SigningOutput compile(const Proto::SigningInput& input, const Data& signature) noexcept; - public: /// build Transaction from signing input static std::shared_ptr build(const Proto::SigningInput& input); diff --git a/src/FIO/Entry.cpp b/src/FIO/Entry.cpp index db300541532..dfb96243b6d 100644 --- a/src/FIO/Entry.cpp +++ b/src/FIO/Entry.cpp @@ -8,6 +8,8 @@ #include "Address.h" #include "Signer.h" +#include "../proto/TransactionCompiler.pb.h" +#include "TransactionBuilder.h" namespace TW::FIO { @@ -25,4 +27,27 @@ void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::D signTemplate(dataIn, dataOut); } +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data &txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto &input, auto &output) { + + Proto::SigningInput in = input; + auto unsignedTxSerialize = TransactionBuilder::buildUnsignedTxBytes(input); + auto preSignData = TransactionBuilder::buildPreSignTxData(TW::data(in.chain_params().chain_id()), unsignedTxSerialize); + auto imageHash = Hash::sha256(preSignData); + output.set_data(preSignData.data(), preSignData.size()); + output.set_data_hash(imageHash.data(), imageHash.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [](const auto& input, auto& output, const auto& signature, [[maybe_unused]] const auto& publicKey) { + output = Signer::compile(input, signature); + }); +} + + } // namespace TW::FIO diff --git a/src/FIO/Entry.h b/src/FIO/Entry.h index 4fa89472acd..2f20456b748 100644 --- a/src/FIO/Entry.h +++ b/src/FIO/Entry.h @@ -15,8 +15,11 @@ namespace TW::FIO { class Entry final : public CoinEntry { public: bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::FIO diff --git a/src/FIO/Signer.cpp b/src/FIO/Signer.cpp index 1bfce9614be..defc5acea72 100644 --- a/src/FIO/Signer.cpp +++ b/src/FIO/Signer.cpp @@ -34,6 +34,12 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { return output; } +Proto::SigningOutput Signer::compile(const Proto::SigningInput& input, const Data& signature) noexcept { + FIO::Proto::SigningOutput output; + output = TransactionBuilder::buildSigningOutput(input, signature); + return output; +} + Data Signer::signData(const PrivateKey& privKey, const Data& data) { Data hash = Hash::sha256(data); Data signature = privKey.sign(hash, TWCurveSECP256k1, isCanonical); diff --git a/src/FIO/Signer.h b/src/FIO/Signer.h index 97048bff872..763417418c7 100644 --- a/src/FIO/Signer.h +++ b/src/FIO/Signer.h @@ -21,7 +21,8 @@ class Signer { public: /// Signs a Proto::SigningInput transaction static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; - + /// Build the compile output + static Proto::SigningOutput compile(const Proto::SigningInput& input, const Data& signature) noexcept; public: static constexpr auto SignatureSuffix = "K1"; static constexpr auto SignaturePrefix = "SIG_K1_"; diff --git a/src/FIO/TransactionBuilder.cpp b/src/FIO/TransactionBuilder.cpp index 8578d1aceef..6b2c5f06242 100644 --- a/src/FIO/TransactionBuilder.cpp +++ b/src/FIO/TransactionBuilder.cpp @@ -86,113 +86,48 @@ string TransactionBuilder::createRegisterFioAddress(const Address& address, cons const string& fioName, const ChainParams& chainParams, uint64_t fee, const string& walletTpId, uint32_t expiryTime) { - const auto* const apiName = "regaddress"; + Transaction transaction = TransactionBuilder::buildUnsignedRegisterFioAddress(address, fioName, chainParams, fee, walletTpId, expiryTime); - string actor = Actor::actor(address); - RegisterFioAddressData raData(fioName, address.string(), fee, walletTpId, actor); - Data serData; - raData.serialize(serData); - - Action action; - action.account = ContractAddress; - action.name = apiName; - action.actionDataSer = serData; - action.auth.authArray.push_back(Authorization{actor, AuthrizationActive}); - - Transaction tx; - expirySetDefaultIfNeeded(expiryTime); - tx.set(expiryTime, chainParams); - tx.actions.push_back(action); Data serTx; - tx.serialize(serTx); + transaction.serialize(serTx); - return signAdnBuildTx(chainParams.chainId, serTx, privateKey); + return signAndBuildTx(chainParams.chainId, serTx, privateKey); } string TransactionBuilder::createAddPubAddress(const Address& address, const PrivateKey& privateKey, const string& fioName, const vector>& pubAddresses, const ChainParams& chainParams, uint64_t fee, const string& walletTpId, uint32_t expiryTime) { - const auto* const apiName = "addaddress"; + Transaction transaction = TransactionBuilder::buildUnsignedAddPubAddress(address, fioName, pubAddresses, chainParams, fee, walletTpId, expiryTime); - string actor = Actor::actor(address); - // convert addresses to add chainCode -- set it equal to coinSymbol - vector pubAddresses2; - for (const auto& a: pubAddresses) { - pubAddresses2.push_back(PublicAddress{a.first, a.first, a.second}); - } - AddPubAddressData aaData(fioName, pubAddresses2, fee, walletTpId, actor); - Data serData; - aaData.serialize(serData); - - Action action; - action.account = ContractAddress; - action.name = apiName; - action.actionDataSer = serData; - action.auth.authArray.push_back(Authorization{actor, AuthrizationActive}); - - Transaction tx; - expirySetDefaultIfNeeded(expiryTime); - tx.set(expiryTime, chainParams); - tx.actions.push_back(action); Data serTx; - tx.serialize(serTx); + transaction.serialize(serTx); - return signAdnBuildTx(chainParams.chainId, serTx, privateKey); + return signAndBuildTx(chainParams.chainId, serTx, privateKey); } string TransactionBuilder::createTransfer(const Address& address, const PrivateKey& privateKey, const string& payeePublicKey, uint64_t amount, const ChainParams& chainParams, uint64_t fee, const string& walletTpId, uint32_t expiryTime) { - const auto* const apiName = "trnsfiopubky"; - - string actor = Actor::actor(address); - TransferData ttData(payeePublicKey, amount, fee, walletTpId, actor); - Data serData; - ttData.serialize(serData); - - Action action; - action.account = ContractToken; - action.name = apiName; - action.actionDataSer = serData; - action.auth.authArray.push_back(Authorization{actor, AuthrizationActive}); + Transaction transaction = TransactionBuilder::buildUnsignedTransfer(address, payeePublicKey, amount, chainParams, fee, walletTpId, expiryTime); - Transaction tx; - expirySetDefaultIfNeeded(expiryTime); - tx.set(expiryTime, chainParams); - tx.actions.push_back(action); Data serTx; - tx.serialize(serTx); + transaction.serialize(serTx); - return signAdnBuildTx(chainParams.chainId, serTx, privateKey); + return signAndBuildTx(chainParams.chainId, serTx, privateKey); } string TransactionBuilder::createRenewFioAddress(const Address& address, const PrivateKey& privateKey, const string& fioName, const ChainParams& chainParams, uint64_t fee, const string& walletTpId, uint32_t expiryTime) { - const auto* const apiName = "renewaddress"; + Transaction transaction = TransactionBuilder::buildUnsignedRenewFioAddress(address, fioName, chainParams, fee, walletTpId, expiryTime); - string actor = Actor::actor(address); - RenewFioAddressData raData(fioName, fee, walletTpId, actor); - Data serData; - raData.serialize(serData); - - Action action; - action.account = ContractAddress; - action.name = apiName; - action.actionDataSer = serData; - action.auth.authArray.push_back(Authorization{actor, AuthrizationActive}); - - Transaction tx; - expirySetDefaultIfNeeded(expiryTime); - tx.set(expiryTime, chainParams); - tx.actions.push_back(action); Data serTx; - tx.serialize(serTx); + transaction.serialize(serTx); - return signAdnBuildTx(chainParams.chainId, serTx, privateKey); + return signAndBuildTx(chainParams.chainId, serTx, privateKey); } string TransactionBuilder::createNewFundsRequest(const Address& address, const PrivateKey& privateKey, @@ -232,14 +167,12 @@ string TransactionBuilder::createNewFundsRequest(const Address& address, const P Data serTx; tx.serialize(serTx); - return signAdnBuildTx(chainParams.chainId, serTx, privateKey); + return signAndBuildTx(chainParams.chainId, serTx, privateKey); } -string TransactionBuilder::signAdnBuildTx(const Data& chainId, const Data& packedTx, const PrivateKey& privateKey) { +string TransactionBuilder::signAndBuildTx(const Data& chainId, const Data& packedTx, const PrivateKey& privateKey) { // create signature - Data sigBuf(chainId); - append(sigBuf, packedTx); - append(sigBuf, TW::Data(32)); // context_free + Data sigBuf = buildPreSignTxData(chainId, packedTx); string signature = Signer::signatureToBase58(Signer::signData(privateKey, sigBuf)); // Build json @@ -252,4 +185,152 @@ string TransactionBuilder::signAdnBuildTx(const Data& chainId, const Data& packe return tx.dump(); } +Data TransactionBuilder::buildPreSignTxData(const Data& chainId, const Data& packedTx) { + // create signature + Data sigBuf(chainId); + append(sigBuf, packedTx); + append(sigBuf, TW::Data(32)); // context_free + return sigBuf; +} + +Data TransactionBuilder::buildUnsignedTxBytes(const Proto::SigningInput& in) { + Address owner(in.owner_public_key()); + + Transaction transaction; + if (in.action().has_register_fio_address_message()) { + const auto action = in.action().register_fio_address_message(); + transaction = TransactionBuilder::buildUnsignedRegisterFioAddress(owner, + in.action().register_fio_address_message().fio_address(), + getChainParams(in), action.fee(), in.tpid(), in.expiry()); + } else if (in.action().has_add_pub_address_message()) { + const auto action = in.action().add_pub_address_message(); + // process addresses + std::vector> addresses; + for (int i = 0; i < action.public_addresses_size(); ++i) { + addresses.emplace_back(std::make_pair(action.public_addresses(i).coin_symbol(), action.public_addresses(i).address())); + } + transaction = TransactionBuilder::buildUnsignedAddPubAddress(owner, action.fio_address(), addresses, + getChainParams(in), action.fee(), in.tpid(), in.expiry()); + } else if (in.action().has_transfer_message()) { + const auto action = in.action().transfer_message(); + transaction = TransactionBuilder::buildUnsignedTransfer(owner, action.payee_public_key(), action.amount(), + getChainParams(in), action.fee(), in.tpid(), in.expiry()); + } else if (in.action().has_renew_fio_address_message()) { + const auto action = in.action().renew_fio_address_message(); + transaction = TransactionBuilder::buildUnsignedRenewFioAddress(owner, action.fio_address(), + getChainParams(in), action.fee(), in.tpid(), in.expiry()); + } + + Data serTx; + transaction.serialize(serTx); + + return serTx; +} + +Proto::SigningOutput TransactionBuilder::buildSigningOutput(const Proto::SigningInput &input, const Data &signature) { + FIO::Proto::SigningOutput output; + Data serTx = buildUnsignedTxBytes(input); + + string signatureString = Signer::signatureToBase58(signature); + // Build json + json tx = { + {"signatures", json::array({signatureString})}, + {"compression", "none"}, + {"packed_context_free_data", ""}, + {"packed_trx", hex(serTx)} + }; + + output.set_json(tx.dump()); + return output; +} + +Transaction TransactionBuilder::buildUnsignedRegisterFioAddress(const Address& address, const std::string& fioName, const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime){ + const auto* const apiName = "regaddress"; + + string actor = Actor::actor(address); + RegisterFioAddressData raData(fioName, address.string(), fee, walletTpId, actor); + Data serData; + raData.serialize(serData); + + Action action; + action.account = ContractAddress; + action.name = apiName; + action.actionDataSer = serData; + action.auth.authArray.push_back(Authorization{actor, AuthrizationActive}); + + Transaction tx; + expirySetDefaultIfNeeded(expiryTime); + tx.set(expiryTime, chainParams); + tx.actions.push_back(action); + return tx; +} + +Transaction TransactionBuilder::buildUnsignedAddPubAddress(const Address& address, const std::string& fioName, const std::vector>& pubAddresses, const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime) { + const auto* const apiName = "addaddress"; + + string actor = Actor::actor(address); + // convert addresses to add chainCode -- set it equal to coinSymbol + vector pubAddresses2; + for (const auto& a: pubAddresses) { + pubAddresses2.push_back(PublicAddress{a.first, a.first, a.second}); + } + AddPubAddressData aaData(fioName, pubAddresses2, fee, walletTpId, actor); + Data serData; + aaData.serialize(serData); + + Action action; + action.account = ContractAddress; + action.name = apiName; + action.actionDataSer = serData; + action.auth.authArray.push_back(Authorization{actor, AuthrizationActive}); + + Transaction tx; + expirySetDefaultIfNeeded(expiryTime); + tx.set(expiryTime, chainParams); + tx.actions.push_back(action); + return tx; +} + +Transaction TransactionBuilder::buildUnsignedTransfer(const Address& address, const std::string& payeePublicKey, uint64_t amount, const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime) { + const auto* const apiName = "trnsfiopubky"; + + string actor = Actor::actor(address); + TransferData ttData(payeePublicKey, amount, fee, walletTpId, actor); + Data serData; + ttData.serialize(serData); + + Action action; + action.account = ContractToken; + action.name = apiName; + action.actionDataSer = serData; + action.auth.authArray.push_back(Authorization{actor, AuthrizationActive}); + + Transaction tx; + expirySetDefaultIfNeeded(expiryTime); + tx.set(expiryTime, chainParams); + tx.actions.push_back(action); + return tx; +} + +Transaction TransactionBuilder::buildUnsignedRenewFioAddress(const Address& address, const std::string& fioName, const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime) { + const auto* const apiName = "renewaddress"; + + string actor = Actor::actor(address); + RenewFioAddressData raData(fioName, fee, walletTpId, actor); + Data serData; + raData.serialize(serData); + + Action action; + action.account = ContractAddress; + action.name = apiName; + action.actionDataSer = serData; + action.auth.authArray.push_back(Authorization{actor, AuthrizationActive}); + + Transaction tx; + expirySetDefaultIfNeeded(expiryTime); + tx.set(expiryTime, chainParams); + tx.actions.push_back(action); + return tx; +} + } // namespace TW::FIO diff --git a/src/FIO/TransactionBuilder.h b/src/FIO/TransactionBuilder.h index 44beb42253c..ce2711d1aab 100644 --- a/src/FIO/TransactionBuilder.h +++ b/src/FIO/TransactionBuilder.h @@ -116,10 +116,32 @@ class TransactionBuilder { const Data& iv); /// Used internally. Creates signatures and json with transaction. - static std::string signAdnBuildTx(const Data& chainId, const Data& packedTx, const PrivateKey& privateKey); + static std::string signAndBuildTx(const Data& chainId, const Data& packedTx, const PrivateKey& privateKey); /// Used internally. If expiry is 0, fill it based on current time. Return true if value has been changed. static bool expirySetDefaultIfNeeded(uint32_t& expiryTime); + + /// Build an unsigned transaction, from the specific SigningInput messages. + static Data buildUnsignedTxBytes(const Proto::SigningInput &in); + + /// Build signing output + static Proto::SigningOutput buildSigningOutput(const Proto::SigningInput &input, const Data &signature); + + /// Build presign TxData + static Data buildPreSignTxData(const Data& chainId, const Data& packedTx); +private: + static Transaction buildUnsignedRegisterFioAddress(const Address& address, const std::string& fioName, + const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime); + + static Transaction buildUnsignedAddPubAddress(const Address& address, const std::string& fioName, + const std::vector>& pubAddresses, + const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime); + + static Transaction buildUnsignedTransfer(const Address& address, const std::string& payeePublicKey, uint64_t amount, + const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime); + + static Transaction buildUnsignedRenewFioAddress(const Address& address, const std::string& fioName, + const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime); }; } // namespace TW::FIO diff --git a/src/Filecoin/Entry.cpp b/src/Filecoin/Entry.cpp index fb42441fdc5..fb4737b3541 100644 --- a/src/Filecoin/Entry.cpp +++ b/src/Filecoin/Entry.cpp @@ -7,6 +7,7 @@ #include "Entry.h" #include "Address.h" +#include "proto/TransactionCompiler.pb.h" #include "Signer.h" namespace TW::Filecoin { @@ -32,4 +33,31 @@ std::string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& return Signer::signJSON(json, key); } +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + auto preImage = Signer::signaturePreimage(input); + auto preImageHash = Hash::blake2b(preImage, 32); + output.set_data_hash(preImageHash.data(), preImageHash.size()); + output.set_data(preImage.data(), preImage.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() == 0 || publicKeys.size() == 0) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return; + } + if (signatures.size() > 1 || publicKeys.size() > 1) { + output.set_error(Common::Proto::Error_no_support_n2n); + output.set_error_message(Common::Proto::SigningError_Name(Common::Proto::Error_no_support_n2n)); + return; + } + output = Signer::compile(signatures[0], publicKeys[0], input); + }); +} + } // namespace TW::Filecoin diff --git a/src/Filecoin/Entry.h b/src/Filecoin/Entry.h index 3ad1f6e7500..0be5a4c4d1a 100644 --- a/src/Filecoin/Entry.h +++ b/src/Filecoin/Entry.h @@ -14,12 +14,15 @@ namespace TW::Filecoin { /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific /// includes in this file class Entry final : public CoinEntry { - public: +public: bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; bool supportsJSONSigning() const { return true; } std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Filecoin diff --git a/src/Filecoin/Signer.cpp b/src/Filecoin/Signer.cpp index dcf6653a3ae..f2f19608ade 100644 --- a/src/Filecoin/Signer.cpp +++ b/src/Filecoin/Signer.cpp @@ -51,11 +51,41 @@ std::string Signer::signJSON(const std::string& json, const Data& key) { return output.json(); } +TW::Data Signer::signaturePreimage(const Proto::SigningInput& input) noexcept { + auto pubkey = PublicKey(Data(input.public_key().begin(), input.public_key().end()), TWPublicKeyTypeSECP256k1Extended); + auto tx = Signer::buildTx(pubkey, input); + return tx.cid(); +} + +Proto::SigningOutput Signer::compile(const Data& signature, const PublicKey& publicKey, const Proto::SigningInput& input) noexcept { + auto tx = Signer::buildTx(publicKey, input); + const auto json = tx.serialize(Transaction::SignatureType::SECP256K1, signature); + + // Return Protobuf output. + Proto::SigningOutput output; + output.set_json(json.data(), json.size()); + return output; +} + Proto::SigningOutput Signer::signSecp256k1(const Proto::SigningInput& input) { // Load private key and transaction from Protobuf input. auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); auto pubkey = key.getPublicKey(TWPublicKeyTypeSECP256k1Extended); - Address from_address = Address::secp256k1Address(pubkey); + + auto transaction = Signer::buildTx(pubkey, input); + + // Sign transaction. + auto signature = sign(key, transaction); + const auto json = transaction.serialize(Transaction::SignatureType::SECP256K1, signature); + + // Return Protobuf output. + Proto::SigningOutput output; + output.set_json(json.data(), json.size()); + return output; +} + +Transaction Signer::buildTx(const PublicKey& publicKey, const Proto::SigningInput& input) noexcept { + Address from_address = Address::secp256k1Address(publicKey); Address to_address(input.to()); // Load the transaction params. @@ -67,26 +97,19 @@ Proto::SigningOutput Signer::signSecp256k1(const Proto::SigningInput& input) { method = Transaction::MethodType::INVOKE_EVM; } - Transaction transaction( - /* to */ to_address, - /* from */ from_address, - /* nonce */ input.nonce(), - /* value */ load(input.value()), - /* gasLimit */ input.gas_limit(), - /* gasFeeCap */ load(input.gas_fee_cap()), - /* gasPremium */ load(input.gas_premium()), - /* method */ method, - /* params */ params - ); - - // Sign transaction. - auto signature = sign(key, transaction); - const auto json = transaction.serialize(Transaction::SignatureType::SECP256K1, signature); - - // Return Protobuf output. - Proto::SigningOutput output; - output.set_json(json.data(), json.size()); - return output; + return { + Transaction( + /* to */ to_address, + /* from */ from_address, + /* nonce */ input.nonce(), + /* value */ load(input.value()), + /* gasLimit */ input.gas_limit(), + /* gasFeeCap */ load(input.gas_fee_cap()), + /* gasPremium */ load(input.gas_premium()), + /* method */ method, + /* params */ params + ) + }; } /// https://github.com/filecoin-project/lotus/blob/ce17546a762eef311069e13410d15465d832a45e/chain/messagesigner/messagesigner.go#L197-L211 diff --git a/src/Filecoin/Signer.h b/src/Filecoin/Signer.h index 4784e930a8e..064b390b29b 100644 --- a/src/Filecoin/Signer.h +++ b/src/Filecoin/Signer.h @@ -29,7 +29,16 @@ class Signer { /// Signs the given transaction. static Data sign(const PrivateKey& privateKey, Transaction& transaction) noexcept; -private: + /// Get transaction data to be signed + static TW::Data signaturePreimage(const Proto::SigningInput& input) noexcept; + + /// build transaction with signature + static Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey, const Proto::SigningInput& input) noexcept; + + private: + /// Get transaction data for secp256k1 to be signed + static Transaction buildTx(const PublicKey& publicKey, const Proto::SigningInput& input) noexcept; + /// Signs a Proto::SigningInput transaction. static Proto::SigningOutput signSecp256k1(const Proto::SigningInput& input); diff --git a/src/Filecoin/Transaction.cpp b/src/Filecoin/Transaction.cpp index 72ea0ab7819..20dc505c8b1 100644 --- a/src/Filecoin/Transaction.cpp +++ b/src/Filecoin/Transaction.cpp @@ -61,7 +61,7 @@ Data Transaction::cid() const { return cid; } -std::string Transaction::serialize(SignatureType signatureType, Data& signature) const { +std::string Transaction::serialize(SignatureType signatureType, const Data& signature) const { // clang-format off json message = { {"To", to.string()}, diff --git a/src/Filecoin/Transaction.h b/src/Filecoin/Transaction.h index 9a9ae97caab..979181f6bfc 100644 --- a/src/Filecoin/Transaction.h +++ b/src/Filecoin/Transaction.h @@ -70,7 +70,7 @@ class Transaction { Data cid() const; // serialize returns json ready for MpoolPush rpc - std::string serialize(SignatureType signatureType, Data& signature) const; + std::string serialize(SignatureType signatureType, const Data& signature) const; }; } // namespace TW::Filecoin diff --git a/src/FullSS58Address.h b/src/FullSS58Address.h new file mode 100644 index 00000000000..0815db1986f --- /dev/null +++ b/src/FullSS58Address.h @@ -0,0 +1,142 @@ +// Copyright © 2017-2023 Trust. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Base58.h" +#include "Data.h" +#include "PublicKey.h" + +#include +#include +#include + +const std::string FullSS58Prefix = "SS58PRE"; + +namespace TW { + +class FullSS58Address { + public: + static const size_t expectPublicKeySize = 32; + + /// Number of bytes in an address. + static const size_t simpleFormatSize = 33; + static const size_t fullFormatSize = 34; + + /// see https://docs.substrate.io/v3/advanced/ss58/#checksum-types + static const size_t checksumSize = 2; + + /// Address data consisting of a network byte followed by the public key. + std::vector bytes; + + + /// Determines whether a string makes a valid address + static bool isValid(const std::string& string, int32_t network) { + const auto decoded = Base58::decode(string); + if (decoded.size() != (simpleFormatSize + checksumSize) && decoded.size() != (fullFormatSize + checksumSize)) { + return false; + } + + if (decoded.size() == (simpleFormatSize + checksumSize)) { + // check network + if (decoded[0] != network) { + return false; + } + } else { + int32_t ss58Decoded = ((decoded[0] & 0b00111111) << 2) | (decoded[1] >> 6) | ((decoded[1] & 0b00111111) << 8); + if (ss58Decoded != network) { + return false; + } + } + + auto checksum = computeChecksum(Data(decoded.begin(), decoded.end() - checksumSize)); + // compare checksum + if (!std::equal(decoded.end() - checksumSize, decoded.end(), checksum.begin())) { + return false; + } + return true; + } + + template + static Data computeChecksum(const T& data) { + auto prefix = Data(FullSS58Prefix.begin(), FullSS58Prefix.end()); + append(prefix, Data(data.begin(), data.end())); + auto hash = Hash::blake2b(prefix, 64); + auto checksum = Data(checksumSize); + std::copy(hash.begin(), hash.begin() + checksumSize, checksum.data()); + return checksum; + } + + FullSS58Address() = default; + + /// Initializes an address with a string representation. + FullSS58Address(const std::string& string, int32_t network) { + if (!isValid(string, network)) { + throw std::invalid_argument("Invalid address string"); + } + const auto decoded = Base58::decode(string); + bytes.resize(decoded.size() - checksumSize); + std::copy(decoded.begin(), decoded.end() - checksumSize, bytes.begin()); + } + + /// Initializes an address with a public key and network. + /// see https://docs.substrate.io/v3/advanced/ss58/#format-in-detail + FullSS58Address(const PublicKey& publicKey, int32_t network) { + if (publicKey.type != TWPublicKeyTypeED25519) { + throw std::invalid_argument("SS58Address expects an ed25519 public key."); + } + if (network < 0 || network > 16383) { + throw std::invalid_argument("network out of range"); + } + if (network < 64) { + // Simple account/address/network + bytes.resize(simpleFormatSize); + bytes[0] = byte(network); + std::copy(publicKey.bytes.begin(), publicKey.bytes.end(), bytes.begin() + 1); + } else { + // Full address/address/network identifier. + byte byte0 = (network & 0b0000000011111100) >> 2 | 0b01000000; + byte byte1 = byte(network >> 8 | (network & 0b0000000000000011) << 6); + + bytes.resize(fullFormatSize); + bytes[0] = byte0; + bytes[1] = byte1; + std::copy(publicKey.bytes.begin(), publicKey.bytes.end(), bytes.begin() + 2); + } + } + + /// Returns a string representation of the address. + std::string string() const { + auto result = Data(bytes.begin(), bytes.end()); + auto checksum = computeChecksum(bytes); + append(result, checksum); + return Base58::encode(result); + } + + /// Returns public key bytes + Data keyBytes() const { + Data bz; + if (bytes.size() == simpleFormatSize) { + bz = Data(bytes.begin() + 1, bytes.end()); + } else if (bytes.size() == fullFormatSize) { + bz = Data(bytes.begin() + 2, bytes.end()); + } else { + throw std::length_error("invalid address bytes length"); + } + + if (bz.size() != expectPublicKeySize) { + throw std::length_error("invalid public key bytes length"); + } + + return bz; + } +}; + +inline bool operator==(const FullSS58Address& lhs, const FullSS58Address& rhs) { + return lhs.bytes == rhs.bytes; +} + +} // namespace TW diff --git a/src/Groestlcoin/Entry.cpp b/src/Groestlcoin/Entry.cpp index d18e12e96e0..d978c2bd861 100644 --- a/src/Groestlcoin/Entry.cpp +++ b/src/Groestlcoin/Entry.cpp @@ -19,9 +19,29 @@ bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& return TW::Bitcoin::SegwitAddress::isValid(address, std::get(addressPrefix)); } -std::string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, const PrefixVariant& addressPrefix) const { - std::string hrp = getFromPrefixHrpOrDefault(addressPrefix, coin); - return TW::Bitcoin::SegwitAddress(publicKey, hrp).string(); +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const { + auto hrp = getFromPrefixHrpOrDefault(addressPrefix, coin); + auto p2pkh = getFromPrefixPkhOrDefault(addressPrefix, coin); + + switch (derivation) { + case TWDerivationBitcoinLegacy: + return Address(publicKey, p2pkh).string(); + default: + return TW::Bitcoin::SegwitAddress(publicKey, hrp).string(); + } +} + +TW::Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + const auto decoded = Bitcoin::SegwitAddress::decode(address); + if (!std::get<2>(decoded)) { + // check if it is a legacy address + if (Address::isValid(address)) { + const auto addr = Address(address); + return {addr.bytes.begin() + 1, addr.bytes.end()}; + } + return {Data()}; + } + return std::get<0>(decoded).witnessProgram; } void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { @@ -32,4 +52,36 @@ void Entry::plan([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::D planTemplate(dataIn, dataOut); } +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + output = Signer::preImageHashes(input); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() == 0 || publicKeys.size() == 0) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return; + } + + if (signatures.size() != publicKeys.size()) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("signatures size and publickeys size not equal"); + return; + } + + HashPubkeyList externalSignatures; + auto n = signatures.size(); + for (auto i = 0ul; i < n; ++i) { + externalSignatures.push_back(std::make_pair(signatures[i], publicKeys[i].bytes)); + } + + output = Signer::sign(input, externalSignatures); + }); +} + } // namespace TW::Groestlcoin diff --git a/src/Groestlcoin/Entry.h b/src/Groestlcoin/Entry.h index 55c29ca14d2..335b9f0c47a 100644 --- a/src/Groestlcoin/Entry.h +++ b/src/Groestlcoin/Entry.h @@ -16,8 +16,12 @@ class Entry final : public CoinEntry { public: bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + Data addressToData(TWCoinType coin, const std::string& address) const; void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Groestlcoin diff --git a/src/Groestlcoin/Signer.cpp b/src/Groestlcoin/Signer.cpp index 6ae1976dbe2..24b8c308528 100644 --- a/src/Groestlcoin/Signer.cpp +++ b/src/Groestlcoin/Signer.cpp @@ -21,9 +21,9 @@ TransactionPlan Signer::plan(const SigningInput& input) noexcept { return plan.proto(); } -SigningOutput Signer::sign(const SigningInput& input) noexcept { +SigningOutput Signer::sign(const SigningInput& input, std::optional optionalExternalSigs) noexcept { SigningOutput output; - auto result = Bitcoin::TransactionSigner::sign(input); + auto result = Bitcoin::TransactionSigner::sign(input, false, optionalExternalSigs); if (!result) { output.set_error(result.error()); return output; @@ -46,4 +46,23 @@ SigningOutput Signer::sign(const SigningInput& input) noexcept { return output; } +PreSigningOutput Signer::preImageHashes(const SigningInput& input) noexcept { + PreSigningOutput output; + auto result = Bitcoin::TransactionSigner::preImageHashes(input); + if (!result) { + output.set_error(result.error()); + output.set_error_message(Common::Proto::SigningError_Name(result.error())); + return output; + } + + auto hashList = result.payload(); + auto* hashPubKeys = output.mutable_hash_public_keys(); + for (auto& h : hashList) { + auto* hpk = hashPubKeys->Add(); + hpk->set_data_hash(h.first.data(), h.first.size()); + hpk->set_public_key_hash(h.second.data(), h.second.size()); + } + return output; +} + } // namespace TW::Groestlcoin diff --git a/src/Groestlcoin/Signer.h b/src/Groestlcoin/Signer.h index c2e5b8fe808..927b1b45c7a 100644 --- a/src/Groestlcoin/Signer.h +++ b/src/Groestlcoin/Signer.h @@ -6,12 +6,18 @@ #pragma once #include "../proto/Bitcoin.pb.h" +#include "Data.h" + +#include +#include +#include namespace TW::Groestlcoin { using SigningInput = Bitcoin::Proto::SigningInput; using SigningOutput = Bitcoin::Proto::SigningOutput; using TransactionPlan = Bitcoin::Proto::TransactionPlan; +using PreSigningOutput = Bitcoin::Proto::PreSigningOutput; class Signer { public: @@ -20,8 +26,11 @@ class Signer { /// Returns a transaction plan (utxo selection, fee estimation) static TransactionPlan plan(const SigningInput& input) noexcept; - /// Signs a Proto::SigningInput transaction - static SigningOutput sign(const SigningInput& input) noexcept; + /// Signs a SigningInput transaction + static SigningOutput sign(const SigningInput& input, std::optional optionalExternalSigs = {}) noexcept; + + /// Collect pre-image hashes to be signed + static PreSigningOutput preImageHashes(const SigningInput& input) noexcept; }; } // namespace TW::Groestlcoin diff --git a/src/Harmony/Entry.cpp b/src/Harmony/Entry.cpp index a1ef86e0401..a12678d17d3 100644 --- a/src/Harmony/Entry.cpp +++ b/src/Harmony/Entry.cpp @@ -5,9 +5,9 @@ // file LICENSE at the root of the source code distribution tree. #include "Entry.h" - #include "Address.h" #include "Signer.h" +#include using namespace TW; using namespace std; @@ -40,4 +40,23 @@ string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json return Signer::signJSON(json, key); } +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data &txInputData) const { + return txCompilerTemplate( + txInputData, [=](const auto &input, auto &output) { + Signer signer(uint256_t(load(input.chain_id()))); + auto unsignedTxBytes = signer.buildUnsignedTxBytes(input); + auto imageHash = Hash::keccak256(unsignedTxBytes); + output.set_data(unsignedTxBytes.data(), unsignedTxBytes.size()); + output.set_data_hash(imageHash.data(), imageHash.size()); }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data &txInputData, const std::vector &signatures, + const std::vector &publicKeys, Data &dataOut) const { + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [](const auto &input, auto &output, const auto& signature, [[maybe_unused]] const auto& publicKey) { + Signer signer(uint256_t(load(input.chain_id()))); + output = signer.buildSigningOutput(input, signature); }); +} + } // namespace TW::Harmony diff --git a/src/Harmony/Entry.h b/src/Harmony/Entry.h index 1889701384d..41f0e5c5aae 100644 --- a/src/Harmony/Entry.h +++ b/src/Harmony/Entry.h @@ -15,11 +15,14 @@ namespace TW::Harmony { class Entry final : public CoinEntry { public: bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; - Data addressToData(TWCoinType coin, const std::string& address) const; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - bool supportsJSONSigning() const { return true; } - std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool supportsJSONSigning() const { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Harmony diff --git a/src/Harmony/Signer.cpp b/src/Harmony/Signer.cpp index 3990f91598a..6fc570d235e 100644 --- a/src/Harmony/Signer.cpp +++ b/src/Harmony/Signer.cpp @@ -11,6 +11,8 @@ namespace TW::Harmony { +using INVALID_ENUM = std::integral_constant; + std::tuple Signer::values(const uint256_t& chainID, const Data& signature) noexcept { auto r = load(Data(signature.begin(), signature.begin() + 32)); @@ -42,29 +44,39 @@ Proto::SigningOutput Signer::prepareOutput(const Data& encoded, const T& transac return protoOutput; } -Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - if (input.has_transaction_message()) { - return signTransaction(input); - } - if (input.has_staking_message()) { - Harmony::Proto::StakingMessage stakingMessage = input.staking_message(); - if (stakingMessage.has_create_validator_message()) { - return signCreateValidator(input); - } - if (stakingMessage.has_edit_validator_message()) { - return signEditValidator(input); - } - if (stakingMessage.has_delegate_message()) { - return signDelegate(input); - } - if (stakingMessage.has_undelegate_message()) { - return signUndelegate(input); +Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { + auto output = Proto::SigningOutput(); + try { + + if (input.has_transaction_message()) { + return signTransaction(input); } - if (stakingMessage.has_collect_rewards()) { - return signCollectRewards(input); + + if (input.has_staking_message()) { + Harmony::Proto::StakingMessage stakingMessage = input.staking_message(); + if (stakingMessage.has_create_validator_message()) { + return Signer::signStaking(input, &Signer::buildUnsignedCreateValidator); + } + if (stakingMessage.has_edit_validator_message()) { + return Signer::signStaking(input, &Signer::buildUnsignedEditValidator); + } + if (stakingMessage.has_delegate_message()) { + return Signer::signStaking(input, &Signer::buildUnsignedDelegate); + } + if (stakingMessage.has_undelegate_message()) { + return Signer::signStaking(input, &Signer::buildUnsignedUndelegate); + } + if (stakingMessage.has_collect_rewards()) { + return Signer::signStaking(input, &Signer::buildUnsignedCollectRewards); + } } + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("Invalid message"); + } catch (const std::exception &e) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message(e.what()); } - return Proto::SigningOutput(); + return output; } std::string Signer::signJSON(const std::string& json, const Data& key) { @@ -74,24 +86,10 @@ std::string Signer::signJSON(const std::string& json, const Data& key) { return hex(Signer::sign(input).encoded()); } -Proto::SigningOutput Signer::signTransaction(const Proto::SigningInput& input) noexcept { +Proto::SigningOutput Signer::signTransaction(const Proto::SigningInput& input) { auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - Address toAddr; - if (!Address::decode(input.transaction_message().to_address(), toAddr)) { - // invalid to address - return Proto::SigningOutput(); - } - auto transaction = Transaction( - /* nonce: */ load(input.transaction_message().nonce()), - /* gasPrice: */ load(input.transaction_message().gas_price()), - /* gasLimit: */ load(input.transaction_message().gas_limit()), - /* fromShardID */ load(input.transaction_message().from_shard_id()), - /* toShardID */ load(input.transaction_message().to_shard_id()), - /* to: */ toAddr, - /* amount: */ load(input.transaction_message().amount()), - /* payload: */ - Data(input.transaction_message().payload().begin(), - input.transaction_message().payload().end())); + + auto transaction = Signer::buildUnsignedTransaction(input); auto signer = Signer(uint256_t(load(input.chain_id()))); auto hash = signer.hash(transaction); @@ -102,8 +100,56 @@ Proto::SigningOutput Signer::signTransaction(const Proto::SigningInput& input) n return prepareOutput(encoded, transaction); } -Proto::SigningOutput Signer::signCreateValidator(const Proto::SigningInput& input) noexcept { +template +uint8_t Signer::getEnum() noexcept { + return INVALID_ENUM::value; +} + +template <> +uint8_t Signer::getEnum() noexcept { + return DirectiveCreateValidator; +} + +template <> +uint8_t Signer::getEnum() noexcept { + return DirectiveEditValidator; +} + +template <> +uint8_t Signer::getEnum() noexcept { + return DirectiveDelegate; +} + +template <> +uint8_t Signer::getEnum() noexcept { + return DirectiveUndelegate; +} + +template <> +uint8_t Signer::getEnum() noexcept { + return DirectiveCollectRewards; +} + +template +Proto::SigningOutput Signer::signStaking(const Proto::SigningInput &input, function func) { auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto stakingTx = buildUnsignedStakingTransaction(input, func); + + auto signer = Signer(uint256_t(load(input.chain_id()))); + auto hash = signer.hash(stakingTx); + signer.sign(key, hash, stakingTx); + auto encoded = signer.rlpNoHash(stakingTx, true); + + return prepareOutput>(encoded, stakingTx); +} + +CreateValidator Signer::buildUnsignedCreateValidator(const Proto::SigningInput &input) { + Address validatorAddr; + if (!Address::decode(input.staking_message().create_validator_message().validator_address(), + validatorAddr)) { + throw std::invalid_argument("Invalid address"); + } + auto description = Description( /* name */ input.staking_message().create_validator_message().description().name(), /* identity */ input.staking_message().create_validator_message().description().identity(), @@ -153,13 +199,8 @@ Proto::SigningOutput Signer::signCreateValidator(const Proto::SigningInput& inpu for (auto sig : input.staking_message().create_validator_message().slot_key_sigs()) { slotKeySigs.emplace_back(Data(sig.begin(), sig.end())); } - Address validatorAddr; - if (!Address::decode(input.staking_message().create_validator_message().validator_address(), - validatorAddr)) { - // invalid to address - return Proto::SigningOutput(); - } - auto createValidator = CreateValidator( + + return CreateValidator( /* ValidatorAddress */ validatorAddr, /* Description */ description, /* Commission */ commissionRates, @@ -170,22 +211,15 @@ Proto::SigningOutput Signer::signCreateValidator(const Proto::SigningInput& inpu /* PubKey */ slotPubKeys, /* BlsSig */ slotKeySigs, /* Amount */ load(input.staking_message().create_validator_message().amount())); - - auto stakingTx = Staking( - DirectiveCreateValidator, createValidator, load(input.staking_message().nonce()), - load(input.staking_message().gas_price()), load(input.staking_message().gas_limit()), - load(input.chain_id()), 0, 0); - - auto signer = Signer(uint256_t(load(input.chain_id()))); - auto hash = signer.hash(stakingTx); - signer.sign(key, hash, stakingTx); - auto encoded = signer.rlpNoHash(stakingTx, true); - - return prepareOutput>(encoded, stakingTx); } -Proto::SigningOutput Signer::signEditValidator(const Proto::SigningInput& input) noexcept { - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); +EditValidator Signer::buildUnsignedEditValidator(const Proto::SigningInput &input) { + + Address validatorAddr; + if (!Address::decode(input.staking_message().edit_validator_message().validator_address(), + validatorAddr)) { + throw std::invalid_argument("Invalid address"); + } auto description = Description( /* name */ input.staking_message().edit_validator_message().description().name(), @@ -204,13 +238,7 @@ Proto::SigningOutput Signer::signEditValidator(const Proto::SigningInput& input) load(input.staking_message().edit_validator_message().commission_rate().precision())); } - Address validatorAddr; - if (!Address::decode(input.staking_message().edit_validator_message().validator_address(), - validatorAddr)) { - // invalid to address - return Proto::SigningOutput(); - } - auto editValidator = EditValidator( + return EditValidator( /* ValidatorAddress */ validatorAddr, /* Description */ description, /* CommissionRate */ commissionRate, @@ -229,101 +257,45 @@ Proto::SigningOutput Signer::signEditValidator(const Proto::SigningInput& input) input.staking_message().edit_validator_message().slot_key_to_add_sig().end()), /* Active */ load(input.staking_message().edit_validator_message().active())); - - auto stakingTx = Staking( - DirectiveEditValidator, editValidator, load(input.staking_message().nonce()), - load(input.staking_message().gas_price()), load(input.staking_message().gas_limit()), - load(input.chain_id()), 0, 0); - - auto signer = Signer(uint256_t(load(input.chain_id()))); - auto hash = signer.hash(stakingTx); - signer.sign(key, hash, stakingTx); - auto encoded = signer.rlpNoHash(stakingTx, true); - - return prepareOutput>(encoded, stakingTx); } -Proto::SigningOutput Signer::signDelegate(const Proto::SigningInput& input) noexcept { - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - +Delegate Signer::buildUnsignedDelegate(const Proto::SigningInput &input) { Address delegatorAddr; if (!Address::decode(input.staking_message().delegate_message().delegator_address(), delegatorAddr)) { - // invalid to address - return Proto::SigningOutput(); + throw std::invalid_argument("Invalid address"); } Address validatorAddr; if (!Address::decode(input.staking_message().delegate_message().validator_address(), validatorAddr)) { - // invalid to address - return Proto::SigningOutput(); + throw std::invalid_argument("Invalid address"); } - auto delegate = Delegate(delegatorAddr, validatorAddr, - load(input.staking_message().delegate_message().amount())); - auto stakingTx = - Staking(DirectiveDelegate, delegate, load(input.staking_message().nonce()), - load(input.staking_message().gas_price()), - load(input.staking_message().gas_limit()), load(input.chain_id()), 0, 0); - - auto signer = Signer(uint256_t(load(input.chain_id()))); - auto hash = signer.hash(stakingTx); - signer.sign(key, hash, stakingTx); - auto encoded = signer.rlpNoHash(stakingTx, true); - - return prepareOutput>(encoded, stakingTx); + return Delegate(delegatorAddr, validatorAddr, + load(input.staking_message().delegate_message().amount())); } -Proto::SigningOutput Signer::signUndelegate(const Proto::SigningInput& input) noexcept { - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - +Undelegate Signer::buildUnsignedUndelegate(const Proto::SigningInput &input) { Address delegatorAddr; if (!Address::decode(input.staking_message().undelegate_message().delegator_address(), delegatorAddr)) { - // invalid to address - return Proto::SigningOutput(); + throw std::invalid_argument("Invalid address"); } Address validatorAddr; if (!Address::decode(input.staking_message().undelegate_message().validator_address(), validatorAddr)) { - // invalid to address - return Proto::SigningOutput(); + throw std::invalid_argument("Invalid address"); } - auto undelegate = Undelegate(delegatorAddr, validatorAddr, - load(input.staking_message().undelegate_message().amount())); - auto stakingTx = Staking( - DirectiveUndelegate, undelegate, load(input.staking_message().nonce()), - load(input.staking_message().gas_price()), load(input.staking_message().gas_limit()), - load(input.chain_id()), 0, 0); - - auto signer = Signer(uint256_t(load(input.chain_id()))); - auto hash = signer.hash(stakingTx); - signer.sign(key, hash, stakingTx); - auto encoded = signer.rlpNoHash(stakingTx, true); - - return prepareOutput>(encoded, stakingTx); + return Undelegate(delegatorAddr, validatorAddr, + load(input.staking_message().undelegate_message().amount())); } -Proto::SigningOutput Signer::signCollectRewards(const Proto::SigningInput& input) noexcept { - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - +CollectRewards Signer::buildUnsignedCollectRewards(const Proto::SigningInput &input) { Address delegatorAddr; if (!Address::decode(input.staking_message().collect_rewards().delegator_address(), delegatorAddr)) { - // invalid to address - return Proto::SigningOutput(); + throw std::invalid_argument("Invalid address"); } - auto collectRewards = CollectRewards(delegatorAddr); - auto stakingTx = Staking( - DirectiveCollectRewards, collectRewards, load(input.staking_message().nonce()), - load(input.staking_message().gas_price()), load(input.staking_message().gas_limit()), - load(input.chain_id()), 0, 0); - - auto signer = Signer(uint256_t(load(input.chain_id()))); - auto hash = signer.hash(stakingTx); - signer.sign(key, hash, stakingTx); - auto encoded = signer.rlpNoHash(stakingTx, true); - - return prepareOutput>(encoded, stakingTx); + return CollectRewards(delegatorAddr); } template @@ -508,4 +480,121 @@ Data Signer::hash(const Staking& transaction) const noexcept { return Hash::keccak256(rlpNoHash(transaction, false)); } +Data Signer::buildUnsignedTxBytes(const Proto::SigningInput &input) { + if (input.has_transaction_message()) { + Transaction transaction = Signer::buildUnsignedTransaction(input); + return rlpNoHash(transaction, false); + } + + if (input.has_staking_message()) { + auto stakingMessage = input.staking_message(); + + if (stakingMessage.has_create_validator_message()) { + auto tx = Signer::buildUnsignedStakingTransaction(input, &Signer::buildUnsignedCreateValidator); + return rlpNoHash(tx, false); + } + if (stakingMessage.has_edit_validator_message()) { + auto tx = Signer::buildUnsignedStakingTransaction(input, &Signer::buildUnsignedEditValidator); + return rlpNoHash(tx, false); + } + if (stakingMessage.has_delegate_message()) { + auto tx = Signer::buildUnsignedStakingTransaction(input, &Signer::buildUnsignedDelegate); + return rlpNoHash(tx, false); + } + if (stakingMessage.has_undelegate_message()) { + auto tx = Signer::buildUnsignedStakingTransaction(input, &Signer::buildUnsignedUndelegate); + return rlpNoHash(tx, false); + } + if (stakingMessage.has_collect_rewards()) { + auto tx = Signer::buildUnsignedStakingTransaction(input, &Signer::buildUnsignedCollectRewards); + return rlpNoHash(tx, false); + } + } + + throw std::invalid_argument("Invalid message"); +} + +Proto::SigningOutput Signer::buildSigningOutput(const Proto::SigningInput &input, const Data &signature) { + if (input.has_transaction_message()) { + Transaction transaction = Signer::buildUnsignedTransaction(input); + + auto tuple = values(chainID, signature); + transaction.r = std::get<0>(tuple); + transaction.s = std::get<1>(tuple); + transaction.v = std::get<2>(tuple); + + auto encoded = rlpNoHash(transaction, true); + return prepareOutput(encoded, transaction); + } + + if (input.has_staking_message()) { + auto stakingMessage = input.staking_message(); + + if (stakingMessage.has_create_validator_message()) { + return buildStakingSigningOutput(input, signature, &Signer::buildUnsignedCreateValidator); + } + if (stakingMessage.has_edit_validator_message()) { + return buildStakingSigningOutput(input, signature, &Signer::buildUnsignedEditValidator); + + } + if (stakingMessage.has_delegate_message()) { + return buildStakingSigningOutput(input, signature, &Signer::buildUnsignedDelegate); + + } + if (stakingMessage.has_undelegate_message()) { + return buildStakingSigningOutput(input, signature, &Signer::buildUnsignedUndelegate); + } + if (stakingMessage.has_collect_rewards()) { + return buildStakingSigningOutput(input, signature, &Signer::buildUnsignedCollectRewards); + } + } + + throw std::invalid_argument("Invalid message"); +} + +Transaction Signer::buildUnsignedTransaction(const Proto::SigningInput &input) { + if (input.message_oneof_case() != Proto::SigningInput::kTransactionMessage) { + throw std::invalid_argument("Invalid message"); + } + + auto transactionMessage = input.transaction_message(); + + Transaction transaction; + + Address toAddr; + if (!Address::decode(transactionMessage.to_address(), transaction.to)) { + throw std::invalid_argument("Invalid address"); + } + + transaction.nonce = load(transactionMessage.nonce()); + transaction.gasPrice = load(transactionMessage.gas_price()); + transaction.gasLimit = load(transactionMessage.gas_limit()); + transaction.amount = load(transactionMessage.amount()); + transaction.fromShardID = load(transactionMessage.from_shard_id()); + transaction.toShardID = load(transactionMessage.to_shard_id()); + transaction.payload = Data(transactionMessage.payload().begin(), transactionMessage.payload().end()); + return transaction; +} + +template +Staking Signer::buildUnsignedStakingTransaction(const Proto::SigningInput &input, function func) { + auto tx = func(input); + return Staking( + getEnum(), tx, load(input.staking_message().nonce()), + load(input.staking_message().gas_price()), load(input.staking_message().gas_limit()), + load(input.chain_id()), 0, 0); +} + +template +Proto::SigningOutput Signer::buildStakingSigningOutput(const Proto::SigningInput &input, const Data &signature, function func) { + auto tx = Signer::buildUnsignedStakingTransaction(input, func); + auto tuple = values(chainID, signature); + + tx.r = std::get<0>(tuple); + tx.s = std::get<1>(tuple); + tx.v = std::get<2>(tuple); + auto encoded = rlpNoHash(tx, true); + return prepareOutput>(encoded, tx); +} + } // namespace TW::Harmony diff --git a/src/Harmony/Signer.h b/src/Harmony/Signer.h index e914485c78f..71a45b162b1 100644 --- a/src/Harmony/Signer.h +++ b/src/Harmony/Signer.h @@ -30,22 +30,26 @@ class Signer { private: static Proto::SigningOutput - signTransaction(const Proto::SigningInput &input) noexcept; + signTransaction(const Proto::SigningInput &input); - static Proto::SigningOutput - signCreateValidator(const Proto::SigningInput &input) noexcept; - - static Proto::SigningOutput - signEditValidator(const Proto::SigningInput &input) noexcept; + template + static Proto::SigningOutput signStaking(const Proto::SigningInput &input, function func); - static Proto::SigningOutput - signDelegate(const Proto::SigningInput &input) noexcept; + template + static uint8_t getEnum() noexcept; - static Proto::SigningOutput - signUndelegate(const Proto::SigningInput &input) noexcept; + template + static Staking buildUnsignedStakingTransaction(const Proto::SigningInput &input, function func); + + template + Proto::SigningOutput buildStakingSigningOutput(const Proto::SigningInput &input, const Data &signature, function func); - static Proto::SigningOutput - signCollectRewards(const Proto::SigningInput &input) noexcept; + static Transaction buildUnsignedTransaction(const Proto::SigningInput &input); + static CreateValidator buildUnsignedCreateValidator(const Proto::SigningInput &input); + static EditValidator buildUnsignedEditValidator(const Proto::SigningInput &input); + static Delegate buildUnsignedDelegate(const Proto::SigningInput &input); + static Undelegate buildUnsignedUndelegate(const Proto::SigningInput &input); + static CollectRewards buildUnsignedCollectRewards(const Proto::SigningInput &input); public: uint256_t chainID; @@ -54,29 +58,32 @@ class Signer { explicit Signer(uint256_t chainID) : chainID(std::move(chainID)) {} template - static Proto::SigningOutput prepareOutput(const Data& encoded, const T &transaction) noexcept; + static Proto::SigningOutput prepareOutput(const Data &encoded, const T &transaction) noexcept; /// Signs the given transaction. template - void sign(const PrivateKey &privateKey, const Data& hash, T &transaction) const noexcept; + void sign(const PrivateKey &privateKey, const Data &hash, T &transaction) const noexcept; /// Signs a hash with the given private key for the given chain identifier. /// /// \returns the r, s, and v values of the transaction signature static std::tuple - sign(const uint256_t &chainID, const PrivateKey &privateKey, const Data& hash) noexcept; + sign(const uint256_t &chainID, const PrivateKey &privateKey, const Data &hash) noexcept; /// R, S, and V values for the given chain identifier and signature. /// /// \returns the r, s, and v values of the transaction signature static std::tuple values(const uint256_t &chainID, - const Data& signature) noexcept; + const Data &signature) noexcept; std::string txnAsRLPHex(Transaction &transaction) const noexcept; template std::string txnAsRLPHex(Staking &transaction) const noexcept; + Data buildUnsignedTxBytes(const Proto::SigningInput &input); + Proto::SigningOutput buildSigningOutput(const Proto::SigningInput &input, const Data &signature); + protected: /// Computes the transaction hash. Data hash(const Transaction &transaction) const noexcept; diff --git a/src/Harmony/Transaction.h b/src/Harmony/Transaction.h index 2badc8951c0..5e568884fa9 100644 --- a/src/Harmony/Transaction.h +++ b/src/Harmony/Transaction.h @@ -31,6 +31,8 @@ class Transaction { uint256_t r = uint256_t(); uint256_t s = uint256_t(); + Transaction() = default; + Transaction(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, uint256_t fromShardID, uint256_t toShardID, Address to, uint256_t amount, const Data& payload) : nonce(std::move(nonce)) diff --git a/src/IOST/Account.cpp b/src/IOST/Account.cpp new file mode 100644 index 00000000000..becf670007b --- /dev/null +++ b/src/IOST/Account.cpp @@ -0,0 +1,66 @@ +// Copyright © 2017-2023 Trust. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Account.h" +#include "../Base58.h" +#include "../Base58Address.h" + +using namespace TW; +using namespace TW::IOST; + +bool isAccountValid(const std::string& account) { + if (account.size() < 5 || account.size() > 11) { + return false; + } + for (char ch : account) { + if ((ch < 'a' || ch > 'z') && (ch < '0' || ch > '9') && ch != '_') { + return false; + } + } + return true; +} + +bool isBase58AddressValid(const std::string& address) { + auto decoded = Base58::decode(address); + return decoded.size() == Base58Address<32>::size; +} + +bool Account::isValid(const std::string& s) { + return isAccountValid(s) ? true : isBase58AddressValid(s); +} + +std::string Account::encodePubKey(const PublicKey& publicKey) { + return Base58::encode(publicKey.bytes); +} + +Account::Account(const Proto::AccountInfo& account) { + name = account.name(); + if (account.active_key() != "") { + auto activeKeyBytesPtr = account.active_key().begin(); + activeKey = Data(activeKeyBytesPtr, activeKeyBytesPtr + PrivateKey::_size); + } + if (account.owner_key() != "") { + auto ownerKeyBytesPtr = account.owner_key().begin(); + ownerKey = Data(ownerKeyBytesPtr, ownerKeyBytesPtr + PrivateKey::_size); + } +} + +Data Account::sign(const Data& digest, TWCurve curve) const { + return PrivateKey(activeKey).sign(digest, curve); +} + +Data Account::publicActiveKey() const { + return PrivateKey(activeKey).getPublicKey(TWPublicKeyTypeED25519).bytes; +} + +Data Account::publicOwnerKey() const { + return PrivateKey(ownerKey).getPublicKey(TWPublicKeyTypeED25519).bytes; +} + +std::string Account::address(const std::string& publickey) { + auto publicKeyData = TW::data(publickey); + return Base58::encode(publicKeyData); +} diff --git a/src/IOST/Account.h b/src/IOST/Account.h new file mode 100644 index 00000000000..3e9ab7b01a8 --- /dev/null +++ b/src/IOST/Account.h @@ -0,0 +1,43 @@ +// Copyright © 2017-2023 Trust. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "../PublicKey.h" +#include "../PrivateKey.h" +#include "../proto/IOST.pb.h" + +#include + +namespace TW::IOST { + +class Account { +public: + static bool isValid(const std::string& name); + static std::string encodePubKey(const PublicKey& publicKey); + static std::string address(const std::string& string); + Account(std::string name): name(std::move(name)) {} + Account([[maybe_unused]] const PublicKey& publicKey) {} + Account(const Proto::AccountInfo& account); + std::string string() const { return name; } + Data sign(const Data& digest, TWCurve curve) const; + Data publicActiveKey() const; + Data publicOwnerKey() const; + std::string name; + Data activeKey; + Data ownerKey; +}; + +inline bool operator==(const Account& lhs, const Account& rhs) { + return lhs.name == rhs.name; +} + +} // namespace TW::IOST + +/// Wrapper for C interface. +struct TWIOSTAccount { + TW::IOST::Account impl; +}; diff --git a/src/IOST/Entry.cpp b/src/IOST/Entry.cpp new file mode 100644 index 00000000000..e27ef441c71 --- /dev/null +++ b/src/IOST/Entry.cpp @@ -0,0 +1,56 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Entry.h" +#include "Account.h" +#include "Signer.h" +#include "../Base58.h" +#include "../proto/Common.pb.h" +#include "../proto/TransactionCompiler.pb.h" + +using namespace TW; +using namespace std; + +namespace TW::IOST { + +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const string& address, [[maybe_unused]] const PrefixVariant& addressPrefixp) const { + return Account::isValid(address); +} + +string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { + return Account::encodePubKey(publicKey); +} + +TW::Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& str) const { + return {Base58::decode(str)}; +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + signTemplate(dataIn, dataOut); +} + +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + const auto signer = Signer(input); + auto preImage = signer.signaturePreimage(); + auto preImageHash = Hash::sha3_256(preImage); + output.set_data_hash(preImageHash.data(), preImageHash.size()); + output.set_data(preImage.data(), preImage.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [](const auto& input, auto& output, const auto& signature, const auto& publicKey) { + const auto signer = Signer(input); + output = signer.compile(signature, publicKey); + }); +} + +} // namespace TW::IOST diff --git a/src/IOST/Entry.h b/src/IOST/Entry.h new file mode 100644 index 00000000000..65ba8aa8221 --- /dev/null +++ b/src/IOST/Entry.h @@ -0,0 +1,29 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "../CoinEntry.h" + +namespace TW::IOST { + +/// Entry point for implementation of IOST coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific +/// includes in this file. +class Entry final : public CoinEntry { + public: + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefixp) const override; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override; + Data addressToData(TWCoinType coin, const std::string& address) const override; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; + void compile(TWCoinType coin, const Data& txInputData, + const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const override; +}; + +} // namespace TW::IOST diff --git a/src/IOST/Signer.cpp b/src/IOST/Signer.cpp new file mode 100644 index 00000000000..e9aa1ccddaa --- /dev/null +++ b/src/IOST/Signer.cpp @@ -0,0 +1,188 @@ +// Copyright © 2017-2023 Trust. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Signer.h" +#include "Account.h" +#include "../BinaryCoding.h" +#include "../Hash.h" +#include "../PrivateKey.h" +#include +#include +#include +#include + +using namespace TW; +using namespace TW::IOST; + +class IOSTEncoder { + public: + IOSTEncoder() = default; + void WriteByte(uint8_t b) { buffer << b; } + void WriteInt32(uint32_t i) { + std::vector data; + encode32BE(i, data); + for (auto b : data) { + buffer << b; + } + } + + void WriteInt64(uint64_t i) { + std::vector data; + encode64BE(i, data); + for (auto b : data) { + buffer << b; + } + } + + void WriteString(const std::string& s) { + WriteInt32(static_cast(s.size())); + buffer << s; + } + + void WriteStringSlice(const std::vector v) { + WriteInt32(static_cast(v.size())); + for (auto& s : v) { + WriteString(s); + } + } + + std::string AsString() { return buffer.str(); } + + private: + std::stringstream buffer; +}; + +std::string Signer::encodeTransaction(const Proto::Transaction& t) noexcept { + IOSTEncoder se; + se.WriteInt64(t.time()); + se.WriteInt64(t.expiration()); + se.WriteInt64(int64_t(t.gas_ratio() * 100)); + se.WriteInt64(int64_t(t.gas_limit() * 100)); + se.WriteInt64(t.delay()); + se.WriteInt32(int32_t(t.chain_id())); + se.WriteString(""); + + std::vector svec; + for (auto& item : t.signers()) { + svec.push_back(item); + } + se.WriteStringSlice(svec); + + se.WriteInt32(t.actions_size()); + for (auto& a : t.actions()) { + IOSTEncoder s; + s.WriteString(a.contract()); + s.WriteString(a.action_name()); + s.WriteString(a.data()); + se.WriteString(s.AsString()); + } + + se.WriteInt32(t.amount_limit_size()); + for (auto& a : t.amount_limit()) { + IOSTEncoder s; + s.WriteString(a.token()); + s.WriteString(a.value()); + se.WriteString(s.AsString()); + } + + se.WriteInt32(t.signatures_size()); + for (auto& sig : t.signatures()) { + IOSTEncoder s; + s.WriteByte(static_cast(sig.algorithm())); + s.WriteString(sig.signature()); + s.WriteString(sig.public_key()); + se.WriteString(s.AsString()); + } + + return se.AsString(); +} + +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { + auto t = input.transaction_template(); + + if (t.actions_size() == 0) { + t.add_actions(); + auto* action = t.mutable_actions(0); + action->set_contract("token.iost"); + action->set_action_name("transfer"); + + std::string token = "iost"; + std::string src = input.account().name(); + std::string dst = input.transfer_destination(); + std::string amount = input.transfer_amount(); + std::string memo = input.transfer_memo(); + + nlohmann::json j; + j.push_back(token); + j.push_back(src); + j.push_back(dst); + j.push_back(amount); + j.push_back(memo); + std::string data = j.dump(); + action->set_data(data); + } + + Account acc(input.account()); + auto pubkey = acc.publicActiveKey(); + std::string pubkeyStr(pubkey.begin(), pubkey.end()); + + t.add_publisher_sigs(); + auto* sig = t.mutable_publisher_sigs(0); + sig->set_algorithm(Proto::Algorithm::ED25519); + sig->set_public_key(pubkeyStr); + auto signature = acc.sign(Hash::sha3_256(encodeTransaction(t)), TWCurveED25519); + std::string signatureStr(signature.begin(), signature.end()); + sig->set_signature(signatureStr); + + Proto::SigningOutput protoOutput; + protoOutput.mutable_transaction()->CopyFrom(t); + std::string transactionJson; + google::protobuf::util::JsonOptions jsonOptions; + jsonOptions.always_print_enums_as_ints = true; + jsonOptions.preserve_proto_field_names = true; + google::protobuf::util::MessageToJsonString(t, &transactionJson, jsonOptions); + protoOutput.set_encoded(transactionJson); + return protoOutput; +} + +Data Signer::signaturePreimage() const { + return data(encodeTransaction(input.transaction_template())); +} + +Proto::SigningOutput Signer::compile(const Data& signature, const PublicKey& publicKey) const { + Proto::SigningOutput output; + // validate public key + if (publicKey.type != TWPublicKeyTypeED25519) { + throw std::invalid_argument("Invalid public key"); + } + { + // validate correctness of signature + const auto hash = Hash::sha3_256(this->signaturePreimage()); + if (!publicKey.verify(signature, hash)) { + throw std::invalid_argument("Invalid signature/hash/publickey combination"); + } + } + auto t = input.transaction_template(); + + std::string pubkeyStr(publicKey.bytes.begin(), publicKey.bytes.end()); + + t.add_publisher_sigs(); + auto* sig = t.mutable_publisher_sigs(0); + sig->set_algorithm(Proto::Algorithm(Proto::ED25519)); + sig->set_public_key(pubkeyStr); + std::string signatureStr(signature.begin(), signature.end()); + sig->set_signature(signatureStr); + + std::string transactionJson; + google::protobuf::util::JsonOptions jsonOptions; + jsonOptions.always_print_enums_as_ints = true; + jsonOptions.preserve_proto_field_names = true; + google::protobuf::util::MessageToJsonString(t, &transactionJson, jsonOptions); + + output.mutable_transaction()->CopyFrom(t); + output.set_encoded(transactionJson); + return output; +} diff --git a/src/IOST/Signer.h b/src/IOST/Signer.h new file mode 100644 index 00000000000..fe53d41b5f1 --- /dev/null +++ b/src/IOST/Signer.h @@ -0,0 +1,26 @@ +// Copyright © 2017-2023 Trust. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "../PrivateKey.h" +#include "../proto/IOST.pb.h" + +namespace TW::IOST { +class Signer { + public: + Proto::SigningInput input; + + /// Initializes a transaction signer. + explicit Signer(const Proto::SigningInput& input) : input(input) {} + + static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; + static std::string encodeTransaction(const Proto::Transaction& t) noexcept; + + Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey) const; + Data signaturePreimage() const; +}; +} // namespace TW::IOST diff --git a/src/Icon/Entry.cpp b/src/Icon/Entry.cpp index c5154b04ae0..51cb9601bd4 100644 --- a/src/Icon/Entry.cpp +++ b/src/Icon/Entry.cpp @@ -6,6 +6,7 @@ #include "Entry.h" +#include "../proto/TransactionCompiler.pb.h" #include "Address.h" #include "Signer.h" @@ -25,4 +26,33 @@ void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::D signTemplate(dataIn, dataOut); } +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + Signer signer(input); + auto preImage = signer.preImage(); + auto preImageHash = signer.hashImage(Data(preImage.begin(), preImage.end())); + output.set_data_hash(preImageHash.data(), preImageHash.size()); + output.set_data(preImage.data(), preImage.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, [[maybe_unused]] const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() == 0) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return; + } + if (signatures.size() > 1) { + output.set_error(Common::Proto::Error_no_support_n2n); + output.set_error_message(Common::Proto::SigningError_Name(Common::Proto::Error_no_support_n2n)); + return; + } + auto signedTx = Signer(input).encode(signatures[0]); + output.set_encoded(signedTx.data(), signedTx.size()); + }); +} + } // namespace TW::Icon diff --git a/src/Icon/Entry.h b/src/Icon/Entry.h index 2044d50a119..78bd64b2f01 100644 --- a/src/Icon/Entry.h +++ b/src/Icon/Entry.h @@ -17,6 +17,9 @@ class Entry final : public CoinEntry { bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Icon diff --git a/src/Icon/Signer.cpp b/src/Icon/Signer.cpp index 5e5da66f3c1..d8a55cd82b8 100644 --- a/src/Icon/Signer.cpp +++ b/src/Icon/Signer.cpp @@ -56,6 +56,10 @@ std::string Signer::preImage() const noexcept { return txHash; } +TW::Data Signer::hashImage(const Data& image) const { + return Hash::sha3_256(image); +} + std::string Signer::encode(const Data& signature) const noexcept { auto json = nlohmann::json(); json["from"] = input.from_address(); diff --git a/src/Icon/Signer.h b/src/Icon/Signer.h index d51112a1e0b..7fb34c96647 100644 --- a/src/Icon/Signer.h +++ b/src/Icon/Signer.h @@ -32,6 +32,8 @@ class Signer { /// Encodes a signed transaction as JSON. std::string encode(const Data& signature) const noexcept; + TW::Data hashImage(const Data& image) const; + private: std::map parameters() const noexcept; }; diff --git a/src/IoTeX/Entry.cpp b/src/IoTeX/Entry.cpp index 314f7bbae8d..3d7b5628cb3 100644 --- a/src/IoTeX/Entry.cpp +++ b/src/IoTeX/Entry.cpp @@ -8,6 +8,7 @@ #include "Address.h" #include "Signer.h" +#include "../proto/TransactionCompiler.pb.h" using namespace std; @@ -27,4 +28,23 @@ void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::D signTemplate(dataIn, dataOut); } +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + Signer signer(input); + auto signHash = signer.hash(); + auto preImage = signer.signaturePreimage(); + output.set_data(preImage.data(), preImage.size()); + output.set_data_hash(signHash.data(), signHash.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [](const auto& input, auto& output, const auto& signature, const auto& publicKey) { + output = Signer::compile(input, signature, publicKey); + }); +} } // namespace TW::IoTeX diff --git a/src/IoTeX/Entry.h b/src/IoTeX/Entry.h index c5f9738511d..bd5c4e17307 100644 --- a/src/IoTeX/Entry.h +++ b/src/IoTeX/Entry.h @@ -15,8 +15,11 @@ namespace TW::IoTeX { class Entry final : public CoinEntry { public: bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::IoTeX diff --git a/src/IoTeX/Signer.cpp b/src/IoTeX/Signer.cpp index c655105a0a6..e1fa6d5d0b2 100644 --- a/src/IoTeX/Signer.cpp +++ b/src/IoTeX/Signer.cpp @@ -39,10 +39,31 @@ Proto::SigningOutput Signer::build() const { return output; } +Proto::SigningOutput Signer::compile(const Proto::SigningInput& input, const Data& signature, + const TW::PublicKey& pubKey) noexcept { + auto signer = Signer(input); + auto signedAction = Proto::Action(); + signedAction.mutable_core()->MergeFrom(signer.action); + + signedAction.set_senderpubkey(pubKey.bytes.data(), pubKey.bytes.size()); + signedAction.set_signature(signature.data(), signature.size()); + // build output + auto output = Proto::SigningOutput(); + auto serialized = signedAction.SerializeAsString(); + output.set_encoded(serialized); + auto h = Hash::keccak256(serialized); + output.set_hash(h.data(), h.size()); + return output; +} + Data Signer::hash() const { return Hash::keccak256(action.SerializeAsString()); } +std::string Signer::signaturePreimage() const { + return action.SerializeAsString(); +} + void Signer::toActionCore() { action.ParseFromString(input.SerializeAsString()); action.DiscardUnknownFields(); diff --git a/src/IoTeX/Signer.h b/src/IoTeX/Signer.h index 191c57f9642..7bcce3e28cb 100644 --- a/src/IoTeX/Signer.h +++ b/src/IoTeX/Signer.h @@ -7,8 +7,8 @@ #pragma once #include "Data.h" - #include "proto/IoTeX.pb.h" +#include "../PrivateKey.h" namespace TW::IoTeX { @@ -17,6 +17,8 @@ class Signer { public: /// Signs a Proto::SigningInput transaction static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; + /// Build the compile output + static Proto::SigningOutput compile(const Proto::SigningInput& input, const Data& signature, const TW::PublicKey& pubKey) noexcept; public: Proto::SigningInput input; Proto::ActionCore action; @@ -36,7 +38,8 @@ class Signer { /// Computes the transaction hash Data hash() const; - + /// Get PreImage transaction data + std::string signaturePreimage() const; protected: /// Converts to proto ActionCore from transaction input void toActionCore(); diff --git a/src/Kusama/Address.h b/src/Kusama/Address.h index 2e1936bcb72..8ae956b4164 100644 --- a/src/Kusama/Address.h +++ b/src/Kusama/Address.h @@ -27,4 +27,3 @@ class Address: public SS58Address { Address(const PublicKey& publicKey): SS58Address(publicKey, TWSS58AddressTypeKusama) {} }; } // namespace TW::Kusama - diff --git a/src/MultiversX/Entry.cpp b/src/MultiversX/Entry.cpp index f40284e203f..b92d236c050 100644 --- a/src/MultiversX/Entry.cpp +++ b/src/MultiversX/Entry.cpp @@ -5,9 +5,9 @@ // file LICENSE at the root of the source code distribution tree. #include "Entry.h" - #include "Address.h" #include "Signer.h" +#include using namespace TW; using namespace std; @@ -39,4 +39,21 @@ string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json return Signer::signJSON(json, key); } +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + auto unsignedTxBytes = Signer::buildUnsignedTxBytes(input); + output.set_data(unsignedTxBytes.data(), unsignedTxBytes.size()); + output.set_data_hash(unsignedTxBytes.data(), unsignedTxBytes.size()); }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [](const auto& input, auto& output, const auto& signature, [[maybe_unused]] const auto& publicKey) { + output = Signer::buildSigningOutput(input, signature); + }); +} + } // namespace TW::MultiversX diff --git a/src/MultiversX/Entry.h b/src/MultiversX/Entry.h index 4ecd848ff19..6300cb2e906 100644 --- a/src/MultiversX/Entry.h +++ b/src/MultiversX/Entry.h @@ -20,6 +20,9 @@ class Entry final : public CoinEntry { void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; bool supportsJSONSigning() const { return true; } std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::MultiversX diff --git a/src/MultiversX/Signer.cpp b/src/MultiversX/Signer.cpp index 0bf0d562d79..60784a8d46b 100644 --- a/src/MultiversX/Signer.cpp +++ b/src/MultiversX/Signer.cpp @@ -17,18 +17,11 @@ namespace TW::MultiversX { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { TransactionFactory factory; - auto transaction = factory.create(input); auto privateKey = PrivateKey(input.private_key()); - auto signableAsString = serializeTransaction(transaction); - auto signableAsData = TW::data(signableAsString); + auto signableAsData = buildUnsignedTxBytes(input); auto signature = privateKey.sign(signableAsData, TWCurveED25519); - auto encodedSignature = hex(signature); - auto encoded = serializeSignedTransaction(transaction, encodedSignature); - auto protoOutput = Proto::SigningOutput(); - protoOutput.set_signature(encodedSignature); - protoOutput.set_encoded(encoded); - return protoOutput; + return buildSigningOutput(input, signature); } std::string Signer::signJSON(const std::string& json, const Data& key) { @@ -39,4 +32,26 @@ std::string Signer::signJSON(const std::string& json, const Data& key) { return output.encoded(); } +Data Signer::buildUnsignedTxBytes(const Proto::SigningInput &input) { + TransactionFactory factory; + auto transaction = factory.create(input); + auto signableAsString = serializeTransaction(transaction); + + auto signableAsData = TW::data(signableAsString); + return signableAsData; +} + +Proto::SigningOutput Signer::buildSigningOutput(const Proto::SigningInput &input, const Data &signature) { + TransactionFactory factory; + + auto transaction = factory.create(input); + auto encodedSignature = hex(signature); + auto encoded = serializeSignedTransaction(transaction, encodedSignature); + + auto protoOutput = Proto::SigningOutput(); + protoOutput.set_signature(encodedSignature); + protoOutput.set_encoded(encoded); + return protoOutput; +} + } // namespace TW::MultiversX diff --git a/src/MultiversX/Signer.h b/src/MultiversX/Signer.h index 8f1e416bbd1..a7c8ee34545 100644 --- a/src/MultiversX/Signer.h +++ b/src/MultiversX/Signer.h @@ -23,6 +23,9 @@ class Signer { /// Signs a json Proto::SigningInput with private key static std::string signJSON(const std::string& json, const Data& key); + + static Data buildUnsignedTxBytes(const Proto::SigningInput &input); + static Proto::SigningOutput buildSigningOutput(const Proto::SigningInput &input, const Data &signature); }; } // namespace TW::MultiversX diff --git a/src/MultiversX/TransactionFactory.cpp b/src/MultiversX/TransactionFactory.cpp index b2087186347..f1f2cf4eb0a 100644 --- a/src/MultiversX/TransactionFactory.cpp +++ b/src/MultiversX/TransactionFactory.cpp @@ -65,9 +65,10 @@ Transaction TransactionFactory::fromEGLDTransfer(const Proto::SigningInput& inpu transaction.receiverUsername = transfer.accounts().receiver_username(); transaction.guardian = transfer.accounts().guardian(); transaction.value = transfer.amount(); + transaction.data = transfer.data(); transaction.gasPrice = coalesceGasPrice(input.gas_price()); transaction.chainID = coalesceChainId(input.chain_id()); - transaction.version = TX_VERSION; + transaction.version = transfer.version() ? transfer.version() : TX_VERSION; transaction.options = decideOptions(transaction); // Estimate & set gasLimit: @@ -95,7 +96,7 @@ Transaction TransactionFactory::fromESDTTransfer(const Proto::SigningInput& inpu transaction.data = data; transaction.gasPrice = coalesceGasPrice(input.gas_price()); transaction.chainID = coalesceChainId(input.chain_id()); - transaction.version = TX_VERSION; + transaction.version = transfer.version() ? transfer.version() : TX_VERSION; transaction.options = decideOptions(transaction); // Estimate & set gasLimit: @@ -125,7 +126,7 @@ Transaction TransactionFactory::fromESDTNFTTransfer(const Proto::SigningInput& i transaction.data = data; transaction.gasPrice = coalesceGasPrice(input.gas_price()); transaction.chainID = coalesceChainId(input.chain_id()); - transaction.version = TX_VERSION; + transaction.version = transfer.version() ? transfer.version() : TX_VERSION; transaction.options = decideOptions(transaction); // Estimate & set gasLimit: diff --git a/src/NEAR/Entry.cpp b/src/NEAR/Entry.cpp index e14b041eed3..118ec62a06a 100644 --- a/src/NEAR/Entry.cpp +++ b/src/NEAR/Entry.cpp @@ -5,9 +5,11 @@ // file LICENSE at the root of the source code distribution tree. #include "Entry.h" - #include "Address.h" +#include "Serialization.h" #include "Signer.h" +#include "../proto/Common.pb.h" +#include "../proto/TransactionCompiler.pb.h" using namespace TW; using namespace std; @@ -37,4 +39,24 @@ void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::D signTemplate(dataIn, dataOut); } +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + const auto signer = Signer(input); + auto preImage = signer.signaturePreimage(); + auto preImageHash = Hash::sha256(preImage); + output.set_data_hash(preImageHash.data(), preImageHash.size()); + output.set_data(preImage.data(), preImage.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [](const auto& input, auto& output, const auto& signature, const auto& publicKey) { + const auto signer = Signer(input); + output = signer.compile(signature, publicKey); + }); +} } // namespace TW::NEAR diff --git a/src/NEAR/Entry.h b/src/NEAR/Entry.h index c852e6af7c8..2efa1b2c254 100644 --- a/src/NEAR/Entry.h +++ b/src/NEAR/Entry.h @@ -15,10 +15,15 @@ namespace TW::NEAR { class Entry final : public CoinEntry { public: bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; - std::string normalizeAddress(TWCoinType coin, const std::string& address) const; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; - Data addressToData(TWCoinType coin, const std::string& address) const; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + std::string normalizeAddress(TWCoinType coin, const std::string& address) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, + const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::NEAR diff --git a/src/NEAR/Serialization.cpp b/src/NEAR/Serialization.cpp index 0a7cc3f7f84..7a15d9bc052 100644 --- a/src/NEAR/Serialization.cpp +++ b/src/NEAR/Serialization.cpp @@ -182,6 +182,23 @@ Data transactionData(const Proto::SigningInput& input) { return data; } +Data transactionDataWithPublicKey(const Proto::SigningInput& input) { + Data data; + writeString(data, input.signer_id()); + auto public_key_proto = Proto::PublicKey(); + public_key_proto.set_data(input.public_key().data(), input.public_key().size()); + writePublicKey(data, public_key_proto); + writeU64(data, input.nonce()); + writeString(data, input.receiver_id()); + const auto& block_hash = input.block_hash(); + writeRawBuffer(data, block_hash); + writeU32(data, input.actions_size()); + for (const auto& action : input.actions()) { + writeAction(data, action); + } + return data; +} + Data signedTransactionData(const Data& transactionData, const Data& signatureData) { Data data; writeRawBuffer(data, transactionData); diff --git a/src/NEAR/Serialization.h b/src/NEAR/Serialization.h index dbcda14940d..4dd17fe840e 100644 --- a/src/NEAR/Serialization.h +++ b/src/NEAR/Serialization.h @@ -13,5 +13,5 @@ namespace TW::NEAR { Data transactionData(const Proto::SigningInput& input); Data signedTransactionData(const Data& transactionData, const Data& signatureData); - +Data transactionDataWithPublicKey(const Proto::SigningInput& input); } // namespace TW::NEAR diff --git a/src/NEAR/Signer.cpp b/src/NEAR/Signer.cpp index 13eb0c26237..d4a00c033e3 100644 --- a/src/NEAR/Signer.cpp +++ b/src/NEAR/Signer.cpp @@ -24,4 +24,26 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { return output; } +Data Signer::signaturePreimage() const { + return TW::NEAR::transactionDataWithPublicKey(input); +}; + +Proto::SigningOutput Signer::compile(const Data& signature, const PublicKey& publicKey) const { + // validate public key + if (publicKey.type != TWPublicKeyTypeED25519) { + throw std::invalid_argument("Invalid public key"); + } + auto preImage = signaturePreimage(); + auto hash = Hash::sha256(preImage); + { + // validate correctness of signature + if (!publicKey.verify(signature, hash)) { + throw std::invalid_argument("Invalid signature/hash/publickey combination"); + } + } + auto signedPreImage = TW::NEAR::signedTransactionData(preImage, signature); + auto output = Proto::SigningOutput(); + output.set_signed_transaction(signedPreImage.data(), signedPreImage.size()); + return output; +} } // namespace TW::NEAR diff --git a/src/NEAR/Signer.h b/src/NEAR/Signer.h index d2e5d852d66..aa4945c1d4d 100644 --- a/src/NEAR/Signer.h +++ b/src/NEAR/Signer.h @@ -7,16 +7,22 @@ #pragma once #include "../proto/NEAR.pb.h" +#include "../Data.h" +#include "../PublicKey.h" namespace TW::NEAR { /// Helper class that performs NEAR transaction signing. class Signer { public: + Proto::SigningInput input; Signer() = delete; - + /// Initializes a transaction signer. + explicit Signer(const Proto::SigningInput& input) : input(input) {} /// Signs the given transaction. static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; + Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey) const; + Data signaturePreimage() const; }; } // namespace TW::NEAR diff --git a/src/NEO/Address.cpp b/src/NEO/Address.cpp index e86ff358a29..e63298521cb 100644 --- a/src/NEO/Address.cpp +++ b/src/NEO/Address.cpp @@ -8,7 +8,6 @@ #include "../Base58.h" #include "Data.h" #include "../Hash.h" -#include "../Ontology/ParamsBuilder.h" #include "Address.h" @@ -46,11 +45,6 @@ Address::Address(const PublicKey& publicKey) { std::copy(keyHash.data(), keyHash.data() + Address::size, bytes.begin()); } -Address::Address(uint8_t m, const std::vector& publicKeys) { - auto builderData = toScriptHash(Ontology::ParamsBuilder::fromMultiPubkey(m, publicKeys)); - std::copy(builderData.begin(), builderData.end(), bytes.begin()); -} - Data Address::toScriptHash(const Data& data) const { return Hash::ripemd(Hash::sha256(data)); } diff --git a/src/NEO/Address.h b/src/NEO/Address.h index 6e2e6cb0aaa..450aabd3de8 100644 --- a/src/NEO/Address.h +++ b/src/NEO/Address.h @@ -31,9 +31,6 @@ class Address : public TW::Base58Address { /// Initializes a NEO address with a collection of bytes. explicit Address(const Data& data) : TW::Base58Address(data) {} - /// Initializes an address with a collection of public key. - explicit Address(uint8_t m, const std::vector& publicKeys); - /// Initializes a NEO address with a public key. explicit Address(const PublicKey &publicKey); @@ -49,4 +46,4 @@ inline bool operator==(const Address& lhs, const Address& rhs) { return lhs.string() == rhs.string(); } -} // namespace TW::NEO \ No newline at end of file +} // namespace TW::NEO diff --git a/src/NEO/BinaryCoding.h b/src/NEO/BinaryCoding.h new file mode 100644 index 00000000000..f152e600c3d --- /dev/null +++ b/src/NEO/BinaryCoding.h @@ -0,0 +1,45 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "OpCode.h" +#include "uint256.h" +#include "../BinaryCoding.h" + +namespace TW::NEO { + +// Append a uint256_t value as a little-endian byte array into the provided buffer, and limit +// the array size by digit/8. +// Noted: No padding with it. +inline void encode256LE(Data& data, const uint256_t& value) { + Data bytes = store(value); + data.insert(data.end(), bytes.rbegin(), bytes.rend()); + if (data.back() >= 128) { + data.push_back(0x00); + } +} + +inline void encodeBytes(Data& data, const Data& value) { + if (value.size() <= (size_t)PUSHBYTES75) { + data.push_back((byte)value.size()); + data.insert(data.end(), value.begin(), value.end()); + } else if (value.size() < 0x100) { + data.push_back(PUSHDATA1); + data.push_back((byte)value.size()); + data.insert(data.end(), value.begin(), value.end()); + } else if (value.size() < 0x10000) { + data.push_back(PUSHDATA2); + encode16LE((uint16_t)value.size(), data); + data.insert(data.end(), value.begin(), value.end()); + } else { + data.push_back(PUSHDATA4); + encode32LE((uint32_t)value.size(), data); + data.insert(data.end(), value.begin(), value.end()); + } +} + +} // namespace TW::NEO diff --git a/src/NEO/Entry.cpp b/src/NEO/Entry.cpp index 73e41120c9c..68158ba9653 100644 --- a/src/NEO/Entry.cpp +++ b/src/NEO/Entry.cpp @@ -8,6 +8,7 @@ #include "Address.h" #include "Signer.h" +#include "proto/TransactionCompiler.pb.h" using namespace TW; using namespace std; @@ -35,4 +36,28 @@ void Entry::plan([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::D planTemplate(dataIn, dataOut); } +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + auto encoded = Signer::signaturePreimage(input); + auto hash = TW::Hash::sha256(encoded); + output.set_data_hash(hash.data(), hash.size()); + output.set_data(encoded.data(), encoded.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.empty() || publicKeys.empty() || signatures.size() != publicKeys.size()) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return; + } + + auto encoded = Signer::encodeTransaction(input, publicKeys, signatures); + output.set_encoded(encoded.data(), encoded.size()); + }); +} + } // namespace TW::NEO diff --git a/src/NEO/Entry.h b/src/NEO/Entry.h index 7891de2b8f0..15d3e721aab 100644 --- a/src/NEO/Entry.h +++ b/src/NEO/Entry.h @@ -19,6 +19,9 @@ class Entry final: public CoinEntry { Data addressToData(TWCoinType coin, const std::string& address) const override; void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; }; } // namespace TW::NEO diff --git a/src/NEO/InvocationTransaction.h b/src/NEO/InvocationTransaction.h new file mode 100644 index 00000000000..e2d13641edc --- /dev/null +++ b/src/NEO/InvocationTransaction.h @@ -0,0 +1,46 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Transaction.h" +#include "../Data.h" + +namespace TW::NEO { + +class InvocationTransaction final: public Transaction { +public: + Data script; + uint64_t gas = 0; + + explicit InvocationTransaction(TransactionType t = TransactionType::TT_InvocationTransaction, byte ver = 1) + : Transaction(t, ver) {} + + size_t deserializeExclusiveData(const Data& data, size_t initial_pos) override { + uint32_t readBytes = 0; + script = readVarBytes(data, initial_pos, &readBytes); + if (version >= 1) { + gas = decode64LE(data.data() + initial_pos + readBytes); + readBytes += sizeof(gas); + } + return initial_pos + static_cast(readBytes); + } + + Data serializeExclusiveData() const override { + auto resp = writeVarBytes(script); + if (version >= 1) { + encode64LE(gas, resp); + } + return resp; + } + + bool operator==(const InvocationTransaction& other) const { + return this->script == other.script && this->gas == other.gas && + Transaction::operator==(other); + } +}; + +} // namespace TW::NEO diff --git a/src/NEO/OpCode.h b/src/NEO/OpCode.h index 0f5b3514ac2..d3633883e06 100644 --- a/src/NEO/OpCode.h +++ b/src/NEO/OpCode.h @@ -10,8 +10,22 @@ namespace TW::NEO { +static const uint8_t PUSHBYTES1{0x01}; static const uint8_t PUSHBYTES21{0x21}; static const uint8_t PUSHBYTES40{0x40}; +static const uint8_t PUSHBYTES75{0x4B}; +static const uint8_t PUSHDATA1{0x4C}; +static const uint8_t PUSHDATA2{0x4D}; +static const uint8_t PUSHDATA4{0x4E}; +static const uint8_t PUSH0{0x00}; +static const uint8_t PUSH1{0x51}; +static const uint8_t PUSH2{0x52}; +static const uint8_t PUSH3{0x53}; +static const uint8_t PUSH5{0x55}; +static const uint8_t RET{0x66}; +static const uint8_t APPCALL{0x67}; static const uint8_t CHECKSIG{0xAC}; +static const uint8_t PACK{0xC1}; +static const uint8_t THROWIFNOT{0xF1}; -} // namespace TW::NEO \ No newline at end of file +} // namespace TW::NEO diff --git a/src/NEO/Script.cpp b/src/NEO/Script.cpp index 5ae587882ec..9259bdc4710 100644 --- a/src/NEO/Script.cpp +++ b/src/NEO/Script.cpp @@ -4,6 +4,8 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. #include "Script.h" + +#include "BinaryCoding.h" #include "OpCode.h" namespace TW::NEO { @@ -23,4 +25,41 @@ Data Script::CreateInvocationScript(const Data& signature) { return result; } +Data Script::CreateNep5TransferScript(const Data& assetId, const Data& from, const Data& to, + uint256_t value, bool withRet /*= false*/) { + Data result; + + // handle value + if (value == uint256_t(0)) { + result.push_back(PUSH0); + } else if (value >= uint256_t(1) && value <= uint256_t(16)) { + result.push_back(PUSH1 - 1 + (byte)value); + } else { + Data v; + encode256LE(v, value); + result.push_back((byte)v.size()); + result.insert(result.end(), v.begin(), v.end()); + } + + encodeBytes(result, to); + encodeBytes(result, from); + + // args length + result.push_back(PUSH3); + + result.push_back(PACK); + + std::string operation = "transfer"; + encodeBytes(result, {operation.begin(), operation.end()}); + + result.push_back(APPCALL); + result.insert(result.end(), assetId.begin(), assetId.end()); + + if (withRet) { + result.push_back(THROWIFNOT); + result.push_back(RET); + } + return result; +} + } // namespace TW::NEO diff --git a/src/NEO/Script.h b/src/NEO/Script.h index 33b401b13fd..1c2d7d2e8d4 100644 --- a/src/NEO/Script.h +++ b/src/NEO/Script.h @@ -6,13 +6,15 @@ #pragma once #include "Data.h" +#include "uint256.h" namespace TW::NEO { class Script { - public: +public: static Data CreateSignatureRedeemScript(const Data& publicKey); static Data CreateInvocationScript(const Data& signature); + // nep5 assetId has only 20 bytes, different with gas & neo that are 32 bytes. + static Data CreateNep5TransferScript(const Data& assetId, const Data& from, const Data& to, uint256_t value, bool withRet = false); }; - } // namespace TW::NEO diff --git a/src/NEO/Signer.cpp b/src/NEO/Signer.cpp index d7a12e4c52d..ad8ff46ec08 100644 --- a/src/NEO/Signer.cpp +++ b/src/NEO/Signer.cpp @@ -5,8 +5,13 @@ // file LICENSE at the root of the source code distribution tree. #include "Signer.h" +#include "InvocationTransaction.h" #include "Script.h" #include "../HexCoding.h" +#include "../PrivateKey.h" +#include "../PublicKey.h" +#include "../proto/Common.pb.h" +#include "../proto/NEO.pb.h" using namespace std; using namespace TW; @@ -56,6 +61,9 @@ Proto::TransactionPlan Signer::plan(const Proto::SigningInput& input) { for (int i = 0; i < input.outputs_size(); i++) { required[input.outputs(i).asset_id()] = input.outputs(i).amount(); + for (int j = 0; j < input.outputs(i).extra_outputs_size(); j++) { + required[input.outputs(i).asset_id()] += input.outputs(i).extra_outputs(j).amount(); + } } for (int i = 0; i < input.inputs_size(); i++) { @@ -65,6 +73,7 @@ Proto::TransactionPlan Signer::plan(const Proto::SigningInput& input) { continue; } + // if the required has been enough, not need to add input if (input.inputs(i).asset_id() != input.gas_asset_id() && required[input.inputs(i).asset_id()] < available[input.inputs(i).asset_id()]) { continue; @@ -79,7 +88,7 @@ Proto::TransactionPlan Signer::plan(const Proto::SigningInput& input) { auto* outputPlan = plan.add_outputs(); if (available.find(input.outputs(i).asset_id()) == available.end() || - available[input.outputs(i).asset_id()] < input.outputs(i).amount()) { + available[input.outputs(i).asset_id()] < required[input.outputs(i).asset_id()]) { throw Common::Proto::SigningError(Common::Proto::Error_low_balance); } @@ -90,16 +99,25 @@ Proto::TransactionPlan Signer::plan(const Proto::SigningInput& input) { int64_t availableAmount = available[input.outputs(i).asset_id()]; outputPlan->set_available_amount(availableAmount); outputPlan->set_amount(input.outputs(i).amount()); - outputPlan->set_change(availableAmount - input.outputs(i).amount()); outputPlan->set_to_address(input.outputs(i).to_address()); outputPlan->set_asset_id(input.outputs(i).asset_id()); outputPlan->set_change_address(input.outputs(i).change_address()); + + auto changeAmount = availableAmount - input.outputs(i).amount(); + for (int j = 0; j < input.outputs(i).extra_outputs_size(); j++) { + auto* extra_plan = outputPlan->add_extra_outputs(); + + extra_plan->set_to_address(input.outputs(i).extra_outputs(j).to_address()); + extra_plan->set_amount(input.outputs(i).extra_outputs(j).amount()); + changeAmount -= input.outputs(i).extra_outputs(j).amount(); + } + outputPlan->set_change(changeAmount); } const int64_t SIGNATURE_SIZE = 103; int64_t transactionSize = - prepareUnsignedTransaction(input, plan, false).serialize().size() + SIGNATURE_SIZE; + prepareUnsignedTransaction(input, plan, false)->serialize().size() + SIGNATURE_SIZE; const int64_t LARGE_TX_SIZE = 1024; const int64_t MIN_FEE_FOR_LARGE_TX = 100000; @@ -130,7 +148,7 @@ Proto::TransactionPlan Signer::plan(const Proto::SigningInput& input) { if (feeNeed) { transactionSize = - prepareUnsignedTransaction(input, plan, false).serialize().size() + SIGNATURE_SIZE; + prepareUnsignedTransaction(input, plan, false)->serialize().size() + SIGNATURE_SIZE; int64_t fee = 0; if (transactionSize >= LARGE_TX_SIZE) { fee = MIN_FEE_FOR_LARGE_TX; @@ -148,12 +166,35 @@ Proto::TransactionPlan Signer::plan(const Proto::SigningInput& input) { return plan; } -Transaction Signer::prepareUnsignedTransaction(const Proto::SigningInput& input, - const Proto::TransactionPlan& plan, bool validate) { +std::shared_ptr Signer::prepareUnsignedTransaction(const Proto::SigningInput& input, + const Proto::TransactionPlan& plan, + bool validate) { + std::shared_ptr transaction; try { - auto transaction = Transaction(); - transaction.type = TransactionType::TT_ContractTransaction; - transaction.version = 0; + switch (input.transaction().transaction_oneof_case()) { + case Proto::Transaction::kNep5Transfer: { + auto t = std::make_shared(); + auto nep5Tx = input.transaction().nep5_transfer(); + t->script = Script::CreateNep5TransferScript( + parse_hex(nep5Tx.asset_id()), Address(nep5Tx.from()).toScriptHash(), + Address(nep5Tx.to()).toScriptHash(), load(nep5Tx.amount()), nep5Tx.script_with_ret()); + + transaction = t; + break; + } + case Proto::Transaction::kInvocationGeneric: { + auto t = std::make_shared(); + auto script = input.transaction().invocation_generic().script(); + t->script = Data(script.begin(), script.end()); + t->gas = input.transaction().invocation_generic().gas(); + + transaction = t; + break; + } + default: + transaction = std::make_shared(); + break; + } for (int i = 0; i < plan.inputs_size(); i++) { CoinReference coin; @@ -162,14 +203,19 @@ Transaction Signer::prepareUnsignedTransaction(const Proto::SigningInput& input, std::reverse(prevHashReverse.begin(), prevHashReverse.end()); coin.prevHash = load(prevHashReverse); coin.prevIndex = (uint16_t)plan.inputs(i).prev_index(); - transaction.inInputs.push_back(coin); + transaction->inInputs.push_back(coin); } for (int i = 0; i < plan.outputs_size(); i++) { if (plan.outputs(i).asset_id() == input.gas_asset_id()) { - if (validate && plan.outputs(i).amount() + plan.outputs(i).change() + plan.fee() != - plan.outputs(i).available_amount()) { - throw Common::Proto::SigningError(Common::Proto::Error_wrong_fee); + if (validate) { + auto sumAmount = plan.outputs(i).amount() + plan.outputs(i).change() + plan.fee(); + for (int j = 0; j < plan.outputs(i).extra_outputs_size(); j++) { + sumAmount += plan.outputs(i).extra_outputs(j).amount(); + } + if (sumAmount != plan.outputs(i).available_amount()) { + throw Common::Proto::SigningError(Common::Proto::Error_wrong_fee); + } } } @@ -179,7 +225,16 @@ Transaction Signer::prepareUnsignedTransaction(const Proto::SigningInput& input, out.value = (int64_t)plan.outputs(i).amount(); auto scriptHash = TW::NEO::Address(plan.outputs(i).to_address()).toScriptHash(); out.scriptHash = load(scriptHash); - transaction.outputs.push_back(out); + transaction->outputs.push_back(out); + + for (int j = 0; j < plan.outputs(i).extra_outputs_size(); j++) { + TransactionOutput extraOut; + extraOut.assetId = load(parse_hex(plan.outputs(i).asset_id())); + extraOut.value = (int64_t)plan.outputs(i).extra_outputs(j).amount(); + auto extraScriptHash = TW::NEO::Address(plan.outputs(i).extra_outputs(j).to_address()).toScriptHash(); + extraOut.scriptHash = load(extraScriptHash); + transaction->outputs.push_back(extraOut); + } } // change @@ -189,20 +244,29 @@ Transaction Signer::prepareUnsignedTransaction(const Proto::SigningInput& input, out.value = plan.outputs(i).change(); auto scriptHash = TW::NEO::Address(plan.outputs(i).change_address()).toScriptHash(); out.scriptHash = load(scriptHash); - transaction.outputs.push_back(out); + transaction->outputs.push_back(out); } } + + for (int i = 0; i < plan.attributes_size(); i++) { + TransactionAttribute attr; + attr.usage = (TransactionAttributeUsage)plan.attributes(i).usage(); + attr._data.assign(plan.attributes(i).data().begin(), plan.attributes(i).data().end()); + + transaction->attributes.push_back(attr); + } return transaction; } catch (...) { } - return Transaction(); + return transaction; } Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto output = Proto::SigningOutput(); try { - auto signer = Signer(PrivateKey(Data(input.private_key().begin(), input.private_key().end()))); + auto signer = + Signer(PrivateKey(Data(input.private_key().begin(), input.private_key().end()))); Proto::TransactionPlan plan; if (input.has_plan()) { plan = input.plan(); @@ -210,8 +274,8 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { plan = signer.plan(input); } auto transaction = prepareUnsignedTransaction(input, plan); - signer.sign(transaction); - auto signedTx = transaction.serialize(); + signer.sign(*transaction); + auto signedTx = transaction->serialize(); output.set_encoded(signedTx.data(), signedTx.size()); } catch (const Common::Proto::SigningError& error) { @@ -221,4 +285,41 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { return output; } +Data Signer::signaturePreimage(const Proto::SigningInput& input) { + Proto::TransactionPlan p; + if (input.has_plan()) { + p = input.plan(); + } else { + p = plan(input); + } + auto transaction = prepareUnsignedTransaction(input, p); + return transaction->serialize(); +} + +Data Signer::encodeTransaction(const Proto::SigningInput& input, + const std::vector& publicKeys, + const std::vector& signatures) { + Proto::TransactionPlan p; + if (input.has_plan()) { + p = input.plan(); + } else { + p = plan(input); + } + auto transaction = prepareUnsignedTransaction(input, p); + transaction->witnesses.clear(); + + if (publicKeys.size() != signatures.size()) { + return {Data()}; + } + + for (size_t i = 0; i < publicKeys.size(); i++) { + Witness witness; + witness.invocationScript = Script::CreateInvocationScript(signatures[i]); + witness.verificationScript = Script::CreateSignatureRedeemScript(publicKeys[i].bytes); + transaction->witnesses.push_back(witness); + } + + return transaction->serialize(); +} + } // namespace TW::NEO diff --git a/src/NEO/Signer.h b/src/NEO/Signer.h index c6b71884b44..e388539fa5f 100644 --- a/src/NEO/Signer.h +++ b/src/NEO/Signer.h @@ -15,12 +15,12 @@ namespace TW::NEO { class Signer { - private: +private: Data publicKey; TW::PrivateKey privateKey; Address address; - public: +public: explicit Signer(const TW::PrivateKey& priKey); PrivateKey getPrivateKey() const; PublicKey getPublicKey() const; @@ -31,10 +31,15 @@ class Signer { void sign(Transaction& tx) const; Data sign(const Data& data) const; - private: - static Transaction prepareUnsignedTransaction(const Proto::SigningInput& input, - const Proto::TransactionPlan& plan, - bool validate = true); + static Data signaturePreimage(const Proto::SigningInput& input); + static Data encodeTransaction(const Proto::SigningInput& input, + const std::vector& publicKeys, + const std::vector& signatures); + +private: + static std::shared_ptr + prepareUnsignedTransaction(const Proto::SigningInput& input, const Proto::TransactionPlan& plan, + bool validate = true); }; } // namespace TW::NEO diff --git a/src/NEO/Transaction.cpp b/src/NEO/Transaction.cpp index 6062db5fd2f..45fa9bbbd58 100644 --- a/src/NEO/Transaction.cpp +++ b/src/NEO/Transaction.cpp @@ -4,10 +4,14 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../uint256.h" -#include "../Hash.h" -#include "Transaction.h" +#include + +#include "InvocationTransaction.h" #include "MinerTransaction.h" +#include "Transaction.h" +#include "Data.h" +#include "../Hash.h" +#include "../uint256.h" using namespace std; using namespace TW; @@ -36,6 +40,12 @@ Transaction* Transaction::deserializeFrom(const Data& data, size_t initial_pos) case TransactionType::TT_MinerTransaction: resp = new MinerTransaction(); break; + case TransactionType::TT_ContractTransaction: + resp = new Transaction(); + break; + case TransactionType::TT_InvocationTransaction: + resp = new InvocationTransaction(); + break; default: throw std::invalid_argument("Transaction::deserializeFrom Invalid transaction type"); break; diff --git a/src/NEO/Transaction.h b/src/NEO/Transaction.h index ba7d12ee1e9..1a17b3f6757 100644 --- a/src/NEO/Transaction.h +++ b/src/NEO/Transaction.h @@ -6,14 +6,14 @@ #pragma once -#include "../uint256.h" +#include "CoinReference.h" #include "ISerializable.h" #include "Serializable.h" -#include "TransactionType.h" #include "TransactionAttribute.h" #include "TransactionOutput.h" -#include "CoinReference.h" +#include "TransactionType.h" #include "Witness.h" +#include "../uint256.h" namespace TW::NEO { @@ -26,12 +26,14 @@ class Transaction : public Serializable { std::vector outputs; std::vector witnesses; + Transaction(TransactionType t = TransactionType::TT_ContractTransaction, byte ver = 0) : type(t), version(ver) {} ~Transaction() override = default; + size_t size() const override; void deserialize(const Data& data, size_t initial_pos = 0) override; Data serialize() const override; - bool operator==(const Transaction &other) const; + bool operator==(const Transaction& other) const; virtual size_t deserializeExclusiveData([[maybe_unused]] const Data& data, size_t initial_pos) { return initial_pos; } virtual Data serializeExclusiveData() const { return {}; } @@ -39,7 +41,7 @@ class Transaction : public Serializable { Data getHash() const; uint256_t getHashUInt256() const; - static Transaction * deserializeFrom(const Data& data, size_t initial_pos = 0); + static Transaction* deserializeFrom(const Data& data, size_t initial_pos = 0); }; } // namespace TW::NEO diff --git a/src/NULS/Address.cpp b/src/NULS/Address.cpp index 631ad898137..1a854067e97 100644 --- a/src/NULS/Address.cpp +++ b/src/NULS/Address.cpp @@ -15,18 +15,26 @@ using namespace TW; namespace TW::NULS { -const std::string Address::prefix("NULSd"); -const std::array Address::mainnetId = {0x01, 0x00}; +std::string mainnetPrefix = std::string("NULSd"); +std::string testnetPrefix = std::string("tNULSe"); -bool Address::isValid(const std::string& string) { - if (string.empty()) { +bool Address::isValid(const std::string& addrStr) { + if (addrStr.empty()) { return false; } - if (string.length() <= prefix.length()) { + std::string addrPrefix; + if(addrStr.find(mainnetPrefix) == 0) { + addrPrefix = mainnetPrefix; + } else if (addrStr.find(testnetPrefix) == 0) { + addrPrefix = testnetPrefix; + } else { + return false; + } + if (addrStr.length() <= addrPrefix.length()) { return false; } - std::string address = string.substr(prefix.length(), string.length() - prefix.length()); + std::string address = addrStr.substr(addrPrefix.length(), addrStr.length() - addrPrefix.length()); Data decoded = Base58::decode(address); if (decoded.size() != size) { return false; @@ -41,21 +49,34 @@ bool Address::isValid(const std::string& string) { return decoded[23] == checkSum; } -Address::Address(const TW::PublicKey& publicKey) { - // Main-Net chainID - bytes[0] = mainnetId[0]; - bytes[1] = mainnetId[1]; +Address::Address(const TW::PublicKey& publicKey, bool isMainnet) { + if (isMainnet) { + prefix = mainnetPrefix; + bytes[0] = 0x01; + bytes[1] = 0x00; + } else { + prefix = testnetPrefix; + bytes[0] = 0x02; + bytes[1] = 0x00; + } // Address Type bytes[2] = addressType; ecdsa_get_pubkeyhash(publicKey.bytes.data(), HASHER_SHA2_RIPEMD, bytes.begin() + 3); bytes[23] = checksum(bytes); } -Address::Address(const std::string& string) { - if (false == isValid(string)) { +Address::Address(const std::string& addrStr) { + if(addrStr.find(mainnetPrefix) == 0) { + prefix = mainnetPrefix; + } else if (addrStr.find(testnetPrefix) == 0) { + prefix = testnetPrefix; + } else { + throw std::invalid_argument("wrong address prefix"); + } + if (!isValid(addrStr)) { throw std::invalid_argument("Invalid address string"); } - std::string address = string.substr(prefix.length(), string.length() - prefix.length()); + std::string address = addrStr.substr(prefix.length(), addrStr.length() - prefix.length()); const auto decoded = Base58::decode(address); std::copy(decoded.begin(), decoded.end(), bytes.begin()); } diff --git a/src/NULS/Address.h b/src/NULS/Address.h index ef9c6a7639f..04efcb97b83 100644 --- a/src/NULS/Address.h +++ b/src/NULS/Address.h @@ -14,23 +14,19 @@ namespace TW::NULS { class Address : public Base58Address<24> { public: - /// NULS Main Net Chain ID = 1 - static const std::array mainnetId; - /// NULS address prefix - static const std::string prefix; + std::string prefix; /// NULS address type static const byte addressType = 0x01; /// Determines whether a string makes a valid address. - static bool isValid(const std::string& string); + static bool isValid(const std::string& addrStr); /// Initializes an address from a string representation. explicit Address(const std::string& string); - /// Initializes an address from a public key. - explicit Address(const PublicKey& publicKey); + explicit Address(const TW::PublicKey& publicKey, bool isMainnet = true); /// Initializes an address with a collection of bytes. explicit Address(const Data& data) : TW::Base58Address<24>(data) {} diff --git a/src/NULS/BinaryCoding.h b/src/NULS/BinaryCoding.h index f7dafced953..4ae1c2f087e 100644 --- a/src/NULS/BinaryCoding.h +++ b/src/NULS/BinaryCoding.h @@ -21,38 +21,69 @@ static inline void serializerRemark(std::string& remark, Data& data) { std::copy(remark.begin(), remark.end(), std::back_inserter(data)); } -static inline void serializerInput(const Proto::TransactionCoinFrom& input, Data& data) { - encodeVarInt(1, data); // there is one coinFrom - const auto& fromAddress = input.from_address(); - if (!NULS::Address::isValid(fromAddress)) { - throw std::invalid_argument("Invalid address"); +static inline void serializerCoinData(const TW::NULS::Proto::Transaction& tx, Data& data) { + auto coinData = Data(); + // CoinFrom + encodeVarInt(tx.input_size(), coinData); + for (int i = 0; i < tx.input_size(); i++) { + // address + const auto& fromAddress = tx.input(i).from_address(); + if (!NULS::Address::isValid(fromAddress)) { + throw std::invalid_argument("Invalid address"); + } + const auto& addr = NULS::Address(fromAddress); + encodeVarInt(addr.bytes.size() - 1, coinData); + std::copy(addr.bytes.begin(), addr.bytes.end() - 1, std::back_inserter(coinData)); + // chain id of asset + encode16LE(static_cast(tx.input(i).assets_chainid()), coinData); + // asset id + encode16LE(static_cast(tx.input(i).assets_id()), coinData); + // amount + uint256_t numeric = load(tx.input(i).id_amount()); + Data numericData = store(numeric); + std::reverse(numericData.begin(), numericData.end()); + std::string numericStr; + numericStr.insert(numericStr.begin(), numericData.begin(), numericData.end()); + numericStr.append(static_cast(numericData.capacity() - numericData.size()), + '\0'); + std::copy(numericStr.begin(), numericStr.end(), std::back_inserter(coinData)); + // nonce + Data nonce = parse_hex(tx.input(i).nonce()); + encodeVarInt(nonce.size(), coinData); + append(coinData, nonce); + // locked + coinData.push_back(static_cast(tx.input(i).locked())); } - const auto& addr = NULS::Address(fromAddress); - encodeVarInt(addr.bytes.size() - 1, data); - std::copy(addr.bytes.begin(), addr.bytes.end() - 1, std::back_inserter(data)); - encode16LE(static_cast(input.assets_chainid()), data); - encode16LE(static_cast(input.assets_id()), data); - std::copy(input.id_amount().begin(), input.id_amount().end(), std::back_inserter(data)); - Data nonce = parse_hex(input.nonce()); - encodeVarInt(nonce.size(), data); - append(data, nonce); - data.push_back(static_cast(input.locked())); -} - -static inline void serializerOutput(const Proto::TransactionCoinTo& output, Data& data) { - encodeVarInt(1, data); // there is one coinTo - - const auto& toAddress = output.to_address(); - if (!NULS::Address::isValid(toAddress)) { - throw std::invalid_argument("Invalid address"); + encodeVarInt(tx.output_size(), coinData); + for (int i = 0; i < tx.output_size(); i++) { + // address + const auto& toAddress = tx.output(i).to_address(); + if (!NULS::Address::isValid(toAddress)) { + throw std::invalid_argument("Invalid address"); + } + const auto& addr = NULS::Address(toAddress); + encodeVarInt(addr.bytes.size() - 1, coinData); + std::copy(addr.bytes.begin(), addr.bytes.end() - 1, std::back_inserter(coinData)); + // chain id of asset + encode16LE(static_cast(tx.output(i).assets_chainid()), coinData); + // asset id + encode16LE(static_cast(tx.output(i).assets_id()), coinData); + // amount + uint256_t numeric = load(tx.output(i).id_amount()); + Data numericData = store(numeric); + std::reverse(numericData.begin(), numericData.end()); + std::string numericStr; + numericStr.insert(numericStr.begin(), numericData.begin(), numericData.end()); + numericStr.append(static_cast(numericData.capacity() - numericData.size()), + '\0'); + std::copy(numericStr.begin(), numericStr.end(), std::back_inserter(coinData)); + // lock time + encode64LE(tx.output(i).lock_time(), coinData); } - const auto& addr = NULS::Address(toAddress); - encodeVarInt(addr.bytes.size() - 1, data); - std::copy(addr.bytes.begin(), addr.bytes.end() - 1, std::back_inserter(data)); - encode16LE(static_cast(output.assets_chainid()), data); - encode16LE(static_cast(output.assets_id()), data); - std::copy(output.id_amount().begin(), output.id_amount().end(), std::back_inserter(data)); - encode64LE(output.lock_time(), data); + encodeVarInt(tx.input_size() * Signer::TRANSACTION_INPUT_SIZE + + tx.output_size() * Signer::TRANSACTION_OUTPUT_SIZE, + data); + append(data, coinData); } static inline Data calcTransactionDigest(Data& data) { diff --git a/src/NULS/Entry.cpp b/src/NULS/Entry.cpp index cf17757b5c1..1c970b47cfd 100644 --- a/src/NULS/Entry.cpp +++ b/src/NULS/Entry.cpp @@ -5,7 +5,6 @@ // file LICENSE at the root of the source code distribution tree. #include "Entry.h" - #include "Address.h" #include "Signer.h" @@ -23,8 +22,53 @@ std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicK return Address(publicKey).string(); } +TW::Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + const auto addr = Address(address); + return {addr.bytes.begin(), addr.bytes.end()}; +} + void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const TW::Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + const auto signer = Signer(input); + auto unsignedTxBytes = signer.buildUnsignedTx(); + output.set_data(unsignedTxBytes.data(), unsignedTxBytes.size()); + Data unsignedTxBytesHash = Hash::sha256(Hash::sha256((unsignedTxBytes))); + output.set_data_hash(unsignedTxBytesHash.data(), unsignedTxBytesHash.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() != publicKeys.size()) { + output.set_error(Common::Proto::Error_signatures_count); + output.set_error_message( + Common::Proto::SigningError_Name(Common::Proto::Error_signatures_count)); + return; + } + const auto signer = Signer(input); + // verify signatures + auto unsignedTxBytes = signer.buildUnsignedTx(); + Data unsignedTxBytesHash = Hash::sha256(Hash::sha256((unsignedTxBytes))); + for (std::vector::size_type i = 0; i < signatures.size(); i++) { + if (!publicKeys[i].verify(signatures[i], unsignedTxBytesHash)) { + throw std::invalid_argument("invalid signature at " + std::to_string(i)); + } + } + std::vector publicKeysData; + for (std::vector::size_type i = 0; i < publicKeys.size(); i++) { + publicKeysData.push_back(publicKeys[i].bytes); + } + + auto signedData = signer.buildSignedTx(publicKeysData, signatures, unsignedTxBytes); + output.set_encoded(signedData.data(), signedData.size()); + }); +} + } // namespace TW::NULS diff --git a/src/NULS/Entry.h b/src/NULS/Entry.h index 5a900384ea7..db7b9741cbb 100644 --- a/src/NULS/Entry.h +++ b/src/NULS/Entry.h @@ -7,6 +7,7 @@ #pragma once #include "../CoinEntry.h" +#include "../proto/TransactionCompiler.pb.h" namespace TW::NULS { @@ -15,8 +16,12 @@ namespace TW::NULS { class Entry final : public CoinEntry { public: bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::NULS diff --git a/src/NULS/Signer.cpp b/src/NULS/Signer.cpp index f90feb882e8..7c6d702659d 100644 --- a/src/NULS/Signer.cpp +++ b/src/NULS/Signer.cpp @@ -5,6 +5,7 @@ // file LICENSE at the root of the source code distribution tree. #include "Signer.h" +#include #include "Address.h" #include "BinaryCoding.h" @@ -21,23 +22,15 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto signer = Signer(input); auto data = signer.sign(); output.set_encoded(data.data(), data.size()); - } catch (...) { + } catch (const std::exception& e) { + output.set_error(Common::Proto::Error_general); + output.set_error_message(e.what()); } return output; } -Signer::Signer(const Proto::SigningInput& input) - : input(input) { - Proto::TransactionCoinFrom coinFrom; - coinFrom.set_from_address(input.from()); - coinFrom.set_assets_chainid(input.chain_id()); - coinFrom.set_assets_id(input.idassets_id()); - // need to update with amount + fee - coinFrom.set_id_amount(input.amount()); - coinFrom.set_nonce(input.nonce()); - // default unlocked - coinFrom.set_locked(0); - *tx.mutable_input() = coinFrom; +Signer::Signer(const Proto::SigningInput& input) : input(input) { + uint256_t balance = load(input.balance()); Proto::TransactionCoinTo coinTo; coinTo.set_to_address(input.to()); @@ -45,10 +38,97 @@ Signer::Signer(const Proto::SigningInput& input) coinTo.set_assets_chainid(input.chain_id()); coinTo.set_assets_id(input.idassets_id()); coinTo.set_lock_time(0); - *tx.mutable_output() = coinTo; + *tx.add_output() = coinTo; - tx.set_remark(input.remark()); tx.set_type(TX_TYPE); + + TW::uint256_t fromAmount; + // get mainnet chain id from address + auto from = Address(input.from()); + if (input.chain_id() == from.chainID() && input.idassets_id() == 1) { + // asset is NULS + uint256_t txAmount = load(input.amount()); + uint32_t txSize = + CalculatorTransactionSize(1, 1, static_cast(tx.remark().size())); + uint256_t fee = (uint256_t)CalculatorTransactionFee(txSize); + fromAmount = txAmount; + if (Address::isValid(input.fee_payer()) && input.fee_payer() != input.from()) { + uint256_t feePayerBalance = load(input.fee_payer_balance()); + if (fee > feePayerBalance) { + throw std::invalid_argument("fee payer balance not sufficient"); + } + Data feeData = store(fee); + std::string feeStr(feeData.begin(), feeData.end()); + Proto::TransactionCoinFrom feeCoinFrom; + feeCoinFrom.set_from_address(input.fee_payer()); + feeCoinFrom.set_assets_chainid(input.chain_id()); + feeCoinFrom.set_assets_id(input.idassets_id()); + feeCoinFrom.set_id_amount(feeStr); + feeCoinFrom.set_nonce(input.fee_payer_nonce()); + // default unlocked + feeCoinFrom.set_locked(0); + *tx.add_input() = feeCoinFrom; + } else { + fromAmount += fee; + } + // update the amount + Data amount = store(fromAmount); + std::string amountStr(amount.begin(), amount.end()); + Proto::TransactionCoinFrom coinFrom; + coinFrom.set_from_address(input.from()); + coinFrom.set_assets_chainid(input.chain_id()); + coinFrom.set_assets_id(input.idassets_id()); + coinFrom.set_id_amount(amountStr); + coinFrom.set_nonce(input.nonce()); + // default unlocked + coinFrom.set_locked(0); + *tx.add_input() = coinFrom; + } else { + // asset is not NULS + uint256_t txAmount = load(input.amount()); + fromAmount = txAmount; + // coinFrom + // asset + Proto::TransactionCoinFrom asset; + asset.set_from_address(input.from()); + asset.set_assets_chainid(input.chain_id()); + asset.set_assets_id(input.idassets_id()); + asset.set_id_amount(input.amount()); + asset.set_nonce(input.nonce()); + // default unlocked + asset.set_locked(0); + *tx.add_input() = asset; + + // fee + uint32_t txSize = CalculatorTransactionSize( + 2, 1, + static_cast( + tx.remark().size())); // 2 inputs, one for the asset, another for NULS fee + uint256_t fee = (uint256_t)CalculatorTransactionFee(txSize); + uint256_t feePayerBalance = load(input.fee_payer_balance()); + if (fee > feePayerBalance) { + throw std::invalid_argument("fee payer balance not sufficient"); + } + Data feeData = store(fee); + std::string feeStr(feeData.begin(), feeData.end()); + // add new input for fee + Proto::TransactionCoinFrom txFee; + txFee.set_from_address(input.fee_payer()); + txFee.set_nonce(input.fee_payer_nonce()); + + txFee.set_assets_chainid(from.chainID()); + // network asset id 1 is NULS + txFee.set_assets_id(1); + txFee.set_id_amount(feeStr); + // default unlocked + txFee.set_locked(0); + *tx.add_input() = txFee; + } + if (fromAmount > balance) { + throw std::invalid_argument("User account balance not sufficient"); + } + + tx.set_remark(input.remark()); tx.set_timestamp(input.timestamp()); tx.set_tx_data(""); } @@ -57,34 +137,6 @@ Data Signer::sign() const { if (input.private_key().empty()) { throw std::invalid_argument("Must have private key string"); } - - uint32_t txSize = CalculatorTransactionSize(1, 1, static_cast(tx.remark().size())); - uint256_t fee = (uint256_t)CalculatorTransactionFee(txSize); - uint256_t txAmount = load(input.amount()); - uint256_t balance = load(input.balance()); - uint256_t fromAmount = txAmount + fee; - if (fromAmount > balance) { - throw std::invalid_argument("User account balance not sufficient"); - } - - auto& coinFrom = (Proto::TransactionCoinFrom&)tx.input(); - Data amount; - amount = store(fromAmount); - std::reverse(amount.begin(), amount.end()); - std::string amountStr; - amountStr.insert(amountStr.begin(), amount.begin(), amount.end()); - amountStr.append(static_cast(amount.capacity() - amount.size()), '\0'); - coinFrom.set_id_amount(amountStr); - - auto& coinTo = (Proto::TransactionCoinTo&)tx.output(); - Data amountTo; - amountTo = store(txAmount); - std::reverse(amountTo.begin(), amountTo.end()); - std::string amountToStr; - amountToStr.insert(amountToStr.begin(), amountTo.begin(), amountTo.end()); - amountToStr.append(static_cast(amountTo.capacity() - amountTo.size()), '\0'); - coinTo.set_id_amount(amountToStr); - auto dataRet = Data(); // Transaction Type encode16LE(static_cast(tx.type()), dataRet); @@ -95,31 +147,78 @@ Data Signer::sign() const { serializerRemark(remark, dataRet); // txData encodeVarInt(0, dataRet); - - // coinFrom and coinTo size - encodeVarInt(TRANSACTION_INPUT_SIZE + TRANSACTION_OUTPUT_SIZE, dataRet); - - // CoinData Input - serializerInput(tx.input(), dataRet); - - // CoinData Output - serializerOutput(tx.output(), dataRet); - + // coinData + serializerCoinData(tx, dataRet); // Calc transaction hash Data txHash = calcTransactionDigest(dataRet); Data privKey = data(input.private_key()); auto priv = PrivateKey(privKey); auto transactionSignature = makeTransactionSignature(priv, txHash); + if (Address::isValid(input.fee_payer()) && input.from() != input.fee_payer()) { + Data feePayerPrivKey = data(input.fee_payer_private_key()); + auto feePayerPriv = PrivateKey(feePayerPrivKey); + auto feePayerTransactionSignature = makeTransactionSignature(feePayerPriv, txHash); + transactionSignature.insert(transactionSignature.end(), + feePayerTransactionSignature.begin(), + feePayerTransactionSignature.end()); + } + encodeVarInt(transactionSignature.size(), dataRet); - std::copy(transactionSignature.begin(), transactionSignature.end(), std::back_inserter(dataRet)); + std::copy(transactionSignature.begin(), transactionSignature.end(), + std::back_inserter(dataRet)); + + return dataRet; +} + +Data Signer::buildUnsignedTx() const { + auto dataRet = Data(); + // Transaction Type + encode16LE(static_cast(tx.type()), dataRet); + // Timestamp + encode32LE(tx.timestamp(), dataRet); + // Remark + std::string remark = tx.remark(); + serializerRemark(remark, dataRet); + // txData + encodeVarInt(0, dataRet); + // coinData + serializerCoinData(tx, dataRet); return dataRet; } -uint32_t Signer::CalculatorTransactionSize(uint32_t inputCount, uint32_t outputCount, uint32_t remarkSize) const { - uint32_t size = TRANSACTION_FIX_SIZE + TRANSACTION_SIG_MAX_SIZE + TRANSACTION_INPUT_SIZE * inputCount + - TRANSACTION_OUTPUT_SIZE * outputCount + remarkSize; +Data Signer::buildSignedTx(const std::vector publicKeys, + const std::vector signatures, + const Data unsignedTxBytes) const { + Data transactionSignature = Data(); + // the size of publicKeys must be the same as the size of the signatures. + for (std::vector::size_type i = 0; i < publicKeys.size(); i++) { + encodeVarInt(publicKeys[i].size(), transactionSignature); + std::copy(publicKeys[i].begin(), publicKeys[i].end(), + std::back_inserter(transactionSignature)); + + std::array tempSigBytes; + size_t size = ecdsa_sig_to_der(signatures[i].data(), tempSigBytes.data()); + auto signature = Data{}; + std::copy(tempSigBytes.begin(), tempSigBytes.begin() + size, std::back_inserter(signature)); + + encodeVarInt(signature.size(), transactionSignature); + std::copy(signature.begin(), signature.end(), std::back_inserter(transactionSignature)); + } + + Data signedTxBytes = unsignedTxBytes; + encodeVarInt(transactionSignature.size(), signedTxBytes); + std::copy(transactionSignature.begin(), transactionSignature.end(), + std::back_inserter(signedTxBytes)); + return signedTxBytes; +} + +uint32_t Signer::CalculatorTransactionSize(uint32_t inputCount, uint32_t outputCount, + uint32_t remarkSize) const { + uint32_t size = TRANSACTION_FIX_SIZE + TRANSACTION_SIG_MAX_SIZE + + TRANSACTION_INPUT_SIZE * inputCount + TRANSACTION_OUTPUT_SIZE * outputCount + + remarkSize; return size; } diff --git a/src/NULS/Signer.h b/src/NULS/Signer.h index 407ec785716..52657470392 100644 --- a/src/NULS/Signer.h +++ b/src/NULS/Signer.h @@ -6,10 +6,10 @@ #pragma once #include "../proto/NULS.pb.h" -#include -#include #include "Data.h" #include "../uint256.h" +#include +#include namespace TW::NULS { @@ -18,6 +18,7 @@ class Signer { public: /// Signs a Proto::SigningInput transaction static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; + public: static const uint16_t TRANSACTION_FIX_SIZE = 11; //type size 2, time size 4, txData size 1, hash size 4 static const uint16_t TRANSACTION_SIG_MAX_SIZE = 110; @@ -41,7 +42,14 @@ class Signer { /// /// \returns the transaction signature or an empty vector if there is an error. Data sign() const; - private: + + Data buildUnsignedTx() const; + + Data buildSignedTx(const std::vector publicKeys, + const std::vector signatures, + const Data unsignedTxBytes) const; + +private: uint64_t CalculatorTransactionFee(uint64_t size) const; int32_t CalculatorMaxInput(uint32_t remarkSize) const; uint32_t CalculatorTransactionSize(uint32_t inputCount, uint32_t outputCount, uint32_t remarkSize) const; diff --git a/src/Nano/Entry.cpp b/src/Nano/Entry.cpp index 640408ccb2b..922bf06c553 100644 --- a/src/Nano/Entry.cpp +++ b/src/Nano/Entry.cpp @@ -5,9 +5,9 @@ // file LICENSE at the root of the source code distribution tree. #include "Entry.h" - #include "Address.h" #include "Signer.h" +#include namespace TW::Nano { @@ -34,4 +34,21 @@ std::string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& return Signer::signJSON(json, key); } +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + + auto unsignedTxBytes = Signer::buildUnsignedTxBytes(input); + output.set_data(unsignedTxBytes.data(), unsignedTxBytes.size()); + output.set_data_hash(unsignedTxBytes.data(), unsignedTxBytes.size()); }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [](const auto& input, auto& output, const auto& signature, [[maybe_unused]] const auto& publicKey) { + output = Signer::buildSigningOutput(input, signature); }); +} + } // namespace TW::Nano diff --git a/src/Nano/Entry.h b/src/Nano/Entry.h index 946ae385887..1ebce88f9d3 100644 --- a/src/Nano/Entry.h +++ b/src/Nano/Entry.h @@ -15,11 +15,14 @@ namespace TW::Nano { class Entry final : public CoinEntry { public: bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; - Data addressToData(TWCoinType coin, const std::string& address) const; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - bool supportsJSONSigning() const { return true; } - std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool supportsJSONSigning() const { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Nano diff --git a/src/Nano/Signer.cpp b/src/Nano/Signer.cpp index d81bd3c5b8f..95d6cfc4f85 100644 --- a/src/Nano/Signer.cpp +++ b/src/Nano/Signer.cpp @@ -181,4 +181,40 @@ Proto::SigningOutput Signer::build() const { return output; } +Data Signer::buildUnsignedTxBytes(const Proto::SigningInput& input) { + const auto pubKey = PublicKey(Data(input.public_key().begin(), input.public_key().end()), TWPublicKeyTypeED25519Blake2b); + auto block = hashBlockData(pubKey, input); + return Data(block.begin(), block.end()); +} + +Proto::SigningOutput Signer::buildSigningOutput(const Proto::SigningInput& input, const Data& signature) { + auto output = Proto::SigningOutput(); + const auto pubKey = PublicKey(Data(input.public_key().begin(), input.public_key().end()), TWPublicKeyTypeED25519Blake2b); + auto block = hashBlockData(pubKey, input); + output.set_signature(signature.data(), signature.size()); + output.set_block_hash(block.data(), block.size()); + + auto prev = previousFromInput(input); + auto li = linkFromInput(input); + + // build json + json json = { + {"type", "state"}, + {"account", Address(pubKey).string()}, + {"previous", hex(prev)}, + {"representative", Address(input.representative()).string()}, + {"balance", input.balance()}, + {"link", hex(li)}, + {"link_as_account", Address(PublicKey(Data(li.begin(), li.end()), TWPublicKeyTypeED25519Blake2b)).string()}, + {"signature", hex(signature)}, + }; + + if (input.work().size() > 0) { + json["work"] = input.work(); + } + + output.set_json(json.dump()); + return output; +} + } // namespace TW::Nano diff --git a/src/Nano/Signer.h b/src/Nano/Signer.h index 7730a7e36db..2185e6d20e2 100644 --- a/src/Nano/Signer.h +++ b/src/Nano/Signer.h @@ -14,12 +14,13 @@ namespace TW::Nano { /// Helper class that performs Ripple transaction signing. class Signer { - public: +public: /// Signs a Proto::SigningInput transaction static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; /// Signs a json Proto::SigningInput with private key static std::string signJSON(const std::string& json, const Data& key); - public: + +public: const PrivateKey privateKey; const PublicKey publicKey; const Proto::SigningInput& input; @@ -34,6 +35,9 @@ class Signer { /// Builds signed transaction, incl. signature, and json format Proto::SigningOutput build() const; + + static Data buildUnsignedTxBytes(const Proto::SigningInput& input); + static Proto::SigningOutput buildSigningOutput(const Proto::SigningInput& input, const Data& signature); }; } // namespace TW::Nano diff --git a/src/Nebulas/Signer.cpp b/src/Nebulas/Signer.cpp index 719a32499fa..6304f010964 100644 --- a/src/Nebulas/Signer.cpp +++ b/src/Nebulas/Signer.cpp @@ -15,14 +15,7 @@ namespace TW::Nebulas { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto signer = Signer(load(input.chain_id())); - auto tx = Transaction(Address(input.from_address()), - load(input.nonce()), - load(input.gas_price()), - load(input.gas_limit()), - Address(input.to_address()), - load(input.amount()), - load(input.timestamp()), - input.payload()); + auto tx = signer.buildTransaction(input); auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); signer.sign(privateKey, tx); @@ -43,6 +36,20 @@ void Signer::sign(const PrivateKey& privateKey, Transaction& transaction) const } Data Signer::hash(const Transaction& transaction) const noexcept { + return Hash::sha3_256(getPreImage(transaction)); +} + +Data Signer::hash(const Data& data) const noexcept { + return Hash::sha3_256(data); +} + +Transaction Signer::buildTransaction(const Proto::SigningInput& input) const noexcept { + return {Transaction(Address(input.from_address()), load(input.nonce()), load(input.gas_price()), + load(input.gas_limit()), Address(input.to_address()), load(input.amount()), + load(input.timestamp()), input.payload())}; +} + +Data Signer::getPreImage(const Transaction& transaction) const noexcept { auto encoded = Data(); auto payload = Data(); auto* data = Transaction::newPayloadData(transaction.payload); @@ -59,7 +66,7 @@ Data Signer::hash(const Transaction& transaction) const noexcept { encode256BE(encoded, chainID, 32); encode256BE(encoded, transaction.gasPrice, 128); encode256BE(encoded, transaction.gasLimit, 128); - return Hash::sha3_256(encoded); + return encoded; } } // namespace TW::Nebulas diff --git a/src/Nebulas/Signer.h b/src/Nebulas/Signer.h index 8c30a821c49..f0d78873a4e 100644 --- a/src/Nebulas/Signer.h +++ b/src/Nebulas/Signer.h @@ -32,6 +32,14 @@ class Signer { protected: /// Computes the transaction hash. Data hash(const Transaction& transaction) const noexcept; + + /// Computes hash. + Data hash(const Data& preImage) const noexcept; + + Transaction buildTransaction(const Proto::SigningInput& input) const noexcept; + + /// Get transaction data. + Data getPreImage(const Transaction& transaction) const noexcept; }; } // namespace TW::Nebulas diff --git a/src/Oasis/Entry.cpp b/src/Oasis/Entry.cpp index 2192774be99..2e153eeaa97 100644 --- a/src/Oasis/Entry.cpp +++ b/src/Oasis/Entry.cpp @@ -9,6 +9,8 @@ #include "Address.h" #include "Signer.h" +#include "../proto/TransactionCompiler.pb.h" + using namespace std; namespace TW::Oasis { @@ -27,4 +29,24 @@ void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::D signTemplate(dataIn, dataOut); } +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + const auto signer = Signer(input); + auto preImage = signer.signaturePreimage(); + auto preImageHash = Hash::sha512_256(preImage); + output.set_data_hash(preImageHash.data(), preImageHash.size()); + output.set_data(preImage.data(), preImage.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [](const auto& input, auto& output, const auto& signature, const auto& publicKey) { + const auto signer = Signer(input); + output = signer.compile(signature, publicKey); + }); +} } // namespace TW::Oasis diff --git a/src/Oasis/Entry.h b/src/Oasis/Entry.h index e123532579a..1412bea7c2b 100644 --- a/src/Oasis/Entry.h +++ b/src/Oasis/Entry.h @@ -15,8 +15,13 @@ namespace TW::Oasis { class Entry final : public CoinEntry { public: bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + + void compile(TWCoinType coin, const Data& txInputData, + const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Oasis diff --git a/src/Oasis/Signer.cpp b/src/Oasis/Signer.cpp index de9dfcad0db..b7251679184 100644 --- a/src/Oasis/Signer.cpp +++ b/src/Oasis/Signer.cpp @@ -10,6 +10,8 @@ #include "Signer.h" #define TRANSFER_METHOD "staking.Transfer" +#define ESCROW_METHOD "staking.AddEscrow" +#define RECLAIM_ESCROW_METHOD "staking.ReclaimEscrow" using namespace TW; @@ -24,6 +26,96 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { } Data Signer::build() const { + auto privateKey = PrivateKey(input.private_key()); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + + if(input.has_transfer()) { + auto tx = buildTransfer(); + auto signature = signTransaction(tx); + auto encoded = tx.serialize(signature, publicKey); + return encoded; + } + + if(input.has_escrow()) { + auto tx = buildEscrow(); + auto signature = signTransaction(tx); + auto encoded = tx.serialize(signature, publicKey); + return encoded; + } + + if(input.has_reclaimescrow()) { + auto tx = buildReclaimEscrow(); + auto signature = signTransaction(tx); + auto encoded = tx.serialize(signature, publicKey); + return encoded; + } + + throw std::invalid_argument("Invalid message"); +} + +template +Data Signer::signTransaction(T& tx) const { + auto privateKey = PrivateKey(input.private_key()); + + // The use of this context thing is explained here --> + // https://docs.oasis.dev/oasis-core/common-functionality/crypto#domain-separation + auto encodedMessage = tx.encodeMessage().encoded(); + Data dataToHash(tx.context.begin(), tx.context.end()); + dataToHash.insert(dataToHash.end(), encodedMessage.begin(), encodedMessage.end()); + auto hash = Hash::sha512_256(dataToHash); + + auto signature = privateKey.sign(hash, TWCurveED25519); + return Data(signature.begin(), signature.end()); +} + +Data Signer::signaturePreimage() const { + if(input.has_transfer()) { + auto tx = buildTransfer(); + return tx.signaturePreimage(); + } + + if(input.has_escrow()) { + auto tx = buildEscrow(); + return tx.signaturePreimage(); + } + + if(input.has_reclaimescrow()) { + auto tx = buildReclaimEscrow(); + return tx.signaturePreimage(); + } + + throw std::invalid_argument("Invalid message"); +} + +Proto::SigningOutput Signer::compile(const Data& signature, const PublicKey& publicKey) const { + Proto::SigningOutput output; + + if(input.has_transfer()) { + auto tx = buildTransfer(); + auto encoded = tx.serialize(signature, publicKey); + output.set_encoded(encoded.data(), encoded.size()); + return output; + } + + if(input.has_escrow()) { + auto tx = buildEscrow(); + auto encoded = tx.serialize(signature, publicKey); + output.set_encoded(encoded.data(), encoded.size()); + return output; + } + + if(input.has_reclaimescrow()) { + auto tx = buildReclaimEscrow(); + auto encoded = tx.serialize(signature, publicKey); + output.set_encoded(encoded.data(), encoded.size()); + return output; + } + + throw std::invalid_argument("Invalid message"); +} + + +Transaction Signer::buildTransfer() const { // Create empty address var and check if value we want to load is valid Address address(input.transfer().to()); @@ -47,27 +139,61 @@ Data Signer::build() const { /* amount */ amount, /* nonce */ input.transfer().nonce(), /* context */ input.transfer().context()); + return transaction; +} - auto privateKey = PrivateKey(input.private_key()); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); +Escrow Signer::buildEscrow() const { + // Create empty address var and check if value we want to load is valid + Address address(input.escrow().account()); + + // Load value on that address var we create before + Address::decode(input.escrow().account(), address); - auto signature = sign(transaction); - auto encoded = transaction.serialize(signature, publicKey); + // Convert values from string to uint256 + std::istringstream amountStream(input.escrow().amount()); + uint256_t amount; + amountStream >> amount; - return encoded; + std::istringstream gasAmountStream(input.escrow().gas_amount()); + uint256_t gasAmount; + gasAmountStream >> gasAmount; + + Escrow escrow( + /* method */ ESCROW_METHOD, + /* gasPrice */ input.escrow().gas_price(), + /* gasAmount */ gasAmount, + /* nonce */ input.escrow().nonce(), + /* account */ address, + /* amount */ amount, + /* context */ input.escrow().context()); + return escrow; } -Data Signer::sign(Transaction& tx) const { - auto privateKey = PrivateKey(input.private_key()); +ReclaimEscrow Signer::buildReclaimEscrow() const { + // Create empty address var and check if value we want to load is valid + Address address(input.reclaimescrow().account()); - // The use of this context thing is explained here --> https://docs.oasis.dev/oasis-core/common-functionality/crypto#domain-separation - auto encodedMessage = tx.encodeMessage().encoded(); - Data dataToHash(tx.context.begin(), tx.context.end()); - dataToHash.insert(dataToHash.end(), encodedMessage.begin(), encodedMessage.end()); - auto hash = Hash::sha512_256(dataToHash); + // Load value on that address var we create before + Address::decode(input.reclaimescrow().account(), address); - auto signature = privateKey.sign(hash, TWCurveED25519); - return Data(signature.begin(), signature.end()); + // Convert values from string to uint256 + std::istringstream sharesStream(input.reclaimescrow().shares()); + uint256_t shares; + sharesStream >> shares; + + std::istringstream gasAmountStream(input.reclaimescrow().gas_amount()); + uint256_t gasAmount; + gasAmountStream >> gasAmount; + + ReclaimEscrow reclaimEscrow( + /* method */ RECLAIM_ESCROW_METHOD, + /* gasPrice */ input.reclaimescrow().gas_price(), + /* gasAmount */ gasAmount, + /* nonce */ input.reclaimescrow().nonce(), + /* account */ address, + /* shares */ shares, + /* context */ input.reclaimescrow().context()); + return reclaimEscrow; } } // namespace TW::Oasis diff --git a/src/Oasis/Signer.h b/src/Oasis/Signer.h index 641e25a6f84..c166eb9bfe0 100644 --- a/src/Oasis/Signer.h +++ b/src/Oasis/Signer.h @@ -18,6 +18,11 @@ namespace TW::Oasis { /// Helper class that performs Oasis transaction signing. class Signer { +private: + Transaction buildTransfer() const; + Escrow buildEscrow() const; + ReclaimEscrow buildReclaimEscrow() const; + public: Proto::SigningInput input; @@ -34,13 +39,16 @@ class Signer { /// /// \returns the transaction signature or an empty vector if there is an /// error. - Data sign(Transaction& tx) const; + template + Data signTransaction(T& tx) const; /// Builds a signed transaction. /// /// \returns the signed transaction data or an empty vector if there is an /// error. Data build() const; + Data signaturePreimage() const; + Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey) const; }; } // namespace TW::Oasis diff --git a/src/Oasis/Transaction.cpp b/src/Oasis/Transaction.cpp index e9654e3822c..5d4b21766fe 100644 --- a/src/Oasis/Transaction.cpp +++ b/src/Oasis/Transaction.cpp @@ -30,6 +30,24 @@ static Data encodeVaruint(const uint256_t& value) { return small; } +static Data serializeTransaction(const Data& signature, const PublicKey& publicKey, const Data& body) { + auto signedMessage = Cbor::Encode::map({ + { Cbor::Encode::string("untrusted_raw_value"), Cbor::Encode::bytes(body) }, + { Cbor::Encode::string("signature"), Cbor::Encode::map({ + { Cbor::Encode::string("public_key"), Cbor::Encode::bytes(publicKey.bytes) }, + { Cbor::Encode::string("signature"), Cbor::Encode::bytes(signature) } + }) + } + }); + return signedMessage.encoded(); +} + +static Data buildSignaturePreimage(std::string context, const Data& encodedMessage) { + Data dataToHash(context.begin(), context.end()); + dataToHash.insert(dataToHash.end(), encodedMessage.begin(), encodedMessage.end()); + return dataToHash; +} + Cbor::Encode Transaction::encodeMessage() const { return Cbor::Encode::map({ @@ -48,18 +66,65 @@ Cbor::Encode Transaction::encodeMessage() const { }); } -Data Transaction::serialize(Data& signature, PublicKey& publicKey) const { - auto signedMessage = Cbor::Encode::map({ - { Cbor::Encode::string("untrusted_raw_value"), Cbor::Encode::bytes(encodeMessage().encoded()) }, - { Cbor::Encode::string("signature"), Cbor::Encode::map({ - { Cbor::Encode::string("public_key"), Cbor::Encode::bytes(publicKey.bytes) }, - { Cbor::Encode::string("signature"), Cbor::Encode::bytes(signature) } - }) - } - }); - return signedMessage.encoded(); +Data Transaction::serialize(const Data& signature, const PublicKey& publicKey) const { + return serializeTransaction(signature, publicKey, encodeMessage().encoded()); +} + +Data Transaction::signaturePreimage() const { + return buildSignaturePreimage(context, encodeMessage().encoded()); +} + +Cbor::Encode Escrow::encodeMessage() const { + + return Cbor::Encode::map({ + { Cbor::Encode::string("nonce"), Cbor::Encode::uint(nonce) }, + { Cbor::Encode::string("method"), Cbor::Encode::string(method) }, + { Cbor::Encode::string("fee"), Cbor::Encode::map({ + { Cbor::Encode::string("gas"), Cbor::Encode::uint(gasPrice) }, + { Cbor::Encode::string("amount"), Cbor::Encode::bytes(encodeVaruint(gasAmount)) } + }) + }, + { Cbor::Encode::string("body"), Cbor::Encode::map({ + { Cbor::Encode::string("account"), Cbor::Encode::bytes(account.getKeyHash()) }, + { Cbor::Encode::string("amount"), Cbor::Encode::bytes(encodeVaruint(amount)) } + }) + } + }); +} + +Data Escrow::serialize(const Data& signature, const PublicKey& publicKey) const { + return serializeTransaction(signature, publicKey, encodeMessage().encoded()); +} + +Data Escrow::signaturePreimage() const { + return buildSignaturePreimage(context, encodeMessage().encoded()); +} + +Cbor::Encode ReclaimEscrow::encodeMessage() const { + + return Cbor::Encode::map({ + { Cbor::Encode::string("nonce"), Cbor::Encode::uint(nonce) }, + { Cbor::Encode::string("method"), Cbor::Encode::string(method) }, + { Cbor::Encode::string("fee"), Cbor::Encode::map({ + { Cbor::Encode::string("gas"), Cbor::Encode::uint(gasPrice) }, + { Cbor::Encode::string("amount"), Cbor::Encode::bytes(encodeVaruint(gasAmount)) } + }) + }, + { Cbor::Encode::string("body"), Cbor::Encode::map({ + { Cbor::Encode::string("account"), Cbor::Encode::bytes(account.getKeyHash()) }, + { Cbor::Encode::string("shares"), Cbor::Encode::bytes(encodeVaruint(shares)) } + }) + } + }); } +Data ReclaimEscrow::serialize(const Data& signature, const PublicKey& publicKey) const { + return serializeTransaction(signature, publicKey, encodeMessage().encoded()); +} + +Data ReclaimEscrow::signaturePreimage() const { + return buildSignaturePreimage(context, encodeMessage().encoded()); +} // clang-format on } // namespace TW::Oasis diff --git a/src/Oasis/Transaction.h b/src/Oasis/Transaction.h index c892aa43bcc..ae26e49ab61 100644 --- a/src/Oasis/Transaction.h +++ b/src/Oasis/Transaction.h @@ -14,6 +14,7 @@ namespace TW::Oasis { +// Transfer transaction class Transaction { public: // Recipient address @@ -46,7 +47,86 @@ class Transaction { Cbor::Encode encodeMessage() const; // serialize returns the CBOR encoding of the SignedMessage. - Data serialize(Data& signature, PublicKey& publicKey) const; + Data serialize(const Data& signature, const PublicKey& publicKey) const; + + // serialize transaction that can be signed + Data signaturePreimage() const; +}; + +// Escrow transaction +class Escrow { + public: + // Method name + std::string method; + // Gas price + uint64_t gasPrice; + // Gas amount + uint256_t gasAmount; + // Transaction nonce + uint64_t nonce; + Address account; + // Transaction amount + uint256_t amount; + // Transaction context + std::string context; + + Escrow(std::string method, uint64_t gasPrice, uint256_t gasAmount, uint64_t nonce, + Address account, uint256_t amount, std::string context) + : method(std::move(method)) + , gasPrice(gasPrice) + , gasAmount(std::move(gasAmount)) + , nonce(nonce) + , account(std::move(account)) + , amount(std::move(amount)) + , context(std::move(context)){} + + public: + // message returns the CBOR encoding of the Message to be signed. + Cbor::Encode encodeMessage() const; + + // serialize returns the CBOR encoding of the SignedMessage. + Data serialize(const Data& signature, const PublicKey& publicKey) const; + + // serialize transaction that can be signed + Data signaturePreimage() const; +}; + +// ReclaimEscrow transaction +class ReclaimEscrow { + public: + // Method name + std::string method; + // Gas price + uint64_t gasPrice; + // Gas amount + uint256_t gasAmount; + // Transaction nonce + uint64_t nonce; + Address account; + // Transaction amount + uint256_t shares; + // Transaction context + std::string context; + + ReclaimEscrow(std::string method, uint64_t gasPrice, uint256_t gasAmount, uint64_t nonce, + Address account, uint256_t shares, std::string context) + : method(std::move(method)) + , gasPrice(gasPrice) + , gasAmount(std::move(gasAmount)) + , nonce(nonce) + , account(std::move(account)) + , shares(std::move(shares)) + , context(std::move(context)){} + + public: + // message returns the CBOR encoding of the Message to be signed. + Cbor::Encode encodeMessage() const; + + // serialize returns the CBOR encoding of the SignedMessage. + Data serialize(const Data& signature, const PublicKey& publicKey) const; + + // serialize transaction that can be signed + Data signaturePreimage() const; }; } // namespace TW::Oasis diff --git a/src/Ontology/Asset.h b/src/Ontology/Asset.h index 29250002203..61621b75049 100644 --- a/src/Ontology/Asset.h +++ b/src/Ontology/Asset.h @@ -7,10 +7,10 @@ #pragma once #include "Address.h" +#include "Data.h" #include "Signer.h" #include "Transaction.h" #include "../BinaryCoding.h" -#include "Data.h" #include #include @@ -33,5 +33,9 @@ class Asset { virtual Transaction transfer(const Signer& from, const Address& to, uint64_t amount, const Signer& payer, uint64_t gasPrice, uint64_t gasLimit, uint32_t nonce) = 0; + + virtual Transaction unsignedTransfer(const Address& from, const Address& to, uint64_t amount, + const Address& payer, uint64_t gasPrice, uint64_t gasLimit, + uint32_t nonce) = 0; }; } // namespace TW::Ontology diff --git a/src/Ontology/Entry.cpp b/src/Ontology/Entry.cpp index 810f3e512ba..7b5ded7ccd5 100644 --- a/src/Ontology/Entry.cpp +++ b/src/Ontology/Entry.cpp @@ -8,6 +8,10 @@ #include "Address.h" #include "Signer.h" +#include "OntTxBuilder.h" +#include "OngTxBuilder.h" +#include "Oep4TxBuilder.h" +#include "../proto/TransactionCompiler.pb.h" namespace TW::Ontology { @@ -25,4 +29,47 @@ void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::D signTemplate(dataIn, dataOut); } +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + auto contract = std::string(input.contract().begin(), input.contract().end()); + Data preImage, preImageHash; + + if (contract == "ONT") { + auto tx = OntTxBuilder::buildTransferTx(input); + preImage = tx.serializeUnsigned(); + preImageHash = tx.txHash(); + } else if (contract == "ONG") { + auto tx = OngTxBuilder::buildTransferTx(input); + preImage = tx.serializeUnsigned(); + preImageHash = tx.txHash(); + } else { + auto tx = Oep4TxBuilder::buildTx(input); + preImage = tx.serializeUnsigned(); + preImageHash = tx.txHash(); + } + + output.set_data_hash(preImageHash.data(), preImageHash.size()); + output.set_data(preImage.data(), preImage.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() == 0 || publicKeys.size() == 0) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return; + } + if (signatures.size() != publicKeys.size()) { + output.set_error(Common::Proto::Error_signatures_count); + output.set_error_message(Common::Proto::SigningError_Name(Common::Proto::Error_signatures_count)); + return; + } + + auto signedTx = Signer::encodeTransaction(input, signatures, publicKeys); + output.set_encoded(signedTx.data(), signedTx.size()); + }); +} } // namespace TW::Ontology diff --git a/src/Ontology/Entry.h b/src/Ontology/Entry.h index 794e43d0986..2b1781cf554 100644 --- a/src/Ontology/Entry.h +++ b/src/Ontology/Entry.h @@ -15,8 +15,11 @@ namespace TW::Ontology { class Entry final : public CoinEntry { public: bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Ontology diff --git a/src/Ontology/Oep4.cpp b/src/Ontology/Oep4.cpp index 8e4f866e8e1..c3743ad2436 100644 --- a/src/Ontology/Oep4.cpp +++ b/src/Ontology/Oep4.cpp @@ -66,4 +66,18 @@ Transaction Oep4::transfer(const Signer& from, const Address& to, uint64_t amoun return tx; } +Transaction Oep4::unsignedTransfer(const Address& from, const Address& to, uint64_t amount, + const Address& payer, uint64_t gasPrice, uint64_t gasLimit, + uint32_t nonce) { + Address contract(oep4Contract); + + NeoVmParamValue::ParamArray args{from._data, to._data, amount}; + // yes, invoke neovm is not like ont transfer + std::reverse(args.begin(), args.end()); + auto invokeCode = ParamsBuilder::buildOep4InvokeCode(contract, "transfer", {args}); + + auto tx = Transaction(0, 0xD1, nonce, gasPrice, gasLimit, payer.string(), invokeCode); + return tx; +} + } // namespace TW::Ontology diff --git a/src/Ontology/Oep4.h b/src/Ontology/Oep4.h index 9258e68f1f4..45bdfc4fe66 100644 --- a/src/Ontology/Oep4.h +++ b/src/Ontology/Oep4.h @@ -8,9 +8,9 @@ #include "Address.h" #include "Asset.h" +#include "Data.h" #include "ParamsBuilder.h" #include "Transaction.h" -#include "Data.h" namespace TW::Ontology { @@ -36,6 +36,7 @@ class Oep4 { Transaction transfer(const Signer& from, const Address& to, uint64_t amount, const Signer& payer, uint64_t gasPrice, uint64_t gasLimit, uint32_t nonce); + Transaction unsignedTransfer(const Address& from, const Address& to, uint64_t amount, + const Address& payer, uint64_t gasPrice, uint64_t gasLimit, uint32_t nonce); }; - } // namespace TW::Ontology diff --git a/src/Ontology/Oep4TxBuilder.cpp b/src/Ontology/Oep4TxBuilder.cpp index 5dc6373ccae..776c7b66581 100644 --- a/src/Ontology/Oep4TxBuilder.cpp +++ b/src/Ontology/Oep4TxBuilder.cpp @@ -48,4 +48,24 @@ Data Oep4TxBuilder::build(const Ontology::Proto::SigningInput& input) { return Data(); } +Transaction Oep4TxBuilder::buildTx(const Ontology::Proto::SigningInput &input) { + Transaction tx; + auto method = std::string(input.method().begin(), input.method().end()); + auto oep4 = Oep4(parse_hex(input.contract())); + + if (method == "transfer") { + auto fromAddress = Address(input.owner_address()); + auto toAddress = Address(input.to_address()); + auto payerAddress = Address(input.payer_address()); + tx = oep4.unsignedTransfer(fromAddress, toAddress, input.amount(), payerAddress, input.gas_price(), input.gas_limit(), input.nonce()); + } else if (method == "balanceOf") { + auto queryAddress = Address(input.query_address()); + tx = oep4.balanceOf(queryAddress, input.nonce()); + } else if (method == "decimals") { + tx = oep4.decimals(input.nonce()); + } + + return tx; +} + } // namespace TW::Ontology diff --git a/src/Ontology/Oep4TxBuilder.h b/src/Ontology/Oep4TxBuilder.h index 22706683001..3b1b7f22cb2 100644 --- a/src/Ontology/Oep4TxBuilder.h +++ b/src/Ontology/Oep4TxBuilder.h @@ -24,6 +24,8 @@ class Oep4TxBuilder { static Data transfer(const Ontology::Proto::SigningInput& input); static Data build(const Ontology::Proto::SigningInput& input); + + static Transaction buildTx(const Ontology::Proto::SigningInput& input); }; } // namespace TW::Ontology diff --git a/src/Ontology/Ong.cpp b/src/Ontology/Ong.cpp index 6d2c9e3860a..a0dedf78349 100644 --- a/src/Ontology/Ong.cpp +++ b/src/Ontology/Ong.cpp @@ -58,4 +58,15 @@ Transaction Ong::withdraw(const Signer& claimer, const Address& receiver, uint64 return tx; } +Transaction Ong::unsignedTransfer(const Address &from, const Address &to, uint64_t amount, const Address &payer, + uint64_t gasPrice, uint64_t gasLimit,uint32_t nonce) { + NeoVmParamValue::ParamList transferParam{from._data, to._data, amount}; + NeoVmParamValue::ParamArray args{transferParam}; + auto invokeCode = + ParamsBuilder::buildNativeInvokeCode(contractAddress(), 0x00, "transfer", {args}); + auto tx = Transaction(version, txType, nonce, gasPrice, gasLimit, + payer.string(), invokeCode); + return tx; +} + } // namespace TW::Ontology diff --git a/src/Ontology/Ong.h b/src/Ontology/Ong.h index d73bdd8d888..5e5b8f8ee33 100644 --- a/src/Ontology/Ong.h +++ b/src/Ontology/Ong.h @@ -31,6 +31,9 @@ class Ong : public Asset { Transaction withdraw(const Signer &claimer, const Address &receiver, uint64_t amount, const Signer &payer, uint64_t gasPrice, uint64_t gasLimit, uint32_t nonce); + + Transaction unsignedTransfer(const Address &from, const Address &to, uint64_t amount, const Address &payer, + uint64_t gasPrice, uint64_t gasLimit, uint32_t nonce) override; }; } // namespace TW::Ontology diff --git a/src/Ontology/OngTxBuilder.cpp b/src/Ontology/OngTxBuilder.cpp index 8ca67a34d74..884e44c7eba 100644 --- a/src/Ontology/OngTxBuilder.cpp +++ b/src/Ontology/OngTxBuilder.cpp @@ -55,4 +55,13 @@ Data OngTxBuilder::build(const Ontology::Proto::SigningInput& input) { return Data(); } +Transaction OngTxBuilder::buildTransferTx(const Ontology::Proto::SigningInput &input) { + auto fromSigner = Address(input.owner_address()); + auto toAddress = Address(input.to_address()); + auto payerAddress = Address(input.payer_address()); + auto transferTx = Ong().unsignedTransfer(fromSigner, toAddress, input.amount(), payerAddress, + input.gas_price(), input.gas_limit(), input.nonce()); + return transferTx; +} + } // namespace TW::Ontology diff --git a/src/Ontology/OngTxBuilder.h b/src/Ontology/OngTxBuilder.h index f76595c73ba..943f8656615 100644 --- a/src/Ontology/OngTxBuilder.h +++ b/src/Ontology/OngTxBuilder.h @@ -26,6 +26,8 @@ class OngTxBuilder { static Data withdraw(const Ontology::Proto::SigningInput& input); static Data build(const Ontology::Proto::SigningInput& input); + + static Transaction buildTransferTx(const Ontology::Proto::SigningInput &input); }; } // namespace TW::Ontology diff --git a/src/Ontology/Ont.cpp b/src/Ontology/Ont.cpp index a25a7745809..94df172d58b 100644 --- a/src/Ontology/Ont.cpp +++ b/src/Ontology/Ont.cpp @@ -44,4 +44,16 @@ Transaction Ont::transfer(const Signer& from, const Address& to, uint64_t amount return tx; } +Transaction Ont::unsignedTransfer(const Address& from, const Address& to, uint64_t amount, + const Address& payer, uint64_t gasPrice, uint64_t gasLimit, + uint32_t nonce) { + NeoVmParamValue::ParamList transferParam{from._data, to._data, amount}; + NeoVmParamValue::ParamArray args{transferParam}; + auto invokeCode = + ParamsBuilder::buildNativeInvokeCode(contractAddress(), 0x00, "transfer", {args}); + auto tx = Transaction(version, txType, nonce, gasPrice, gasLimit, + payer.string(), invokeCode); + return tx; +} + } // namespace TW::Ontology diff --git a/src/Ontology/Ont.h b/src/Ontology/Ont.h index a44b8031503..bf1649864fc 100644 --- a/src/Ontology/Ont.h +++ b/src/Ontology/Ont.h @@ -23,11 +23,15 @@ class Ont : public Asset { Transaction decimals(uint32_t nonce) override; - Transaction balanceOf(const Address &address, uint32_t nonce) override; + Transaction balanceOf(const Address& address, uint32_t nonce) override; - Transaction transfer(const Signer &from, const Address &to, uint64_t amount, - const Signer &payer, uint64_t gasPrice, uint64_t gasLimit, + Transaction transfer(const Signer& from, const Address& to, uint64_t amount, + const Signer& payer, uint64_t gasPrice, uint64_t gasLimit, uint32_t nonce) override; + + Transaction unsignedTransfer(const Address& from, const Address& to, uint64_t amount, + const Address& payer, uint64_t gasPrice, uint64_t gasLimit, + uint32_t nonce) override; }; } // namespace TW::Ontology diff --git a/src/Ontology/OntTxBuilder.cpp b/src/Ontology/OntTxBuilder.cpp index de94601a929..683d8323310 100644 --- a/src/Ontology/OntTxBuilder.cpp +++ b/src/Ontology/OntTxBuilder.cpp @@ -43,4 +43,13 @@ Data OntTxBuilder::build(const Ontology::Proto::SigningInput& input) { return Data(); } +Transaction OntTxBuilder::buildTransferTx(const Ontology::Proto::SigningInput &input) { + auto fromSigner = Address(input.owner_address()); + auto toAddress = Address(input.to_address()); + auto payerAddress = Address(input.payer_address()); + auto transferTx = Ont().unsignedTransfer(fromSigner, toAddress, input.amount(), payerAddress, + input.gas_price(), input.gas_limit(), input.nonce()); + return transferTx; +} + } // namespace TW::Ontology diff --git a/src/Ontology/OntTxBuilder.h b/src/Ontology/OntTxBuilder.h index 1ca982541ad..e50f28a324f 100644 --- a/src/Ontology/OntTxBuilder.h +++ b/src/Ontology/OntTxBuilder.h @@ -24,6 +24,8 @@ class OntTxBuilder { static Data transfer(const Ontology::Proto::SigningInput& input); static Data build(const Ontology::Proto::SigningInput& input); + + static Transaction buildTransferTx(const Ontology::Proto::SigningInput &input); }; } // namespace TW::Ontology diff --git a/src/Ontology/Signer.cpp b/src/Ontology/Signer.cpp index fd09418b761..0c25073bff6 100644 --- a/src/Ontology/Signer.cpp +++ b/src/Ontology/Signer.cpp @@ -9,7 +9,6 @@ #include "HexCoding.h" #include "SigData.h" #include "../Ontology/Oep4TxBuilder.h" - #include "../Ontology/OngTxBuilder.h" #include "../Ontology/OntTxBuilder.h" @@ -37,8 +36,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { return output; } -Signer::Signer(TW::PrivateKey priKey) - : privKey(std::move(priKey)) { +Signer::Signer(TW::PrivateKey priKey) : privKey(std::move(priKey)) { auto pubKey = privKey.getPublicKey(TWPublicKeyTypeNIST256p1); publicKey = pubKey.bytes; address = Address(pubKey).string(); @@ -60,7 +58,7 @@ void Signer::sign(Transaction& tx) const { if (tx.sigVec.size() >= Transaction::sigVecLimit) { throw std::runtime_error("the number of transaction signatures should not be over 16."); } - auto signature = getPrivateKey().sign(Hash::sha256(tx.txHash()), TWCurveNIST256p1); + auto signature = getPrivateKey().sign(tx.txHash(), TWCurveNIST256p1); signature.pop_back(); tx.sigVec.emplace_back(publicKey, signature, 1); } @@ -69,9 +67,30 @@ void Signer::addSign(Transaction& tx) const { if (tx.sigVec.size() >= Transaction::sigVecLimit) { throw std::runtime_error("the number of transaction signatures should not be over 16."); } - auto signature = getPrivateKey().sign(Hash::sha256(tx.txHash()), TWCurveNIST256p1); + auto signature = getPrivateKey().sign(tx.txHash(), TWCurveNIST256p1); signature.pop_back(); tx.sigVec.emplace_back(publicKey, signature, 1); } +Data Signer::encodeTransaction(const Proto::SigningInput& input, const std::vector& signatures, const std::vector& publicKeys) { + assert(signatures.size() > 0 && signatures.size() == publicKeys.size()); + + auto contract = std::string(input.contract().begin(), input.contract().end()); + auto tx = Transaction(); + + if (contract == "ONT") { + tx = OntTxBuilder::buildTransferTx(input); + } else if (contract == "ONG") { + tx = OngTxBuilder::buildTransferTx(input); + } else { + tx = Oep4TxBuilder::buildTx(input); + } + + for (auto i = 0u; i < signatures.size(); ++i) { + tx.sigVec.emplace_back(publicKeys[i].bytes, signatures[i], 1); + } + + return tx.serialize(); +} + } // namespace TW::Ontology diff --git a/src/Ontology/Signer.h b/src/Ontology/Signer.h index e28816e09c2..4b65213f87c 100644 --- a/src/Ontology/Signer.h +++ b/src/Ontology/Signer.h @@ -21,9 +21,9 @@ class Signer { public: /// Signs a Proto::SigningInput transaction static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; - + static Data encodeTransaction(const Proto::SigningInput& input, const std::vector& signatures, const std::vector& publicKeyss); private: - Data publicKey; + Data publicKey; TW::PrivateKey privKey; std::string address; diff --git a/src/Ontology/Transaction.cpp b/src/Ontology/Transaction.cpp index b8825195fa5..8c0bc0e119d 100644 --- a/src/Ontology/Transaction.cpp +++ b/src/Ontology/Transaction.cpp @@ -45,7 +45,7 @@ std::vector Transaction::serialize() { std::vector Transaction::txHash() { auto txSerialized = Transaction::serializeUnsigned(); - return Hash::sha256(Hash::sha256(txSerialized)); + return Hash::sha256(Hash::sha256(Hash::sha256(txSerialized))); } std::vector Transaction::serialize(const PublicKey& pk) { diff --git a/src/Ontology/Transaction.h b/src/Ontology/Transaction.h index 970b5f82671..48ecd4339de 100644 --- a/src/Ontology/Transaction.h +++ b/src/Ontology/Transaction.h @@ -40,6 +40,8 @@ class Transaction { std::vector sigVec; + Transaction() = default; + Transaction(uint8_t ver, uint8_t type, uint32_t nonce, uint64_t gasPrice, uint64_t gasLimit, std::string payer, std::vector payload) : version(ver) diff --git a/src/Polkadot/Address.h b/src/Polkadot/Address.h index cc865a5dc4d..af6edbdeb92 100644 --- a/src/Polkadot/Address.h +++ b/src/Polkadot/Address.h @@ -30,4 +30,3 @@ class Address: public SS58Address { explicit Address(const PublicKey& publicKey, std::uint32_t ss58Prefix): SS58Address(publicKey, ss58Prefix) {} }; } // namespace TW::Polkadot - diff --git a/src/Polkadot/Entry.cpp b/src/Polkadot/Entry.cpp index 60f8ead63bb..b780059a240 100644 --- a/src/Polkadot/Entry.cpp +++ b/src/Polkadot/Entry.cpp @@ -8,6 +8,7 @@ #include "Address.h" #include "Signer.h" +#include "../proto/TransactionCompiler.pb.h" namespace TW::Polkadot { @@ -36,4 +37,23 @@ void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::D signTemplate(dataIn, dataOut); } +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + + auto preImage = Signer::signaturePreImage(input); + auto preImageHash = Signer::hash(preImage); + output.set_data_hash(preImageHash.data(), preImageHash.size()); + output.set_data(preImage.data(), preImage.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [](const auto& input, auto& output, const auto& signature, const auto& publicKey) { + auto signedTx = Signer::encodeTransaction(input, publicKey.bytes, signature); + output.set_encoded(signedTx.data(), signedTx.size()); + }); +} } // namespace TW::Polkadot diff --git a/src/Polkadot/Entry.h b/src/Polkadot/Entry.h index bf22d9b83b9..be982d40921 100644 --- a/src/Polkadot/Entry.h +++ b/src/Polkadot/Entry.h @@ -18,6 +18,8 @@ class Entry final : public CoinEntry { std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; Data addressToData(TWCoinType coin, const std::string& address) const; void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Polkadot diff --git a/src/Polkadot/Extrinsic.cpp b/src/Polkadot/Extrinsic.cpp index c9b73549593..c85154ae809 100644 --- a/src/Polkadot/Extrinsic.cpp +++ b/src/Polkadot/Extrinsic.cpp @@ -24,6 +24,7 @@ static const std::string stakingUnbond = "Staking.unbond"; static const std::string stakingWithdrawUnbond = "Staking.withdraw_unbonded"; static const std::string stakingNominate = "Staking.nominate"; static const std::string stakingChill = "Staking.chill"; +static const std::string stakingReBond = "Staking.rebond"; // Readable decoded call index can be found from https://polkascan.io static std::map polkadotCallIndices = { @@ -35,6 +36,7 @@ static std::map polkadotCallIndices = { {stakingNominate, Data{0x07, 0x05}}, {stakingChill, Data{0x07, 0x06}}, {utilityBatch, Data{0x1a, 0x02}}, + {stakingReBond, Data{0x07, 0x13}}, }; static std::map kusamaCallIndices = { @@ -46,6 +48,7 @@ static std::map kusamaCallIndices = { {stakingNominate, Data{0x06, 0x05}}, {stakingChill, Data{0x06, 0x06}}, {utilityBatch, Data{0x18, 0x02}}, + {stakingReBond, Data{0x06, 0x13}}, }; static Data getCallIndex(TWSS58AddressType network, const std::string& key) { @@ -89,15 +92,36 @@ Data Extrinsic::encodeCall(const Proto::SigningInput& input) { Data Extrinsic::encodeBalanceCall(const Proto::Balance& balance, TWSS58AddressType network, uint32_t specVersion) { Data data; - auto transfer = balance.transfer(); - auto address = SS58Address(transfer.to_address(), network); - auto value = load(transfer.value()); - // call index - append(data, getCallIndex(network, balanceTransfer)); - // destination - append(data, encodeAccountId(address.keyBytes(), encodeRawAccount(network, specVersion))); - // value - append(data, encodeCompact(value)); + if (balance.has_transfer()) { + auto transfer = balance.transfer(); + auto address = SS58Address(transfer.to_address(), network); + auto value = load(transfer.value()); + // call index + append(data, getCallIndex(network, balanceTransfer)); + // destination + append(data, encodeAccountId(address.keyBytes(), encodeRawAccount(network, specVersion))); + // value + append(data, encodeCompact(value)); + } else if (balance.has_batchtransfer()) { + //init call array + auto calls = std::vector(); + auto batchTransfer = balance.batchtransfer().transfers(); + for (auto transfer : batchTransfer) { + Data itemData; + auto address = SS58Address(transfer.to_address(), network); + auto value = load(transfer.value()); + // index + append(itemData, getCallIndex(network, balanceTransfer)); + // destination + append(itemData, encodeAccountId(address.keyBytes(), encodeRawAccount(network, specVersion))); + // value + append(itemData, encodeCompact(value)); + // put into calls array + calls.push_back(itemData); + } + data = encodeBatchCall(calls, network); + } + return data; } @@ -170,6 +194,14 @@ Data Extrinsic::encodeStakingCall(const Proto::Staking& staking, TWSS58AddressTy append(data, encodeCompact(value)); } break; + case Proto::Staking::kRebond: { + auto value = load(staking.rebond().value()); + // call index + append(data, getCallIndex(network, stakingReBond)); + // value + append(data, encodeCompact(value)); + } break; + case Proto::Staking::kWithdrawUnbonded: { auto spans = staking.withdraw_unbonded().slashing_spans(); // call index diff --git a/src/Polkadot/Signer.cpp b/src/Polkadot/Signer.cpp index 0c3a01a786d..fcba54e5f0e 100644 --- a/src/Polkadot/Signer.cpp +++ b/src/Polkadot/Signer.cpp @@ -30,4 +30,30 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { return protoOutput; } +Data Signer::signaturePreImage(const Proto::SigningInput &input) { + auto extrinsic = Extrinsic(input); + auto payload = extrinsic.encodePayload(); + // check if need to hash + if (payload.size() > hashTreshold) { + payload = Hash::blake2b(payload, 32); + } + return payload; +} + +Data Signer::encodeTransaction(const Proto::SigningInput &input, const Data &publicKey, const Data &signature) { + auto pbk = PublicKey(publicKey, TWPublicKeyTypeED25519); + auto extrinsic = Extrinsic(input); + auto encoded = extrinsic.encodeSignature(pbk, signature); + return encoded; +} + +Data Signer::hash(const Data &payload) { + // check if need to hash + if (payload.size() > hashTreshold) { + return Hash::blake2b(payload, 32); + } + + return payload; +} + } // namespace TW::Polkadot diff --git a/src/Polkadot/Signer.h b/src/Polkadot/Signer.h index bf4cb288246..fe6d9fd5e9f 100644 --- a/src/Polkadot/Signer.h +++ b/src/Polkadot/Signer.h @@ -16,10 +16,14 @@ namespace TW::Polkadot { class Signer { public: /// Hide default constructor - Signer() = delete; + explicit Signer(); /// Signs a Proto::SigningInput transaction static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; + + static Data signaturePreImage(const Proto::SigningInput &input); + static Data encodeTransaction(const Proto::SigningInput &input, const Data &publicKey, const Data &signature); + static Data hash(const Data &payload); }; } // namespace TW::Polkadot diff --git a/src/Solana/Constants.h b/src/Solana/Constants.h index 588be1ec89e..f78f89c224e 100644 --- a/src/Solana/Constants.h +++ b/src/Solana/Constants.h @@ -21,6 +21,7 @@ static const std::string STAKE_CONFIG_ID_ADDRESS = "StakeConfig11111111111111111 static const std::string NULL_ID_ADDRESS = "11111111111111111111111111111111"; static const std::string SYSVAR_STAKE_HISTORY_ID_ADDRESS = "SysvarStakeHistory1111111111111111111111111"; static const std::string MEMO_PROGRAM_ID_ADDRESS = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"; +static const std::string SYSVAR_RECENT_BLOCKHASHS_ADDRESS = "SysvarRecentB1ockHashes11111111111111111111"; // https://github.com/solana-labs/solana/blob/master/sdk/program/src/message/versions/mod.rs#L24 static const std::uint8_t MESSAGE_VERSION_PREFIX{0x80}; diff --git a/src/Solana/Entry.cpp b/src/Solana/Entry.cpp index 09df76df7f3..26c2db5c1ea 100644 --- a/src/Solana/Entry.cpp +++ b/src/Solana/Entry.cpp @@ -34,4 +34,29 @@ string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json return Signer::signJSON(json, key); } +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const TW::Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + auto signer = Signer(input); + auto preimageHash = signer.preImageHash(); + // for Solana, there is no need to hash data. + output.set_data(preimageHash.data(), preimageHash.size()); + auto signers = signer.signers(); + auto nSigners = output.mutable_signers(); + for (auto i = 0ul; i < signers.size();i++) { + auto newSigner = nSigners->Add(); + *newSigner = signers[i]; + } + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + auto signer = Signer(input); + output = signer.compile(signatures, publicKeys); + }); +} + } // namespace TW::Solana diff --git a/src/Solana/Entry.h b/src/Solana/Entry.h index 81d7ed64446..beb6b1457ab 100644 --- a/src/Solana/Entry.h +++ b/src/Solana/Entry.h @@ -6,6 +6,7 @@ #pragma once +#include "PublicKey.h" #include "CoinEntry.h" namespace TW::Solana { @@ -15,11 +16,13 @@ namespace TW::Solana { class Entry final : public CoinEntry { public: bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; - Data addressToData(TWCoinType coin, const std::string& address) const; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - bool supportsJSONSigning() const { return true; } - std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool supportsJSONSigning() const { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Solana diff --git a/src/Solana/Instruction.h b/src/Solana/Instruction.h index 535fcb25ccd..dfc63e02e5b 100644 --- a/src/Solana/Instruction.h +++ b/src/Solana/Instruction.h @@ -18,7 +18,10 @@ enum SystemInstruction { CreateAccount, Assign, Transfer, - CreateAccountWithSeed + CreateAccountWithSeed, + AdvanceNonceAccount, + WithdrawNonceAccount, + InitializeNonceAccount }; // Stake instruction types @@ -129,6 +132,53 @@ struct Instruction { std::vector accounts; // empty return Instruction(Address(MEMO_PROGRAM_ID_ADDRESS), accounts, data); } + + // create a system advance nonce account instruction to update nonce + static Instruction advanceNonceAccount(const Address authorizer, const Address nonceAccount) { + std::vector accountMetas = { + AccountMeta(nonceAccount, false, false), + AccountMeta(Address(SYSVAR_RECENT_BLOCKHASHS_ADDRESS), false, true), + AccountMeta(authorizer, true, true), + }; + const SystemInstruction type = AdvanceNonceAccount; + auto data = Data(); + encode32LE(static_cast(type), data); + + return Instruction(Address(SYSTEM_PROGRAM_ID_ADDRESS), accountMetas, data); + } + + // create a System initialize nonce instruction + static Instruction createInitializeNonce(const std::vector& accounts, + const Address authorizer) { + const SystemInstruction type = InitializeNonceAccount; + auto data = Data(); + encode32LE(static_cast(type), data); + append(data, authorizer.vector()); + + return Instruction(Address(SYSTEM_PROGRAM_ID_ADDRESS), accounts, data); + } + + // create a system create account instruction + static Instruction createAccount(const std::vector& accounts, uint64_t value, + uint64_t space, const Address owner) { + const SystemInstruction type = CreateAccount; + auto data = Data(); + encode32LE(static_cast(type), data); + encode64LE(static_cast(value), data); + encode64LE(static_cast(space), data); + append(data, owner.vector()); + + return Instruction(Address(SYSTEM_PROGRAM_ID_ADDRESS), accounts, data); + } + + static Instruction withdrawNonceAccount(const std::vector& accounts, + uint64_t value) { + const SystemInstruction type = WithdrawNonceAccount; + auto data = Data(); + encode32LE(static_cast(type), data); + encode64LE(static_cast(value), data); + return Instruction(Address(SYSTEM_PROGRAM_ID_ADDRESS), accounts, data); + } }; } diff --git a/src/Solana/LegacyMessage.cpp b/src/Solana/LegacyMessage.cpp index d997465e8b9..863e02edfd5 100644 --- a/src/Solana/LegacyMessage.cpp +++ b/src/Solana/LegacyMessage.cpp @@ -59,7 +59,11 @@ Data LegacyMessage::serialize() const { return buffer; } -void LegacyMessage::compileAccounts() { +void LegacyMessage::compileAccounts(const std::string& feePayerStr) { + if (Address::isValid(feePayerStr)) { + addAccount(AccountMeta(Address(feePayerStr), true, false)); + } + for (auto& instr : instructions) { for (auto& address : instr.accounts) { addAccount(address); diff --git a/src/Solana/LegacyMessage.h b/src/Solana/LegacyMessage.h index 6694e23607d..1f1b71aa207 100644 --- a/src/Solana/LegacyMessage.h +++ b/src/Solana/LegacyMessage.h @@ -37,10 +37,10 @@ class LegacyMessage { LegacyMessage() : mRecentBlockHash(Base58::decode(NULL_ID_ADDRESS)){}; - LegacyMessage(Data recentBlockHash, const std::vector& instructions) + LegacyMessage(Data recentBlockHash, const std::vector& instructions, const std::string& feePayerStr = "") : mRecentBlockHash(recentBlockHash) , instructions(instructions) { - compileAccounts(); + compileAccounts(feePayerStr); } // add an acount, to the corresponding bucket @@ -48,7 +48,7 @@ class LegacyMessage { // add an account to accountKeys if not yet present void addAccountKeys(const Address& account); // compile the single accounts lists from the buckets - void compileAccounts(); + void compileAccounts(const std::string& feePayerStr = ""); // compile the instructions; replace instruction accounts with indices void compileInstructions(); @@ -63,8 +63,12 @@ class LegacyMessage { // This constructor creates a default single-signer Transfer message static LegacyMessage createTransfer(const Address& from, const Address& to, uint64_t value, Data mRecentBlockHash, - std::string memo = "", std::vector
references = {}) { + std::string memo = "", std::vector
references = {}, const std::string& nonceAccountStr = "") { std::vector instructions; + if (Address::isValid(nonceAccountStr)) { + instructions.push_back( + Instruction::advanceNonceAccount(from, Address(nonceAccountStr))); + } if (memo.length() > 0) { // Optional memo. Order: before transfer, as per documentation. instructions.push_back(Instruction::createMemo(memo)); @@ -179,10 +183,15 @@ class LegacyMessage { // This constructor creates a createAccount token message // see create_associated_token_account() solana-program-library/associated-token-account/program/src/lib.rs - static LegacyMessage createTokenCreateAccount(const Address& signer, const Address& otherMainAccount, const Address& tokenMintAddress, const Address& tokenAddress, Data mRecentBlockHash) { + static LegacyMessage createTokenCreateAccount(const Address& signer, const Address& otherMainAccount, const Address& tokenMintAddress, const Address& tokenAddress, Data mRecentBlockHash, const std::string& nonceAccountStr = "") { auto sysvarRentId = Address(SYSVAR_RENT_ID_ADDRESS); auto systemProgramId = Address(SYSTEM_PROGRAM_ID_ADDRESS); auto tokenProgramId = Address(TOKEN_PROGRAM_ID_ADDRESS); + std::vector instructions; + if (Address::isValid(nonceAccountStr)) { + instructions.push_back( + Instruction::advanceNonceAccount(signer, Address(nonceAccountStr))); + } auto instruction = Instruction::createTokenCreateAccount(std::vector{ AccountMeta(signer, true, false), // fundingAddress, AccountMeta(tokenAddress, false, false), @@ -192,15 +201,21 @@ class LegacyMessage { AccountMeta(tokenProgramId, false, true), AccountMeta(sysvarRentId, false, true), }); - return LegacyMessage(mRecentBlockHash, {instruction}); + instructions.push_back(instruction); + return LegacyMessage(mRecentBlockHash, instructions); } // This constructor creates a transfer token message. // see transfer_checked() solana-program-library/token/program/src/instruction.rs static LegacyMessage createTokenTransfer(const Address& signer, const Address& tokenMintAddress, const Address& senderTokenAddress, const Address& recipientTokenAddress, uint64_t amount, uint8_t decimals, Data mRecentBlockHash, - std::string memo = "", std::vector
references = {}) { + std::string memo = "", std::vector
references = {}, + const std::string& nonceAccountStr = "", const std::string& feePayerStr = "") { std::vector instructions; + if (Address::isValid(nonceAccountStr)) { + instructions.push_back( + Instruction::advanceNonceAccount(signer, Address(nonceAccountStr))); + } if (memo.length() > 0) { // Optional memo. Order: before transfer, as per documentation. instructions.push_back(Instruction::createMemo(memo)); @@ -213,27 +228,41 @@ class LegacyMessage { }; appendReferences(accountMetas, references); instructions.push_back(Instruction::createTokenTransfer(accountMetas, amount, decimals)); + + if (Address::isValid(feePayerStr)) { + return LegacyMessage(mRecentBlockHash, instructions, feePayerStr); + } + return LegacyMessage(mRecentBlockHash, instructions); } // This constructor creates a createAndTransferToken message, combining createAccount and transfer. static LegacyMessage createTokenCreateAndTransfer(const Address& signer, const Address& recipientMainAddress, const Address& tokenMintAddress, const Address& recipientTokenAddress, const Address& senderTokenAddress, uint64_t amount, uint8_t decimals, Data mRecentBlockHash, - std::string memo = "", std::vector
references = {}) { + std::string memo = "", std::vector
references = {}, + const std::string& nonceAccountStr = "", const std::string& feePayerStr = "") { const auto sysvarRentId = Address(SYSVAR_RENT_ID_ADDRESS); const auto systemProgramId = Address(SYSTEM_PROGRAM_ID_ADDRESS); const auto tokenProgramId = Address(TOKEN_PROGRAM_ID_ADDRESS); std::vector instructions; instructions.reserve(3); - instructions.emplace_back(Instruction::createTokenCreateAccount(std::vector{ - AccountMeta(signer, true, false), // fundingAddress, - AccountMeta(recipientTokenAddress, false, false), - AccountMeta(recipientMainAddress, false, true), - AccountMeta(tokenMintAddress, false, true), - AccountMeta(systemProgramId, false, true), - AccountMeta(tokenProgramId, false, true), - AccountMeta(sysvarRentId, false, true), - })); + if (Address::isValid(nonceAccountStr)) { + instructions.push_back( + Instruction::advanceNonceAccount(signer, Address(nonceAccountStr))); + } + std::vector createTokenAccountAccountMetas; + if (Address::isValid(feePayerStr)) { + createTokenAccountAccountMetas.emplace_back(AccountMeta(Address(feePayerStr), true, false)); + } else { + createTokenAccountAccountMetas.emplace_back(AccountMeta(signer, true, false)); + } + createTokenAccountAccountMetas.emplace_back(AccountMeta(recipientTokenAddress, false, false)); + createTokenAccountAccountMetas.emplace_back(AccountMeta(recipientMainAddress, false, true)); + createTokenAccountAccountMetas.emplace_back(AccountMeta(tokenMintAddress, false, true)); + createTokenAccountAccountMetas.emplace_back(AccountMeta(systemProgramId, false, true)); + createTokenAccountAccountMetas.emplace_back(AccountMeta(tokenProgramId, false, true)); + createTokenAccountAccountMetas.emplace_back(AccountMeta(sysvarRentId, false, true)); + instructions.emplace_back(Instruction::createTokenCreateAccount(createTokenAccountAccountMetas)); if (memo.length() > 0) { // Optional memo. Order: before transfer, as per documentation. instructions.emplace_back(Instruction::createMemo(memo)); @@ -246,6 +275,63 @@ class LegacyMessage { }; appendReferences(accountMetas, references); instructions.push_back(Instruction::createTokenTransfer(accountMetas, amount, decimals)); + if (Address::isValid(feePayerStr)) { + return LegacyMessage(mRecentBlockHash, instructions, feePayerStr); + } + return LegacyMessage(mRecentBlockHash, instructions); + } + + static LegacyMessage createNonceAccount(const Address& sender, const Address& newNonceAccountAddress, + uint64_t rent, Data mRecentBlockHash, + std::string nonceAccountStr = "") { + auto sysvarRentId = Address(SYSVAR_RENT_ID_ADDRESS); + auto sysvarRecentBlockhashsId = Address(SYSVAR_RECENT_BLOCKHASHS_ADDRESS); + auto systemProgramId = Address(SYSTEM_PROGRAM_ID_ADDRESS); + std::vector createAccountAccountMetas = { + AccountMeta(sender, true, false), AccountMeta(newNonceAccountAddress, true, false)}; + std::vector initializeNonceAccountAccountMetas = { + AccountMeta(newNonceAccountAddress, false, false), + AccountMeta(sysvarRecentBlockhashsId, false, true), + AccountMeta(sysvarRentId, false, true)}; + std::vector instructions; + if (Address::isValid(nonceAccountStr)) { + instructions.push_back( + Instruction::advanceNonceAccount(sender, Address(nonceAccountStr))); + } + instructions.push_back( + Instruction::createAccount(createAccountAccountMetas, rent, 80, systemProgramId)); + instructions.push_back( + Instruction::createInitializeNonce(initializeNonceAccountAccountMetas, sender)); + return LegacyMessage(mRecentBlockHash, instructions); + } + + static LegacyMessage createWithdrawNonceAccount(const Address& authorizer, + const Address& nonceAccountAddress, const Address& to, + uint64_t value, Data mRecentBlockHash, + std::string nonceAccountStr = "") { + auto sysvarRecentBlockhashsId = Address(SYSVAR_RECENT_BLOCKHASHS_ADDRESS); + auto sysvarRentId = Address(SYSVAR_RENT_ID_ADDRESS); + std::vector accountMetas = { + AccountMeta(nonceAccountAddress, false, false), AccountMeta(to, false, false), + AccountMeta(sysvarRecentBlockhashsId, false, true), + AccountMeta(sysvarRentId, false, true), AccountMeta(authorizer, true, true)}; + std::vector instructions; + if (Address::isValid(nonceAccountStr)) { + instructions.push_back( + Instruction::advanceNonceAccount(authorizer, Address(nonceAccountStr))); + } + instructions.push_back(Instruction::withdrawNonceAccount(accountMetas, value)); + return LegacyMessage(mRecentBlockHash, instructions); + } + + static LegacyMessage advanceNonceAccount(const Address& authorizer, + const Address& nonceAccountAddress, Data mRecentBlockHash) { + auto sysvarRecentBlockhashsId = Address(SYSVAR_RECENT_BLOCKHASHS_ADDRESS); + std::vector accountMetas = {AccountMeta(nonceAccountAddress, false, false), + AccountMeta(sysvarRecentBlockhashsId, false, true), + AccountMeta(authorizer, true, true)}; + std::vector instructions; + instructions.push_back(Instruction::advanceNonceAccount(authorizer, nonceAccountAddress)); return LegacyMessage(mRecentBlockHash, instructions); } }; diff --git a/src/Solana/Signer.cpp b/src/Solana/Signer.cpp index 788cb77de5f..5130dbb0cff 100644 --- a/src/Solana/Signer.cpp +++ b/src/Solana/Signer.cpp @@ -26,7 +26,8 @@ void Signer::sign(const std::vector& privateKeys, VersionedTransacti } // Helper to convert protobuf-string-collection references to Address vector -std::vector
convertReferences(const google::protobuf::RepeatedPtrField& references) { +std::vector
+convertReferences(const google::protobuf::RepeatedPtrField& references) { std::vector
ret; for (auto i = 0; i < references.size(); ++i) { if (Address::isValid(references[i])) { @@ -42,6 +43,11 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { LegacyMessage message; std::vector signerKeys; + if (Address::isValid(input.fee_payer())) { + auto feePayerKey = PrivateKey(Data(input.fee_payer_private_key().begin(), input.fee_payer_private_key().end())); + signerKeys.push_back(feePayerKey); + } + switch (input.transaction_type_case()) { case Proto::SigningInput::TransactionTypeCase::kTransferTransaction: { auto protoMessage = input.transfer_transaction(); @@ -130,7 +136,8 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto mainAddress = Address(protoMessage.main_address()); auto tokenMintAddress = Address(protoMessage.token_mint_address()); auto tokenAddress = Address(protoMessage.token_address()); - message = LegacyMessage::createTokenCreateAccount(userAddress, mainAddress, tokenMintAddress, tokenAddress, blockhash); + message = LegacyMessage::createTokenCreateAccount(userAddress, mainAddress, tokenMintAddress, + tokenAddress, blockhash, input.nonce_account()); signerKeys.push_back(key); } break; @@ -143,8 +150,10 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto amount = protoMessage.amount(); auto decimals = static_cast(protoMessage.decimals()); const auto memo = protoMessage.memo(); - message = LegacyMessage::createTokenTransfer(userAddress, tokenMintAddress, senderTokenAddress, recipientTokenAddress, amount, decimals, blockhash, - memo, convertReferences(protoMessage.references())); + message = LegacyMessage::createTokenTransfer(userAddress, tokenMintAddress, senderTokenAddress, + recipientTokenAddress, amount, decimals, blockhash, + memo, convertReferences(protoMessage.references()), + input.nonce_account(), input.fee_payer()); signerKeys.push_back(key); } break; @@ -158,8 +167,44 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto amount = protoMessage.amount(); auto decimals = static_cast(protoMessage.decimals()); const auto memo = protoMessage.memo(); - message = LegacyMessage::createTokenCreateAndTransfer(userAddress, recipientMainAddress, tokenMintAddress, recipientTokenAddress, senderTokenAddress, amount, decimals, blockhash, - memo, convertReferences(protoMessage.references())); + message = LegacyMessage::createTokenCreateAndTransfer( + userAddress, recipientMainAddress, tokenMintAddress, recipientTokenAddress, + senderTokenAddress, amount, decimals, blockhash, memo, + convertReferences(protoMessage.references()), input.nonce_account(), input.fee_payer()); + signerKeys.push_back(key); + } break; + + case Proto::SigningInput::TransactionTypeCase::kCreateNonceAccount: { + auto createNonceAccountTransaction = input.create_nonce_account(); + auto nonceAccountKey = + PrivateKey(Data(createNonceAccountTransaction.nonce_account_private_key().begin(), + createNonceAccountTransaction.nonce_account_private_key().end())); + message = LegacyMessage::createNonceAccount( + /* owner */ Address(key.getPublicKey(TWPublicKeyTypeED25519)), + /* new nonce_account */ Address(nonceAccountKey.getPublicKey(TWPublicKeyTypeED25519)), + /* rent */ createNonceAccountTransaction.rent(), + /* recent_blockhash */ blockhash, + /* nonce_account */ input.nonce_account()); + signerKeys.push_back(key); + signerKeys.push_back(nonceAccountKey); + } break; + + case Proto::SigningInput::TransactionTypeCase::kWithdrawNonceAccount: { + auto withdrawNonceAccountTransaction = input.withdraw_nonce_account(); + message = LegacyMessage::createWithdrawNonceAccount( + /* owner */ Address(key.getPublicKey(TWPublicKeyTypeED25519)), + /* sender */ Address(withdrawNonceAccountTransaction.nonce_account()), + /* recipient */ Address(withdrawNonceAccountTransaction.recipient()), + /* value */ withdrawNonceAccountTransaction.value(), + /* recent_blockhash */ blockhash, + /* nonce_account */ input.nonce_account()); + signerKeys.push_back(key); + } break; + case Proto::SigningInput::TransactionTypeCase::kAdvanceNonceAccount: { + auto advanceNonceAccountTransaction = input.advance_nonce_account(); + auto userAddress = Address(key.getPublicKey(TWPublicKeyTypeED25519)); + auto nonceAccountAddress = Address(advanceNonceAccountTransaction.nonce_account()); + message = LegacyMessage::advanceNonceAccount(userAddress, nonceAccountAddress, blockhash); signerKeys.push_back(key); } break; @@ -214,4 +259,274 @@ std::string Signer::signJSON(const std::string& json, const Data& key) { return Signer::sign(input).encoded(); } +TW::Data Signer::preImageHash() const { + TW::Data preImageHash; + auto recentBlockhash = Base58::decode(input.recent_blockhash()); + switch (input.transaction_type_case()) { + case Proto::SigningInput::TransactionTypeCase::kTransferTransaction: { + auto transferTransaction = input.transfer_transaction(); + auto from = input.sender(); + auto message = LegacyMessage::createTransfer( + /* from */ Address(from), + /* to */ Address(transferTransaction.recipient()), + /* value */ transferTransaction.value(), + /* recent_blockhash */ recentBlockhash, + /* memo */ transferTransaction.memo(), + /* references */ convertReferences(transferTransaction.references()), + /* nonce_account */ input.nonce_account()); + auto transaction = Transaction(message); + preImageHash = transaction.messageData(); + } break; + case Proto::SigningInput::TransactionTypeCase::kCreateNonceAccount: { + auto createNonceAccountTransaction = input.create_nonce_account(); + auto from = input.sender(); + auto nonceAccount = createNonceAccountTransaction.nonce_account(); + auto message = LegacyMessage::createNonceAccount( + /* owner */ Address(from), + /* new nonce_account */ Address(createNonceAccountTransaction.nonce_account()), + /* rent */ createNonceAccountTransaction.rent(), + /* recent_blockhash */ recentBlockhash, + /* nonce_account */ input.nonce_account()); + auto transaction = Transaction(message); + preImageHash = transaction.messageData(); + } break; + case Proto::SigningInput::TransactionTypeCase::kWithdrawNonceAccount: { + auto withdrawNonceAccountTransaction = input.withdraw_nonce_account(); + auto owner = input.sender(); + auto message = LegacyMessage::createWithdrawNonceAccount( + /* owner */ Address(owner), + /* sender */ Address(withdrawNonceAccountTransaction.nonce_account()), + /* recipient */ Address(withdrawNonceAccountTransaction.recipient()), + /* value */ withdrawNonceAccountTransaction.value(), + /* recent_blockhash */ recentBlockhash, + /* nonce_account */ input.nonce_account()); + auto transaction = Transaction(message); + preImageHash = transaction.messageData(); + } break; + case Proto::SigningInput::TransactionTypeCase::kCreateTokenAccountTransaction: { + auto createTokenAccontTransaction = input.create_token_account_transaction(); + auto userAddress = Address(input.sender()); + auto mainAddress = Address(createTokenAccontTransaction.main_address()); + auto tokenMintAddress = Address(createTokenAccontTransaction.token_mint_address()); + auto tokenAddress = Address(createTokenAccontTransaction.token_address()); + auto message = + LegacyMessage::createTokenCreateAccount(userAddress, mainAddress, tokenMintAddress, + tokenAddress, recentBlockhash, input.nonce_account()); + auto transaction = Transaction(message); + preImageHash = transaction.messageData(); + } break; + case Proto::SigningInput::TransactionTypeCase::kTokenTransferTransaction: { + auto tokenTransferTransaction = input.token_transfer_transaction(); + auto userAddress = Address(input.sender()); + auto tokenMintAddress = Address(tokenTransferTransaction.token_mint_address()); + auto senderTokenAddress = Address(tokenTransferTransaction.sender_token_address()); + auto recipientTokenAddress = Address(tokenTransferTransaction.recipient_token_address()); + auto amount = tokenTransferTransaction.amount(); + auto decimals = static_cast(tokenTransferTransaction.decimals()); + const auto memo = tokenTransferTransaction.memo(); + auto message = LegacyMessage::createTokenTransfer( + userAddress, tokenMintAddress, senderTokenAddress, recipientTokenAddress, amount, + decimals, recentBlockhash, memo, + convertReferences(tokenTransferTransaction.references()), input.nonce_account(), input.fee_payer()); + auto transaction = Transaction(message); + preImageHash = transaction.messageData(); + } break; + case Proto::SigningInput::TransactionTypeCase::kCreateAndTransferTokenTransaction: { + auto createAndTransferTokenTransaction = input.create_and_transfer_token_transaction(); + auto userAddress = Address(input.sender()); + auto recipientMainAddress = + Address(createAndTransferTokenTransaction.recipient_main_address()); + auto tokenMintAddress = Address(createAndTransferTokenTransaction.token_mint_address()); + auto recipientTokenAddress = + Address(createAndTransferTokenTransaction.recipient_token_address()); + auto senderTokenAddress = Address(createAndTransferTokenTransaction.sender_token_address()); + auto amount = createAndTransferTokenTransaction.amount(); + auto decimals = static_cast(createAndTransferTokenTransaction.decimals()); + const auto memo = createAndTransferTokenTransaction.memo(); + auto message = LegacyMessage::createTokenCreateAndTransfer( + userAddress, recipientMainAddress, tokenMintAddress, recipientTokenAddress, + senderTokenAddress, amount, decimals, recentBlockhash, memo, + convertReferences(createAndTransferTokenTransaction.references()), + input.nonce_account(), input.fee_payer()); + auto transaction = Transaction(message); + preImageHash = transaction.messageData(); + } break; + case Proto::SigningInput::TransactionTypeCase::kAdvanceNonceAccount: { + auto advanceNonceAccountTransaction = input.advance_nonce_account(); + auto userAddress = Address(input.sender()); + auto nonceAccountAddress = Address(advanceNonceAccountTransaction.nonce_account()); + auto message = + LegacyMessage::advanceNonceAccount(userAddress, nonceAccountAddress, recentBlockhash); + auto transaction = Transaction(message); + preImageHash = transaction.messageData(); + } break; + default: + if (input.transaction_type_case() == + Proto::SigningInput::TransactionTypeCase::TRANSACTION_TYPE_NOT_SET) { + throw std::invalid_argument("transaction type not set"); + } + } + return preImageHash; +}; + +std::vector Signer::signers() const { + std::vector signers; + if (Address::isValid(input.fee_payer())) { + signers.push_back(input.fee_payer()); + } + switch (input.transaction_type_case()) { + case Proto::SigningInput::TransactionTypeCase::kTransferTransaction: + case Proto::SigningInput::TransactionTypeCase::kWithdrawNonceAccount: + case Proto::SigningInput::TransactionTypeCase::kCreateTokenAccountTransaction: + case Proto::SigningInput::TransactionTypeCase::kTokenTransferTransaction: + case Proto::SigningInput::TransactionTypeCase::kCreateAndTransferTokenTransaction: + case Proto::SigningInput::TransactionTypeCase::kAdvanceNonceAccount: { + auto sender = input.sender(); + signers.push_back(sender); + } break; + case Proto::SigningInput::TransactionTypeCase::kCreateNonceAccount: { + auto from = input.sender(); + auto createNonceAccountTransaction = input.create_nonce_account(); + auto nonceAccount = createNonceAccountTransaction.nonce_account(); + signers.push_back(from); + signers.push_back(nonceAccount); + } break; + default: + if (input.transaction_type_case() == + Proto::SigningInput::TransactionTypeCase::TRANSACTION_TYPE_NOT_SET) { + throw std::invalid_argument("transaction type not set"); + } + } + return signers; +}; + +Proto::SigningOutput Signer::compile(const std::vector& signatures, + const std::vector& publicKeys) const { + auto output = Proto::SigningOutput(); + LegacyMessage message; + auto recentBlockhash = Base58::decode(input.recent_blockhash()); + switch (input.transaction_type_case()) { + case Proto::SigningInput::TransactionTypeCase::kTransferTransaction: { + if (signatures.size() < 1) { + throw std::invalid_argument("too few signatures"); + } + auto transferTransaction = input.transfer_transaction(); + message = LegacyMessage::createTransfer( + /* from */ Address(input.sender()), + /* to */ Address(transferTransaction.recipient()), + /* value */ transferTransaction.value(), + /* recent_blockhash */ recentBlockhash, + /* memo */ transferTransaction.memo(), + /* references */ convertReferences(transferTransaction.references()), + /* nonce_account */ input.nonce_account()); + } break; + case Proto::SigningInput::TransactionTypeCase::kCreateNonceAccount: { + if (signatures.size() < 2) { + throw std::invalid_argument("too few signatures"); + } + auto createNonceAccountTransaction = input.create_nonce_account(); + auto from = input.sender(); + auto nonceAccount = createNonceAccountTransaction.nonce_account(); + message = LegacyMessage::createNonceAccount( + /* owner */ Address(from), + /* nonce_account */ Address(createNonceAccountTransaction.nonce_account()), + /* rent */ createNonceAccountTransaction.rent(), + /* recent_blockhash */ recentBlockhash, + /* nonce_account */ input.nonce_account()); + } break; + case Proto::SigningInput::TransactionTypeCase::kWithdrawNonceAccount: { + if (signatures.size() < 1) { + throw std::invalid_argument("too few signatures"); + } + auto withdrawNonceAccountTransaction = input.withdraw_nonce_account(); + message = LegacyMessage::createWithdrawNonceAccount( + /* owner */ Address(input.sender()), + /* sender */ Address(withdrawNonceAccountTransaction.nonce_account()), + /* recipient */ Address(withdrawNonceAccountTransaction.recipient()), + /* value */ withdrawNonceAccountTransaction.value(), + /* recent_blockhash */ recentBlockhash, + /* nonce_account */ input.nonce_account()); + } break; + case Proto::SigningInput::TransactionTypeCase::kCreateTokenAccountTransaction: { + if (signatures.size() < 1) { + throw std::invalid_argument("too few signatures"); + } + auto createTokenAccontTransaction = input.create_token_account_transaction(); + auto userAddress = Address(input.sender()); + auto mainAddress = Address(createTokenAccontTransaction.main_address()); + auto tokenMintAddress = Address(createTokenAccontTransaction.token_mint_address()); + auto tokenAddress = Address(createTokenAccontTransaction.token_address()); + message = + LegacyMessage::createTokenCreateAccount(userAddress, mainAddress, tokenMintAddress, + tokenAddress, recentBlockhash, input.nonce_account()); + } break; + case Proto::SigningInput::TransactionTypeCase::kTokenTransferTransaction: { + if (signatures.size() < 1) { + throw std::invalid_argument("too few signatures"); + } + auto tokenTransferTransaction = input.token_transfer_transaction(); + auto userAddress = Address(input.sender()); + auto tokenMintAddress = Address(tokenTransferTransaction.token_mint_address()); + auto senderTokenAddress = Address(tokenTransferTransaction.sender_token_address()); + auto recipientTokenAddress = Address(tokenTransferTransaction.recipient_token_address()); + auto amount = tokenTransferTransaction.amount(); + auto decimals = static_cast(tokenTransferTransaction.decimals()); + const auto memo = tokenTransferTransaction.memo(); + message = LegacyMessage::createTokenTransfer( + userAddress, tokenMintAddress, senderTokenAddress, recipientTokenAddress, amount, + decimals, recentBlockhash, memo, + convertReferences(tokenTransferTransaction.references()), input.nonce_account(), input.fee_payer()); + } break; + case Proto::SigningInput::TransactionTypeCase::kCreateAndTransferTokenTransaction: { + auto createAndTransferTokenTransaction = input.create_and_transfer_token_transaction(); + auto userAddress = Address(input.sender()); + auto recipientMainAddress = + Address(createAndTransferTokenTransaction.recipient_main_address()); + auto tokenMintAddress = Address(createAndTransferTokenTransaction.token_mint_address()); + auto recipientTokenAddress = + Address(createAndTransferTokenTransaction.recipient_token_address()); + auto senderTokenAddress = Address(createAndTransferTokenTransaction.sender_token_address()); + auto amount = createAndTransferTokenTransaction.amount(); + auto decimals = static_cast(createAndTransferTokenTransaction.decimals()); + const auto memo = createAndTransferTokenTransaction.memo(); + message = LegacyMessage::createTokenCreateAndTransfer( + userAddress, recipientMainAddress, tokenMintAddress, recipientTokenAddress, + senderTokenAddress, amount, decimals, recentBlockhash, memo, + convertReferences(createAndTransferTokenTransaction.references()), + input.nonce_account(), input.fee_payer()); + } break; + case Proto::SigningInput::TransactionTypeCase::kAdvanceNonceAccount: { + if (signatures.size() < 1) { + throw std::invalid_argument("too few signatures"); + } + auto advanceNonceAccountTransaction = input.advance_nonce_account(); + auto userAddress = Address(input.sender()); + auto nonceAccountAddress = Address(advanceNonceAccountTransaction.nonce_account()); + message = LegacyMessage::advanceNonceAccount(userAddress, nonceAccountAddress, recentBlockhash); + } break; + default: + if (input.transaction_type_case() == + Proto::SigningInput::TransactionTypeCase::TRANSACTION_TYPE_NOT_SET) { + throw std::invalid_argument("transaction type not set"); + } + } + auto transaction = Transaction(message); + auto preImageHash = transaction.messageData(); + if (publicKeys.size() != signatures.size()) { + throw std::invalid_argument( + "the number of public keys and the number of signatures not aligned"); + } + for (auto i = 0ul; i < signatures.size(); i++) { + if (!publicKeys[i].verify(signatures[i], preImageHash)) { + throw std::invalid_argument("invalid signature at " + std::to_string(i)); + } + auto addressIdx = transaction.getAccountIndex(Address(publicKeys[i])); + transaction.signatures[addressIdx] = signatures[i]; + } + // construst the output + auto encoded = transaction.serialize(); + output.set_encoded(encoded); + return output; +}; + } // namespace TW::Solana diff --git a/src/Solana/Signer.h b/src/Solana/Signer.h index d89d4656a34..e73e1844856 100644 --- a/src/Solana/Signer.h +++ b/src/Solana/Signer.h @@ -17,6 +17,11 @@ namespace TW::Solana { /// Helper class that performs Solana transaction signing. class Signer { public: + Proto::SigningInput input; + + /// Initializes a transaction signer. + explicit Signer(const Proto::SigningInput& input) : input(input) {} + /// Signs the given transaction. static void sign(const std::vector& privateKeys, VersionedTransaction& transaction); @@ -28,6 +33,10 @@ class Signer { static Data signRawMessage(const std::vector& privateKeys, const Data messageData); static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; -}; + TW::Data preImageHash() const; + Proto::SigningOutput compile(const std::vector& signatures, + const std::vector& publicKeys) const; + std::vector signers() const; +}; } // namespace TW::Solana diff --git a/src/Solana/Transaction.h b/src/Solana/Transaction.h index 0608382986b..c933f98a7c9 100644 --- a/src/Solana/Transaction.h +++ b/src/Solana/Transaction.h @@ -11,8 +11,8 @@ #include "Data.h" #include "BinaryCoding.h" -#include #include +#include namespace TW::Solana { diff --git a/src/Stellar/Entry.cpp b/src/Stellar/Entry.cpp index 1cd891d8752..aed693bdbc7 100644 --- a/src/Stellar/Entry.cpp +++ b/src/Stellar/Entry.cpp @@ -7,6 +7,7 @@ #include "Entry.h" #include "Address.h" +#include "proto/TransactionCompiler.pb.h" #include "Signer.h" namespace TW::Stellar { @@ -25,4 +26,29 @@ void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::D signTemplate(dataIn, dataOut); } +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + Signer signer(input); + + auto preImage = signer.signaturePreimage(); + auto preImageHash = Hash::sha256(preImage); + output.set_data_hash(preImageHash.data(), preImageHash.size()); + output.set_data(preImage.data(), preImage.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, [[maybe_unused]] const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() != 1) { + output.set_error(Common::Proto::Error_signatures_count); + output.set_error_message(Common::Proto::SigningError_Name(Common::Proto::Error_signatures_count)); + return; + } + + output = Signer(input).compile(signatures[0]); + }); +} + } // namespace TW::Stellar diff --git a/src/Stellar/Entry.h b/src/Stellar/Entry.h index b4c3489c989..4edd56c7959 100644 --- a/src/Stellar/Entry.h +++ b/src/Stellar/Entry.h @@ -14,9 +14,12 @@ namespace TW::Stellar { /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file class Entry final : public CoinEntry { public: - bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const override; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; }; } // namespace TW::Stellar diff --git a/src/Stellar/Signer.cpp b/src/Stellar/Signer.cpp index 4e39ad5e0e2..7defda0268d 100644 --- a/src/Stellar/Signer.cpp +++ b/src/Stellar/Signer.cpp @@ -33,7 +33,7 @@ std::string Signer::sign() const noexcept { auto publicNetwork = _input.passphrase(); // Header auto passphrase = Hash::sha256(publicNetwork); encodedWithHeaders.insert(encodedWithHeaders.end(), passphrase.begin(), passphrase.end()); - auto transactionType = Data{0, 0, 0, 2}; // Header + auto transactionType = Data{0, 0, 0, 2}; encodedWithHeaders.insert(encodedWithHeaders.end(), transactionType.begin(), transactionType.end()); encodedWithHeaders.insert(encodedWithHeaders.end(), encoded.begin(), encoded.end()); @@ -67,7 +67,13 @@ Data Signer::encode(const Proto::SigningInput& input) const { encode64BE(0, data); // from encode64BE(input.op_change_trust().valid_before(), data); // to } else { - encode32BE(0, data); // missing + if (input.time_bounds() > 0) { + encode32BE(1, data); + encode64BE(0, data); //from + encode64BE(input.time_bounds(), data); //to + } else { + encode32BE(0, data); // missing + } } // Memo @@ -142,6 +148,36 @@ Data Signer::encode(const Proto::SigningInput& input) const { return data; } +Data Signer::signaturePreimage() const { + auto encoded = encode(_input); + + auto encodedWithHeaders = Data(); + auto publicNetwork = _input.passphrase(); // Header + auto passphrase = Hash::sha256(publicNetwork); + encodedWithHeaders.insert(encodedWithHeaders.end(), passphrase.begin(), passphrase.end()); + auto transactionType = Data{0, 0, 0, 2}; // Header + encodedWithHeaders.insert(encodedWithHeaders.end(), transactionType.begin(), + transactionType.end()); + encodedWithHeaders.insert(encodedWithHeaders.end(), encoded.begin(), encoded.end()); + return encodedWithHeaders; +} + +Proto::SigningOutput Signer::compile(const Data& sig) const { + auto account = Address(_input.account()); + auto encoded = encode(_input); + + auto signature = Data(); + signature.insert(signature.end(), encoded.begin(), encoded.end()); + encode32BE(1, signature); + signature.insert(signature.end(), account.bytes.end() - 4, account.bytes.end()); + encode32BE(static_cast(sig.size()), signature); + signature.insert(signature.end(), sig.begin(), sig.end()); + + Proto::SigningOutput output; + output.set_signature(Base64::encode(signature)); + return output; +} + uint32_t Signer::operationType(const Proto::SigningInput& input) { switch (input.operation_oneof_case()) { case Proto::SigningInput::kOpCreateAccount: diff --git a/src/Stellar/Signer.h b/src/Stellar/Signer.h index fe7f983def7..39cf60fc172 100644 --- a/src/Stellar/Signer.h +++ b/src/Stellar/Signer.h @@ -28,6 +28,8 @@ class Signer { std::string sign() const noexcept; Data encode(const Proto::SigningInput& input) const; + Data signaturePreimage() const; + Proto::SigningOutput compile(const Data& sig) const; private: static uint32_t operationType(const Proto::SigningInput& input); diff --git a/src/Substrate/Address.h b/src/Substrate/Address.h new file mode 100644 index 00000000000..636a70594af --- /dev/null +++ b/src/Substrate/Address.h @@ -0,0 +1,33 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "../Data.h" +#include "../PublicKey.h" +#include "../FullSS58Address.h" + +#include + +namespace TW::Substrate { + +class Address: public FullSS58Address { + public: + /// Determines whether a string makes a valid address. + static bool isValid(const std::string& string, int32_t network) { return FullSS58Address::isValid(string, network); } + + /// Initializes a substrate like address with a string representation. + Address(const std::string& string, int32_t network): FullSS58Address(string, network) {} + + /// Initializes a substrate like address with a public key. + Address(const PublicKey& publicKey, int32_t network): FullSS58Address(publicKey, network) {} +}; + +} + +struct TWSubstrateAddress { + TW::Substrate::Address impl; +}; diff --git a/src/Substrate/Extrinsic.cpp b/src/Substrate/Extrinsic.cpp new file mode 100644 index 00000000000..992e1084f98 --- /dev/null +++ b/src/Substrate/Extrinsic.cpp @@ -0,0 +1,295 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "../BinaryCoding.h" +#include "Extrinsic.h" +#include + +namespace TW::Substrate { + +static constexpr uint8_t signedBit = 0x80; +static constexpr uint8_t sigTypeEd25519 = 0x00; +static constexpr uint8_t extrinsicFormat = 4; + +// max uint8 +static constexpr byte maxByte = 255; + +static Data encodeCallIndex(int32_t moduleIndex, int32_t methodIndex) { + if (moduleIndex > maxByte) { + throw std::invalid_argument("module index too large"); + } + if (methodIndex > maxByte) { + throw std::invalid_argument("method index too large"); + } + + return Data{byte(moduleIndex), byte(methodIndex)}; +} + +Data Extrinsic::encodeCall() { + Data data; + if (_network > maxByte) { + throw std::invalid_argument("method index too large"); + } + if (_input.has_balance_call()) { + data = encodeBalanceCall(_input.balance_call()); + } else if (_input.has_polymesh_call()) { + if (_input.polymesh_call().has_authorization_call()) { + data = encodeAuthorizationCall(_input.polymesh_call().authorization_call()); + } else if (_input.polymesh_call().has_identity_call()) { + data = encodeIdentityCall(_input.polymesh_call().identity_call()); + } else { + throw std::invalid_argument("Invalid polymesh call message"); + } + } else { + throw std::invalid_argument("Invalid call message"); + } + + return data; +} + +// Payload to sign. +Data Extrinsic::encodePayload() const { + Data data; + // call + append(data, call); + // era / nonce / tip + append(data, encodeEraNonceTip()); + // fee asset id + if (feeAssetId.size() > 0) { + append(data, feeAssetId); + } + // specVersion + encode32LE(_specVersion, data); + // transactionVersion + encode32LE(version, data); + // genesis hash + append(data, genesisHash); + // block hash + append(data, blockHash); + return data; +} + +// length prefix (2 bytes) + version header (1 bytes) + signer public key (AccountId [1byte] + pub key [32 bytes]) + signature types (1 byte, ed25519 is 0) + signature... + +// Encode final data with signer public key and signature. +Data Extrinsic::encodeSignature(const PublicKey& signer, const Data& signature) const { + Data data; + // version header + append(data, Data{extrinsicFormat | signedBit}); + // signer public key + append(data, Polkadot::encodeAccountId(signer.bytes, encodeRawAccount(multiAddress))); + // signature type + append(data, sigTypeEd25519); + // signature + append(data, signature); + // era / nonce / tip + append(data, encodeEraNonceTip()); + // fee asset id + if (feeAssetId.size() > 0) { + append(data, feeAssetId); + } + // call + append(data, call); + // append length + Polkadot::encodeLengthPrefix(data); + return data; +} + +bool Extrinsic::encodeRawAccount(bool enableMultiAddress) const { + return !enableMultiAddress; +} + +Data Extrinsic::encodeTransfer(const Proto::Balance::Transfer& transfer, int32_t network, bool enableMultiAddress) const { + Data data; + auto address = FullSS58Address(transfer.to_address(), network); + auto value = load(transfer.value()); + // call index + append(data, encodeCallIndex(transfer.module_index(), transfer.method_index())); + // destination + append(data, Polkadot::encodeAccountId(address.keyBytes(), encodeRawAccount(enableMultiAddress))); + // value + append(data, Polkadot::encodeCompact(value)); + // memo + if (transfer.memo().length() > 0) { + append(data, 0x01); + auto memo = transfer.memo(); + if (memo.length() < 32) { + // padding memo with null + memo.append(32 - memo.length(), '\0'); + } + append(data, TW::data(memo)); + } + return data; +} + +Data Extrinsic::encodeAssetTransfer(const Proto::Balance::AssetTransfer& transfer, int32_t network, bool enableMultiAddress) { + Data data; + auto address = FullSS58Address(transfer.to_address(), network); + auto value = load(transfer.value()); + + // call index + append(data, encodeCallIndex(transfer.module_index(), transfer.method_index())); + // asset id + if (transfer.asset_id() > 0) { + // For native token transfer, should ignore asset id + append(data, Polkadot::encodeCompact(transfer.asset_id())); + } + // destination + append(data, Polkadot::encodeAccountId(address.keyBytes(), encodeRawAccount(enableMultiAddress))); + // value + append(data, Polkadot::encodeCompact(value)); + return data; +} + +Data Extrinsic::encodeBalanceCall(const Proto::Balance& balance) { + Data data; + if (balance.has_transfer()) { + auto transfer = balance.transfer(); + append(data, encodeTransfer(transfer, _network, multiAddress)); + } else if (balance.has_batch_transfer()) { + // init call array + auto calls = std::vector(); + auto batchTransfer = balance.batch_transfer().transfers(); + for (auto transfer : batchTransfer) { + // put into calls array + calls.push_back(encodeTransfer(transfer, _network, multiAddress)); + } + data = encodeBatchCall(calls, balance.batch_transfer().module_index(), balance.batch_transfer().method_index()); + } else if (balance.has_asset_transfer()) { + // keep fee asset id encoding which will be used in encodePayload & encodeSignature. + // see: https://github.com/paritytech/substrate/blob/d1221692968b8bc62d6eab9d10cb6b5bf38c5dc2/frame/transaction-payment/asset-tx-payment/src/lib.rs#L152 + auto rawFeeAssetId = balance.asset_transfer().fee_asset_id(); + if (rawFeeAssetId > 0) { + this->feeAssetId.push_back(0x01); + Data feeEncoding; + encode32LE(rawFeeAssetId, feeEncoding); + append(this->feeAssetId, feeEncoding); + } else { + // use native token + this->feeAssetId.push_back(0x00); + } + + append(data, encodeAssetTransfer(balance.asset_transfer(), _network, multiAddress)); + } else if (balance.has_batch_asset_transfer()) { + // keep fee asset id encoding which will be used in encodePayload & encodeSignature. + // see: https://github.com/paritytech/substrate/blob/d1221692968b8bc62d6eab9d10cb6b5bf38c5dc2/frame/transaction-payment/asset-tx-payment/src/lib.rs#L152 + auto rawFeeAssetId = balance.batch_asset_transfer().fee_asset_id(); + if (rawFeeAssetId > 0) { + this->feeAssetId.push_back(0x01); + Data feeEncoding; + encode32LE(rawFeeAssetId, feeEncoding); + append(this->feeAssetId, feeEncoding); + } else { + // use native token + this->feeAssetId.push_back(0x00); + } + + // init call array + auto calls = std::vector(); + auto batchTransfer = balance.batch_asset_transfer().transfers(); + for (auto transfer : batchTransfer) { + // put into calls array + calls.push_back(encodeAssetTransfer(transfer, _network, multiAddress)); + } + data = encodeBatchCall(calls, balance.batch_asset_transfer().module_index(), balance.batch_asset_transfer().method_index()); + } + + return data; +} + +Data Extrinsic::encodeBatchCall(const std::vector& calls, int32_t moduleIndex, int32_t methodIndex) const { + Data data; + append(data, encodeCallIndex(moduleIndex, methodIndex)); + append(data, Polkadot::encodeVector(calls)); + return data; +} + +Data Extrinsic::encodeEraNonceTip() const { + Data data; + // era + append(data, era); + // nonce + append(data, Polkadot::encodeCompact(nonce)); + // tip + append(data, Polkadot::encodeCompact(tip)); + return data; +} + +Data Extrinsic::encodeAuthorizationCall(const Proto::Authorization& authorization) const { + Data data; + if (authorization.has_join_identity()) { + auto identity = authorization.join_identity(); + + // call index + append(data, encodeCallIndex(identity.module_index(), identity.method_index())); + + // target + append(data, 0x01); + + auto address = FullSS58Address(identity.target(), _network); + append(data, Polkadot::encodeAccountId(address.keyBytes(), true)); + + // join identity + append(data, 0x05); + + if (identity.has_data()) { + auto authData = identity.data(); + + // asset + if (authData.has_asset()) { + append(data, 0x01); + append(data, TW::data(authData.asset().data())); + } else { + append(data, 0x00); + } + + // extrinsic + if (authData.has_extrinsic()) { + append(data, 0x01); + append(data, TW::data(authData.extrinsic().data())); + } else { + append(data, 0x00); + } + + // portfolio + if (authData.has_portfolio()) { + append(data, 0x01); + append(data, TW::data(authData.portfolio().data())); + } else { + append(data, 0x00); + } + } else { + // authorize all permissions + append(data, {0x01, 0x00}); // asset + append(data, {0x01, 0x00}); // extrinsic + append(data, {0x01, 0x00}); // portfolio + } + append(data, Polkadot::encodeCompact(identity.expiry())); + } else { + throw std::invalid_argument("Invalid authorization message"); + } + + return data; +} + +Data Extrinsic::encodeIdentityCall(const Proto::Identity& identity) const { + Data data; + if (identity.has_join_identity_as_key()) { + auto key = identity.join_identity_as_key(); + + // call index + append(data, encodeCallIndex(key.module_index(), key.method_index())); + + // data + encode64LE(key.auth_id(), data); + } else { + throw std::invalid_argument("Invalid identity message"); + } + + return data; +} + +} // namespace TW::Substrate diff --git a/src/Substrate/Extrinsic.h b/src/Substrate/Extrinsic.h new file mode 100644 index 00000000000..c86a464d455 --- /dev/null +++ b/src/Substrate/Extrinsic.h @@ -0,0 +1,80 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Address.h" +#include "../Data.h" +#include "../Polkadot/ScaleCodec.h" +#include "../proto/Substrate.pb.h" +#include "../uint256.h" + +namespace TW::Substrate { + +// ExtrinsicV4 +class Extrinsic { +private: + Proto::SigningInput _input; + +public: + Data blockHash; + Data genesisHash; + uint64_t nonce; + // Runtime spec version + uint32_t _specVersion; + // transaction version + uint32_t version; + // balances::TakeFees + uint256_t tip; + // encoded Era data + Data era; + // encoded Call data + Data call; + // network + int32_t _network; + // enable multi-address + bool multiAddress; + + // keep fee asset ids + Data feeAssetId; + + Extrinsic(const Proto::SigningInput& input) + : _input(input) + , blockHash(input.block_hash().begin(), input.block_hash().end()) + , genesisHash(input.genesis_hash().begin(), input.genesis_hash().end()) + , nonce(input.nonce()) + , _specVersion(input.spec_version()) + , version(input.transaction_version()) + , tip(load(input.tip())) + , _network(input.network()) + , multiAddress(input.multi_address()) { + if (input.has_era()) { + era = Polkadot::encodeEra(input.era().block_number(), input.era().period()); + } else { + // immortal era + era = Polkadot::encodeCompact(0); + } + call = encodeCall(); + } + + Data encodeCall(); + // Payload to sign. + Data encodePayload() const; + // Encode final data with signer public key and signature. + Data encodeSignature(const PublicKey& signer, const Data& signature) const; + +protected: + bool encodeRawAccount(bool enableMultiAddress) const; + Data encodeTransfer(const Proto::Balance::Transfer& transfer, int32_t network, bool enableMultiAddress) const; + Data encodeAssetTransfer(const Proto::Balance::AssetTransfer& transfer, int32_t network, bool enableMultiAddress); + Data encodeBalanceCall(const Proto::Balance& balance); + Data encodeBatchCall(const std::vector& calls, int32_t moduleIndex, int32_t methodIndex) const; + Data encodeEraNonceTip() const; + Data encodeAuthorizationCall(const Proto::Authorization& authorization) const; + Data encodeIdentityCall(const Proto::Identity& authorization) const; +}; + +} // namespace TW::Substrate diff --git a/src/Substrate/Signer.cpp b/src/Substrate/Signer.cpp new file mode 100644 index 00000000000..3382aac1c4f --- /dev/null +++ b/src/Substrate/Signer.cpp @@ -0,0 +1,31 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Signer.h" +#include "Extrinsic.h" + +namespace TW::Substrate { + +static constexpr size_t hashTreshold = 256; + +Data Signer::signaturePreImage(const Proto::SigningInput& input) { + auto extrinsic = Extrinsic(input); + auto payload = extrinsic.encodePayload(); + // check if need to hash + if (payload.size() > hashTreshold) { + payload = Hash::blake2b(payload, 32); + } + return payload; +} + +Data Signer::encodeTransaction(const Proto::SigningInput& input, const Data& publicKey, const Data& signature) { + auto pbk = PublicKey(publicKey, TWPublicKeyTypeED25519); + auto extrinsic = Extrinsic(input); + auto encoded = extrinsic.encodeSignature(pbk, signature); + return encoded; +} + +} // namespace TW::Substrate diff --git a/src/Substrate/Signer.h b/src/Substrate/Signer.h new file mode 100644 index 00000000000..2a9c9fddf01 --- /dev/null +++ b/src/Substrate/Signer.h @@ -0,0 +1,21 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "../Data.h" +#include "../proto/Substrate.pb.h" + +namespace TW::Substrate { +class Signer { + public: + Signer() = delete; + + static Data signaturePreImage(const Proto::SigningInput &input); + static Data encodeTransaction(const Proto::SigningInput &input, const Data &publicKey, const Data &signature); +}; + +} // namespace TW::Substrate diff --git a/src/THORChain/Swap.h b/src/THORChain/Swap.h index 533d63c3b28..972d044d39b 100644 --- a/src/THORChain/Swap.h +++ b/src/THORChain/Swap.h @@ -13,6 +13,7 @@ #include #include #include +#include namespace TW::THORChainSwap { diff --git a/src/Tezos/Address.cpp b/src/Tezos/Address.cpp index 2b1ce0e83c1..7259209b2f3 100644 --- a/src/Tezos/Address.cpp +++ b/src/Tezos/Address.cpp @@ -21,6 +21,7 @@ namespace TW::Tezos { const std::array tz1Prefix{6, 161, 159}; const std::array tz2Prefix{6, 161, 161}; const std::array tz3Prefix{6, 161, 164}; +const std::array kt1Prefix{2, 90, 121}; bool Address::isValid(const std::string& string) { const auto decoded = Base58::decodeCheck(string); @@ -35,13 +36,25 @@ bool Address::isValid(const std::string& string) { return true; } + // contract prefix + if (std::equal(kt1Prefix.begin(), kt1Prefix.end(), decoded.begin())) { + return true; + } + return false; } Address::Address(const PublicKey& publicKey) { auto encoded = Data(publicKey.bytes.begin(), publicKey.bytes.end()); auto hash = Hash::blake2b(encoded, 20); - auto addressData = Data({6, 161, 159}); + Data addressData; + if (publicKey.type == TWPublicKeyTypeSECP256k1) { + addressData = Data({6, 161, 161}); + } else if (publicKey.type == TWPublicKeyTypeED25519){ + addressData = Data({6, 161, 159}); + } else { + throw std::invalid_argument("unsupported public key type"); + } append(addressData, hash); if (addressData.size() != Address::size) throw std::invalid_argument("Invalid address key data"); @@ -62,9 +75,38 @@ std::string Address::deriveOriginatedAddress(const std::string& operationHash, i return Base58::encodeCheck(prefix); } -Data Address::forge() const { +Data Address::forgePKH() const { std::string s = string(); return forgePublicKeyHash(s); } +Data Address::forge() const { + // normal address + // https://github.com/ecadlabs/taquito/blob/master/packages/taquito-local-forging/src/codec.ts#L183 + if (std::equal(tz1Prefix.begin(), tz1Prefix.end(), bytes.begin()) || + std::equal(tz2Prefix.begin(), tz2Prefix.end(), bytes.begin()) || + std::equal(tz3Prefix.begin(), tz3Prefix.end(), bytes.begin())) { + std::string s = string(); + Data forgedPKH = forgePublicKeyHash(s); + Data forged = Data(); + forged.insert(forged.end(), 0x00); + forged.insert(forged.end(), forgedPKH.begin(), forgedPKH.end()); + return forged; + } + + // contract address + // https://github.com/ecadlabs/taquito/blob/master/packages/taquito-local-forging/src/codec.ts#L183 + if (std::equal(kt1Prefix.begin(), kt1Prefix.end(), bytes.begin())) { + std::string s = string(); + Data forgedPrefix = forgePrefix(kt1Prefix, s); + Data forged = Data(); + forged.insert(forged.end(), 0x01); + forged.insert(forged.end(), forgedPrefix.begin(), forgedPrefix.end()); + forged.insert(forged.end(), 0x00); + return forged; + } + + throw std::invalid_argument("invalid address"); +} + } // namespace TW::Tezos diff --git a/src/Tezos/Address.h b/src/Tezos/Address.h index 54bceca1605..7a588266ebb 100644 --- a/src/Tezos/Address.h +++ b/src/Tezos/Address.h @@ -33,6 +33,9 @@ class Address : public TW::Base58Address<23> { /// Forge an address to hex bytes. Data forge() const; + + // without type prefix + Data forgePKH() const; }; } // namespace TW::Tezos diff --git a/src/Tezos/BinaryCoding.cpp b/src/Tezos/BinaryCoding.cpp index 143cdbb74c9..805dd47d561 100644 --- a/src/Tezos/BinaryCoding.cpp +++ b/src/Tezos/BinaryCoding.cpp @@ -27,15 +27,30 @@ std::string base58ToHex(const std::string& string, size_t prefixLength) { PublicKey parsePublicKey(const std::string& publicKey) { const auto decoded = Base58::decodeCheck(publicKey); - std::array prefix = {13, 15, 37, 217}; - auto pk = Data(); + std::array prefix; + enum TWPublicKeyType type; + std::array ed25519Prefix = {13, 15, 37, 217}; + std::array secp256k1Prefix = {3, 254, 226, 86}; - if (decoded.size() != 32 + prefix.size()) { + if (std::equal(std::begin(ed25519Prefix), std::end(ed25519Prefix), std::begin(decoded))) { + prefix = ed25519Prefix; + type = TWPublicKeyTypeED25519; + } else if (std::equal(std::begin(secp256k1Prefix), std::end(secp256k1Prefix), std::begin(decoded))) { + prefix = secp256k1Prefix; + type = TWPublicKeyTypeSECP256k1; + } else { + throw std::invalid_argument("Unsupported Public Key Type"); + } + auto pk = Data(); + if (type == TWPublicKeyTypeED25519 && decoded.size() != 32 + prefix.size()) { + throw std::invalid_argument("Invalid Public Key"); + } + if (type == TWPublicKeyTypeSECP256k1 && decoded.size() != 33 + prefix.size()) { throw std::invalid_argument("Invalid Public Key"); } append(pk, Data(decoded.begin() + prefix.size(), decoded.end())); - return PublicKey(pk, TWPublicKeyTypeED25519); + return PublicKey(pk, type); } PrivateKey parsePrivateKey(const std::string& privateKey) { diff --git a/src/Tezos/Entry.cpp b/src/Tezos/Entry.cpp index e134eadce05..a69af51916e 100644 --- a/src/Tezos/Entry.cpp +++ b/src/Tezos/Entry.cpp @@ -7,6 +7,7 @@ #include "Entry.h" #include "Address.h" +#include "proto/TransactionCompiler.pb.h" #include "Signer.h" namespace TW::Tezos { @@ -29,4 +30,44 @@ std::string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& return Signer::signJSON(json, key); } +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + auto operationList = TW::Tezos::OperationList(input.operation_list().branch()); + for (TW::Tezos::Proto::Operation operation : input.operation_list().operations()) { + operationList.addOperation(operation); + } + + auto preImage = Signer().buildUnsignedTx(operationList); + + // get preImage hash + Data watermarkedData = Data(); + watermarkedData.push_back(0x03); + append(watermarkedData, preImage); + auto preImageHash = Hash::blake2b(watermarkedData, 32); + + output.set_data_hash(preImageHash.data(), preImageHash.size()); + output.set_data(preImage.data(), preImage.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, [[maybe_unused]] const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() != 1) { + output.set_error(Common::Proto::Error_signatures_count); + output.set_error_message(Common::Proto::SigningError_Name(Common::Proto::Error_signatures_count)); + return; + } + + auto operationList = TW::Tezos::OperationList(input.operation_list().branch()); + for (TW::Tezos::Proto::Operation operation : input.operation_list().operations()) { + operationList.addOperation(operation); + } + auto tx = Signer().buildSignedTx(operationList, signatures[0]); + + output.set_encoded(tx.data(), tx.size()); + }); +} + } // namespace TW::Tezos diff --git a/src/Tezos/Entry.h b/src/Tezos/Entry.h index 85647cc89c6..579522f5feb 100644 --- a/src/Tezos/Entry.h +++ b/src/Tezos/Entry.h @@ -14,11 +14,14 @@ namespace TW::Tezos { /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file class Entry final : public CoinEntry { public: - bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - bool supportsJSONSigning() const { return true; } - std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const override; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + bool supportsJSONSigning() const override { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const override; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; }; } // namespace TW::Tezos diff --git a/src/Tezos/Forging.cpp b/src/Tezos/Forging.cpp index a81055f4242..19e3c07b68b 100644 --- a/src/Tezos/Forging.cpp +++ b/src/Tezos/Forging.cpp @@ -107,15 +107,36 @@ Data forgeAddress(const std::string& address) { throw std::invalid_argument("Invalid Prefix"); } +// https://github.com/ecadlabs/taquito/blob/master/packages/taquito-local-forging/src/codec.ts#L19 +Data forgePrefix(std::array prefix, const std::string& val) { + const auto decoded = Base58::decodeCheck(val); + if (!std::equal(prefix.begin(), prefix.end(), decoded.begin())) { + throw std::invalid_argument("prefix not match"); + } + + const auto prefixSize = 3; + Data forged = Data(); + forged.insert(forged.end(), decoded.begin() + prefixSize, decoded.end()); + return forged; +} + // Forge the given public key into a hex encoded string. Data forgePublicKey(PublicKey publicKey) { - std::array prefix = {13, 15, 37, 217}; + std::string tag; + std::array prefix; + if (publicKey.type == TWPublicKeyTypeED25519) { + prefix = {13, 15, 37, 217}; + tag = "00"; + } else if (publicKey.type == TWPublicKeyTypeSECP256k1) { + prefix = {3, 254, 226, 86}; + tag = "01"; + } auto data = Data(prefix.begin(), prefix.end()); auto bytes = Data(publicKey.bytes.begin(), publicKey.bytes.end()); append(data, bytes); auto pk = Base58::encodeCheck(data); - auto decoded = "00" + base58ToHex(pk, 4); + auto decoded = tag + base58ToHex(pk, 4); return parse_hex(decoded); } @@ -135,14 +156,22 @@ Data forgeOperation(const Proto::Operation& operation) { using namespace Proto; auto forged = Data(); auto source = Address(operation.source()); - auto forgedSource = source.forge(); + auto forgedSource = source.forgePKH(); //https://github.com/ecadlabs/taquito/blob/master/packages/taquito-local-forging/src/schema/operation.ts#L40 auto forgedFee = forgeZarith(operation.fee()); auto forgedCounter = forgeZarith(operation.counter()); auto forgedGasLimit = forgeZarith(operation.gas_limit()); auto forgedStorageLimit = forgeZarith(operation.storage_limit()); if (operation.kind() == Operation_OperationKind_REVEAL) { - auto publicKey = PublicKey(data(operation.reveal_operation_data().public_key()), TWPublicKeyTypeED25519); + enum TWPublicKeyType type; + if (operation.reveal_operation_data().public_key().size() == 32) { + type = TWPublicKeyTypeED25519; + } else if (operation.reveal_operation_data().public_key().size() == 33) { + type = TWPublicKeyTypeSECP256k1; + } else { + throw std::invalid_argument("unsupported public key type"); + } + auto publicKey = PublicKey(data(operation.reveal_operation_data().public_key()), type); auto forgedPublicKey = forgePublicKey(publicKey); forged.push_back(Operation_OperationKind_REVEAL); @@ -177,7 +206,7 @@ Data forgeOperation(const Proto::Operation& operation) { if (operation.kind() == Operation_OperationKind_TRANSACTION) { auto forgedAmount = forgeZarith(operation.transaction_operation_data().amount()); - auto forgedDestination = Address(operation.transaction_operation_data().destination()).forge(); + auto forgedDestination = forgeAddress(operation.transaction_operation_data().destination()); forged.emplace_back(Operation_OperationKind_TRANSACTION); append(forged, forgedSource); @@ -186,12 +215,10 @@ Data forgeOperation(const Proto::Operation& operation) { append(forged, forgedGasLimit); append(forged, forgedStorageLimit); append(forged, forgedAmount); - if (!operation.transaction_operation_data().has_parameters()) { - append(forged, forgeBool(false)); - append(forged, forgedDestination); + append(forged, forgedDestination); + if (!operation.transaction_operation_data().has_parameters() && operation.transaction_operation_data().encoded_parameter().empty()) { append(forged, forgeBool(false)); } else if (operation.transaction_operation_data().has_parameters()) { - append(forged, forgeAddress(operation.transaction_operation_data().destination())); append(forged, forgeBool(true)); auto& parameters = operation.transaction_operation_data().parameters(); switch (parameters.parameters_case()) { @@ -206,6 +233,8 @@ Data forgeOperation(const Proto::Operation& operation) { case OperationParameters::PARAMETERS_NOT_SET: break; } + } else { + append(forged, TW::data(operation.transaction_operation_data().encoded_parameter())); } return forged; } diff --git a/src/Tezos/Forging.h b/src/Tezos/Forging.h index 1b2d365cab2..7e8f0231aba 100644 --- a/src/Tezos/Forging.h +++ b/src/Tezos/Forging.h @@ -24,6 +24,7 @@ Data forgeOperation(const Proto::Operation& operation); Data forgeAddress(const std::string& address); Data forgeArray(const Data& data); Data forgePublicKeyHash(const std::string& publicKeyHash); +Data forgePrefix(std::array prefix, const std::string& val); Data forgePublicKey(PublicKey publicKey); Data forgeZarith(uint64_t input); Data forgeInt32(int value, int len = 4); diff --git a/src/Tezos/OperationList.cpp b/src/Tezos/OperationList.cpp index 15e445ce082..859a2d85b92 100644 --- a/src/Tezos/OperationList.cpp +++ b/src/Tezos/OperationList.cpp @@ -49,4 +49,14 @@ Data Tezos::OperationList::forge(const PrivateKey& privateKey) const { return forged; } +Data TW::Tezos::OperationList::forge() const { + auto forged = forgeBranch(); + + for (auto operation : operation_list) { + append(forged, forgeOperation(operation)); + } + + return forged; +} + } // namespace TW::Tezos diff --git a/src/Tezos/OperationList.h b/src/Tezos/OperationList.h index bf2cc6afe09..21023cd1385 100644 --- a/src/Tezos/OperationList.h +++ b/src/Tezos/OperationList.h @@ -23,6 +23,7 @@ class OperationList { void addOperation(const Operation& transaction); /// Returns a data representation of the operations. Data forge(const PrivateKey& privateKey) const; + Data forge() const; Data forgeBranch() const; }; diff --git a/src/Tezos/Signer.cpp b/src/Tezos/Signer.cpp index 7043ede69fa..0cf6b863006 100644 --- a/src/Tezos/Signer.cpp +++ b/src/Tezos/Signer.cpp @@ -63,4 +63,20 @@ Data Signer::signData(const PrivateKey& privateKey, const Data& data) { return signedData; } +Data Signer::buildUnsignedTx(const OperationList& operationList) { + Data txData = operationList.forge(); + return txData; +} + +Data Signer::buildSignedTx(const OperationList& operationList, Data signature) { + Data signedData = Data(); + + Data txData = operationList.forge(); + + append(signedData, txData); + append(signedData, signature); + + return signedData; +} + } // namespace TW::Tezos diff --git a/src/Tezos/Signer.h b/src/Tezos/Signer.h index 549480854d1..857875492cc 100644 --- a/src/Tezos/Signer.h +++ b/src/Tezos/Signer.h @@ -22,9 +22,12 @@ class Signer { static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; /// Signs a json Proto::SigningInput with private key static std::string signJSON(const std::string& json, const Data& key); + public: /// Signs the given transaction. Data signOperationList(const PrivateKey& privateKey, const OperationList& operationList); + Data buildUnsignedTx(const OperationList& operationList); + Data buildSignedTx(const OperationList& operationList, Data signature); Data signData(const PrivateKey& privateKey, const Data& data); }; diff --git a/src/Theta/Entry.cpp b/src/Theta/Entry.cpp index f4c585338b2..7590fba906c 100644 --- a/src/Theta/Entry.cpp +++ b/src/Theta/Entry.cpp @@ -5,9 +5,10 @@ // file LICENSE at the root of the source code distribution tree. #include "Entry.h" - #include "Ethereum/Address.h" #include "Signer.h" +#include "../proto/Theta.pb.h" +#include "../proto/TransactionCompiler.pb.h" namespace TW::Theta { @@ -15,4 +16,24 @@ void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::D signTemplate(dataIn, dataOut); } +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + const auto signer = Signer(input); + auto preImage = signer.signaturePreimage(); + auto preImageHash = Hash::keccak256(preImage); + output.set_data_hash(preImageHash.data(), preImageHash.size()); + output.set_data(preImage.data(), preImage.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [](const auto& input, auto& output, const auto& signature, const auto& publicKey) { + const auto signer = Signer(input); + output = signer.compile(signature, publicKey); + }); +} + } // namespace TW::Theta diff --git a/src/Theta/Entry.h b/src/Theta/Entry.h index cecc7e23336..7c6e2eeabfb 100644 --- a/src/Theta/Entry.h +++ b/src/Theta/Entry.h @@ -6,6 +6,7 @@ #pragma once +#include "Ethereum/Entry.h" #include "../CoinEntry.h" #include "Ethereum/Entry.h" @@ -16,6 +17,8 @@ namespace TW::Theta { class Entry final : public Ethereum::Entry { public: void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Theta diff --git a/src/Theta/Signer.cpp b/src/Theta/Signer.cpp index b5e8734f4b3..01d46f037f6 100755 --- a/src/Theta/Signer.cpp +++ b/src/Theta/Signer.cpp @@ -37,7 +37,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { return output; } -Data Signer::encode(const Transaction& transaction) noexcept { +Data Signer::encode(const Transaction& transaction) const { const uint64_t nonce = 0; const uint256_t gasPrice = 0; const uint64_t gasLimit = 0; @@ -67,4 +67,45 @@ Data Signer::sign(const PrivateKey& privateKey, const Transaction& transaction) return signature; } +Transaction Signer::buildTransaction() const{ + auto publicKey = PublicKey(Data(input.public_key().begin(), input.public_key().end()), TWPublicKeyTypeSECP256k1Extended); + auto from = Ethereum::Address(publicKey); + + auto transaction = Transaction( + /* from: */ from, + /* to: */ Ethereum::Address(input.to_address()), + /* thetaAmount: */ load(input.theta_amount()), + /* tfuelAmount: */ load(input.tfuel_amount()), + /* sequence: */ input.sequence(), + /* feeAmount: */ load(input.fee())); + return transaction; +} + +Data Signer::signaturePreimage() const { + return encode(this->buildTransaction()); +} + +Proto::SigningOutput Signer::compile(const Data& signature, const PublicKey& publicKey) const{ + // validate public key + if (publicKey.type != TWPublicKeyTypeSECP256k1Extended) { + throw std::invalid_argument("Invalid public key"); + } + { + // validate correctness of signature + auto preImage = signaturePreimage(); + auto preImageHash = Hash::keccak256(preImage); + if (!publicKey.verify(signature, preImageHash)) { + throw std::invalid_argument("Invalid signature/hash/publickey combination"); + } + } + auto from = Ethereum::Address(publicKey); + auto protoOutput = Proto::SigningOutput(); + auto transaction = buildTransaction(); + transaction.setSignature(from, signature); + auto encoded = transaction.encode(); + + protoOutput.set_encoded(encoded.data(), encoded.size()); + protoOutput.set_signature(signature.data(), signature.size()); + return protoOutput; +} } // namespace TW::Theta diff --git a/src/Theta/Signer.h b/src/Theta/Signer.h index 0d4dc08cd63..fb7caf13c2e 100644 --- a/src/Theta/Signer.h +++ b/src/Theta/Signer.h @@ -23,17 +23,19 @@ class Signer { public: std::string chainID; - - Signer() = default; + Proto::SigningInput input; + /// Initializes a transaction signer. + explicit Signer(const Proto::SigningInput& input) : chainID(input.chain_id()), input(input) {} /// Initializes a signer with a chain identifier which could be `mainnet`, `testnet` or /// `privatenet` explicit Signer(std::string chainID) : chainID(std::move(chainID)) {} /// Signs the given transaction Data sign(const PrivateKey& privateKey, const Transaction& transaction) noexcept; - - private: - Data encode(const Transaction& transaction) noexcept; + Data encode(const Transaction& transaction) const; + Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey) const; + Data signaturePreimage() const; + Transaction buildTransaction() const; }; } // namespace TW::Theta diff --git a/src/TransactionCompiler.cpp b/src/TransactionCompiler.cpp index f23ad77d032..721120c1259 100644 --- a/src/TransactionCompiler.cpp +++ b/src/TransactionCompiler.cpp @@ -36,3 +36,17 @@ Data TransactionCompiler::compileWithSignatures(TWCoinType coinType, const Data& anyCoinCompileWithSignatures(coinType, txInputData, signatures, pubs, txOutput); return txOutput; } + +Data TransactionCompiler::compileWithSignaturesAndPubKeyType(TWCoinType coinType, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, enum TWPublicKeyType pubKeyType) { + std::vector pubs; + for (auto& p: publicKeys) { + if (!PublicKey::isValid(p, pubKeyType)) { + throw std::invalid_argument("Invalid public key"); + } + pubs.push_back(PublicKey(p, pubKeyType)); + } + + Data txOutput; + anyCoinCompileWithSignatures(coinType, txInputData, signatures, pubs, txOutput); + return txOutput; +} diff --git a/src/TransactionCompiler.h b/src/TransactionCompiler.h index fc2cd66e6f1..68688af0827 100644 --- a/src/TransactionCompiler.h +++ b/src/TransactionCompiler.h @@ -29,6 +29,9 @@ class TransactionCompiler { /// Compile a complete transation with an external signature, put together from transaction input and provided public key and signature static Data compileWithSignatures(TWCoinType coinType, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys); + + static Data compileWithSignaturesAndPubKeyType(TWCoinType coinType, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, TWPublicKeyType pubKeyType); + }; } // namespace TW diff --git a/src/Tron/Entry.cpp b/src/Tron/Entry.cpp index a4e165be3a1..d3332240f5c 100644 --- a/src/Tron/Entry.cpp +++ b/src/Tron/Entry.cpp @@ -5,9 +5,10 @@ // file LICENSE at the root of the source code distribution tree. #include "Entry.h" - #include "Address.h" +#include "Serialization.h" #include "Signer.h" +#include "../proto/TransactionCompiler.pb.h" using namespace std; @@ -26,4 +27,30 @@ void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::D signTemplate(dataIn, dataOut); } +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + const auto signer = Signer(input); + auto preImage = signer.signaturePreimage(); + auto preImageHash = Hash::sha256(preImage); + output.set_data_hash(preImageHash.data(), preImageHash.size()); + output.set_data(preImage.data(), preImage.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, + [[maybe_unused]] const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + const auto signer = Signer(input); + if (signatures.size() != 1) { + output.set_error(Common::Proto::Error_no_support_n2n); + output.set_error_message( + Common::Proto::SigningError_Name(Common::Proto::Error_no_support_n2n)); + return; + } + output = signer.compile(signatures[0]); + }); +} } // namespace TW::Tron diff --git a/src/Tron/Entry.h b/src/Tron/Entry.h index 7fc6713a177..a25f876c253 100644 --- a/src/Tron/Entry.h +++ b/src/Tron/Entry.h @@ -15,8 +15,14 @@ namespace TW::Tron { class Entry final : public CoinEntry { public: bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + + void compile(TWCoinType coin, const Data& txInputData, + const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Tron diff --git a/src/Tron/Signer.cpp b/src/Tron/Signer.cpp index c55401a3c09..735925cbf8e 100644 --- a/src/Tron/Signer.cpp +++ b/src/Tron/Signer.cpp @@ -8,13 +8,13 @@ #include "Protobuf/TronInternal.pb.h" +#include "Serialization.h" #include "../Base58.h" #include "../BinaryCoding.h" #include "../HexCoding.h" -#include "Serialization.h" -#include #include +#include namespace TW::Tron { @@ -269,26 +269,11 @@ void setBlockReference(const Proto::Transaction& transaction, protocol::Transact internal.mutable_raw_data()->set_ref_block_bytes(heightData.data() + heightData.size() - 2, 2); } -Proto::SigningOutput signDirect(const Proto::SigningInput& input) { - const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - auto hash = parse_hex(input.txid()); - const auto signature = key.sign(hash, TWCurveSECP256k1); - auto output = Proto::SigningOutput(); - output.set_signature(signature.data(), signature.size()); - output.set_id(input.txid()); - output.set_id(hash.data(), hash.size()); - return output; -} - -Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - if (!input.txid().empty()) { - return signDirect(input); - } - auto internal = protocol::Transaction(); - auto output = Proto::SigningOutput(); +protocol::Transaction buildTransaction(const Proto::SigningInput& input) noexcept { + auto tx = protocol::Transaction(); if (input.transaction().has_transfer()) { - auto* contract = internal.mutable_raw_data()->add_contract(); + auto contract = tx.mutable_raw_data()->add_contract(); contract->set_type(protocol::Transaction_Contract_ContractType_TransferContract); auto transfer = to_internal(input.transaction().transfer()); @@ -296,7 +281,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { any.PackFrom(transfer); *contract->mutable_parameter() = any; } else if (input.transaction().has_transfer_asset()) { - auto* contract = internal.mutable_raw_data()->add_contract(); + auto contract = tx.mutable_raw_data()->add_contract(); contract->set_type(protocol::Transaction_Contract_ContractType_TransferAssetContract); auto transfer = to_internal(input.transaction().transfer_asset()); @@ -304,7 +289,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { any.PackFrom(transfer); *contract->mutable_parameter() = any; } else if (input.transaction().has_freeze_balance()) { - auto* contract = internal.mutable_raw_data()->add_contract(); + auto contract = tx.mutable_raw_data()->add_contract(); contract->set_type(protocol::Transaction_Contract_ContractType_FreezeBalanceContract); auto freeze_balance = to_internal(input.transaction().freeze_balance()); @@ -312,49 +297,49 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { any.PackFrom(freeze_balance); *contract->mutable_parameter() = any; } else if (input.transaction().has_freeze_balance_v2()) { - auto* contract = internal.mutable_raw_data()->add_contract(); + auto* contract = tx.mutable_raw_data()->add_contract(); contract->set_type(protocol::Transaction_Contract_ContractType_FreezeBalanceV2Contract); auto freeze_balance = to_internal(input.transaction().freeze_balance_v2()); google::protobuf::Any any; any.PackFrom(freeze_balance); *contract->mutable_parameter() = any; } else if (input.transaction().has_unfreeze_balance()) { - auto* contract = internal.mutable_raw_data()->add_contract(); + auto contract = tx.mutable_raw_data()->add_contract(); contract->set_type(protocol::Transaction_Contract_ContractType_UnfreezeBalanceContract); auto unfreeze_balance = to_internal(input.transaction().unfreeze_balance()); google::protobuf::Any any; any.PackFrom(unfreeze_balance); *contract->mutable_parameter() = any; } else if (input.transaction().has_unfreeze_balance_v2()) { - auto* contract = internal.mutable_raw_data()->add_contract(); + auto* contract = tx.mutable_raw_data()->add_contract(); contract->set_type(protocol::Transaction_Contract_ContractType_UnfreezeBalanceV2Contract); auto unfreeze_balance = to_internal(input.transaction().unfreeze_balance_v2()); google::protobuf::Any any; any.PackFrom(unfreeze_balance); *contract->mutable_parameter() = any; } else if (input.transaction().has_withdraw_expire_unfreeze()) { - auto* contract = internal.mutable_raw_data()->add_contract(); + auto* contract = tx.mutable_raw_data()->add_contract(); contract->set_type(protocol::Transaction_Contract_ContractType_WithdrawExpireUnfreezeContract); auto withdraw_expire_unfreeze = to_internal(input.transaction().withdraw_expire_unfreeze()); google::protobuf::Any any; any.PackFrom(withdraw_expire_unfreeze); *contract->mutable_parameter() = any; } else if (input.transaction().has_delegate_resource()) { - auto* contract = internal.mutable_raw_data()->add_contract(); + auto* contract = tx.mutable_raw_data()->add_contract(); contract->set_type(protocol::Transaction_Contract_ContractType_DelegateResourceContract); auto delegate_resource = to_internal(input.transaction().delegate_resource()); google::protobuf::Any any; any.PackFrom(delegate_resource); *contract->mutable_parameter() = any; } else if (input.transaction().has_undelegate_resource()) { - auto* contract = internal.mutable_raw_data()->add_contract(); + auto* contract = tx.mutable_raw_data()->add_contract(); contract->set_type(protocol::Transaction_Contract_ContractType_UnDelegateResourceContract); auto undelegate_resource = to_internal(input.transaction().undelegate_resource()); google::protobuf::Any any; any.PackFrom(undelegate_resource); *contract->mutable_parameter() = any; } else if (input.transaction().has_unfreeze_asset()) { - auto* contract = internal.mutable_raw_data()->add_contract(); + auto contract = tx.mutable_raw_data()->add_contract(); contract->set_type(protocol::Transaction_Contract_ContractType_UnfreezeAssetContract); auto unfreeze_asset = to_internal(input.transaction().unfreeze_asset()); @@ -362,7 +347,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { any.PackFrom(unfreeze_asset); *contract->mutable_parameter() = any; } else if (input.transaction().has_vote_asset()) { - auto* contract = internal.mutable_raw_data()->add_contract(); + auto contract = tx.mutable_raw_data()->add_contract(); contract->set_type(protocol::Transaction_Contract_ContractType_VoteAssetContract); auto vote_asset = to_internal(input.transaction().vote_asset()); @@ -370,7 +355,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { any.PackFrom(vote_asset); *contract->mutable_parameter() = any; } else if (input.transaction().has_vote_witness()) { - auto* contract = internal.mutable_raw_data()->add_contract(); + auto contract = tx.mutable_raw_data()->add_contract(); contract->set_type(protocol::Transaction_Contract_ContractType_VoteWitnessContract); auto vote_witness = to_internal(input.transaction().vote_witness()); @@ -378,7 +363,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { any.PackFrom(vote_witness); *contract->mutable_parameter() = any; } else if (input.transaction().has_withdraw_balance()) { - auto* contract = internal.mutable_raw_data()->add_contract(); + auto contract = tx.mutable_raw_data()->add_contract(); contract->set_type(protocol::Transaction_Contract_ContractType_WithdrawBalanceContract); auto withdraw = to_internal(input.transaction().withdraw_balance()); @@ -386,7 +371,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { any.PackFrom(withdraw); *contract->mutable_parameter() = any; } else if (input.transaction().has_trigger_smart_contract()) { - auto* contract = internal.mutable_raw_data()->add_contract(); + auto contract = tx.mutable_raw_data()->add_contract(); contract->set_type(protocol::Transaction_Contract_ContractType_TriggerSmartContract); auto trigger_smart_contract = to_internal(input.transaction().trigger_smart_contract()); @@ -394,7 +379,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { any.PackFrom(trigger_smart_contract); *contract->mutable_parameter() = any; } else if (input.transaction().has_transfer_trc20_contract()) { - auto* contract = internal.mutable_raw_data()->add_contract(); + auto contract = tx.mutable_raw_data()->add_contract(); contract->set_type(protocol::Transaction_Contract_ContractType_TriggerSmartContract); auto trigger_smart_contract = to_internal(input.transaction().transfer_trc20_contract()); @@ -403,6 +388,38 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { *contract->mutable_parameter() = any; } + tx.mutable_raw_data()->set_timestamp(input.transaction().timestamp()); + tx.mutable_raw_data()->set_expiration(input.transaction().expiration()); + tx.mutable_raw_data()->set_fee_limit(input.transaction().fee_limit()); + setBlockReference(input.transaction(), tx); + + return tx; +} + +Data serialize(const protocol::Transaction& tx) noexcept { + const auto serialized = tx.raw_data().SerializeAsString(); + return Data(serialized.begin(), serialized.end()); +} + +Proto::SigningOutput signDirect(const Proto::SigningInput& input) { + const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto hash = parse_hex(input.txid()); + const auto signature = key.sign(hash, TWCurveSECP256k1); + auto output = Proto::SigningOutput(); + output.set_signature(signature.data(), signature.size()); + output.set_id(input.txid()); + output.set_id(hash.data(), hash.size()); + return output; +} + +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { + if (!input.txid().empty()) { + return signDirect(input); + } + + auto output = Proto::SigningOutput(); + auto tx = buildTransaction(input); + // Get default timestamp and expiration const uint64_t now = duration_cast( std::chrono::system_clock::now().time_since_epoch()) @@ -414,21 +431,18 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { ? timestamp + 10 * 60 * 60 * 1000 // 10 hours : input.transaction().expiration(); - internal.mutable_raw_data()->set_timestamp(timestamp); - internal.mutable_raw_data()->set_expiration(expiration); - internal.mutable_raw_data()->set_fee_limit(input.transaction().fee_limit()); - setBlockReference(input.transaction(), internal); + tx.mutable_raw_data()->set_timestamp(timestamp); + tx.mutable_raw_data()->set_expiration(expiration); - output.set_ref_block_bytes(internal.raw_data().ref_block_bytes()); - output.set_ref_block_hash(internal.raw_data().ref_block_hash()); + output.set_ref_block_bytes(tx.raw_data().ref_block_bytes()); + output.set_ref_block_hash(tx.raw_data().ref_block_hash()); - const auto serialized = internal.raw_data().SerializeAsString(); - const auto hash = Hash::sha256(Data(serialized.begin(), serialized.end())); + const auto hash = Hash::sha256(serialize(tx)); const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); const auto signature = key.sign(hash, TWCurveSECP256k1); - const auto json = transactionJSON(internal, hash, signature).dump(); + const auto json = transactionJSON(tx, hash, signature).dump(); output.set_id(hash.data(), hash.size()); output.set_signature(signature.data(), signature.size()); @@ -437,4 +451,22 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { return output; } +Proto::SigningOutput Signer::compile(const Data& signature) const { + Proto::SigningOutput output; + auto preImage = signaturePreimage(); + auto hash = Hash::sha256(preImage); + auto transaction = buildTransaction(input); + const auto json = transactionJSON(transaction, hash, signature).dump(); + output.set_json(json.data(), json.size()); + output.set_ref_block_bytes(transaction.raw_data().ref_block_bytes()); + output.set_ref_block_hash(transaction.raw_data().ref_block_hash()); + output.set_id(hash.data(), hash.size()); + output.set_signature(signature.data(), signature.size()); + return output; +} + +Data Signer::signaturePreimage() const { + return serialize(buildTransaction(input)); +} + } // namespace TW::Tron diff --git a/src/Tron/Signer.h b/src/Tron/Signer.h index fb9ed978053..3f2fa7f1c65 100644 --- a/src/Tron/Signer.h +++ b/src/Tron/Signer.h @@ -15,10 +15,14 @@ namespace TW::Tron { /// Helper class that performs Tron transaction signing. class Signer { public: + Proto::SigningInput input; Signer() = delete; - + /// Initializes a transaction signer. + explicit Signer(const Proto::SigningInput& input) : input(input) {} /// Signs the given transaction. static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; + Proto::SigningOutput compile(const Data& signature) const; + Data signaturePreimage() const; }; } // namespace TW::Tron diff --git a/src/VeChain/Entry.cpp b/src/VeChain/Entry.cpp index 28574d44a17..1d15dcfa22b 100644 --- a/src/VeChain/Entry.cpp +++ b/src/VeChain/Entry.cpp @@ -5,6 +5,9 @@ // file LICENSE at the root of the source code distribution tree. #include "Entry.h" +#include +#include "../proto/Common.pb.h" +#include "../Hash.h" #include "Ethereum/Address.h" #include "Signer.h" @@ -15,4 +18,29 @@ void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::D signTemplate(dataIn, dataOut); } +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + auto unsignedTxBytes = Signer::buildUnsignedTx(input); + auto imageHash = Hash::blake2b(unsignedTxBytes, 32); + output.set_data(unsignedTxBytes.data(), unsignedTxBytes.size()); + output.set_data_hash(imageHash.data(), imageHash.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, [[maybe_unused]] const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() != 1) { + output.set_error(Common::Proto::Error_no_support_n2n); + output.set_error_message(Common::Proto::SigningError_Name(Common::Proto::Error_no_support_n2n)); + return; + } + + Data signedTx = Signer::buildSignedTx(input, signatures[0]); + output.set_encoded(signedTx.data(), signedTx.size()); + output.set_signature(signatures[0].data(), signatures[0].size()); + }); +} + } // namespace TW::VeChain diff --git a/src/VeChain/Entry.h b/src/VeChain/Entry.h index 8ba48845d06..f1977330557 100644 --- a/src/VeChain/Entry.h +++ b/src/VeChain/Entry.h @@ -6,7 +6,6 @@ #pragma once -#include "../CoinEntry.h" #include "Ethereum/Entry.h" namespace TW::VeChain { @@ -15,7 +14,10 @@ namespace TW::VeChain { /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file class Entry final : public Ethereum::Entry { public: - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::VeChain diff --git a/src/VeChain/Signer.cpp b/src/VeChain/Signer.cpp index b0007cf6104..23c4f4b3878 100644 --- a/src/VeChain/Signer.cpp +++ b/src/VeChain/Signer.cpp @@ -43,4 +43,37 @@ Data Signer::sign(const PrivateKey& privateKey, Transaction& transaction) noexce return Data(signature.begin(), signature.end()); } +Data Signer::buildUnsignedTx(const Proto::SigningInput& input) noexcept { + auto transaction = Transaction(); + transaction.chainTag = static_cast(input.chain_tag()); + transaction.blockRef = input.block_ref(); + transaction.expiration = input.expiration(); + for (auto& clause : input.clauses()) { + transaction.clauses.emplace_back(clause); + } + transaction.gasPriceCoef = static_cast(input.gas_price_coef()); + transaction.gas = input.gas(); + transaction.dependsOn = Data(input.depends_on().begin(), input.depends_on().end()); + transaction.nonce = input.nonce(); + + return transaction.encode(); +} + +Data Signer::buildSignedTx(const Proto::SigningInput& input, const Data& signature) noexcept { + auto transaction = Transaction(); + transaction.chainTag = static_cast(input.chain_tag()); + transaction.blockRef = input.block_ref(); + transaction.expiration = input.expiration(); + for (auto& clause : input.clauses()) { + transaction.clauses.emplace_back(clause); + } + transaction.gasPriceCoef = static_cast(input.gas_price_coef()); + transaction.gas = input.gas(); + transaction.dependsOn = Data(input.depends_on().begin(), input.depends_on().end()); + transaction.nonce = input.nonce(); + transaction.signature = signature; + + return transaction.encode(); +} + } // namespace TW::VeChain diff --git a/src/VeChain/Signer.h b/src/VeChain/Signer.h index d434bd4d1d8..550810961e0 100644 --- a/src/VeChain/Signer.h +++ b/src/VeChain/Signer.h @@ -27,6 +27,10 @@ class Signer { /// Signs a Proto::SigningInput transaction static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; + static Data buildUnsignedTx(const Proto::SigningInput& input) noexcept; + + static Data buildSignedTx(const Proto::SigningInput& input, const Data& signature) noexcept; + /// Signs the given transaction. static Data sign(const PrivateKey& privateKey, Transaction& transaction) noexcept; }; diff --git a/src/Verge/Entry.cpp b/src/Verge/Entry.cpp new file mode 100644 index 00000000000..d0a13896040 --- /dev/null +++ b/src/Verge/Entry.cpp @@ -0,0 +1,93 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Entry.h" + +#include "Bitcoin/Address.h" +#include "Bitcoin/SegwitAddress.h" +#include "Signer.h" + +using namespace std; + +namespace TW::Verge { + +// Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. + +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const string& address, const PrefixVariant& addressPrefix) const { + auto* base58Prefix = std::get_if(&addressPrefix); + auto* hrp = std::get_if(&addressPrefix); + bool isValidBase58 = base58Prefix ? Bitcoin::Address::isValid(address) : false; + bool isValidHrp = hrp ? Bitcoin::SegwitAddress::isValid(address, *hrp) : false; + return isValidBase58 || isValidHrp; +} + +string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const { + byte p2pkh = getFromPrefixPkhOrDefault(addressPrefix, coin); + const char* hrp = getFromPrefixHrpOrDefault(addressPrefix, coin); + + switch (derivation) { + case TWDerivationBitcoinLegacy: + case TWDerivationDefault: + return Bitcoin::Address(publicKey, p2pkh).string(); + default: + return TW::Bitcoin::SegwitAddress(publicKey, hrp).string(); + } +} + +TW::Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + const auto decoded = Bitcoin::SegwitAddress::decode(address); + if (!std::get<2>(decoded)) { + // check if it is a legacy address + if (Bitcoin::Address::isValid(address)) { + const auto addr = Bitcoin::Address(address); + return {addr.bytes.begin() + 1, addr.bytes.end()}; + } + return {Data()}; + } + return std::get<0>(decoded).witnessProgram; +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + signTemplate(dataIn, dataOut); +} + +void Entry::plan([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + planTemplate(dataIn, dataOut); +} + +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + output = Signer::preImageHashes(input); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() == 0 || publicKeys.size() == 0) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return; + } + + if (signatures.size() != publicKeys.size()) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("signatures size and publickeys size not equal"); + return; + } + + HashPubkeyList externalSignatures; + auto n = signatures.size(); + for (auto i = 0ul; i < n; ++i) { + externalSignatures.push_back(std::make_pair(signatures[i], publicKeys[i].bytes)); + } + + output = Signer::sign(input, externalSignatures); + }); +} + +} // namespace TW::Verge diff --git a/src/Verge/Entry.h b/src/Verge/Entry.h new file mode 100644 index 00000000000..483e8e51448 --- /dev/null +++ b/src/Verge/Entry.h @@ -0,0 +1,27 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "../CoinEntry.h" + +namespace TW::Verge { + +/// Entry point for implementation of Verge coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file +class Entry final: public CoinEntry { +public: + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const override; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override; + Data addressToData(TWCoinType coin, const std::string& address) const override; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; +}; + +} // namespace TW::Verge diff --git a/src/Verge/Signer.cpp b/src/Verge/Signer.cpp new file mode 100644 index 00000000000..6110e68bcfc --- /dev/null +++ b/src/Verge/Signer.cpp @@ -0,0 +1,62 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Signer.h" +#include "Bitcoin/TransactionSigner.h" +#include "Hash.h" +#include "HexCoding.h" +#include "Transaction.h" +#include "TransactionBuilder.h" + +using namespace TW; +namespace TW::Verge { + +TransactionPlan Signer::plan(const SigningInput& input) noexcept { + auto plan = Bitcoin::TransactionSigner::plan(input); + return plan.proto(); +} + +SigningOutput Signer::sign(const SigningInput& input, std::optional optionalExternalSigs) noexcept { + SigningOutput output; + auto result = Bitcoin::TransactionSigner::sign(input, false, optionalExternalSigs); + if (!result) { + output.set_error(result.error()); + output.set_error_message(Common::Proto::SigningError_Name(result.error())); + } else { + const auto& tx = result.payload(); + *output.mutable_transaction() = tx.proto(); + + Data encoded; + tx.encode(encoded); + output.set_encoded(encoded.data(), encoded.size()); + + auto txHash = Hash::sha256d(encoded.data(), encoded.size()); + std::reverse(txHash.begin(), txHash.end()); + output.set_transaction_id(hex(txHash)); + } + return output; +} + +PreSigningOutput Signer::preImageHashes(const SigningInput& input) noexcept { + PreSigningOutput output; + auto result = Bitcoin::TransactionSigner::preImageHashes(input); + if (!result) { + output.set_error(result.error()); + output.set_error_message(Common::Proto::SigningError_Name(result.error())); + return output; + } + + auto hashList = result.payload(); + auto* hashPubKeys = output.mutable_hash_public_keys(); + for (auto& h : hashList) { + auto* hpk = hashPubKeys->Add(); + hpk->set_data_hash(h.first.data(), h.first.size()); + hpk->set_public_key_hash(h.second.data(), h.second.size()); + } + return output; +} + +} // namespace TW::Verge diff --git a/src/Verge/Signer.h b/src/Verge/Signer.h new file mode 100644 index 00000000000..c2912972b58 --- /dev/null +++ b/src/Verge/Signer.h @@ -0,0 +1,39 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "../Data.h" +#include "../proto/Bitcoin.pb.h" + +#include +#include +#include + +namespace TW::Verge { + +using SigningInput = Bitcoin::Proto::SigningInput; +using SigningOutput = Bitcoin::Proto::SigningOutput; +using TransactionPlan = Bitcoin::Proto::TransactionPlan; +using PreSigningOutput = Bitcoin::Proto::PreSigningOutput; + +/// Helper class that performs Verge transaction signing. +class Signer { +public: + /// Hide default constructor + Signer() = delete; + + /// Returns a transaction plan (utxo selection, fee estimation) + static TransactionPlan plan(const SigningInput& input) noexcept; + + /// Signs a SigningInput transaction + static SigningOutput sign(const SigningInput& input, std::optional optionalExternalSigs = {}) noexcept; + + /// Collect pre-image hashes to be signed + static PreSigningOutput preImageHashes(const SigningInput& input) noexcept; +}; + +} // namespace TW::Verge diff --git a/src/Verge/Transaction.cpp b/src/Verge/Transaction.cpp new file mode 100644 index 00000000000..db3461e012f --- /dev/null +++ b/src/Verge/Transaction.cpp @@ -0,0 +1,180 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Transaction.h" +#include "../BinaryCoding.h" +#include "../Hash.h" +#include "../Data.h" +#include "../HexCoding.h" + +#include "../Bitcoin/SegwitAddress.h" +#include "../Bitcoin/SigHashType.h" +#include "../Bitcoin/SignatureVersion.h" + +#include + +using namespace TW; +namespace TW::Verge { + +Data Transaction::getPreImage(const Bitcoin::Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType, uint64_t amount) const { + assert(index < inputs.size()); + + Data data; + + // Version + encode32LE(_version, data); + + // Time + encode32LE(time, data); + + // Input prevouts (none/all, depending on flags) + if ((hashType & TWBitcoinSigHashTypeAnyoneCanPay) == 0) { + auto hashPrevouts = getPrevoutHash(); + std::copy(std::begin(hashPrevouts), std::end(hashPrevouts), std::back_inserter(data)); + } else { + std::fill_n(back_inserter(data), 32, 0); + } + + // Input nSequence (none/all, depending on flags) + if ((hashType & TWBitcoinSigHashTypeAnyoneCanPay) == 0 && + !Bitcoin::hashTypeIsSingle(hashType) && !Bitcoin::hashTypeIsNone(hashType)) { + auto hashSequence = getSequenceHash(); + std::copy(std::begin(hashSequence), std::end(hashSequence), std::back_inserter(data)); + } else { + std::fill_n(back_inserter(data), 32, 0); + } + + // The input being signed (replacing the scriptSig with scriptCode + amount) + // The prevout may already be contained in hashPrevout, and the nSequence + // may already be contain in hashSequence. + inputs[index].previousOutput.encode(data); + scriptCode.encode(data); + + encode64LE(amount, data); + encode32LE(inputs[index].sequence, data); + + // Outputs (none/one/all, depending on flags) + if (!Bitcoin::hashTypeIsSingle(hashType) && !Bitcoin::hashTypeIsNone(hashType)) { + auto hashOutputs = getOutputsHash(); + copy(begin(hashOutputs), end(hashOutputs), back_inserter(data)); + } else if (Bitcoin::hashTypeIsSingle(hashType) && index < outputs.size()) { + Data outputData; + outputs[index].encode(outputData); + auto hashOutputs = Hash::hash(hasher, outputData); + copy(begin(hashOutputs), end(hashOutputs), back_inserter(data)); + } else { + fill_n(back_inserter(data), 32, 0); + } + + // Locktime + encode32LE(lockTime, data); + + // Sighash type + encode32LE(hashType, data); + + return data; +} + +void Transaction::encode(Data& data, enum SegwitFormatMode segwitFormat) const { + bool useWitnessFormat = true; + switch (segwitFormat) { + case NonSegwit: useWitnessFormat = false; break; + case IfHasWitness: useWitnessFormat = hasWitness(); break; + case Segwit: useWitnessFormat = true; break; + } + + encode32LE(_version, data); + + encode32LE(time, data); + + if (useWitnessFormat) { + // Use extended format in case witnesses are to be serialized. + data.push_back(0); // marker + data.push_back(1); // flag + } + + // txins + encodeVarInt(inputs.size(), data); + for (auto& input : inputs) { + input.encode(data); + } + + // txouts + encodeVarInt(outputs.size(), data); + for (auto& output : outputs) { + output.encode(data); + } + + if (useWitnessFormat) { + encodeWitness(data); + } + + encode32LE(lockTime, data); // nLockTime +} + +Data Transaction::getSignatureHash(const Bitcoin::Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType, uint64_t amount, + enum Bitcoin::SignatureVersion version) const { + switch (version) { + case Bitcoin::BASE: + return getSignatureHashBase(scriptCode, index, hashType); + case Bitcoin::WITNESS_V0: + return getSignatureHashWitnessV0(scriptCode, index, hashType, amount); + } +} + +/// Generates the signature hash for Witness version 0 scripts. +Data Transaction::getSignatureHashWitnessV0(const Bitcoin::Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType, + uint64_t amount) const { + auto preimage = getPreImage(scriptCode, index, hashType, amount); + auto hash = Hash::hash(hasher, preimage); + return hash; +} + +/// Generates the signature hash for for scripts other than witness scripts. +Data Transaction::getSignatureHashBase(const Bitcoin::Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType) const { + assert(index < inputs.size()); + + Data data; + + encode32LE(_version, data); + + encode32LE(time, data); + + auto serializedInputCount = + (hashType & TWBitcoinSigHashTypeAnyoneCanPay) != 0 ? 1 : inputs.size(); + encodeVarInt(serializedInputCount, data); + for (auto subindex = 0ul; subindex < serializedInputCount; subindex += 1) { + serializeInput(subindex, scriptCode, index, hashType, data); + } + + auto hashNone = Bitcoin::hashTypeIsNone(hashType); + auto hashSingle = Bitcoin::hashTypeIsSingle(hashType); + auto serializedOutputCount = hashNone ? 0 : (hashSingle ? index + 1 : outputs.size()); + encodeVarInt(serializedOutputCount, data); + for (auto subindex = 0ul; subindex < serializedOutputCount; subindex += 1) { + if (hashSingle && subindex != index) { + auto output = Bitcoin::TransactionOutput(-1, {}); + output.encode(data); + } else { + outputs[subindex].encode(data); + } + } + + // Locktime + encode32LE(lockTime, data); + + // Sighash type + encode32LE(hashType, data); + + auto hash = Hash::hash(hasher, data); + return hash; +} + +} // namespace TW::Verge \ No newline at end of file diff --git a/src/Verge/Transaction.h b/src/Verge/Transaction.h new file mode 100644 index 00000000000..022efdfd0cc --- /dev/null +++ b/src/Verge/Transaction.h @@ -0,0 +1,58 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include +#include "../Bitcoin/Transaction.h" +#include "../PrivateKey.h" +#include "../Hash.h" +#include "../Data.h" +#include "../proto/Bitcoin.pb.h" + +#include "../Bitcoin/Script.h" +#include "../Bitcoin/SignatureVersion.h" + +#include + +namespace TW::Verge { + +struct Transaction : public Bitcoin::Transaction { +public: + /// Transaction time + uint32_t time = 0; + +public: + Transaction() = default; + + Transaction(int32_t version, uint32_t time = 0, uint32_t lockTime = 0, TW::Hash::Hasher hasher = TW::Hash::HasherSha256d) + : Bitcoin::Transaction(version, lockTime, hasher) + , time(time) {} + + /// Generates the signature pre-image. + Data getPreImage(const Bitcoin::Script& scriptCode, size_t index, enum TWBitcoinSigHashType hashType, uint64_t amount) const; + + /// Encodes the transaction into the provided buffer. + void encode(Data& data, enum SegwitFormatMode segwitFormat) const; + + /// Default one-parameter version, needed for templated usage. + void encode(Data& data) const { encode(data, SegwitFormatMode::IfHasWitness); } + + /// Generates the signature hash for this transaction. + Data getSignatureHash(const Bitcoin::Script& scriptCode, size_t index, enum TWBitcoinSigHashType hashType, + uint64_t amount, enum Bitcoin::SignatureVersion version) const; + +private: + /// Generates the signature hash for Witness version 0 scripts. + Data getSignatureHashWitnessV0(const Bitcoin::Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType, uint64_t amount) const; + + /// Generates the signature hash for for scripts other than witness scripts. + Data getSignatureHashBase(const Bitcoin::Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType) const; +}; + +} // namespace TW::Verge diff --git a/src/Verge/TransactionBuilder.h b/src/Verge/TransactionBuilder.h new file mode 100644 index 00000000000..51f85b0581b --- /dev/null +++ b/src/Verge/TransactionBuilder.h @@ -0,0 +1,43 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Transaction.h" +#include "../Bitcoin/TransactionBuilder.h" +#include "../Bitcoin/TransactionPlan.h" +#include "../proto/Bitcoin.pb.h" +#include "../HexCoding.h" +#include + +#include + +namespace TW::Verge { + +struct TransactionBuilder { + /// Plans a transaction by selecting UTXOs and calculating fees. + static Bitcoin::TransactionPlan plan(const Bitcoin::SigningInput& input) { + return Bitcoin::TransactionBuilder::plan(input); + } + + /// Builds a transaction by selecting UTXOs and calculating fees. + template + static Result build(const Bitcoin::TransactionPlan& plan, + const Bitcoin::SigningInput& input) { + auto tx_result = Bitcoin::TransactionBuilder::build(plan, input); + if (!tx_result) { return Result::failure(tx_result.error()); } + Transaction tx = tx_result.payload(); + + tx.time = input.time; + // if not set, always use latest time + if (tx.time == 0) { + tx.time = (uint32_t)std::time(nullptr); + } + return Result(tx); + } +}; + +} // namespace TW::Verge diff --git a/src/XRP/Entry.cpp b/src/XRP/Entry.cpp index bcb490a9383..8c7d27314f2 100644 --- a/src/XRP/Entry.cpp +++ b/src/XRP/Entry.cpp @@ -9,6 +9,7 @@ #include "Address.h" #include "XAddress.h" #include "Signer.h" +#include "../proto/TransactionCompiler.pb.h" namespace TW::Ripple { @@ -26,4 +27,25 @@ void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::D signTemplate(dataIn, dataOut); } +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + auto signer = Signer(input); + auto preimage = signer.preImage(); + output.set_data(preimage.data(), preimage.size()); + auto hash = Hash::sha512(preimage); + auto preimageHash = Data(hash.begin(), hash.begin() + 32); + output.set_data_hash(preimageHash.data(), preimageHash.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [](const auto& input, auto& output, const auto& signature, const auto& publicKey) { + auto signer = Signer(input); + output = signer.compile(signature, publicKey); + }); +} } // namespace TW::Ripple diff --git a/src/XRP/Entry.h b/src/XRP/Entry.h index 60e24ca5f4c..27a74ec33ea 100644 --- a/src/XRP/Entry.h +++ b/src/XRP/Entry.h @@ -15,8 +15,10 @@ namespace TW::Ripple { class Entry final : public CoinEntry { public: bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Ripple diff --git a/src/XRP/Signer.cpp b/src/XRP/Signer.cpp index 352311d34e0..c8b3e018585 100644 --- a/src/XRP/Signer.cpp +++ b/src/XRP/Signer.cpp @@ -65,7 +65,6 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto encoded = transaction.serialize(); output.set_encoded(encoded.data(), encoded.size()); - return output; } @@ -80,9 +79,131 @@ void Signer::sign(const PrivateKey& privateKey, Transaction& transaction) const transaction.signature = privateKey.signAsDER(half); } +TW::Data Signer::preImage() const { + auto output = Proto::SigningOutput(); + + auto transaction = + Transaction( + input.fee(), + input.flags(), + input.sequence(), + input.last_ledger_sequence(), + Address(input.account())); + switch (input.operation_oneof_case()) { + case Proto::SigningInput::kOpPayment: + signPayment(input, output, transaction); + break; + + case Proto::SigningInput::kOpNftokenBurn: + transaction.createNFTokenBurn(input.op_nftoken_burn().nftoken_id()); + break; + + case Proto::SigningInput::kOpNftokenCreateOffer: + transaction.createNFTokenCreateOffer( + input.op_nftoken_create_offer().nftoken_id(), + input.op_nftoken_create_offer().destination()); + break; + + case Proto::SigningInput::kOpNftokenAcceptOffer: + transaction.createNFTokenAcceptOffer(input.op_nftoken_accept_offer().sell_offer()); + break; + + case Proto::SigningInput::kOpNftokenCancelOffer: + signNfTokenCancelOffer(input, transaction); + break; + + case Proto::SigningInput::kOpTrustSet: + transaction.createTrustSet( + input.op_trust_set().limit_amount().currency(), + input.op_trust_set().limit_amount().value(), + input.op_trust_set().limit_amount().issuer()); + break; + + default: + break; + } + + if (output.error()) { + return {}; + } + + auto publicKey = Data(input.public_key().begin(), input.public_key().end()); + transaction.pub_key = publicKey; + + auto unsignedTx = transaction.getPreImage(); + return unsignedTx; +} + +Proto::SigningOutput Signer::compile(const Data& signature, const PublicKey& publicKey) const { + auto output = Proto::SigningOutput(); + + auto transaction = + Transaction( + input.fee(), + input.flags(), + input.sequence(), + input.last_ledger_sequence(), + Address(input.account())); + switch (input.operation_oneof_case()) { + case Proto::SigningInput::kOpPayment: + signPayment(input, output, transaction); + break; + + case Proto::SigningInput::kOpNftokenBurn: + transaction.createNFTokenBurn(input.op_nftoken_burn().nftoken_id()); + break; + + case Proto::SigningInput::kOpNftokenCreateOffer: + transaction.createNFTokenCreateOffer( + input.op_nftoken_create_offer().nftoken_id(), + input.op_nftoken_create_offer().destination()); + break; + + case Proto::SigningInput::kOpNftokenAcceptOffer: + transaction.createNFTokenAcceptOffer(input.op_nftoken_accept_offer().sell_offer()); + break; + + case Proto::SigningInput::kOpNftokenCancelOffer: + signNfTokenCancelOffer(input, transaction); + break; + + case Proto::SigningInput::kOpTrustSet: + transaction.createTrustSet( + input.op_trust_set().limit_amount().currency(), + input.op_trust_set().limit_amount().value(), + input.op_trust_set().limit_amount().issuer()); + break; + + default: + break; + } + + if (output.error()) { + return output; + } + + auto pubKey = Data(input.public_key().begin(), input.public_key().end()); + transaction.pub_key = pubKey; + + auto unsignedTx = transaction.getPreImage(); + auto hash = Hash::sha512(unsignedTx); + auto half = Data(hash.begin(), hash.begin() + 32); + if (!publicKey.verifyAsDER(signature, half)) { + output.set_error(Common::Proto::SigningError::Error_signing); + output.set_error_message("Signature verification failed"); + return output; + } + + transaction.signature = signature; + + auto encoded = transaction.serialize(); + output.set_encoded(encoded.data(), encoded.size()); + return output; +} + void Signer::signPayment(const Proto::SigningInput& input, Proto::SigningOutput& output, - Transaction& transaction) noexcept { + Transaction& transaction) { const int64_t tag = input.op_payment().destination_tag(); if (tag > std::numeric_limits::max() || tag < 0) { output.set_error(Common::Proto::SigningError::Error_invalid_memo); diff --git a/src/XRP/Signer.h b/src/XRP/Signer.h index 6e0c7aad889..e8953133cb8 100644 --- a/src/XRP/Signer.h +++ b/src/XRP/Signer.h @@ -16,16 +16,29 @@ namespace TW::Ripple { /// Helper class that performs Ripple transaction signing. class Signer { public: + Proto::SigningInput input; + + Signer() = default; + + /// Initializes a transaction signer. + explicit Signer(const Proto::SigningInput& input) : input(input) {} + /// Signs a Proto::SigningInput transaction static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; /// Signs the given transaction. void sign(const PrivateKey& privateKey, Transaction& transaction) const noexcept; + /// preImage returns the transaction pre-image without hashing. + TW::Data preImage() const; + + /// compile returns the final serialized signed transaction. + Proto::SigningOutput compile(const Data& signatures, const PublicKey& publicKeys) const; + private: static void signPayment(const Proto::SigningInput& input, Proto::SigningOutput& output, - Transaction& transaction) noexcept; + Transaction& transaction); static void signNfTokenCancelOffer(const Proto::SigningInput& input, Transaction& transaction) noexcept; }; diff --git a/src/XRP/Transaction.cpp b/src/XRP/Transaction.cpp index 9acc738f9cc..f608fa962e0 100644 --- a/src/XRP/Transaction.cpp +++ b/src/XRP/Transaction.cpp @@ -82,7 +82,6 @@ Data Transaction::serialize() const { encodeType(FieldType::vl, 3, data); encodeBytes(pub_key, data); } - /// "txnSignature" if (!signature.empty()) { encodeType(FieldType::vl, 4, data); diff --git a/src/Zcash/Entry.cpp b/src/Zcash/Entry.cpp index 6b908486f7a..143bb432756 100644 --- a/src/Zcash/Entry.cpp +++ b/src/Zcash/Entry.cpp @@ -6,21 +6,34 @@ #include "Entry.h" +#include "Bitcoin/Address.h" #include "Signer.h" #include "TAddress.h" namespace TW::Zcash { -bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { +bool Entry::validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const { + if (coin == TWCoinTypeKomodo) { + auto* base58Prefix = std::get_if(&addressPrefix); + return base58Prefix ? Bitcoin::Address::isValid(address, {{base58Prefix->p2pkh}, {base58Prefix->p2sh}}) : false; + } return TAddress::isValid(address); } -std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, const PrefixVariant& addressPrefix) const { +std::string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, const PrefixVariant& addressPrefix) const { byte p2pkh = getFromPrefixPkhOrDefault(addressPrefix, coin); + if (coin == TWCoinTypeKomodo) { + return Bitcoin::Address(publicKey, p2pkh).string(); + } return TAddress(publicKey, p2pkh).string(); } -Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { +Data Entry::addressToData(TWCoinType coin, const std::string& address) const { + if (coin == TWCoinTypeKomodo) { + const auto addr = Bitcoin::Address(address); + return {addr.bytes.begin() + 1, addr.bytes.end()}; + } + const auto addr = TAddress(address); return {addr.bytes.begin() + 2, addr.bytes.end()}; } @@ -33,4 +46,36 @@ void Entry::plan([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::D planTemplate(dataIn, dataOut); } +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + output = Signer::preImageHashes(input); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() == 0 || publicKeys.size() == 0) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return; + } + + if (signatures.size() != publicKeys.size()) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("signatures size and publickeys size not equal"); + return; + } + + HashPubkeyList externalSignatures; + auto n = signatures.size(); + for (auto i = 0ul; i < n; ++i) { + externalSignatures.push_back(std::make_pair(signatures[i], publicKeys[i].bytes)); + } + + output = Signer::sign(input, externalSignatures); + }); +} + } // namespace TW::Zcash diff --git a/src/Zcash/Entry.h b/src/Zcash/Entry.h index ea8ac5092fc..78c2f587970 100644 --- a/src/Zcash/Entry.h +++ b/src/Zcash/Entry.h @@ -15,10 +15,13 @@ namespace TW::Zcash { class Entry final : public CoinEntry { public: bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; - Data addressToData(TWCoinType coin, const std::string& address) const; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Zcash diff --git a/src/Zcash/Signer.cpp b/src/Zcash/Signer.cpp index 341d1df51c5..05c09687a46 100644 --- a/src/Zcash/Signer.cpp +++ b/src/Zcash/Signer.cpp @@ -18,11 +18,12 @@ TransactionPlan Signer::plan(const SigningInput& input) noexcept { return plan.proto(); } -SigningOutput Signer::sign(const SigningInput& input) noexcept { +SigningOutput Signer::sign(const SigningInput& input, std::optional optionalExternalSigs) noexcept { SigningOutput output; - auto result = Bitcoin::TransactionSigner::sign(input); + auto result = Bitcoin::TransactionSigner::sign(input, false, optionalExternalSigs); if (!result) { output.set_error(result.error()); + output.set_error_message(Common::Proto::SigningError_Name(result.error())); } else { const auto& tx = result.payload(); *output.mutable_transaction() = tx.proto(); @@ -38,4 +39,23 @@ SigningOutput Signer::sign(const SigningInput& input) noexcept { return output; } +PreSigningOutput Signer::preImageHashes(const SigningInput& input) noexcept { + PreSigningOutput output; + auto result = Bitcoin::TransactionSigner::preImageHashes(input); + if (!result) { + output.set_error(result.error()); + output.set_error_message(Common::Proto::SigningError_Name(result.error())); + return output; + } + + auto hashList = result.payload(); + auto* hashPubKeys = output.mutable_hash_public_keys(); + for (auto& h : hashList) { + auto* hpk = hashPubKeys->Add(); + hpk->set_data_hash(h.first.data(), h.first.size()); + hpk->set_public_key_hash(h.second.data(), h.second.size()); + } + return output; +} + } // namespace TW::Zcash diff --git a/src/Zcash/Signer.h b/src/Zcash/Signer.h index af46ab6e259..77cdbec2739 100644 --- a/src/Zcash/Signer.h +++ b/src/Zcash/Signer.h @@ -5,13 +5,18 @@ // file LICENSE at the root of the source code distribution tree. #pragma once + #include "../proto/Bitcoin.pb.h" +#include "Data.h" + +#include namespace TW::Zcash { using SigningInput = Bitcoin::Proto::SigningInput; using SigningOutput = Bitcoin::Proto::SigningOutput; using TransactionPlan = Bitcoin::Proto::TransactionPlan; +using PreSigningOutput = Bitcoin::Proto::PreSigningOutput; class Signer { public: @@ -20,8 +25,11 @@ class Signer { /// Returns a transaction plan (utxo selection, fee estimation) static TransactionPlan plan(const SigningInput& input) noexcept; - /// Signs a Proto::SigningInput transaction - static SigningOutput sign(const SigningInput& input) noexcept; + /// Signs a SigningInput transaction + static SigningOutput sign(const SigningInput& input, std::optional optionalExternalSigs = {}) noexcept; + + /// Collect pre-image hashes to be signed + static PreSigningOutput preImageHashes(const SigningInput& input) noexcept; }; } // namespace TW::Zcash diff --git a/src/Zcash/Transaction.cpp b/src/Zcash/Transaction.cpp index e4d72009e93..d7caf474138 100644 --- a/src/Zcash/Transaction.cpp +++ b/src/Zcash/Transaction.cpp @@ -96,7 +96,7 @@ Data Transaction::getPreImage(const Bitcoin::Script& scriptCode, size_t index, e // The input being signed (replacing the scriptSig with scriptCode + amount) // The prevout may already be contained in hashPrevout, and the nSequence // may already be contain in hashSequence. - reinterpret_cast(inputs[index].previousOutput).encode(data); + inputs[index].previousOutput.encode(data); scriptCode.encode(data); encode64LE(amount, data); diff --git a/src/Zcash/TransactionBuilder.h b/src/Zcash/TransactionBuilder.h index 9726378dcb5..62c9e32081d 100644 --- a/src/Zcash/TransactionBuilder.h +++ b/src/Zcash/TransactionBuilder.h @@ -26,11 +26,9 @@ struct TransactionBuilder { /// Builds a transaction by selecting UTXOs and calculating fees. template - static Result build(const Bitcoin::TransactionPlan& plan, const std::string& toAddress, - const std::string& changeAddress, enum TWCoinType coin, uint32_t lockTime) { - coin = TWCoinTypeZcash; + static Result build(const Bitcoin::TransactionPlan& plan, const Bitcoin::SigningInput& input) { auto tx_result = - Bitcoin::TransactionBuilder::build(plan, toAddress, changeAddress, coin, lockTime); + Bitcoin::TransactionBuilder::build(plan, input); if (!tx_result) { return Result::failure(tx_result.error()); } Transaction tx = tx_result.payload(); // if not set, always use latest consensus branch id diff --git a/src/Zen/Address.h b/src/Zen/Address.h new file mode 100644 index 00000000000..617872a2401 --- /dev/null +++ b/src/Zen/Address.h @@ -0,0 +1,49 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "../Base58Address.h" +#include "../PublicKey.h" + +#include +#include +#include + +namespace TW::Zen { + +class Address : public TW::Base58Address<22> { +public: + static const TW::byte staticPrefix = 0x20; + static const TW::byte p2pkh = 0x89; // p2pkhPrefix(TWCoinType::TWCoinTypeZcash); + static const TW::byte p2sh = 0x96; // p2shPrefix(TWCoinType::TWCoinTypeZcash); + + /// Determines whether a string makes a valid ZCash address. + static bool isValid(const std::string& string) { + return TW::Base58Address::isValid(string, + {{staticPrefix, p2pkh}, {staticPrefix, p2sh}}); + } + + /// Determines whether a string makes a valid ZCash address, with possible prefixes. + static bool isValid(const std::string& string, const std::vector& validPrefixes) { + return TW::Base58Address::isValid(string, validPrefixes); + } + + /// Initializes an address with a string representation. + explicit Address(const std::string& string) : TW::Base58Address(string) {} + + /// Initializes an address with a collection of bytes. + explicit Address(const Data& data) : TW::Base58Address(data) {} + + /// Initializes a address with a public key and a prefix (2nd byte). + Address(const PublicKey& publicKey, uint8_t prefix = p2pkh) + : TW::Base58Address(publicKey, {staticPrefix, prefix}) {} + +private: + Address() = default; +}; + +} // namespace TW::Zen diff --git a/src/Zen/Entry.cpp b/src/Zen/Entry.cpp new file mode 100644 index 00000000000..19ea562e2ba --- /dev/null +++ b/src/Zen/Entry.cpp @@ -0,0 +1,70 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Entry.h" + +#include "Address.h" +#include "Signer.h" + +using namespace std; + +namespace TW::Zen { + +// Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const string& address, [[maybe_unused]] const PrefixVariant& addressPrefixp) const { + return Address::isValid(address); +} + +string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { + return Address(publicKey).string(); +} + +TW::Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + const auto addr = Address(address); + return {addr.bytes.begin() + 2, addr.bytes.end()}; +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + signTemplate(dataIn, dataOut); +} + +void Entry::plan([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + planTemplate(dataIn, dataOut); +} + +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + output = Signer::preImageHashes(input); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() == 0 || publicKeys.size() == 0) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return; + } + + if (signatures.size() != publicKeys.size()) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("signatures size and publickeys size not equal"); + return; + } + + HashPubkeyList externalSignatures; + auto n = signatures.size(); + for (auto i = 0ul; i < n; ++i) { + externalSignatures.push_back(std::make_pair(signatures[i], publicKeys[i].bytes)); + } + + output = Signer::sign(input, externalSignatures); + }); +} + +} // namespace TW::Zen diff --git a/src/Zen/Entry.h b/src/Zen/Entry.h new file mode 100644 index 00000000000..ce15dd8b8dc --- /dev/null +++ b/src/Zen/Entry.h @@ -0,0 +1,27 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "../CoinEntry.h" + +namespace TW::Zen { + +/// Entry point for implementation of Zen coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file +class Entry final : public CoinEntry { +public: + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefixp) const override; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override; + Data addressToData(TWCoinType coin, const std::string& address) const override; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; +}; + +} // namespace TW::Zen diff --git a/src/Zen/Signer.cpp b/src/Zen/Signer.cpp new file mode 100644 index 00000000000..4ea7261b937 --- /dev/null +++ b/src/Zen/Signer.cpp @@ -0,0 +1,60 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Signer.h" +#include "Bitcoin/TransactionSigner.h" +#include "Hash.h" +#include "Bitcoin/Transaction.h" +#include "TransactionBuilder.h" + +namespace TW::Zen { + +TransactionPlan Signer::plan(const SigningInput& input) noexcept { + auto plan = Bitcoin::TransactionSigner::plan(input); + return plan.proto(); +} + +SigningOutput Signer::sign(const SigningInput& input, std::optional optionalExternalSigs) noexcept { + SigningOutput output; + auto result = Bitcoin::TransactionSigner::sign(input, false, optionalExternalSigs); + if (!result) { + output.set_error(result.error()); + output.set_error_message(Common::Proto::SigningError_Name(result.error())); + } else { + const auto& tx = result.payload(); + *output.mutable_transaction() = tx.proto(); + + Data encoded; + tx.encode(encoded); + output.set_encoded(encoded.data(), encoded.size()); + + auto txHash = Hash::sha256d(encoded.data(), encoded.size()); + std::reverse(txHash.begin(), txHash.end()); + output.set_transaction_id(hex(txHash)); + } + return output; +} + +PreSigningOutput Signer::preImageHashes(const SigningInput& input) noexcept { + PreSigningOutput output; + auto result = Bitcoin::TransactionSigner::preImageHashes(input); + if (!result) { + output.set_error(result.error()); + output.set_error_message(Common::Proto::SigningError_Name(result.error())); + return output; + } + + auto hashList = result.payload(); + auto* hashPubKeys = output.mutable_hash_public_keys(); + for (const auto& h : hashList) { + auto* hpk = hashPubKeys->Add(); + hpk->set_data_hash(h.first.data(), h.first.size()); + hpk->set_public_key_hash(h.second.data(), h.second.size()); + } + return output; +} + +} // namespace TW::Zen diff --git a/src/Zen/Signer.h b/src/Zen/Signer.h new file mode 100644 index 00000000000..a2031e1a072 --- /dev/null +++ b/src/Zen/Signer.h @@ -0,0 +1,37 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "../Data.h" +#include "../proto/Bitcoin.pb.h" + +#include + +namespace TW::Zen { + +using SigningInput = Bitcoin::Proto::SigningInput; +using SigningOutput = Bitcoin::Proto::SigningOutput; +using TransactionPlan = Bitcoin::Proto::TransactionPlan; +using PreSigningOutput = Bitcoin::Proto::PreSigningOutput; + +/// Helper class that performs Zen transaction signing. +class Signer { +public: + /// Hide default constructor + Signer() = delete; + + /// Returns a transaction plan (utxo selection, fee estimation) + static TransactionPlan plan(const SigningInput& input) noexcept; + + /// Signs a SigningInput transaction + static SigningOutput sign(const SigningInput& input, std::optional optionalExternalSigs = {}) noexcept; + + /// Collect pre-image hashes to be signed + static PreSigningOutput preImageHashes(const SigningInput& input) noexcept; +}; + +} // namespace TW::Zen diff --git a/src/Zen/TransactionBuilder.h b/src/Zen/TransactionBuilder.h new file mode 100644 index 00000000000..641e1b42756 --- /dev/null +++ b/src/Zen/TransactionBuilder.h @@ -0,0 +1,95 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Address.h" +#include "../Bitcoin/Transaction.h" +#include "../Bitcoin/TransactionBuilder.h" +#include "../Bitcoin/TransactionPlan.h" +#include "../Bitcoin/TransactionOutput.h" +#include "../Coin.h" +#include "../proto/Bitcoin.pb.h" +#include "../HexCoding.h" +#include + +#include + +using namespace TW; + +namespace TW::Zen { + +struct TransactionBuilder { + /// Plans a transaction by selecting UTXOs and calculating fees. + static Bitcoin::TransactionPlan plan(const Bitcoin::SigningInput& input) { + return Bitcoin::TransactionBuilder::plan(input); + } + + /// Builds a transaction with the selected input UTXOs, and one main output and an optional change output. + template + static Result build(const Bitcoin::TransactionPlan& plan, const Bitcoin::SigningInput& input) { + Transaction tx; + tx.lockTime = input.lockTime; + + auto blockHash = plan.preBlockHash; + auto blockHeight = plan.preBlockHeight; + + auto outputToAmount = input.amount; + if (plan.useMaxAmount) { + outputToAmount = plan.amount; + } + auto outputTo = prepareOutputWithScript(input.toAddress, outputToAmount, input.coinType, blockHash, blockHeight); + if (!outputTo.has_value()) { + return Result::failure(Common::Proto::Error_invalid_address); + } + tx.outputs.push_back(outputTo.value()); + + if (plan.change > 0) { + auto outputChange = prepareOutputWithScript(input.changeAddress, plan.change, input.coinType, blockHash, blockHeight); + if (!outputChange.has_value()) { + return Result::failure(Common::Proto::Error_invalid_address); + } + tx.outputs.push_back(outputChange.value()); + } + + const auto emptyScript = Bitcoin::Script(); + for (auto& utxo : plan.utxos) { + tx.inputs.emplace_back(utxo.outPoint, emptyScript, utxo.outPoint.sequence); + } + + // Optional OP_RETURN output + if (plan.outputOpReturn.size() > 0) { + auto lockingScriptOpReturn = Bitcoin::Script::buildOpReturnScript(plan.outputOpReturn); + if (lockingScriptOpReturn.bytes.size() == 0) { + return Result::failure(Common::Proto::Error_invalid_memo); + } + tx.outputs.push_back(Bitcoin::TransactionOutput(0, lockingScriptOpReturn)); + } + + // extra outputs + for (auto& o : input.extraOutputs) { + auto output = prepareOutputWithScript(o.first, o.second, input.coinType, blockHash, blockHeight); + if (!output.has_value()) { + return Result::failure(Common::Proto::Error_invalid_address); + } + tx.outputs.push_back(output.value()); + } + + return Result(tx); + } + + /// Prepares a TransactionOutput with given address and amount, prepares script for it + static std::optional prepareOutputWithScript(const std::string& addr, Bitcoin::Amount amount, enum TWCoinType coin, const Data& blockHash, int64_t blockHeight) { + auto lockingScript = Bitcoin::Script::lockScriptForAddress(addr, coin, blockHash, blockHeight); + if (lockingScript.empty()) { + return {}; + } + return Bitcoin::TransactionOutput(amount, lockingScript); + } + +}; + +} // namespace TW::Zen diff --git a/src/interface/TWBitcoinScript.cpp b/src/interface/TWBitcoinScript.cpp index 1150abb28a2..c79764940cd 100644 --- a/src/interface/TWBitcoinScript.cpp +++ b/src/interface/TWBitcoinScript.cpp @@ -154,6 +154,13 @@ struct TWBitcoinScript *_Nonnull TWBitcoinScriptLockScriptForAddress(TWString *_ return new TWBitcoinScript{ .impl = script }; } +struct TWBitcoinScript *_Nonnull TWBitcoinScriptLockScriptForAddressReplay(TWString *_Nonnull address, enum TWCoinType coin, TWData *blockHash, int64_t blockHeight) { + auto* s = reinterpret_cast(address); + auto* v = reinterpret_cast*>(blockHash); + auto script = TW::Bitcoin::Script::lockScriptForAddress(*s, coin, *v, blockHeight); + return new TWBitcoinScript{ .impl = script }; +} + uint32_t TWBitcoinScriptHashTypeForCoin(enum TWCoinType coinType) { return TW::Bitcoin::hashTypeForCoin(coinType); } diff --git a/src/interface/TWCoinType.cpp b/src/interface/TWCoinType.cpp index 8e6a2e9c151..c6d31860ab4 100644 --- a/src/interface/TWCoinType.cpp +++ b/src/interface/TWCoinType.cpp @@ -55,6 +55,11 @@ TWString *_Nonnull TWCoinTypeDeriveAddressFromPublicKey(enum TWCoinType coin, st return TWStringCreateWithUTF8Bytes(string.c_str()); } +TWString *_Nonnull TWCoinTypeDeriveAddressFromPublicKeyAndDerivation(enum TWCoinType coin, struct TWPublicKey *_Nonnull publicKey, enum TWDerivation derivation) { + const auto string = TW::deriveAddress(coin, publicKey->impl, derivation); + return TWStringCreateWithUTF8Bytes(string.c_str()); +} + enum TWHRP TWCoinTypeHRP(enum TWCoinType coin) { return TW::hrp(coin); } diff --git a/src/interface/TWHash.cpp b/src/interface/TWHash.cpp index 19aa0aeee39..26cac076adf 100644 --- a/src/interface/TWHash.cpp +++ b/src/interface/TWHash.cpp @@ -83,6 +83,16 @@ TWData* _Nonnull TWHashSHA256SHA256(TWData* _Nonnull data) { return TWDataCreateWithBytes(result.data(), result.size()); } +TWData *_Nonnull TWHashBlake2bPersonal(TWData *_Nonnull data, TWData * _Nonnull personal, size_t outlen) { + auto resultBytes = TW::Data(outlen); + auto dataBytes = TWDataBytes(data); + auto personalBytes = TWDataBytes(personal); + auto personalSize = TWDataSize(personal); + blake2b_Personal(dataBytes, static_cast(TWDataSize(data)), personalBytes, personalSize, resultBytes.data(), outlen); + auto result = TWDataCreateWithBytes(resultBytes.data(), outlen); + return result; +} + TWData* _Nonnull TWHashSHA256RIPEMD(TWData* _Nonnull data) { const auto result = Hash::sha256ripemd(reinterpret_cast(TWDataBytes(data)), TWDataSize(data)); return TWDataCreateWithBytes(result.data(), result.size()); diff --git a/src/interface/TWSubstrateAddress.cpp b/src/interface/TWSubstrateAddress.cpp new file mode 100644 index 00000000000..ae0492f6aba --- /dev/null +++ b/src/interface/TWSubstrateAddress.cpp @@ -0,0 +1,44 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "../Substrate/Address.h" + +#include +#include + +using namespace TW; + +bool TWSubstrateAddressEqual(struct TWSubstrateAddress *_Nonnull lhs, struct TWSubstrateAddress *_Nonnull rhs) { + return lhs->impl == rhs->impl; +} + +bool TWSubstrateAddressIsValidString(TWString *_Nonnull string, int32_t network) { + auto s = reinterpret_cast(string); + return TW::Substrate::Address::isValid(*s, network); +} + +struct TWSubstrateAddress *_Nullable TWSubstrateAddressCreateWithString(TWString *_Nonnull string, int32_t network) { + auto s = reinterpret_cast(string); + try { + const auto address = TW::Substrate::Address(*s, network); + return new TWSubstrateAddress{ std::move(address) }; + } catch (...) { + return nullptr; + } +} + +struct TWSubstrateAddress *_Nonnull TWSubstrateAddressCreateWithPublicKey(struct TWPublicKey *_Nonnull publicKey, int32_t network) { + return new TWSubstrateAddress{ TW::Substrate::Address(publicKey->impl, network) }; +} + +void TWSubstrateAddressDelete(struct TWSubstrateAddress *_Nonnull address) { + delete address; +} + +TWString *_Nonnull TWSubstrateAddressDescription(struct TWSubstrateAddress *_Nonnull address) { + const auto string = address->impl.string(); + return TWStringCreateWithUTF8Bytes(string.c_str()); +} diff --git a/src/interface/TWSubstrateSigner.cpp b/src/interface/TWSubstrateSigner.cpp new file mode 100644 index 00000000000..13daad8fb18 --- /dev/null +++ b/src/interface/TWSubstrateSigner.cpp @@ -0,0 +1,35 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include "../Substrate/Signer.h" + +using namespace TW; +using namespace TW::Substrate; + +TWData *_Nonnull TWSubstrateSignerMessage(TWData *_Nonnull data) { + TW::Substrate::Proto::SigningInput input; + input.ParseFromArray(TWDataBytes(data), static_cast(TWDataSize(data))); + auto encoded = Signer::signaturePreImage(input); + return TWDataCreateWithBytes(reinterpret_cast(encoded.data()), encoded.size());; +} + +TWData *_Nonnull TWSubstrateSignerTransaction(TWData *_Nonnull data, TWData *_Nonnull publicKey, TWData *_Nonnull signature) { + TW::Substrate::Proto::SigningInput input; + input.ParseFromArray(TWDataBytes(data), static_cast(TWDataSize(data))); + + std::vector pkVec; + auto rawPk = TWDataBytes(publicKey); + pkVec.assign(rawPk, rawPk + static_cast(TWDataSize(publicKey))); + + std::vector signVec; + auto rawSign = TWDataBytes(signature); + signVec.assign(rawSign, rawSign + static_cast(TWDataSize(signature))); + + auto encoded = TW::Substrate::Signer::encodeTransaction(input, pkVec, signVec); + + return TWDataCreateWithBytes(reinterpret_cast(encoded.data()), encoded.size()); +} diff --git a/src/interface/TWTransactionCompiler.cpp b/src/interface/TWTransactionCompiler.cpp index f7a586d5eef..454e8f9aa0a 100644 --- a/src/interface/TWTransactionCompiler.cpp +++ b/src/interface/TWTransactionCompiler.cpp @@ -69,3 +69,18 @@ TWData *_Nonnull TWTransactionCompilerCompileWithSignatures(enum TWCoinType coin } catch (...) {} // return empty return TWDataCreateWithBytes(result.data(), result.size()); } + +TWData *_Nonnull TWTransactionCompilerCompileWithSignaturesAndPubKeyType(enum TWCoinType coinType, TWData *_Nonnull txInputData, const struct TWDataVector *_Nonnull signatures, const struct TWDataVector *_Nonnull publicKeys, enum TWPublicKeyType pubKeyType) { + Data result; + try { + assert(txInputData != nullptr); + const Data inputData = data(TWDataBytes(txInputData), TWDataSize(txInputData)); + assert(signatures != nullptr); + const auto signaturesVec = createFromTWDataVector(signatures); + assert(publicKeys != nullptr); + const auto publicKeysVec = createFromTWDataVector(publicKeys); + + result = TransactionCompiler::compileWithSignaturesAndPubKeyType(coinType, inputData, signaturesVec, publicKeysVec, pubKeyType); + } catch (...) {} // return empty + return TWDataCreateWithBytes(result.data(), result.size()); +} diff --git a/src/proto/Aion.proto b/src/proto/Aion.proto index 2b194fc0237..3df8e2d5679 100644 --- a/src/proto/Aion.proto +++ b/src/proto/Aion.proto @@ -3,6 +3,8 @@ syntax = "proto3"; package TW.Aion.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // Input data necessary to create a signed transaction. message SigningInput { // Nonce (uint256, serialized little endian) @@ -37,4 +39,10 @@ message SigningOutput { // Signature. bytes signature = 2; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 3; + + // error description + string error_message = 4; } diff --git a/src/proto/Algorand.proto b/src/proto/Algorand.proto index 21fe4881fac..744a2562070 100644 --- a/src/proto/Algorand.proto +++ b/src/proto/Algorand.proto @@ -3,6 +3,8 @@ syntax = "proto3"; package TW.Algorand.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // Simple transfer message, transfer an amount to an address message Transfer { // Destination address (string) @@ -52,6 +54,8 @@ message SigningInput { // fee amount uint64 fee = 7; + // public key + bytes public_key = 8; // message payload oneof message_oneof { @@ -68,4 +72,10 @@ message SigningOutput { // Signature in base64. string signature = 2; + + // Error code, 0 is ok, other codes will be treated as errors. + Common.Proto.SigningError error = 3; + + // Error description. + string error_message = 4; } diff --git a/src/proto/Bitcoin.proto b/src/proto/Bitcoin.proto index cb321e44a8a..5cd8636d728 100644 --- a/src/proto/Bitcoin.proto +++ b/src/proto/Bitcoin.proto @@ -42,6 +42,9 @@ message OutPoint { // Transaction version as defined by the sender. uint32 sequence = 3; + + // The tree in utxo, only works for DCR + int32 tree = 4; } // Bitcoin transaction output. @@ -65,6 +68,15 @@ message UnspentTransaction { int64 amount = 3; } +// Pair of destination address and amount, used for extra outputs +message OutputAddress { + // Destination address + string to_address = 1; + + // Amount to be paid to this output + int64 amount = 2; +} + // Input data necessary to create a signed transaction. message SigningInput { // Hash type to use when signing. @@ -111,6 +123,18 @@ message SigningInput { // Optional zero-amount, OP_RETURN output bytes output_op_return = 13; + + // Optional additional destination addresses, additional to first to_address output + repeated OutputAddress extra_outputs = 14; + + // If use max utxo. + bool use_max_utxo = 15; + + // If disable dust filter. + bool disable_dust_filter = 16; + + // transaction creation time that will be used for verge(xvg) + uint32 time = 17; } // Describes a preliminary transaction plan. @@ -138,6 +162,12 @@ message TransactionPlan { // Optional zero-amount, OP_RETURN output bytes output_op_return = 8; + + // zen & bitcoin diamond preblockhash + bytes preblockhash = 9; + + // zen preblockheight + int64 preblockheight = 10; }; // Result containing the signed and encoded transaction. diff --git a/src/proto/Cardano.proto b/src/proto/Cardano.proto index 90477ff2521..d32bae5509b 100644 --- a/src/proto/Cardano.proto +++ b/src/proto/Cardano.proto @@ -24,6 +24,9 @@ message TokenAmount { // The amount (uint256, serialized little endian) bytes amount = 3; + + // The name of the asset (hex encoded). Ignored if `asset_name` is set + string asset_name_hex = 4; } // One input for a transaction @@ -140,7 +143,7 @@ message TransactionPlan { uint64 deposit = 10; // coins undeposited (coming from deposit) in this TX - uint64 undeposit = 11; + uint64 undeposit = 11; // total tokens in the utxos (optional) repeated TokenAmount available_tokens = 5; @@ -156,6 +159,9 @@ message TransactionPlan { // Optional error Common.Proto.SigningError error = 9; + + // Optional additional destination addresses, additional to first to_address output + repeated TxOutput extra_outputs = 12; } // Input data necessary to create a signed transaction. @@ -187,6 +193,9 @@ message SigningInput { // Optional plan, if missing it will be computed TransactionPlan plan = 5; + + // extra output UTXOs + repeated TxOutput extra_outputs = 10; } // Result containing the signed and encoded transaction. @@ -199,4 +208,7 @@ message SigningOutput { // Optional error Common.Proto.SigningError error = 3; + + // error code description + string error_message = 4; } diff --git a/src/proto/Cosmos.proto b/src/proto/Cosmos.proto index bbde8aa383c..0b6a2b25d76 100644 --- a/src/proto/Cosmos.proto +++ b/src/proto/Cosmos.proto @@ -3,6 +3,8 @@ syntax = "proto3"; package TW.Cosmos.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // A denomination and an amount message Amount { // name of the denomination @@ -88,6 +90,13 @@ message Message { string type_prefix = 5; } + // cosmos-sdk/MsgSetWithdrawAddress + message SetWithdrawAddress { + string delegator_address = 1; + string withdraw_address = 2; + string type_prefix = 3; + } + // cosmos-sdk/MsgWithdrawDelegationReward message WithdrawDelegationReward { string delegator_address = 1; @@ -95,6 +104,14 @@ message Message { string type_prefix = 3; } + message ExecuteContract { + string sender = 1; + string contract = 2; + string execute_msg = 3; + repeated Amount coins = 4; + string type_prefix = 5; + } + // transfer within wasm/MsgExecuteContract, used by Terra Classic message WasmTerraExecuteContractTransfer { // sender address @@ -316,16 +333,18 @@ message Message { WasmTerraExecuteContractTransfer wasm_terra_execute_contract_transfer_message = 8; WasmTerraExecuteContractSend wasm_terra_execute_contract_send_message = 9; THORChainSend thorchain_send_message = 10; - WasmTerraExecuteContractGeneric wasm_terra_execute_contract_generic = 11; - WasmExecuteContractTransfer wasm_execute_contract_transfer_message = 12; - WasmExecuteContractSend wasm_execute_contract_send_message = 13; - WasmExecuteContractGeneric wasm_execute_contract_generic = 14; - SignDirect sign_direct_message = 15; - AuthGrant auth_grant = 16; - AuthRevoke auth_revoke = 17; - MsgVote msg_vote = 18; - MsgStrideLiquidStakingStake msg_stride_liquid_staking_stake = 19; - MsgStrideLiquidStakingRedeem msg_stride_liquid_staking_redeem = 20; + ExecuteContract execute_contract_message = 11; + WasmTerraExecuteContractGeneric wasm_terra_execute_contract_generic = 12; + WasmExecuteContractTransfer wasm_execute_contract_transfer_message = 13; + WasmExecuteContractSend wasm_execute_contract_send_message = 14; + WasmExecuteContractGeneric wasm_execute_contract_generic = 15; + SignDirect sign_direct_message = 16; + AuthGrant auth_grant = 17; + AuthRevoke auth_revoke = 18; + SetWithdrawAddress set_withdraw_address_message = 19; + MsgVote msg_vote = 20; + MsgStrideLiquidStakingStake msg_stride_liquid_staking_stake = 21; + MsgStrideLiquidStakingRedeem msg_stride_liquid_staking_redeem = 22; } } @@ -363,6 +382,8 @@ message SigningInput { // Broadcast mode (included in output, relevant when broadcasting) BroadcastMode mode = 9; + + bytes public_key = 10; } // Result containing the signed and encoded transaction. @@ -377,9 +398,11 @@ message SigningOutput { // wrapped in a ready-to-broadcast json. string serialized = 3; - // Set in case of error - string error = 4; - // signatures array json string - string signature_json = 5; + string signature_json = 4; + + // error description + string error_message = 5; + + Common.Proto.SigningError error = 6; } diff --git a/src/proto/Decred.proto b/src/proto/Decred.proto index fe9a8ff5663..82ff3dd429a 100644 --- a/src/proto/Decred.proto +++ b/src/proto/Decred.proto @@ -73,4 +73,6 @@ message SigningOutput { // Optional error Common.Proto.SigningError error = 4; + + string error_message = 5; } diff --git a/src/proto/EOS.proto b/src/proto/EOS.proto index 7f32c0a75fd..f890a8c83d1 100644 --- a/src/proto/EOS.proto +++ b/src/proto/EOS.proto @@ -54,6 +54,9 @@ message SigningInput { // Type of the private key KeyType private_key_type = 10; + + // Expiration of the transaction, if not set, default is reference_block_time + 3600 seconds + sfixed32 expiration = 11; } // Result containing the signed and encoded transaction. @@ -63,4 +66,7 @@ message SigningOutput { // Optional error Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; } diff --git a/src/proto/FIO.proto b/src/proto/FIO.proto index e946dcf3fa7..9af884ddf9b 100644 --- a/src/proto/FIO.proto +++ b/src/proto/FIO.proto @@ -147,6 +147,9 @@ message SigningInput { // Context-specific action data Action action = 5; + + // FIO address of the owner. Ex.: "FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf" + string owner_public_key = 6; } // Result containing the signed and encoded transaction. @@ -156,4 +159,7 @@ message SigningOutput { // Optional error Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; } diff --git a/src/proto/Filecoin.proto b/src/proto/Filecoin.proto index 78bbcc302d8..f43b2d1d6e5 100644 --- a/src/proto/Filecoin.proto +++ b/src/proto/Filecoin.proto @@ -3,6 +3,8 @@ syntax = "proto3"; package TW.Filecoin.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // Defines the type of `from` address derivation. enum DerivationType { // Defines a Secp256k1 (`f1`) derivation for the sender address. @@ -40,6 +42,9 @@ message SigningInput { // Sender address derivation type. DerivationType derivation = 9; + + // Public key secp256k1 extended + bytes public_key = 10; } // Result containing the signed and encoded transaction. @@ -47,6 +52,9 @@ message SigningOutput { // Resulting transaction, in JSON. string json = 1; - // Error description. - string error_message = 2; + // Error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 2; + + // Error description + string error_message = 3; } diff --git a/src/proto/Harmony.proto b/src/proto/Harmony.proto index 86647fad3bd..8d341e7ebe1 100644 --- a/src/proto/Harmony.proto +++ b/src/proto/Harmony.proto @@ -3,6 +3,8 @@ syntax = "proto3"; package TW.Harmony.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // Input data necessary to create a signed transaction. message SigningInput { // Chain identifier (uint256, serialized little endian) @@ -27,6 +29,12 @@ message SigningOutput { bytes v = 2; bytes r = 3; bytes s = 4; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 5; + + // error code description + string error_message = 6; } // A Transfer message diff --git a/src/proto/IOST.proto b/src/proto/IOST.proto new file mode 100644 index 00000000000..0566e0fde68 --- /dev/null +++ b/src/proto/IOST.proto @@ -0,0 +1,101 @@ +syntax = "proto3"; + +package TW.IOST.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Common.proto"; + +// The message defines transaction action struct. +message Action { + // contract name + string contract = 1; + // action name + string action_name = 2; + // data + string data = 3; +} + +// The message defines transaction amount limit struct. +message AmountLimit { + // token name + string token = 1; + // limit value + string value = 2; +} + +// The enumeration defines the signature algorithm. +enum Algorithm { + // unknown + UNKNOWN = 0; + // secp256k1 + SECP256K1 = 1; + // ed25519 + ED25519 = 2; +} + +// The message defines signature struct. +message Signature { + // signature algorithm + Algorithm algorithm = 1; + // signature bytes + bytes signature = 2; + // public key + bytes public_key = 3; +} + +// The message defines the transaction request. +message Transaction { + // transaction timestamp + int64 time = 1; + // expiration timestamp + int64 expiration = 2; + // gas price + double gas_ratio = 3; + // gas limit + double gas_limit = 4; + // delay nanoseconds + int64 delay = 5; + // chain id + uint32 chain_id = 6; + // action list + repeated Action actions = 7; + // amount limit + repeated AmountLimit amount_limit = 8; + // signer list + repeated string signers = 9; + // signatures of signers + repeated Signature signatures = 10; + // publisher + string publisher = 11; + // signatures of publisher + repeated Signature publisher_sigs = 12; +} + +message AccountInfo { + string name = 1; + bytes active_key = 2; + bytes owner_key = 3; +} + +// Input data necessary to create a signed transaction. +message SigningInput { + AccountInfo account = 1; + Transaction transaction_template = 2; + string transfer_destination = 3; + string transfer_amount = 4; + string transfer_memo = 5; +} + +// Transaction signing output. +message SigningOutput { + // Signed transaction + Transaction transaction = 1; + // Signed and encoded transaction bytes. + bytes encoded = 2; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 3; + + // error code description + string error_message = 4; +} diff --git a/src/proto/Icon.proto b/src/proto/Icon.proto index fc39efc3ee8..9c19fbe6ec4 100644 --- a/src/proto/Icon.proto +++ b/src/proto/Icon.proto @@ -3,6 +3,8 @@ syntax = "proto3"; package TW.Icon.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // Input data necessary to create a signed transaction. message SigningInput { // Sender address. @@ -37,4 +39,9 @@ message SigningOutput { // Signature. bytes signature = 2; + + // error description + string error_message = 3; + + Common.Proto.SigningError error = 4; } diff --git a/src/proto/IoTeX.proto b/src/proto/IoTeX.proto index 275dd40d8ab..47a7238de0d 100644 --- a/src/proto/IoTeX.proto +++ b/src/proto/IoTeX.proto @@ -3,6 +3,8 @@ syntax = "proto3"; package TW.IoTeX.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // A transfer message Transfer { // Amount (as string) @@ -152,8 +154,11 @@ message SigningInput { // Gas price string gasPrice = 4; + // The chain id of blockchain + uint32 chainID = 5; + // The secret private key used for signing (32 bytes). - bytes privateKey = 5; + bytes privateKey = 6; // Payload transfer oneof action { @@ -179,6 +184,12 @@ message SigningOutput { // Signed Action hash bytes hash = 2; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 3; + + // error code description + string error_message = 4; } // An Action structure @@ -196,6 +207,9 @@ message ActionCore { // Gas price string gasPrice = 4; + // Chain ID + uint32 chainID = 5; + // action payload oneof action { Transfer transfer = 10; diff --git a/src/proto/MultiversX.proto b/src/proto/MultiversX.proto index e4b17c9b5ec..ec160d41d31 100644 --- a/src/proto/MultiversX.proto +++ b/src/proto/MultiversX.proto @@ -9,6 +9,8 @@ syntax = "proto3"; package TW.MultiversX.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // Generic action. Using one of the more specific actions (e.g. transfers, see below) is recommended. message GenericAction { // Accounts involved @@ -34,6 +36,10 @@ message EGLDTransfer { // Transfer amount (string) string amount = 2; + string data = 3; + + // transaction version, if empty, the default value will be used + uint32 version = 4; } // ESDT transfer (transfer regular ESDTs - fungible tokens). @@ -46,6 +52,9 @@ message ESDTTransfer { // Transfer token amount (string) string amount = 3; + + // transaction version, if empty, the default value will be used + uint32 version = 4; } // ESDTNFT transfer (transfer NFTs, SFTs and Meta ESDTs). @@ -61,6 +70,9 @@ message ESDTNFTTransfer { // transfer amount string amount = 4; + + // transaction version, if empty, the default value will be used + uint32 version = 5; } // Transaction sender & receiver etc. @@ -111,4 +123,10 @@ message SigningInput { message SigningOutput { string encoded = 1; string signature = 2; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 3; + + // error code description + string error_message = 4; } diff --git a/src/proto/NEAR.proto b/src/proto/NEAR.proto index a08668da60c..920fa57d587 100644 --- a/src/proto/NEAR.proto +++ b/src/proto/NEAR.proto @@ -3,6 +3,8 @@ syntax = "proto3"; package TW.NEAR.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // Public key with type message PublicKey { // Key type @@ -143,6 +145,9 @@ message SigningInput { // The secret private key used for signing (32 bytes). bytes private_key = 6; + + // The public key used for compiling a transaction with a signature. + bytes public_key = 7; } // Result containing the signed and encoded transaction. @@ -150,6 +155,12 @@ message SigningOutput { // Signed transaction blob bytes signed_transaction = 1; + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; + // Hash of the transaction - bytes hash = 2; + bytes hash = 4; } diff --git a/src/proto/NEO.proto b/src/proto/NEO.proto index 096327cbc51..acb0984d947 100644 --- a/src/proto/NEO.proto +++ b/src/proto/NEO.proto @@ -20,6 +20,15 @@ message TransactionInput { string asset_id = 4; } +// extra address of Output +message OutputAddress { + // Amount (as string) + sint64 amount = 1; + + // destination address + string to_address = 2; +} + // Output of a transaction message TransactionOutput { // Asset @@ -33,6 +42,39 @@ message TransactionOutput { // change address string change_address = 4; + + // extra output + repeated OutputAddress extra_outputs = 5; +} + +// Transaction +message Transaction { + // nep5 token transfer transaction + message Nep5Transfer { + string asset_id = 1; + string from = 2; + string to = 3; + + // Amount to send (256-bit number) + bytes amount = 4; + + // determine if putting THROWIFNOT & RET instructions + bool script_with_ret = 5; + } + + // Generic invocation transaction + message InvocationGeneric { + // gas to use + uint64 gas = 1; + + // Contract call payload data + bytes script = 2; + } + + oneof transaction_oneof { + Nep5Transfer nep5_transfer = 1; + InvocationGeneric invocation_generic = 2; + } } // Input data necessary to create a signed transaction. @@ -57,6 +99,7 @@ message SigningInput { // Optional transaction plan (if missing it will be computed) TransactionPlan plan = 7; + Transaction transaction = 8; } // Result containing the signed and encoded transaction. @@ -66,6 +109,9 @@ message SigningOutput { // Optional error Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; } // Describes a preliminary transaction output plan. @@ -87,8 +133,16 @@ message TransactionOutputPlan { // Address for the change string change_address = 6; + + // extra output + repeated OutputAddress extra_outputs = 7; }; +message TransactionAttributePlan { + int32 usage = 1; + bytes data = 2; +} + // Describes a preliminary transaction plan. message TransactionPlan { // Used assets @@ -102,4 +156,7 @@ message TransactionPlan { // Optional error Common.Proto.SigningError error = 4; + + // Attribute + repeated TransactionAttributePlan attributes = 5; }; diff --git a/src/proto/NULS.proto b/src/proto/NULS.proto index 51ed1806919..2ee273d7824 100644 --- a/src/proto/NULS.proto +++ b/src/proto/NULS.proto @@ -3,6 +3,8 @@ syntax = "proto3"; package TW.NULS.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // Transaction from address message TransactionCoinFrom { // Source address @@ -72,10 +74,10 @@ message Transaction { bytes tx_data = 4; // CoinFrom - TransactionCoinFrom input = 5; + repeated TransactionCoinFrom input = 5; // CoinTo - TransactionCoinTo output = 6; + repeated TransactionCoinTo output = 6; // Signature Signature tx_sigs = 7; @@ -115,10 +117,24 @@ message SigningInput { // time, accurate to the second uint32 timestamp = 10; + // external address paying fee, required for token transfer, optional for NULS transfer, depending on if an external fee payer is provided. If provided, it will be the fee paying address. + string fee_payer = 11; + // fee payer address nonce, required for token transfer, optional for NULS transfer, depending on if fee_payer is provided. + bytes fee_payer_nonce = 12; + // fee payer address private key, required for token transfer, optional for NULS transfer, depending on if fee_payer is provided. + bytes fee_payer_private_key = 13; + // fee payer NULS balance, it is required for token transfer. optional for NULS transfer, depending on if fee_payer is provided. + bytes fee_payer_balance = 14; } // Result containing the signed and encoded transaction. message SigningOutput { // Encoded transaction bytes encoded = 1; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; } diff --git a/src/proto/Nano.proto b/src/proto/Nano.proto index 9bd7a7a26bd..f9bc057cb13 100644 --- a/src/proto/Nano.proto +++ b/src/proto/Nano.proto @@ -3,6 +3,8 @@ syntax = "proto3"; package TW.Nano.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // Input data necessary to create a signed transaction. message SigningInput { // The secret private key used for signing (32 bytes). @@ -27,6 +29,9 @@ message SigningInput { // Work string work = 7; + + // Pulic key used for building preImage (32 bytes). + bytes public_key = 8; } // Result containing the signed and encoded transaction. @@ -39,4 +44,10 @@ message SigningOutput { // JSON representation of the block string json = 3; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 4; + + // error code description + string error_message = 5; } diff --git a/src/proto/Oasis.proto b/src/proto/Oasis.proto index 9391bb81d9b..5a5cdec18ac 100644 --- a/src/proto/Oasis.proto +++ b/src/proto/Oasis.proto @@ -9,6 +9,8 @@ syntax = "proto3"; package TW.Oasis.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // Transfer message TransferMessage { // destination address @@ -30,6 +32,28 @@ message TransferMessage { string context = 6; } +message EscrowMessage { + uint64 gas_price = 1; + string gas_amount = 2; + uint64 nonce = 3; + + string account = 4; + string amount = 5; + + string context = 6; +} + +message ReclaimEscrowMessage { + uint64 gas_price = 1; + string gas_amount = 2; + uint64 nonce = 3; + + string account = 4; + string shares = 5; + + string context = 6; +} + // Input data necessary to create a signed transaction. message SigningInput { // The secret private key used for signing (32 bytes). @@ -38,6 +62,8 @@ message SigningInput { // Transfer payload oneof message { TransferMessage transfer = 2; + EscrowMessage escrow = 3; + ReclaimEscrowMessage reclaimEscrow = 4; } } @@ -45,4 +71,10 @@ message SigningInput { message SigningOutput { // Signed and encoded transaction bytes. bytes encoded = 1; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; } diff --git a/src/proto/Ontology.proto b/src/proto/Ontology.proto index 9496537dd18..6b837d53212 100644 --- a/src/proto/Ontology.proto +++ b/src/proto/Ontology.proto @@ -3,6 +3,8 @@ syntax = "proto3"; package TW.Ontology.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // Input data necessary to create a signed transaction. message SigningInput { // Contract ID, e.g. "ONT" @@ -35,10 +37,21 @@ message SigningInput { // Nonce (should be larger than in the last transaction of the account) uint32 nonce = 10; + // base58 encode address string (160-bit number) + string owner_address = 11; + + // base58 encode address string (160-bit number) + string payer_address = 12; } // Result containing the signed and encoded transaction. message SigningOutput { // Signed and encoded transaction bytes. bytes encoded = 1; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; } diff --git a/src/proto/Polkadot.proto b/src/proto/Polkadot.proto index 8fa16926363..23dd556190a 100644 --- a/src/proto/Polkadot.proto +++ b/src/proto/Polkadot.proto @@ -3,6 +3,8 @@ syntax = "proto3"; package TW.Polkadot.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // Destination options for reward enum RewardDestination { STAKED = 0; @@ -29,8 +31,12 @@ message Balance { // amount (uint256, serialized little endian) bytes value = 2; } + message BatchTransfer { + repeated Transfer transfers = 1; + } oneof message_oneof { Transfer transfer = 1; + BatchTransfer batchTransfer = 2; } } @@ -75,6 +81,12 @@ message Staking { bytes value = 1; } + // Rebond + message Rebond { + // amount (uint256, serialized little endian) + bytes value = 1; + } + // Withdraw unbonded amounts message WithdrawUnbonded { int32 slashing_spans = 1; @@ -106,6 +118,7 @@ message Staking { Nominate nominate = 6; Chill chill = 7; ChillAndUnbond chill_and_unbond = 8; + Rebond rebond = 9; } } @@ -149,4 +162,10 @@ message SigningInput { message SigningOutput { // Signed and encoded transaction bytes. bytes encoded = 1; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; } diff --git a/src/proto/Ripple.proto b/src/proto/Ripple.proto index dd903dc7435..8423e56fc97 100644 --- a/src/proto/Ripple.proto +++ b/src/proto/Ripple.proto @@ -100,6 +100,9 @@ message SigningInput { OperationNFTokenCancelOffer op_nftoken_cancel_offer = 12; } + + // Only used by tss chain-integration. + bytes public_key = 15; } // Result containing the signed and encoded transaction. @@ -109,4 +112,7 @@ message SigningOutput { // Optional error Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; } diff --git a/src/proto/Solana.proto b/src/proto/Solana.proto index 67a250cbffe..7ee3e0cf244 100644 --- a/src/proto/Solana.proto +++ b/src/proto/Solana.proto @@ -3,6 +3,8 @@ syntax = "proto3"; package TW.Solana.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // Transfer transaction message Transfer { // destination address @@ -130,6 +132,24 @@ message CreateAndTransferToken { repeated string references = 8; } +message CreateNonceAccount { + // Required for building pre-signing hash of a transaction + string nonce_account = 1; + uint64 rent = 2; + // Optional for building pre-signing hash of a transaction + bytes nonce_account_private_key = 3; +} + +message WithdrawNonceAccount { + string nonce_account = 1; + string recipient = 2; + uint64 value = 3; +} + +message AdvanceNonceAccount { + string nonce_account = 1; +} + // Input data necessary to create a signed transaction. message SigningInput { // The secret private key used for signing (32 bytes). @@ -151,14 +171,46 @@ message SigningInput { CreateTokenAccount create_token_account_transaction = 10; TokenTransfer token_transfer_transaction = 11; CreateAndTransferToken create_and_transfer_token_transaction = 12; + CreateNonceAccount create_nonce_account = 13; + WithdrawNonceAccount withdraw_nonce_account = 16; + AdvanceNonceAccount advance_nonce_account = 19; } + // Required for building pre-signing hash of a transaction + string sender = 14; + // Required for using durable transaction nonce + string nonce_account = 15; + // Optional external fee payer private key. support: TokenTransfer, CreateAndTransferToken + bytes fee_payer_private_key = 17; + // Optional external fee payer. support: TokenTransfer, CreateAndTransferToken + string fee_payer = 18; } // Result containing the signed and encoded transaction. message SigningOutput { // The encoded transaction string encoded = 1; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; // The unsigned transaction - string unsigned_tx = 2; + string unsigned_tx = 4; +} + +/// Transaction pre-signing output +message PreSigningOutput { + /// Signer list + repeated bytes signers = 1; + + /// Pre-image data. There is no hashing for Solana presign image + bytes data = 2; + + // Error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 3; + + // Error code description + string error_message = 4; } diff --git a/src/proto/Stellar.proto b/src/proto/Stellar.proto index 44a67e3672f..ab890559337 100644 --- a/src/proto/Stellar.proto +++ b/src/proto/Stellar.proto @@ -3,6 +3,8 @@ syntax = "proto3"; package TW.Stellar.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // Represents an asset // Note: alphanum12 currently not supported message Asset { @@ -130,10 +132,18 @@ message SigningInput { MemoHash memo_hash = 12; MemoHash memo_return_hash = 13; } + + int64 time_bounds = 16; } // Result containing the signed and encoded transaction. message SigningOutput { // Signature. string signature = 1; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; } diff --git a/src/proto/Substrate.proto b/src/proto/Substrate.proto new file mode 100644 index 00000000000..437884c30c3 --- /dev/null +++ b/src/proto/Substrate.proto @@ -0,0 +1,119 @@ +syntax = "proto3"; + +package TW.Substrate.Proto; +option java_package = "wallet.core.jni.proto"; + +message Era { + // recent block number (called phase in polkadot code), should match block hash + uint64 block_number = 1; + + // length of period, calculated from block number, e.g. 64 + uint64 period = 2; +} + +message Balance { + message Transfer { + int32 module_index = 1; + int32 method_index = 2; + string to_address = 3; + bytes value = 4; // BigInteger + string memo = 5; // max 32 chars + } + message BatchTransfer { + int32 module_index = 1; + int32 method_index = 2; + repeated Transfer transfers = 3; + } + message AssetTransfer { + int32 module_index = 1; + int32 method_index = 2; + string to_address = 3; + bytes value = 4; // BigInteger + uint32 asset_id = 5; + uint32 fee_asset_id = 6; + } + message BatchAssetTransfer { + int32 module_index = 1; + int32 method_index = 2; + uint32 fee_asset_id = 3; + repeated AssetTransfer transfers = 4; + } + oneof message_oneof { + Transfer transfer = 1; + BatchTransfer batch_transfer = 2; + AssetTransfer asset_transfer = 3; + BatchAssetTransfer batch_asset_transfer = 4; + } +} + +message Authorization { + message JoinIdentity { + message Data { + bytes data = 1; + } + message AuthData { + Data asset = 1; // authorization data, empty means all permissions, null means no persimission + Data extrinsic = 2; // authorization data, empty means all permissions, null means no persimission + Data portfolio = 3; // authorization data, empty means all permissions, null means no persimission + } + int32 module_index = 1; + int32 method_index = 2; + string target = 3; // address that will be added to the Identity + AuthData data = 4; // authorization data, null means all permissions + uint64 expiry = 5; // expire time, unix seconds + } + oneof message_oneof { + JoinIdentity join_identity = 1; + } +} + +message Identity { + message JoinIdentityAsKey { + int32 module_index = 1; + int32 method_index = 2; + uint64 auth_id = 3; + } + oneof message_oneof { + JoinIdentityAsKey join_identity_as_key = 1; + } +} + +message PolymeshCall { + oneof message_oneof { + Authorization authorization_call = 1; + Identity identity_call = 2; + } +} + +// Input data necessary to create a signed transaction. +message SigningInput { + // Recent block hash, or genesis hash if era is not set + bytes block_hash = 1; + + bytes genesis_hash = 2; + + // Current account nonce + uint64 nonce = 3; + + uint32 spec_version = 4; + uint32 transaction_version = 5; + bytes tip = 6; // big integer + + // Optional time validity limit, recommended, for replay-protection. Empty means Immortal. + Era era = 7; + + bytes private_key = 8; + int32 network = 9; + oneof message_oneof { + Balance balance_call = 10; + } + bool multi_address = 11; + PolymeshCall polymesh_call = 12; +} + +// Transaction signing output. +message SigningOutput { + // Signed and encoded transaction bytes. + bytes encoded = 1; +} + diff --git a/src/proto/Tezos.proto b/src/proto/Tezos.proto index 8732c2e1974..7d632d2e90a 100644 --- a/src/proto/Tezos.proto +++ b/src/proto/Tezos.proto @@ -3,6 +3,8 @@ syntax = "proto3"; package TW.Tezos.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // Input data necessary to create a signed Tezos transaction. // Next field: 3 message SigningInput { @@ -21,6 +23,12 @@ message SigningInput { message SigningOutput { // The encoded transaction bytes encoded = 1; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; } // A list of operations and a branch. @@ -107,7 +115,8 @@ message OperationParameters { message TransactionOperationData { string destination = 1; int64 amount = 2; - OperationParameters parameters = 3; + bytes encoded_parameter = 3; + OperationParameters parameters = 4; } // Reveal operation specific data. diff --git a/src/proto/Theta.proto b/src/proto/Theta.proto index 4647d89ba83..01e8ebdfff9 100644 --- a/src/proto/Theta.proto +++ b/src/proto/Theta.proto @@ -3,6 +3,8 @@ syntax = "proto3"; package TW.Theta.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + /// Input data necessary to create a signed transaction message SigningInput { /// Chain ID string, mainnet, testnet and privatenet @@ -25,6 +27,9 @@ message SigningInput { /// The secret private key used for signing (32 bytes). bytes private_key = 7; + + /// Public key + bytes public_key = 8; } // Result containing the signed and encoded transaction. @@ -34,4 +39,10 @@ message SigningOutput { /// Signature bytes signature = 2; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 3; + + // error code description + string error_message = 4; } diff --git a/src/proto/Tron.proto b/src/proto/Tron.proto index fd24c124332..22abe490c8a 100644 --- a/src/proto/Tron.proto +++ b/src/proto/Tron.proto @@ -3,6 +3,8 @@ syntax = "proto3"; package TW.Tron.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // A transfer transaction message TransferContract { // Sender address. @@ -274,4 +276,10 @@ message SigningOutput { // Result in JSON string json = 5; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 6; + + // error code description + string error_message = 7; } diff --git a/src/proto/VeChain.proto b/src/proto/VeChain.proto index 2b2b7e151c2..62f641ad577 100644 --- a/src/proto/VeChain.proto +++ b/src/proto/VeChain.proto @@ -3,6 +3,8 @@ syntax = "proto3"; package TW.VeChain.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // A clause, between a sender and destination message Clause { /// Recipient address. @@ -55,4 +57,10 @@ message SigningOutput { // Signature. bytes signature = 2; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 3; + + // error code description + string error_message = 4; } diff --git a/swift/Podfile.lock b/swift/Podfile.lock index a8504fac8bd..5fd52c0fed2 100644 --- a/swift/Podfile.lock +++ b/swift/Podfile.lock @@ -13,4 +13,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: aac2324ba35cdd5631cb37618cd483887bab9cfd -COCOAPODS: 1.11.3 +COCOAPODS: 1.12.0 diff --git a/swift/Tests/Blockchains/BitcoinDiamondTests.swift b/swift/Tests/Blockchains/BitcoinDiamondTests.swift new file mode 100644 index 00000000000..0a7a0941e54 --- /dev/null +++ b/swift/Tests/Blockchains/BitcoinDiamondTests.swift @@ -0,0 +1,56 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +import WalletCore +import XCTest + +class BitcoinDiamondTests: XCTestCase { + func testAddress() { + let key = PrivateKey(data: Data(hexString: "d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: true) + let address = AnyAddress(publicKey: pubkey, coin: .bitcoinDiamond) + let addressFromString = AnyAddress(string: "1G15VvshDxwFTnahZZECJfFwEkq9fP79o8", coin: .bitcoinDiamond)! + + XCTAssertEqual(pubkey.data.hexString, "02485a209514cc896f8ed736e205bc4c35bd5299ef3f9e84054475336b964c02a3") + XCTAssertEqual(address.description, addressFromString.description) + } + + func testSign() { + let key = PrivateKey(data: Data(hexString: "d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee")!)! + + let script = BitcoinScript.lockScriptForAddress(address: "1G15VvshDxwFTnahZZECJfFwEkq9fP79o8", coin: .bitcoinDiamond) + let utxos = [ + BitcoinUnspentTransaction.with { + $0.outPoint.hash = Data(hexString: "034f4667301711e8a69236a93476ed798f9c11aaae472da5b315191a0453461d")! + $0.outPoint.index = 0 + $0.outPoint.sequence = UINT32_MAX + $0.script = script.data + $0.amount = 27615 + } + ] + + let plan = BitcoinTransactionPlan.with { + $0.amount = 17615 + $0.fee = 10000 + $0.change = 0 + $0.utxos = utxos + $0.preblockhash = Data(hexString: "99668daf7c77c6462f3ba8fab61b4f62abaabd62911e7e59729ee9e1929dfa4b")! + } + + let input = BitcoinSigningInput.with { + $0.amount = 17615 + $0.toAddress = "1HevQVTSEc8cEpDm65UpfCdj5erd4xxBhx" + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .bitcoinDiamond) + $0.coinType = CoinType.bitcoinDiamond.rawValue + $0.privateKey = [key.data] + $0.plan = plan + } + + let output: BitcoinSigningOutput = AnySigner.sign(input: input, coin: .bitcoinDiamond) + XCTAssertEqual(output.error, .ok) + XCTAssertEqual(output.encoded.hexString, "0c00000099668daf7c77c6462f3ba8fab61b4f62abaabd62911e7e59729ee9e1929dfa4b01034f4667301711e8a69236a93476ed798f9c11aaae472da5b315191a0453461d000000006a473044022078e0d3a9e1eb270ab02c15f8fcf1d3bfc95a324839690b7de4f011a4266132ff02204679e8103c4d3f0bb5192a5f53cc273732fd0e8392ab3b00dc708fd24d0160b3012102485a209514cc896f8ed736e205bc4c35bd5299ef3f9e84054475336b964c02a3ffffffff01cf440000000000001976a914b6adfbbf15c8f6fa53f1edb37054dce5c7c145c688ac00000000") + } +} diff --git a/swift/Tests/Blockchains/BitcoinTests.swift b/swift/Tests/Blockchains/BitcoinTests.swift index 4ace92e08e5..a0f50736ef3 100644 --- a/swift/Tests/Blockchains/BitcoinTests.swift +++ b/swift/Tests/Blockchains/BitcoinTests.swift @@ -106,6 +106,7 @@ class BitcoinTransactionSignerTests: XCTestCase { // redeem p2wpkh nested in p2sh let scriptHash = lockScript.matchPayToScriptHash()! let input = BitcoinSigningInput.with { + $0.amount = 43980 $0.toAddress = "3NqULUrjZ7NL36YtBGsSVzqr5q1x9CJWwu" $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .bitcoin) $0.coinType = CoinType.bitcoin.rawValue diff --git a/swift/Tests/Blockchains/CardanoTests.swift b/swift/Tests/Blockchains/CardanoTests.swift index 8d4036d7d43..45c36d2af7e 100644 --- a/swift/Tests/Blockchains/CardanoTests.swift +++ b/swift/Tests/Blockchains/CardanoTests.swift @@ -109,7 +109,7 @@ class CardanoTests: XCTestCase { } let token1 = CardanoTokenAmount.with { $0.policyID = "9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77" - $0.assetName = "SUNDAE" + $0.assetNameHex = "53554e444145" $0.amount = Data(hexString: "04d3e8d9")! // 80996569 } utxo2.tokenAmount.append(token1) @@ -231,7 +231,7 @@ class CardanoTests: XCTestCase { let tokenAmount = CardanoTokenAmount.with { $0.policyID = "219820e6cb04316f41a337fea356480f412e7acc147d28f175f21b5e" - $0.assetName = "coolcatssociety4567" + $0.assetNameHex = "636f6f6c63617473736f636965747934353637" $0.amount = Data(hexString: "01")! // 1 }; var input = CardanoSigningInput.with { diff --git a/swift/Tests/Blockchains/CosmosTests.swift b/swift/Tests/Blockchains/CosmosTests.swift index 3d940fe09d5..6903c9487ee 100644 --- a/swift/Tests/Blockchains/CosmosTests.swift +++ b/swift/Tests/Blockchains/CosmosTests.swift @@ -65,7 +65,7 @@ class CosmosSignerTests: XCTestCase { let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .cosmos) XCTAssertJSONEqual(output.serialized, "{\"tx_bytes\": \"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H\", \"mode\": \"BROADCAST_MODE_BLOCK\"}") - XCTAssertEqual(output.error, "") + XCTAssertEqual(output.errorMessage, "") } func testAuthCompounding() { @@ -103,7 +103,7 @@ class CosmosSignerTests: XCTestCase { let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .cosmos) XCTAssertJSONEqual(output.serialized, "{\"tx_bytes\": \"CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczEzazBxMGw3bGcya3IzMmt2dDdseTIzNnBwbGR5OHY5ZHp3aDNnZBItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjoSNgo0Y29zbW9zdmFsb3BlcjFnanR2bHk5bGVsNnpza3Z3dHZsZzV2aHdwdTljOXdhdzdzeHp3eCABEgYI4LD6pgYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAUSEwoNCgV1YXRvbRIEMjQxOBCp8wUaQIFyfuijGKf87Hz61ZqxasfLI1PZnNge4RDq/tRyB/tZI6p80iGRqHecoV6+84EQkc9GTlNRQOSlApRCsivT9XI=\", \"mode\": \"BROADCAST_MODE_BLOCK\"}") - XCTAssertEqual(output.error, "") + XCTAssertEqual(output.errorMessage, "") } func testRevokeAuthCompounding() { @@ -137,7 +137,7 @@ class CosmosSignerTests: XCTestCase { let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .cosmos) XCTAssertJSONEqual(output.serialized, "{\"tx_bytes\": \"CqoBCqcBCh8vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnUmV2b2tlEoMBCi1jb3Ntb3MxM2swcTBsN2xnMmtyMzJrdnQ3bHkyMzZwcGxkeTh2OWR6d2gzZ2QSLWNvc21vczFmczdsdTI4aHg1bTlha203cnAwYzI0MjJjbjhyMmY3Z3VydWpoZhojL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuTXNnRGVsZWdhdGUSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAQSEwoNCgV1YXRvbRIEMjE5NBC3rQUaQI7K+W7MMBoD6FbFZxRBqs9VTjErztjWTy57+fvrLaTCIZ+eBs7CuaKqfUZdSN8otjubSHVTQID3k9DpPAX0yDo=\", \"mode\": \"BROADCAST_MODE_BLOCK\"}") - XCTAssertEqual(output.error, "") + XCTAssertEqual(output.errorMessage, "") } func testStaking() { @@ -176,7 +176,7 @@ class CosmosSignerTests: XCTestCase { let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .cosmos) XCTAssertJSONEqual(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CpsBCpgBCiMvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dEZWxlZ2F0ZRJxCi1jb3Ntb3MxaHNrNmpyeXlxamZocDVkaGM1NXRjOWp0Y2t5Z3gwZXBoNmRkMDISNGNvc21vc3ZhbG9wZXIxemt1cHI4M2hyemtuM3VwNWVsa3R6Y3EzdHVmdDhueHNtd2RxZ3AaCgoEbXVvbhICMTASZgpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAcSEgoMCgRtdW9uEgQxMDE4ENmaBhpA8O9Jm/kL6Za2I3poDs5vpMowYJgNvYCJBRU/vxAjs0lNZYsq40qpTbwOTbORjJA5UjQ6auc40v6uCFT4q4z+uA==\"}") - XCTAssertEqual(output.error, "") + XCTAssertEqual(output.errorMessage, "") } func testWithdraw() { @@ -221,7 +221,7 @@ class CosmosSignerTests: XCTestCase { let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .cosmos) XCTAssertJSONEqual(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CukDCqABCjcvY29zbW9zLmRpc3RyaWJ1dGlvbi52MWJldGExLk1zZ1dpdGhkcmF3RGVsZWdhdG9yUmV3YXJkEmUKLWNvc21vczEwMHJoeGNscWFzeTZ2bnJjZXJ2Z2g5OWFseDV4dzdsa2ZwNHU1NBI0Y29zbW9zdmFsb3BlcjFleTY5cjM3Z2Z4dnhnNjJzaDRyMGt0cHVjNDZwempybTg3M2FlOAqgAQo3L2Nvc21vcy5kaXN0cmlidXRpb24udjFiZXRhMS5Nc2dXaXRoZHJhd0RlbGVnYXRvclJld2FyZBJlCi1jb3Ntb3MxMDByaHhjbHFhc3k2dm5yY2VydmdoOTlhbHg1eHc3bGtmcDR1NTQSNGNvc21vc3ZhbG9wZXIxc2psbHNucmFtdGczZXd4cXd3cndqeGZnYzRuNGVmOXUybGNuajAKoAEKNy9jb3Ntb3MuZGlzdHJpYnV0aW9uLnYxYmV0YTEuTXNnV2l0aGRyYXdEZWxlZ2F0b3JSZXdhcmQSZQotY29zbW9zMTAwcmh4Y2xxYXN5NnZucmNlcnZnaDk5YWx4NXh3N2xrZnA0dTU0EjRjb3Ntb3N2YWxvcGVyMTY0OHlubHBkdzdmcWEyYXh0MHcyeXAzZms1NDJqdW5sN3JzdnE2EmUKUQpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQJXKG7D830zVXu7qgALJ3RKyQI6qZZ8rnWhgdH/kfqdxRIECgIIARi+AhIQCgoKBXVhdG9tEgExEOC2DRpAXLgJ+8xEMUn7nkFj3ukg2V65Vh5ob7HKeCaNpMM6OPQrpW2r6askfssIFcOd8ThiBEz65bJz81Fmb5MtDTGv4g==\"}") - XCTAssertEqual(output.error, "") + XCTAssertEqual(output.errorMessage, "") } func testIbcTransfer() { @@ -270,6 +270,6 @@ class CosmosSignerTests: XCTestCase { // https://www.mintscan.io/cosmos/txs/817101F3D96314AD028733248B28BAFAD535024D7D2C8875D3FE31DC159F096B XCTAssertJSONEqual(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"Cr4BCrsBCikvaWJjLmFwcGxpY2F0aW9ucy50cmFuc2Zlci52MS5Nc2dUcmFuc2ZlchKNAQoIdHJhbnNmZXISC2NoYW5uZWwtMTQxGg8KBXVhdG9tEgYxMDAwMDAiLWNvc21vczFta3k2OWNuOGVrdHd5MDg0NXZlYzl1cHNkcGhrdHh0MDNna3dseCorb3NtbzE4czBoZG5zbGxnY2Nsd2V1OWF5bXc0bmdrdHIyazBya3ZuN2ptbjIHCAEQgI6ZBBJoClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEC7O9c5DejAsZ/lUaN5LMfNukR9GfX5qUrQcHhPh1WNkkSBAoCCAEYAhIUCg4KBXVhdG9tEgUxMjUwMBCgwh4aQK0HIWdFMk+C6Gi1KG/vELe1ffcc1aEWUIqz2t/ZhwqNNHxUUSp27wteiugHEMVTEIOBhs84t2gIcT/nD/1yKOU=\"}") - XCTAssertEqual(output.error, "") + XCTAssertEqual(output.errorMessage, "") } } diff --git a/swift/Tests/Blockchains/CryptoorgTests.swift b/swift/Tests/Blockchains/CryptoorgTests.swift index 7926940b6b5..c4c2a1941ea 100644 --- a/swift/Tests/Blockchains/CryptoorgTests.swift +++ b/swift/Tests/Blockchains/CryptoorgTests.swift @@ -60,6 +60,6 @@ class CryptoorgTests: XCTestCase { // https://crypto.org/explorer/tx/BCB213B0A121F0CF11BECCF52475F1C8328D6070F3CFDA9E14C42E6DB30E847E XCTAssertJSONEqual(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CpABCo0BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm0KKmNybzFjdHd0Y3dwZ2tza3k5ODhkaHRoNmpzbHh2ZXVtZ3UwZDQ1emdmMBIqY3JvMXhwYWh5NmM3d2xkeGFjdjZsZDk5aDQzNW1odmZuc3VwMjR2Y3VzGhMKB2Jhc2Vjcm8SCDUwMDAwMDAwEmkKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQOIMbBhYj5+i+WdiIxxCEpFyNCJMHy/XsVdxiwde9Vr4xIECgIIARgCEhUKDwoHYmFzZWNybxIENTAwMBDAmgwaQAcxK9xk6r69gmz+1UWaCnYxNuXPXZdp59YcqKPJE5d6fp+IICTBOwd2rs8MiApcf8kNSrbZ6oECxcGQAdxF0SI=\"}") - XCTAssertEqual(output.error, "") + XCTAssertEqual(output.errorMessage, "") } } diff --git a/swift/Tests/Blockchains/EOSTests.swift b/swift/Tests/Blockchains/EOSTests.swift index 30ea50eda7c..7da87e0c6e6 100644 --- a/swift/Tests/Blockchains/EOSTests.swift +++ b/swift/Tests/Blockchains/EOSTests.swift @@ -60,8 +60,8 @@ class EOSTests: XCTestCase { { "compression": "none", "packed_context_free_data": "", - "packed_trx": "7c59a35cd6679a1f3d4800000000010000000080a920cd000000572d3ccdcd010000000080a920cd00000000a8ed3232330000000080a920cd0000000000ea3055e09304000000000004544b4e00000000126d79207365636f6e64207472616e7366657200", - "signatures": ["SIG_K1_KfCdjsrTnx5cBpbA5cUdHZAsRYsnC9uKzuS1shFeqfMCfdZwX4PBm9pfHwGRT6ffz3eavhtkyNci5GoFozQAx8P8PBnDmj"] + "packed_trx": "6e67a35cd6679a1f3d4800000000010000000080a920cd000000572d3ccdcd010000000080a920cd00000000a8ed3232330000000080a920cd0000000000ea3055e09304000000000004544b4e00000000126d79207365636f6e64207472616e7366657200", + "signatures": ["SIG_K1_K9RdLC7DEDWjTfR64GU8BtDHcAjzR1ntcT651JMcfHNTpdsvDrUwfyzF1FkvL9fxEi2UCtGJZ9zYoNbJoMF1fbU64cRiJ7"] } """ XCTAssertEqual(output.error, TW_Common_Proto_SigningError.ok) diff --git a/swift/Tests/Blockchains/EvmosTests.swift b/swift/Tests/Blockchains/EvmosTests.swift index ae10bdc08aa..1809a9c40ca 100644 --- a/swift/Tests/Blockchains/EvmosTests.swift +++ b/swift/Tests/Blockchains/EvmosTests.swift @@ -71,6 +71,6 @@ class EvmosTests: XCTestCase { {"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CpoBCpcBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEncKLGV2bW9zMXJrMzlkazN3ZmY1bnBzN2VtdWh2M250a24zbnN6NnoyZXJxZnIwEixldm1vczEwazlscnJydWFwOW51OTZteHd3eWUyZjZhNXdhemVoMzNrcTY3ehoZCgZhZXZtb3MSDzIwMDAwMDAwMDAwMDAwMBJ7ClcKTwooL2V0aGVybWludC5jcnlwdG8udjEuZXRoc2VjcDI1NmsxLlB1YktleRIjCiEClHXJ+iPsaTZnuqdsTaabSczP3wWMTcsnumfPvJCC2e0SBAoCCAESIAoaCgZhZXZtb3MSEDE0MDAwMDAwMDAwMDAwMDAQ4MUIGkAz9vh1EutbLrLZmRA4eK72bA6bhfMX0YnhtRl5jeaL3AYmk0qdrwG9XzzleBsZ++IokJIk47cgOOyvEjl92Jhj"} """ XCTAssertJSONEqual(output.serialized, expected) - XCTAssertEqual(output.error, "") + XCTAssertEqual(output.errorMessage, "") } } diff --git a/swift/Tests/Blockchains/NULSTests.swift b/swift/Tests/Blockchains/NULSTests.swift index 3bafd7b654a..214d4035c5b 100644 --- a/swift/Tests/Blockchains/NULSTests.swift +++ b/swift/Tests/Blockchains/NULSTests.swift @@ -36,4 +36,73 @@ class NULSTests: XCTestCase { XCTAssertEqual(output.encoded.hexString, "0200f885885d00008c0117010001f7ec6473df12e751d64cf20a8baa7edd50810f8101000100201d9a0000000000000000000000000000000000000000000000000000000000080000000000000000000117010001f05e7878971f3374515eabb6f16d75219d8873120100010080969800000000000000000000000000000000000000000000000000000000000000000000000000692103958b790c331954ed367d37bac901de5c2f06ac8368b37d7bd6cd5ae143c1d7e3463044022028019c0099e2233c7adb84bb03a9a5666ece4a5b65a026a090fa460f3679654702204df0fcb8762b5944b3aba033fa1a287ccb098150035dd8b66f52dc58d3d0843a") } + + func testTokenSign() { + let input = NULSSigningInput.with { + $0.privateKey = Data(hexString: "0x9ce21dad67e0f0af2599b41b515a7f7018059418bab892a7b68f283d489abc4b")! + $0.from = "NULSd6Hgj7ZoVgsPN9ybB4C1N2TbvkgLc8Z9H" + $0.to = "NULSd6Hgied7ym6qMEfVzZanMaa9qeqA6TZSe" + $0.amount = Data(hexString: "0x989680")! + $0.chainID = 9 + $0.idassetsID = 1 + $0.nonce = "0000000000000000".data(using: .utf8)! + $0.remark = "" + $0.balance = Data(hexString: "0x05f5e100")! + $0.timestamp = 1569228280 + $0.feePayer = "NULSd6Hgj7ZoVgsPN9ybB4C1N2TbvkgLc8Z9H" + $0.feePayerBalance = Data(hexString: "0x0f4240")! + $0.feePayerNonce = "0000000000000000".data(using: .utf8)! + + } + + let output: NULSSigningOutput = AnySigner.sign(input: input, coin: .nuls) + + XCTAssertEqual(output.encoded.hexString, "0200f885885d0000d20217010001f7ec6473df12e751d64cf20a8baa7edd50810f810900010080969800000000000000000000000000000000000000000000000000000000000800000000000000000017010001f7ec6473df12e751d64cf20a8baa7edd50810f8101000100a086010000000000000000000000000000000000000000000000000000000000080000000000000000000117010001f05e7878971f3374515eabb6f16d75219d8873120900010080969800000000000000000000000000000000000000000000000000000000000000000000000000692103958b790c331954ed367d37bac901de5c2f06ac8368b37d7bd6cd5ae143c1d7e346304402204629e7e39708468a405f8d8dd208d0133a686beb5d3ae829b7a2f5867c74480902203d0dffac8189b1caa9087e480fd57581e8c382cc4e17034b492dd2178dac851d") + } + + func testSignWithFeePayer() { + let input = NULSSigningInput.with { + $0.privateKey = Data(hexString: "0x48c91cd24a27a1cdc791022ff39316444229db1c466b3b1841b40c919dee3002")! + $0.from = "NULSd6HgYx7bdWWv7PxYhYeTRBhD6kZs1o5Ac" + $0.to = "NULSd6HgaHjzhMEUjd4T5DFnLz9EvV4TntrdV" + $0.amount = Data(hexString: "0x0186a0")! + $0.chainID = 1 + $0.idassetsID = 1 + $0.nonce = "0000000000000000".data(using: .utf8)! + $0.remark = "" + $0.balance = Data(hexString: "0x0f4240")! + $0.timestamp = 1660632991 + $0.feePayer = "NULSd6HgYj81NrQBFZYXvyQhHCJCkGYWDTNeA" + $0.feePayerNonce = "0000000000000000".data(using: .utf8)! + $0.feePayerBalance = Data(hexString: "0x0f4240")! + $0.feePayerPrivateKey = Data(hexString: "0x9401fd554cb700777e57b05338f9ff47597add8b23ce9f1c8e041e9b4e2116b6")! + } + + let output: NULSSigningOutput = AnySigner.sign(input: input, coin: .nuls) + + XCTAssertEqual(output.encoded.hexString, "02009f3ffb620000d202170100014f019a4227bff89d51590fbf53fbd98d994485f801000100a086010000000000000000000000000000000000000000000000000000000000080000000000000000001701000152a6388c8bf54e8fcd73cc824813bfef0912299b01000100a086010000000000000000000000000000000000000000000000000000000000080000000000000000000117010001686a3c9cd2ac45aee0ef825b0443d1eb209368b701000100a0860100000000000000000000000000000000000000000000000000000000000000000000000000d22102764370693450d654d6fc061d1d4dbfbe0c95715fd3cde7e15df073ab0983b8c8463044022040b5820b93fd5e272f2a00518af45a722571834934ba20d9a866de8e6d6409ab022003c610c647648444c1e2193634b2c51817a5a6ac3fe781da1a9ea773506afd8221025910df09ca768909cce9224d182401044c7b5bd44b0adee2ec5f2e64446573ff4630440220140e46b260942a8475f38df39bf54a2eea56c77199fc7ba775aa4b7f147d0d2102206c82705cba509f37ba0e35520a97bccb71a9e35cadcb8d95dd7fde5c8aa9e428") + } + + func testTokenSignWithFeePayer() { + let input = NULSSigningInput.with { + $0.privateKey = Data(hexString: "0x48c91cd24a27a1cdc791022ff39316444229db1c466b3b1841b40c919dee3002")! + $0.from = "NULSd6HgYx7bdWWv7PxYhYeTRBhD6kZs1o5Ac" + $0.to = "NULSd6HgaHjzhMEUjd4T5DFnLz9EvV4TntrdV" + $0.amount = Data(hexString: "0x0186a0")! + $0.chainID = 9 + $0.idassetsID = 1 + $0.nonce = "0000000000000000".data(using: .utf8)! + $0.remark = "" + $0.balance = Data(hexString: "0x0dbba0")! + $0.timestamp = 1660636748 + $0.feePayer = "NULSd6HgYj81NrQBFZYXvyQhHCJCkGYWDTNeA" + $0.feePayerNonce = "e05d03df6ede0e22".data(using: .utf8)! + $0.feePayerBalance = Data(hexString: "0x0f4240")! + $0.feePayerPrivateKey = Data(hexString: "0x9401fd554cb700777e57b05338f9ff47597add8b23ce9f1c8e041e9b4e2116b6")! + } + + let output: NULSSigningOutput = AnySigner.sign(input: input, coin: .nuls) + + XCTAssertEqual(output.encoded.hexString, "02004c4efb620000d2021701000152a6388c8bf54e8fcd73cc824813bfef0912299b09000100a08601000000000000000000000000000000000000000000000000000000000008000000000000000000170100014f019a4227bff89d51590fbf53fbd98d994485f801000100a08601000000000000000000000000000000000000000000000000000000000008e05d03df6ede0e22000117010001686a3c9cd2ac45aee0ef825b0443d1eb209368b709000100a0860100000000000000000000000000000000000000000000000000000000000000000000000000d32102764370693450d654d6fc061d1d4dbfbe0c95715fd3cde7e15df073ab0983b8c8463044022025cb5b40bda4e6fc0ba719bb0c1a907b2a0faa91316ef2c836310d49f906b6a802207d530a56a6c56d974036c86125da06d6e47f9d8ba1544ac3e620cebd883a707821025910df09ca768909cce9224d182401044c7b5bd44b0adee2ec5f2e64446573ff473045022100ff6f45a1c3856f9ea954baca6b2988295bbb22c958f87f0d3baf9989930549530220426ecb1520513710b99ab50e1f6c7e21b0175adef08aa05070bb9bfca8a001d8") + } } diff --git a/swift/Tests/Blockchains/OsmosisTests.swift b/swift/Tests/Blockchains/OsmosisTests.swift index bd7303dbe51..690d85eaa0f 100644 --- a/swift/Tests/Blockchains/OsmosisTests.swift +++ b/swift/Tests/Blockchains/OsmosisTests.swift @@ -58,6 +58,6 @@ class OsmosisTests: XCTestCase { let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .osmosis) XCTAssertJSONEqual(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"Co0BCooBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmoKK29zbW8xbWt5Njljbjhla3R3eTA4NDV2ZWM5dXBzZHBoa3R4dDBlbjk3ZjUSK29zbW8xOHMwaGRuc2xsZ2NjbHdldTlheW13NG5na3RyMmswcmt2bjdqbW4aDgoFdW9zbW8SBTk5ODAwEmQKTgpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQLs71zkN6MCxn+VRo3ksx826RH0Z9fmpStBweE+HVY2SRIECgIIARISCgwKBXVvc21vEgMyMDAQwJoMGkAMY//Md5GRUR4lVZhk558hFS3kii9QZYoYKfg4+ac/xgNeyoiEweVDhcmEvlH1orVwjLUOnYs4ly2a/yIurYVj\"}") - XCTAssertEqual(output.error, "") + XCTAssertEqual(output.errorMessage, "") } } diff --git a/swift/Tests/Blockchains/SecretTests.swift b/swift/Tests/Blockchains/SecretTests.swift index 5abd8ff81b9..4cb9a0f877f 100644 --- a/swift/Tests/Blockchains/SecretTests.swift +++ b/swift/Tests/Blockchains/SecretTests.swift @@ -58,6 +58,6 @@ class SecretTests: XCTestCase { let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .secret) XCTAssertJSONEqual(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CpIBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLXNlY3JldDE4bWRyamE0MGdmdWZ0dDV5eDZ0Z2owZm41bHVycGxlenlwODk0eRItc2VjcmV0MXJucTZoamZuYWx4ZWVmODdybWRleWEzbnU5ZGhwYzdrOXB1anMzGg8KBXVzY3J0EgYxMDAwMDASZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAkZqxdKMtPq2w0kGDGwWGejTAed0H7azPMHtrCX0XYZGEgQKAggBGAESEwoNCgV1c2NydBIEMjUwMBCowwEaQOcHd2gHpa5WKZ/5RRerEtrHlyDlojIEzUGhC9xMFgs7UQMWy+kTTN+NRf7zQ8rx3cPkIKeZhv0u1KRc8uRCc4o=\"}") - XCTAssertEqual(output.error, "") + XCTAssertEqual(output.errorMessage, "") } } diff --git a/swift/Tests/Blockchains/StargazeTests.swift b/swift/Tests/Blockchains/StargazeTests.swift index 03c83f68695..ee95f7c7591 100644 --- a/swift/Tests/Blockchains/StargazeTests.swift +++ b/swift/Tests/Blockchains/StargazeTests.swift @@ -61,7 +61,7 @@ class StargazeTests: XCTestCase { } """; XCTAssertJSONEqual(output.serialized, expected) - XCTAssertEqual(output.error, "") + XCTAssertEqual(output.errorMessage, "") } func testSign() { @@ -111,6 +111,6 @@ class StargazeTests: XCTestCase { } """; XCTAssertJSONEqual(output.serialized, expected) - XCTAssertEqual(output.error, "") + XCTAssertEqual(output.errorMessage, "") } } diff --git a/swift/Tests/Blockchains/SyscoinTests.swift b/swift/Tests/Blockchains/SyscoinTests.swift new file mode 100644 index 00000000000..3e0a6010c8c --- /dev/null +++ b/swift/Tests/Blockchains/SyscoinTests.swift @@ -0,0 +1,169 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +import WalletCore +import XCTest + +class SyscoinTests: XCTestCase { + func testAddress() { + let privateKey1 = PrivateKey(data: Data(hexString: "a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730")!)! + let publicKey1 = privateKey1.getPublicKeySecp256k1(compressed: true) + + let legacyAddress = BitcoinAddress(publicKey: publicKey1, prefix: CoinType.syscoin.p2pkhPrefix)! + XCTAssertEqual(BitcoinAddress(string: "SXBPFk2PFDAP13qyKSdC4yptsJ8kJRAT3T")!.description, legacyAddress.description) + + let privateKey2 = PrivateKey(data: Data(hexString: "f6ee7e6c9bd2f4dc8f0db0dc4679de06c998afc42d825edf7966dd4488b0aa1f")!)! + let publicKey2 = privateKey2.getPublicKeySecp256k1(compressed: true) + let compatibleAddress = BitcoinAddress.compatibleAddress(publicKey: publicKey2, prefix: CoinType.syscoin.p2shPrefix) + XCTAssertEqual(BitcoinAddress(string: "32SKP7HqJLPRNEUNUDddNAXCxyMinjbX8g")!.description, compatibleAddress.description) + + let privateKey3 = PrivateKey(data: Data(hexString: "55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625")!)! + let publicKey3 = privateKey3.getPublicKeySecp256k1(compressed: true) + let bech32Address = SegwitAddress(hrp: .syscoin, publicKey: publicKey3) + XCTAssertEqual(SegwitAddress(string: "sys1qytnqzjknvv03jwfgrsmzt0ycmwqgl0as7lkcf7")!.description, bech32Address.description) + } + + func testSyscoinBlockchain() { + let chain = CoinType.syscoin + XCTAssertTrue(chain.validate(address: "sys1qytnqzjknvv03jwfgrsmzt0ycmwqgl0as7lkcf7")) + XCTAssertTrue(chain.validate(address: "SXBPFk2PFDAP13qyKSdC4yptsJ8kJRAT3T")) + XCTAssertTrue(chain.validate(address: "32SKP7HqJLPRNEUNUDddNAXCxyMinjbX8g")) + XCTAssertFalse(chain.validate(address: "Xm1iDLBP5tdxTxc6t7uJBCVjC4L2A5vB2J")) + XCTAssertFalse(chain.validate(address: "bitcoincash:qq07l6rr5lsdm3m80qxw80ku2ex0tj76vvsxpvmgme")) + XCTAssertFalse(chain.validate(address: "bc1qvtvte5tzlqlfhcdmph94lxk8jcz54q6psyvgla")) + } + + func testExtendedKeys() { + let wallet = HDWallet.test + + // .bip44 + let xprv = wallet.getExtendedPrivateKey(purpose: .bip44, coin: .syscoin, version: .xprv) + let xpub = wallet.getExtendedPublicKey(purpose: .bip44, coin: .syscoin, version: .xpub) + + XCTAssertEqual(xprv, "xprv9yFNgN7z81uG6QtwFt7gvbmLeDGeGfS2ar3DunwEkZcC7uLBXyy4eaaV3ir769zMLe3eHuTaGUtWVXwp6dkunLsfmA7bf3XqEFpTjHxSijx") + XCTAssertEqual(xpub, "xpub6CEj5sesxPTZJtyQMuehHji5CF78g89sx4xpiBLrJu9AzhfL5XHKCNtxtzPzyGxqb6jMbZfmbHeSGZZArL4hLttmdC6KEMuiWy7VocTYjzR") + + // .bip49 + let yprv = wallet.getExtendedPrivateKey(purpose: .bip49, coin: .syscoin, version: .yprv) + let ypub = wallet.getExtendedPublicKey(purpose: .bip49, coin: .syscoin, version: .ypub) + XCTAssertEqual(yprv, "yprvAJAofBFEEQ1DLJJVMkPr4pufHLUKZ2VSbtHqPpphEgwgfvG8exgadM8vtW8AW52N7tqU4qM8JHk5xZkq3icnzoph5QA5kRVHBnhXuRMGw2b") + XCTAssertEqual(ypub, "ypub6XAA4gn84mZWYnNxTmvrRxrPqNJoxVDHy7DSCDEJo2UfYibHCVzqB9TQjmL2TKSEZVFmTNcmdJXunEu6oV2AFQNeiszjzcnX4nbG27s4SgS") + + // .bip84 + let zprv = wallet.getExtendedPrivateKey(purpose: .bip84, coin: .syscoin, version: .zprv) + let zpub = wallet.getExtendedPublicKey(purpose: .bip84, coin: .syscoin, version: .zpub) + XCTAssertEqual(zprv, "zprvAcdCiLx9ooAFnC1hXh7stnobLnnu7u25rqfLeJ9v632xdCXJrc8KvgNk2eZeQQbPQHvcUpsfJzgyDkRdfnkT6vjpYqkxFv1LsPxQ7uFwLGy") + XCTAssertEqual(zpub, "zpub6qcZ7rV3eAiYzg6AdietFvkKtpdPXMjwE4awSgZXeNZwVzrTQ9SaUUhDswmdA4A5riyimx322es7niQvJ1Fbi3mJNSVz3d3f9GBsYBb8Wky") + } + + func testDeriveFromXpub() { + let xpub = "xpub6CEj5sesxPTZJtyQMuehHji5CF78g89sx4xpiBLrJu9AzhfL5XHKCNtxtzPzyGxqb6jMbZfmbHeSGZZArL4hLttmdC6KEMuiWy7VocTYjzR" + let syscoin = CoinType.syscoin + let xpubAddr2 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: syscoin, + derivationPath: DerivationPath(purpose: .bip44, coin: syscoin.slip44Id, account: 0, change: 0, address: 2).description + )! + let xpubAddr9 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: syscoin, + derivationPath: DerivationPath(purpose: .bip44, coin: syscoin.slip44Id, account: 0, change: 0, address: 9).description + )! + + XCTAssertEqual(BitcoinAddress(publicKey: xpubAddr2, prefix: CoinType.syscoin.p2pkhPrefix)!.description, "SkXxaA1GQu6D49qxrjSMCgsybWVsWMZb32") + XCTAssertEqual(BitcoinAddress(publicKey: xpubAddr9, prefix: CoinType.syscoin.p2pkhPrefix)!.description, "ST8SwH6wp6qx6RnQcnUfTJCHznDPsdatzC") + } + + func testDeriveFromYpub() { + let ypub = "ypub6XAA4gn84mZWYnNxTmvrRxrPqNJoxVDHy7DSCDEJo2UfYibHCVzqB9TQjmL2TKSEZVFmTNcmdJXunEu6oV2AFQNeiszjzcnX4nbG27s4SgS" + + let syscoin = CoinType.syscoin + let ypubAddr3 = HDWallet.getPublicKeyFromExtended( + extended: ypub, + coin: syscoin, + derivationPath: DerivationPath(purpose: .bip49, coin: syscoin.slip44Id, account: 0, change: 0, address: 3).description + )! + let ypubAddr10 = HDWallet.getPublicKeyFromExtended( + extended: ypub, + coin: syscoin, + derivationPath: DerivationPath(purpose: .bip49, coin: syscoin.slip44Id, account: 0, change: 0, address: 10).description + )! + + XCTAssertEqual(BitcoinAddress.compatibleAddress(publicKey: ypubAddr3, prefix: CoinType.syscoin.p2shPrefix).description, "31hP9bQFV1iX49yGaaz2ZwoxXzqpPx2vbk") + XCTAssertEqual(BitcoinAddress.compatibleAddress(publicKey: ypubAddr10, prefix: CoinType.syscoin.p2shPrefix).description, "3FhBf4MUNWFiMz3RTbpKb7eie4WHhErSs5") + } + + func testDeriveFromZPub() { + let zpub = "zpub6qcZ7rV3eAiYzg6AdietFvkKtpdPXMjwE4awSgZXeNZwVzrTQ9SaUUhDswmdA4A5riyimx322es7niQvJ1Fbi3mJNSVz3d3f9GBsYBb8Wky" + let syscoin = CoinType.syscoin + let zpubAddr4 = HDWallet.getPublicKeyFromExtended( + extended: zpub, + coin: syscoin, + derivationPath: DerivationPath(purpose: .bip49, coin: syscoin.slip44Id, account: 0, change: 0, address: 4).description + )! + let zpubAddr11 = HDWallet.getPublicKeyFromExtended( + extended: zpub, + coin: syscoin, + derivationPath: DerivationPath(purpose: .bip49, coin: syscoin.slip44Id, account: 0, change: 0, address: 11).description + )! + + XCTAssertEqual(SegwitAddress(hrp: .syscoin, publicKey: zpubAddr4).description, "sys1q54ylw7uztxagxq5hz93cmdrawthhrd00knkp23") + XCTAssertEqual(SegwitAddress(hrp: .syscoin, publicKey: zpubAddr11).description, "sys1q7yzsja5wtkswdc6rleupsvzny3gnyhye0qvdda") + } + + func testPlanAndSign_8435() throws { + let address = "sys1qytnqzjknvv03jwfgrsmzt0ycmwqgl0as7lkcf7" + let lockScript = BitcoinScript.lockScriptForAddress(address: address, coin: .syscoin) + let utxos = [ + BitcoinUnspentTransaction.with { + $0.outPoint.hash = Data.reverse(hexString: "a85fd6a9a7f2f54cacb57e83dfd408e51c0a5fc82885e3fa06be8692962bc407") + $0.outPoint.index = 0 + $0.outPoint.sequence = UINT32_MAX + $0.script = lockScript.data + $0.amount = 3899774 + } + ] + + // redeem p2pwkh + let scriptHash = lockScript.matchPayToWitnessPublicKeyHash()! + var input = BitcoinSigningInput.with { + $0.toAddress = "sys1q54ylw7uztxagxq5hz93cmdrawthhrd00knkp23" + $0.changeAddress = address + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .syscoin) + $0.amount = 1200000 + $0.coinType = CoinType.syscoin.rawValue + $0.byteFee = 1 + $0.utxo = utxos + $0.useMaxAmount = false + $0.scripts = [ + scriptHash.hexString: BitcoinScript.buildPayToPublicKeyHash(hash: scriptHash).data + ] + } + + // Plan + let plan: BitcoinTransactionPlan = AnySigner.plan(input: input, coin: .syscoin) + + XCTAssertEqual(plan.amount, 1200000) + XCTAssertEqual(plan.fee, 141) + XCTAssertEqual(plan.change, 2699633) + + // Extend input with private key + input.privateKey = [Data(hexString: "55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625")!] + input.plan = plan + + // Sign + let output: BitcoinSigningOutput = AnySigner.sign(input: input, coin: .syscoin) + XCTAssertEqual(output.error, TW_Common_Proto_SigningError.ok) + + let txId = output.transactionID + XCTAssertEqual(txId, "cb92bae012ebdd88b720198e40d470746d1af2e8b9b875bb763c831341cb2ded") + + let encoded = output.encoded + XCTAssertEqual(encoded.hexString, + "0100000000010107c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa80000000000ffffffff02804f120000000000160014a549f77b8259ba83029711638db47d72ef71b5ef713129000000000016001422e6014ad3631f1939281c3625bc98db808fbfb00247304402201fff942424755a4ecc84c916c3045c73efab03f9e13e55b27a6ecf6d2027d88602205e54d2fd728e0cfedeecb987dcb346e6e14c5b24ffb26d3db543d90f6571f7080121025cf26d221b01ca4d6040893b96f1dabfd2a108d449b3fa62854421f98a42562b00000000" + ) + } +} diff --git a/swift/Tests/Blockchains/TerraClassicTests.swift b/swift/Tests/Blockchains/TerraClassicTests.swift index bb1e8c81e21..94d98889f9d 100644 --- a/swift/Tests/Blockchains/TerraClassicTests.swift +++ b/swift/Tests/Blockchains/TerraClassicTests.swift @@ -498,7 +498,7 @@ class TerraClassicTests: XCTestCase { } """ ) - XCTAssertEqual(output.error, "") + XCTAssertEqual(output.errorMessage, "") } func testSigningWasmTerraGenericProtobuf() { @@ -547,7 +547,7 @@ class TerraClassicTests: XCTestCase { } """ ) - XCTAssertEqual(output.error, "") + XCTAssertEqual(output.errorMessage, "") } func testSigningWasmTerraGenericWithCoins() { @@ -600,6 +600,6 @@ class TerraClassicTests: XCTestCase { } """ ) - XCTAssertEqual(output.error, "") + XCTAssertEqual(output.errorMessage, "") } } diff --git a/swift/Tests/Blockchains/TerraTests.swift b/swift/Tests/Blockchains/TerraTests.swift index 02a264beadf..588967419ad 100644 --- a/swift/Tests/Blockchains/TerraTests.swift +++ b/swift/Tests/Blockchains/TerraTests.swift @@ -67,7 +67,7 @@ class TerraTests: XCTestCase { } """ XCTAssertJSONEqual(expectedJSON, output.serialized) - XCTAssertEqual(output.error, "") + XCTAssertEqual(output.errorMessage, "") } func testSignWasmTransferTx() { @@ -115,7 +115,7 @@ class TerraTests: XCTestCase { } """ ) - XCTAssertEqual(output.error, "") + XCTAssertEqual(output.errorMessage, "") } func testSignStakeTx() throws { @@ -161,7 +161,7 @@ class TerraTests: XCTestCase { // https://finder.terra.money/mainnet/tx/4441c65f24783b8f59b20b1b80ee43f1f4f6ff827597d87b6bbc94982b45be0c XCTAssertJSONEqual(output.serialized, expected) - XCTAssertEqual(output.error, "") + XCTAssertEqual(output.errorMessage, "") } func testSignClaimRewards() throws { @@ -211,7 +211,7 @@ class TerraTests: XCTestCase { // https://finder.terra.money/mainnet/tx/0e62170ed5407992251d7e161f23c3467e1bea54c7f601953953bdabc7f0c30c XCTAssertJSONEqual(output.serialized, expected) - XCTAssertEqual(output.error, "") + XCTAssertEqual(output.errorMessage, "") } } diff --git a/swift/Tests/Blockchains/ZenTests.swift b/swift/Tests/Blockchains/ZenTests.swift new file mode 100644 index 00000000000..c0fa3d9cdd5 --- /dev/null +++ b/swift/Tests/Blockchains/ZenTests.swift @@ -0,0 +1,61 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +import WalletCore +import XCTest + +class ZenTests: XCTestCase { + func testAddress() { + let key = PrivateKey(data: Data(hexString: "3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: true) + let address = AnyAddress(publicKey: pubkey, coin: .zen) + let addressFromString = AnyAddress(string: "znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg", coin: .zen)! + + XCTAssertEqual(pubkey.data.hexString, "02b4ac9056d20c52ac11b0d7e83715dd3eac851cfc9cb64b8546d9ea0d4bb3bdfe") + XCTAssertEqual(address.description, addressFromString.description) + } + + func testSign() { + let key = PrivateKey(data: Data(hexString: "3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a")!)! + let blockHash = Data(hexString: "81dc725fd33fada1062323802eefb54d3325d924d4297a692214560400000000")! + let blockHeight = Int64(1147624) + let utxos = [ + BitcoinUnspentTransaction.with { + $0.outPoint.hash = Data(hexString: "a39e13b5ab406547e31284cd96fb40ed271813939c195ae7a86cd67fb8a4de62")! + $0.outPoint.index = 0 + $0.outPoint.sequence = UINT32_MAX + $0.script = Data(hexString: "76a914cf83669620de8bbdf2cefcdc5b5113195603c56588ac2081dc725fd33fada1062323802eefb54d3325d924d4297a69221456040000000003e88211b4")! + $0.amount = 17600 + } + ] + + // Plan + let plan = BitcoinTransactionPlan.with { + $0.amount = 10000 + $0.fee = 226 + $0.change = 7374 + $0.utxos = utxos + $0.preblockhash = blockHash + $0.preblockheight = blockHeight + } + + let input = BitcoinSigningInput.with { + $0.amount = 10000 + $0.toAddress = "zngBGZGKaaBamofSuFw5WMnvU2HQAtwGeb5" + $0.changeAddress = "znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg" + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .zen) + $0.coinType = CoinType.zen.rawValue + $0.privateKey = [key.data] + $0.plan = plan + } + + // Sign + let output: BitcoinSigningOutput = AnySigner.sign(input: input, coin: .zen) + XCTAssertEqual(output.error, .ok) + XCTAssertEqual(output.encoded.hexString, "0100000001a39e13b5ab406547e31284cd96fb40ed271813939c195ae7a86cd67fb8a4de62000000006a473044022014d687c0bee0b7b584db2eecbbf73b545ee255c42b8edf0944665df3fa882cfe02203bce2412d93c5a56cb4806ddd8297ff05f8fc121306e870bae33377a58a02f05012102b4ac9056d20c52ac11b0d7e83715dd3eac851cfc9cb64b8546d9ea0d4bb3bdfeffffffff0210270000000000003f76a914a58d22659b1082d1fa8698fc51996b43281bfce788ac2081dc725fd33fada1062323802eefb54d3325d924d4297a69221456040000000003e88211b4ce1c0000000000003f76a914cf83669620de8bbdf2cefcdc5b5113195603c56588ac2081dc725fd33fada1062323802eefb54d3325d924d4297a69221456040000000003e88211b400000000") + + } +} diff --git a/swift/Tests/CoinAddressDerivationTests.swift b/swift/Tests/CoinAddressDerivationTests.swift index 0b82174dafb..7fdacc28e1e 100644 --- a/swift/Tests/CoinAddressDerivationTests.swift +++ b/swift/Tests/CoinAddressDerivationTests.swift @@ -34,9 +34,15 @@ class CoinAddressDerivationTests: XCTestCase { case .binance: let expectedResult = "bnb12vtaxl9952zm6rwf7v8jerq74pvaf77fcmvzhw" assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .tbinance: + let expectedResult = "tbnb12vtaxl9952zm6rwf7v8jerq74pvaf77fkw9xhl" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .bitcoin: let expectedResult = "bc1quvuarfksewfeuevuc6tn0kfyptgjvwsvrprk9d" assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .bitcoinDiamond: + let expectedResult = "1KaRW9xPPtCTZ9FdaTHduCPck4YvSeEWNn" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .bitcoinCash: let expectedResult = "bitcoincash:qpzl3jxkzgvfd9flnd26leud5duv795fnv7vuaha70" assertCoinDerivation(coin, expectedResult, derivedAddress, address) @@ -173,6 +179,9 @@ class CoinAddressDerivationTests: XCTestCase { case .poanetwork: let expectedResult = "0xe8a3e8bE17E172B6926130eAfB521e9D2849aca9" assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .pivx: + let expectedResult = "D81AqC8zKma3Cht4TbVuh4jyVVyLkZULCm" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .polkadot: let expectedResult = "13nN6BGAoJwd7Nw1XxeBCx5YcBXuYnL94Mh7i3xBprqVSsFk" assertCoinDerivation(coin, expectedResult, derivedAddress, address) @@ -214,6 +223,9 @@ class CoinAddressDerivationTests: XCTestCase { case .viacoin: let expectedResult = "via1qnmsgjd6cvfprnszdgmyg9kewtjfgqflz67wwhc" assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .verge: + let expectedResult = "DPb3Xz4vjB6QGLKDmrbprrtv4XzNqkADc2" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .wanchain: let expectedResult = "0xD5ca90b928279FE5D06144136a25DeD90127aC15" assertCoinDerivation(coin, expectedResult, derivedAddress, address) @@ -226,6 +238,12 @@ class CoinAddressDerivationTests: XCTestCase { case .zcash: let expectedResult = "t1YYnByMzdGhQv3W3rnjHMrJs6HH4Y231gy" assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .komodo: + let expectedResult = "RCWJLXE5CSXydxdSnwcghzPgkFswERegyb" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .zen: + let expectedResult = "znUmzvod1f4P9LYsBhNxjqCDQvNSStAmYEX" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .firo: let expectedResult = "aEd5XFChyXobvEics2ppAqgK3Bgusjxtik" assertCoinDerivation(coin, expectedResult, derivedAddress, address) @@ -256,6 +274,15 @@ class CoinAddressDerivationTests: XCTestCase { case .ecash: let expectedResult = "ecash:qpelrdn7a0hcucjlf9ascz3lkxv7r3rffgzn6x5377" assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .iost: + let expectedResult = "4av8w81EyzUgHonsVWqfs15WM4Vrpgox4BYYQWhNQDVu" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .syscoin: + let expectedResult = "sys1qkl640se3mwpt666e3lyywnwh09e9jquvx9x8qj" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .stratis: + let expectedResult = "strax1q0caanaw4nkf6fzwnzq2p7yum680e57pdg05zkm" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .nativeEvmos: let expectedResult = "evmos13u6g7vqgw074mgmf2ze2cadzvkz9snlwstd20d" assertCoinDerivation(coin, expectedResult, derivedAddress, address) @@ -268,6 +295,9 @@ class CoinAddressDerivationTests: XCTestCase { case .aptos: let expectedResult = "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"; assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .nebl: + let expectedResult = "NgDVaXAwNgBwb88xLiFKomfBmPkEh9F2d7"; + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .sui: let expectedResult = "0xada112cfb90b44ba889cc5d39ac2bf46281e4a91f7919c693bcd9b8323e81ed2"; assertCoinDerivation(coin, expectedResult, derivedAddress, address) diff --git a/tests/chains/Aeternity/AddressTests.cpp b/tests/chains/Aeternity/AddressTests.cpp index bfd77019330..986566fd979 100644 --- a/tests/chains/Aeternity/AddressTests.cpp +++ b/tests/chains/Aeternity/AddressTests.cpp @@ -6,6 +6,7 @@ #include #include +#include #include namespace TW::Aeternity::tests { diff --git a/tests/chains/Aion/AddressTests.cpp b/tests/chains/Aion/AddressTests.cpp index d0ac730ce6e..6ac25da0182 100644 --- a/tests/chains/Aion/AddressTests.cpp +++ b/tests/chains/Aion/AddressTests.cpp @@ -5,10 +5,13 @@ // file LICENSE at the root of the source code distribution tree. #include "Aion/Address.h" +#include "Coin.h" #include "HexCoding.h" #include +#include "TestUtilities.h" + using namespace TW; namespace TW::Aion::tests { @@ -37,7 +40,9 @@ TEST(AionAddress, isValid) { std::string invalidAddress = "0xzzd2312facea71b740679c926d040c9056a65a4bfa2ddd18ec160064f82909e7"; ASSERT_TRUE(Address::isValid(validAddress)); + ASSERT_EQ(Address(parse_hex(validAddress)).string(), validAddress); ASSERT_FALSE(Address::isValid(invalidAddress)); + EXPECT_EXCEPTION(Address(parse_hex(invalidAddress)), "Invalid address data"); } } // namespace TW::Aion::tests diff --git a/tests/chains/Aion/TransactionCompilerTests.cpp b/tests/chains/Aion/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..43acc78f1f1 --- /dev/null +++ b/tests/chains/Aion/TransactionCompilerTests.cpp @@ -0,0 +1,97 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Base64.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Aion.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +namespace TW::Aion::tests { + +TEST(AionCompiler, CompileWithSignatures) { + /// Step 1: Prepare transaction input (protobuf) + auto privateKey = parse_hex("db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9"); + auto key = PrivateKey(privateKey); + auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); + + Proto::SigningInput input; + input.set_to_address("0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e"); + auto amount = store(uint256_t(10000)); + input.set_amount(amount.data(), amount.size()); + auto gasPrice = store(uint256_t(20000000000)); + input.set_gas_price(gasPrice.data(), gasPrice.size()); + auto gasLimit = store(uint256_t(21000)); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + auto nonce = store(uint256_t(9)); + input.set_nonce(nonce.data(), nonce.size()); + input.set_timestamp(155157377101); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto txInputData = data(input.SerializeAsString()); + auto preImageHashes = TransactionCompiler::preImageHashes(TWCoinTypeAion, txInputData); + + TxCompiler::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImageHash = data(preSigningOutput.data_hash()); + EXPECT_EQ(hex(preImageHash), "d4423fe7d233b85c1bf5b1120ec03842e572fb25f3755f7a20bc83addc8c4d85"); + + // Simulate signature, normally obtained from signature server + const auto signature = key.sign(preImageHash, TWCurveED25519); + + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(TWCoinTypeAion, txInputData, {signature}, {publicKey.bytes}); + + const auto ExpectedTx = + "f89b09a0a082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e8227108085242019b04d8252088800000004a817c80001b860a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a7d3d3386742c2716031b79950cef5fcb49c079a5cab095c8b08915e126b9741389924ba2d5c00036a3b39c2a8562fa0800f1a13a566ce6e027274ce63a41dec07"; + { + Aion::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + // double check + { + Aion::Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeAion); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + TWCoinTypeAion, txInputData, {signature, signature}, {publicKey.bytes}); + Aion::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + TWCoinTypeAion, txInputData, {}, {}); + Aion::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} + +} // namespace TW::Aion::tests diff --git a/tests/chains/Algorand/AddressTests.cpp b/tests/chains/Algorand/AddressTests.cpp index edc98e23735..9dda252fc1c 100644 --- a/tests/chains/Algorand/AddressTests.cpp +++ b/tests/chains/Algorand/AddressTests.cpp @@ -24,6 +24,8 @@ TEST(AlgorandAddress, Validation) { ASSERT_FALSE(Address::isValid("JBCQYJ2FREG667NAN7BFKH4RFIKPT7CYDQJNW3SNN5Z7F7ILFLKQ346TSU ")); // Stellar address ASSERT_FALSE(Address::isValid("GABQHYQOY22KHGTCTAK24AWAUE4TXERF4O4JBSXELNM7IL5CTPUWM3SC")); + // invalid base32 + ASSERT_FALSE(Address::isValid("0000000000000000000000000000000000000000000000000000000000")); ASSERT_TRUE(Address::isValid("HXIWBVQGOO6ZWE5NYJO22XMYRUGZ6TGNX2K2EERPT3ZIWPHE5CLJGB2GEA")); } @@ -38,11 +40,17 @@ TEST(AlgorandAddress, FromPublicKey) { auto publicKey = PublicKey(parse_hex("c2b423afa8b0095e5ae105668b91b2132db4dadbf38acfc64908d3476a00191f"), TWPublicKeyTypeED25519); auto address = Address(publicKey); ASSERT_EQ(address.string(), "YK2CHL5IWAEV4WXBAVTIXENSCMW3JWW36OFM7RSJBDJUO2QADEP5QYVO5I"); + + auto privateKey2 = PrivateKey(parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f")); + auto publicKey2 = privateKey2.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + EXPECT_ANY_THROW(new Address(publicKey2)); } TEST(AlgorandAddress, FromString) { auto address = Address("PITDOF57RHOVLT37KM7DCXDCETLDL3OA5CBAN7LQ44Z36LGFC27IJ2IQ64"); ASSERT_EQ(address.string(), "PITDOF57RHOVLT37KM7DCXDCETLDL3OA5CBAN7LQ44Z36LGFC27IJ2IQ64"); + + EXPECT_ANY_THROW(Address("")); } } // namespace TW::Algorand::tests diff --git a/tests/chains/Algorand/SignerTests.cpp b/tests/chains/Algorand/SignerTests.cpp index e6718174066..3cb2ff2d432 100644 --- a/tests/chains/Algorand/SignerTests.cpp +++ b/tests/chains/Algorand/SignerTests.cpp @@ -147,6 +147,32 @@ TEST(AlgorandSigner, SignAsset) { ASSERT_EQ(hex(result), "82a3736967c440412720eff99a17280a437bdb8eeba7404b855d6433fffd5dde7f7966c1f9ae531a1af39e18b8a58b4a6c6acb709cca92f8a18c36d8328be9520c915311027005a374786e8aa461616d74ce000f4240a461726376c420325164cafa253b116f4b54c63bd960d610209d44df635d65e095f3855a96b956a3666565cd0924a26676ce00f0b7c3a367656eac746573746e65742d76312e30a26768c4204863b518a4b3c84ec810f22d4f1081cb0f71f059a7ac20dec62f7f70e5093a22a26c76ce00f0bbaba3736e64c42082872d60c338cb928006070e02ec0942addcb79e7fbd01c76458aea526899bd3a474797065a56178666572a478616964ce00cc264a"); } +TEST(AlgorandSigner, SignAssetWithNote) { + auto key = PrivateKey(parse_hex("5a6a3cfe5ff4cc44c19381d15a0d16de2a76ee5c9b9d83b232e38cb5a2c84b04")); + auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); + auto from = Address(publicKey); + auto to = Address("GJIWJSX2EU5RC32LKTDDXWLA2YICBHKE35RV2ZPASXZYKWUWXFLKNFSS4U"); + Data note = data("note"); + std::string genesisId = "testnet-v1.0"; + auto genesisHash = Base64::decode("SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI="); + auto transaction = AssetTransfer( + /* from */ from, + /* to */ to, + /* fee */ 2340, + /* amount */ 1000000, + /* asset id */ 13379146, + /* first round */ 15775683, + /* last round */ 15776683, + /* note */ note, + /* type */ "axfer", + /* genesis id*/ genesisId, + /* genesis hash*/ genesisHash); + + auto serialized = transaction.serialize(); + + ASSERT_EQ(hex(serialized), "8ba461616d74ce000f4240a461726376c420325164cafa253b116f4b54c63bd960d610209d44df635d65e095f3855a96b956a3666565cd0924a26676ce00f0b7c3a367656eac746573746e65742d76312e30a26768c4204863b518a4b3c84ec810f22d4f1081cb0f71f059a7ac20dec62f7f70e5093a22a26c76ce00f0bbaba46e6f7465c4046e6f7465a3736e64c42082872d60c338cb928006070e02ec0942addcb79e7fbd01c76458aea526899bd3a474797065a56178666572a478616964ce00cc264a"); +} + TEST(AlgorandSigner, SignAssetOptIn) { // https://testnet.algoexplorer.io/tx/47LE2QS4B5N6IFHXOUN2MJUTCOQCHNY6AB3AJYECK4IM2VYKJDKQ auto key = PrivateKey(parse_hex("5a6a3cfe5ff4cc44c19381d15a0d16de2a76ee5c9b9d83b232e38cb5a2c84b04")); diff --git a/tests/chains/Algorand/TransactionCompilerTests.cpp b/tests/chains/Algorand/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..647495b2443 --- /dev/null +++ b/tests/chains/Algorand/TransactionCompilerTests.cpp @@ -0,0 +1,99 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Base64.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Algorand.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +namespace TW::Algorand::tests { + +TEST(AlgorandCompiler, CompileWithSignatures) { + /// Step 1: Prepare transaction input (protobuf) + auto privateKey = parse_hex("d5b43d706ef0cb641081d45a2ec213b5d8281f439f2425d1af54e2afdaabf55b"); + auto key = PrivateKey(privateKey); + auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); + auto note = parse_hex("68656c6c6f"); + auto genesisHash = Base64::decode("wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8="); + + auto input = Algorand::Proto::SigningInput(); + auto& transaction = *input.mutable_transfer(); + transaction.set_to_address("CRLADAHJZEW2GFY2UPEHENLOGCUOU74WYSTUXQLVLJUJFHEUZOHYZNWYR4"); + transaction.set_amount(1000000000000ull); + input.set_first_round(1937767ull); + input.set_last_round(1938767ull); + input.set_fee(263000ull); + input.set_genesis_id("mainnet-v1.0"); + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_note(note.data(), note.size()); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); + + auto txInputData = data(input.SerializeAsString()); + auto preImageHashes = TransactionCompiler::preImageHashes(TWCoinTypeAlgorand, txInputData); + + TxCompiler::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImage = data(preSigningOutput.data()); + EXPECT_EQ(hex(preImage), "54588aa3616d74cf000000e8d4a51000a3666565ce00040358a26676ce001d9167a367656eac6d61696e6e65742d76312e30a26768c420c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adfa26c76ce001d954fa46e6f7465c40568656c6c6fa3726376c42014560180e9c92da3171aa3c872356e30a8ea7f96c4a74bc1755a68929c94cb8fa3736e64c42061bf060efc02e2887dfffc8ed85268c8c091c013eedf315bc50794d02a8791ada474797065a3706179"); + + // Simulate signature, normally obtained from signature server + const auto signature = key.sign(preImage, TWCurveED25519); + + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(TWCoinTypeAlgorand, txInputData, {signature}, {publicKey.bytes}); + + const auto ExpectedTx = + "82a3736967c440baa00062adcdcb5875e4435cdc6885d26bfe5308ab17983c0fda790b7103051fcb111554e5badfc0ac7edf7e1223a434342a9eeed5cdb047690827325051560ba374786e8aa3616d74cf000000e8d4a51000a3666565ce00040358a26676ce001d9167a367656eac6d61696e6e65742d76312e30a26768c420c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adfa26c76ce001d954fa46e6f7465c40568656c6c6fa3726376c42014560180e9c92da3171aa3c872356e30a8ea7f96c4a74bc1755a68929c94cb8fa3736e64c42061bf060efc02e2887dfffc8ed85268c8c091c013eedf315bc50794d02a8791ada474797065a3706179"; + { + Algorand::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + // double check + { + Algorand::Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeAlgorand); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + TWCoinTypeAlgorand, txInputData, {signature, signature}, {publicKey.bytes}); + Algorand::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + TWCoinTypeAlgorand, txInputData, {}, {}); + Algorand::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} + +} // namespace TW::Algorand::tests diff --git a/tests/chains/Arbitrum/TWCoinTypeTests.cpp b/tests/chains/Arbitrum/TWCoinTypeTests.cpp index 4bc00aa468b..1b2f67839f4 100644 --- a/tests/chains/Arbitrum/TWCoinTypeTests.cpp +++ b/tests/chains/Arbitrum/TWCoinTypeTests.cpp @@ -4,6 +4,9 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. // +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// #include "TestUtilities.h" #include diff --git a/tests/chains/Binance/AddressTests.cpp b/tests/chains/Binance/AddressTests.cpp new file mode 100644 index 00000000000..fd7a769c277 --- /dev/null +++ b/tests/chains/Binance/AddressTests.cpp @@ -0,0 +1,43 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Binance/Address.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PublicKey.h" + +#include +#include "TestUtilities.h" + +using namespace TW; +using namespace TW::Binance; + +TEST(BinanceAddress, AddressToData) { + // mainnet + auto data = TW::addressToData(TWCoinTypeBinance, "bnb1h8xf9htasu9aclra954dnnve8fgcda4ae7qfa8"); + EXPECT_EQ(hex(data), "b9cc92dd7d870bdc7c7d2d2ad9cd993a5186f6bd"); + + // testnet + data = TW::addressToData(TWCoinTypeTBinance, "tbnb1x4hxmtdf7pwea9dghq73dufh3qspm8gp5qht9c"); + EXPECT_EQ(hex(data), "356e6dada9f05d9e95a8b83d16f13788201d9d01"); + + EXPECT_EQ(Address(data, TWCoinTypeTBinance).string(), "tbnb1x4hxmtdf7pwea9dghq73dufh3qspm8gp5qht9c"); + + // invalid address + data = TW::addressToData(TWCoinTypeTBinance, "tbnb1x4hxmtdf7pwea9dghq73dufh3qspm8gp"); + EXPECT_EQ(hex(data), ""); +} + +TEST(BinanceAddress, DeriveAddress) { + // mainnet + auto pubkey = parse_hex("02bf9a5e2b514492326e7ba9a5161b6e47df7a4aa970dd2d13c398bec89608d8d0"); + auto address = TW::deriveAddress(TWCoinTypeBinance, PublicKey(pubkey, TW::publicKeyType(TWCoinTypeBinance))); + EXPECT_EQ(address, "bnb1h8xf9htasu9aclra954dnnve8fgcda4ae7qfa8"); + + // testnet + address = TW::deriveAddress(TWCoinTypeTBinance, PublicKey(pubkey, TW::publicKeyType(TWCoinTypeTBinance))); + EXPECT_EQ(address, "tbnb1h8xf9htasu9aclra954dnnve8fgcda4ahtfdak"); +} diff --git a/tests/chains/Binance/SignerTests.cpp b/tests/chains/Binance/SignerTests.cpp index f28b4e0e289..dd9280894e3 100644 --- a/tests/chains/Binance/SignerTests.cpp +++ b/tests/chains/Binance/SignerTests.cpp @@ -369,9 +369,8 @@ TEST(BinanceSigner, BuildIssueOrder) { "4e657742696e616e6365546f6b656e" "1a0b" "4e4e422d3333385f424e42" - "208094ebdc032801126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac" - "993dbe0f6b6a8fc71240b2612500b80f5ee8fab1574e9b1763fd898a7048910d02e00dea6337d3c848c9" - "5aa2213db595179db076dfdb10f6e2d9b2aa76c9cd3ee11396ac224991e3e0cd180f2001"); + "208094ebdc032801126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc712401fbb993d643f03b3e8e757a502035f58c4c45aaaa6e107a3059ab7c6164283c10f1254e87feee21477c64c87b1a27d8481048533ae2f685b3ac0dc66e4edbc0b180f2001" + ); } TEST(BinanceSigner, BuildMintOrder) { @@ -397,9 +396,8 @@ TEST(BinanceSigner, BuildMintOrder) { "467e0829" "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b" "4e4e422d3333385f424e42" - "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f" - "6b6a8fc71240b2612500b80f5ee8fab1574e9b1763fd898a7048910d02e00dea6337d3c848c95aa2213d" - "b595179db076dfdb10f6e2d9b2aa76c9cd3ee11396ac224991e3e0cd180f2001"); + "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240e3022069d897bf5bf4846d354fcd2c0e85807053be643c8b8c8596306003f7340d43a162722673eb848258b0435b1f49993d0e75d4ae43d03453a3ae57fe6991180f2001" + ); } TEST(BinanceSigner, BuildBurnOrder) { @@ -425,9 +423,8 @@ TEST(BinanceSigner, BuildBurnOrder) { "7ed2d2a0" "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b" "4e4e422d3333385f424e42" - "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f" - "6b6a8fc71240b2612500b80f5ee8fab1574e9b1763fd898a7048910d02e00dea6337d3c848c95aa2213d" - "b595179db076dfdb10f6e2d9b2aa76c9cd3ee11396ac224991e3e0cd180f2001"); + "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240e3022069d897bf5bf4846d354fcd2c0e85807053be643c8b8c8596306003f7340d43a162722673eb848258b0435b1f49993d0e75d4ae43d03453a3ae57fe6991180f2001" + ); } TEST(BinanceSigner, BuildFreezeOrder) { diff --git a/tests/chains/Binance/TransactionCompilerTests.cpp b/tests/chains/Binance/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..8e5580d5b7c --- /dev/null +++ b/tests/chains/Binance/TransactionCompilerTests.cpp @@ -0,0 +1,120 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" +#include "uint256.h" + +#include "proto/Binance.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(BinanceCompiler, CompileWithSignatures) { + /// Step 1: Prepare transaction input (protobuf) + const auto coin = TWCoinTypeBinance; + const auto txInputData = TransactionCompiler::buildInput( + coin, + "bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", // from + "bnb1hlly02l6ahjsgxw9wlcswnlwdhg4xhx38yxpd5", // to + "1", // amount + "BNB", // asset + "", // memo + "Binance-Chain-Nile" // testnet chainId + ); + + { + // Check, by parsing + EXPECT_EQ(txInputData.size(), 88ul); + Binance::Proto::SigningInput input; + ASSERT_TRUE(input.ParseFromArray(txInputData.data(), (int)txInputData.size())); + EXPECT_EQ(input.chain_id(), "Binance-Chain-Nile"); + EXPECT_TRUE(input.has_send_order()); + ASSERT_EQ(input.send_order().inputs_size(), 1); + EXPECT_EQ(hex(data(input.send_order().inputs(0).address())), + "40c2979694bbc961023d1d27be6fc4d21a9febe6"); + } + + /// Step 2: Obtain preimage hash + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + ASSERT_GT(preImageHashes.size(), 0ul); + + TxCompiler::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImageHash = data(preSigningOutput.data_hash()); + EXPECT_EQ(hex(preImageHash), "3f3fece9059e714d303a9a1496ddade8f2c38fa78fc4cc2e505c5dbb0ea678d1"); + + // Simulate signature, normally obtained from signature server + const auto publicKeyData = + parse_hex("026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e502"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1); + const auto signature = + parse_hex("1b1181faec30b60a2ddaa2804c253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5" + "366bb8b041781a6771248550d04094c3d7a504f9e8310679"); + + // Verify signature (pubkey & hash & signature) + { EXPECT_TRUE(publicKey.verify(signature, preImageHash)); } + + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, txInputData, {signature}, {publicKeyData}); + + const auto ExpectedTx = + "b801f0625dee0a462a2c87fa0a1f0a1440c2979694bbc961023d1d27be6fc4d21a9febe612070a03424e421001" + "121f0a14bffe47abfaede50419c577f1074fee6dd1535cd112070a03424e421001126a0a26eb5ae98721026a35" + "920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e50212401b1181faec30b60a2ddaa2804c" + "253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5366bb8b041781a6771248550d04094c3d7a5" + "04f9e8310679"; + { + EXPECT_EQ(outputData.size(), 189ul); + Binance::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + Binance::Proto::SigningInput input; + ASSERT_TRUE(input.ParseFromArray(txInputData.data(), (int)txInputData.size())); + auto key = parse_hex("95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832"); + input.set_private_key(key.data(), key.size()); + + Binance::Proto::SigningOutput output; + ANY_SIGN(input, coin); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {signature, signature}, {publicKey.bytes}); + Binance::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {}, {}); + Binance::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} diff --git a/tests/chains/Bitcoin/BitcoinScriptTests.cpp b/tests/chains/Bitcoin/BitcoinScriptTests.cpp index b790c092a14..1f7c85b327f 100644 --- a/tests/chains/Bitcoin/BitcoinScriptTests.cpp +++ b/tests/chains/Bitcoin/BitcoinScriptTests.cpp @@ -80,6 +80,22 @@ TEST(BitcoinScript, PayToScriptHash) { EXPECT_EQ(PayToPublicKeyHash.matchPayToScriptHash(res), false); } +TEST(BitcoinScript, PayToScriptHashReplay) { + const Script script = Script::buildPayToScriptHashReplay( + parse_hex("2cda89c2f217517108d55ffdf3d90e111d450be9"), + parse_hex("f1bf729948789aef5801fd91a3bf4e014ffcf4fcbd4f685de2b6990100000000"), 1190791); + EXPECT_EQ(hex(script.bytes), "a9142cda89c2f217517108d55ffdf3d90e111d450be98720f1bf729948789aef5801fd91a3bf4e014ffcf4fcbd4f685de2b699010000000003872b12b4"); + + const Script script2 = Script::lockScriptForAddress( + "zsiZvKaCW9bSVt7Fy6C79CE5rFR6SEiJxAw", TWCoinTypeZen, + parse_hex("f1bf729948789aef5801fd91a3bf4e014ffcf4fcbd4f685de2b6990100000000"), 1190791); + EXPECT_EQ(script.bytes, script2.bytes); + + Data res; + EXPECT_EQ(script.matchPayToScriptHashReplay(res), true); + EXPECT_EQ(hex(res), "2cda89c2f217517108d55ffdf3d90e111d450be9"); +} + TEST(BitcoinScript, PayToWitnessScriptHash) { const Script script = Script::buildPayToWitnessScriptHash(parse_hex("ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db")); EXPECT_EQ(hex(script.bytes), hex(PayToWitnessScriptHash.bytes)); @@ -143,6 +159,11 @@ TEST(BitcoinScript, EncodeNumber) { EXPECT_EQ(Script::encodeNumber(1), OP_1); EXPECT_EQ(Script::encodeNumber(3), OP_3); EXPECT_EQ(Script::encodeNumber(9), OP_9); + + EXPECT_EQ(hex(Script::encodeNumber(int64_t(0))), "00"); + EXPECT_EQ(hex(Script::encodeNumber(int64_t(1))), "51"); + EXPECT_EQ(hex(Script::encodeNumber(int64_t(10000000))), "80969800"); + EXPECT_EQ(hex(Script::encodeNumber(int64_t(-10000000))), "80969880"); } TEST(BitcoinScript, DecodeNumber) { diff --git a/tests/chains/Bitcoin/FeeCalculatorTests.cpp b/tests/chains/Bitcoin/FeeCalculatorTests.cpp index d932866186f..582a332fcb5 100644 --- a/tests/chains/Bitcoin/FeeCalculatorTests.cpp +++ b/tests/chains/Bitcoin/FeeCalculatorTests.cpp @@ -49,6 +49,17 @@ TEST(BitcoinFeeCalculator, SegwitCalculate) { EXPECT_EQ(feeCalculator.calculate(1, 2, 10), 1740); } +TEST(BitcoinFeeCalculator, BitcoinCalculateNoDustFilter) { + const FeeCalculator& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin, true); + EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 174); + EXPECT_EQ(feeCalculator.calculate(1, 1, 1), 143); + EXPECT_EQ(feeCalculator.calculate(0, 2, 1), 72); + EXPECT_EQ(feeCalculator.calculate(1, 0, 1), 112); + EXPECT_EQ(feeCalculator.calculate(0, 0, 1), 10); + EXPECT_EQ(feeCalculator.calculate(1, 2, 10), 1740); + EXPECT_EQ(feeCalculator.calculateSingleInput(1), 0); +} + TEST(BitcoinFeeCalculator, DefaultCalculate) { DefaultFeeCalculator defaultFeeCalculator; EXPECT_EQ(defaultFeeCalculator.calculate(1, 2, 1), 226); @@ -57,15 +68,22 @@ TEST(BitcoinFeeCalculator, DefaultCalculate) { EXPECT_EQ(defaultFeeCalculator.calculate(1, 0, 1), 158); EXPECT_EQ(defaultFeeCalculator.calculate(0, 0, 1), 10); EXPECT_EQ(defaultFeeCalculator.calculate(1, 2, 10), 2260); -} - -TEST(BitcoinFeeCalculator, DefaultCalculateSingleInput) { - DefaultFeeCalculator defaultFeeCalculator; EXPECT_EQ(defaultFeeCalculator.calculateSingleInput(1), 148); EXPECT_EQ(defaultFeeCalculator.calculateSingleInput(2), 296); EXPECT_EQ(defaultFeeCalculator.calculateSingleInput(10), 1480); } +TEST(BitcoinFeeCalculator, DefaultCalculateNoDustFilter) { + DefaultFeeCalculator defaultFeeCalculator(true); + EXPECT_EQ(defaultFeeCalculator.calculate(1, 2, 1), 226); + EXPECT_EQ(defaultFeeCalculator.calculate(1, 1, 1), 192); + EXPECT_EQ(defaultFeeCalculator.calculate(0, 2, 1), 78); + EXPECT_EQ(defaultFeeCalculator.calculate(1, 0, 1), 158); + EXPECT_EQ(defaultFeeCalculator.calculate(0, 0, 1), 10); + EXPECT_EQ(defaultFeeCalculator.calculate(1, 2, 10), 2260); + EXPECT_EQ(defaultFeeCalculator.calculateSingleInput(1), 0); +} + TEST(BitcoinFeeCalculator, DecredCalculate) { const FeeCalculator& feeCalculator = getFeeCalculator(TWCoinTypeDecred); EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 254); @@ -74,4 +92,12 @@ TEST(BitcoinFeeCalculator, DecredCalculate) { EXPECT_EQ(feeCalculator.calculateSingleInput(1), 166); } +TEST(BitcoinFeeCalculator, DecredCalculateNoDustFilter) { + const FeeCalculator& feeCalculator = getFeeCalculator(TWCoinTypeDecred, true); + EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 254); + EXPECT_EQ(feeCalculator.calculate(0, 0, 1), 12); + EXPECT_EQ(feeCalculator.calculate(1, 2, 10), 2540); + EXPECT_EQ(feeCalculator.calculateSingleInput(1), 0); +} + } // namespace TW::Bitcoin diff --git a/tests/chains/Bitcoin/TWBitcoinScriptTests.cpp b/tests/chains/Bitcoin/TWBitcoinScriptTests.cpp index 560400f1f4d..cc555c1b7a7 100644 --- a/tests/chains/Bitcoin/TWBitcoinScriptTests.cpp +++ b/tests/chains/Bitcoin/TWBitcoinScriptTests.cpp @@ -23,6 +23,10 @@ const auto PayToPublicKeyHash = WRAP(TWBitcoinScript, TWBitcoinScriptCreateWithD TEST(TWBitcoinScript, Create) { auto data = DATA("a9144733f37cf4db86fbc2efed2500b4f4e49f31202387"); + { + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptCreate()); + ASSERT_TRUE(script.get() != nullptr); + } { auto script = WRAP(TWBitcoinScript, TWBitcoinScriptCreateWithData(data.get())); ASSERT_TRUE(script.get() != nullptr); diff --git a/tests/chains/Bitcoin/TWBitcoinSigningTests.cpp b/tests/chains/Bitcoin/TWBitcoinSigningTests.cpp index 06d2ff9daf8..93a218564f4 100644 --- a/tests/chains/Bitcoin/TWBitcoinSigningTests.cpp +++ b/tests/chains/Bitcoin/TWBitcoinSigningTests.cpp @@ -37,6 +37,7 @@ SigningInput buildInputP2PKH(bool omitKey = false) { SigningInput input; input.hashType = hashTypeForCoin(TWCoinTypeBitcoin); input.amount = 335'790'000; + input.totalAmount = 335'790'000; input.byteFee = 1; input.toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"; input.changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"; @@ -168,6 +169,7 @@ TEST(BitcoinSigning, SignP2WPKH_Bip143) { input.hashType = TWBitcoinSigHashTypeAll; const auto amount = 112340000; // 0x06B22C20 input.amount = amount; + input.totalAmount = amount; input.byteFee = 20; // not relevant input.toAddress = "1Cu32FVupVCgHkMMRJdYJugxwo2Aprgk7H"; input.changeAddress = "16TZ8J6Q5iZKBWizWzFAYnrsaox5Z5aBRV"; @@ -254,6 +256,7 @@ SigningInput buildInputP2WPKH(int64_t amount, TWBitcoinSigHashType hashType, int SigningInput input; input.hashType = hashType; input.amount = amount; + input.totalAmount = amount; input.useMaxAmount = useMaxAmount; input.byteFee = 1; input.toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"; @@ -418,7 +421,7 @@ TEST(BitcoinSigning, SignP2WPKH_HashAnyoneCanPay_TwoInput) { TEST(BitcoinSigning, SignP2WPKH_MaxAmount) { auto input = buildInputP2WPKH(1'000, TWBitcoinSigHashTypeAll, 625'000'000, 600'000'000, true); - + input.totalAmount = 1224999773; { // test plan (but do not reuse plan result) auto plan = TransactionBuilder::plan(input); @@ -475,6 +478,7 @@ SigningInput buildInputP2WSH(enum TWBitcoinSigHashType hashType, bool omitScript SigningInput input; input.hashType = hashType; input.amount = 1000; + input.totalAmount = 1000; input.byteFee = 1; input.toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"; input.changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"; @@ -742,6 +746,7 @@ SigningInput buildInputP2SH_P2WPKH(bool omitScript = false, bool omitKeys = fals SigningInput input; input.hashType = hashTypeForCoin(TWCoinTypeBitcoin); input.amount = 200'000'000; + input.totalAmount = 200'000'000; input.byteFee = 1; input.toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"; input.changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"; @@ -902,6 +907,7 @@ TEST(BitcoinSigning, SignP2SH_P2WSH) { // Setup signing input SigningInput input; input.amount = 900000000; + input.totalAmount = 900000000; input.hashType = (TWBitcoinSigHashType)0; input.toAddress = "16AQVuBMt818u2HBcbxztAZTT2VTDKupPS"; input.changeAddress = "1Bd1VA2bnLjoBk4ook3H19tZWETk8s6Ym5"; @@ -944,7 +950,7 @@ TEST(BitcoinSigning, SignP2SH_P2WSH) { input.utxos.push_back(utxo); TransactionPlan plan; - plan.amount = input.amount; + plan.amount = input.totalAmount; plan.availableAmount = input.utxos[0].amount; plan.change = 87000000; plan.fee = plan.availableAmount - plan.amount - plan.change; @@ -993,6 +999,7 @@ TEST(BitcoinSigning, Sign_NegativeNoUtxos) { SigningInput input; input.hashType = TWBitcoinSigHashTypeAll; input.amount = 335'790'000; + input.totalAmount = 335'790'000; input.byteFee = 1; input.toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"; input.changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"; @@ -1028,6 +1035,7 @@ TEST(BitcoinSigning, Sign_NegativeInvalidAddress) { SigningInput input; input.hashType = TWBitcoinSigHashTypeAll; input.amount = 335'790'000; + input.totalAmount = 335'790'000; input.byteFee = 1; input.toAddress = "THIS-IS-NOT-A-BITCOIN-ADDRESS"; input.changeAddress = "THIS-IS-NOT-A-BITCOIN-ADDRESS-EITHER"; @@ -1102,6 +1110,7 @@ TEST(BitcoinSigning, Plan_10input_MaxAmount) { input.hashType = hashTypeForCoin(TWCoinTypeBitcoin); input.useMaxAmount = true; input.amount = 2'000'000; + input.totalAmount = 2'000'000; input.byteFee = 1; input.toAddress = "bc1qauwlpmzamwlf9tah6z4w0t8sunh6pnyyjgk0ne"; input.changeAddress = ownAddress; @@ -1141,6 +1150,7 @@ TEST(BitcoinSigning, Sign_LitecoinReal_a85f) { input.coinType = coin; input.hashType = hashTypeForCoin(coin); input.amount = 3'899'774; + input.totalAmount = 3'899'774; input.useMaxAmount = true; input.byteFee = 1; input.toAddress = "ltc1q0dvup9kzplv6yulzgzzxkge8d35axkq4n45hum"; @@ -1210,6 +1220,7 @@ TEST(BitcoinSigning, PlanAndSign_LitecoinReal_8435) { input.coinType = coin; input.hashType = hashTypeForCoin(coin); input.amount = 1'200'000; + input.totalAmount = 1'200'000; input.useMaxAmount = false; input.byteFee = 1; input.toAddress = "ltc1qt36tu30tgk35tyzsve6jjq3dnhu2rm8l8v5q00"; @@ -1301,6 +1312,7 @@ TEST(BitcoinSigning, Sign_ManyUtxos_400) { input.hashType = hashTypeForCoin(TWCoinTypeBitcoin); input.useMaxAmount = false; input.amount = 300'000; + input.totalAmount = 300'000; input.byteFee = 1; input.toAddress = "bc1qauwlpmzamwlf9tah6z4w0t8sunh6pnyyjgk0ne"; input.changeAddress = ownAddress; @@ -1370,6 +1382,7 @@ TEST(BitcoinSigning, Sign_ManyUtxos_2000) { input.hashType = hashTypeForCoin(TWCoinTypeBitcoin); input.useMaxAmount = false; input.amount = 2'000'000; + input.totalAmount = 2'000'000; input.byteFee = 1; input.toAddress = "bc1qauwlpmzamwlf9tah6z4w0t8sunh6pnyyjgk0ne"; input.changeAddress = ownAddress; @@ -1515,6 +1528,7 @@ TEST(BitcoinSigning, RedeemExtendedPubkeyUTXO) { input.coinType = TWCoinTypeBitcoin; input.hashType = hashTypeForCoin(TWCoinTypeBitcoin); input.amount = 26972; + input.totalAmount = 26972; input.useMaxAmount = true; input.byteFee = 1; input.toAddress = addressString; @@ -1560,6 +1574,7 @@ TEST(BitcoinSigning, SignP2TR_5df51e) { SigningInput input; input.hashType = hashTypeForCoin(coin); input.amount = 1100; + input.totalAmount = 1100; input.useMaxAmount = false; input.byteFee = 1; input.toAddress = toAddress; @@ -1703,6 +1718,7 @@ TEST(BitcoinSigning, Sign_OpReturn_THORChainSwap) { input.coinType = TWCoinTypeBitcoin; input.hashType = hashTypeForCoin(TWCoinTypeBitcoin); input.amount = toAmount; + input.totalAmount = toAmount; input.byteFee = byteFee; input.toAddress = toAddress; input.changeAddress = ownAddressString; diff --git a/tests/chains/Bitcoin/TWSegwitAddressTests.cpp b/tests/chains/Bitcoin/TWSegwitAddressTests.cpp index 88a16bfa805..6b21a066205 100644 --- a/tests/chains/Bitcoin/TWSegwitAddressTests.cpp +++ b/tests/chains/Bitcoin/TWSegwitAddressTests.cpp @@ -6,8 +6,10 @@ #include "TestUtilities.h" -#include +#include "Data.h" +#include "HexCoding.h" #include +#include #include @@ -15,6 +17,21 @@ const char* address1 = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"; const char* address2 = "bc1qr583w2swedy2acd7rung055k8t3n7udp7vyzyg"; const char* address3Taproot = "bc1ptmsk7c2yut2xah4pgflpygh2s7fh0cpfkrza9cjj29awapv53mrslgd5cf"; +TEST(TWSegwitAddress, CreateAndDelete) { + { + TWSegwitAddress* addr = TWSegwitAddressCreateWithString(STRING(address1).get()); + EXPECT_TRUE(addr != nullptr); + TWSegwitAddressDelete(addr); + } + { + auto pkData = DATA("0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798"); + auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(pkData.get(), TWPublicKeyTypeSECP256k1)); + TWSegwitAddress* addr = TWSegwitAddressCreateWithPublicKey(TWHRPBitcoin, publicKey.get()); + EXPECT_TRUE(addr != nullptr); + TWSegwitAddressDelete(addr); + } +} + TEST(TWSegwitAddress, PublicKeyToAddress) { auto pkData = DATA("0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798"); auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(pkData.get(), TWPublicKeyTypeSECP256k1)); @@ -23,10 +40,16 @@ TEST(TWSegwitAddress, PublicKeyToAddress) { auto string = WRAPS(TWSegwitAddressDescription(address.get())); ASSERT_STREQ(address1, TWStringUTF8Bytes(string.get())); + + auto str = STRING(address1); + auto addr = WRAP(TWSegwitAddress, TWSegwitAddressCreateWithString(string.get())); + ASSERT_TRUE(TWSegwitAddressEqual(address.get(), addr.get())); } TEST(TWSegwitAddress, InitWithAddress) { auto string = STRING(address1); + ASSERT_TRUE(TWSegwitAddressIsValidString(string.get())); + auto address = WRAP(TWSegwitAddress, TWSegwitAddressCreateWithString(string.get())); auto description = WRAPS(TWSegwitAddressDescription(address.get())); @@ -36,6 +59,9 @@ TEST(TWSegwitAddress, InitWithAddress) { ASSERT_EQ(0, TWSegwitAddressWitnessVersion(address.get())); ASSERT_EQ(TWHRPBitcoin, TWSegwitAddressHRP(address.get())); + + auto witness = WRAPS(TWSegwitAddressWitnessProgram(address.get())); + ASSERT_EQ(TW::hex(TW::data(TWDataBytes(witness.get()), TWDataSize(witness.get()))), "751e76e8199196d454941c45d1b3a323f1433bd6"); } TEST(TWSegwitAddress, TaprootString) { diff --git a/tests/chains/Bitcoin/TransactionCompilerTests.cpp b/tests/chains/Bitcoin/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..212a67ed805 --- /dev/null +++ b/tests/chains/Bitcoin/TransactionCompilerTests.cpp @@ -0,0 +1,296 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Bitcoin.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include +#include + +#include "Bitcoin/Script.h" +#include "Bitcoin/SegwitAddress.h" +#include "Bitcoin/TransactionInput.h" +#include "Bitcoin/TransactionPlan.h" +#include "Bitcoin/TransactionSigner.h" + +#include "TxComparisonHelper.h" +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(BitcoinCompiler, CompileWithSignatures) { + // Test external signining with a Bircoin transaction with 3 input UTXOs, all used, but only + // using 2 public keys. Three signatures are neeeded. This illustrates that order of + // UTXOs/hashes is not always the same. + + const auto revUtxoHash0 = + parse_hex("07c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa8"); + const auto revUtxoHash1 = + parse_hex("d6892a5aa54e3b8fe430efd23f49a8950733aaa9d7c915d9989179f48dd1905e"); + const auto revUtxoHash2 = + parse_hex("6021efcf7555f90627364339fc921139dd40a06ccb2cb2a2a4f8f4ea7a2dc74d"); + const auto inPubKey0 = + parse_hex("024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382"); + const auto inPubKey1 = + parse_hex("0217142f69535e4dad0dc7060df645c55a174cc1bfa5b9eb2e59aad2ae96072dfc"); + const auto inPubKeyHash0 = parse_hex("bd92088bb7e82d611a9b94fbb74a0908152b784f"); + const auto inPubKeyHash1 = parse_hex("6641abedacf9483b793afe1718689cc9420bbb1c"); + + // Input UTXO infos + struct UtxoInfo { + Data revUtxoHash; + Data publicKey; + long amount; + int index; + }; + std::vector utxoInfos = { + // first + UtxoInfo{revUtxoHash0, inPubKey0, 600'000, 0}, + // second UTXO, with same pubkey + UtxoInfo{revUtxoHash1, inPubKey0, 500'000, 1}, + // third UTXO, with different pubkey + UtxoInfo{revUtxoHash2, inPubKey1, 400'000, 0}, + }; + + // Signature infos, indexed by pubkeyhash+hash + struct SignatureInfo { + Data signature; + Data publicKey; + }; + std::map signatureInfos = { + {hex(inPubKeyHash0) + "+" + + "a296bead4172007be69b21971a790e076388666c162a9505698415f1b003ebd7", + { + parse_hex("304402201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b349022" + "00a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f2a40"), + inPubKey0, + }}, + {hex(inPubKeyHash1) + "+" + + "505f527f00e15fcc5a2d2416c9970beb57dfdfaca99e572a01f143b24dd8fab6", + { + parse_hex("3044022041294880caa09bb1b653775310fcdd1458da6b8e7d7fae34e37966414fe11582022" + "0646397c9d2513edc5974ecc336e9b287de0cdf071c366f3b3dc3ff309213e4e4"), + inPubKey1, + }}, + {hex(inPubKeyHash0) + "+" + + "60ed6e9371e5ddc72fd88e46a12cb2f68516ebd307c0fd31b1b55cf767272101", + { + parse_hex("30440220764e3d5b3971c4b3e70b23fb700a7462a6fe519d9830e863a1f8388c402ad0b1022" + "07e777f7972c636961f92375a2774af3b7a2a04190251bbcb31d19c70927952dc"), + inPubKey0, + }}, + }; + + const auto coin = TWCoinTypeBitcoin; + const auto ownAddress = "bc1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z00ppggv"; + + // Setup input for Plan + Bitcoin::Proto::SigningInput signingInput; + signingInput.set_coin_type(coin); + signingInput.set_hash_type(TWBitcoinSigHashTypeAll); + signingInput.set_amount(1'200'000); + signingInput.set_use_max_amount(false); + signingInput.set_byte_fee(1); + signingInput.set_to_address("bc1q2dsdlq3343vk29runkgv4yc292hmq53jedfjmp"); + signingInput.set_change_address(ownAddress); + + // process UTXOs + int count = 0; + for (auto& u : utxoInfos) { + const auto publicKey = PublicKey(u.publicKey, TWPublicKeyTypeSECP256k1); + const auto address = Bitcoin::SegwitAddress(publicKey, "bc"); + if (count == 0) + EXPECT_EQ(address.string(), ownAddress); + if (count == 1) + EXPECT_EQ(address.string(), ownAddress); + if (count == 2) + EXPECT_EQ(address.string(), "bc1qveq6hmdvl9yrk7f6lct3s6yue9pqhwcuxedggg"); + + const auto utxoScript = Bitcoin::Script::lockScriptForAddress(address.string(), coin); + if (count == 0) + EXPECT_EQ(hex(utxoScript.bytes), "0014bd92088bb7e82d611a9b94fbb74a0908152b784f"); + if (count == 1) + EXPECT_EQ(hex(utxoScript.bytes), "0014bd92088bb7e82d611a9b94fbb74a0908152b784f"); + if (count == 2) + EXPECT_EQ(hex(utxoScript.bytes), "00146641abedacf9483b793afe1718689cc9420bbb1c"); + + Data keyHash; + EXPECT_TRUE(utxoScript.matchPayToWitnessPublicKeyHash(keyHash)); + if (count == 0) + EXPECT_EQ(hex(keyHash), hex(inPubKeyHash0)); + if (count == 1) + EXPECT_EQ(hex(keyHash), hex(inPubKeyHash0)); + if (count == 2) + EXPECT_EQ(hex(keyHash), hex(inPubKeyHash1)); + + const auto redeemScript = Bitcoin::Script::buildPayToPublicKeyHash(keyHash); + if (count == 0) + EXPECT_EQ(hex(redeemScript.bytes), + "76a914bd92088bb7e82d611a9b94fbb74a0908152b784f88ac"); + if (count == 1) + EXPECT_EQ(hex(redeemScript.bytes), + "76a914bd92088bb7e82d611a9b94fbb74a0908152b784f88ac"); + if (count == 2) + EXPECT_EQ(hex(redeemScript.bytes), + "76a9146641abedacf9483b793afe1718689cc9420bbb1c88ac"); + (*signingInput.mutable_scripts())[hex(keyHash)] = + std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); + + auto utxo = signingInput.add_utxo(); + utxo->set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); + utxo->set_amount(u.amount); + utxo->mutable_out_point()->set_hash( + std::string(u.revUtxoHash.begin(), u.revUtxoHash.end())); + utxo->mutable_out_point()->set_index(u.index); + utxo->mutable_out_point()->set_sequence(UINT32_MAX); + + ++count; + } + EXPECT_EQ(count, 3); + EXPECT_EQ(signingInput.utxo_size(), 3); + + // Plan + Bitcoin::Proto::TransactionPlan plan; + ANY_PLAN(signingInput, plan, coin); + + // Plan is checked, assume it is accepted + EXPECT_EQ(plan.amount(), 1'200'000); + EXPECT_EQ(plan.fee(), 277); + EXPECT_EQ(plan.change(), 299'723); + ASSERT_EQ(plan.utxos_size(), 3); + // Note that UTXOs happen to be in reverse order compared to the input + EXPECT_EQ(hex(plan.utxos(0).out_point().hash()), hex(revUtxoHash2)); + EXPECT_EQ(hex(plan.utxos(1).out_point().hash()), hex(revUtxoHash1)); + EXPECT_EQ(hex(plan.utxos(2).out_point().hash()), hex(revUtxoHash0)); + + // Extend input with accepted plan + *signingInput.mutable_plan() = plan; + + // Serialize input + const auto txInputData = data(signingInput.SerializeAsString()); + EXPECT_GT(txInputData.size(), 0ul); + + /// Step 2: Obtain preimage hashes + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + TW::Bitcoin::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), (int)preImageHashes.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[0].data_hash()), "505f527f00e15fcc5a2d2416c9970beb57dfdfaca99e572a01f143b24dd8fab6"); + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[1].data_hash()), "a296bead4172007be69b21971a790e076388666c162a9505698415f1b003ebd7"); + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[2].data_hash()), "60ed6e9371e5ddc72fd88e46a12cb2f68516ebd307c0fd31b1b55cf767272101"); + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[0].public_key_hash()), hex(inPubKeyHash1)); + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[1].public_key_hash()), hex(inPubKeyHash0)); + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[2].public_key_hash()), hex(inPubKeyHash0)); + + // Simulate signatures, normally obtained from signature server. + std::vector signatureVec; + std::vector pubkeyVec; + for (const auto& h : preSigningOutput.hash_public_keys()) { + const auto& preImageHash = h.data_hash(); + const auto& pubkeyhash = h.public_key_hash(); + + const std::string key = hex(pubkeyhash) + "+" + hex(preImageHash); + const auto sigInfoFind = signatureInfos.find(key); + ASSERT_TRUE(sigInfoFind != signatureInfos.end()); + const auto& sigInfo = std::get<1>(*sigInfoFind); + const auto& publicKeyData = sigInfo.publicKey; + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1); + const auto signature = sigInfo.signature; + + signatureVec.push_back(signature); + pubkeyVec.push_back(publicKeyData); + + EXPECT_TRUE(publicKey.verifyAsDER(signature, TW::Data(preImageHash.begin(), preImageHash.end()))); + } + + /// Step 3: Compile transaction info + auto compileWithSignatures = + TransactionCompiler::compileWithSignatures(coin, txInputData, signatureVec, pubkeyVec); + + const auto ExpectedTx = + "010000000001036021efcf7555f90627364339fc921139dd40a06ccb2cb2a2a4f8f4ea7a2dc74d0000000000ff" + "ffffffd6892a5aa54e3b8fe430efd23f49a8950733aaa9d7c915d9989179f48dd1905e0100000000ffffffff07" + "c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa80000000000ffffffff02804f1200" + "000000001600145360df8231ac5965147c9d90ca930a2aafb05232cb92040000000000160014bd92088bb7e82d" + "611a9b94fbb74a0908152b784f02473044022041294880caa09bb1b653775310fcdd1458da6b8e7d7fae34e379" + "66414fe115820220646397c9d2513edc5974ecc336e9b287de0cdf071c366f3b3dc3ff309213e4e40121021714" + "2f69535e4dad0dc7060df645c55a174cc1bfa5b9eb2e59aad2ae96072dfc0247304402201857bc6e6e48b46046" + "a4bd204136fc77e24c240943fb5a1f0e86387aae59b34902200a7f31478784e51c49f46ef072745a4f263d7efd" + "bc9c6784aa2571ff4f6f2a400121024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb49" + "3382024730440220764e3d5b3971c4b3e70b23fb700a7462a6fe519d9830e863a1f8388c402ad0b102207e777f" + "7972c636961f92375a2774af3b7a2a04190251bbcb31d19c70927952dc0121024bc2a31265153f07e70e0bab08" + "724e6b85e217f8cd628ceb62974247bb49338200000000"; + { + EXPECT_EQ(compileWithSignatures.size(), 786ul); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(compileWithSignatures.data(), (int)compileWithSignatures.size())); + + EXPECT_EQ(output.encoded().size(), 518ul); + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + Bitcoin::Proto::SigningInput input; + ASSERT_TRUE(input.ParseFromArray(txInputData.data(), (int)txInputData.size())); + + // 2 private keys are needed (despite >2 UTXOs) + auto key0 = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); + auto key1 = parse_hex("7878787878787878787878787878787878787878787878787878787878787878"); + EXPECT_EQ(hex(PrivateKey(key0).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), + hex(inPubKey0)); + EXPECT_EQ(hex(PrivateKey(key1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), + hex(inPubKey1)); + *input.add_private_key() = std::string(key0.begin(), key0.end()); + *input.add_private_key() = std::string(key1.begin(), key1.end()); + + Bitcoin::Proto::SigningOutput output; + ANY_SIGN(input, coin); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: not enough signatures + const Data outputData = TransactionCompiler::compileWithSignatures(coin, txInputData, {signatureVec[0]}, pubkeyVec); + EXPECT_GT(outputData.size(), 1ul); + + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + { // Negative: invalid public key + const auto publicKeyBlake = + parse_hex("b689ab808542e13f3d2ec56fe1efe43a1660dcadc73ce489fde7df98dd8ce5d9"); + EXPECT_EXCEPTION( + TransactionCompiler::compileWithSignatures( + coin, txInputData, signatureVec, {pubkeyVec[0], pubkeyVec[1], publicKeyBlake}), + "Invalid public key"); + } + { // Negative: wrong signature (formally valid) + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, + {parse_hex("415502201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b349022" + "00a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f3b51"), + signatureVec[1], signatureVec[2]}, + pubkeyVec); + EXPECT_EQ(outputData.size(), 2ul); + + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_signing); + } +} diff --git a/tests/chains/Bitcoin/TxComparisonHelper.cpp b/tests/chains/Bitcoin/TxComparisonHelper.cpp index db1f603610e..1e7bec24a4e 100644 --- a/tests/chains/Bitcoin/TxComparisonHelper.cpp +++ b/tests/chains/Bitcoin/TxComparisonHelper.cpp @@ -43,7 +43,7 @@ UTXOs buildTestUTXOs(const std::vector& amounts) { SigningInput buildSigningInput(Amount amount, int byteFee, const UTXOs& utxos, bool useMaxAmount, enum TWCoinType coin, bool omitPrivateKey) { SigningInput input; - input.amount = amount; + input.totalAmount = amount; input.byteFee = byteFee; input.useMaxAmount = useMaxAmount; input.coinType = coin; diff --git a/tests/chains/Bitcoin/TxComparisonHelper.h b/tests/chains/Bitcoin/TxComparisonHelper.h index 85b8c877c53..41f4a602337 100644 --- a/tests/chains/Bitcoin/TxComparisonHelper.h +++ b/tests/chains/Bitcoin/TxComparisonHelper.h @@ -45,14 +45,14 @@ struct EncodedTxSize { bool operator==(const EncodedTxSize& s1, const EncodedTxSize& s2); /// Return the encoded size of the transaction, virtual and non-segwit, etc. -EncodedTxSize getEncodedTxSize(const Transaction& tx); +EncodedTxSize getEncodedTxSize(const TW::Bitcoin::Transaction& tx); /// Validate the previously estimated transaction size (if available) with the actual transaction size. /// Uses segwit byte size (virtual size). Tolerance is estiamte-smaller and estimate-larger, like -1 and 20. /// Returns false on mismatch, and error is printed (stderr). -bool validateEstimatedSize(const Transaction& tx, int smallerTolerance = -1, int biggerTolerance = 20); +bool validateEstimatedSize(const TW::Bitcoin::Transaction& tx, int smallerTolerance = -1, int biggerTolerance = 20); /// Print out a transaction in a nice format, as structured hex strings. -void prettyPrintTransaction(const Transaction& tx, bool useWitnessFormat = true); +void prettyPrintTransaction(const TW::Bitcoin::Transaction& tx, bool useWitnessFormat = true); } // namespace TW::Bitcoin diff --git a/tests/chains/BitcoinDiamond/AddressTests.cpp b/tests/chains/BitcoinDiamond/AddressTests.cpp new file mode 100644 index 00000000000..de5f60aeb6f --- /dev/null +++ b/tests/chains/BitcoinDiamond/AddressTests.cpp @@ -0,0 +1,58 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include + +#include "Coin.h" +#include "HexCoding.h" +#include "HDWallet.h" +#include "Bitcoin/Address.h" +#include "PublicKey.h" +#include "PrivateKey.h" +#include +#include + +using namespace TW; +using namespace TW::Bitcoin; + +TEST(BitcoinDiamondAddress, Valid) { + ASSERT_TRUE(Address::isValid(std::string("1G15VvshDxwFTnahZZECJfFwEkq9fP79o8"))); + ASSERT_TRUE(Address::isValid(std::string("3PaCtJcD7sxpCf3JNPLpMNnHnwJHoBeucF"))); + ASSERT_TRUE(Address::isValid(std::string("395mRH5aV3DLHPXC4Md9cPdpiquLtTqRSX"))); +} + +TEST(BitcoinDiamondAddress, FromPublicKey) { + const auto publicKey = PublicKey(parse_hex("02485a209514cc896f8ed736e205bc4c35bd5299ef3f9e84054475336b964c02a3"), TWPublicKeyTypeSECP256k1); + const auto address = Address(publicKey, TWCoinTypeP2pkhPrefix(TWCoinTypeBitcoinDiamond)); + EXPECT_EQ(address.string(), "1G15VvshDxwFTnahZZECJfFwEkq9fP79o8"); + + auto wallet = HDWallet("shoot island position soft burden budget tooth cruel issue economy destroy above", ""); + auto addr = wallet.deriveAddress(TWCoinTypeBitcoinDiamond); + EXPECT_EQ(addr, "1KaRW9xPPtCTZ9FdaTHduCPck4YvSeEWNn"); + + addr = wallet.deriveAddress(TWCoinTypeBitcoinDiamond, TWDerivationBitcoinSegwit); + EXPECT_EQ(addr, "bcd1q7jh5qukuy9fc2pjm89xnyvx5dtfyvru9evw30x"); +} + +TEST(BitcoinDiamondAddress, FromString) { + auto address = Address("1G15VvshDxwFTnahZZECJfFwEkq9fP79o8"); + ASSERT_EQ(address.string(), "1G15VvshDxwFTnahZZECJfFwEkq9fP79o8"); + + address = Address("3PaCtJcD7sxpCf3JNPLpMNnHnwJHoBeucF"); + ASSERT_EQ(address.string(), "3PaCtJcD7sxpCf3JNPLpMNnHnwJHoBeucF"); +} + +TEST(BitcoinDiamondAddress, AddressData) { + const auto publicKey = PublicKey(parse_hex("02485a209514cc896f8ed736e205bc4c35bd5299ef3f9e84054475336b964c02a3"), TWPublicKeyTypeSECP256k1); + auto address = TW::deriveAddress(TWCoinTypeBitcoinDiamond, publicKey, TWDerivationBitcoinSegwit); + + auto data = TW::addressToData(TWCoinTypeBitcoinDiamond, "1G15VvshDxwFTnahZZECJfFwEkq9fP79o8"); + ASSERT_EQ(hex(data), "a48da46386ce52cccad178de900c71f06130c310"); + ASSERT_EQ(data, TW::addressToData(TWCoinTypeBitcoinDiamond, address)); + + data = TW::addressToData(TWCoinTypeBitcoinDiamond, "1G15VvshDxwFTnahZZECJfFwEkq9fP79"); // invalid address + ASSERT_EQ(data.size(), 0ul); +} diff --git a/tests/chains/BitcoinDiamond/SignerTests.cpp b/tests/chains/BitcoinDiamond/SignerTests.cpp new file mode 100644 index 00000000000..b8543e86070 --- /dev/null +++ b/tests/chains/BitcoinDiamond/SignerTests.cpp @@ -0,0 +1,227 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Bitcoin/TransactionPlan.h" +#include "Bitcoin/TransactionSigner.h" +#include "BitcoinDiamond/Signer.h" +#include "BitcoinDiamond/TransactionBuilder.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" +#include "proto/Bitcoin.pb.h" + +#include +#include + +using namespace TW; +namespace TW::BitcoinDiamond::tests { + +TEST(BitcoinDiamondSigner, Sign) { + const int64_t amount = 17615; + const int64_t fee = 10000; + const std::string toAddress = "1HevQVTSEc8cEpDm65UpfCdj5erd4xxBhx"; + + auto toScript = Bitcoin::Script::lockScriptForAddress(toAddress, TWCoinTypeBitcoinDiamond); + ASSERT_EQ(hex(toScript.bytes), "76a914b6adfbbf15c8f6fa53f1edb37054dce5c7c145c688ac"); + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("1G15VvshDxwFTnahZZECJfFwEkq9fP79o8"); + input.set_coin_type(TWCoinTypeBitcoinDiamond); + + auto txHash0 = parse_hex("1d4653041a1915b3a52d47aeaa119c8f79ed7634a93692a6e811173067464f03"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo0->set_amount(27615); + + auto utxoKey0 = + PrivateKey(parse_hex("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee")); + auto utxoAddr0 = TW::deriveAddress(TWCoinTypeBitcoinDiamond, utxoKey0); + ASSERT_EQ(utxoAddr0, "1G15VvshDxwFTnahZZECJfFwEkq9fP79o8"); + auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, TWCoinTypeBitcoinDiamond); + ASSERT_EQ(hex(script0.bytes), "76a914a48da46386ce52cccad178de900c71f06130c31088ac"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto plan = TransactionBuilder::plan(input); + plan.amount = amount; + plan.fee = fee; + plan.change = 0; + plan.preBlockHash = + parse_hex("4bfa9d92e1e99e72597e1e9162bdaaab624f1bb6faa83b2f46c6777caf8d6699"); + std::reverse(plan.preBlockHash.begin(), plan.preBlockHash.end()); + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan.proto(); + + // Sign + auto result = BitcoinDiamond::Signer::sign(input); + + ASSERT_EQ( + hex(result.encoded()), + "0c00000099668daf7c77c6462f3ba8fab61b4f62abaabd62911e7e59729ee9e1929dfa4b01034f4667301711e8" + "a69236a93476ed798f9c11aaae472da5b315191a0453461d000000006a473044022078e0d3a9e1eb270ab02c15" + "f8fcf1d3bfc95a324839690b7de4f011a4266132ff02204679e8103c4d3f0bb5192a5f53cc273732fd0e8392ab" + "3b00dc708fd24d0160b3012102485a209514cc896f8ed736e205bc4c35bd5299ef3f9e84054475336b964c02a3" + "ffffffff01cf440000000000001976a914b6adfbbf15c8f6fa53f1edb37054dce5c7c145c688ac00000000"); +} + +TEST(BitcoinDiamondSigner, SignSegwit) { + const int64_t amount = 17615; + const int64_t fee = 10000; + const std::string toAddress = "1HevQVTSEc8cEpDm65UpfCdj5erd4xxBhx"; + + auto toScript = Bitcoin::Script::lockScriptForAddress(toAddress, TWCoinTypeBitcoinDiamond); + ASSERT_EQ(hex(toScript.bytes), "76a914b6adfbbf15c8f6fa53f1edb37054dce5c7c145c688ac"); + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("1G15VvshDxwFTnahZZECJfFwEkq9fP79o8"); + input.set_coin_type(TWCoinTypeBitcoinDiamond); + + auto txHash0 = parse_hex("1d4653041a1915b3a52d47aeaa119c8f79ed7634a93692a6e811173067464f03"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo0->set_amount(27615); + + auto utxoKey0 = + PrivateKey(parse_hex("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee")); + auto utxoAddr0 = + TW::deriveAddress(TWCoinTypeBitcoinDiamond, utxoKey0, TWDerivationBitcoinSegwit); + ASSERT_EQ(utxoAddr0, "bcd1q5jx6gcuxeefvejk30r0fqrr37psnpscslrrd0y"); + auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, TWCoinTypeBitcoinDiamond); + ASSERT_EQ(hex(script0.bytes), "0014a48da46386ce52cccad178de900c71f06130c310"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto plan = TransactionBuilder::plan(input); + plan.amount = amount; + plan.fee = fee; + plan.change = 0; + plan.preBlockHash = + parse_hex("4bfa9d92e1e99e72597e1e9162bdaaab624f1bb6faa83b2f46c6777caf8d6699"); + std::reverse(plan.preBlockHash.begin(), plan.preBlockHash.end()); + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan.proto(); + + // Sign + auto result = BitcoinDiamond::Signer::sign(input); + + ASSERT_EQ(hex(result.encoded()), + "0c00000099668daf7c77c6462f3ba8fab61b4f62abaabd62911e7e59729ee9e1929dfa4b000101034f46" + "67301711e8a69236a93476ed798f9c11aaae472da5b315191a0453461d0000000000ffffffff01cf4400" + "00000000001976a914b6adfbbf15c8f6fa53f1edb37054dce5c7c145c688ac02483045022100e9cd497e" + "a0156dbe72bd0e052869c8feb9bea182907023bcc447b98655a5e1080220767e85892df6e4c952bd62b1" + "8c76317f623c5e4f1a8bf4cef6b5e1193e17cf6e012102485a209514cc896f8ed736e205bc4c35bd5299" + "ef3f9e84054475336b964c02a300000000"); +} + +TEST(BitcoinDiamondSigner, SignAnyoneCanPay) { + const int64_t amount = 17615; + const int64_t fee = 10000; + const std::string toAddress = "1HevQVTSEc8cEpDm65UpfCdj5erd4xxBhx"; + + auto toScript = Bitcoin::Script::lockScriptForAddress(toAddress, TWCoinTypeBitcoinDiamond); + ASSERT_EQ(hex(toScript.bytes), "76a914b6adfbbf15c8f6fa53f1edb37054dce5c7c145c688ac"); + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAnyoneCanPay | TWBitcoinSigHashTypeSingle); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("1G15VvshDxwFTnahZZECJfFwEkq9fP79o8"); + input.set_coin_type(TWCoinTypeBitcoinDiamond); + + auto txHash0 = parse_hex("1d4653041a1915b3a52d47aeaa119c8f79ed7634a93692a6e811173067464f03"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo0->set_amount(27615); + + auto utxoKey0 = + PrivateKey(parse_hex("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee")); + auto utxoAddr0 = + TW::deriveAddress(TWCoinTypeBitcoinDiamond, utxoKey0, TWDerivationBitcoinSegwit); + ASSERT_EQ(utxoAddr0, "bcd1q5jx6gcuxeefvejk30r0fqrr37psnpscslrrd0y"); + auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, TWCoinTypeBitcoinDiamond); + ASSERT_EQ(hex(script0.bytes), "0014a48da46386ce52cccad178de900c71f06130c310"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto plan = TransactionBuilder::plan(input); + plan.amount = amount; + plan.fee = fee; + plan.change = 0; + plan.preBlockHash = + parse_hex("4bfa9d92e1e99e72597e1e9162bdaaab624f1bb6faa83b2f46c6777caf8d6699"); + std::reverse(plan.preBlockHash.begin(), plan.preBlockHash.end()); + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan.proto(); + + // Sign + auto result = BitcoinDiamond::Signer::sign(input); + + ASSERT_EQ(hex(result.encoded()), + "0c00000099668daf7c77c6462f3ba8fab61b4f62abaabd62911e7e59729ee9e1929dfa4b000101034f46" + "67301711e8a69236a93476ed798f9c11aaae472da5b315191a0453461d0000000000ffffffff01cf4400" + "00000000001976a914b6adfbbf15c8f6fa53f1edb37054dce5c7c145c688ac02483045022100d659b1e0" + "8f30e133658eca9d97158723b49658bbe28930e361fa274bd11a0b090220587436eaaf3b9397d14af18f" + "a8b4c77a7d7d51bc4733a2821bf03865704966d7832102485a209514cc896f8ed736e205bc4c35bd5299" + "ef3f9e84054475336b964c02a300000000"); +} + +TEST(BitcoinDiamondSigner, SignWithError) { + const int64_t amount = 17615; + const std::string toAddress = "1HevQVTSEc8cEpDm65UpfCdj5erd4xxBhx"; + + auto toScript = Bitcoin::Script::lockScriptForAddress(toAddress, TWCoinTypeBitcoinDiamond); + ASSERT_EQ(hex(toScript.bytes), "76a914b6adfbbf15c8f6fa53f1edb37054dce5c7c145c688ac"); + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("1G15VvshDxwFTnahZZECJfFwEkq9fP79o8"); + input.set_coin_type(TWCoinTypeBitcoinDiamond); + + auto inputData = data(input.SerializeAsString()); + + // PreImageHash + auto preData = TransactionCompiler::preImageHashes(TWCoinTypeBitcoinDiamond, inputData); + + TW::Bitcoin::Proto::PreSigningOutput output; + ASSERT_TRUE(output.ParseFromArray(preData.data(), (int)preData.size())); + + ASSERT_NE(output.error(), Common::Proto::OK); + + // Sign + auto result = BitcoinDiamond::Signer::sign(input); + ASSERT_NE(result.error(), Common::Proto::OK); +} + +} // namespace TW::BitcoinDiamond::tests diff --git a/tests/chains/BitcoinDiamond/TWAnyAddressTests.cpp b/tests/chains/BitcoinDiamond/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..592f555a505 --- /dev/null +++ b/tests/chains/BitcoinDiamond/TWAnyAddressTests.cpp @@ -0,0 +1,37 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TWBitcoinDiamond, Address) { + auto string = STRING("3CDf39adX4mc1AnvDzYHjw2NhxKswAPV3y"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeBitcoinDiamond)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "737cb7c194ec6502be59ed985d66b8bfe8b2b986"); + + string = STRING("1DH9cvKqGgzCvwoap45Nh75qV62wqje9pJ"); + addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeBitcoinDiamond)); + string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "86af5c1d5e754fc8906ec3c5d26e0135e1cb7c85"); + + // segwit addrerss + string = STRING("bcd1q35t4g0ezx9cl5qzac4tl377n9492uztqjwf5wh"); + addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeBitcoinDiamond)); + string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "8d17543f223171fa005dc557f8fbd32d4aae0960"); +} diff --git a/tests/chains/BitcoinDiamond/TWAnySignerTests.cpp b/tests/chains/BitcoinDiamond/TWAnySignerTests.cpp new file mode 100644 index 00000000000..e241fb38bf3 --- /dev/null +++ b/tests/chains/BitcoinDiamond/TWAnySignerTests.cpp @@ -0,0 +1,74 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "proto/Bitcoin.pb.h" +#include "proto/Common.pb.h" + +#include +#include +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + + +TEST(TWAnySignerBitcoinDiamond, Sign) { + const int64_t amount = 17615; + const int64_t fee = 10000; + const std::string toAddress = "1HevQVTSEc8cEpDm65UpfCdj5erd4xxBhx"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("1G15VvshDxwFTnahZZECJfFwEkq9fP79o8"); + input.set_coin_type(TWCoinTypeBitcoinDiamond); + + auto txHash0 = parse_hex("1d4653041a1915b3a52d47aeaa119c8f79ed7634a93692a6e811173067464f03"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo0->set_amount(27615); + + auto script0 = parse_hex("76a914a48da46386ce52cccad178de900c71f06130c31088ac"); + utxo0->set_script(script0.data(), (int)script0.size()); + + auto utxoKey0 = PrivateKey(parse_hex("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee")); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + Bitcoin::Proto::TransactionPlan plan; + { + ANY_PLAN(input, plan, TWCoinTypeBitcoinDiamond); + + plan.set_amount(amount); + plan.set_fee(fee); + plan.set_change(0); + auto preBlockHash = parse_hex("4bfa9d92e1e99e72597e1e9162bdaaab624f1bb6faa83b2f46c6777caf8d6699"); + std::reverse(preBlockHash.begin(), preBlockHash.end()); + plan.set_preblockhash(preBlockHash.data(), (int)preBlockHash.size()); + } + + *input.mutable_plan() = plan; + + Bitcoin::Proto::SigningOutput output; + { + ANY_SIGN(input, TWCoinTypeBitcoinDiamond); + ASSERT_EQ(output.error(), Common::Proto::OK); + } + // Sign + ASSERT_EQ(hex(output.encoded()), "0c00000099668daf7c77c6462f3ba8fab61b4f62abaabd62911e7e59729ee9e1929dfa4b01034f4667301711e8a69236a93476ed798f9c11aaae472da5b315191a0453461d000000006a473044022078e0d3a9e1eb270ab02c15f8fcf1d3bfc95a324839690b7de4f011a4266132ff02204679e8103c4d3f0bb5192a5f53cc273732fd0e8392ab3b00dc708fd24d0160b3012102485a209514cc896f8ed736e205bc4c35bd5299ef3f9e84054475336b964c02a3ffffffff01cf440000000000001976a914b6adfbbf15c8f6fa53f1edb37054dce5c7c145c688ac00000000"); +} diff --git a/tests/chains/BitcoinDiamond/TWCoinTypeTests.cpp b/tests/chains/BitcoinDiamond/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..023dc5b38fa --- /dev/null +++ b/tests/chains/BitcoinDiamond/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWBitcoinDiamondCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeBitcoinDiamond)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("ec564fe8993ba77f3f5c8b7f6ebb4cbc08e564a54612d6f4584cd1017cf723d4")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeBitcoinDiamond, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("1HNTyntGXNhy4WxNzWfffPqp7LHb8bGJ9R")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeBitcoinDiamond, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeBitcoinDiamond)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeBitcoinDiamond)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeBitcoinDiamond), 7); + ASSERT_EQ(TWBlockchainBitcoinDiamond, TWCoinTypeBlockchain(TWCoinTypeBitcoinDiamond)); + ASSERT_EQ(0x5, TWCoinTypeP2shPrefix(TWCoinTypeBitcoinDiamond)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeBitcoinDiamond)); + assertStringsEqual(symbol, "BCD"); + assertStringsEqual(txUrl, "http://explorer.btcd.io/#/tx?tx=ec564fe8993ba77f3f5c8b7f6ebb4cbc08e564a54612d6f4584cd1017cf723d4"); + assertStringsEqual(accUrl, "http://explorer.btcd.io/#/address?address=1HNTyntGXNhy4WxNzWfffPqp7LHb8bGJ9R"); + assertStringsEqual(id, "bitcoindiamond"); + assertStringsEqual(name, "Bitcoin Diamond"); +} diff --git a/tests/chains/BitcoinDiamond/TWSegwitAddressTests.cpp b/tests/chains/BitcoinDiamond/TWSegwitAddressTests.cpp new file mode 100644 index 00000000000..f13078a1595 --- /dev/null +++ b/tests/chains/BitcoinDiamond/TWSegwitAddressTests.cpp @@ -0,0 +1,50 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// + +#include "TestUtilities.h" +#include "Bitcoin/SegwitAddress.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "HexCoding.h" +#include +#include + +using namespace TW; + +TEST(TWBitcoinDiamondSegwitAddress, Valid) { + ASSERT_TRUE(Bitcoin::SegwitAddress::isValid("bcd1q35t4g0ezx9cl5qzac4tl377n9492uztqjwf5wh")); + ASSERT_FALSE(Bitcoin::SegwitAddress::isValid("bcd1q35t4g0ezx9cl5qzac4tl377n9492uztqjwf5ac")); +} + +/// Initializes a Bech32 address with a human-readable part, a witness +/// version, and a witness program. +TEST(TWBitcoinDiamondSegwitAddress, WitnessProgramToAddress) { + auto address = Bitcoin::SegwitAddress("bcd", 0, parse_hex("8d17543f223171fa005dc557f8fbd32d4aae0960")); + + ASSERT_TRUE(Bitcoin::SegwitAddress::isValid(address.string())); + ASSERT_EQ(address.string(), "bcd1q35t4g0ezx9cl5qzac4tl377n9492uztqjwf5wh"); +} + +/// Initializes a Bech32 address with a public key and a HRP prefix. +TEST(TWBitcoinDiamondSegwitAddress, PubkeyToAddress) { + const auto publicKey = PublicKey(parse_hex("032a9ccb9cc6fd461df091b0f711730daa4292f9226aec918ac19381ac2af5e9ee"), TWPublicKeyTypeSECP256k1); + + /// construct with public key + auto address = Bitcoin::SegwitAddress(publicKey, "bcd"); + + ASSERT_TRUE(Bitcoin::SegwitAddress::isValid(address.string())); + ASSERT_EQ(address.string(), "bcd1q35t4g0ezx9cl5qzac4tl377n9492uztqjwf5wh"); +} + +/// Decodes a SegWit address. +TEST(TWBitcoinDiamondSegwitAddress, Decode) { + auto result = Bitcoin::SegwitAddress::decode("bcd1q35t4g0ezx9cl5qzac4tl377n9492uztqjwf5wh"); + + ASSERT_TRUE(std::get<2>(result)); + ASSERT_EQ(std::get<0>(result).string(), "bcd1q35t4g0ezx9cl5qzac4tl377n9492uztqjwf5wh"); + ASSERT_EQ(std::get<1>(result), "bcd"); +} diff --git a/tests/chains/BitcoinDiamond/TransactionCompilerTests.cpp b/tests/chains/BitcoinDiamond/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..d7192e18b91 --- /dev/null +++ b/tests/chains/BitcoinDiamond/TransactionCompilerTests.cpp @@ -0,0 +1,158 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Bitcoin.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include +#include + +#include "Bitcoin/Script.h" +#include "Bitcoin/SegwitAddress.h" +#include "Bitcoin/TransactionInput.h" +#include "Bitcoin/TransactionPlan.h" +#include "Bitcoin/TransactionSigner.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(BitcoinDiamondCompiler, CompileWithSignatures) { + // tx on mainnet + // http://explorer.btcd.io/#/tx?tx=6f8db2317c0940ff97c461e5e9b89692c6c1fded15fb30ae8b9cc2429ce43f66 + + const auto coin = TWCoinTypeBitcoinDiamond; + const int64_t amount = 196007725; + const int64_t fee = 1014; + const std::string toAddress = "39mKL9gxk29f2RiofywHYRDmgXPv1ur8uC"; + + auto toScript = Bitcoin::Script::lockScriptForAddress(toAddress, coin); + ASSERT_EQ(hex(toScript.bytes), "a914589133651fd11901381ecb4d3beef58bc28ba2e787"); + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("15ehpdrZqfZ5rj2e4T4hZKMi3kA8qdSyQu"); + input.set_coin_type(coin); + + auto txHash0 = parse_hex("6ce528c1192a9be648dd8c960695a15454c4c77b5a1dd5c8a5a208e6ae7e0ca8"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo0->set_amount(501008739); + + auto utxoAddr0 = "15ehpdrZqfZ5rj2e4T4hZKMi3kA8qdSyQu"; + auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, coin); + ASSERT_EQ(hex(script0.bytes), "76a9143301f83977102415e34cccd5ca15136a3dba87d588ac"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + EXPECT_EQ(input.utxo_size(), 1); + + // Plan + Bitcoin::Proto::TransactionPlan plan; + ANY_PLAN(input, plan, coin); + + plan.set_amount(amount); + plan.set_fee(fee); + plan.set_change(305000000); + auto preBlockHash = parse_hex("840980d100724999ea20e8b14ddd5ea5e37e2beacb9157a17fe87d0854bc7e6f"); + std::reverse(preBlockHash.begin(), preBlockHash.end()); + plan.set_preblockhash(preBlockHash.data(), (int)preBlockHash.size()); + + // Extend input with accepted plan + *input.mutable_plan() = plan; + + // Serialize input + const auto txInputData = data(input.SerializeAsString()); + EXPECT_GT((int)txInputData.size(), 0); + + /// Step 2: Obtain preimage hashes + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + TW::Bitcoin::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), (int)preImageHashes.size())); + + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[0].data_hash()), + "13565ac8d1d5a8a721417e0391cd13ea1a212b51b9d6bba093babaa203ed9d74"); + + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[0].public_key_hash()), "3301f83977102415e34cccd5ca15136a3dba87d5"); + + auto publicKeyHex = "02f65e76c2a7c239bd6c8b18dc10b71d463b96c0b0d827c97345e6bbe8ee8f2ddc"; + auto publicKey = PublicKey(parse_hex(publicKeyHex), TWPublicKeyTypeSECP256k1); + auto preImageHash = preSigningOutput.hash_public_keys()[0].data_hash(); + auto signature = parse_hex("3045022100e2c048cdf844c77275ac92cc27cfc357155d42d9a82d5d22f62247dce7681467022052c57d744a2ea91970b14e8863efdbcb3fb91f6448c027c25a8e86b752acb5ce"); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE( + publicKey.verifyAsDER(signature, TW::Data(preImageHash.begin(), preImageHash.end()))); + + // Simulate signatures, normally obtained from signature server. + std::vector signatureVec; + std::vector pubkeyVec; + signatureVec.push_back(signature); + pubkeyVec.push_back(publicKey.bytes); + + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, txInputData, signatureVec, pubkeyVec); + + const auto ExpectedTx = + "0c0000006f7ebc54087de87fa15791cbea2b7ee3a55edd4db1e820ea99497200d180098401a80c7eaee608a2a5c8d51d5a7bc7c45454a19506968cdd48e69b2a19c128e56c000000006b483045022100e2c048cdf844c77275ac92cc27cfc357155d42d9a82d5d22f62247dce7681467022052c57d744a2ea91970b14e8863efdbcb3fb91f6448c027c25a8e86b752acb5ce012102f65e76c2a7c239bd6c8b18dc10b71d463b96c0b0d827c97345e6bbe8ee8f2ddcffffffff022dd7ae0b0000000017a914589133651fd11901381ecb4d3beef58bc28ba2e78740ee2d12000000001976a9143301f83977102415e34cccd5ca15136a3dba87d588ac00000000"; + { + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: not enough signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {signature, signature}, pubkeyVec); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {}, {}); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + { // Negative: invalid public key + const auto publicKeyBlake = + parse_hex("b689ab808542e13f3d2ec56fe1efe43a1660dcadc73ce489fde7df98dd8ce5d9"); + EXPECT_EXCEPTION( + TransactionCompiler::compileWithSignatures( + coin, txInputData, signatureVec, {publicKeyBlake}), + "Invalid public key"); + } + { // Negative: wrong signature (formally valid) + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, + {parse_hex("415502201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b349022" + "00a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f3b51")}, + pubkeyVec); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_signing); + } +} diff --git a/tests/chains/BitcoinGold/TWCoinTypeTests.cpp b/tests/chains/BitcoinGold/TWCoinTypeTests.cpp index 25b900369db..aeb8f2221a8 100644 --- a/tests/chains/BitcoinGold/TWCoinTypeTests.cpp +++ b/tests/chains/BitcoinGold/TWCoinTypeTests.cpp @@ -24,8 +24,8 @@ TEST(TWBitcoinGoldCoinType, TWCoinType) { ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeBitcoinGold), 8); ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeBitcoinGold)); - ASSERT_EQ(23, TWCoinTypeP2shPrefix(TWCoinTypeBitcoinGold)); - ASSERT_EQ(0, TWCoinTypeStaticPrefix(TWCoinTypeBitcoinGold)); + ASSERT_EQ(0x17, TWCoinTypeP2shPrefix(TWCoinTypeBitcoinGold)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeBitcoinGold)); assertStringsEqual(symbol, "BTG"); assertStringsEqual(txUrl, "https://explorer.bitcoingold.org/insight/tx/2f807d7734de35d2236a1b3d8704eb12954f5f82ea66987949b10e94d9999b23"); assertStringsEqual(accUrl, "https://explorer.bitcoingold.org/insight/address/GJjz2Du9BoJQ3CPcoyVTHUJZSj62i1693U"); diff --git a/tests/chains/Cardano/AddressTests.cpp b/tests/chains/Cardano/AddressTests.cpp index 428d0d7e156..5ade41f1cc4 100644 --- a/tests/chains/Cardano/AddressTests.cpp +++ b/tests/chains/Cardano/AddressTests.cpp @@ -36,6 +36,7 @@ TEST(CardanoAddress, V3NetworkIdKind) { TEST(CardanoAddress, Validation) { // valid V3 address + ASSERT_TRUE(AddressV3::isValidLegacy("addr1v9wa6entm75duchtu50mu6u6hkagdgqzaevt0cwryaw3pnca870vt")); ASSERT_TRUE(AddressV3::isValidLegacy("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23")); ASSERT_TRUE(AddressV3::isValid("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23")); @@ -97,6 +98,9 @@ TEST(CardanoAddress, FromStringV2) { auto address = AddressV3("DdzFFzCqrhssmYoG5Eca1bKZFdGS8d6iag1mU4wbLeYcSPVvBNF2wRG8yhjzQqErbg63N6KJA4DHqha113tjKDpGEwS5x1dT2KfLSbSJ"); ASSERT_EQ(address.string(), "DdzFFzCqrhssmYoG5Eca1bKZFdGS8d6iag1mU4wbLeYcSPVvBNF2wRG8yhjzQqErbg63N6KJA4DHqha113tjKDpGEwS5x1dT2KfLSbSJ"); } + { + EXPECT_ANY_THROW(new AddressV3("")); + } } TEST(CardanoAddress, FromStringV3_Base) { @@ -156,6 +160,10 @@ TEST(CardanoAddress, MnemonicToAddressV3) { string addr = wallet.deriveAddress(TWCoinType::TWCoinTypeCardano); EXPECT_EQ("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq", addr); } + { + auto addressData = addressToData(TWCoinType::TWCoinTypeCardano, "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23"); + EXPECT_EQ(hex(addressData), "01df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b"); + } { const auto privateKey = wallet.getKey(coin, derivPath); EXPECT_EQ(hex(privateKey.bytes), "e8c8c5b2df13f3abed4e6b1609c808e08ff959d7e6fc3d849e3f2880550b574437aa559095324d78459b9bb2da069da32337e1cc5da78f48e1bd084670107f3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26fae0d152bb611cb9ff34e945e4ff627e6fba81da687a601a879759cd76530b5744424db69a75edd4780a5fbc05d1a3c84ac4166ff8e424808481dd8e77627ce5f5bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276"); @@ -165,7 +173,8 @@ TEST(CardanoAddress, MnemonicToAddressV3) { EXPECT_EQ(address.string(), "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); EXPECT_EQ(address.networkId, AddressV3::Network_Production); EXPECT_EQ(address.kind, AddressV3::Kind_Base); - EXPECT_EQ(hex(address.bytes), "8d98bea0414243dc84070f96265577e7e6cf702d62e871016885034e" "cc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468"); + EXPECT_EQ(hex(address.bytes), "8d98bea0414243dc84070f96265577e7e6cf702d62e871016885034e" + "cc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468"); } { PrivateKey privateKey = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/1852'/1815'/0'/0/0")); @@ -284,10 +293,43 @@ TEST(CardanoAddress, FromDataV3_Base) { } { auto address = AddressV3::createBase(AddressV3::Network_Production, - PublicKey(parse_hex("fafa7eb4146220db67156a03a5f7a79c666df83eb31abbfbe77c85e06d40da31"), TWPublicKeyTypeED25519), - PublicKey(parse_hex("f4b8d5201961e68f2e177ba594101f513ee70fe70a41324e8ea8eb787ffda6f4"), TWPublicKeyTypeED25519)); + PublicKey(parse_hex("fafa7eb4146220db67156a03a5f7a79c666df83eb31abbfbe77c85e06d40da31"), TWPublicKeyTypeED25519), + PublicKey(parse_hex("f4b8d5201961e68f2e177ba594101f513ee70fe70a41324e8ea8eb787ffda6f4"), TWPublicKeyTypeED25519)); EXPECT_EQ(address.string(), "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); } + { + EXPECT_ANY_THROW(AddressV3::createBase(AddressV3::Network_Production, + PublicKey(parse_hex("039997a497d964fc1a62885b05a51166a65a90df00492c8d7cf61d6accf54803be"), TWPublicKeyTypeSECP256k1), + PublicKey(parse_hex("f4b8d5201961e68f2e177ba594101f513ee70fe70a41324e8ea8eb787ffda6f4"), TWPublicKeyTypeED25519))); + } + { + EXPECT_ANY_THROW(AddressV3::createBase(AddressV3::Network_Production, + PublicKey(parse_hex("fafa7eb4146220db67156a03a5f7a79c666df83eb31abbfbe77c85e06d40da31"), TWPublicKeyTypeED25519), + PublicKey(parse_hex("039997a497d964fc1a62885b05a51166a65a90df00492c8d7cf61d6accf54803be"), TWPublicKeyTypeSECP256k1))); + } + { + EXPECT_ANY_THROW(AddressV3::createBase(AddressV3::Network_Production, + parse_hex("039997a497d964fc1a62885b05a51166a65a90df00492c8d7cf61d6accf54803be"), + parse_hex("f4b8d5201961e68f2e177ba594101f513ee70fe70a41324e8ea8eb787ffda6f4"))); + } + { + EXPECT_ANY_THROW(AddressV3::createBase(AddressV3::Network_Production, + parse_hex("fafa7eb4146220db67156a03a5f7a79c666df83eb31abbfbe77c85e06d40da31"), + parse_hex("039997a497d964fc1a62885b05a51166a65a90df00492c8d7cf61d6accf54803be"))); + } +} + +TEST(CardanoAddress, FromPrivateKeyV3) { + { + // from cardano-crypto.js test + auto privateKey = PrivateKey( + parse_hex("d809b1b4b4c74734037f76aace501730a3fe2fca30b5102df99ad3f7c0103e48"), + parse_hex("d54cde47e9041b31f3e6873d700d83f7a937bea746dadfa2c5b0a6a92502356c"), + parse_hex("69272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000"), + dummyKey, dummyKey, dummyKey); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + EXPECT_ANY_THROW(new AddressV3(publicKey)); + } } TEST(CardanoAddress, FromDataV3_Enterprise) { @@ -373,10 +415,10 @@ TEST(CardanoAddress, FromPrivateKeyV2) { ); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); ASSERT_EQ(hex(publicKey.bytes), - "57fd54be7b38bb8952782c2f59aa276928a4dcbb66c8c62ce44f9d623ecd5a03" - "bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4" - "857eed804ff087b97f87848f6493e87257a8c5203cb9f422f6e7a7d8a4d299f3" - "1111111111111111111111111111111111111111111111111111111111111111"); + "57fd54be7b38bb8952782c2f59aa276928a4dcbb66c8c62ce44f9d623ecd5a03" + "bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4" + "857eed804ff087b97f87848f6493e87257a8c5203cb9f422f6e7a7d8a4d299f3" + "1111111111111111111111111111111111111111111111111111111111111111"); auto address = AddressV2(publicKey); ASSERT_EQ(address.string(), "Ae2tdPwUPEZ6RUCnjGHFqi59k5WZLiv3HoCCNGCW8SYc5H9srdTzn1bec4W"); } @@ -390,10 +432,10 @@ TEST(CardanoAddress, FromPrivateKeyV2) { ); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); ASSERT_EQ(hex(publicKey.bytes), - "7cee0f30b9d536a786547dd77b35679b6830e945ffde768eb4f2a061b9dba016" - "e513fa1290da1d22e83a41f17eed72d4489483b561fff36b9555ffdb91c430e2" - "857eed804ff087b97f87848f6493e87257a8c5203cb9f422f6e7a7d8a4d299f3" - "1111111111111111111111111111111111111111111111111111111111111111"); + "7cee0f30b9d536a786547dd77b35679b6830e945ffde768eb4f2a061b9dba016" + "e513fa1290da1d22e83a41f17eed72d4489483b561fff36b9555ffdb91c430e2" + "857eed804ff087b97f87848f6493e87257a8c5203cb9f422f6e7a7d8a4d299f3" + "1111111111111111111111111111111111111111111111111111111111111111"); auto address = AddressV2(publicKey); ASSERT_EQ(address.string(), "Ae2tdPwUPEZJYT9g1JgQWtLveUHavyRxQGi6hVzoQjct7yyCLGgk3pCyx7h"); } @@ -407,13 +449,23 @@ TEST(CardanoAddress, FromPrivateKeyV2) { ); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); ASSERT_EQ(hex(publicKey.bytes), - "e6f04522f875c1563682ca876ddb04c2e2e3ae718e3ff9f11c03dd9f9dccf698" - "69272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000" - "857eed804ff087b97f87848f6493e87257a8c5203cb9f422f6e7a7d8a4d299f3" - "1111111111111111111111111111111111111111111111111111111111111111"); + "e6f04522f875c1563682ca876ddb04c2e2e3ae718e3ff9f11c03dd9f9dccf698" + "69272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000" + "857eed804ff087b97f87848f6493e87257a8c5203cb9f422f6e7a7d8a4d299f3" + "1111111111111111111111111111111111111111111111111111111111111111"); auto address = AddressV2(publicKey); ASSERT_EQ(address.string(), "Ae2tdPwUPEZCxt4UV1Uj2AMMRvg5pYPypqZowVptz3GYpK4pkcvn3EjkuNH"); } + { + // from cardano-crypto.js test + auto privateKey = PrivateKey( + parse_hex("d809b1b4b4c74734037f76aace501730a3fe2fca30b5102df99ad3f7c0103e48"), + parse_hex("d54cde47e9041b31f3e6873d700d83f7a937bea746dadfa2c5b0a6a92502356c"), + parse_hex("69272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000"), + dummyKey, dummyKey, dummyKey); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + EXPECT_ANY_THROW(new AddressV2(publicKey)); + } } TEST(CardanoAddress, PrivateKeyExtended) { @@ -429,8 +481,7 @@ TEST(CardanoAddress, PrivateKeyExtended) { // Non-extended: both are 32 bytes. auto privateKeyNonext = PrivateKey( - parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744") - ); + parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744")); auto publicKeyNonext = privateKeyNonext.getPublicKey(TWPublicKeyTypeED25519); ASSERT_EQ(32ul, publicKeyNonext.bytes.size()); } diff --git a/tests/chains/Cardano/SigningTests.cpp b/tests/chains/Cardano/SigningTests.cpp index 230820144cc..20aa485f02d 100644 --- a/tests/chains/Cardano/SigningTests.cpp +++ b/tests/chains/Cardano/SigningTests.cpp @@ -245,6 +245,88 @@ TEST(CardanoSigning, Plan) { } } +TEST(CardanoSigning, ExtraOutputPlan) { + auto input = createSampleInput(2000000, 10, "addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5", true); + // two output + Proto::TxOutput txOutput; + txOutput.set_address("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5"); + txOutput.set_amount(2000000); + *input.add_extra_outputs() = txOutput; + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.utxos.size(), 1ul); + EXPECT_EQ(plan.availableAmount, 6500000ul); + EXPECT_EQ(plan.amount, 2000000ul); + EXPECT_EQ(plan.fee, 171474ul); + uint64_t extraAmountSum = 0; + for (auto& output: plan.extraOutputs) { + extraAmountSum = extraAmountSum + output.amount; + } + EXPECT_EQ(plan.amount + plan.change + plan.fee + extraAmountSum, plan.availableAmount); + + { + // also test proto fromProto / fromProto + Proto::TxOutput txOutputProto; + txOutputProto.set_address("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5"); + txOutputProto.set_amount(2000000); + + auto* token = txOutputProto.add_token_amount(); + token->set_policy_id(sundaeTokenPolicy); + token->set_asset_name_hex("43554259"); + const auto tokenAmount = store(uint256_t(3000000)); + token->set_amount(tokenAmount.data(), tokenAmount.size()); + + const auto txOutput1 = TxOutput::fromProto(txOutputProto); + EXPECT_EQ(txOutput1.amount, 2000000ul); + const auto toAddress = AddressV3(txOutput1.address); + EXPECT_EQ(toAddress.string(), "addr1v9jxgu33wyunycmdddnh5a3edq6x2dt3xakkuun6wd6hsar8v9uhvee5w9erw7fnvauhswfhw44k673nv3n8sdmj89n82denweckuv34xvmnw6m9xeerq7rt8ymh5aesxaj8zu3e0y6k67tcd3nkzervxfenqer8ddjn27jkkrj"); + EXPECT_EQ(txOutput1.tokenBundle.getByPolicyId(sundaeTokenPolicy)[0].amount, 3000000); + EXPECT_EQ(txOutput1.tokenBundle.getByPolicyId(sundaeTokenPolicy)[0].assetName, "CUBY"); + EXPECT_EQ(txOutput1.tokenBundle.getByPolicyId(sundaeTokenPolicy)[0].policyId, sundaeTokenPolicy); + } + { + // also test proto toProto / toProto + const auto toAddress = AddressV3("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5"); + std::vector tokenAmount; + tokenAmount.emplace_back(sundaeTokenPolicy, "CUBY", 3000000); + const Proto::TxOutput txOutputProto = TxOutput(toAddress.data(), 2000000, TokenBundle(tokenAmount)).toProto(); + EXPECT_EQ(txOutputProto.amount(), 2000000ul); + EXPECT_EQ(txOutputProto.address(), "addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5"); + const auto token = txOutputProto.token_amount(0); + EXPECT_EQ(token.policy_id(), sundaeTokenPolicy); + EXPECT_EQ(token.asset_name(), "CUBY"); + EXPECT_EQ(token.asset_name_hex(), "43554259"); + const auto amount = store(uint256_t(3000000)); + EXPECT_EQ(data(token.amount()), amount); + } + + { + // also test proto toProto / fromProto + const Proto::TransactionPlan planProto = Signer::plan(input); + const auto plan2 = TransactionPlan::fromProto(planProto); + EXPECT_EQ(plan2.amount, 2000000ul); + EXPECT_EQ(plan2.change, 2328526ul); + } +} + +TEST(CardanoSigning, ErrorDoPlan) { + { + // Common::Proto::Error_missing_input_utxos + auto input = createSampleInput(2000000, 0, "addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5", true); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.error, Common::Proto::Error_missing_input_utxos); + } + { + // Common::Proto::Error_low_balance + auto input = createSampleInput(9000000, 1, "addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5", true); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.error, Common::Proto::Error_low_balance); + + } +} + TEST(CardanoSigning, PlanForceFee) { auto requestedAmount = 6500000ul; auto availableAmount = 8000000ul; @@ -601,13 +683,15 @@ TEST(CardanoSigning, SignTransferToken) { // some SUNDAE token, to be transferred auto* token1 = utxo2->add_token_amount(); token1->set_policy_id(sundaeTokenPolicy); - token1->set_asset_name("SUNDAE"); + token1->set_asset_name_hex("53554e444145"); const auto tokenAmount1 = store(uint256_t(80996569)); token1->set_amount(tokenAmount1.data(), tokenAmount1.size()); // some other token, to be preserved auto* token2 = utxo2->add_token_amount(); token2->set_policy_id(sundaeTokenPolicy); token2->set_asset_name("CUBY"); + // This should be ignored! + token2->set_asset_name_hex("00"); const auto tokenAmount2 = store(uint256_t(2000000)); token2->set_amount(tokenAmount2.data(), tokenAmount2.size()); @@ -618,7 +702,7 @@ TEST(CardanoSigning, SignTransferToken) { input.mutable_transfer_message()->set_amount(1500000); auto* toToken = input.mutable_transfer_message()->mutable_token_amount()->add_token(); toToken->set_policy_id(sundaeTokenPolicy); - toToken->set_asset_name("SUNDAE"); + toToken->set_asset_name_hex("53554e444145"); const auto toTokenAmount = store(uint256_t(20000000)); toToken->set_amount(toTokenAmount.data(), toTokenAmount.size()); input.mutable_transfer_message()->set_use_max_amount(false); @@ -681,7 +765,7 @@ TEST(CardanoSigning, SignTransferToken_1dd248) { // some token auto* token3 = utxo1->add_token_amount(); token3->set_policy_id(sundaeTokenPolicy); - token3->set_asset_name("SUNDAE"); + token3->set_asset_name_hex("53554e444145"); const auto tokenAmount3 = store(uint256_t(20000000)); token3->set_amount(tokenAmount3.data(), tokenAmount3.size()); @@ -699,7 +783,7 @@ TEST(CardanoSigning, SignTransferToken_1dd248) { input.mutable_transfer_message()->set_amount(1600000); auto* toToken = input.mutable_transfer_message()->mutable_token_amount()->add_token(); toToken->set_policy_id(sundaeTokenPolicy); - toToken->set_asset_name("SUNDAE"); + toToken->set_asset_name_hex("53554e444145"); const auto toTokenAmount = store(uint256_t(11000000)); toToken->set_amount(toTokenAmount.data(), toTokenAmount.size()); input.mutable_transfer_message()->set_use_max_amount(false); @@ -767,7 +851,7 @@ TEST(CardanoSigning, SignTransferTokenMaxAmount_620b71) { // some token auto* token1 = utxo1->add_token_amount(); token1->set_policy_id(sundaeTokenPolicy); - token1->set_asset_name("SUNDAE"); + token1->set_asset_name_hex("53554e444145"); const auto tokenAmount1 = store(uint256_t(20000000)); token1->set_amount(tokenAmount1.data(), tokenAmount1.size()); @@ -778,7 +862,7 @@ TEST(CardanoSigning, SignTransferTokenMaxAmount_620b71) { input.mutable_transfer_message()->set_amount(666); // doesn't matter, max is used auto* toToken = input.mutable_transfer_message()->mutable_token_amount()->add_token(); toToken->set_policy_id(sundaeTokenPolicy); - toToken->set_asset_name("SUNDAE"); + toToken->set_asset_name_hex("53554e444145"); const auto toTokenAmount = store(uint256_t(666)); // doesn't matter, max is used input.mutable_transfer_message()->set_use_max_amount(true); input.set_ttl(61085916); @@ -827,12 +911,12 @@ TEST(CardanoSigning, SignTransferTwoTokens) { input.mutable_transfer_message()->set_amount(1500000); auto* token1 = input.mutable_transfer_message()->mutable_token_amount()->add_token(); token1->set_policy_id(sundaeTokenPolicy); - token1->set_asset_name("SUNDAE"); + token1->set_asset_name_hex("53554e444145"); const auto tokenAmount1 = store(uint256_t(40000000)); token1->set_amount(tokenAmount1.data(), tokenAmount1.size()); auto* token2 = input.mutable_transfer_message()->mutable_token_amount()->add_token(); token2->set_policy_id(sundaeTokenPolicy); - token2->set_asset_name("CUBY"); + token2->set_asset_name_hex("43554259"); const auto tokenAmount2 = store(uint256_t(2000000)); token2->set_amount(tokenAmount2.data(), tokenAmount2.size()); diff --git a/tests/chains/Cardano/TransactionCompilerTests.cpp b/tests/chains/Cardano/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..b2bc61fd7e0 --- /dev/null +++ b/tests/chains/Cardano/TransactionCompilerTests.cpp @@ -0,0 +1,114 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" +#include "uint256.h" + +#include "proto/Cardano.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(CardanoCompiler, CompileWithSignaturesAndPubKeyType) { + /// Prepare transaction input (protobuf) + const auto coin = TWCoinTypeCardano; + auto input = Cardano::Proto::SigningInput(); + input.mutable_transfer_message()->set_to_address("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + input.mutable_transfer_message()->set_change_address("addr1v8mv75d2evhr4kt048cx7m3f97x363ajadha8xv8dp96nuggpv8rn"); + input.mutable_transfer_message()->set_amount(1850000); + + auto* utxo1 = input.add_utxos(); + utxo1->set_address("addr1v8mv75d2evhr4kt048cx7m3f97x363ajadha8xv8dp96nuggpv8rn"); + utxo1->set_amount(1000000); + auto txHash = parse_hex("d87f6e99c8d3a0fb22b1ea4de477f5a6d1f0e419450c2a194304371cada0ebb9"); + utxo1->mutable_out_point()->set_tx_hash(txHash.data(), txHash.size()); + utxo1->mutable_out_point()->set_output_index(0); + + auto* utxo2 = input.add_utxos(); + utxo2->set_address("addr1v8mv75d2evhr4kt048cx7m3f97x363ajadha8xv8dp96nuggpv8rn"); + utxo2->set_amount(4040957); + utxo2->mutable_out_point()->set_tx_hash(txHash.data(), txHash.size()); + utxo2->mutable_out_point()->set_output_index(1); + + auto* tokenBundle1 = utxo2->add_token_amount(); + tokenBundle1->set_policy_id("122d15a15dc753d2b3ca9ee46c1c6ca41dda38d735942d9d259c785b"); + tokenBundle1->set_asset_name_hex("5454546f6b656e2d31"); + const auto tokenAmount1 = store(uint256_t(3000000)); + tokenBundle1->set_amount(tokenAmount1.data(), tokenAmount1.size()); + + auto* tokenBundle2 = utxo2->add_token_amount(); + tokenBundle2->set_policy_id("122d15a15dc753d2b3ca9ee46c1c6ca41dda38d735942d9d259c785b"); + tokenBundle2->set_asset_name("TTToken-2"); + const auto tokenAmount2 = store(uint256_t(3000000)); + tokenBundle2->set_amount(tokenAmount2.data(), tokenAmount2.size()); + + auto* tokenBundle3 = utxo2->add_token_amount(); + tokenBundle3->set_policy_id("122d15a15dc753d2b3ca9ee46c1c6ca41dda38d735942d9d259c785b"); + tokenBundle3->set_asset_name_hex("5454546f6b656e2d33"); + const auto tokenAmount3 = store(uint256_t(5000000)); + tokenBundle3->set_amount(tokenAmount3.data(), tokenAmount3.size()); + + auto inputData = data(input.SerializeAsString()); + const auto preImageHash = TransactionCompiler::preImageHashes(coin, inputData); + + auto preOut = TxCompiler::Proto::PreSigningOutput(); + preOut.ParseFromArray(preImageHash.data(), (int)preImageHash.size()); + EXPECT_EQ(hex(preOut.data_hash()), "3e5a7c1d1afbc7e3ca783daba1beb12010fc4ecc748722558697509212c9f186"); + + // Simulate signature, normally obtained from signature server + const auto publicKeyData = parse_hex("17c55d712152ccabf28215fe2d008d615f94796e098a97f1aa43d986ac3cb946"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeED25519); + const auto sig = parse_hex("1096ddcfb2ad21a4c0d861ef3fabe18841e8de88105b0d8e36430d7992c588634ead4100c32b2800b31b65e014d54a8238bdda63118d829bf0bcf1b631e86f0e"); + + /// Compile transaction info + const auto expectedTx = + "83a40082825820d87f6e99c8d3a0fb22b1ea4de477f5a6d1f0e419450c2a194304371cada0ebb901" + "825820d87f6e99c8d3a0fb22b1ea4de477f5a6d1f0e419450c2a194304371cada0ebb90001828258" + "39018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cd" + "d9fdb03e10b4e4ac08f5da1fdec6222a34681a001c3a9082581d61f6cf51aacb2e3ad96fa9f06f6e" + "292f8d1d47b2eb6fd39987684ba9f1821a002e0feea1581c122d15a15dc753d2b3ca9ee46c1c6ca4" + "1dda38d735942d9d259c785ba3495454546f6b656e2d311a002dc6c0495454546f6b656e2d321a00" + "2dc6c0495454546f6b656e2d331a004c4b40021a0002a0bf0300a1008182582017c55d712152ccab" + "f28215fe2d008d615f94796e098a97f1aa43d986ac3cb94658401096ddcfb2ad21a4c0d861ef3fab" + "e18841e8de88105b0d8e36430d7992c588634ead4100c32b2800b31b65e014d54a8238bdda63118d" + "829bf0bcf1b631e86f0ef6"; + const Data outData = TransactionCompiler::compileWithSignaturesAndPubKeyType(coin, inputData, {sig}, {publicKeyData}, TWPublicKeyTypeED25519); + + { + auto output = Cardano::Proto::SigningOutput(); + output.ParseFromArray(outData.data(), (int)outData.size()); + + EXPECT_EQ(hex(output.encoded()), expectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + const Data outputData = TransactionCompiler::compileWithSignaturesAndPubKeyType( + coin, inputData, {sig, sig}, {publicKeyData}, TWPublicKeyTypeED25519); + Cardano::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + const Data outputData = TransactionCompiler::compileWithSignaturesAndPubKeyType( + coin, inputData, {}, {}, TWPublicKeyTypeED25519); + Cardano::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} diff --git a/tests/chains/Cardano/TransactionTests.cpp b/tests/chains/Cardano/TransactionTests.cpp index eb92b409773..599b3d4119b 100644 --- a/tests/chains/Cardano/TransactionTests.cpp +++ b/tests/chains/Cardano/TransactionTests.cpp @@ -61,7 +61,8 @@ TEST(CardanoTransaction, minAdaAmount) { const auto tb = TokenBundle(); EXPECT_EQ(tb.minAdaAmount(), 1000000ul); } - { // 1 policyId, 1 6-char asset name + + { // 1 policyId, 1 6-char asset name const auto tb = TokenBundle({TokenAmount(policyId, "TOKEN1", 0)}); EXPECT_EQ(tb.minAdaAmount(), 1444443ul); } @@ -74,7 +75,7 @@ TEST(CardanoTransaction, minAdaAmount) { { // 10 policyId, 10 6-char asset names auto tb = TokenBundle(); for (auto i = 0; i < 10; ++i) { - std::string policyId1 = +"012345678901234567890123456" + std::to_string(i); + std::string policyId1 = + "012345678901234567890123456" + std::to_string(i); std::string name = "ASSET" + std::to_string(i); tb.add(TokenAmount(policyId1, name, 0)); } diff --git a/tests/chains/Celo/TWCoinTypeTests.cpp b/tests/chains/Celo/TWCoinTypeTests.cpp index 9ee45657c5b..3a87c947e8e 100644 --- a/tests/chains/Celo/TWCoinTypeTests.cpp +++ b/tests/chains/Celo/TWCoinTypeTests.cpp @@ -4,6 +4,9 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. // +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// #include "TestUtilities.h" #include @@ -21,7 +24,8 @@ TEST(TWCeloCoinType, TWCoinType) { ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeCelo), 18); ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeCelo)); - + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeCelo)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeCelo)); assertStringsEqual(symbol, "CELO"); assertStringsEqual(txUrl, "https://explorer.celo.org/tx/0xaf41ee58afe633dc7b179c15693cca40fe0372c1d7c70351620105d59d326693"); assertStringsEqual(accUrl, "https://explorer.celo.org/address/0xFBFf95e2Fa7e4Ff3aeA34eFB05fB60F9968a6aaD"); diff --git a/tests/chains/Cosmos/CryptoOrg/TWAnySignerTests.cpp b/tests/chains/Cosmos/CryptoOrg/TWAnySignerTests.cpp index ad38fd921ed..f3db43120b7 100644 --- a/tests/chains/Cosmos/CryptoOrg/TWAnySignerTests.cpp +++ b/tests/chains/Cosmos/CryptoOrg/TWAnySignerTests.cpp @@ -52,7 +52,7 @@ TEST(TWAnySignerCryptoorg, SignTx_Proto_BCB213) { assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"CpABCo0BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm0KKmNybzFjdHd0Y3dwZ2tza3k5ODhkaHRoNmpzbHh2ZXVtZ3UwZDQ1emdmMBIqY3JvMXhwYWh5NmM3d2xkeGFjdjZsZDk5aDQzNW1odmZuc3VwMjR2Y3VzGhMKB2Jhc2Vjcm8SCDUwMDAwMDAwEmkKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQOIMbBhYj5+i+WdiIxxCEpFyNCJMHy/XsVdxiwde9Vr4xIECgIIARgCEhUKDwoHYmFzZWNybxIENTAwMBDAmgwaQAcxK9xk6r69gmz+1UWaCnYxNuXPXZdp59YcqKPJE5d6fp+IICTBOwd2rs8MiApcf8kNSrbZ6oECxcGQAdxF0SI=\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "07312bdc64eabebd826cfed5459a0a763136e5cf5d9769e7d61ca8a3c913977a7e9f882024c13b0776aecf0c880a5c7fc90d4ab6d9ea8102c5c19001dc45d122"); EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); } TEST(TWAnySignerCryptoorg, SignTx_Json_DDCCE4) { @@ -125,7 +125,7 @@ TEST(TWAnySignerCryptoorg, SignTx_Json_DDCCE4) { )"); EXPECT_EQ(hex(output.signature()), "e7ee6b485160d0513d713925416407364b410c9b33ed40a7312805d2dd3e81872b2211165324ed89b5da1d941b28001a7222750641d7611123539e55b3007256"); EXPECT_EQ(output.serialized(), ""); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); /// https://crypto.org/explorer/tx/DDCCE4052040B05914CADEFE78C0A75BE363AE39504E7EF6B2EDB8A9072AD44B /// curl -H 'Content-Type: application/json' --data-binary '{"mode":"block","tx":{"fee": ... }}' https://mainnet.crypto.org:1317/txs diff --git a/tests/chains/Cosmos/Juno/TWAnySignerTests.cpp b/tests/chains/Cosmos/Juno/TWAnySignerTests.cpp index 138fc8ad36f..13444945f39 100644 --- a/tests/chains/Cosmos/Juno/TWAnySignerTests.cpp +++ b/tests/chains/Cosmos/Juno/TWAnySignerTests.cpp @@ -50,7 +50,7 @@ TEST(TWAnySignerJuno, Sign) { assertJSONEqual(output.serialized(), expectedJson); EXPECT_EQ(hex(output.signature()), "06b03649436dbabd57a80233363338518b45ca52804647cc776609522d75aaa3245f4ad97e61eb10b2fe42a8c4467d28defb11db7d5f98f1897b83f422c8f08f"); EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); } } // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/Neutron/TWAnySignerTests.cpp b/tests/chains/Cosmos/Neutron/TWAnySignerTests.cpp index 0724f191629..349a932d872 100644 --- a/tests/chains/Cosmos/Neutron/TWAnySignerTests.cpp +++ b/tests/chains/Cosmos/Neutron/TWAnySignerTests.cpp @@ -52,7 +52,7 @@ TEST(TWAnySignerNeutron, SignAirdropNeutron) { assertJSONEqual(output.serialized(), expectedJson); EXPECT_EQ(hex(output.signature()), "c2045c2f33caeb975ae21e5fb759041cf13239a178e3f538d8ad06b5597709ae414802966c2139d5b427045ef022793542aab436198ca1403ca184590daf783b"); EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); } TEST(TWAnySignerNeutron, SignWithdrawAirdropNeutron) { @@ -93,7 +93,7 @@ TEST(TWAnySignerNeutron, SignWithdrawAirdropNeutron) { assertJSONEqual(output.serialized(), expectedJson); EXPECT_EQ(hex(output.signature()), "dff3cc5c830b68bf96f414349f58db8b16943b00c76a5afa9eca3c0222c9af9336bb15ae05bd8eaf37822f0e3e14bffbfe3b84f1fc2d8391e354e28d69cb8030"); EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); } } // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/Osmosis/SignerTests.cpp b/tests/chains/Cosmos/Osmosis/SignerTests.cpp index ad6e3f255a4..d7935f190de 100644 --- a/tests/chains/Cosmos/Osmosis/SignerTests.cpp +++ b/tests/chains/Cosmos/Osmosis/SignerTests.cpp @@ -52,8 +52,7 @@ TEST(OsmosisSigner, SignTransfer_81B4) { assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"Co0BCooBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmoKK29zbW8xbWt5Njljbjhla3R3eTA4NDV2ZWM5dXBzZHBoa3R4dDBlbjk3ZjUSK29zbW8xOHMwaGRuc2xsZ2NjbHdldTlheW13NG5na3RyMmswcmt2bjdqbW4aDgoFdW9zbW8SBTk5ODAwEmQKTgpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQLs71zkN6MCxn+VRo3ksx826RH0Z9fmpStBweE+HVY2SRIECgIIARISCgwKBXVvc21vEgMyMDAQwJoMGkAMY//Md5GRUR4lVZhk558hFS3kii9QZYoYKfg4+ac/xgNeyoiEweVDhcmEvlH1orVwjLUOnYs4ly2a/yIurYVj\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "0c63ffcc779191511e25559864e79f21152de48a2f50658a1829f838f9a73fc6035eca8884c1e54385c984be51f5a2b5708cb50e9d8b38972d9aff222ead8563"); - EXPECT_EQ(output.error(), ""); - EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error_message(), ""); } } // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/Osmosis/TWAnySignerTests.cpp b/tests/chains/Cosmos/Osmosis/TWAnySignerTests.cpp index 74ae63a5000..a4cb692aacc 100644 --- a/tests/chains/Cosmos/Osmosis/TWAnySignerTests.cpp +++ b/tests/chains/Cosmos/Osmosis/TWAnySignerTests.cpp @@ -50,7 +50,6 @@ TEST(TWAnySignerOsmosis, Sign) { assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"Co0BCooBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmoKK29zbW8xbWt5Njljbjhla3R3eTA4NDV2ZWM5dXBzZHBoa3R4dDBlbjk3ZjUSK29zbW8xOHMwaGRuc2xsZ2NjbHdldTlheW13NG5na3RyMmswcmt2bjdqbW4aDgoFdW9zbW8SBTk5ODAwEmQKTgpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQLs71zkN6MCxn+VRo3ksx826RH0Z9fmpStBweE+HVY2SRIECgIIARISCgwKBXVvc21vEgMyMDAQwJoMGkAMY//Md5GRUR4lVZhk558hFS3kii9QZYoYKfg4+ac/xgNeyoiEweVDhcmEvlH1orVwjLUOnYs4ly2a/yIurYVj\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "0c63ffcc779191511e25559864e79f21152de48a2f50658a1829f838f9a73fc6035eca8884c1e54385c984be51f5a2b5708cb50e9d8b38972d9aff222ead8563"); EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); } } // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/ProtobufTests.cpp b/tests/chains/Cosmos/ProtobufTests.cpp index a23dd4da33b..009bf227afd 100644 --- a/tests/chains/Cosmos/ProtobufTests.cpp +++ b/tests/chains/Cosmos/ProtobufTests.cpp @@ -9,6 +9,7 @@ #include "Cosmos/Protobuf/authz_tx.pb.h" #include "Cosmos/Protobuf/bank_tx.pb.h" #include "Cosmos/Protobuf/tx.pb.h" +#include "Cosmos/ProtobufSerialization.h" #include "Data.h" #include "HexCoding.h" @@ -46,6 +47,31 @@ TEST(CosmosProtobuf, SendMsg) { assertJSONEqual(json, R"({"messages":[{"@type":"type.googleapis.com/cosmos.bank.v1beta1.MsgSend","amount":[{"amount":"1","denom":"muon"}],"fromAddress":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","toAddress":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}]})"); } +TEST(CosmosProtobuf, ExecuteContractMsg) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::JSON); // obsolete + input.set_account_number(1037); + input.set_chain_id("gaia-13003"); + input.set_memo(""); + input.set_sequence(8); + + auto fromAddress = Address("cosmos", parse_hex("BC2DA90C84049370D1B7C528BC164BC588833F21")); + auto toAddress = Address("cosmos", parse_hex("12E8FE8B81ECC1F4F774EA6EC8DF267138B9F2D9")); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_execute_contract_message(); + message.set_sender(fromAddress.string()); + message.set_contract(toAddress.string()); + message.set_execute_msg("transfer"); + auto* coin = message.add_coins(); + coin->set_denom("muon"); + coin->set_amount("1"); + + auto body = Protobuf::buildProtoTxBody(input); + + EXPECT_EQ(hex(body), "0a9d010a262f74657272612e7761736d2e763162657461312e4d736745786563757465436f6e747261637412730a2d636f736d6f733168736b366a727979716a6668703564686335357463396a74636b796778306570683664643032122d636f736d6f73317a743530617a7570616e716c66616d356166687633686578777975746e756b656834633537331a087472616e736665722a090a046d756f6e120131"); +} + TEST(CosmosProtobuf, DeterministicSerialization_Article) { // https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-027-deterministic-protobuf-serialization.md auto article = blog::Article(); diff --git a/tests/chains/Cosmos/Secret/SignerTests.cpp b/tests/chains/Cosmos/Secret/SignerTests.cpp index 6980806f46c..997ab2d3886 100644 --- a/tests/chains/Cosmos/Secret/SignerTests.cpp +++ b/tests/chains/Cosmos/Secret/SignerTests.cpp @@ -52,7 +52,7 @@ TEST(SecretSigner, Sign) { assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"CpIBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLXNlY3JldDE4bWRyamE0MGdmdWZ0dDV5eDZ0Z2owZm41bHVycGxlenlwODk0eRItc2VjcmV0MXJucTZoamZuYWx4ZWVmODdybWRleWEzbnU5ZGhwYzdrOXB1anMzGg8KBXVzY3J0EgYxMDAwMDASZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAkZqxdKMtPq2w0kGDGwWGejTAed0H7azPMHtrCX0XYZGEgQKAggBGAESEwoNCgV1c2NydBIEMjUwMBCowwEaQOcHd2gHpa5WKZ/5RRerEtrHlyDlojIEzUGhC9xMFgs7UQMWy+kTTN+NRf7zQ8rx3cPkIKeZhv0u1KRc8uRCc4o=\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "e707776807a5ae56299ff94517ab12dac79720e5a23204cd41a10bdc4c160b3b510316cbe9134cdf8d45fef343caf1ddc3e420a79986fd2ed4a45cf2e442738a"); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); EXPECT_EQ(output.json(), ""); } diff --git a/tests/chains/Cosmos/Secret/TWAnySignerTests.cpp b/tests/chains/Cosmos/Secret/TWAnySignerTests.cpp index bded5786005..9abb485b2d3 100644 --- a/tests/chains/Cosmos/Secret/TWAnySignerTests.cpp +++ b/tests/chains/Cosmos/Secret/TWAnySignerTests.cpp @@ -50,7 +50,7 @@ TEST(TWAnySignerSecret, Sign) { assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"CpIBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLXNlY3JldDE4bWRyamE0MGdmdWZ0dDV5eDZ0Z2owZm41bHVycGxlenlwODk0eRItc2VjcmV0MXJucTZoamZuYWx4ZWVmODdybWRleWEzbnU5ZGhwYzdrOXB1anMzGg8KBXVzY3J0EgYxMDAwMDASZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAkZqxdKMtPq2w0kGDGwWGejTAed0H7azPMHtrCX0XYZGEgQKAggBGAESEwoNCgV1c2NydBIEMjUwMBCowwEaQOcHd2gHpa5WKZ/5RRerEtrHlyDlojIEzUGhC9xMFgs7UQMWy+kTTN+NRf7zQ8rx3cPkIKeZhv0u1KRc8uRCc4o=\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "e707776807a5ae56299ff94517ab12dac79720e5a23204cd41a10bdc4c160b3b510316cbe9134cdf8d45fef343caf1ddc3e420a79986fd2ed4a45cf2e442738a"); EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); } } // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/SignerTests.cpp b/tests/chains/Cosmos/SignerTests.cpp index 22e5d64dce1..dc45bc5cd1c 100644 --- a/tests/chains/Cosmos/SignerTests.cpp +++ b/tests/chains/Cosmos/SignerTests.cpp @@ -12,6 +12,7 @@ #include "Cosmos/Signer.h" #include "TestUtilities.h" #include "Cosmos/Protobuf/bank_tx.pb.h" +#include "Cosmos/Protobuf/coin.pb.h" #include #include @@ -55,8 +56,6 @@ TEST(CosmosSigner, SignTxProtobuf) { assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "f9e1f4001657a42009c4eb6859625d2e41e961fc72efd2842909c898e439fc1f549916e4ecac676ee353c7d54c5ae30a29b4210b8bff0ebfdcb375e105002f47"); - EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); } TEST(CosmosSigner, SignProtobuf_ErrorMissingMessage) { @@ -81,7 +80,7 @@ TEST(CosmosSigner, SignProtobuf_ErrorMissingMessage) { auto output = Signer::sign(input, TWCoinTypeCosmos); - EXPECT_EQ(output.error(), "Error: No message found"); + EXPECT_EQ(output.error_message(), "Error: No message found"); EXPECT_EQ(output.serialized(), ""); EXPECT_EQ(output.json(), ""); EXPECT_EQ(hex(output.signature()), ""); @@ -127,8 +126,84 @@ TEST(CosmosSigner, SignTxJson) { EXPECT_EQ(R"({"mode":"block","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"muon"}],"from_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","to_address":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"/D74mdIGyIB3/sQvIboLTfS9P9EV/fYGrgHZE2/vNj9X6eM6e57G3atljNB+PABnRw3pTk51uXmhCFop8O/ZJg=="}]}})", output.json()); EXPECT_EQ(hex(output.signature()), "fc3ef899d206c88077fec42f21ba0b4df4bd3fd115fdf606ae01d9136fef363f57e9e33a7b9ec6ddab658cd07e3c0067470de94e4e75b979a1085a29f0efd926"); - EXPECT_EQ(output.serialized(), ""); - EXPECT_EQ(output.error(), ""); +} + +TEST(CosmosSigner, SignTxJsonWithExecuteContractMsg) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::JSON); // obsolete + input.set_account_number(1037); + input.set_chain_id("gaia-13003"); + input.set_memo(""); + input.set_sequence(8); + + auto fromAddress = Address("cosmos", parse_hex("BC2DA90C84049370D1B7C528BC164BC588833F21")); + auto toAddress = Address("cosmos", parse_hex("12E8FE8B81ECC1F4F774EA6EC8DF267138B9F2D9")); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_execute_contract_message(); + message.set_sender(fromAddress.string()); + message.set_contract(toAddress.string()); + message.set_execute_msg("transfer"); + auto* coin = message.add_coins(); + coin->set_denom("muon"); + coin->set_amount("1"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("muon"); + amountOfFee->set_amount("200"); + + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + + EXPECT_EQ("{\"accountNumber\":\"1037\",\"chainId\":\"gaia-13003\",\"fee\":{\"amounts\":[{\"denom\":\"muon\",\"amount\":\"200\"}],\"gas\":\"200000\"},\"sequence\":\"8\",\"messages\":[{\"executeContractMessage\":{\"sender\":\"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02\",\"contract\":\"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573\",\"executeMsg\":\"transfer\",\"coins\":[{\"denom\":\"muon\",\"amount\":\"1\"}]}}]}", json); + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeCosmos); + + EXPECT_EQ("{\"mode\":\"block\",\"tx\":{\"fee\":{\"amount\":[{\"amount\":\"200\",\"denom\":\"muon\"}],\"gas\":\"200000\"},\"memo\":\"\",\"msg\":[{\"type\":\"wasm/MsgExecuteContract\",\"value\":{\"coins\":[{\"amount\":\"1\",\"denom\":\"muon\"}],\"contract\":\"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573\",\"execute_msg\":\"transfer\",\"sender\":\"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02\"}}],\"signatures\":[{\"pub_key\":{\"type\":\"tendermint/PubKeySecp256k1\",\"value\":\"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F\"},\"signature\":\"9iQTB1Jjw8FuYPwgzVLbs1cABGYFlk3JRKGQyojQcwY/ni+9D/ViNQMb+4UuokYi74GnpPZpH5RqbJ2ju6VL2g==\"}]}}", output.json()); + + EXPECT_EQ(hex(output.signature()), "f62413075263c3c16e60fc20cd52dbb35700046605964dc944a190ca88d073063f9e2fbd0ff56235031bfb852ea24622ef81a7a4f6691f946a6c9da3bba54bda"); +} + +TEST(CosmosSigner, SignTxJsonWithRawJSONMsg) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::JSON); // obsolete + input.set_account_number(1037); + input.set_chain_id("gaia-13003"); + input.set_memo(""); + input.set_sequence(8); + + auto fromAddress = Address("cosmos", parse_hex("BC2DA90C84049370D1B7C528BC164BC588833F21")); + auto toAddress = Address("cosmos", parse_hex("12E8FE8B81ECC1F4F774EA6EC8DF267138B9F2D9")); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_raw_json_message(); + message.set_type("test"); + message.set_value("{\"test\":\"hello\"}"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("muon"); + amountOfFee->set_amount("200"); + + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + + EXPECT_EQ(json, "{\"accountNumber\":\"1037\",\"chainId\":\"gaia-13003\",\"fee\":{\"amounts\":[{\"denom\":\"muon\",\"amount\":\"200\"}],\"gas\":\"200000\"},\"sequence\":\"8\",\"messages\":[{\"rawJsonMessage\":{\"type\":\"test\",\"value\":\"{\\\"test\\\":\\\"hello\\\"}\"}}]}"); + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeCosmos); + + EXPECT_EQ(output.json(), "{\"mode\":\"block\",\"tx\":{\"fee\":{\"amount\":[{\"amount\":\"200\",\"denom\":\"muon\"}],\"gas\":\"200000\"},\"memo\":\"\",\"msg\":[{\"type\":\"test\",\"value\":{\"test\":\"hello\"}}],\"signatures\":[{\"pub_key\":{\"type\":\"tendermint/PubKeySecp256k1\",\"value\":\"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F\"},\"signature\":\"qhxxCOMiVhP7e7Mx+98HUZI0t5DNOFXwzIqNQz+fT6hDKR/ebW0uocsYnE5CiBNEalmBcs5gSIJegNkHhgyEmA==\"}]}}"); + + EXPECT_EQ(hex(output.signature()), "aa1c7108e3225613fb7bb331fbdf07519234b790cd3855f0cc8a8d433f9f4fa843291fde6d6d2ea1cb189c4e428813446a598172ce6048825e80d907860c8498"); } TEST(CosmosSigner, SignTxJson_WithMode) { @@ -163,13 +238,13 @@ TEST(CosmosSigner, SignTxJson_WithMode) { { auto output = Signer::sign(input, TWCoinTypeCosmos); EXPECT_EQ(R"({"mode":"async","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"muon"}],"from_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","to_address":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"/D74mdIGyIB3/sQvIboLTfS9P9EV/fYGrgHZE2/vNj9X6eM6e57G3atljNB+PABnRw3pTk51uXmhCFop8O/ZJg=="}]}})", output.json()); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); } input.set_mode(Proto::BroadcastMode::SYNC); { auto output = Signer::sign(input, TWCoinTypeCosmos); EXPECT_EQ(R"({"mode":"sync","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"muon"}],"from_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","to_address":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"/D74mdIGyIB3/sQvIboLTfS9P9EV/fYGrgHZE2/vNj9X6eM6e57G3atljNB+PABnRw3pTk51uXmhCFop8O/ZJg=="}]}})", output.json()); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); } } @@ -214,7 +289,7 @@ TEST(CosmosSigner, SignIbcTransferProtobuf_817101) { assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"Cr4BCrsBCikvaWJjLmFwcGxpY2F0aW9ucy50cmFuc2Zlci52MS5Nc2dUcmFuc2ZlchKNAQoIdHJhbnNmZXISC2NoYW5uZWwtMTQxGg8KBXVhdG9tEgYxMDAwMDAiLWNvc21vczFta3k2OWNuOGVrdHd5MDg0NXZlYzl1cHNkcGhrdHh0MDNna3dseCorb3NtbzE4czBoZG5zbGxnY2Nsd2V1OWF5bXc0bmdrdHIyazBya3ZuN2ptbjIHCAEQgI6ZBBJoClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEC7O9c5DejAsZ/lUaN5LMfNukR9GfX5qUrQcHhPh1WNkkSBAoCCAEYAhIUCg4KBXVhdG9tEgUxMjUwMBCgwh4aQK0HIWdFMk+C6Gi1KG/vELe1ffcc1aEWUIqz2t/ZhwqNNHxUUSp27wteiugHEMVTEIOBhs84t2gIcT/nD/1yKOU=\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "ad07216745324f82e868b5286fef10b7b57df71cd5a116508ab3dadfd9870a8d347c54512a76ef0b5e8ae80710c55310838186cf38b76808713fe70ffd7228e5"); EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); } TEST(CosmosSigner, SignDirect1) { @@ -244,7 +319,7 @@ TEST(CosmosSigner, SignDirect1) { assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "f9e1f4001657a42009c4eb6859625d2e41e961fc72efd2842909c898e439fc1f549916e4ecac676ee353c7d54c5ae30a29b4210b8bff0ebfdcb375e105002f47"); EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); } TEST(CosmosSigner, SignDirect_0a90010a) { @@ -289,7 +364,7 @@ TEST(CosmosSigner, SignDirect_0a90010a) { assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"CpMBCpABChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEnAKLWNvc21vczFwa3B0cmU3ZmRrbDZnZnJ6bGVzamp2aHhobGMzcjRnbW1rOHJzNhItY29zbW9zMXF5cHF4cHE5cWNyc3N6ZzJwdnhxNnJzMHpxZzN5eWM1bHp2N3h1GhAKBXVjb3NtEgcxMjM0NTY3EiEKCgoAEgQKAggBGAESEwoNCgV1Y29zbRIEMjAwMBDAmgwaQEgXmSAlm4M5bz+OX1GtvvZ3fBV2wrZrp4A/Imd55KM7ASivB/siYJegmYiOKzQ82uwoEmFalNnG2BrHHDwDR2Y=\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "48179920259b83396f3f8e5f51adbef6777c1576c2b66ba7803f226779e4a33b0128af07fb226097a099888e2b343cdaec2812615a94d9c6d81ac71c3c034766"); EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); } TEST(CosmosSigner, MsgVote) { diff --git a/tests/chains/Cosmos/StakingTests.cpp b/tests/chains/Cosmos/StakingTests.cpp index c409e5c4ad2..91e097e0342 100644 --- a/tests/chains/Cosmos/StakingTests.cpp +++ b/tests/chains/Cosmos/StakingTests.cpp @@ -113,14 +113,14 @@ TEST(CosmosStaking, Staking) { assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"CpsBCpgBCiMvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dEZWxlZ2F0ZRJxCi1jb3Ntb3MxaHNrNmpyeXlxamZocDVkaGM1NXRjOWp0Y2t5Z3gwZXBoNmRkMDISNGNvc21vc3ZhbG9wZXIxemt1cHI4M2hyemtuM3VwNWVsa3R6Y3EzdHVmdDhueHNtd2RxZ3AaCgoEbXVvbhICMTASZgpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAcSEgoMCgRtdW9uEgQxMDE4ENmaBhpA8O9Jm/kL6Za2I3poDs5vpMowYJgNvYCJBRU/vxAjs0lNZYsq40qpTbwOTbORjJA5UjQ6auc40v6uCFT4q4z+uA==\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "f0ef499bf90be996b6237a680ece6fa4ca3060980dbd808905153fbf1023b3494d658b2ae34aa94dbc0e4db3918c903952343a6ae738d2feae0854f8ab8cfeb8"); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); { // Json-serialization, for coverage (to be removed later) input.set_signing_mode(Proto::JSON); - auto signingOutput = Signer::sign(input, TWCoinTypeCosmos); - ASSERT_EQ(hex(signingOutput.signature()), "c08bdf6c2b0b4428f37975e85d329f1cb19745b000994a743b5df81d57d573aa5f755349befcc848c1d1507818723b1288594bc91df685e89aff22e0303b4861"); - EXPECT_EQ(signingOutput.error(), ""); - EXPECT_EQ(hex(signingOutput.serialized()), ""); + output = Signer::sign(input, TWCoinTypeCosmos); + ASSERT_EQ(hex(output.signature()), "c08bdf6c2b0b4428f37975e85d329f1cb19745b000994a743b5df81d57d573aa5f755349befcc848c1d1507818723b1288594bc91df685e89aff22e0303b4861"); + EXPECT_EQ(output.error_message(), ""); + EXPECT_EQ(hex(output.serialized()), ""); } } @@ -153,14 +153,14 @@ TEST(CosmosStaking, Unstaking) { assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"Cp0BCpoBCiUvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dVbmRlbGVnYXRlEnEKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhI0Y29zbW9zdmFsb3BlcjF6a3VwcjgzaHJ6a24zdXA1ZWxrdHpjcTN0dWZ0OG54c213ZHFncBoKCgRtdW9uEgIxMBJmClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECVyhuw/N9M1V7u6oACyd0SskCOqmWfK51oYHR/5H6ncUSBAoCCAEYBxISCgwKBG11b24SBDEwMTgQ2ZoGGkBhlxHFnjBERxLtjLbMCKXcrDctaSZ9djtWCa3ely1bpV6m+6aAFjpr8aEZH+q2AtjJSEdgpQRJxP+9/gQsRTnZ\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "619711c59e30444712ed8cb6cc08a5dcac372d69267d763b5609adde972d5ba55ea6fba680163a6bf1a1191feab602d8c9484760a50449c4ffbdfe042c4539d9"); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); { // Json-serialization, for coverage (to be removed later) input.set_signing_mode(Proto::JSON); - auto signingOutput = Signer::sign(input, TWCoinTypeCosmos); - ASSERT_EQ(hex(signingOutput.signature()), "8f85a9515a211881daebfb346c2beeca3ab5c2d406a9b3ad402cfddaa3d08e2b13378e13cfef8ecf1d6500fe85d0ce3e793034dd77aba90f216427807cbff79f"); - EXPECT_EQ(signingOutput.error(), ""); - EXPECT_EQ(hex(signingOutput.serialized()), ""); + output = Signer::sign(input, TWCoinTypeCosmos); + ASSERT_EQ(hex(output.signature()), "8f85a9515a211881daebfb346c2beeca3ab5c2d406a9b3ad402cfddaa3d08e2b13378e13cfef8ecf1d6500fe85d0ce3e793034dd77aba90f216427807cbff79f"); + EXPECT_EQ(output.error_message(), ""); + EXPECT_EQ(hex(output.serialized()), ""); } } @@ -195,14 +195,14 @@ TEST(CosmosStaking, Restaking) { assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"CtIBCs8BCiovY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dCZWdpblJlZGVsZWdhdGUSoAEKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhI0Y29zbW9zdmFsb3BlcjF6a3VwcjgzaHJ6a24zdXA1ZWxrdHpjcTN0dWZ0OG54c213ZHFncBotY29zbW9zMWhzazZqcnl5cWpmaHA1ZGhjNTV0YzlqdGNreWd4MGVwaDZkZDAyIgoKBG11b24SAjEwEmYKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQJXKG7D830zVXu7qgALJ3RKyQI6qZZ8rnWhgdH/kfqdxRIECgIIARgHEhIKDAoEbXVvbhIEMTAxOBDZmgYaQJ52qO5xdtBkNUeFeWrnqUXkngyHFKCXnOPPClyVI0HrULdp5jbwGra2RujEOn4BrbFCb3JFnpc2o1iuLXbKQxg=\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "9e76a8ee7176d064354785796ae7a945e49e0c8714a0979ce3cf0a5c952341eb50b769e636f01ab6b646e8c43a7e01adb1426f72459e9736a358ae2d76ca4318"); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); { // Json-serialization, for coverage (to be removed later) input.set_signing_mode(Proto::JSON); - auto signingOutput = Signer::sign(input, TWCoinTypeCosmos); - ASSERT_EQ(hex(signingOutput.signature()), "e64d3761bd25a28befcda80c0a0e208d024fdb0a2b89955170e65a5c5d454aba2ce81d57e01f0c126de5a59c2b58124c109560c9803d65a17a14b548dd6c50db"); - EXPECT_EQ(signingOutput.error(), ""); - EXPECT_EQ(hex(signingOutput.serialized()), ""); + output = Signer::sign(input, TWCoinTypeCosmos); + ASSERT_EQ(hex(output.signature()), "e64d3761bd25a28befcda80c0a0e208d024fdb0a2b89955170e65a5c5d454aba2ce81d57e01f0c126de5a59c2b58124c109560c9803d65a17a14b548dd6c50db"); + EXPECT_EQ(output.error_message(), ""); + EXPECT_EQ(hex(output.serialized()), ""); } } @@ -232,14 +232,51 @@ TEST(CosmosStaking, Withdraw) { assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"CqMBCqABCjcvY29zbW9zLmRpc3RyaWJ1dGlvbi52MWJldGExLk1zZ1dpdGhkcmF3RGVsZWdhdG9yUmV3YXJkEmUKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhI0Y29zbW9zdmFsb3BlcjF6a3VwcjgzaHJ6a24zdXA1ZWxrdHpjcTN0dWZ0OG54c213ZHFncBJmClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECVyhuw/N9M1V7u6oACyd0SskCOqmWfK51oYHR/5H6ncUSBAoCCAEYBxISCgwKBG11b24SBDEwMTgQ2ZoGGkBW1Cd+0pNfMPEVXQtqG1VIijDjZP2UOiDlvUF478axnxlF8PaOAsY0S5OdUE3Wz7+nu8YVmrLZQS/8mlqLaK05\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "56d4277ed2935f30f1155d0b6a1b55488a30e364fd943a20e5bd4178efc6b19f1945f0f68e02c6344b939d504dd6cfbfa7bbc6159ab2d9412ffc9a5a8b68ad39"); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); { // Json-serialization, for coverage (to be removed later) input.set_signing_mode(Proto::JSON); - auto signingOutput = Signer::sign(input, TWCoinTypeCosmos); - ASSERT_EQ(hex(signingOutput.signature()), "546f0d67356f6af94cfb5ab22b974e499c33123f2c2c292f4f0e64878e0e728f4643105fd771550beb3f2371f08880aaa38fa8f2334c103a779f1d82d2db98d6"); - EXPECT_EQ(signingOutput.error(), ""); - EXPECT_EQ(hex(signingOutput.serialized()), ""); + output = Signer::sign(input, TWCoinTypeCosmos); + ASSERT_EQ(hex(output.signature()), "546f0d67356f6af94cfb5ab22b974e499c33123f2c2c292f4f0e64878e0e728f4643105fd771550beb3f2371f08880aaa38fa8f2334c103a779f1d82d2db98d6"); + EXPECT_EQ(output.error_message(), ""); + EXPECT_EQ(hex(output.serialized()), ""); + } +} + +TEST(CosmosStaking, SetWithdrawAddress) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(1037); + input.set_chain_id("gaia-13003"); + input.set_memo(""); + input.set_sequence(7); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_set_withdraw_address_message(); + message.set_delegator_address("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); + message.set_withdraw_address("cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(101721); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("muon"); + amountOfFee->set_amount("1018"); + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeCosmos); + + assertJSONEqual(output.serialized(), R"({"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"Cp4BCpsBCjIvY29zbW9zLmRpc3RyaWJ1dGlvbi52MWJldGExLk1zZ1NldFdpdGhkcmF3QWRkcmVzcxJlCi1jb3Ntb3MxaHNrNmpyeXlxamZocDVkaGM1NXRjOWp0Y2t5Z3gwZXBoNmRkMDISNGNvc21vc3ZhbG9wZXIxemt1cHI4M2hyemtuM3VwNWVsa3R6Y3EzdHVmdDhueHNtd2RxZ3ASZgpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAcSEgoMCgRtdW9uEgQxMDE4ENmaBhpAkm2TJLw4FcIwN5bkqVaGbmAgkTSHeYD8sUkIyJHLa89cPvThkFO/lKlxBMl2UAMs06hL6cYcl4Px+B6rpFdBpA=="})"); + EXPECT_EQ(hex(output.signature()), "926d9324bc3815c2303796e4a956866e60209134877980fcb14908c891cb6bcf5c3ef4e19053bf94a97104c97650032cd3a84be9c61c9783f1f81eaba45741a4"); + EXPECT_EQ(output.error_message(), ""); + + { // Json-serialization, for coverage (to be removed later) + input.set_signing_mode(Proto::JSON); + output = Signer::sign(input, TWCoinTypeCosmos); + ASSERT_EQ(hex(output.signature()), "22cfbcec33d06ed42623264049d11d6fb86566103d5621a23b1444022eb1aace3a0790a1c46b48c0218689616daf97f99ae72c3589966205de45b57194fbada2"); + EXPECT_EQ(output.error_message(), ""); + EXPECT_EQ(hex(output.serialized()), ""); } } diff --git a/tests/chains/Cosmos/Stargaze/TWAnySignerTests.cpp b/tests/chains/Cosmos/Stargaze/TWAnySignerTests.cpp index 129306a7cd7..efde5f8a471 100644 --- a/tests/chains/Cosmos/Stargaze/TWAnySignerTests.cpp +++ b/tests/chains/Cosmos/Stargaze/TWAnySignerTests.cpp @@ -52,7 +52,7 @@ TEST(TWAnySignerStargaze, SignNftTransferCW721) { assertJSONEqual(output.serialized(), expectedJson); EXPECT_EQ(hex(output.signature()), "cc7e976b1d3390c03db0f2635a75ef730b9e31b235621c46668589536984bb93577b638703dbb17100d21c91c72592def374ff1505fc912c3bdc11b298f6689e"); EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); } TEST(TWAnySignerStargaze, Sign) { @@ -91,7 +91,7 @@ TEST(TWAnySignerStargaze, Sign) { assertJSONEqual(output.serialized(), expectedJson); EXPECT_EQ(hex(output.signature()), "70249edc730b5a07ed875ee3e2849a707e2b75db248630d2ae8ca8c3c463ae5803004334339d693bbfae97a1bacff6d0cbc17db004662c3dfb08735cba644390"); EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); } } // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/Stride/TWAnySignerTests.cpp b/tests/chains/Cosmos/Stride/TWAnySignerTests.cpp index e28ed3e964e..f125a955553 100644 --- a/tests/chains/Cosmos/Stride/TWAnySignerTests.cpp +++ b/tests/chains/Cosmos/Stride/TWAnySignerTests.cpp @@ -49,7 +49,7 @@ TEST(TWAnySignerStride, SignLiquidStaking) { assertJSONEqual(output.serialized(), expectedJson); EXPECT_EQ(hex(output.signature()), "836991d5e7f677080402e435e4687985e8e294892e57283b25dd99c169a56e263952595daf03b30fc066c2d7b3fa8db549b79f192dcc48585b7be943b7cb58b1"); EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); } TEST(TWAnySignerStride, SignLiquidStakingRedeem) { @@ -87,7 +87,7 @@ TEST(TWAnySignerStride, SignLiquidStakingRedeem) { assertJSONEqual(output.serialized(), expectedJson); EXPECT_EQ(TW::Base64::encode(data(output.signature())), "p/zhNig+rCdfDbavR2iTZ9imVQW7TTdOUh+mhP7CpNkXWUc8L5FMCgmhvrHnQKd+8aWAkUERBg8P4Jy/rn1VUA=="); EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); } } // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/THORChain/SignerTests.cpp b/tests/chains/Cosmos/THORChain/SignerTests.cpp index 72e3592ca74..c0da390ffd4 100644 --- a/tests/chains/Cosmos/THORChain/SignerTests.cpp +++ b/tests/chains/Cosmos/THORChain/SignerTests.cpp @@ -92,7 +92,7 @@ TEST(THORChainSigner, SignTx_Protobuf_7E480F) { )"); EXPECT_EQ(hex(output.signature()), "a66d4b70136b6e8e386bea74a9b5e196c778d7e43ed21a8d78b93246795da5f649639e5fe62708f67d4f117d263e5c548a3bd66e79c6ae2154bdf20db45ef3d8"); EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); } TEST(THORChainSigner, SignTx_Json_Deprecated) { diff --git a/tests/chains/Cosmos/THORChain/SwapTests.cpp b/tests/chains/Cosmos/THORChain/SwapTests.cpp index 5561f3e4bf2..805248c0052 100644 --- a/tests/chains/Cosmos/THORChain/SwapTests.cpp +++ b/tests/chains/Cosmos/THORChain/SwapTests.cpp @@ -434,7 +434,7 @@ TEST(THORChainSwap, SwapAtomBnb) { Cosmos::Proto::SigningOutput output; ANY_SIGN(tx, TWCoinTypeCosmos); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); ASSERT_EQ(output.serialized(), "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CtMBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLWNvc21vczF2NGU2dnBlaHdyZmV6MmRxZXBudzlnNnQ0Zmw4M3h6ZWdkNWFjORItY29zbW9zMTU0dDV5Y2VqbHI3YXgzeW5tZWQ5ejA1eWc1YTI3eTl1NnBqNWhxGg8KBXVhdG9tEgYzMDAwMDASPz06Qk5CLkJOQjpibmIxczRrYWxseG5ncHlzcHptNm5yZXprbWw5cmd5dzZreHB3NGZocjI6ODE5MzkxOnQ6MBJmClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDmmNIYBvR9bnOloFEMOWdk9DHYIGe7naW0T19y+/k1SUSBAoCCAEYARISCgwKBXVhdG9tEgM1MDAQwJoMGkCFqUWtDu0pn1P/cnVQnIJIWF8HFJn/xkJh55Mc7ZLVPF60uXYUOg8nNkt0IQPuTFREw32/yff6lmA5w6KwPen/\"}"); // https://viewblock.io/thorchain/tx/07F47D71A74245538E205F24ADB4BBB799B49C3A8A8875665D249EA51662AA50 diff --git a/tests/chains/Cosmos/THORChain/TWSwapTests.cpp b/tests/chains/Cosmos/THORChain/TWSwapTests.cpp index 706946a463e..bd47a046c5f 100644 --- a/tests/chains/Cosmos/THORChain/TWSwapTests.cpp +++ b/tests/chains/Cosmos/THORChain/TWSwapTests.cpp @@ -99,7 +99,7 @@ TEST(TWTHORChainSwap, SwapBtcToEth) { { Bitcoin::Proto::SigningOutput output; ANY_SIGN(txInput, TWCoinTypeBitcoin); - EXPECT_EQ(output.error(), 0); + EXPECT_EQ(output.error(), Common::Proto::OK); EXPECT_EQ(hex(output.encoded()), // printed using prettyPrintTransaction "01000000" // version "0001" // marker & flag @@ -275,7 +275,7 @@ TEST(TWTHORChainSwap, SwapAtomBnb) { // sign and encode resulting input Cosmos::Proto::SigningOutput output; ANY_SIGN(txInput, TWCoinTypeCosmos); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); ASSERT_EQ(output.serialized(), "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CtMBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLWNvc21vczF2NGU2dnBlaHdyZmV6MmRxZXBudzlnNnQ0Zmw4M3h6ZWdkNWFjORItY29zbW9zMTU0dDV5Y2VqbHI3YXgzeW5tZWQ5ejA1eWc1YTI3eTl1NnBqNWhxGg8KBXVhdG9tEgYzMDAwMDASPz06Qk5CLkJOQjpibmIxczRrYWxseG5ncHlzcHptNm5yZXprbWw5cmd5dzZreHB3NGZocjI6ODE5MzkxOnQ6MBJmClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDmmNIYBvR9bnOloFEMOWdk9DHYIGe7naW0T19y+/k1SUSBAoCCAEYARISCgwKBXVhdG9tEgM1MDAQwJoMGkCFqUWtDu0pn1P/cnVQnIJIWF8HFJn/xkJh55Mc7ZLVPF60uXYUOg8nNkt0IQPuTFREw32/yff6lmA5w6KwPen/\"}"); // https://viewblock.io/thorchain/tx/07F47D71A74245538E205F24ADB4BBB799B49C3A8A8875665D249EA51662AA50 diff --git a/tests/chains/Cosmos/TWAnySignerTests.cpp b/tests/chains/Cosmos/TWAnySignerTests.cpp index 422248f9eb7..c2c25f96c17 100644 --- a/tests/chains/Cosmos/TWAnySignerTests.cpp +++ b/tests/chains/Cosmos/TWAnySignerTests.cpp @@ -50,7 +50,7 @@ TEST(TWAnySignerCosmos, SignTx) { assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"CpIBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLWNvc21vczFta3k2OWNuOGVrdHd5MDg0NXZlYzl1cHNkcGhrdHh0MDNna3dseBItY29zbW9zMThzMGhkbnNsbGdjY2x3ZXU5YXltdzRuZ2t0cjJrMHJreWdkemRwGg8KBXVhdG9tEgY0MDAwMDASZQpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJEgQKAggBEhMKDQoFdWF0b20SBDEwMDAQwJoMGkCvvVE6d29P30cO9/lnXyGunWMPxNY12NuqDcCnFkNM0H4CUQdl1Gc9+ogIJbro5nyzZzlv9rl2/GsZox/JXoCX\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "afbd513a776f4fdf470ef7f9675f21ae9d630fc4d635d8dbaa0dc0a716434cd07e02510765d4673dfa880825bae8e67cb367396ff6b976fc6b19a31fc95e8097"); EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); } TEST(TWAnySignerCosmos, SignJSON) { diff --git a/tests/chains/Cosmos/Terra/SignerTests.cpp b/tests/chains/Cosmos/Terra/SignerTests.cpp index 619f22df7a4..2d6373de942 100644 --- a/tests/chains/Cosmos/Terra/SignerTests.cpp +++ b/tests/chains/Cosmos/Terra/SignerTests.cpp @@ -97,7 +97,7 @@ TEST(TerraClassicSigner, SignSendTx) { )"); EXPECT_EQ(hex(output.signature()), "a1f748b0b27390e0dc4302c6f3d784da0e0739a5267ca3e1ff4f2dd3b7a128f52a44c978ad5a27ce8ef79a43afaadac75a376d07eeade91f031ae74fa5be9b46"); EXPECT_EQ(output.serialized(), ""); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); } TEST(TerraClassicSigner, SignWasmTransferTxProtobuf_9FF3F0) { @@ -173,7 +173,6 @@ TEST(TerraClassicSigner, SignWasmTransferTxProtobuf_9FF3F0) { )"); EXPECT_EQ(hex(output.signature()), "1aa6b20430b3c7d87985770146a1a8a96f6188ca15edea899bcfe58c9c6da6391048ea016dd0cf88ca6620cd9cfac35496e7647ddb6c9a7c39e7ef25dfbad470"); EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); } TEST(TerraClassicSigner, SignWasmTransferTxJson_078E90) { @@ -253,7 +252,6 @@ TEST(TerraClassicSigner, SignWasmTransferTxJson_078E90) { })"); EXPECT_EQ(hex(output.signature()), "06311376d6c0f7b5afd73bdcb02575b4cf9b758282f0edee19393898c46fea9049076cbf0eceeaa12eecff3ae48586a4d580a192541eb47b08df45d15f318892"); EXPECT_EQ(output.serialized(), ""); - EXPECT_EQ(output.error(), ""); } TEST(TerraClassicSigner, SignWasmGeneric_EC4F85) { @@ -299,7 +297,6 @@ TEST(TerraClassicSigner, SignWasmGeneric_EC4F85) { EXPECT_EQ(hex(output.signature()), "90fb12ef19529e0d8b31cf4a883d6ca0de4d2da0dc521f08f6890f9ac74937795ed41ef2c5118d0786907e169197ff19d2be6f8ad453d005aec436ac77cc1c17"); EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); } TEST(TerraClassicSigner, SignWasmGenericWithCoins_6651FC) { @@ -349,7 +346,6 @@ TEST(TerraClassicSigner, SignWasmGenericWithCoins_6651FC) { EXPECT_EQ(hex(output.signature()), "1b28bb7f58a863c5d5ea98c5ab5b3ce8e9b8fbe088527777acb1e27f68a8a42718bd7d262e44e543a35411183c9d64d8817723e9f3933bfa6d80fb80b6fd0d5a"); EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); } TEST(TerraClassicSigner, SignWasmSendTxProtobuf) { @@ -429,7 +425,6 @@ TEST(TerraClassicSigner, SignWasmSendTxProtobuf) { )"); EXPECT_EQ(hex(output.signature()), "be8d07229e459b32ab983a09331d98faa233bee739ef6e2cef058e97ad1609011621b72deee279967ee2f08c843d5b70580cc121e26c523fb8f8a5f38ed4ffe8"); EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); } TEST(TerraClassicSigner, SignWasmTerraTransferPayload) { diff --git a/tests/chains/Cosmos/TerraV2/SignerTests.cpp b/tests/chains/Cosmos/TerraV2/SignerTests.cpp index 94c86f8f0e5..cfd8dd8dde4 100644 --- a/tests/chains/Cosmos/TerraV2/SignerTests.cpp +++ b/tests/chains/Cosmos/TerraV2/SignerTests.cpp @@ -94,7 +94,7 @@ TEST(TerraSigner, SignSendTx) { } )"); EXPECT_EQ(hex(output.signature()), "f8740b7ae3cdd8b12148b23f1dc5956031cdb2882cd01c49155e427693975bec2390c47d86b6a1895404bab28a570c09c53f89a24b85ec77d0da366a4d199f54"); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); EXPECT_EQ(output.json(), ""); } @@ -170,7 +170,7 @@ TEST(TerraSigner, SignWasmTransferTx) { } )"); EXPECT_EQ(hex(output.signature()), "88119b41a8fe8ec5c4ebf16cb03ddf0bbed03b1a658bd1aab0f79af8aa0dc8c2048158fcf478a2fa85356c01104b8a95d12684d95e91372db8b827054c845b71"); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); EXPECT_EQ(output.json(), ""); } @@ -212,7 +212,7 @@ TEST(TerraSigner, SignWasmGeneric) { )"); EXPECT_EQ(hex(output.signature()), "6958ccea00bbf3dfc824d24e2b062555bd296c227f131b0a6cf2db1c2a360f991d09038d547250197b8266d75cbcdd38caadc05d4231ab898651b098477384ae"); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); EXPECT_EQ(output.json(), ""); } @@ -258,7 +258,7 @@ TEST(TerraSigner, SignWasmGenericWithCoins) { )"); EXPECT_EQ(hex(output.signature()), "40c0d9fa177a017eaec963ea8f1b5f61cf25f9feabfe84cdbab688077448539342a4bc680cb2c1091d649453a1771a6f2d473982f55af9ac30f82b1730a1898e"); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); EXPECT_EQ(output.json(), ""); } @@ -336,7 +336,7 @@ TEST(TerraSigner, SignWasmSendTx) { } )"); EXPECT_EQ(hex(output.signature()), "4a25b5b5183aeff63d8521c926eccb690d9ccfd918e00e353ce28626387347b311013cb8edb776008ec29604202c8db9db568277b365c51b4f27c505d2f7104a"); - EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.error_message(), ""); EXPECT_EQ(output.json(), ""); } diff --git a/tests/chains/Cosmos/TransactionCompilerTests.cpp b/tests/chains/Cosmos/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..4500533ab7a --- /dev/null +++ b/tests/chains/Cosmos/TransactionCompilerTests.cpp @@ -0,0 +1,185 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Base64.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Cosmos.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(CosmosCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeCosmos; + TW::Cosmos::Proto::SigningInput input; + + PrivateKey privateKey = + PrivateKey(parse_hex("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + /// Step 1: Prepare transaction input (protobuf) + input.set_account_number(546179); + input.set_chain_id("cosmoshub-4"); + input.set_memo(""); + input.set_sequence(0); + + PublicKey publicKey = privateKey.getPublicKey(TWCoinTypePublicKeyType(coin)); + const auto pubKeyBz = publicKey.bytes; + input.set_public_key(pubKeyBz.data(), pubKeyBz.size()); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address("cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx"); + message.set_to_address("cosmos18s0hdnsllgcclweu9aymw4ngktr2k0rkygdzdp"); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("uatom"); + amountOfTx->set_amount("400000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uatom"); + amountOfFee->set_amount("1000"); + + /// Step 2: Obtain protobuf preimage hash + input.set_signing_mode(TW::Cosmos::Proto::Protobuf); + auto protoInputString = input.SerializeAsString(); + auto protoInputData = TW::Data(protoInputString.begin(), protoInputString.end()); + + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, protoInputData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + auto preImage = preSigningOutput.data(); + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ( + hex(preImage), + "0a92010a8f010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e64126f0a2d636f736d6f" + "73316d6b793639636e38656b74777930383435766563397570736470686b7478743033676b776c78122d636f73" + "6d6f733138733068646e736c6c6763636c7765753961796d77346e676b7472326b30726b7967647a64701a0f0a" + "057561746f6d120634303030303012650a4e0a460a1f2f636f736d6f732e63727970746f2e736563703235366b" + "312e5075624b657912230a2102ecef5ce437a302c67f95468de4b31f36e911f467d7e6a52b41c1e13e1d563649" + "12040a02080112130a0d0a057561746f6d12043130303010c09a0c1a0b636f736d6f736875622d342083ab21"); + EXPECT_EQ(hex(preImageHash), + "fa7990e1814c900efaedf1bdbedba22c22336675befe0ae39974130fc204f3de"); + + auto expectedTx = + "7b226d6f6465223a2242524f4144434153545f4d4f44455f424c4f434b222c2274785f6279746573223a224370" + "4942436f3842436877765932397a6257397a4c6d4a68626d7375646a46695a5852684d53354e633264545a5735" + "6b456d384b4c574e7663323176637a467461336b324f574e754f475672644864354d4467304e585a6c597a6c31" + "63484e6b63476872644868304d444e6e61336473654249745932397a6257397a4d54687a4d47686b626e4e7362" + "47646a593278335a58553559586c74647a52755a327430636a4a724d484a726557646b656d52774767384b4258" + "566864473974456759304d4441774d4441535a51704f436b594b4879396a62334e7462334d7559334a35634852" + "764c6e4e6c593341794e545a724d53355164574a4c5a586b5349776f6841757a76584f51336f774c4766355647" + "6a65537a487a62704566526e312b616c4b30484234543464566a5a4a4567514b4167674245684d4b44516f4664" + "57463062323053424445774d444151774a6f4d476b437676564536643239503330634f392f6c6e587947756e57" + "4d50784e5931324e75714463436e466b4e4d304834435551646c314763392b6f67494a62726f356e797a5a7a6c" + "7639726c322f47735a6f782f4a586f4358227d"; + Data signature; + + { + TW::Cosmos::Proto::SigningOutput output; + ANY_SIGN(input, coin); + + assertJSONEqual( + output.serialized(), + "{\"tx_bytes\": " + "\"CpIBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLWNvc21vczFta3k2OWNuOGVrdHd5MDg0NXZl" + "Yzl1cHNkcGhrdHh0MDNna3dseBItY29zbW9zMThzMGhkbnNsbGdjY2x3ZXU5YXltdzRuZ2t0cjJrMHJreWdkemRwGg" + "8KBXVhdG9tEgY0MDAwMDASZQpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAuzvXOQ3owLG" + "f5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJEgQKAggBEhMKDQoFdWF0b20SBDEwMDAQwJoMGkCvvVE6d29P30cO9/" + "lnXyGunWMPxNY12NuqDcCnFkNM0H4CUQdl1Gc9+ogIJbro5nyzZzlv9rl2/GsZox/JXoCX\", \"mode\": " + "\"BROADCAST_MODE_BLOCK\"}"); + + signature = data(output.signature()); + EXPECT_EQ(hex(signature), + "afbd513a776f4fdf470ef7f9675f21ae9d630fc4d635d8dbaa0dc0a716434cd07e02510765d4673dfa88" + "0825bae8e67cb367396ff6b976fc6b19a31fc95e8097"); + + ASSERT_TRUE(publicKey.verify(signature, data(preImageHash.data()))); + EXPECT_EQ(hex(output.serialized()), expectedTx); + } + + { + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, protoInputData, {signature}, {publicKey.bytes}); + Cosmos::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.error(), Common::Proto::OK); + EXPECT_EQ(hex(output.serialized()), expectedTx); + } + + { // Negative: not enough signatures + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, protoInputData, {signature, signature}, {publicKey.bytes}); + Cosmos::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.serialized().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, protoInputData, {}, {}); + Cosmos::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.serialized().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + + /// Step 3: Obtain json preimage hash + input.set_signing_mode(TW::Cosmos::Proto::JSON); + auto jsonInputString = input.SerializeAsString(); + auto jsonInputData = TW::Data(jsonInputString.begin(), jsonInputString.end()); + + const auto jsonPreImageHashData = TransactionCompiler::preImageHashes(coin, jsonInputData); + auto jsonPreSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE(jsonPreSigningOutput.ParseFromArray(jsonPreImageHashData.data(), + (int)jsonPreImageHashData.size())); + ASSERT_EQ(jsonPreSigningOutput.error(), 0); + auto jsonPreImage = jsonPreSigningOutput.data(); + auto jsonPreImageHash = jsonPreSigningOutput.data_hash(); + EXPECT_EQ(hex(jsonPreImage), + "7b226163636f756e745f6e756d626572223a22353436313739222c22636861696e5f6964223a22636f73" + "6d6f736875622d34222c22666565223a7b22616d6f756e74223a5b7b22616d6f756e74223a2231303030" + "222c2264656e6f6d223a227561746f6d227d5d2c22676173223a22323030303030227d2c226d656d6f22" + "3a22222c226d736773223a5b7b2274797065223a22636f736d6f732d73646b2f4d736753656e64222c22" + "76616c7565223a7b22616d6f756e74223a5b7b22616d6f756e74223a22343030303030222c2264656e6f" + "6d223a227561746f6d227d5d2c2266726f6d5f61646472657373223a22636f736d6f73316d6b79363963" + "6e38656b74777930383435766563397570736470686b7478743033676b776c78222c22746f5f61646472" + "657373223a22636f736d6f733138733068646e736c6c6763636c7765753961796d77346e676b7472326b" + "30726b7967647a6470227d7d5d2c2273657175656e6365223a2230227d"); + EXPECT_EQ(hex(jsonPreImageHash), + "0a31f6cd50f1a5c514929ba68a977e222a7df2dc11e8470e93118cc3545e6b37"); + + signature = Base64::decode("tTyOrburrHEHa14qiw78e9StoZyyGmoku98IxYrWCmtN8Qo5mTeKa0BKKDfgG4LmmNdwYcrXtqQQ7F4dL3c26g=="); + { + auto result = TW::anySignJSON(coin, jsonPreImage, privateKey.bytes); + EXPECT_EQ(result, "{\"mode\":\"block\",\"tx\":{\"fee\":{\"amount\":[],\"gas\":\"0\"},\"memo\":\"\",\"msg\":[],\"signatures\":[{\"pub_key\":{\"type\":\"tendermint/PubKeySecp256k1\",\"value\":\"AuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJ\"},\"signature\":\"tTyOrburrHEHa14qiw78e9StoZyyGmoku98IxYrWCmtN8Qo5mTeKa0BKKDfgG4LmmNdwYcrXtqQQ7F4dL3c26g==\"}]}}"); + } + + { // JSON + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, jsonInputData, {signature}, {publicKey.bytes}); + Cosmos::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.error(), Common::Proto::OK); + EXPECT_EQ(hex(output.serialized()), "7b226d6f6465223a22626c6f636b222c227478223a7b22666565223a7b22616d6f756e74223a5b7b22616d6f756e74223a2231303030222c2264656e6f6d223a227561746f6d227d5d2c22676173223a22323030303030227d2c226d656d6f223a22222c226d7367223a5b7b2274797065223a22636f736d6f732d73646b2f4d736753656e64222c2276616c7565223a7b22616d6f756e74223a5b7b22616d6f756e74223a22343030303030222c2264656e6f6d223a227561746f6d227d5d2c2266726f6d5f61646472657373223a22636f736d6f73316d6b793639636e38656b74777930383435766563397570736470686b7478743033676b776c78222c22746f5f61646472657373223a22636f736d6f733138733068646e736c6c6763636c7765753961796d77346e676b7472326b30726b7967647a6470227d7d5d2c227369676e617475726573223a5b7b227075625f6b6579223a7b2274797065223a2274656e6465726d696e742f5075624b6579536563703235366b31222c2276616c7565223a2241757a76584f51336f774c47663556476a65537a487a62704566526e312b616c4b30484234543464566a5a4a227d2c227369676e6174757265223a227454794f72627572724845486131347169773738653953746f5a7979476d6f6b7539384978597257436d744e38516f356d54654b6130424b4b44666747344c6d6d4e64775963725874715151374634644c33633236673d3d227d5d7d7d"); + } +} diff --git a/tests/chains/Decred/AddressTests.cpp b/tests/chains/Decred/AddressTests.cpp index 72de5c191fb..a8f65f33f0c 100644 --- a/tests/chains/Decred/AddressTests.cpp +++ b/tests/chains/Decred/AddressTests.cpp @@ -15,9 +15,16 @@ namespace TW::Decred::tests { TEST(DecredAddress, FromPublicKey) { - const auto publicKey = PublicKey(parse_hex("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), TWPublicKeyTypeSECP256k1); - const auto address = Address(publicKey); - ASSERT_EQ(address.string(), "DsmcYVbP1Nmag2H4AS17UTvmWXmGeA7nLDx"); + { + const auto publicKey = PublicKey(parse_hex("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), TWPublicKeyTypeSECP256k1); + const auto address = Address(publicKey); + ASSERT_EQ(address.string(), "DsmcYVbP1Nmag2H4AS17UTvmWXmGeA7nLDx"); + } + { + const auto privateKey = PrivateKey(parse_hex("a1269039e4ffdf43687852d7247a295f0b5bc55e6dda031cffaa3295ca0a9d7a")); + const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeED25519)); + EXPECT_ANY_THROW(new Address(publicKey)); + } } TEST(DecredAddress, Valid) { diff --git a/tests/chains/Decred/SignerTests.cpp b/tests/chains/Decred/SignerTests.cpp index a9de34e35c9..f519fa0e8a2 100644 --- a/tests/chains/Decred/SignerTests.cpp +++ b/tests/chains/Decred/SignerTests.cpp @@ -122,6 +122,88 @@ TEST(DecredSigner, SignP2PKH) { EXPECT_EQ(hex(encoded), expectedEncoded); } +TEST(DecredSigner, SignP2PK) { + const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); + + const auto address = Address(publicKey); + ASSERT_EQ(address.string(), "DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + + + // For this example, create a fake transaction that represents what would + // ordinarily be the real transaction that is being spent. It contains a + // single output that pays to address in the amount of 1 DCR. + auto originTx = Transaction(); + + auto txInOrigin = TransactionInput(); + txInOrigin.previousOutput = OutPoint(std::array{}, UINT32_MAX, 0); + txInOrigin.valueIn = 100'000'000; + txInOrigin.script = Bitcoin::Script(Data{OP_0, OP_0}); + originTx.inputs.push_back(txInOrigin); + + auto txOutOrigin = TransactionOutput(); + txOutOrigin.value = 100'000'000; + txOutOrigin.script = Bitcoin::Script::buildPayToPublicKey(publicKey.bytes); + originTx.outputs.push_back(txOutOrigin); + + ASSERT_EQ(hex(originTx.hash()), "ac59d53c1eaf3fa41a47a9fca9d6b5b79857e7ef8cbe2556460e690e71eeb08b"); + + // Setup input + Bitcoin::Proto::SigningInput input; + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(100'000'000); + input.set_byte_fee(1); + input.set_to_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + input.set_change_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + + auto utxoKey0 = parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c"); + input.add_private_key(utxoKey0.data(), utxoKey0.size()); + + auto utxo0 = input.add_utxo(); + auto utxo0Script = Bitcoin::Script::buildPayToPublicKey(publicKey.bytes); + utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); + utxo0->set_amount(100'000'000); + utxo0->mutable_out_point()->set_hash(originTx.hash().data(), originTx.hash().size()); + utxo0->mutable_out_point()->set_index(0); + + // Create the transaction to redeem the fake transaction. + auto redeemTx = Transaction(); + + auto txIn = TransactionInput(); + txIn.previousOutput = OutPoint(originTx.hash(), 0, 0); + txIn.valueIn = 100'000'000; + redeemTx.inputs.push_back(txIn); + + auto txOut = TransactionOutput(); + redeemTx.outputs.push_back(txOut); + + auto plan = input.mutable_plan(); + plan->set_amount(100'000'000); + plan->set_available_amount(100'000'000); + plan->set_fee(0); + plan->set_change(0); + auto utxop0 = plan->add_utxos(); + *utxop0 = *utxo0; + + // Sign + auto signer = Signer(std::move(input)); + signer._transaction = redeemTx; + signer.txPlan.amount = 100'000'000; + const auto result = signer.sign(); + + ASSERT_TRUE(result); + + const auto expectedSignature = "47304402202f9e6b7c849fc4ebcea8f544680f2652d24cede340b9370084d740e6483f44a60220582c9e70faa35fdf5af38a4268ef5564bbb527fb4e39303afba6ce71848a7b9301"; + EXPECT_EQ(hex(result.payload().inputs[0].script.bytes), expectedSignature); + + const auto expectedEncoded = + "0100000001ac59d53c1eaf3fa41a47a9fca9d6b5b79857e7ef8cbe2556460e690e71eeb08b0000000000ffffffff01000000000000000000000000000000000000000100e1f5050000000000000000ffffffff4847304402202f9e6b7c849fc4ebcea8f544680f2652d24cede340b9370084d740e6483f44a60220582c9e70faa35fdf5af38a4268ef5564bbb527fb4e39303afba6ce71848a7b9301"; + auto encoded = Data(); + result.payload().encode(encoded); + EXPECT_EQ(hex(encoded), expectedEncoded); +} + TEST(DecredSigner, SignP2SH) { const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); @@ -425,9 +507,12 @@ TEST(DecredSigning, SignP2WPKH_NegativeAddressWrongType) { utxo1->mutable_out_point()->set_sequence(UINT32_MAX); // Sign - auto result = Signer(std::move(input)).sign(); + auto result = Signer::sign(std::move(input)); + ASSERT_NE(result.error(), Common::Proto::OK); - ASSERT_FALSE(result) << std::to_string(result.error()); + // PreImageHashes + auto preResult = Signer::preImageHashes(std::move(input)); + ASSERT_NE(preResult.error(), Common::Proto::OK); } // clang-format on diff --git a/tests/chains/Decred/TransactionCompilerTests.cpp b/tests/chains/Decred/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..55520304c62 --- /dev/null +++ b/tests/chains/Decred/TransactionCompilerTests.cpp @@ -0,0 +1,163 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Bitcoin/Script.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Bitcoin.pb.h" +#include "proto/Decred.pb.h" + +#include "proto/TransactionCompiler.pb.h" + +#include +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +namespace TW::Decred::tests { + +TEST(DecredCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeDecred; + + const int64_t utxoValue = 39900000; + const int64_t amount = 10000000; + const int64_t fee = 100000; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address("Dsesp1V6DZDEtcq2behmBVKdYqKMdkh96hL"); + input.set_change_address("DsUoWCAxprdGNtKQqambFbTcSBgH1SHn9Gp"); + input.set_coin_type(coin); + + auto& utxo = *input.add_utxo(); + + auto hash = parse_hex("fdbfe9dd703f306794a467f175be5bd9748a7925033ea1cf9889d7cf4dd11550"); + auto script = parse_hex("76a914b75fdec70b2e731795dd123ab40f918bf099fee088ac"); + auto utxoKey = parse_hex("ba005cd605d8a02e3d5dfd04234cef3a3ee4f76bfbad2722d1fb5af8e12e6764"); + + utxo.set_amount(utxoValue); + utxo.set_script(script.data(), script.size()); + + auto& outpoint = *utxo.mutable_out_point(); + outpoint.set_hash(hash.data(), hash.size()); + outpoint.set_index(0); + + auto& plan = *input.mutable_plan(); + plan.set_amount(amount); + plan.set_available_amount(utxoValue); + plan.set_fee(fee); + plan.set_change(utxoValue - amount - fee); + auto& planUtxo = *plan.add_utxos(); + planUtxo = input.utxo(0); + + // build preimage + const auto txInputData = data(input.SerializeAsString()); + EXPECT_GT(txInputData.size(), 0ul); + + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + ASSERT_GT(preImageHashes.size(), 0ul); + + Bitcoin::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + ASSERT_EQ(preSigningOutput.hash_public_keys().size(), 1); + + auto preImageHash = data(preSigningOutput.hash_public_keys()[0].data_hash()); + EXPECT_EQ(hex(preImageHash), + "9e4305478d1a69ee5c89a2e234d1cf270798d447d5db983b8fc3c817afddec34"); + + // compile + auto publicKey = PrivateKey(utxoKey).getPublicKey(TWPublicKeyTypeSECP256k1); + auto signature = parse_hex("304402206ee887c9239e5fff0048674bdfff2a8cfbeec6cd4a3ccebcc12fac44b24cc5ac0220718f7c760818fde18bc5ba8457d43d5a145cc4cf13d2a5557cba9107e9f4558d"); + auto outputData = + TransactionCompiler::compileWithSignatures(coin, txInputData, {signature}, {publicKey.bytes}); + Decred::Proto::SigningOutput signingOutput; + ASSERT_TRUE(signingOutput.ParseFromArray(outputData.data(), (int)outputData.size())); + ASSERT_EQ(hex(signingOutput.encoded()), + "0100000001fdbfe9dd703f306794a467f175be5bd9748a7925033ea1cf9889d7cf4dd1155000000000000000000002809698000000000000001976a914989b1aecabf1c24e213cc0f2d8a22ffee25dd4e188ac40b6c6010000000000001976a9142a194fc92e27fef9cc2b057bc9060c580cbb484888ac000000000000000001000000000000000000000000ffffffff6a47304402206ee887c9239e5fff0048674bdfff2a8cfbeec6cd4a3ccebcc12fac44b24cc5ac0220718f7c760818fde18bc5ba8457d43d5a145cc4cf13d2a5557cba9107e9f4558d0121026cc34b92cefb3a4537b3edb0b6044c04af27c01583c577823ecc69a9a21119b6"); + + { + input.add_private_key(utxoKey.data(), utxoKey.size()); + Decred::Proto::SigningOutput output; + ANY_SIGN(input, coin); + ASSERT_EQ(output.encoded(), signingOutput.encoded()); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {signature, signature}, {publicKey.bytes}); + Decred::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {}, {}); + Decred::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} + +TEST(DecredCompiler, UtxoWithTree) { + const auto coin = TWCoinTypeDecred; + + const int64_t utxoValue = 10000000; + const int64_t amount = 1000000; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address("Dcur2mcGjmENx4DhNqDctW5wJCVyT3Qeqkx"); + input.set_change_address("DskhnpQqQVgoSuKeyM6Unn2CEbfaenbcJBT"); + input.set_coin_type(coin); + + auto& utxo = *input.add_utxo(); + + auto script = Bitcoin::Script::lockScriptForAddress("DskhnpQqQVgoSuKeyM6Unn2CEbfaenbcJBT", coin); + auto hash = parse_hex("3f7b77a111634faa107c539b0c7db54e2cdbddc0c979568034aaa1ef56d2db90"); + std::reverse(hash.begin(), hash.end()); + utxo.set_amount(utxoValue); + utxo.set_script(script.bytes.data(), script.bytes.size()); + + auto& outpoint = *utxo.mutable_out_point(); + outpoint.set_hash(hash.data(), hash.size()); + outpoint.set_index(0); + outpoint.set_sequence(UINT32_MAX); + outpoint.set_tree(1); + + // build preimage + const auto txInputData = data(input.SerializeAsString()); + EXPECT_GT(txInputData.size(), 0ul); + + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + ASSERT_GT(preImageHashes.size(), 0ul); + + Bitcoin::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + ASSERT_EQ(preSigningOutput.hash_public_keys().size(), 1); + + auto preImageHash = data(preSigningOutput.hash_public_keys()[0].data_hash()); + EXPECT_EQ(hex(preImageHash), + "cca7dcac2ac86f40037a51aeac7b6aaacf57e3304354449e140b698023b3fce7"); +} + +} // namespace TW::Decred::tests \ No newline at end of file diff --git a/tests/chains/Decred/TransactionTests.cpp b/tests/chains/Decred/TransactionTests.cpp new file mode 100644 index 00000000000..098f42c1d11 --- /dev/null +++ b/tests/chains/Decred/TransactionTests.cpp @@ -0,0 +1,86 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Bitcoin/Script.h" +#include "Decred/OutPoint.h" +#include "Decred/Transaction.h" +#include "Decred/TransactionInput.h" +#include "Decred/TransactionOutput.h" +#include "HexCoding.h" +#include "TestUtilities.h" + +#include + +#include + +using namespace TW; +using namespace TW::Decred; + +TEST(DecredTransaction, SignatureHash) { + Decred::Transaction transaction; + + auto po0 = OutPoint( + parse_hex("5897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f"), 0, 0); + transaction.inputs.emplace_back(po0, Bitcoin::Script(), 4294967295); + + auto po1 = OutPoint( + parse_hex("bf829c6bcf84579331337659d31f89dfd138f7f7785802d5501c92333145ca7c"), 18, 0); + transaction.inputs.emplace_back(po1, Bitcoin::Script(), 4294967295); + + auto po2 = OutPoint( + parse_hex("22a6f904655d53ae2ff70e701a0bbd90aa3975c0f40bfc6cc996a9049e31cdfc"), 1, 0); + transaction.inputs.emplace_back(po2, Bitcoin::Script(), 4294967295); + + auto oscript0 = + Bitcoin::Script(parse_hex("76a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac")); + transaction.outputs.emplace_back(18000000, 0, oscript0); + + auto oscript1 = + Bitcoin::Script(parse_hex("0x76a914f2d4db28cad6502226ee484ae24505c2885cb12d88ac")); + transaction.outputs.emplace_back(400000000, 0, oscript1); + + auto preOutScript = + Bitcoin::Script(parse_hex("a914f5916158e3e2c4551c1796708db8367207ed13bb87")); + + // throw exception + EXPECT_EXCEPTION(transaction.computeSignatureHash(preOutScript, transaction.outputs.size(), + TWBitcoinSigHashTypeSingle), + "attempt to sign single input at index larger than the number of outputs"); + + // All + auto hash = transaction.computeSignatureHash(preOutScript, 1, TWBitcoinSigHashTypeAll); + EXPECT_EQ(hex(hash), "05b01b517f41112e279b1a9da89d7847a64e5143dba799d7355b1c6c97b4b397"); + + // AnyoneCanPay|Single + hash = transaction.computeSignatureHash( + preOutScript, 1, + TWBitcoinSigHashType(TWBitcoinSigHashTypeAnyoneCanPay | TWBitcoinSigHashTypeSingle)); + EXPECT_EQ(hex(hash), "fa2a276cd2c4d9f56e05ccae6022ca8c201dccffda36b45c39a031711135bc58"); + + // AnyoneCanPay|None + hash = transaction.computeSignatureHash( + preOutScript, 1, + TWBitcoinSigHashType(TWBitcoinSigHashTypeAnyoneCanPay | TWBitcoinSigHashTypeNone)); + EXPECT_EQ(hex(hash), "82338ab38b4d154c72de55c4700909ad97c0f9bb10d8858759d0c90acb220edb"); + + // All & noWitness + Data result; + transaction.serializeType = SerializeType::noWitness; + transaction.encode(result); + EXPECT_EQ(hex(result), + "01000100035897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f0000000000" + "ffffffffbf829c6bcf84579331337659d31f89dfd138f7f7785802d5501c92333145ca7c1200000000ff" + "ffffff22a6f904655d53ae2ff70e701a0bbd90aa3975c0f40bfc6cc996a9049e31cdfc0100000000ffff" + "ffff0280a812010000000000001976a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac0084d7" + "170000000000001976a914f2d4db28cad6502226ee484ae24505c2885cb12d88ac0000000000000000"); + + // All & onlyWitness + result.clear(); + transaction.serializeType = SerializeType::onlyWitness; + transaction.encode(result); + EXPECT_EQ(hex(result), "0100020003000000000000000000000000ffffffff00000000000000000000000000fff" + "fffff00000000000000000000000000ffffffff00"); +} \ No newline at end of file diff --git a/tests/chains/DigiByte/TWDigiByteTests.cpp b/tests/chains/DigiByte/TWDigiByteTests.cpp index 0548aa115ec..456ca49543e 100644 --- a/tests/chains/DigiByte/TWDigiByteTests.cpp +++ b/tests/chains/DigiByte/TWDigiByteTests.cpp @@ -61,12 +61,12 @@ TEST(DigiByteTransaction, SignTransaction) { protoPlan = plan.proto(); // Sign - auto result = TransactionSigner::sign(Bitcoin::SigningInput(input)); + auto result = Bitcoin::TransactionSigner::sign(Bitcoin::SigningInput(input)); auto signedTx = result.payload(); ASSERT_TRUE(result); Data serialized; - signedTx.encode(serialized, Transaction::SegwitFormatMode::NonSegwit); + signedTx.encode(serialized, Bitcoin::Transaction::SegwitFormatMode::NonSegwit); ASSERT_EQ( hex(serialized), "01000000" @@ -110,7 +110,7 @@ TEST(DigiByteTransaction, SignP2WPKH) { input.add_private_key(utxoKey0.data(), utxoKey0.size()); auto utxo0 = input.add_utxo(); - auto utxo0Script = Script(parse_hex("00144b62694cfdd7bdac59cbed211288ccd5c0dabd02")); + auto utxo0Script = Bitcoin::Script(parse_hex("00144b62694cfdd7bdac59cbed211288ccd5c0dabd02")); utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); utxo0->set_amount(utxo_amount); auto hash0 = parse_hex("80a16412a880d13b0c88929397a50341018da2e78b70b313062b4a496fea5940"); @@ -118,7 +118,7 @@ TEST(DigiByteTransaction, SignP2WPKH) { utxo0->mutable_out_point()->set_index(1); utxo0->mutable_out_point()->set_sequence(UINT32_MAX); - auto result = TransactionSigner::sign(input); + auto result = Bitcoin::TransactionSigner::sign(input); ASSERT_TRUE(result) << std::to_string(result.error()); auto signedTx = result.payload(); diff --git a/tests/chains/EOS/AddressTests.cpp b/tests/chains/EOS/AddressTests.cpp index 14c41a2e0e0..8a14d9059cb 100644 --- a/tests/chains/EOS/AddressTests.cpp +++ b/tests/chains/EOS/AddressTests.cpp @@ -47,16 +47,16 @@ TEST(EOSAddress, FromPrivateKey) { "e6b783120a21cb234d8e15077ce186c47261d1043781ab8b16b84f2acd377668", "bb96c0a4a6ec9c93ccc0b2cbad6b0e8110b9ca4731aef9c6937b99552a319b03"}; - Type privTypes[]{Type::Legacy, Type::Legacy, Type::ModernR1, Type::ModernR1}; + Type privTypes[]{Type::Legacy, Type::Legacy, Type::ModernR1, Type::ModernK1}; std::string pubArray[]{"EOS6TFKUKVvtvjRq9T4fV9pdxNUuJke92nyb4rzSFtZfdR5ssmVuY", "EOS5YtaCcbPJ3BknNBTDezE9eJoGNnAVuUwT8bnxhSRS5dqRvyfxr", "PUB_R1_67itCyDj42CRgtpyP4fLbAccBYnVHGeZQujQAeK3fyNbvfvZM6", - "PUB_R1_5DpVkbrMBDnY4JRhiEdHLmdLDKGQLNfL7X7it2pqT7Uk83ccDL"}; + "PUB_K1_6enPVMggisfqVVRZ1tj47d9UeHK46CBssoCmAz6sLDMBdtZk78"}; for (int i = 0; i < 4; i++) { const auto privateKey = PrivateKey(parse_hex(privArray[i])); - const auto publicKey = PublicKey(privateKey.getPublicKey(privTypes[i] == Type::Legacy ? TWPublicKeyTypeSECP256k1 : TWPublicKeyTypeNIST256p1)); + const auto publicKey = PublicKey(privateKey.getPublicKey(privTypes[i] == Type::ModernR1 ? TWPublicKeyTypeNIST256p1 : TWPublicKeyTypeSECP256k1)); const auto address = Address(publicKey, privTypes[i]); ASSERT_EQ(address.string(), pubArray[i]); @@ -68,6 +68,7 @@ TEST(EOSAddress, IsValid) { ASSERT_TRUE(Address::isValid("PUB_R1_6pQRUVU5vdneRnmjSiZPsvu3zBqcptvg6iK2Vz4vKo4ugnzow3")); ASSERT_TRUE(Address::isValid("EOS5mGcPvsqFDe8YRrA3yMMjQgjrCa6yiCho79KViDhvxh4ajQjgS")); ASSERT_TRUE(Address::isValid("PUB_R1_82dMu3zSSfyHYc4cvWJ6SPsHZWB5mBNAyhL53xiM5xpqmfqetN")); + ASSERT_TRUE(Address::isValid("PUB_K1_6enPVMggisfqVVRZ1tj47d9UeHK46CBssoCmAz6sLDMBdtZk78")); ASSERT_NO_THROW(Address(parse_hex("039d91164ea04f4e751762643ef4ae520690af361b8e677cf341fd213419956b356cb721b7"), Type::ModernR1)); ASSERT_NO_THROW(Address(parse_hex("02d3c8e736a9a50889766caf3c37bd16e2fecc7340b3130e25d4c01b153f996a10a78afc0e"), Type::Legacy)); diff --git a/tests/chains/EOS/TWAnySignerTests.cpp b/tests/chains/EOS/TWAnySignerTests.cpp index f69ebc4ec80..1b2face2c2d 100644 --- a/tests/chains/EOS/TWAnySignerTests.cpp +++ b/tests/chains/EOS/TWAnySignerTests.cpp @@ -39,7 +39,7 @@ TEST(TWAnySignerEOS, Sign) { ANY_SIGN(input, TWCoinTypeEOS); EXPECT_EQ(output.error(), Common::Proto::OK); - EXPECT_EQ(output.json_encoded(), R"({"compression":"none","packed_context_free_data":"","packed_trx":"7c59a35cd6679a1f3d4800000000010000000080a920cd000000572d3ccdcd010000000080a920cd00000000a8ed3232330000000080a920cd0000000000ea3055e09304000000000004544b4e00000000126d79207365636f6e64207472616e7366657200","signatures":["SIG_K1_KfCdjsrTnx5cBpbA5cUdHZAsRYsnC9uKzuS1shFeqfMCfdZwX4PBm9pfHwGRT6ffz3eavhtkyNci5GoFozQAx8P8PBnDmj"]})"); + EXPECT_EQ(output.json_encoded(), R"({"compression":"none","packed_context_free_data":"","packed_trx":"6e67a35cd6679a1f3d4800000000010000000080a920cd000000572d3ccdcd010000000080a920cd00000000a8ed3232330000000080a920cd0000000000ea3055e09304000000000004544b4e00000000126d79207365636f6e64207472616e7366657200","signatures":["SIG_K1_K9RdLC7DEDWjTfR64GU8BtDHcAjzR1ntcT651JMcfHNTpdsvDrUwfyzF1FkvL9fxEi2UCtGJZ9zYoNbJoMF1fbU64cRiJ7"]})"); } input.set_private_key_type(Proto::KeyType::LEGACY); diff --git a/tests/chains/EOS/TransactionCompilerTests.cpp b/tests/chains/EOS/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..81954d4506a --- /dev/null +++ b/tests/chains/EOS/TransactionCompilerTests.cpp @@ -0,0 +1,94 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "EOS/Signer.h" + +#include "proto/EOS.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(EOSCompiler, CompileWithSignatures) { + /// Step 1: Prepare transaction input (protobuf) + const auto coin = TWCoinTypeEOS; + EOS::Proto::SigningInput input; + auto chainId = parse_hex("cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f"); + auto refBlock = parse_hex("000067d6f6a7e7799a1f3d487439a679f8cf95f1c986f35c0d2fa320f51a7144"); + auto key = parse_hex("559aead08264d5795d3909718cdd05abd49572e84fe55590eef31a88a08fdffd"); + + auto& asset = *input.mutable_asset(); + asset.set_amount(300000); + asset.set_decimals(4); + asset.set_symbol("TKN"); + + input.set_chain_id(chainId.data(), chainId.size()); + input.set_reference_block_id(refBlock.data(), refBlock.size()); + input.set_reference_block_time(1554209118); + input.set_currency("token"); + input.set_sender("token"); + input.set_recipient("eosio"); + input.set_memo("my second transfer"); + input.set_private_key(key.data(), key.size()); + input.set_private_key_type(EOS::Proto::KeyType::MODERNK1); + + auto txInputData = data(input.SerializeAsString()); + { + EOS::Proto::SigningOutput output; + ANY_SIGN(input, coin); + + EXPECT_EQ(output.error(), Common::Proto::OK); + EXPECT_EQ(output.json_encoded(), R"({"compression":"none","packed_context_free_data":"","packed_trx":"6e67a35cd6679a1f3d4800000000010000000080a920cd000000572d3ccdcd010000000080a920cd00000000a8ed3232330000000080a920cd0000000000ea3055e09304000000000004544b4e00000000126d79207365636f6e64207472616e7366657200","signatures":["SIG_K1_K9RdLC7DEDWjTfR64GU8BtDHcAjzR1ntcT651JMcfHNTpdsvDrUwfyzF1FkvL9fxEi2UCtGJZ9zYoNbJoMF1fbU64cRiJ7"]})"); + } + + /// Step 2: Obtain preimage hash + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + ASSERT_GT(preImageHashes.size(), 0ul); + + TxCompiler::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImageHash = data(preSigningOutput.data_hash()); + EXPECT_EQ(hex(preImageHash), "14fc3299ee3e1113096bf1869dfa14c04a7ffdedd8ebdabf530683e4cfcd726c"); + + // Simulate signature, normally obtained from signature server + const PublicKey publicKey = PrivateKey(key).getPublicKey(TWPublicKeyTypeNIST256p1); + const auto signature = parse_hex("1f6c4efceb5a6dadab271fd7e2153d97d22690938475b23f017cf9ec29e20d25725e90e541e130daa83c38fc4c933725f05837422c3f4a51f8c1d07208c8fd5e0b"); // data("SIG_K1_K9RdLC7DEDWjTfR64GU8BtDHcAjzR1ntcT651JMcfHNTpdsvDrUwfyzF1FkvL9fxEi2UCtGJZ9zYoNbJoMF1fbU64cRiJ7"); + + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, txInputData, {signature}, {publicKey.bytes}); + + const auto ExpectedTx = R"({"compression":"none","packed_context_free_data":"","packed_trx":"6e67a35cd6679a1f3d4800000000010000000080a920cd000000572d3ccdcd010000000080a920cd00000000a8ed3232330000000080a920cd0000000000ea3055e09304000000000004544b4e00000000126d79207365636f6e64207472616e7366657200","signatures":["SIG_K1_K9RdLC7DEDWjTfR64GU8BtDHcAjzR1ntcT651JMcfHNTpdsvDrUwfyzF1FkvL9fxEi2UCtGJZ9zYoNbJoMF1fbU64cRiJ7"]})"; + { + EOS::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.json_encoded(), ExpectedTx); + } + + input.set_private_key_type(EOS::Proto::KeyType::LEGACY); + { + EOS::Proto::SigningOutput output; + ANY_SIGN(input, coin); + EXPECT_EQ(output.error(), Common::Proto::Error_internal); + EXPECT_TRUE(output.json_encoded().empty()); + } +} diff --git a/tests/chains/EOS/TransactionTests.cpp b/tests/chains/EOS/TransactionTests.cpp index 98e8c133b21..6ce41687ca0 100644 --- a/tests/chains/EOS/TransactionTests.cpp +++ b/tests/chains/EOS/TransactionTests.cpp @@ -4,34 +4,32 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "EOS/Transaction.h" -#include "EOS/Signer.h" #include "EOS/Action.h" #include "EOS/Address.h" -#include "PrivateKey.h" -#include "HexCoding.h" +#include "EOS/Signer.h" +#include "EOS/Transaction.h" #include "Hash.h" +#include "HexCoding.h" +#include "PrivateKey.h" #include using namespace TW; namespace TW::EOS::tests { -static std::string k1Sigs[5] { - "SIG_K1_KfCdjsrTnx5cBpbA5cUdHZAsRYsnC9uKzuS1shFeqfMCfdZwX4PBm9pfHwGRT6ffz3eavhtkyNci5GoFozQAx8P8PBnDmj", - "SIG_K1_K6wW678ngyWT7fgR4nNqm5XoKZBp9NDN4tKsctyzzADjXn15iAH9tcnQ393t6uvsqYxHKjdnxxduT1nyKMLiZbRqL7dHYr", - "SIG_K1_K6cUbZX6xfWcV5iotVprnf12Lc5AmV8SKmN5hVdv39gcM8wfEcwcNScvTuGLWpWzDT463dyhNmUfMB4nqt7tJVFnzx8mSi", - "SIG_K1_Khj7xhMd8HxrT6dUzuwiFM1MfMHtog5jCygJj7ADvdmUGkzZkmjymZXucEAud3whJ2qsMcxHcKtLWs8Ndm6be14qjTAH2a", - "SIG_K1_K93MjjE39CSH7kwJBgoRsSF2VaH6a8psQKU29nSg4xxxrVhz2iQuubyyB5t2ACZFFYSkNHSdYia5efhnW6Z9SPtbQTquMY" -}; - -static std::string r1Sigs[5] { - "SIG_R1_Kbywy4Mjun4Ymrh23Xk5yRtKJxcDWaDjQjLKERAny6Vs6oT1DYoEdoAj9AJK9iukHdEd9HBYnf3GmLtA55JFY5VaNszJMa", - "SIG_R1_KAhJJg4QGYBWY7hG6BKGAbW57fg6g8xTh3LG3Sss3bGv4BwiwHmRV1jsgh6hrnVRUoCaKMbJQzzWy9HXy6PnDmfJ6fbZMJ", - "SIG_R1_KxAwVKfpLr2MeK4aSAp5LSi2Vohsp94Uhk5UvZZDUJqd7ccBkhc2kYY1L6z5rjRNNo7BeP1Qr6H2xPFqo54YQ6DjczAqLW", - "SIG_R1_K1isJT8pJhkrHi3mcvrfY12nY6jirMCWaAHWuBXvu2ondcm3QHkgdaTwERskftZ9cqB5k2r8ajoYS4VWsiivjbd56D6pxX", - "SIG_R1_KWtgvnj2LaaYdtBTjM7bTR23LPBytDHFE7gPEfGTZ7PWc4yc6piPuPUHsVJVkvKmpW2gEUhq3toCfjkt34itSxMgekovdG" -}; +static std::string k1Sigs[5]{ + "SIG_K1_K9RdLC7DEDWjTfR64GU8BtDHcAjzR1ntcT651JMcfHNTpdsvDrUwfyzF1FkvL9fxEi2UCtGJZ9zYoNbJoMF1fbU64cRiJ7", + "SIG_K1_K4oXhfa8xJnAB26EeTz8FLfZtY4kw4kjqUqQLz5snryP7USRJ7yGyuBBfYzTBtZ8djBo87pAW53xHsUxjvQvaKHGQRhKd5", + "SIG_K1_K2BumXj5Qtk2CtaYe6EvMSZ2JXLCFeNsTQirx4cnZFURBFpkchuS4sNFxGLH5Qrdv4R7cN4rax6ZF9HBMz4f4d6GFoTNN2", + "SIG_K1_K8LNseWYiePTM646LZduWevssozJ9t3gaNe2ipZfXbbSFsx36dJFXnk5UBasT2G3cX1Niu7LSUVFspkDYSSPxMFGbWcAvk", + "SIG_K1_Jx2JFftzdx28PZXkmoeWk2afm3KHYsn5knYxynA3GrGevMEJVd1GhcuS5h3f2wdUS2ZUojqycyyVizyJFVSajeR2LZGnJr"}; + +static std::string r1Sigs[5]{ + "SIG_R1_KC4qBxibZpXLESzN37F46Jv8w7dQtyPDeRmeFs8Z4DmEFhr3FACLkjcbSLkVN7acBt4eb6zUa9N76UfJncr4htxCSe7huZ", + "SIG_R1_KaWNQefJReykjKUsS51caChJRgeywUoeuucFReyKY1SPNveSoFFVgxT3jEzW56ZLtpN7qGgekoSNsKs1BzzyZ9snhCALcG", + "SIG_R1_KarN7JJxHeKgRJLmscWzCsdDpfdktWrBGJLvVFN7RYZpSeNHBsqNV7dKqxkvKtbhsqHukLKw1EQNTjcUcxUD6hUTVvNWcP", + "SIG_R1_KvHdcwEDW8RQWEPTA3BoK9RVZqtAwKqVvYQN9Wz44XUrzjrNyRkpc7vguqc8q6FLMJBUUen59hyXM3BuLvvrp2x4S6m1o8", + "SIG_R1_KZB6ivprUS1zwGxMxZQJ7UvWk4Tpvoo6WiFKUPJuUHj4Es39ejiFVoD7ZB6MfSJxAaRPvnAF39hnApwzFAM8Erxmj3Suvm"}; TEST(EOSTransaction, Serialization) { ASSERT_THROW(TransferAction("token", "eosio", "token", Asset::fromString("-20.1234 TKN"), "my first transfer"), std::invalid_argument); @@ -40,24 +38,22 @@ TEST(EOSTransaction, Serialization) { int32_t referenceBlockTime = 1554121728; auto chainId = parse_hex("cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f"); - Transaction tx {referenceBlockId, referenceBlockTime}; + Transaction tx{referenceBlockId, referenceBlockTime}; tx.actions.push_back(TransferAction("token", "eosio", "token", Asset::fromString("20.1234 TKN"), "my first transfer")); ASSERT_EQ(tx.actions.back().serialize().dump(), "{\"account\":\"token\",\"authorizations\":[{\"actor\":\"eosio\",\"permission\":\"active\"}],\"data\":\"0000000000ea30550000000080a920cd121203000000000004544b4e00000000116d79206669727374207472616e73666572\",\"name\":\"transfer\"}"); Data buf; tx.serialize(buf); - Signer signer {chainId}; + Signer signer{chainId}; ASSERT_EQ( hex(buf), - "1e04a25cdc46a452d92c00000000010000000080a920cd000000572d3ccdcd010000000000ea305500000000a8ed3232320000000000ea30550000000080a920cd121203000000000004544b4e00000000116d79206669727374207472616e7366657200" - ); + "1012a25cdc46a452d92c00000000010000000080a920cd000000572d3ccdcd010000000000ea305500000000a8ed3232320000000000ea30550000000080a920cd121203000000000004544b4e00000000116d79206669727374207472616e7366657200"); ASSERT_EQ( hex(signer.hash(tx)), - "5de974bb90b940b462688609735a1dd522fa853aba765c30d14bedd27d719dd1" - ); + "acbf7e6beb6fd4e224462e87c1d70bca6a15b76f8d9e0b31782c5cdfd493b050"); // make transaction invalid and see if signing succeeds tx.maxNetUsageWords = UINT32_MAX; @@ -66,20 +62,18 @@ TEST(EOSTransaction, Serialization) { referenceBlockId = parse_hex("000067d6f6a7e7799a1f3d487439a679f8cf95f1c986f35c0d2fa320f51a7144"); referenceBlockTime = 1554209118; - Transaction tx2 {referenceBlockId, referenceBlockTime}; + Transaction tx2{referenceBlockId, referenceBlockTime}; tx2.actions.push_back(TransferAction("token", "token", "eosio", Asset::fromString("30.0000 TKN"), "my second transfer")); buf.clear(); tx2.serialize(buf); ASSERT_EQ( hex(buf), - "7c59a35cd6679a1f3d4800000000010000000080a920cd000000572d3ccdcd010000000080a920cd00000000a8ed3232330000000080a920cd0000000000ea3055e09304000000000004544b4e00000000126d79207365636f6e64207472616e7366657200" - ); + "6e67a35cd6679a1f3d4800000000010000000080a920cd000000572d3ccdcd010000000080a920cd00000000a8ed3232330000000080a920cd0000000000ea3055e09304000000000004544b4e00000000126d79207365636f6e64207472616e7366657200"); ASSERT_EQ( hex(signer.hash(tx2)), - "4dac38a8ad7f095a09ec0eb0cbd060c9d8ea0a842535d369c9ce526cdf1b5d85" - ); + "14fc3299ee3e1113096bf1869dfa14c04a7ffdedd8ebdabf530683e4cfcd726c"); ASSERT_NO_THROW(tx2.serialize()); @@ -90,8 +84,7 @@ TEST(EOSTransaction, Serialization) { ASSERT_EQ( tx2.signatures.back().string(), - k1Sigs[i] - ); + k1Sigs[i]); } // verify r1 sigs @@ -101,9 +94,22 @@ TEST(EOSTransaction, Serialization) { ASSERT_EQ( tx2.signatures.back().string(), - r1Sigs[i] - ); + r1Sigs[i]); } + + referenceBlockId = parse_hex(""); + referenceBlockTime = 0; + EXPECT_ANY_THROW(new Transaction(referenceBlockId, referenceBlockTime)); + + referenceBlockId = parse_hex("000067d6f6a7e7799a1f3d487439a679f8cf95f1c986f35c0d2fa320f51a7144"); + referenceBlockTime = 1554209118; + Transaction tx3{referenceBlockId, referenceBlockTime}; + Data extBuf; + auto ext = Extension(uint16_t(0), extBuf); + tx3.transactionExtensions.push_back(ext); + buf.clear(); + tx3.serialize(buf); + ASSERT_EQ(hex(buf), "6e67a35cd6679a1f3d48000000000001000000"); } TEST(EOSTransaction, formatDate) { @@ -113,4 +119,9 @@ TEST(EOSTransaction, formatDate) { EXPECT_EQ(Transaction::formatDate(std::numeric_limits::max()), "2038-01-19T03:14:07"); } +TEST(EOSTransaction, Create) { + auto emptyData = Data{}; + EXPECT_ANY_THROW(new Transaction(emptyData, 0)); +} + } // namespace TW::EOS::tests diff --git a/tests/chains/Ethereum/AddressTests.cpp b/tests/chains/Ethereum/AddressTests.cpp index 2020ea8c801..1a6da54e421 100644 --- a/tests/chains/Ethereum/AddressTests.cpp +++ b/tests/chains/Ethereum/AddressTests.cpp @@ -49,6 +49,9 @@ TEST(EthereumAddress, FromPrivateKey) { const auto address = Address(publicKey); ASSERT_EQ(address.string(), "0xAc1ec44E4f0ca7D172B7803f6836De87Fb72b309"); + + const auto publicKey2 = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeED25519)); + EXPECT_ANY_THROW(new Address(publicKey2)); } TEST(EthereumAddress, IsValid) { @@ -56,4 +59,8 @@ TEST(EthereumAddress, IsValid) { ASSERT_TRUE(Address::isValid("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")); } +TEST(EthereumAddress, FromData) { + EXPECT_ANY_THROW(new Address(Data{})); +} + } // namespace TW::Ethereum::tests diff --git a/tests/chains/Ethereum/TransactionCompilerTests.cpp b/tests/chains/Ethereum/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..79c0feb2c7c --- /dev/null +++ b/tests/chains/Ethereum/TransactionCompilerTests.cpp @@ -0,0 +1,157 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" +#include "uint256.h" + +#include "proto/Ethereum.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(EthereumCompiler, CompileWithSignatures) { + /// Step 1: Prepare transaction input (protobuf) + const auto coin = TWCoinTypeEthereum; + const auto txInputData0 = + TransactionCompiler::buildInput(coin, + "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F", // from + "0x3535353535353535353535353535353535353535", // to + "1000000000000000000", // amount + "ETH", // asset + "", // memo + "" // chainId + ); + + // Check, by parsing + EXPECT_EQ((int)txInputData0.size(), 61); + Ethereum::Proto::SigningInput input; + ASSERT_TRUE(input.ParseFromArray(txInputData0.data(), (int)txInputData0.size())); + EXPECT_EQ(hex(input.chain_id()), "01"); + EXPECT_EQ(input.to_address(), "0x3535353535353535353535353535353535353535"); + ASSERT_TRUE(input.transaction().has_transfer()); + EXPECT_EQ(hex(input.transaction().transfer().amount()), "0de0b6b3a7640000"); + + // Set a few other values + const auto nonce = store(uint256_t(11)); + const auto gasPrice = store(uint256_t(20000000000)); + const auto gasLimit = store(uint256_t(21000)); + input.set_nonce(nonce.data(), nonce.size()); + input.set_gas_price(gasPrice.data(), gasPrice.size()); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_tx_mode(Ethereum::Proto::Legacy); + + // Serialize back, this shows how to serialize SigningInput protobuf to byte array + const auto txInputData = data(input.SerializeAsString()); + EXPECT_EQ(txInputData.size(), 75ul); + + /// Step 2: Obtain preimage hash + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + ASSERT_GT(preImageHashes.size(), 0ul); + + TxCompiler::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::SigningError::OK); + + auto preImageHash = data(preSigningOutput.data_hash()); + EXPECT_EQ(hex(preImageHash), "15e180a6274b2f6a572b9b51823fce25ef39576d10188ecdcd7de44526c47217"); + + // Simulate signature, normally obtained from signature server + const Data publicKeyData = + parse_hex("044bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382ce28cab79ad711" + "9ee1ad3ebcdb98a16805211530ecc6cfefa1b88e6dff99232a"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1Extended); + const auto signature = + parse_hex("360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07b53bd9d1376e15a19" + "1d844db458893b928f3efbfee90c9febf51ab84c9796677900"); + + // Verify signature (pubkey & hash & signature) + { EXPECT_TRUE(publicKey.verify(signature, preImageHash)); } + + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, txInputData, {signature}, {publicKeyData}); + + const auto ExpectedTx = + "f86c0b8504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a0" + "360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07ba053bd9d1376e15a191d844db4" + "58893b928f3efbfee90c9febf51ab84c97966779"; + { + EXPECT_EQ(outputData.size(), 217ul); + Ethereum::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.encoded().size(), 110ul); + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + Ethereum::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(txInputData.data(), (int)txInputData.size())); + auto key = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); + signingInput.set_private_key(key.data(), key.size()); + + Ethereum::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {}, {}); + Ethereum::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_signatures_count); + } +} + +TEST(EthereumCompiler, BuildTransactionInput) { + const auto coin = TWCoinTypeEthereum; + const auto txInputData0 = + TransactionCompiler::buildInput(coin, + "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F", // from + "0x3535353535353535353535353535353535353535", // to + "1000000000000000000", // amount + "ETH", // asset + "Memo", // memo + "05" // chainId + ); + + // Check, by parsing + EXPECT_EQ(txInputData0.size(), 61ul); + Ethereum::Proto::SigningInput input; + ASSERT_TRUE(input.ParseFromArray(txInputData0.data(), (int)txInputData0.size())); + EXPECT_EQ(hex(input.chain_id()), "05"); + EXPECT_EQ(input.to_address(), "0x3535353535353535353535353535353535353535"); + ASSERT_TRUE(input.transaction().has_transfer()); + EXPECT_EQ(hex(input.transaction().transfer().amount()), "0de0b6b3a7640000"); +} + +TEST(EthereumCompiler, BuildTransactionInputInvalidAddress) { + const auto coin = TWCoinTypeEthereum; + EXPECT_EXCEPTION( + TransactionCompiler::buildInput(coin, + "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F", // from + "__INVALID_ADDRESS__", // to + "1000000000000000000", // amount + "ETH", // asset + "", // memo + "" // chainId + ), + "Invalid to address"); +} diff --git a/tests/chains/FIO/SignerTests.cpp b/tests/chains/FIO/SignerTests.cpp index a2e917b47c3..16d6299c4e2 100644 --- a/tests/chains/FIO/SignerTests.cpp +++ b/tests/chains/FIO/SignerTests.cpp @@ -6,6 +6,7 @@ #include "FIO/Actor.h" #include "FIO/Signer.h" +#include "FIO/TransactionBuilder.h" #include "Base58.h" #include "Hash.h" @@ -31,14 +32,14 @@ TEST(FIOSigner, SignInternals) { append(pk2, pk.bytes); EXPECT_EQ("5KEDWtAUJcFX6Vz38WXsAQAv2geNqT7UaZC8gYu9kTuryr3qkri", Base58::encodeCheck(pk2)); } - Data rawData = parse_hex("4e46572250454b796d7296eec9e8896327ea82dd40f2cd74cf1b1d8ba90bcd774a26285e19fac10ac5390000000001003056372503a85b0000c6eaa6645232017016f2cc12266c6b00000000a8ed3232bd010f6164616d4066696f746573746e657403034254432a626331717679343037347267676b647232707a773576706e6e3632656730736d7a6c7877703730643776034554482a30786365356342366339324461333762624261393142643430443443394434443732344133613846353103424e422a626e6231747333646735346170776c76723968757076326e306a366534367135347a6e6e75736a6b397300000000000000007016f2cc12266c6b0e726577617264734077616c6c6574000000000000000000000000000000000000000000000000000000000000000000"); + Data rawData = parse_hex("4e46572250454b796d7296eec9e8896327ea82dd40f2cd74cf1b1d8ba90bcd77b0ae295e50c3400a6dee00000000010000980ad20ca85be0e1d195ba85e7cd01102b2f46fca756b200000000a8ed32325d3546494f37754d5a6f6565693548745841443234433479436b70575762663234626a597472524e6a57646d474358485a63637775694500ca9a3b0000000080b2e60e00000000102b2f46fca756b20e726577617264734077616c6c6574000000000000000000000000000000000000000000000000000000000000000000"); Data hash = Hash::sha256(rawData); - EXPECT_EQ("0f3cca0f50da4200b2858f65de1ea4530a9afd9e4bfc0b6b7196e36c25cc7a8b", hex(hash)); + EXPECT_EQ("6a82a57fb9bfc43918aa757d6094ba71fa2c7ece1691c4b8551a0607273771d7", hex(hash)); Data sign2 = Signer::signData(pk, rawData); - EXPECT_EQ("1f4ae8d1b993f94d0de4b249d5185481770de0711863ad640b3aac21de598fcc02761c6e5395106bafb7b09aab1c7aa5ac0573dbd821c2d255725391a5105d30d1", hex(sign2)); + EXPECT_EQ("1f6ccee1f4cd188cc8aefa63f8fda8c90c0493ca1504806d3a26a7300a9687bb701f188337bc9a32f01ee0c2ecf030aee197b050460d72f7272cc6ce36ef14c95b", hex(sign2)); string sigStr = Signer::signatureToBase58(sign2); - EXPECT_EQ("SIG_K1_K54CA1jmhgWrSdvrNrkokPyvqh7dwsSoQHNU9xgD3Ezf6cJySzhKeUubVRqmpYdnjoP1DM6SorroVAgrCu3qqvJ9coAQ6u", sigStr); + EXPECT_EQ("SIG_K1_K9VRCnvaTYN7vgcoVKVXgyJTdKUGV8hLXgFLoEbvqAcFxy7DXQ1rSnAfEuabi4ATkgmvnpaSBdVFN7TBtM1wrbZYqeJQw9", sigStr); EXPECT_TRUE(Signer::verify(pk.getPublicKey(TWPublicKeyTypeSECP256k1), hash, sign2)); } @@ -75,4 +76,35 @@ TEST(FIOSigner, Actor) { } } +TEST(FIOSigner, compile) { + const Data chainId = parse_hex("4e46572250454b796d7296eec9e8896327ea82dd40f2cd74cf1b1d8ba90bcd77"); + // 5KEDWtAUJcFX6Vz38WXsAQAv2geNqT7UaZC8gYu9kTuryr3qkri FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf + const PrivateKey privKeyBA = PrivateKey(parse_hex("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035")); + const PublicKey pubKey6M = privKeyBA.getPublicKey(TWPublicKeyTypeSECP256k1); + const Address addr6M(pubKey6M); + Proto::SigningInput input; + input.set_expiry(1579790000); + input.set_tpid("rewards@wallet"); + input.set_owner_public_key(addr6M.string()); + + input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); + input.mutable_chain_params()->set_head_block_number(50000); + input.mutable_chain_params()->set_ref_block_prefix(4000123456); + + input.mutable_action()->mutable_transfer_message()->set_amount(1000000000); + input.mutable_action()->mutable_transfer_message()->set_fee(250000000); + input.mutable_action()->mutable_transfer_message()->set_payee_public_key("FIO7uMZoeei5HtXAD24C4yCkpWWbf24bjYtrRNjWdmGCXHZccwuiE"); + + auto txBytes = TransactionBuilder::buildUnsignedTxBytes(input); + // create signature + Data sigBuf(chainId); + append(sigBuf, txBytes); + append(sigBuf, TW::Data(32)); // context_free + Data signature = Signer::signData(privKeyBA, sigBuf); + + Proto::SigningOutput result = Signer::compile(input, signature); + EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"b0ae295e50c3400a6dee00000000010000980ad20ca85be0e1d195ba85e7cd01102b2f46fca756b200000000a8ed32325d3546494f37754d5a6f6565693548745841443234433479436b70575762663234626a597472524e6a57646d474358485a63637775694500ca9a3b0000000080b2e60e00000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_K9VRCnvaTYN7vgcoVKVXgyJTdKUGV8hLXgFLoEbvqAcFxy7DXQ1rSnAfEuabi4ATkgmvnpaSBdVFN7TBtM1wrbZYqeJQw9"]})" + , result.json()); +} + } // namespace TW::FIO::tests diff --git a/tests/chains/FIO/TransactionBuilderTests.cpp b/tests/chains/FIO/TransactionBuilderTests.cpp index 4c5f47f99db..cc11ba5dfd6 100644 --- a/tests/chains/FIO/TransactionBuilderTests.cpp +++ b/tests/chains/FIO/TransactionBuilderTests.cpp @@ -6,6 +6,7 @@ #include "FIO/Action.h" #include "FIO/NewFundsRequest.h" +#include "FIO/Signer.h" #include "FIO/Transaction.h" #include "FIO/TransactionBuilder.h" @@ -346,4 +347,215 @@ TEST(FIOTransactionBuilder, encodeString) { } } +TEST(FIOTransactionBuilder, buildUnsignedTxBytes) { + { + // Test register_fio_address_message + Proto::SigningInput input; + + input.set_expiry(1579784511); + input.set_tpid("rewards@wallet"); + input.set_owner_public_key(gAddr6M.string()); + + input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); + input.mutable_chain_params()->set_head_block_number(39881); + input.mutable_chain_params()->set_ref_block_prefix(4279583376); + + input.mutable_action()->mutable_register_fio_address_message()->set_fio_address("adam@fiotestnet"); + input.mutable_action()->mutable_register_fio_address_message()->set_owner_fio_public_key(gAddr6M.string()); + input.mutable_action()->mutable_register_fio_address_message()->set_fee(5000000000); + + auto result = TransactionBuilder::buildUnsignedTxBytes(input); + //packed_trx: 3f99295ec99b904215ff0000000001003056372503a85b0000c6eaa66498ba01102b2f46fca756b200000000a8ed3232650f6164616d4066696f746573746e65743546494f366d31664d645470526b52426e6564765973685843784c4669433573755255384b44667838787874587032686e7478706e6600f2052a01000000102b2f46fca756b20e726577617264734077616c6c657400 + EXPECT_EQ("3f99295ec99b904215ff0000000001003056372503a85b0000c6eaa66498ba01102b2f46fca756b200000000a8ed3232650f6164616d4066696f746573746e65743546494f366d31664d645470526b52426e6564765973685843784c4669433573755255384b44667838787874587032686e7478706e6600f2052a01000000102b2f46fca756b20e726577617264734077616c6c657400" + , hex(result)); + } + { + // Test add_pub_address_message + Proto::SigningInput input; + + input.set_expiry(1579729429); + input.set_tpid("rewards@wallet"); + input.set_owner_public_key(gAddr6M.string()); + + input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); + input.mutable_chain_params()->set_head_block_number(11565); + input.mutable_chain_params()->set_ref_block_prefix(4281229859); + + input.mutable_action()->mutable_add_pub_address_message()->set_fee(0); + input.mutable_action()->mutable_add_pub_address_message()->set_fio_address("adam@fiotestnet"); + + TW::FIO::Proto::PublicAddress *value; + value = input.mutable_action()->mutable_add_pub_address_message()->add_public_addresses(); + value->set_address("bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v"); + value->set_coin_symbol("BTC"); + TW::FIO::Proto::PublicAddress *value1; + value1 = input.mutable_action()->mutable_add_pub_address_message()->add_public_addresses(); + value1->set_address("0xce5cB6c92Da37bbBa91Bd40D4C9D4D724A3a8F51"); + value1->set_coin_symbol("ETH"); + TW::FIO::Proto::PublicAddress *value2; + value2 = input.mutable_action()->mutable_add_pub_address_message()->add_public_addresses(); + value2->set_address("bnb1ts3dg54apwlvr9hupv2n0j6e46q54znnusjk9s"); + value2->set_coin_symbol("BNB"); + + auto result = TransactionBuilder::buildUnsignedTxBytes(input); + + EXPECT_EQ("15c2285e2d2d23622eff0000000001003056372503a85b0000c6eaa664523201102b2f46fca756b200000000a8ed3232c9010f6164616d4066696f746573746e65740303425443034254432a626331717679343037347267676b647232707a773576706e6e3632656730736d7a6c787770373064377603455448034554482a30786365356342366339324461333762624261393142643430443443394434443732344133613846353103424e4203424e422a626e6231747333646735346170776c76723968757076326e306a366534367135347a6e6e75736a6b39730000000000000000102b2f46fca756b20e726577617264734077616c6c657400" + , hex(result)); + } + + { + // Test transfer_message + Proto::SigningInput input; + input.set_expiry(1579790000); + input.set_tpid("rewards@wallet"); + input.set_owner_public_key(gAddr6M.string()); + + input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); + input.mutable_chain_params()->set_head_block_number(50000); + input.mutable_chain_params()->set_ref_block_prefix(4000123456); + + input.mutable_action()->mutable_transfer_message()->set_amount(1000000000); + input.mutable_action()->mutable_transfer_message()->set_fee(250000000); + input.mutable_action()->mutable_transfer_message()->set_payee_public_key("FIO7uMZoeei5HtXAD24C4yCkpWWbf24bjYtrRNjWdmGCXHZccwuiE"); + + auto result = TransactionBuilder::buildUnsignedTxBytes(input); + EXPECT_EQ("b0ae295e50c3400a6dee00000000010000980ad20ca85be0e1d195ba85e7cd01102b2f46fca756b200000000a8ed32325d3546494f37754d5a6f6565693548745841443234433479436b70575762663234626a597472524e6a57646d474358485a63637775694500ca9a3b0000000080b2e60e00000000102b2f46fca756b20e726577617264734077616c6c657400" + , hex(result)); + } + + { + // Test renew_fio_address_message + Proto::SigningInput input; + input.set_expiry(1579785000); + input.set_tpid("rewards@wallet"); + input.set_owner_public_key(gAddr6M.string()); + + input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); + input.mutable_chain_params()->set_head_block_number(39881); + input.mutable_chain_params()->set_ref_block_prefix(4279583376); + + input.mutable_action()->mutable_renew_fio_address_message()->set_fee(3000000000); + input.mutable_action()->mutable_renew_fio_address_message()->set_fio_address("nick@fiotestnet"); + + auto result = TransactionBuilder::buildUnsignedTxBytes(input); + EXPECT_EQ("289b295ec99b904215ff0000000001003056372503a85b80b1ba2919aea6ba01102b2f46fca756b200000000a8ed32322f0f6e69636b4066696f746573746e6574005ed0b200000000102b2f46fca756b20e726577617264734077616c6c657400" + , hex(result)); + } + +} + +TEST(FIOTransactionBuilder, buildSigningOutput) { + { + // Test register_fio_address_message + Proto::SigningInput input; + + input.set_expiry(1579784511); + input.set_tpid("rewards@wallet"); + input.set_owner_public_key(gAddr6M.string()); + + input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); + input.mutable_chain_params()->set_head_block_number(39881); + input.mutable_chain_params()->set_ref_block_prefix(4279583376); + + input.mutable_action()->mutable_register_fio_address_message()->set_fio_address("adam@fiotestnet"); + input.mutable_action()->mutable_register_fio_address_message()->set_owner_fio_public_key(gAddr6M.string()); + input.mutable_action()->mutable_register_fio_address_message()->set_fee(5000000000); + + Data txBytes = TransactionBuilder::buildUnsignedTxBytes(input); + Data sigBuf = TransactionBuilder::buildPreSignTxData(chainId, txBytes); + // create signature + Data signature = Signer::signData(gPrivKeyBA, sigBuf); + + Proto::SigningOutput result = TransactionBuilder::buildSigningOutput(input, signature); + + EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"3f99295ec99b904215ff0000000001003056372503a85b0000c6eaa66498ba01102b2f46fca756b200000000a8ed3232650f6164616d4066696f746573746e65743546494f366d31664d645470526b52426e6564765973685843784c4669433573755255384b44667838787874587032686e7478706e6600f2052a01000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_K19ugLriG3ApYgjJCRDsy21p9xgsjbDtqBuZrmAEix9XYzndR1kNbJ6fXCngMJMAhxUHfwHAsPnh58otXiJZkazaM1EkS5"]})" + , result.json()); + + } + { + // Test add_pub_address_message + Proto::SigningInput input; + + input.set_expiry(1579729429); + input.set_tpid("rewards@wallet"); + input.set_owner_public_key(gAddr6M.string()); + + input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); + input.mutable_chain_params()->set_head_block_number(11565); + input.mutable_chain_params()->set_ref_block_prefix(4281229859); + + input.mutable_action()->mutable_add_pub_address_message()->set_fee(0); + input.mutable_action()->mutable_add_pub_address_message()->set_fio_address("adam@fiotestnet"); + + TW::FIO::Proto::PublicAddress *value; + value = input.mutable_action()->mutable_add_pub_address_message()->add_public_addresses(); + value->set_address("bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v"); + value->set_coin_symbol("BTC"); + TW::FIO::Proto::PublicAddress *value1; + value1 = input.mutable_action()->mutable_add_pub_address_message()->add_public_addresses(); + value1->set_address("0xce5cB6c92Da37bbBa91Bd40D4C9D4D724A3a8F51"); + value1->set_coin_symbol("ETH"); + TW::FIO::Proto::PublicAddress *value2; + value2 = input.mutable_action()->mutable_add_pub_address_message()->add_public_addresses(); + value2->set_address("bnb1ts3dg54apwlvr9hupv2n0j6e46q54znnusjk9s"); + value2->set_coin_symbol("BNB"); + + Data txBytes = TransactionBuilder::buildUnsignedTxBytes(input); + Data sigBuf = TransactionBuilder::buildPreSignTxData(chainId, txBytes); + // create signature + Data signature = Signer::signData(gPrivKeyBA, sigBuf); + + Proto::SigningOutput result = TransactionBuilder::buildSigningOutput(input, signature); + EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"15c2285e2d2d23622eff0000000001003056372503a85b0000c6eaa664523201102b2f46fca756b200000000a8ed3232c9010f6164616d4066696f746573746e65740303425443034254432a626331717679343037347267676b647232707a773576706e6e3632656730736d7a6c787770373064377603455448034554482a30786365356342366339324461333762624261393142643430443443394434443732344133613846353103424e4203424e422a626e6231747333646735346170776c76723968757076326e306a366534367135347a6e6e75736a6b39730000000000000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_K3zimaMKU8cBhVRPw46KM2u7uQWaAKXrnoeYZ7MEbp6sVJcDQmQR2RtdavpUPwkAnYUkd8NqLun8H48tcxZBcTUgkiPGVJ"]})" + , result.json()); + } + { + // Test transfer_message + Proto::SigningInput input; + input.set_expiry(1579790000); + input.set_tpid("rewards@wallet"); + input.set_owner_public_key(gAddr6M.string()); + + input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); + input.mutable_chain_params()->set_head_block_number(50000); + input.mutable_chain_params()->set_ref_block_prefix(4000123456); + + input.mutable_action()->mutable_transfer_message()->set_amount(1000000000); + input.mutable_action()->mutable_transfer_message()->set_fee(250000000); + input.mutable_action()->mutable_transfer_message()->set_payee_public_key("FIO7uMZoeei5HtXAD24C4yCkpWWbf24bjYtrRNjWdmGCXHZccwuiE"); + + Data txBytes = TransactionBuilder::buildUnsignedTxBytes(input); + Data sigBuf = TransactionBuilder::buildPreSignTxData(chainId, txBytes); + // create signature + Data signature = Signer::signData(gPrivKeyBA, sigBuf); + + Proto::SigningOutput result = TransactionBuilder::buildSigningOutput(input, signature); + EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"b0ae295e50c3400a6dee00000000010000980ad20ca85be0e1d195ba85e7cd01102b2f46fca756b200000000a8ed32325d3546494f37754d5a6f6565693548745841443234433479436b70575762663234626a597472524e6a57646d474358485a63637775694500ca9a3b0000000080b2e60e00000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_K9VRCnvaTYN7vgcoVKVXgyJTdKUGV8hLXgFLoEbvqAcFxy7DXQ1rSnAfEuabi4ATkgmvnpaSBdVFN7TBtM1wrbZYqeJQw9"]})" + , result.json()); + } + { + // Test renew_fio_address_message + Proto::SigningInput input; + input.set_expiry(1579785000); + input.set_tpid("rewards@wallet"); + input.set_owner_public_key(gAddr6M.string()); + + input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); + input.mutable_chain_params()->set_head_block_number(39881); + input.mutable_chain_params()->set_ref_block_prefix(4279583376); + + input.mutable_action()->mutable_renew_fio_address_message()->set_fee(3000000000); + input.mutable_action()->mutable_renew_fio_address_message()->set_fio_address("nick@fiotestnet"); + + Data txBytes = TransactionBuilder::buildUnsignedTxBytes(input); + Data sigBuf = TransactionBuilder::buildPreSignTxData(chainId, txBytes); + // create signature + Data signature = Signer::signData(gPrivKeyBA, sigBuf); + + Proto::SigningOutput result = TransactionBuilder::buildSigningOutput(input, signature); + EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"289b295ec99b904215ff0000000001003056372503a85b80b1ba2919aea6ba01102b2f46fca756b200000000a8ed32322f0f6e69636b4066696f746573746e6574005ed0b200000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_Jxz7oCJ7Z4ECsxqb2utqBcyP3zPQCeQCBws9wWQjyptUKoWVk2AyCVEqtdMHJwqtLniio5Z7npMnaZB8E4pa2G75P9uGkb"]})" + , result.json()); + } +} + } // namespace TW::FIO::TransactionBuilderTests diff --git a/tests/chains/FIO/TransactionCompilerTests.cpp b/tests/chains/FIO/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..6fe67156e4c --- /dev/null +++ b/tests/chains/FIO/TransactionCompilerTests.cpp @@ -0,0 +1,134 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" +#include "uint256.h" + +#include "FIO/Address.h" +#include "FIO/Action.h" +#include "FIO/NewFundsRequest.h" +#include "FIO/Transaction.h" +#include "FIO/TransactionBuilder.h" +#include "FIO/Signer.h" + +#include "proto/FIO.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; +using namespace TW::FIO; +using namespace std; + +TEST(FIOCompiler, CompileWithSignatures) { + Data chainId = parse_hex("4e46572250454b796d7296eec9e8896327ea82dd40f2cd74cf1b1d8ba90bcd77"); + // 5KEDWtAUJcFX6Vz38WXsAQAv2geNqT7UaZC8gYu9kTuryr3qkri FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf + PrivateKey privateKey = PrivateKey(parse_hex("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035")); + PublicKey pubKeyA = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + Address addrA(pubKeyA); + const auto coin = TWCoinTypeFIO; + + /// Step 1: Prepare transaction input (protobuf) + Proto::SigningInput input; + input.set_expiry(1579790000); + input.set_tpid("rewards@wallet"); + input.set_owner_public_key(addrA.string()); + + input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); + input.mutable_chain_params()->set_head_block_number(50000); + input.mutable_chain_params()->set_ref_block_prefix(4000123456); + + input.mutable_action()->mutable_transfer_message()->set_amount(1000000000); + input.mutable_action()->mutable_transfer_message()->set_fee(250000000); + input.mutable_action()->mutable_transfer_message()->set_payee_public_key("FIO7uMZoeei5HtXAD24C4yCkpWWbf24bjYtrRNjWdmGCXHZccwuiE"); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImage = data(preSigningOutput.data()); + auto preImageHash = data(preSigningOutput.data_hash()); + + std::string expectedPreImage = "4e46572250454b796d7296eec9e8896327ea82dd40f2cd74cf1b1d8ba90bcd77b0ae295e50c3400a6dee00000000010000980ad20ca85be0e1d195ba85e7cd01102b2f46fca756b200000000a8ed32325d3546494f37754d5a6f6565693548745841443234433479436b70575762663234626a597472524e6a57646d474358485a63637775694500ca9a3b0000000080b2e60e00000000102b2f46fca756b20e726577617264734077616c6c6574000000000000000000000000000000000000000000000000000000000000000000"; + std::string expectedPreImageHash = "6a82a57fb9bfc43918aa757d6094ba71fa2c7ece1691c4b8551a0607273771d7"; + ASSERT_EQ(hex(preImage), expectedPreImage); + ASSERT_EQ(hex(preImageHash), expectedPreImageHash); + + // create signature + Data signature = Signer::signData(privateKey, preImage); + EXPECT_EQ("1f6ccee1f4cd188cc8aefa63f8fda8c90c0493ca1504806d3a26a7300a9687bb701f188337bc9a32f01ee0c2ecf030aee197b050460d72f7272cc6ce36ef14c95b", hex(signature)); + std::string sigStr = Signer::signatureToBase58(signature); + EXPECT_EQ("SIG_K1_K9VRCnvaTYN7vgcoVKVXgyJTdKUGV8hLXgFLoEbvqAcFxy7DXQ1rSnAfEuabi4ATkgmvnpaSBdVFN7TBtM1wrbZYqeJQw9", sigStr); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(Signer::verify(pubKeyA, preImageHash, signature)); + + const auto ExpectedTx = R"({"compression":"none","packed_context_free_data":"","packed_trx":"b0ae295e50c3400a6dee00000000010000980ad20ca85be0e1d195ba85e7cd01102b2f46fca756b200000000a8ed32325d3546494f37754d5a6f6565693548745841443234433479436b70575762663234626a597472524e6a57646d474358485a63637775694500ca9a3b0000000080b2e60e00000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_K9VRCnvaTYN7vgcoVKVXgyJTdKUGV8hLXgFLoEbvqAcFxy7DXQ1rSnAfEuabi4ATkgmvnpaSBdVFN7TBtM1wrbZYqeJQw9"]})"; + /// Step 3: Compile transaction info + { + const Data outputData = TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {pubKeyA.bytes}); + + TW::FIO::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(ExpectedTx, output.json()); + } + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::FIO::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + signingInput.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + TW::FIO::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.json(), ExpectedTx); + } + { // Negative: not enough signatures + const Data outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {}, {pubKeyA.bytes}); + + TW::FIO::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.json().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + EXPECT_EQ(output.error_message(), "empty signatures or publickeys"); + } + + { // Negative: not enough publicKey + const Data outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {TW::data(sigStr)}, {}); + + TW::FIO::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.json().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + EXPECT_EQ(output.error_message(), "empty signatures or publickeys"); + } + + { // Negative: not one to one + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {TW::data(sigStr)}, {pubKeyA.bytes, pubKeyA.bytes}); + + TW::FIO::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.json().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + EXPECT_EQ(output.error_message(), "signatures and publickeys size can only be one"); + } +} \ No newline at end of file diff --git a/tests/chains/Fantom/TWCoinTypeTests.cpp b/tests/chains/Fantom/TWCoinTypeTests.cpp index 9b497c7ad4b..9d80853a8a8 100644 --- a/tests/chains/Fantom/TWCoinTypeTests.cpp +++ b/tests/chains/Fantom/TWCoinTypeTests.cpp @@ -4,6 +4,9 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. // +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// #include "TestUtilities.h" #include @@ -21,7 +24,8 @@ TEST(TWFantomCoinType, TWCoinType) { ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeFantom), 18); ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeFantom)); - + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeFantom)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeFantom)); assertStringsEqual(symbol, "FTM"); assertStringsEqual(txUrl, "https://ftmscan.com/tx/0xb0a741d882291951de1fac72e90b9baf886ddb0c9c87658a0c206490dfaa5202"); assertStringsEqual(accUrl, "https://ftmscan.com/address/0x9474feb9917b87da6f0d830ba66ee0035835c0d3"); diff --git a/tests/chains/Filecoin/AddressTests.cpp b/tests/chains/Filecoin/AddressTests.cpp index 1bdb728991e..06b889d4376 100644 --- a/tests/chains/Filecoin/AddressTests.cpp +++ b/tests/chains/Filecoin/AddressTests.cpp @@ -74,6 +74,7 @@ static const std::string invalidAddresses[] = { "f15ihq5ibzwki2b4ep2f46avlkrqzhpqgtga7rdrr", "f24vg6ut43yw2h2jqydgbg2xq7x6f4kub3bg6as66", "f3vvmn62lofvhjd2ugzca6sof2j2ubwok6cj4xxbfzz4yuxfkgobpihhd2thlanmsh3w2ptld2gqkn2jvlss44", + "f0vvmn62lofvhjd2ugzca6sof2j2ubwok6cj4xxbfzz4yuxfkgobpihhd2thlanmsh3w2ptld2gqkn2jvlss44", "f410f2oekwcmo2pueydmaq53eic2i62crtbeyuzx2gma", }; @@ -108,6 +109,8 @@ TEST(FilecoinAddress, Equal) { const Address lhs(test.string), rhs(encodedBytes); ASSERT_EQ(lhs, rhs) << "Address(string) != Address(Data)"; } + + EXPECT_ANY_THROW(new Address(Data{})); } TEST(FilecoinAddress, ExpectedProperties) { @@ -139,6 +142,9 @@ TEST(FilecoinAddress, ToBytes) { Address a(test.string); ASSERT_EQ(hex(a.toBytes()), test.encoded) << "Address(" << test.string << ")"; } -} + for (const auto& test : invalidAddresses) + EXPECT_ANY_THROW(new Address(test)); } + +} // TW::Filecoin::tests diff --git a/tests/chains/Filecoin/TWCoinTypeTests.cpp b/tests/chains/Filecoin/TWCoinTypeTests.cpp index ff727a75add..6292d0be1e5 100644 --- a/tests/chains/Filecoin/TWCoinTypeTests.cpp +++ b/tests/chains/Filecoin/TWCoinTypeTests.cpp @@ -12,6 +12,7 @@ #include #include + TEST(TWFilecoinCoinType, TWCoinType) { auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeFilecoin)); auto txId = WRAPS(TWStringCreateWithUTF8Bytes("bafy2bzacedsgjcd6xfhrrymmfrqubb44otlyhvgqkgsh533d5j5hwniiqespm")); diff --git a/tests/chains/Filecoin/TransactionCompilerTests.cpp b/tests/chains/Filecoin/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..53e7a4976d2 --- /dev/null +++ b/tests/chains/Filecoin/TransactionCompilerTests.cpp @@ -0,0 +1,104 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" +#include "uint256.h" + +#include "proto/Filecoin.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(FilecoinCompiler, CompileWithSignatures) { + auto coin = TWCoinTypeFilecoin; + + /// Step 1: Prepare transaction input (protobuf) + Filecoin::Proto::SigningInput input; + auto privateKey = parse_hex("1d969865e189957b9824bd34f26d5cbf357fda1a6d844cbf0c9ab1ed93fa7dbe"); + auto key = PrivateKey(privateKey); + auto publicKey = key.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + auto toAddress = + "f3um6uo3qt5of54xjbx3hsxbw5mbsc6auxzrvfxekn5bv3duewqyn2tg5rhrlx73qahzzpkhuj7a34iq7oifsq"; + uint64_t nonce = 2; + // 600 FIL + // auto value = parse_hex("2086ac351052600000"); + auto value = store(uint256_t(600) * uint256_t(1'000'000'000) * uint256_t(1'000'000'000)); + uint64_t gasLimit = 1000; + // auto gasFeeCap = parse_hex("25f273933db5700000"); + auto gasFeeCap = store(uint256_t(700) * uint256_t(1'000'000'000) * uint256_t(1'000'000'000)); + // auto gasPremium = parse_hex("2b5e3af16b18800000"); + auto gasPremium = store(uint256_t(800) * uint256_t(1'000'000'000) * uint256_t(1'000'000'000)); + + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_to(toAddress); + input.set_nonce(nonce); + input.set_value(value.data(), value.size()); + input.set_gas_limit(gasLimit); + input.set_gas_fee_cap(gasFeeCap.data(), gasFeeCap.size()); + input.set_gas_premium(gasPremium.data(), gasPremium.size()); + input.set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); + + auto txInputData = data(input.SerializeAsString()); + auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + + TxCompiler::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImageHash = data(preSigningOutput.data_hash()); + EXPECT_EQ(hex(preImageHash), "8368c0f622b2c529c7fa147d75aa02aaa7fc13fc4847d4dc57e7a5c59048aafe"); + + // Simulate signature, normally obtained from signature server + const auto signature = key.sign(preImageHash, TWCurveSECP256k1); + + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, txInputData, {signature}, {publicKey.bytes}); + + const auto ExpectedTx = R"({"Message":{"From":"f1z4a36sc7mfbv4z3qwutblp2flycdui3baffytbq","GasFeeCap":"700000000000000000000","GasLimit":1000,"GasPremium":"800000000000000000000","Method":0,"Nonce":2,"To":"f3um6uo3qt5of54xjbx3hsxbw5mbsc6auxzrvfxekn5bv3duewqyn2tg5rhrlx73qahzzpkhuj7a34iq7oifsq","Value":"600000000000000000000"},"Signature":{"Data":"jMRu+OZ/lfppgmqSfGsntFrRLWZnUg3ZYmJTTRLsVt4V1310vR3VKGJpaE6S4sNvDOE6sEgmN9YmfTkPVK2qMgE=","Type":1}})"; + { + Filecoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + ASSERT_EQ(output.json(), ExpectedTx); + } + + // double check + { + Filecoin::Proto::SigningOutput output; + ANY_SIGN(input, coin); + + ASSERT_EQ(output.json(), ExpectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {signature, signature}, {publicKey.bytes}); + Filecoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.json().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {}, {}); + Filecoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.json().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} diff --git a/tests/chains/Groestlcoin/AddressTests.cpp b/tests/chains/Groestlcoin/AddressTests.cpp index 9799147412c..40c335a9f5b 100644 --- a/tests/chains/Groestlcoin/AddressTests.cpp +++ b/tests/chains/Groestlcoin/AddressTests.cpp @@ -10,6 +10,7 @@ #include "HDWallet.h" #include "HexCoding.h" +#include "TestUtilities.h" #include namespace TW::Groestlcoin::tests { @@ -18,14 +19,27 @@ TEST(GroestlcoinAddress, FromPublicKey) { const auto publicKey = PublicKey(parse_hex("03b85cc59b67c35851eb5060cfc3a759a482254553c5857075c9e247d74d412c91"), TWPublicKeyTypeSECP256k1); const auto address = Address(publicKey, 36); ASSERT_EQ(address.string(), "Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM"); + + const auto privateKey = PrivateKey(parse_hex("a1269039e4ffdf43687852d7247a295f0b5bc55e6dda031cffaa3295ca0a9d7a")); + const auto publicKey2 = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeED25519)); + EXPECT_ANY_THROW(new Address(publicKey2, 36)); } TEST(GroestlcoinAddress, Valid) { ASSERT_TRUE(Address::isValid(std::string("Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM"))); + TW::Data addressData; + addressData.push_back(TW::p2pkhPrefix(TWCoinTypeGroestlcoin)); + + auto addressHash = parse_hex("98af0aaca388a7e1024f505c033626d908e3b54a"); + std::copy(addressHash.begin(), addressHash.end(), std::back_inserter(addressData)); + + ASSERT_EQ(Address(addressData).string(), "Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM"); } TEST(GroestlcoinAddress, Invalid) { - ASSERT_FALSE(Address::isValid(std::string("1JAd7XCBzGudGpJQSDSfpmJhiygtLQWaGL"))); // Valid bitcoin address + EXPECT_EXCEPTION(Address("Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLo"), "Invalid address string"); // Invalid address + EXPECT_EXCEPTION(Address(parse_hex("98af0aaca388a7e1024f505c033626d908e3b5")), "Invalid address key data"); // Invalid address data + ASSERT_FALSE(Address::isValid(std::string("1JAd7XCBzGudGpJQSDSfpmJhiygtLQWaGL"))); // Valid bitcoin address } TEST(GroestlcoinAddress, FromString) { @@ -39,8 +53,26 @@ TEST(GroestlcoinAddress, Derive) { const auto mnemonic = "all all all all all all all all all all all all"; const auto wallet = HDWallet(mnemonic, ""); const auto path = TW::derivationPath(TWCoinTypeGroestlcoin); - const auto address = TW::deriveAddress(TWCoinTypeGroestlcoin, wallet.getKey(TWCoinTypeGroestlcoin, path)); + auto address = TW::deriveAddress(TWCoinTypeGroestlcoin, wallet.getKey(TWCoinTypeGroestlcoin, path)); ASSERT_EQ(address, "grs1qw4teyraux2s77nhjdwh9ar8rl9dt7zww8r6lne"); + + address = TW::deriveAddress(TWCoinTypeGroestlcoin, wallet.getKey(TWCoinTypeGroestlcoin, path), TWDerivationBitcoinLegacy); + ASSERT_EQ(address, "FfsAQrzfdECwEsApubn2rvxgamU8CcqsLT"); +} + +TEST(GroestlcoinAddress, AddressData) { + const auto publicKey = PublicKey(parse_hex("03b85cc59b67c35851eb5060cfc3a759a482254553c5857075c9e247d74d412c91"), TWPublicKeyTypeSECP256k1); + auto address = TW::deriveAddress(TWCoinTypeGroestlcoin, publicKey, TWDerivationBitcoinLegacy); + ASSERT_EQ(address, "Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM"); + + auto addressData = TW::addressToData(TWCoinTypeGroestlcoin, address); + ASSERT_EQ(hex(addressData), "98af0aaca388a7e1024f505c033626d908e3b54a"); + + address = TW::deriveAddress(TWCoinTypeGroestlcoin, publicKey, TWDerivationBitcoinSegwit); + EXPECT_EQ(address, "grs1qnzhs4t9r3zn7zqj02pwqxd3xmyyw8d22q55nf8"); + + addressData = TW::addressToData(TWCoinTypeGroestlcoin, address); + EXPECT_EQ(hex(addressData), "98af0aaca388a7e1024f505c033626d908e3b54a"); } } // namespace TW::Groestlcoin::tests diff --git a/tests/chains/Groestlcoin/TWGroestlcoinSigningTests.cpp b/tests/chains/Groestlcoin/TWGroestlcoinSigningTests.cpp index 5bba40d5ffc..02cd09d6e53 100644 --- a/tests/chains/Groestlcoin/TWGroestlcoinSigningTests.cpp +++ b/tests/chains/Groestlcoin/TWGroestlcoinSigningTests.cpp @@ -5,6 +5,7 @@ // file LICENSE at the root of the source code distribution tree. #include "Bitcoin/Script.h" +#include "Groestlcoin/Signer.h" #include "Hash.h" #include "HexCoding.h" #include "PrivateKey.h" @@ -106,6 +107,23 @@ TEST(GroestlcoinSigning, SignP2PKH) { ASSERT_EQ(hex(output.encoded()), "01000000019568b09e6c6d940302ec555a877c9e5f799de8ee473e18d3a19ae14478cc4e8f000000006a47304402202163ab98b028aa13563f0de00b785d6df81df5eac0b7c91d23f5be7ea674aa3702202bf6cd7055c6f8f697ce045b1a4f9b997cf6e5761a661d27696ac34064479d19012103b85cc59b67c35851eb5060cfc3a759a482254553c5857075c9e247d74d412c91ffffffff02c4090000000000001600147557920fbc32a1ef4ef26bae5e8ce3f95abf09cee20800000000000017a9140055b0c94df477ee6b9f75185dfc9aa8ce2e52e48700000000"); } +TEST(GroestlcoinSigning, SignWithError) { + Proto::SigningInput input; + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(2500); + input.set_byte_fee(1); + input.set_to_address("grs1qw4teyraux2s77nhjdwh9ar8rl9dt7zww8r6lne"); + input.set_change_address("31inaRqambLsd9D7Ke4USZmGEVd3PHkh7P"); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeGroestlcoin); + + ASSERT_NE(output.error(), Common::Proto::OK); + + auto result = Groestlcoin::Signer::preImageHashes(input); + ASSERT_NE(result.error(), Common::Proto::OK); +} + TEST(GroestlcoinSigning, SignP2SH_P2WPKH) { // TX outputs Proto::SigningInput input; diff --git a/tests/chains/Groestlcoin/TWGroestlcoinTests.cpp b/tests/chains/Groestlcoin/TWGroestlcoinTests.cpp index 852d6456a5e..9dab12a0c3a 100644 --- a/tests/chains/Groestlcoin/TWGroestlcoinTests.cpp +++ b/tests/chains/Groestlcoin/TWGroestlcoinTests.cpp @@ -23,11 +23,17 @@ TEST(Groestlcoin, Address) { auto addressString = WRAPS(TWGroestlcoinAddressDescription(address.get())); assertStringsEqual(addressString, "Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM"); + ASSERT_TRUE(TWGroestlcoinAddressIsValidString(addressString.get())); + auto address2 = WRAP(TWGroestlcoinAddress, TWGroestlcoinAddressCreateWithString(STRING("Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM").get())); auto address2String = WRAPS(TWGroestlcoinAddressDescription(address2.get())); assertStringsEqual(address2String, "Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM"); ASSERT_TRUE(TWGroestlcoinAddressEqual(address.get(), address2.get())); + + // invalid address + auto address3 = WRAP(TWGroestlcoinAddress, TWGroestlcoinAddressCreateWithString(STRING("Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLo").get())); + ASSERT_EQ(address3, nullptr); } TEST(Groestlcoin, BuildForLegacyAddress) { diff --git a/tests/chains/Groestlcoin/TransactionCompilerTests.cpp b/tests/chains/Groestlcoin/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..819d5b3f522 --- /dev/null +++ b/tests/chains/Groestlcoin/TransactionCompilerTests.cpp @@ -0,0 +1,119 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Bitcoin.pb.h" + +#include +#include +#include + +#include "Groestlcoin/Signer.h" +#include "Groestlcoin/Transaction.h" + +#include "../Bitcoin/TxComparisonHelper.h" +#include "TestUtilities.h" +#include + +using namespace TW; + +namespace TW::Bitcoin { + +TEST(GroestlcoinCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeGroestlcoin; + + Bitcoin::Proto::SigningInput input; + input.set_coin_type(coin); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(2500); + input.set_byte_fee(1); + input.set_to_address("grs1qw4teyraux2s77nhjdwh9ar8rl9dt7zww8r6lne"); + input.set_change_address("31inaRqambLsd9D7Ke4USZmGEVd3PHkh7P"); + + auto utxoKey0 = parse_hex("3c3385ddc6fd95ba7282051aeb440bc75820b8c10db5c83c052d7586e3e98e84"); + input.add_private_key(utxoKey0.data(), utxoKey0.size()); + + auto utxo0 = input.add_utxo(); + auto utxo0Script = Bitcoin::Script(parse_hex("76a91498af0aaca388a7e1024f505c033626d908e3b54a88ac")); + utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); + utxo0->set_amount(5000); + auto hash0 = parse_hex("9568b09e6c6d940302ec555a877c9e5f799de8ee473e18d3a19ae14478cc4e8f"); + utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + + Bitcoin::Proto::TransactionPlan plan; + { + // try plan first + ANY_PLAN(input, plan, coin); + EXPECT_TRUE(verifyPlan(plan, {5000}, 2500, 221)); + } + + // Supply plan for signing, to match fee of previously-created real TX + *input.mutable_plan() = plan; + input.mutable_plan()->set_fee(226); + input.mutable_plan()->set_change(2274); + + // build preimage + const auto txInputData = data(input.SerializeAsString()); + EXPECT_GT(txInputData.size(), 0ul); + + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + ASSERT_GT(preImageHashes.size(), 0ul); + + Bitcoin::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + ASSERT_EQ(preSigningOutput.hash_public_keys().size(), 1); + + auto preImageHash = data(preSigningOutput.hash_public_keys()[0].data_hash()); + EXPECT_EQ(hex(preImageHash), + "0fb3da786ad1028574f0b40ff1446515eb85cacccff3f3d0459e191b660597b3"); + + // compile + auto publicKey = PrivateKey(utxoKey0).getPublicKey(TWPublicKeyTypeSECP256k1); + auto signature = parse_hex("304402202163ab98b028aa13563f0de00b785d6df81df5eac0b7c91d23f5be7ea674aa3702202bf6cd7055c6f8f697ce045b1a4f9b997cf6e5761a661d27696ac34064479d19"); + { + const Data outputData = + TransactionCompiler::compileWithSignatures(coin, txInputData, {signature}, {publicKey.bytes}); + Bitcoin::Proto::SigningOutput signingOutput; + ASSERT_TRUE(signingOutput.ParseFromArray(outputData.data(), (int)outputData.size())); + ASSERT_EQ(hex(signingOutput.encoded()), + "01000000019568b09e6c6d940302ec555a877c9e5f799de8ee473e18d3a19ae14478cc4e8f000000" + "006a47304402202163ab98b028aa13563f0de00b785d6df81df5eac0b7c91d23f5be7ea674aa3702" + "202bf6cd7055c6f8f697ce045b1a4f9b997cf6e5761a661d27696ac34064479d19012103b85cc59b" + "67c35851eb5060cfc3a759a482254553c5857075c9e247d74d412c91ffffffff02c4090000000000" + "001600147557920fbc32a1ef4ef26bae5e8ce3f95abf09cee20800000000000017a9140055b0c94d" + "f477ee6b9f75185dfc9aa8ce2e52e48700000000"); + Bitcoin::Proto::SigningOutput output; + ANY_SIGN(input, coin); + ASSERT_EQ(output.encoded(), signingOutput.encoded()); + } + + { // Negative: not enough signatures + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {signature}, {publicKey.bytes, publicKey.bytes}); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + { // Negative: empty signatures + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {}, {}); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} + +} // TW::Bitcoin \ No newline at end of file diff --git a/tests/chains/Harmony/AddressTests.cpp b/tests/chains/Harmony/AddressTests.cpp index 157270ae555..9bee16bec9d 100644 --- a/tests/chains/Harmony/AddressTests.cpp +++ b/tests/chains/Harmony/AddressTests.cpp @@ -29,6 +29,8 @@ TEST(HarmonyAddress, FromData) { const auto address_2 = Address(parse_hex("0xed1ebe4fd1f73f86388f231997859ca42c07da5d")); ASSERT_EQ(address.string(), "one1tp7xdd9ewwnmyvws96au0e7e7mz6f8hjqr3g3p"); ASSERT_EQ(address_2.string(), "one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe"); + + EXPECT_ANY_THROW(new Address(parse_hex(""))); } TEST(HarmonyAddress, InvalidHarmonyAddress) { @@ -36,12 +38,15 @@ TEST(HarmonyAddress, InvalidHarmonyAddress) { ASSERT_FALSE(Address::isValid("oe1tp7xdd9ewwnmyvws96au0ee7e7mz6f8hjqr3g3p")); } -TEST(HarmonyAddress, FromPrivateKey) { +TEST(HarmonyAddress, FromPublicKey) { const auto privateKey = PrivateKey(parse_hex("e2f88b4974ae763ca1c2db49218802c2e441293a09eaa9ab681779e05d1b7b94")); const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); const auto address = Address(publicKey); ASSERT_EQ(address.string(), "one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe"); + + const auto publicKey2 = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeED25519)); + EXPECT_ANY_THROW(new Address(publicKey2)); } } // namespace TW::Harmony::tests diff --git a/tests/chains/Harmony/SignerTests.cpp b/tests/chains/Harmony/SignerTests.cpp index 3b0485e2dba..33e4b97e391 100644 --- a/tests/chains/Harmony/SignerTests.cpp +++ b/tests/chains/Harmony/SignerTests.cpp @@ -162,4 +162,128 @@ TEST(HarmonySigner, SignOverProtoBufAssumeMainNet) { ASSERT_EQ(hex(proto_output.r()), r); ASSERT_EQ(hex(proto_output.s()), s); } + +TEST(HarmonySigner, BuildSigningOutput) { + auto input = Proto::SigningInput(); + auto trasactionMsg = input.mutable_transaction_message(); + trasactionMsg->set_to_address(TEST_RECEIVER.string()); + const auto privateKey = + PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e")); + auto payload = parse_hex(""); + trasactionMsg->set_payload(payload.data(), payload.size()); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto value = store(MAIN_NET); + input.set_chain_id(value.data(), value.size()); + + value = store(uint256_t("0xa")); + trasactionMsg->set_nonce(value.data(), value.size()); + + value = store(uint256_t("")); + trasactionMsg->set_gas_price(value.data(), value.size()); + + value = store(uint256_t("0x5208")); + trasactionMsg->set_gas_limit(value.data(), value.size()); + + value = store(uint256_t("0x1")); + trasactionMsg->set_from_shard_id(value.data(), value.size()); + + value = store(uint256_t("0x0")); + trasactionMsg->set_to_shard_id(value.data(), value.size()); + + value = store(uint256_t("0x4c53ecdc18a60000")); + trasactionMsg->set_amount(value.data(), value.size()); + + Data signature = parse_hex("74acbc63a58e7861e54ca24babf1cb800c5b694da25c3ae2b154304505366708616ab8262ee6f6fb30ffcab3e9e8261479c7469ce97010a70b3d3f962842c61a01"); + + Signer signer(uint256_t(load(input.chain_id()))); + auto proto_output = signer.buildSigningOutput(input, signature); + + auto expectEncoded = "f8690a808252080180946a87346f3ba9958d08d09484a2b7fdbbe42b0df6884c53ecdc18a" + "600008026a074acbc63a58e7861e54ca24babf1cb800c5b694da25c3ae2b1543045053667" + "08a0616ab8262ee6f6fb30ffcab3e9e8261479c7469ce97010a70b3d3f962842c61a"; + + auto v = "26"; + auto r = "74acbc63a58e7861e54ca24babf1cb800c5b694da25c3ae2b154304505366708"; + auto s = "616ab8262ee6f6fb30ffcab3e9e8261479c7469ce97010a70b3d3f962842c61a"; + + ASSERT_EQ(hex(proto_output.encoded()), expectEncoded); + ASSERT_EQ(hex(proto_output.v()), v); + ASSERT_EQ(hex(proto_output.r()), r); + ASSERT_EQ(hex(proto_output.s()), s); +} + +TEST(HarmonySigner, BuildUnsignedTxBytes) { + auto input = Proto::SigningInput(); + auto trasactionMsg = input.mutable_transaction_message(); + trasactionMsg->set_to_address(TEST_RECEIVER.string()); + const auto privateKey = + PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e")); + auto payload = parse_hex(""); + trasactionMsg->set_payload(payload.data(), payload.size()); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto value = store(MAIN_NET); + input.set_chain_id(value.data(), value.size()); + + value = store(uint256_t("0xa")); + trasactionMsg->set_nonce(value.data(), value.size()); + + value = store(uint256_t("")); + trasactionMsg->set_gas_price(value.data(), value.size()); + + value = store(uint256_t("0x5208")); + trasactionMsg->set_gas_limit(value.data(), value.size()); + + value = store(uint256_t("0x1")); + trasactionMsg->set_from_shard_id(value.data(), value.size()); + + value = store(uint256_t("0x0")); + trasactionMsg->set_to_shard_id(value.data(), value.size()); + + value = store(uint256_t("0x4c53ecdc18a60000")); + trasactionMsg->set_amount(value.data(), value.size()); + + Signer signer(uint256_t(load(input.chain_id()))); + auto unsignedTxBytes = signer.buildUnsignedTxBytes(input); + + auto expectEncoded = "e90a808252080180946a87346f3ba9958d08d09484a2b7fdbbe42b0df6884c53ecdc18a6000080018080"; + + ASSERT_EQ(hex(unsignedTxBytes), expectEncoded); +} + +TEST(HarmonySigner, BuildUnsignedStakingTxBytes) { + auto input = Proto::SigningInput(); + auto stakingMsg = input.mutable_staking_message(); + const auto privateKey = + PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto value = store(MAIN_NET); + input.set_chain_id(value.data(), value.size()); + + value = store(uint256_t("0xa")); + stakingMsg->set_nonce(value.data(), value.size()); + + value = store(uint256_t("")); + stakingMsg->set_gas_price(value.data(), value.size()); + + value = store(uint256_t("0x5208")); + stakingMsg->set_gas_limit(value.data(), value.size()); + + // delegate message + auto delegateMsg = stakingMsg->mutable_delegate_message(); + delegateMsg->set_delegator_address(TEST_RECEIVER.string()); + delegateMsg->set_validator_address(TEST_RECEIVER.string()); + + value = store(uint256_t("0x4c53ecdc18a60000")); + delegateMsg->set_amount(value.data(), value.size()); + + Signer signer(uint256_t(load(input.chain_id()))); + auto unsignedTxBytes = signer.buildUnsignedTxBytes(input); + + auto expectEncoded = "f83d02f3946a87346f3ba9958d08d09484a2b7fdbbe42b0df6946a87346f3ba9958d08d09484a2b7fdbbe42b0df6884c53ecdc18a600000a80825208018080"; + + ASSERT_EQ(hex(unsignedTxBytes), expectEncoded); +} } // namespace TW::Harmony diff --git a/tests/chains/Harmony/TransactionCompilerTests.cpp b/tests/chains/Harmony/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..9de42582fff --- /dev/null +++ b/tests/chains/Harmony/TransactionCompilerTests.cpp @@ -0,0 +1,122 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" +#include "uint256.h" + +#include "proto/Harmony.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(HarmonyCompiler, CompileWithSignatures) { + // txHash 0x238c0db5f139422d64d12b3d5208243b4b355bfb87024cec7795660291a628d0 on https://explorer.ps.hmny.io/ + /// Step 1: Prepare transaction input (protobuf) + auto coin = TWCoinTypeHarmony; + auto input = TW::Harmony::Proto::SigningInput(); + auto trasactionMsg = input.mutable_transaction_message(); + auto receiver = "one1y563nrrtcpu7874cry68ehxwrpteyhp0sztlym"; + trasactionMsg->set_to_address(receiver); + auto payload = parse_hex(""); + trasactionMsg->set_payload(payload.data(), payload.size()); + + uint256_t MAIN_NET = 0x4; + auto value = store(MAIN_NET); + input.set_chain_id(value.data(), value.size()); + + value = store(uint256_t(0)); + trasactionMsg->set_nonce(value.data(), value.size()); + + value = store(uint256_t(1000000000000000)); + trasactionMsg->set_gas_price(value.data(), value.size()); + + value = store(uint256_t(1000000)); + trasactionMsg->set_gas_limit(value.data(), value.size()); + + value = store(uint256_t("0x0")); + trasactionMsg->set_from_shard_id(value.data(), value.size()); + + value = store(uint256_t("0x0")); + trasactionMsg->set_to_shard_id(value.data(), value.size()); + + value = store(uint256_t(10)); + trasactionMsg->set_amount(value.data(), value.size()); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImage = data(preSigningOutput.data()); + auto preImageHash = data(preSigningOutput.data_hash()); + + std::string expectedPreImage = "e98087038d7ea4c68000830f42408080942535198c6bc079e3fab819347cdcce1857925c2f0a80048080"; + std::string expectedPreImageHash = "fd1be8579542dc60f15a6218887cc1b42945bf04b50205d15ad7df8b5fac5714"; + ASSERT_EQ(hex(preImage), expectedPreImage); + ASSERT_EQ(hex(preImageHash), expectedPreImageHash); + + const auto privateKey = PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e")); + Data signature = parse_hex("43824f50bf4b16ebe1020114de16e3579bdb5f3dcaa26117de87a73b5414b72550506609fd60e3cb565b1f9bae0952d37f3a6c6be262380f7f18cbda5216f34300"); + const PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, preImageHash)); + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKey.bytes}); + const auto ExpectedTx = + "f8698087038d7ea4c68000830f42408080942535198c6bc079e3fab819347cdcce1857925c2f0a802ba043824f50bf4b16ebe1020114de16e3579bdb5f3dcaa26117de87a73b5414b725a050506609fd60e3cb565b1f9bae0952d37f3a6c6be262380f7f18cbda5216f343"; + { + TW::Harmony::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::Harmony::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + signingInput.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + TW::Harmony::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, signature}, {publicKey.bytes}); + Harmony::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {}, {}); + Harmony::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} diff --git a/tests/chains/ICON/AddressTests.cpp b/tests/chains/ICON/AddressTests.cpp index c21ff7e2821..b32d40044ba 100644 --- a/tests/chains/ICON/AddressTests.cpp +++ b/tests/chains/ICON/AddressTests.cpp @@ -29,6 +29,8 @@ TEST(IconAddress, String) { const auto address2 = Address("cx116f042497e5f34268b1b91e742680f84cf4e9f3"); ASSERT_EQ(address2.string(), "cx116f042497e5f34268b1b91e742680f84cf4e9f3"); + + EXPECT_ANY_THROW(new Address("")); } TEST(IconAddress, FromPrivateKey) { diff --git a/tests/chains/ICON/TransactionCompilerTests.cpp b/tests/chains/ICON/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..18efb781d63 --- /dev/null +++ b/tests/chains/ICON/TransactionCompilerTests.cpp @@ -0,0 +1,64 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "HexCoding.h" +#include "proto/Icon.pb.h" +#include "proto/TransactionCompiler.pb.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" +#include +#include + +using namespace TW; + +TEST(IconCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeICON; + TW::Icon::Proto::SigningInput input; + + input.set_from_address("hxbe258ceb872e08851f1f59694dac2558708ece11"); + input.set_to_address("hx5bfdb090f43a808005ffc27c25b213145e80b7cd"); + + auto value = uint256_t(1000000000000000000); + auto valueData = store(value); + input.set_value(valueData.data(), valueData.size()); + + auto stepLimit = uint256_t("74565"); + auto stepLimitData = store(stepLimit); + input.set_step_limit(stepLimitData.data(), stepLimitData.size()); + + auto one = uint256_t("01"); + auto oneData = store(one); + input.set_network_id(oneData.data(), oneData.size()); + input.set_nonce(oneData.data(), oneData.size()); + + input.set_timestamp(1516942975500598); + + auto protoInputString = input.SerializeAsString(); + auto protoInputData = TW::Data(protoInputString.begin(), protoInputString.end()); + + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, protoInputData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + auto preImage = preSigningOutput.data(); + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImage),"6963785f73656e645472616e73616374696f6e2e66726f6d2e6878626532353863656238373265303838353166316635393639346461633235353837303865636531312e6e69642e3078312e6e6f6e63652e3078312e737465704c696d69742e307831323334352e74696d657374616d702e3078353633613663663333303133362e746f2e6878356266646230393066343361383038303035666663323763323562323133313435653830623763642e76616c75652e30786465306236623361373634303030302e76657273696f6e2e307833"); + EXPECT_EQ(hex(preImageHash),"f0c68a4f588233d722fff7b5a738ffa6b56ad4cb62ad6bc9fb3e5facb0c25059"); + + auto key = parse_hex("2d42994b2f7735bbc93a3e64381864d06747e574aa94655c516f9ad0a74eed79"); + const auto privateKey = PrivateKey(key); + const auto publicKey = privateKey.getPublicKey(TWCoinTypePublicKeyType(coin)); + const auto signature = privateKey.sign(parse_hex(hex(preImageHash)), TWCurveSECP256k1); + + const Data outputData = TransactionCompiler::compileWithSignatures(coin, protoInputData, {signature}, {publicKey.bytes}); + Icon::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.error(), Common::Proto::OK); + EXPECT_EQ(hex(output.encoded()), "7b2266726f6d223a22687862653235386365623837326530383835316631663539363934646163323535383730386563653131222c226e6964223a22307831222c226e6f6e6365223a22307831222c227369676e6174757265223a22785236774b732b49412b374539316254383936366a464b6c4b356d61797574584376617975534d437278394b4237363730437357613042374c517a6773785530474c58616f766c4154324d4c73315875446953615a51453d222c22737465704c696d6974223a2230783132333435222c2274696d657374616d70223a22307835363361366366333330313336222c22746f223a22687835626664623039306634336138303830303566666332376332356232313331343565383062376364222c2276616c7565223a223078646530623662336137363430303030222c2276657273696f6e223a22307833227d"); +} \ No newline at end of file diff --git a/tests/chains/IOST/AddressTests.cpp b/tests/chains/IOST/AddressTests.cpp new file mode 100644 index 00000000000..e088e411477 --- /dev/null +++ b/tests/chains/IOST/AddressTests.cpp @@ -0,0 +1,68 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HDWallet.h" +#include "HexCoding.h" +#include "IOST/Account.h" +#include "proto/IOST.pb.h" + +#include + +using namespace TW; +using namespace TW::IOST; + +TEST(IOSTAddress, ValidateAccount) { + // https://www.iostabc.com/tx/DnR4QuRDJAjUZ2qfJK9MT92p95BBub2FnyigeXn2Z1es + ASSERT_TRUE(Account::isValid("12345xusong")); + ASSERT_TRUE(Account::isValid("12345")); + ASSERT_TRUE(Account::isValid("1234_xuson")); + ASSERT_TRUE(Account::isValid("EKRQPgX7nKt8hJABwm9m3BKWGj7kcSECkJnCBauHQWin")); + + ASSERT_FALSE(TW::validateAddress(TWCoinTypeIOST, "1234")); + ASSERT_FALSE(TW::validateAddress(TWCoinTypeIOST, "1234_xusonG")); + ASSERT_FALSE(TW::validateAddress(TWCoinTypeIOST, "12345xusong6")); + ASSERT_FALSE(TW::validateAddress(TWCoinTypeIOST, "bc1quvuarfksewfeuevuc6tn0kfyptgjvwsvrprk9d")); + ASSERT_FALSE(TW::validateAddress(TWCoinTypeIOST, "DJRFZNg8jkUtjcpo2zJd92FUAzwRjitw6f")); + + ASSERT_EQ(TW::normalizeAddress(TWCoinTypeIOST, "EKRQPgX7nKt8hJABwm9m3BKWGj7kcSECkJnCBauHQWin"), "EKRQPgX7nKt8hJABwm9m3BKWGj7kcSECkJnCBauHQWin"); + ASSERT_EQ(TW::normalizeAddress(TWCoinTypeIOST, "Gcv8c2tH8qZrUYnKdEEdTtASsxivic2834MQW6mgxqto"), "Gcv8c2tH8qZrUYnKdEEdTtASsxivic2834MQW6mgxqto"); +} + +TEST(IOSTAddress, Account) { + std::string key = ("63095105a37b4e896e5ebbd740e751c6f9df7cca2410beba3261dc5680299cebe" + "812b52ea9ad5cba9a9af03afcc6f2942a4524b0df3c0344dc195072831670c4"); + Data secKeyBytes = parse_hex(key); + std::string secKey(secKeyBytes.begin(), secKeyBytes.end()); + + Proto::AccountInfo ai; + ai.set_active_key(secKey); + ai.set_owner_key(secKey); + + Account account(ai); + ASSERT_EQ(hex(account.activeKey), "63095105a37b4e896e5ebbd740e751c6f9df7cca2410beba3261dc5680299ceb"); + EXPECT_EQ(hex(account.ownerKey), "63095105a37b4e896e5ebbd740e751c6f9df7cca2410beba3261dc5680299ceb"); + + auto pubKey = account.publicOwnerKey(); + auto address = Account::address(std::string(pubKey.begin(), pubKey.end())); + EXPECT_EQ(address, "Gcv8c2tH8qZrUYnKdEEdTtASsxivic2834MQW6mgxqto"); + + EXPECT_EQ(hex(TW::addressToData(TWCoinTypeIOST, address)), "e812b52ea9ad5cba9a9af03afcc6f2942a4524b0df3c0344dc195072831670c4"); +} + +TEST(IOSTAddress, FromPrivateKey) { + auto wallet = HDWallet( + "shoot island position soft burden budget tooth cruel issue economy destroy above", ""); + auto address = wallet.deriveAddress(TWCoinTypeIOST); + ASSERT_EQ(address, "4av8w81EyzUgHonsVWqfs15WM4Vrpgox4BYYQWhNQDVu"); +} + +TEST(IOSTAddress, EqualOperator) { + auto acnt1 = Account("account1"); + auto acnt2 = Account("account2"); + ASSERT_TRUE(acnt1 == acnt1); + ASSERT_FALSE(acnt1 == acnt2); +} \ No newline at end of file diff --git a/tests/chains/IOST/SignerTests.cpp b/tests/chains/IOST/SignerTests.cpp new file mode 100644 index 00000000000..22a3a545940 --- /dev/null +++ b/tests/chains/IOST/SignerTests.cpp @@ -0,0 +1,86 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "HexCoding.h" +#include "IOST/Signer.h" +#include "proto/IOST.pb.h" +#include + +#include + +using namespace TW; +using namespace TW::IOST; + +TEST(IOSTSigner, Sign) { + auto tx = Proto::Transaction(); + tx.set_time(1550137587000000000); + tx.set_expiration(tx.time() + int64_t(1000000000) * 300); + tx.set_gas_ratio(1); + tx.set_gas_limit(1000000); + tx.set_chain_id(1024); + + tx.add_amount_limit(); + auto amountLimit = tx.mutable_amount_limit(0); + amountLimit->set_token("*"); + amountLimit->set_value("unlimited"); + + Data secKeyBytes = parse_hex("63095105a37b4e896e5ebbd740e751c6f9df7cca2410beba3261dc5680299cebe" + "812b52ea9ad5cba9a9af03afcc6f2942a4524b0df3c0344dc195072831670c4"); + std::string secKey(secKeyBytes.begin(), secKeyBytes.end()); + Proto::AccountInfo account; + account.set_active_key(secKey); + account.set_name("myname"); + + Proto::SigningInput input; + input.mutable_account()->CopyFrom(account); + input.mutable_transaction_template()->CopyFrom(tx); + input.set_transfer_destination("admin"); + input.set_transfer_amount("10"); + input.set_transfer_memo(""); + + Signer signer(input); + std::string signature = signer.sign(input).transaction().publisher_sigs(0).signature(); + + ASSERT_EQ(hex(signature), "e8ce15214bad39683021c15dd318e963da8541fd8f3d8484df5042b4ea7fdafb7f46" + "505b85841367d6e1736c7d3b433ca72089b88a23f43661dfb0429a10cb03"); +} + +TEST(IOSTSigner, EncodeTransaction) { + auto tx = Proto::Transaction(); + tx.set_time(1550137587000000000); + tx.set_expiration(tx.time() + int64_t(1000000000) * 300); + tx.set_gas_ratio(1); + tx.set_gas_limit(1000000); + tx.set_chain_id(1024); + + tx.add_amount_limit(); + auto amountLimit = tx.mutable_amount_limit(0); + amountLimit->set_token("*"); + amountLimit->set_value("unlimited"); + + Data secKeyBytes = parse_hex("63095105a37b4e896e5ebbd740e751c6f9df7cca2410beba3261dc5680299cebe" + "812b52ea9ad5cba9a9af03afcc6f2942a4524b0df3c0344dc195072831670c4"); + std::string secKey(secKeyBytes.begin(), secKeyBytes.end()); + Proto::AccountInfo account; + account.set_active_key(secKey); + account.set_name("myname"); + + tx.add_signers(); + *tx.mutable_signers(0) = secKey; + + const auto signature = + parse_hex("1e5e2de66512658e9317fa56766678166abcf492d020863935723db2030f736710e13437cef0981f" + "cc376eae45349759508767d407b6c9963712910ada2c3606"); + + tx.add_signatures(); + auto* sig = tx.mutable_signatures(0); + sig->set_algorithm(Proto::Algorithm::ED25519); + sig->set_public_key(secKey); + sig->set_signature(std::string(signature.begin(), signature.end())); + + auto encoded = Signer::encodeTransaction(tx); + ASSERT_EQ(hex(encoded), "158331ec221dbe0015833231fb82760000000000000000640000000005f5e10000000000000000000000040000000000000000010000004063095105a37b4e896e5ebbd740e751c6f9df7cca2410beba3261dc5680299cebe812b52ea9ad5cba9a9af03afcc6f2942a4524b0df3c0344dc195072831670c4000000000000000100000012000000012a00000009756e6c696d69746564000000010000008902000000401e5e2de66512658e9317fa56766678166abcf492d020863935723db2030f736710e13437cef0981fcc376eae45349759508767d407b6c9963712910ada2c36060000004063095105a37b4e896e5ebbd740e751c6f9df7cca2410beba3261dc5680299cebe812b52ea9ad5cba9a9af03afcc6f2942a4524b0df3c0344dc195072831670c4"); +} diff --git a/tests/chains/IOST/TWCoinTypeTests.cpp b/tests/chains/IOST/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..bb3d675f549 --- /dev/null +++ b/tests/chains/IOST/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWIOSTCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeIOST)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("7dKQzgTkPBNrZqrQ2Bqhqq132CHGPKANFDtzRsjHRCqx")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeIOST, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("4av8w81EyzUgHonsVWqfs15WM4Vrpgox4BYYQWhNQDVu")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeIOST, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeIOST)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeIOST)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeIOST), 2); + ASSERT_EQ(TWBlockchainIOST, TWCoinTypeBlockchain(TWCoinTypeIOST)); + ASSERT_EQ(0x5, TWCoinTypeP2shPrefix(TWCoinTypeIOST)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeIOST)); + assertStringsEqual(symbol, "IOST"); + assertStringsEqual(txUrl, "https://explorer.iost.io/tx/7dKQzgTkPBNrZqrQ2Bqhqq132CHGPKANFDtzRsjHRCqx"); + assertStringsEqual(accUrl, "https://explorer.iost.io/account/4av8w81EyzUgHonsVWqfs15WM4Vrpgox4BYYQWhNQDVu"); + assertStringsEqual(id, "iost"); + assertStringsEqual(name, "IOST"); +} diff --git a/tests/chains/IOST/TransactionCompilerTests.cpp b/tests/chains/IOST/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..6b33a86a025 --- /dev/null +++ b/tests/chains/IOST/TransactionCompilerTests.cpp @@ -0,0 +1,142 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Base58.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/IOST.pb.h" +#include "proto/Theta.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(IostCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeIOST; + /// Step 1: Prepare transaction input (protobuf) + const auto privKeyBytes = Base58::decode( + "4TQwN7wWXg26ByuU5WkUPErd5v6PD6HsDuULyGNJgpS979wXF7jRU8NKviJs5boHrRKbLMomKycbek4NyDy6cLb8"); + const auto pkFrom = PrivateKey(Data(privKeyBytes.begin(), privKeyBytes.begin() + 32)); + const auto publicKey = pkFrom.getPublicKey(TWPublicKeyTypeED25519); + TW::IOST::Proto::SigningInput input; + input.set_transfer_memo(""); + auto t = input.mutable_transaction_template(); + t->set_publisher("astastooo"); + t->set_time(1647421579901555000); + t->set_expiration(1647421879901555000); + t->set_gas_ratio(1); + t->set_gas_limit(100000); + t->set_chain_id(1023); + t->set_delay(0); + t->set_publisher("astastooo"); + t->add_actions(); + auto action = t->mutable_actions(0); + action->set_contract("token.iost"); + action->set_action_name("transfer"); + action->set_data("[\"IOST\",\"astastooo\",\"test_iosted\",\"0.001\",\"test\"]"); + t->add_amount_limit(); + auto amountLimit = t->mutable_amount_limit(0); + amountLimit->set_token("*"); + amountLimit->set_value("unlimited"); + input.mutable_account()->set_active_key(privKeyBytes.data(), 32); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, inputStrData); + + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), + "451ed1e542da2422ed152bff6f30c95e2a8ee2153f4d36f15c45914fa2d2e9f1"); + + // Simulate signature, normally obtained from signature server + const auto publicKeyData = publicKey.bytes; + const auto signature = + parse_hex("1e5e2de66512658e9317fa56766678166abcf492d020863935723db2030f736710e13437cef0981f" + "cc376eae45349759508767d407b6c9963712910ada2c3606"); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preSigningOutput.data_hash()))); + + /// Step 3: Compile transaction info + const auto expectedTx = + "7b2274696d65223a2231363437343231353739393031353535303030222c2265787069726174696f6e223a2231" + "363437343231383739393031353535303030222c226761735f726174696f223a312c226761735f6c696d697422" + "3a3130303030302c22636861696e5f6964223a313032332c22616374696f6e73223a5b7b22636f6e7472616374" + "223a22746f6b656e2e696f7374222c22616374696f6e5f6e616d65223a227472616e73666572222c2264617461" + "223a225b5c22494f53545c222c5c226173746173746f6f6f5c222c5c22746573745f696f737465645c222c5c22" + "302e3030315c222c5c22746573745c225d227d5d2c22616d6f756e745f6c696d6974223a5b7b22746f6b656e22" + "3a222a222c2276616c7565223a22756e6c696d69746564227d5d2c227075626c6973686572223a226173746173" + "746f6f6f222c227075626c69736865725f73696773223a5b7b22616c676f726974686d223a322c227369676e61" + "74757265223a22486c3474356d55535a593654462f7057646d5a34466d7138394a4c51494959354e5849397367" + "4d5063326351345451337a76435948387733627135464e4a645a5549646e31416532795a59334570454b326977" + "3242673d3d222c227075626c69635f6b6579223a2234687a496d2b6d383234536f663866645641474545332b64" + "667931554c7a69786e6f4c5047694a565879383d227d5d7d"; + { + const Data outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKeyData}); + + IOST::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(hex(output.encoded()), expectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::IOST::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + + TW::IOST::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.encoded()), expectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, signature}, {publicKey.bytes}); + IOST::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: invalid signatures + const auto invalidSignature = + parse_hex("fb43727477caaa12542b9060856816d42eedef6ebf2e98e4f8dff4355fe384751925833c4a26b2fed1707aebe655cb3317504a61ee59697c086f7baa6ca06a09"); + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {invalidSignature}, {publicKey.bytes}); + IOST::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_internal); + EXPECT_EQ(output.error_message(), "Invalid signature/hash/publickey combination"); + } + + { // Negative: empty signatures + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {}, {}); + IOST::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} diff --git a/tests/chains/IoTeX/SignerTests.cpp b/tests/chains/IoTeX/SignerTests.cpp index 7c5eded4590..7b2787511d0 100644 --- a/tests/chains/IoTeX/SignerTests.cpp +++ b/tests/chains/IoTeX/SignerTests.cpp @@ -7,7 +7,7 @@ #include #include "HexCoding.h" - +#include "Hash.h" #include "IoTeX/Address.h" #include "IoTeX/Signer.h" #include "proto/IoTeX.pb.h" @@ -57,4 +57,69 @@ TEST(IoTeXSigner, Build) { ASSERT_EQ(hex(h), "6c84ac119058e859a015221f87a4e187c393d0c6ee283959342eac95fad08c33"); } +TEST(IoTeXSigner, Compile) { + auto input = Proto::SigningInput(); + input.set_version(1); + input.set_nonce(123); + input.set_gaslimit(888); + input.set_gasprice("999"); + auto tsf = input.mutable_transfer(); + tsf->set_amount("456"); + tsf->set_recipient("io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j"); + auto text = parse_hex("68656c6c6f20776f726c6421"); // "hello world!" + tsf->set_payload(text.data(), text.size()); + + //build preImage + auto preInputData = IoTeX::Signer(std::move(input)); + auto h = preInputData.hash(); + auto checkHash = "0f17cd7f43bdbeff73dfe8f5cb0c0045f2990884e5050841de887cf22ca35b50"; + //check un sign hash + ASSERT_EQ(hex(h), checkHash); + + //build sign + auto key = parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f"); + input.set_privatekey(key.data(), key.size()); + auto signer = IoTeX::Signer(std::move(input)); + Data sig = signer.sign(); + auto checkSig = "555cc8af4181bf85c044c3201462eeeb95374f78aa48c67b87510ee63d5e502372e53082f03e9a11c1e351de539cedf85d8dff87de9d003cb9f92243541541a000"; + //check signature + ASSERT_EQ(hex(sig), checkSig); + + //build compile + auto k = PrivateKey(key); + PublicKey pk = k.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + //merge hash data and signature + auto output = signer.compile(input, sig, pk); + + auto encoded = output.encoded(); + auto hash = output.hash(); // signed action's hash + + auto checkEncoded = "0a4c0801107b18f8062203393939523e0a033435361229696f313837777a703038766e686a6a706b79646e723937716c68386b683064706b6b797466616d386a1a0c68656c6c6f20776f726c64211241044e18306ae9ef4ec9d07bf6e705442d4d1a75e6cdf750330ca2d880f2cc54607c9c33deb9eae9c06e06e04fe9ce3d43962cc67d5aa34fbeb71270d4bad3d648d91a41555cc8af4181bf85c044c3201462eeeb95374f78aa48c67b87510ee63d5e502372e53082f03e9a11c1e351de539cedf85d8dff87de9d003cb9f92243541541a000"; + auto compileHash = "6c84ac119058e859a015221f87a4e187c393d0c6ee283959342eac95fad08c33"; + //check encoded + ASSERT_EQ(hex(encoded), checkEncoded); + //check hash + ASSERT_EQ(hex(hash), compileHash); +} + +TEST(IoTeXSigner, SignaturePreimage) { + auto input = Proto::SigningInput(); + input.set_version(1); + input.set_nonce(123); + input.set_gaslimit(888); + input.set_gasprice("999"); + auto key = parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f"); + input.set_privatekey(key.data(), key.size()); + auto tsf = input.mutable_transfer(); + tsf->set_amount("456"); + tsf->set_recipient("io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j"); + auto text = parse_hex("68656c6c6f20776f726c6421"); // "hello world!" + tsf->set_payload(text.data(), text.size()); + + auto signer = IoTeX::Signer(std::move(input)); + + auto preImage = signer.signaturePreimage(); + ASSERT_EQ(hex(preImage), "0801107b18f8062203393939523e0a033435361229696f313837777a703038766e686a6a706b79646e723937716c68386b683064706b6b797466616d386a1a0c68656c6c6f20776f726c6421"); +} + } // namespace TW::IoTeX diff --git a/tests/chains/IoTeX/TransactionCompilerTests.cpp b/tests/chains/IoTeX/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..2dd65ce85d6 --- /dev/null +++ b/tests/chains/IoTeX/TransactionCompilerTests.cpp @@ -0,0 +1,168 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/IoTeX.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TransactionCompiler, IoTeXCompileWithSignatures) { + const auto coin = TWCoinTypeIoTeX; + + const auto privateKey0 = + parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f"); + const auto privateKey1 = + parse_hex("6021efcf7555f90627364339fc921139dd40a06ccb2cb2a2a4f8f4ea7a2dc74d"); + const auto pubKey0 = + parse_hex("034e18306ae9ef4ec9d07bf6e705442d4d1a75e6cdf750330ca2d880f2cc54607c"); + const auto pubKey1 = + parse_hex("0253ad2f3b734a197f64911242aabc9b5b10bf5744949f5396e56427f35448eafa"); + const auto ExpectedTx0 = + "0a4c0801107b18f8062203393939523e0a033435361229696f313837777a703038766e686a6a706b79646e7239" + "37716c68386b683064706b6b797466616d386a1a0c68656c6c6f20776f726c64211241044e18306ae9ef4ec9d0" + "7bf6e705442d4d1a75e6cdf750330ca2d880f2cc54607c9c33deb9eae9c06e06e04fe9ce3d43962cc67d5aa34f" + "beb71270d4bad3d648d91a41555cc8af4181bf85c044c3201462eeeb95374f78aa48c67b87510ee63d5e502372" + "e53082f03e9a11c1e351de539cedf85d8dff87de9d003cb9f92243541541a000"; + const auto ExpectedTx1 = + "0a4c0801107b18f8062203393939523e0a033435361229696f313837777a703038766e686a6a706b79646e7239" + "37716c68386b683064706b6b797466616d386a1a0c68656c6c6f20776f726c642112410453ad2f3b734a197f64" + "911242aabc9b5b10bf5744949f5396e56427f35448eafa84a5d74b49ecb56e011b18c3d5a300e8cff7c6b39d33" + "0d1d3799c4700a0b1be21a41de4be56ce74dce8e526590f5b5f947385b00947c4c2ead014429aa706a2470055c" + "56c7e57d1b119b487765d59b21bcdeafac25108f6929a14f9edf4b2309534501"; + + const auto prkey0 = PrivateKey(privateKey0); + const PublicKey pbkey0 = prkey0.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + + const auto prkey1 = PrivateKey(privateKey1); + const PublicKey pbkey1 = prkey1.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::IoTeX::Proto::SigningInput(); + input.set_version(1); + input.set_nonce(123); + input.set_gaslimit(888); + input.set_gasprice("999"); + auto tsf = input.mutable_transfer(); + tsf->set_amount("456"); + tsf->set_recipient("io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j"); + auto text = parse_hex("68656c6c6f20776f726c6421"); // "hello world!" + tsf->set_payload(text.data(), text.size()); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImage = data(preSigningOutput.data()); + auto preImageHash = data(preSigningOutput.data_hash()); + + std::string expectedPreImage = + "0801107b18f8062203393939523e0a033435361229696f313837777a703038766e686a6a706b79646e72393771" + "6c68386b683064706b6b797466616d386a1a0c68656c6c6f20776f726c6421"; + std::string expectedPreImageHash = + "0f17cd7f43bdbeff73dfe8f5cb0c0045f2990884e5050841de887cf22ca35b50"; + ASSERT_EQ(hex(preImage), expectedPreImage); + ASSERT_EQ(hex(preImageHash), expectedPreImageHash); + + Data signature = parse_hex("555cc8af4181bf85c044c3201462eeeb95374f78aa48c67b87510ee63d5e502372e" + "53082f03e9a11c1e351de539cedf85d8dff87de9d003cb9f92243541541a000"); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(pbkey0.verify(signature, preImageHash)); + /// Step 3: Compile transaction info + { + const Data outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {pbkey0.bytes}); + + TW::IoTeX::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(hex(output.encoded()), ExpectedTx0); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::IoTeX::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + EXPECT_EQ(hex(PrivateKey(privateKey0).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), + hex(pubKey0)); + signingInput.set_privatekey(prkey0.bytes.data(), prkey0.bytes.size()); + TW::IoTeX::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx0); + } + + { // more signatures + TW::IoTeX::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + EXPECT_EQ(hex(PrivateKey(privateKey1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), + hex(pubKey1)); + signingInput.set_privatekey(prkey1.bytes.data(), prkey1.bytes.size()); + TW::IoTeX::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx1); + } + + { // Negative: invalid public key + const auto publicKeyBlake = + parse_hex("6641abedacf9483b793afe1718689cc9420bbb1c"); + EXPECT_EXCEPTION( + TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature}, {publicKeyBlake}), + "Invalid public key"); + } + + { // Negative: not enough signatures + const Data outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {}, {pbkey0.bytes}); + + TW::IoTeX::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + EXPECT_EQ(output.error_message(), "empty signatures or publickeys"); + } + + { // Negative: not enough publicKey + const Data outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {}); + + TW::IoTeX::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + EXPECT_EQ(output.error_message(), "empty signatures or publickeys"); + } + + { // Negative: not one to on + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature}, {pbkey0.bytes, pbkey1.bytes}); + + TW::IoTeX::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + EXPECT_EQ(output.error_message(), "signatures and publickeys size can only be one"); + } +} \ No newline at end of file diff --git a/tests/chains/Komodo/AddressTests.cpp b/tests/chains/Komodo/AddressTests.cpp new file mode 100644 index 00000000000..5591db9cd64 --- /dev/null +++ b/tests/chains/Komodo/AddressTests.cpp @@ -0,0 +1,36 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HDWallet.h" +#include "HexCoding.h" +#include "PublicKey.h" +#include +#include + +using namespace TW; + +namespace TW::Komodo::tests { + +TEST(KomodoAddress, Valid) { + ASSERT_TRUE(TW::validateAddress(TWCoinTypeKomodo, "RXL3YXG2ceaB6C5hfJcN4fvmLH2C34knhA")); +} + +TEST(KomodoAddress, Invalid) { + ASSERT_FALSE(TW::validateAddress(TWCoinTypeKomodo, "t1Wg9uPPAfwhBWeRjtDPa5ZHNzyBx9rJVKY")); +} + +TEST(KomodoAddress, FromPublicKey) { + auto publicKey = PublicKey(parse_hex("020e46e79a2a8d12b9b5d12c7a91adb4e454edfae43c0a0cb805427d2ac7613fd9"), TWPublicKeyTypeSECP256k1); + auto address = TW::deriveAddress(TWCoinTypeKomodo, publicKey); + ASSERT_EQ(address, "RXL3YXG2ceaB6C5hfJcN4fvmLH2C34knhA"); + + auto wallet = HDWallet("shoot island position soft burden budget tooth cruel issue economy destroy above", ""); + auto addr = wallet.deriveAddress(TWCoinTypeKomodo); + EXPECT_EQ(addr, "RCWJLXE5CSXydxdSnwcghzPgkFswERegyb"); +} + +} // namespace TW::Komodo::tests diff --git a/tests/chains/Komodo/TWAnyAddressTests.cpp b/tests/chains/Komodo/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..57c366e9340 --- /dev/null +++ b/tests/chains/Komodo/TWAnyAddressTests.cpp @@ -0,0 +1,26 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "HexCoding.h" +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +namespace TW::Komodo::tests { + +TEST(TWKomodo, Address) { + auto string = STRING("RALiENnMMjyubc38hM31h6oicPsuWdAMYg"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeKomodo)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "0ba28b3eebfe1d39dab038324be2c66090ee21a3"); +} + +} // namespace TW::Komodo::tests diff --git a/tests/chains/Komodo/TWCoinTypeTests.cpp b/tests/chains/Komodo/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..48295ea72c7 --- /dev/null +++ b/tests/chains/Komodo/TWCoinTypeTests.cpp @@ -0,0 +1,38 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + +namespace TW::Komodo::tests { + +TEST(TWKomodoCoinType, TWCoinType) { + const auto coin = TWCoinTypeKomodo; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("f53bd1a5c0f5dc4b60ba9a1882742ea96faa996e1b870795812a29604dd7829e")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("RWvfkt8UjbPWXgeZEcgYmKw2vA1bbAx5t2")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "komodo"); + assertStringsEqual(name, "Komodo"); + assertStringsEqual(symbol, "KMD"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 8); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainZcash); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x55); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(txUrl, "https://kmdexplorer.io//tx/f53bd1a5c0f5dc4b60ba9a1882742ea96faa996e1b870795812a29604dd7829e"); + assertStringsEqual(accUrl, "https://kmdexplorer.io//address/RWvfkt8UjbPWXgeZEcgYmKw2vA1bbAx5t2"); +} + +} // namespace TW::Komodo::tests \ No newline at end of file diff --git a/tests/chains/Komodo/TransactionCompilerTests.cpp b/tests/chains/Komodo/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..ebd363ed108 --- /dev/null +++ b/tests/chains/Komodo/TransactionCompilerTests.cpp @@ -0,0 +1,111 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" +#include "Zcash/TransactionBuilder.h" + +#include "proto/Bitcoin.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include + +#include "TestUtilities.h" +#include + +using namespace TW; +namespace TW::Komodo::tests { + +TEST(KomodoCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeKomodo; + + // tx on mainnet + // https://kmdexplorer.io/tx/dab3e7a705b0f80f0cd557a1e727dc50d8ccd24ff0ae159ca8cdefda656d7c9b + + const int64_t amount = 892984972; + const int64_t fee = 407; + const std::string toAddress = "RVUiqSDZEqTw9Ny4XRBsp6fgJKtmUj5nXD"; + auto publicKey = PublicKey(parse_hex("021f5a3a5f78b1f0adbbd8685c2c32de45e00e5b83faa814db57ce410295405207"), TWPublicKeyTypeSECP256k1); + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_coin_type(coin); + + auto txHash0 = parse_hex("f6118b221c4e5f436d536eded8486f6b0cc6ab99ca424da120fec593304acd8c"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(1); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo0->set_amount(892989042); + + auto utxoAddr0 = "R9TKEwwiDLA2oD7a1jt8YmCoX2cjg1pfEU"; + auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, coin); + ASSERT_EQ(hex(script0.bytes), "76a91401ea238017d65b2c5152a81146b95582b5284c2f88ac"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + + auto plan = Zcash::TransactionBuilder::plan(input); + ASSERT_EQ(plan.error, Common::Proto::OK); + + plan.amount = amount; + plan.fee = fee; + plan.change = 0; + std::copy(Zcash::SaplingBranchID.begin(), Zcash::SaplingBranchID.end(), std::back_inserter(plan.branchId)); + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan.proto(); + + // build preimage + const auto txInputData = data(input.SerializeAsString()); + EXPECT_GT(txInputData.size(), 0ul); + + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + ASSERT_GT(preImageHashes.size(), 0ul); + + Bitcoin::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + ASSERT_EQ(preSigningOutput.hash_public_keys().size(), 1); + + auto preImageHash = data(preSigningOutput.hash_public_keys()[0].data_hash()); + EXPECT_EQ(hex(preImageHash), + "09323f2c24af2cf44453aa228c213f26f40e1f87548031bad35cc4c65edc087a"); + + // compile + TW::Data signature0 = parse_hex("3045022100fb6e7a940815bc0de683dd70ed85696ffe21199958161331e76954af2ba11b1b02204860632bcad9c5a3cbaa2d60c401f7616f529e4c65915f1996286d3bd54c01cb"); + auto outputData = + TransactionCompiler::compileWithSignatures(coin, txInputData, {signature0}, {publicKey.bytes}); + Bitcoin::Proto::SigningOutput signingOutput; + ASSERT_TRUE(signingOutput.ParseFromArray(outputData.data(), (int)outputData.size())); + ASSERT_EQ(signingOutput.error(), Common::Proto::OK); + ASSERT_EQ(hex(signingOutput.encoded()), "0400008085202f89018ccd4a3093c5fe20a14d42ca99abc60c6b6f48d8de6e536d435f4e1c228b11f6010000006b483045022100fb6e7a940815bc0de683dd70ed85696ffe21199958161331e76954af2ba11b1b02204860632bcad9c5a3cbaa2d60c401f7616f529e4c65915f1996286d3bd54c01cb0121021f5a3a5f78b1f0adbbd8685c2c32de45e00e5b83faa814db57ce410295405207ffffffff018cde3935000000001976a914dd90c41f2916bcfea10ed11cd10ed4db01c5be6488ac00000000000000000000000000000000000000"); + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {signature0, signature0}, {publicKey.bytes}); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {}, {}); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} + +} // namespace TW::Komodo::tests \ No newline at end of file diff --git a/tests/chains/Kusama/SignerTests.cpp b/tests/chains/Kusama/SignerTests.cpp index 507492896db..4c18ddaa015 100644 --- a/tests/chains/Kusama/SignerTests.cpp +++ b/tests/chains/Kusama/SignerTests.cpp @@ -4,10 +4,10 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "Polkadot/Signer.h" +#include "HexCoding.h" #include "Polkadot/Extrinsic.h" #include "Polkadot/SS58Address.h" -#include "HexCoding.h" +#include "Polkadot/Signer.h" #include "Coin.h" #include "PrivateKey.h" #include "PublicKey.h" @@ -23,11 +23,11 @@ namespace TW::Polkadot::tests { extern PublicKey toPublicKey; auto genesisHashKSM = parse_hex("b0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); -TEST(PolkadotSigner, SignTransferKSM) { +TEST(KusamaSigner, SignTransferKSM) { auto blockHash = parse_hex("4955dd4813f3e91ef3fd5a825b928af2fc50a71380085f753ccef00bb1582891"); auto toAddress = SS58Address(toPublicKey, TWSS58AddressTypeKusama); - auto input = Proto::SigningInput(); + auto input = TW::Polkadot::Proto::SigningInput(); input.set_block_hash(blockHash.data(), blockHash.size()); input.set_genesis_hash(genesisHashKSM.data(), genesisHashKSM.size()); input.set_nonce(0); @@ -42,9 +42,9 @@ TEST(PolkadotSigner, SignTransferKSM) { transfer.set_to_address(toAddress.string()); transfer.set_value(value.data(), value.size()); - auto extrinsic = Extrinsic(input); + auto extrinsic = TW::Polkadot::Extrinsic(input); auto preimage = extrinsic.encodePayload(); - auto output = Signer::sign(input); + auto output = TW::Polkadot::Signer::sign(input); ASSERT_EQ(hex(preimage), "04008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0000000e307000002000000b0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe4955dd4813f3e91ef3fd5a825b928af2fc50a71380085f753ccef00bb1582891"); ASSERT_EQ(hex(output.encoded()), "25028488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee000765cfa76cfe19499f4f19ef7dc4527652ec5b2e6b5ecfaf68725dafd48ae2694ad52e61f44152a544784e847de10ddb2c56bee4406574dcbcfdb5e5d35b6d0300000004008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0"); diff --git a/tests/chains/MultiversX/SignerTests.cpp b/tests/chains/MultiversX/SignerTests.cpp index e550874e133..42136ac1c0b 100644 --- a/tests/chains/MultiversX/SignerTests.cpp +++ b/tests/chains/MultiversX/SignerTests.cpp @@ -7,6 +7,7 @@ #include #include +#include "boost/format.hpp" #include "HexCoding.h" #include "MultiversX/Address.h" #include "MultiversX/Codec.h" @@ -609,4 +610,44 @@ TEST(MultiversXSigner, SignEGLDTransferWithGuardian) { assertJSONEqual(expected, nlohmann::json::parse(encoded)); } +TEST(ElrondSigner, buildUnsignedTxBytes) { + auto input = Proto::SigningInput(); + input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(7); + input.mutable_generic_action()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_generic_action()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_generic_action()->set_value("0"); + input.mutable_generic_action()->set_data("foo"); + input.mutable_generic_action()->set_version(1); + input.set_gas_price(1000000000); + input.set_gas_limit(50000); + input.set_chain_id("1"); + auto unsignedTxBytes = Signer::buildUnsignedTxBytes(input); + auto expectedData = TW::data((boost::format(R"({"nonce":7,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1})") % BOB_BECH32 % ALICE_BECH32).str()); + ASSERT_EQ(expectedData, unsignedTxBytes); +} + +TEST(ElrondSigner, buildSigningOutput) { + auto input = Proto::SigningInput(); + input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(7); + input.mutable_generic_action()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_generic_action()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_generic_action()->set_value("0"); + input.mutable_generic_action()->set_data("foo"); + input.mutable_generic_action()->set_version(1); + input.set_gas_price(1000000000); + input.set_gas_limit(50000); + input.set_chain_id("1"); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + auto unsignedTxBytes = Signer::buildUnsignedTxBytes(input); + auto signature = privateKey.sign(unsignedTxBytes, TWCurveED25519); + + auto output = Signer::buildSigningOutput(input, signature); + std::string expectedSignatureHex = "e8647dae8b16e034d518a1a860c6a6c38d16192d0f1362833e62424f424e5da660770dff45f4b951d9cc58bfb9d14559c977d443449bfc4b8783ff9c84065700"; + ASSERT_EQ(expectedSignatureHex, hex(signature)); + auto expectedEncoded = (boost::format(R"({"nonce":7,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignatureHex).str(); + ASSERT_EQ(output.signature(), expectedSignatureHex); + ASSERT_EQ(output.encoded(), expectedEncoded); +} + } // namespace TW::MultiversX::tests diff --git a/tests/chains/MultiversX/TestAccounts.h b/tests/chains/MultiversX/TestAccounts.h index 2bc2abdf747..17bed05027c 100644 --- a/tests/chains/MultiversX/TestAccounts.h +++ b/tests/chains/MultiversX/TestAccounts.h @@ -6,6 +6,8 @@ #pragma once +namespace TW::MultiversX::tests { + // Well-known accounts on Testnet & Devnet, // https://github.com/multiversx/mx-sdk-testwallets: const auto ALICE_BECH32 = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"; @@ -15,3 +17,5 @@ const auto BOB_BECH32 = "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrru const auto BOB_PUBKEY_HEX = "8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8"; const auto BOB_SEED_HEX = "b8ca6f8203fb4b545a8e83c5384da033c415db155b53fb5b8eba7ff5a039d639"; const auto CAROL_BECH32 = "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"; + +} // namespace TW::MultiversX::tests diff --git a/tests/chains/MultiversX/TransactionCompilerTests.cpp b/tests/chains/MultiversX/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..59dacf8fc0b --- /dev/null +++ b/tests/chains/MultiversX/TransactionCompilerTests.cpp @@ -0,0 +1,247 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "TransactionCompiler.h" +#include "proto/MultiversX.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include +#include +#include + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TestAccounts.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +namespace TW::MultiversX::tests { + +TEST(MultiversXCompiler, CompileGenericActionWithSignatures) { + // txHash 2d3d69707de60e93868a417353c8ecbc6b717e09e384f1a27100287067a5f970 on testnet + /// Step 1: Prepare transaction input (protobuf) + auto coin = TWCoinTypeMultiversX; + auto input = TW::MultiversX::Proto::SigningInput(); + input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(2383); + input.mutable_generic_action()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_generic_action()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_generic_action()->set_value("1"); + input.mutable_generic_action()->set_data("foo"); + input.mutable_generic_action()->set_version(1); + input.set_gas_price(1000000000); + input.set_gas_limit(11100000); + input.set_chain_id("T"); + auto inputString = input.SerializeAsString(); + auto localInputData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, localInputData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImage = data(preSigningOutput.data()); + auto preImageHash = data(preSigningOutput.data_hash()); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + const PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + Data signature = parse_hex("4f0eb7dca9177f1849bc98b856ab4b3238a666abb3369b4fc0faba429b5c91c46b06893e841a8f411aa199c78cc456514abe39948108baf83a7be0b3fae9d70a"); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, preImageHash)); + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, localInputData, {signature}, {publicKey.bytes}); + const auto ExpectedTx = "7b226e6f6e6365223a323338332c2276616c7565223a2231222c227265636569766572223a22657264317370796176773039353676713638786a38793474656e6a7071327764356139703263366a3867737a377a7479726e7078727275717a7536366a78222c2273656e646572223a2265726431717975357774686c647a72387778356339756367386b6a616767306a6673353373386e72337a707a336879706566736464387373796372367468222c226761735072696365223a313030303030303030302c226761734c696d6974223a31313130303030302c2264617461223a225a6d3976222c22636861696e4944223a2254222c2276657273696f6e223a312c227369676e6174757265223a223466306562376463613931373766313834396263393862383536616234623332333861363636616262333336396234666330666162613432396235633931633436623036383933653834316138663431316161313939633738636334353635313461626533393934383130386261663833613762653062336661653964373061227d"; + + { + TW::MultiversX::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::MultiversX::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(localInputData.data(), (int)localInputData.size())); + signingInput.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + TW::MultiversX::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, localInputData, {signature, signature}, {publicKey.bytes}); + MultiversX::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, localInputData, {}, {}); + MultiversX::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} + +TEST(MultiversXCompiler, CompileEGLDTransferWithSignatures) { + // txHash a4dd60099bb2cd14b57f3feb54d868d64dfe1b74d8ad90d8bd0668b96ead13af on testnet + /// Step 1: Prepare transaction input (protobuf) + auto coin = TWCoinTypeMultiversX; + auto input = TW::MultiversX::Proto::SigningInput(); + input.mutable_egld_transfer()->mutable_accounts()->set_sender_nonce(2418); + input.mutable_egld_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_egld_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_egld_transfer()->set_amount("1"); + input.mutable_egld_transfer()->set_data("foo"); + input.mutable_egld_transfer()->set_version(1); + input.set_gas_price(1000000000); + input.set_gas_limit(11100000); + input.set_chain_id("T"); + auto inputString = input.SerializeAsString(); + auto localInputData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, localInputData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImage = data(preSigningOutput.data()); + auto preImageHash = data(preSigningOutput.data_hash()); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + const PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + Data signature = parse_hex("e55ad0642c7d47806410c12b1c93eb6250ccb76f711bbf82c5963bf59b5cdfe291d8b083b75de526f20457eede0c8a1dacf65c2c0034d47560c3bab5319c4006"); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, preImageHash)); + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, localInputData, {signature}, {publicKey.bytes}); + const auto ExpectedTx = "7b226e6f6e6365223a323431382c2276616c7565223a2231222c227265636569766572223a22657264317370796176773039353676713638786a38793474656e6a7071327764356139703263366a3867737a377a7479726e7078727275717a7536366a78222c2273656e646572223a2265726431717975357774686c647a72387778356339756367386b6a616767306a6673353373386e72337a707a336879706566736464387373796372367468222c226761735072696365223a313030303030303030302c226761734c696d6974223a31313130303030302c2264617461223a225a6d3976222c22636861696e4944223a2254222c2276657273696f6e223a312c227369676e6174757265223a226535356164303634326337643437383036343130633132623163393365623632353063636237366637313162626638326335393633626635396235636466653239316438623038336237356465353236663230343537656564653063386131646163663635633263303033346434373536306333626162353331396334303036227d"; + { + TW::MultiversX::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::MultiversX::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(localInputData.data(), (int)localInputData.size())); + signingInput.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + TW::MultiversX::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, localInputData, {signature, signature}, {publicKey.bytes}); + MultiversX::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, localInputData, {}, {}); + MultiversX::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} + +TEST(MultiversXCompiler, CompileESDTTransferWithSignatures) { + // txHash d399477c5d2784d55521fadb2692d447e3459c6a006b1720a3bd513c68dc6848 on testnet + /// Step 1: Prepare transaction input (protobuf) + auto coin = TWCoinTypeMultiversX; + auto input = TW::MultiversX::Proto::SigningInput(); + input.mutable_esdt_transfer()->mutable_accounts()->set_sender_nonce(2529); + input.mutable_esdt_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_esdt_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_esdt_transfer()->set_amount("100"); + input.mutable_esdt_transfer()->set_token_identifier("MBONDTEST-4ce053"); + input.mutable_esdt_transfer()->set_version(1); + input.set_gas_price(1000000000); + input.set_gas_limit(11100000); + input.set_chain_id("T"); + auto inputString = input.SerializeAsString(); + auto localInputData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, localInputData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImage = data(preSigningOutput.data()); + auto preImageHash = data(preSigningOutput.data_hash()); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + const PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + Data signature = parse_hex("99314010bca2251e52f3a8c2efae2f02b81cb27c83edbaf553e7fb771ffbe69e99ac6304bdc8477ff6727e6e6a47b3d5e17c5537859e21d06a81ec8d632ad100"); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, preImageHash)); + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, localInputData, {signature}, {publicKey.bytes}); + const auto ExpectedTx = "7b226e6f6e6365223a323532392c2276616c7565223a2230222c227265636569766572223a22657264317370796176773039353676713638786a38793474656e6a7071327764356139703263366a3867737a377a7479726e7078727275717a7536366a78222c2273656e646572223a2265726431717975357774686c647a72387778356339756367386b6a616767306a6673353373386e72337a707a336879706566736464387373796372367468222c226761735072696365223a313030303030303030302c226761734c696d6974223a31313130303030302c2264617461223a2252564e45564652795957357a5a6d56795144526b4e4449305a6a526c4e4451314e4451314e544d314e444a6b4d7a51324d7a59314d7a417a4e544d7a51445930222c22636861696e4944223a2254222c2276657273696f6e223a312c227369676e6174757265223a223939333134303130626361323235316535326633613863326566616532663032623831636232376338336564626166353533653766623737316666626536396539396163363330346264633834373766663637323765366536613437623364356531376335353337383539653231643036613831656338643633326164313030227d"; + + { + TW::MultiversX::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::MultiversX::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(localInputData.data(), (int)localInputData.size())); + signingInput.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + TW::MultiversX::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, localInputData, {signature, signature}, {publicKey.bytes}); + MultiversX::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, localInputData, {}, {}); + MultiversX::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} + +} // namespace TW::MultiversX::tests diff --git a/tests/chains/MultiversX/TransactionFactoryTests.cpp b/tests/chains/MultiversX/TransactionFactoryTests.cpp index 2c52ff34691..f61e2af8744 100644 --- a/tests/chains/MultiversX/TransactionFactoryTests.cpp +++ b/tests/chains/MultiversX/TransactionFactoryTests.cpp @@ -4,6 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +#include "boost/format.hpp" #include #include diff --git a/tests/chains/NEAR/TransactionCompilerTests.cpp b/tests/chains/NEAR/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..a37df1c0f98 --- /dev/null +++ b/tests/chains/NEAR/TransactionCompilerTests.cpp @@ -0,0 +1,93 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Base58.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/NEAR.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(NEARCompiler, CompileWithSignatures) { + auto privateKeyBytes = Base58::decode( + "3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv"); + auto publicKey = Base58::decode("Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC"); + const auto coin = TWCoinTypeNEAR; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::NEAR::Proto::SigningInput(); + input.set_signer_id("test.near"); + input.set_receiver_id("whatever.near"); + input.set_nonce(1); + auto blockHash = Base58::decode("244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM"); + input.set_block_hash(blockHash.data(), blockHash.size()); + input.set_public_key(publicKey.data(), publicKey.size()); + + input.add_actions(); + auto& transfer = *input.mutable_actions(0)->mutable_transfer(); + Data deposit(16, 0); + deposit[0] = 1; + transfer.set_deposit(deposit.data(), deposit.size()); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + /// Step 2: Obtain preimage hash + const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + preSigningOutput.ParseFromArray(preImageHashesData.data(), (int)preImageHashesData.size()); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), + "eea6e680f3ea51a7f667e9a801d0bfadf66e03d41ed54975b3c6006351461b32"); + auto signature = parse_hex("969a83332186ee9755e4839325525806e189a3d2d2bb4b4760e94443e97e1c4f22d" + "eeef0059a8e9713100eda6e19144da7e8a0ef7e539b20708ba1d8d021bd01"); + + /// Step 3: Compile transaction info + const auto tx = "09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341" + "538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901d" + "f296be6adc4cc4df34d040efa2435224b6986910e630c2fef60100000003010000000000000000" + "0000000000000000969a83332186ee9755e4839325525806e189a3d2d2bb4b4760e94443e97e1c" + "4f22deeef0059a8e9713100eda6e19144da7e8a0ef7e539b20708ba1d8d021bd01"; + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKey}); + + { + TW::NEAR::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(hex(output.signed_transaction()), tx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::NEAR::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + signingInput.set_private_key(privateKeyBytes.data(), 32); + + TW::NEAR::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.signed_transaction()), tx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, signature}, {publicKey}); + NEAR::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.signed_transaction().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } +} diff --git a/tests/chains/NEO/BinaryCodingTests.cpp b/tests/chains/NEO/BinaryCodingTests.cpp new file mode 100644 index 00000000000..1e2e643e83d --- /dev/null +++ b/tests/chains/NEO/BinaryCodingTests.cpp @@ -0,0 +1,54 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "HexCoding.h" +#include "NEO/BinaryCoding.h" + +#include + +namespace TW::NEO::tests { + +using namespace std; + +TEST(NEOBinaryCoding, encode256LE) { + Data d; + encode256LE(d, uint256_t(110000000)); + EXPECT_EQ(hex(d), "80778e06"); +} + +TEST(NEOBinaryCoding, encode256LEWithPadding) { + Data d; + encode256LE(d, uint256_t(10000000)); + EXPECT_EQ(hex(d), "80969800"); +} + +TEST(NEOBinaryCoding, encodeBytes) { + Data d; + Data value; + std::fill_n(std::back_inserter(value), 10, 'a'); + encodeBytes(d, value); + EXPECT_EQ(d.size(), value.size() + 1); + + d.clear(); + value.clear(); + std::fill_n(std::back_inserter(value), 255, 'a'); + encodeBytes(d, value); + EXPECT_EQ(d.size(), value.size() + 1 + 1); + + d.clear(); + value.clear(); + std::fill_n(std::back_inserter(value), 300, 'a'); + encodeBytes(d, value); + EXPECT_EQ(d.size(), value.size() + 1 + 2); + + d.clear(); + value.clear(); + std::fill_n(std::back_inserter(value), 0x10000, 'a'); + encodeBytes(d, value); + EXPECT_EQ(d.size(), value.size() + 1 + 4); +} + +} // namespace TW::NEO::tests diff --git a/tests/chains/NEO/ReadDataTests.cpp b/tests/chains/NEO/ReadDataTests.cpp new file mode 100644 index 00000000000..fda607bdee4 --- /dev/null +++ b/tests/chains/NEO/ReadDataTests.cpp @@ -0,0 +1,34 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "HexCoding.h" +#include "NEO/ReadData.h" + +#include + +#include "TestUtilities.h" + +namespace TW::NEO::tests { + +using namespace std; + +TEST(NEOReadData, readBytes) { + EXPECT_EXCEPTION(TW::readBytes(Data{}, 10), "Data::Cannot read enough bytes!"); +} + +TEST(NEOReadData, readVar) { + Data from{0xfe, 0x00, 0x00, 0x00, 0x01}; + int64_t max = 0; + EXPECT_EXCEPTION(TW::readVar(from, 0, max), "ReadData::ReadVarInt error: Too huge value! FormatException"); + + max = INT64_MAX; + ASSERT_EQ(TW::readVar(from, 0, max), 0x1000000); + + from = {0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}; + ASSERT_EQ(TW::readVar(from, 0, max), 0x100000000000000); +} + +} // namespace TW::NEO::tests diff --git a/tests/chains/NEO/ScriptTests.cpp b/tests/chains/NEO/ScriptTests.cpp new file mode 100644 index 00000000000..fa0afbb3237 --- /dev/null +++ b/tests/chains/NEO/ScriptTests.cpp @@ -0,0 +1,37 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "HexCoding.h" +#include "NEO/Script.h" +#include "NEO/Address.h" + +#include + +namespace TW::NEO::tests { + +using namespace std; + +TEST(NEOScript, Nep5TransferWithRet) { + auto assetId = parse_hex("0d821bd7b6d53f5c2b40e217c6defc8bbe896cf5"); + std::reverse(assetId.begin(),assetId.end()); + auto from = Address("AJzoeKrj7RHMwSrPQDPdv61ciVEYpmhkjk").toScriptHash(); + auto to = Address("AeRsDBqPiGKZhzNtL2vWhXbXGccJLCGrbJ").toScriptHash(); + auto script = Script::CreateNep5TransferScript(assetId, from, to, uint256_t(110000000), true); + + EXPECT_EQ(hex(script), "0480778e0614f88235a26e55cce0747ee827f39fd8167849672b14235a717ed7ed18a43de47499c3d05b8d4a4bcf3a53c1087472616e7366657267f56c89be8bfcdec617e2402b5c3fd5b6d71b820df166"); +} + +TEST(NEOScript, Nep5Transfer) { + auto assetId = parse_hex("f46719e2d16bf50cddcef9d4bbfece901f73cbb6"); + std::reverse(assetId.begin(),assetId.end()); + auto from = Address("APqYfjvV2cCwcvFjceVcSrcouyq74qNFKS").toScriptHash(); + auto to = Address("ANeo2toNeo3MigrationAddressxwPB2Hz").toScriptHash(); + auto script = Script::CreateNep5TransferScript(assetId, from, to, uint256_t(15000000000)); + + EXPECT_EQ(hex(script), "0500d6117e03144b721e06b50cc74e68b417716e3b099fb99757a8145872d3dd8741af4c8d5a94f8a1bfff5c617be01b53c1087472616e7366657267b6cb731f90cefebbd4f9cedd0cf56bd1e21967f4"); +} + +} // namespace TW::NEO::tests diff --git a/tests/chains/NEO/TWAnySignerTests.cpp b/tests/chains/NEO/TWAnySignerTests.cpp index 3f24c026e40..039940c5e49 100644 --- a/tests/chains/NEO/TWAnySignerTests.cpp +++ b/tests/chains/NEO/TWAnySignerTests.cpp @@ -81,6 +81,83 @@ Proto::SigningInput createInput() { return input; } +Proto::SigningInput createInputWithMultiOutput() { + const std::string NEO_ASSET_ID = "9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5"; + const std::string GAS_ASSET_ID = NEO_ASSET_ID; // use NEO as gas token to cover the gas token check + + Proto::SigningInput input; + auto privateKey = parse_hex("F18B2F726000E86B4950EBEA7BFF151F69635951BC4A31C44F28EE6AF7AEC128"); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_fee(12345); // too low + input.set_gas_asset_id(GAS_ASSET_ID); + input.set_gas_change_address("AdtSLMBqACP4jv8tRWwyweXGpyGG46eMXV"); + +#define ADD_UTXO_INPUT(hash, index, value, assetId) \ + { \ + auto utxo = input.add_inputs(); \ + utxo->set_prev_hash(parse_hex(hash).data(), parse_hex(hash).size()); \ + utxo->set_prev_index(index); \ + utxo->set_asset_id(assetId); \ + utxo->set_value(value); \ + } + + ADD_UTXO_INPUT("c61508268c5d0343af1875c60e569493100824dbdba108b31789e0e33bcb50fb", 1, 98899890000, GAS_ASSET_ID); + ADD_UTXO_INPUT("4eb2f96937a0d4dc96b77ba69a29e1de9574cbd62b16d881f1ee2061a291d70b", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("3fee0109d155dcfab272176117306b45b176914c88e8c379933c246a9e29ea0b", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("6ea9ce8c578bfeeecdf281f498e2a764689df3b93d6855a3cc45bd6b5213c426", 0, 400000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("f75ad3cbd277d83ee240e08f99a97ffd7e42a82a868e0f7043414f6d6147262b", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("054734e98f442b3e73a940ca8f594859ece1c7ddac14130b0e2f5e2799b85931", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("8b0c42d448912fc28c674fdcf8e21e4667d7d2133666168eaa0570488a9c5036", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 1, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 2, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 3, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 4, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 5, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 6, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 7, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 8, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 9, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("cf83bce600626b6077e136581c1aecc78a0bbb7d7649b1f580b6be881087ec40", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("9bd7572ba8df685e262369897d24f7217b42be496b9eed16e16a889dd83b394e", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("b4ee250397dde2f1001d782d3c803c38992447d3b351cdc9bf20cfaa2cbf995b", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("e1019ca259a1615f77263324156a70007b76cb4f26b01b2956b8f85e6842ac62", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("bd379df2aca526ac600919aaba0e59d4a1ad4e2f22d18966063cf45e431d016f", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("164c3f843b9b7bfa6a7376a1548f343acb5cdfa0193b8f31e8c9a647ea63ea7d", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("4acec74a76161eafe70e0791b1f504b5ba1d175fd4f340d5bf56804e25505e92", 0, 300000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("895c6629a71c84cbdc8956abea9ca2d9d215e909e6173b1a1a96289186a67796", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("54828143c4c3a0e1b09102e4ed29220b141089c2bc4200b1042eeb12e5e49296", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("5345e4abc86f7ace47112f5a91c129175833bafcaf9f1e1bcbbaf4d019c1c69d", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("c83e19d0d4210df97b3bc7768dc7184ae3acfc1b5b3ac9b05d2be0fe5a636b9f", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("3456b03f5cb688ce26ab1d09b7a15799136c8c886ca7c3c6bcb2363e61bb1bb1", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 10, 34000000000, NEO_ASSET_ID); + // all inputs below must be unused in this tx + ADD_UTXO_INPUT("e5a7887521b8b3aaf2d5426617ddabe8ef8ea3eab31c80a977c3b8f339df5be0", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("1455e9dd3cd6a04d81cd47acc07a7335212029ebbdcd0abc3e52c33f8b77f6eb", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("da711260085211b5573801d0dfe064235c69e61a55f9c15449ac55cc02b9adee", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("04486cfed371103dd51a89205b2c8bcc45ad887c49a768a62465f35810437bef", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("a5f27055a442db0e65103561900456d37af4233267960daded870c1ab2219ef4", 0, 500000000, NEO_ASSET_ID); + + { + auto output = input.add_outputs(); + output->set_asset_id(NEO_ASSET_ID); + output->set_to_address("Ad9A1xPbuA5YBFr1XPznDwBwQzdckAjCev"); + output->set_change_address("AdtSLMBqACP4jv8tRWwyweXGpyGG46eMXV"); + output->set_amount(25000000000); + + auto extra_output1 = output->add_extra_outputs(); + extra_output1->set_amount(100000000); + extra_output1->set_to_address("AdtSLMBqACP4jv8tRWwyweXGpyGG46eMXV"); + + auto extra_output2 = output->add_extra_outputs(); + extra_output2->set_amount(200000000); + extra_output2->set_to_address("AdtSLMBqACP4jv8tRWwyweXGpyGG46eMXV"); + } + + return input; +} + + TEST(TWAnySignerNEO, Sign) { Proto::SigningInput input = createInput(); Proto::SigningOutput output; @@ -101,4 +178,23 @@ TEST(TWAnySignerNEO, Plan) { EXPECT_EQ(plan.error(), Common::Proto::OK); } +TEST(TWAnySignerNEO, SignMultiOutput) { + Proto::SigningInput input = createInputWithMultiOutput(); + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNEO); + + ASSERT_EQ(hex(output.encoded()), "80000023fb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00e05bdf39f3b8c377a9801cb3eaa38eefe8abdd176642d5f2aab3b8217588a7e50000ebf6778b3fc3523ebc0acdbdeb29202135737ac0ac47cd814da0d63cdde955140000eeadb902cc55ac4954c1f9551ae6695c2364e0dfd0013857b5115208601271da0000ef7b431058f36524a668a7497c88ad45cc8b2c5b20891ad53d1071d3fe6c48040000f49e21b21a0c87edad0d96673223f47ad3560490613510650edb42a45570f2a50000049b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500e1f50500000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500c2eb0b00000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5e069f3d21c000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac01414057b786872d667a637ff44412e3ccf50933a9c30609016ecbcaec8b9d80f2b0e26eb0cf111674ff0802a42357671867b11c334807c40146419825eed8c45a6eed232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"); +} + +TEST(TWAnySignerNEO, PlanMultiOutput) { + Proto::SigningInput input = createInputWithMultiOutput(); + Proto::TransactionPlan plan; + ANY_PLAN(input, plan, TWCoinTypeNEO); + + EXPECT_EQ(plan.inputs_size(), 35); + EXPECT_EQ(plan.outputs_size(), 1); + EXPECT_EQ(plan.fee(), 1638000); + EXPECT_EQ(plan.error(), Common::Proto::OK); +} + } // namespace TW::NEO::tests diff --git a/tests/chains/NEO/TWCoinTypeTests.cpp b/tests/chains/NEO/TWCoinTypeTests.cpp index da443091d2e..6eb46899db5 100644 --- a/tests/chains/NEO/TWCoinTypeTests.cpp +++ b/tests/chains/NEO/TWCoinTypeTests.cpp @@ -31,4 +31,4 @@ TEST(TWNEOCoinType, TWCoinType) { assertStringsEqual(accUrl, "https://neoscan.io/address/AcxuqWhTureEQGeJgbmtSWNAtssjMLU7pb"); assertStringsEqual(id, "neo"); assertStringsEqual(name, "NEO"); -} \ No newline at end of file +} diff --git a/tests/chains/NEO/TWNEOAddressTests.cpp b/tests/chains/NEO/TWNEOAddressTests.cpp deleted file mode 100644 index 733892768ea..00000000000 --- a/tests/chains/NEO/TWNEOAddressTests.cpp +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include -#include -#include "HexCoding.h" - -#include "TestUtilities.h" -#include - -using namespace TW; - -#include - - -TEST(NEO, ExtendedKeys) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic( - STRING("client recycle grass verb guitar battle abstract table they swamp accuse athlete recall ski light").get(), - STRING("NEO").get() - )); - - auto xpub = WRAPS(TWHDWalletGetExtendedPublicKey(wallet.get(), TWPurposeBIP44, TWCoinTypeNEO, TWHDVersionXPUB)); - auto xprv = WRAPS(TWHDWalletGetExtendedPrivateKey(wallet.get(), TWPurposeBIP44, TWCoinTypeNEO, TWHDVersionXPRV)); - - assertStringsEqual(xpub, "xpub6CEwQUwgAnJmxNkb7CxuJGZN8FQNnqkKWeFjHqBEsD6PN267g3yNejdZyNEALzM7CxbQbtBzmndRjhvKyQDZoP8JrBLBQ8DJbhS1ge9Ln6F"); - assertStringsEqual(xprv, "xprv9yFazyQnLQkUjtg81BRtw8cdaDZtPP2U9RL8VSmdJsZQVDky8Wf86wK687witsCZhYZCaRALSbGRVbLBuzDzbp6dpJFqnjnvNbiNV4JgrNY"); -} \ No newline at end of file diff --git a/tests/chains/NEO/TransactionCompilerTests.cpp b/tests/chains/NEO/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..de7339cf848 --- /dev/null +++ b/tests/chains/NEO/TransactionCompilerTests.cpp @@ -0,0 +1,292 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/NEO.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "NEO/Address.h" +#include "NEO/Script.h" +#include "NEO/TransactionAttribute.h" +#include "NEO/TransactionAttributeUsage.h" + +#include "TestUtilities.h" +#include + +namespace TW::NEO::tests { + +TEST(NEOCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeNEO; + /// Step 1: Prepare transaction input (protobuf) + const std::string NEO_ASSET_ID = + "9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5"; + const std::string GAS_ASSET_ID = + "e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c60"; + + TW::NEO::Proto::SigningInput input; + auto privateKey = + PrivateKey(parse_hex("F18B2F726000E86B4950EBEA7BFF151F69635951BC4A31C44F28EE6AF7AEC128")); + auto publicKey = privateKey.getPublicKey(publicKeyType(coin)); + input.set_gas_asset_id(GAS_ASSET_ID); + input.set_gas_change_address("AdtSLMBqACP4jv8tRWwyweXGpyGG46eMXV"); + + auto* utxo = input.add_inputs(); + auto hash = parse_hex("9c85b39cd5677e2bfd6bf8a711e8da93a2f1d172b2a52c6ca87757a4bccc24de"); + std::reverse(hash.begin(), hash.end()); + utxo->set_prev_hash(hash.data(), hash.size()); + utxo->set_prev_index(1); + utxo->set_asset_id(NEO_ASSET_ID); + utxo->set_value(89300000000); + + auto txOutput = input.add_outputs(); + txOutput->set_asset_id(NEO_ASSET_ID); + txOutput->set_to_address("Ad9A1xPbuA5YBFr1XPznDwBwQzdckAjCev"); + txOutput->set_change_address("AdtSLMBqACP4jv8tRWwyweXGpyGG46eMXV"); + txOutput->set_amount(100000000); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, inputStrData); + + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), + "7fd5629cfc7cb0f8f01f15fc6d8b37ed1240c4f818d0b397bac65266aa6466da"); + + // Simulate signature, normally obtained from signature server + const auto publicKeyData = publicKey.bytes; + const auto signature = + parse_hex("5046619c8e20e1fdeec92ce95f3019f6e7cc057294eb16b2d5e55c105bf32eb27e1fc01c18585762" + "28f1fef8c0945a8ad69688e52a4ed19f5b85f5eff7e961d7"); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + + /// Step 3: Compile transaction info + auto expectedTx = + "800000019c85b39cd5677e2bfd6bf8a711e8da93a2f1d172b2a52c6ca87757a4bccc24de0100029b7cffdaa674" + "beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500e1f50500000000ea610aa6db39bd8c8556c9" + "569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500fcbbc4" + "14000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac0141405046619c8e20e1fdeec92ce95f3019f6e7cc" + "057294eb16b2d5e55c105bf32eb27e1fc01c1858576228f1fef8c0945a8ad69688e52a4ed19f5b85f5eff7e961" + "d7232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"; + + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKeyData}); + { + NEO::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(hex(output.encoded()), expectedTx); + } + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + NEO::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + signingInput.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + NEO::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.encoded()), expectedTx); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {}, {}); + NEO::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} + +TEST(NEOCompiler, Nep5TokenCompileWithSignatures) { + // tx on mainnet + // https://neoscan.io/transaction/6368FC6127DD7FAA3B7548CA97162D5BE18D4B2FA0046A79853E5864318E19B8 + + const auto coin = TWCoinTypeNEO; + /// Step 1: Prepare transaction input (protobuf) + const std::string ASSET_ID = + "f56c89be8bfcdec617e2402b5c3fd5b6d71b820d"; + const std::string GAS_ASSET_ID = + "e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c60"; + + TW::NEO::Proto::SigningInput input; + auto publicKey = PublicKey(parse_hex("030ab39b99d8675cd9bd90aaec37cba964297cc817078d33e508ab11f1d245c068"), publicKeyType(coin)); + input.set_gas_asset_id(GAS_ASSET_ID); + input.set_gas_change_address("AJzoeKrj7RHMwSrPQDPdv61ciVEYpmhkjk"); + + auto* utxo = input.add_inputs(); + auto hash = parse_hex("f231ee7b5097d912a22fe7cb6b7417490d2ddde200e3c57042be5abcdf6f974c"); + utxo->set_prev_hash(hash.data(), hash.size()); + utxo->set_prev_index(0); + utxo->set_asset_id(GAS_ASSET_ID); + utxo->set_value(7011673); + + auto txOutput = input.add_outputs(); + txOutput->set_asset_id(GAS_ASSET_ID); + txOutput->set_to_address("AJzoeKrj7RHMwSrPQDPdv61ciVEYpmhkjk"); + txOutput->set_change_address("AJzoeKrj7RHMwSrPQDPdv61ciVEYpmhkjk"); + txOutput->set_amount(6911673); + + // set nep5 transfer + auto nep5Tx = input.mutable_transaction()->mutable_nep5_transfer(); + nep5Tx->set_asset_id(ASSET_ID); + nep5Tx->set_from("AJzoeKrj7RHMwSrPQDPdv61ciVEYpmhkjk"); + nep5Tx->set_to("AeRsDBqPiGKZhzNtL2vWhXbXGccJLCGrbJ"); + auto amount = store(110000000); + nep5Tx->set_amount(amount.data(), (int)amount.size()); + nep5Tx->set_script_with_ret(true); + + input.set_fee(100000); + + // Plan + NEO::Proto::TransactionPlan plan; + ANY_PLAN(input, plan, coin); + + ASSERT_EQ(plan.error(), Common::Proto::OK); + + auto attr = plan.add_attributes(); + auto remark = parse_hex("f15508a6ea4e15e8"); + attr->set_usage((int32_t)NEO::TransactionAttributeUsage::TAU_Remark); + attr->set_data(remark.data(), (int)remark.size()); + + attr = plan.add_attributes(); + auto script = parse_hex("235a717ed7ed18a43de47499c3d05b8d4a4bcf3a"); + attr->set_usage((int32_t)NEO::TransactionAttributeUsage::TAU_Script); + attr->set_data(script.data(), (int)script.size()); + + *input.mutable_plan() = plan; + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, inputStrData); + + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), + "d301bc00e59a1c92741a31955714c76689f627dcb9d7ec176435f269a981159c"); + + // Simulate signature, normally obtained from signature server + const auto publicKeyData = publicKey.bytes; + const auto signature = + parse_hex("8b707d23f84d39ddaad7da100e2d8b657ef6c0858c6c09edc029f441f28e49ff6af994ba7ad180f90e12dd9d7828f8f28785ae5079ed9a52bb5ddd3bcce1b189"); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + + /// Step 3: Compile transaction info + auto expectedTx = + "d101510480778e0614f88235a26e55cce0747ee827f39fd8167849672b14235a717ed7ed18a43de47499c3d05b8d4a4bcf3a53c1087472616e7366657267f56c89be8bfcdec617e2402b5c3fd5b6d71b820df166000000000000000002f008f15508a6ea4e15e820235a717ed7ed18a43de47499c3d05b8d4a4bcf3a014c976fdfbc5abe4270c5e300e2dd2d0d4917746bcbe72fa212d997507bee31f2000001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c60b976690000000000235a717ed7ed18a43de47499c3d05b8d4a4bcf3a0141408b707d23f84d39ddaad7da100e2d8b657ef6c0858c6c09edc029f441f28e49ff6af994ba7ad180f90e12dd9d7828f8f28785ae5079ed9a52bb5ddd3bcce1b1892321030ab39b99d8675cd9bd90aaec37cba964297cc817078d33e508ab11f1d245c068ac"; + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKeyData}); + { + NEO::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(hex(output.encoded()), expectedTx); + } +} + +TEST(NEOCompiler, InvocationTransactionCompileWithSignatures) { + // tx on mainnet + // https://neoscan.io/transaction/73f540f5ce6fd4f363262e4b168d411f5443c694f3c53beee36fc03861c00356 + + const auto coin = TWCoinTypeNEO; + /// Step 1: Prepare transaction input (protobuf) + auto ASSET_ID = parse_hex("f46719e2d16bf50cddcef9d4bbfece901f73cbb6"); + std::reverse(ASSET_ID.begin(), ASSET_ID.end()); + + const std::string GAS_ASSET_ID = + "e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c60"; + + TW::NEO::Proto::SigningInput input; + auto publicKey = PublicKey(parse_hex("02337b2d3982d71aa234a112cd8507260f480994d20129921f5a95c77f8bbe1bb3"), publicKeyType(coin)); + + auto amount = store(15000000000); + + auto script = NEO::Script::CreateNep5TransferScript( + ASSET_ID, + NEO::Address("APqYfjvV2cCwcvFjceVcSrcouyq74qNFKS").toScriptHash(), + NEO::Address("ANeo2toNeo3MigrationAddressxwPB2Hz").toScriptHash(), + load(amount)); + + // set invocation transaction script + input.mutable_transaction()->mutable_invocation_generic()->set_script(script.data(), (int)script.size()); + + auto plan = input.mutable_plan(); + auto attr = plan->add_attributes(); + auto attrScript = parse_hex("5872d3dd8741af4c8d5a94f8a1bfff5c617be01b"); + attr->set_usage((int32_t)NEO::TransactionAttributeUsage::TAU_Script); + attr->set_data(attrScript.data(), (int)attrScript.size()); + + attr = plan->add_attributes(); + auto remark1 = parse_hex("46726f6d204e656f4c696e652061742031363539303030373533343031"); + attr->set_usage((int32_t)NEO::TransactionAttributeUsage::TAU_Remark1); + attr->set_data(remark1.data(), (int)remark1.size()); + + attr = plan->add_attributes(); + auto remark14 = parse_hex("4e55577138626836486363746e5357346167384a4a4b453734333841637374554d54"); + attr->set_usage((int32_t)NEO::TransactionAttributeUsage::TAU_Remark14); + attr->set_data(remark14.data(), (int)remark14.size()); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, inputStrData); + + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), + "a59a59f840dfc9426f070355bbbbe024b673095d86ba1b2810f61d5291f127f3"); + + // Simulate signature, normally obtained from signature server + const auto publicKeyData = publicKey.bytes; + const auto signature = + parse_hex("0c22129691f4438adf0ff178acc4811dee1c625df0f65909791e2c0f563cd88f7967f0ccbb6b60609e5225fb7b2873d510fe42c43c2741d90ca002afb4861d5c"); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + + /// Step 3: Compile transaction info + const auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKeyData}); + + NEO::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + auto expectedTx = + "d101500500d6117e03144b721e06b50cc74e68b417716e3b099fb99757a8145872d3dd8741af4c8d5a94f8a1bfff5c617be01b53c1087472616e7366657267b6cb731f90cefebbd4f9cedd0cf56bd1e21967f4000000000000000003205872d3dd8741af4c8d5a94f8a1bfff5c617be01bf11d46726f6d204e656f4c696e652061742031363539303030373533343031fe224e55577138626836486363746e5357346167384a4a4b453734333841637374554d5400000141400c22129691f4438adf0ff178acc4811dee1c625df0f65909791e2c0f563cd88f7967f0ccbb6b60609e5225fb7b2873d510fe42c43c2741d90ca002afb4861d5c232102337b2d3982d71aa234a112cd8507260f480994d20129921f5a95c77f8bbe1bb3ac"; + EXPECT_EQ(hex(output.encoded()), expectedTx); +} + +} // namespace TW::NEO::tests diff --git a/tests/chains/NEO/TransactionTests.cpp b/tests/chains/NEO/TransactionTests.cpp index c4f7386c1bc..0d3f252b978 100644 --- a/tests/chains/NEO/TransactionTests.cpp +++ b/tests/chains/NEO/TransactionTests.cpp @@ -222,6 +222,24 @@ TEST(NEOTransaction, SerializeDeserializeMiner) { std::invalid_argument); } +TEST(NEOTransaction, SerializeDeserializeInvocation) { + string data = "d1014f04e0aebb00148b720fabfe9cf041f9a27df4f7f1daaabf73c66414f88235a26e55cce0747ee827f39fd8167849672b53c1087472616e7366657267e345419e7377286ee5b0a39b56e30f6213ab9e4d00000000000000000220f88235a26e55cce0747ee827f39fd8167849672bf0084d65822107fcfd5200000141407d9089f957dbaf526a425f7dc494c62511989124f357364c750a3c3bff94e9f677c9cd497d9dcea3c7a1abbfa59411608736c86ef23297d0813699cd279fe27923210339fbbe7bdce6be3101fd8a1fb45df6782cd8ab76c80c884bfee14d5daafac392ac"; + std::unique_ptr deserializedTransaction(Transaction::deserializeFrom(parse_hex(data))); + auto serialized = deserializedTransaction->serialize(); + std::unique_ptr serializedTransaction(Transaction::deserializeFrom(serialized)); + + EXPECT_EQ(*deserializedTransaction, *serializedTransaction); +} + +TEST(NEOTransaction, SerializeDeserializeContract) { + string data = "80000001820e56309f4c26b32cc00025d0e35e7faa25641fa03138877478a155794f54490000019b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500e1f50500000000f88235a26e55cce0747ee827f39fd8167849672b0141401ca0aa69bccbcb97ca9423573a2c3c696422055c02e8d4ce37e2460ae88753c66144f4fbf392de6619c73ff27346f1d0a9f3d23967327d4d4f4b3b151dbb05942321025c01e1650f6bf488c318a0105ed7214c1b6a9a14cfebd9ec5da53a29723f8101ac"; + std::unique_ptr deserializedTransaction(Transaction::deserializeFrom(parse_hex(data))); + auto serialized = deserializedTransaction->serialize(); + std::unique_ptr serializedTransaction(Transaction::deserializeFrom(serialized)); + + EXPECT_EQ(*deserializedTransaction, *serializedTransaction); +} + TEST(NEOTransaction, GetHash) { string block2tn = "0000d11f7a2800000000"; std::unique_ptr deserializedTransaction(Transaction::deserializeFrom(parse_hex(block2tn))); diff --git a/tests/chains/NEO/WitnessTests.cpp b/tests/chains/NEO/WitnessTests.cpp index 3b285ba22df..c597814c1cf 100644 --- a/tests/chains/NEO/WitnessTests.cpp +++ b/tests/chains/NEO/WitnessTests.cpp @@ -22,12 +22,14 @@ TEST(NEOWitness, Serialize) { witness.invocationScript = parse_hex(invocationScript); witness.verificationScript = parse_hex(verificationScript); EXPECT_EQ("20" + invocationScript + "14" + verificationScript, hex(witness.serialize())); + EXPECT_EQ((size_t)witness.size(), witness.invocationScript.size() + witness.verificationScript.size()); invocationScript = "bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4aba"; verificationScript = "cbb23e6f9ade28d5a8ff3eac9d73af039e821b"; witness.invocationScript = parse_hex(invocationScript); witness.verificationScript = parse_hex(verificationScript); EXPECT_EQ("21" + invocationScript + "13" + verificationScript, hex(witness.serialize())); + EXPECT_EQ((size_t)witness.size(), witness.invocationScript.size() + witness.verificationScript.size()); } TEST(NEOWitness, Deserialize) { diff --git a/tests/chains/NULS/AddressTests.cpp b/tests/chains/NULS/AddressTests.cpp index f23677b98f1..f9762480ed2 100644 --- a/tests/chains/NULS/AddressTests.cpp +++ b/tests/chains/NULS/AddressTests.cpp @@ -13,10 +13,12 @@ namespace TW::NULS::tests { TEST(NULSAddress, StaticInvalid) { ASSERT_FALSE(Address::isValid("abc")); + ASSERT_FALSE(Address::isValid("tNULSe")); ASSERT_FALSE(Address::isValid("aaeb60f3e94c9b9a09f33669435e7ef1beaed")); ASSERT_FALSE(Address::isValid("NULSd6HgbwcM8wz48f6UkFYHLVriT1L81X9z")); ASSERT_TRUE(Address::isValid("NULSd6HgUxmcJWc88iELEJ7RH9XHsazBQqnJc")); ASSERT_TRUE(Address::isValid("NULSd6HgbwcM8wz48f6UkFYHLVriT1L81X9z2")); + ASSERT_TRUE(Address::isValid("tNULSeBaMtdtUNbnNBZ5RoFoh3fHrrrm6dVZLH")); } TEST(NULSAddress, ChainID) { @@ -32,21 +34,27 @@ TEST(NULSAddress, Type) { TEST(NULSAddress, FromString) { const auto address = Address("NULSd6HgbwcM8wz48f6UkFYHLVriT1L81X9z2"); ASSERT_EQ(address.string(), "NULSd6HgbwcM8wz48f6UkFYHLVriT1L81X9z2"); + const auto address2 = Address("tNULSeBaMtdtUNbnNBZ5RoFoh3fHrrrm6dVZLH"); + ASSERT_EQ(address2.string(), "tNULSeBaMtdtUNbnNBZ5RoFoh3fHrrrm6dVZLH"); + + EXPECT_ANY_THROW(new Address("WrongPrefix")); + EXPECT_ANY_THROW(new Address("NULSdPrefixButInvalid")); } TEST(NULSAddress, FromPrivateKey) { const auto privateKey = PrivateKey(parse_hex("a1269039e4ffdf43687852d7247a295f0b5bc55e6dda031cffaa3295ca0a9d7a")); const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const auto address = Address(publicKey); - + const auto address = Address(publicKey, true); ASSERT_EQ(address.string(), "NULSd6HghWa4CN5qdxqMwYVikQxRZyj57Jn4L"); + const auto address2 = Address(publicKey, false); + ASSERT_EQ(address2.string(), "tNULSeBaMtdtUNbnNBZ5RoFoh3fHrrrm6dVZLH"); } TEST(NULSAddress, FromCompressedPublicKey) { const auto publicKey = PublicKey(parse_hex("0244d50ff36c3136b4bf81f0c74b066695bc2af43e28d7f0ca1d48fcfd084bea66"), TWPublicKeyTypeSECP256k1); - const auto address = Address(publicKey); + const auto address = Address(publicKey, true); ASSERT_EQ(address.string(), "NULSd6HgUiMKPNi221bPfqvvho8QpuYBvn1x3"); } @@ -54,7 +62,7 @@ TEST(NULSAddress, FromCompressedPublicKey) { TEST(NULSAddress, FromPrivateKey33) { const auto privateKey = PrivateKey(parse_hex("d77580833f0b3c35b7114c23d6b66790d726c308baf237ec8c369152f2c08d27")); const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const auto address = Address(publicKey); + const auto address = Address(publicKey, true); ASSERT_EQ(address.string(), "NULSd6HgXx8YkwEjePLWUmdRSZzPQzK6BXnsB"); } diff --git a/tests/chains/NULS/SignerTests.cpp b/tests/chains/NULS/SignerTests.cpp new file mode 100644 index 00000000000..02b1e589d4e --- /dev/null +++ b/tests/chains/NULS/SignerTests.cpp @@ -0,0 +1,272 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "HexCoding.h" +#include "NULS/Signer.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "proto/NULS.pb.h" + +#include + +namespace TW::NULS::tests { + +TEST(NULSSigner, Sign) { + auto privateKey = + parse_hex("0x9ce21dad67e0f0af2599b41b515a7f7018059418bab892a7b68f283d489abc4b"); + auto amount = store(uint256_t(10000000)); + auto balance = store(uint256_t(100000000)); + std::string nonce = "0000000000000000"; + Proto::SigningInput input; + + input.set_from("NULSd6Hgj7ZoVgsPN9ybB4C1N2TbvkgLc8Z9H"); + input.set_to("NULSd6Hgied7ym6qMEfVzZanMaa9qeqA6TZSe"); + input.set_amount(amount.data(), amount.size()); + input.set_chain_id(1); + input.set_idassets_id(1); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_balance(balance.data(), balance.size()); + input.set_timestamp(1569228280); + input.set_nonce(nonce.data(), nonce.size()); + + Proto::SigningOutput output; + output = Signer::sign(input); + + EXPECT_EQ( + hex(output.encoded()), + "0200f885885d00008c0117010001f7ec6473df12e751d64cf20a8baa7edd50810f8101000100201d9a00000000" + "00000000000000000000000000000000000000000000000000080000000000000000000117010001f05e787897" + "1f3374515eabb6f16d75219d887312010001008096980000000000000000000000000000000000000000000000" + "0000000000000000000000000000692103958b790c331954ed367d37bac901de5c2f06ac8368b37d7bd6cd5ae1" + "43c1d7e3463044022028019c0099e2233c7adb84bb03a9a5666ece4a5b65a026a090fa460f3679654702204df0" + "fcb8762b5944b3aba033fa1a287ccb098150035dd8b66f52dc58d3d0843a"); +} + +TEST(NULSSigner, SignToken) { + auto privateKey = + parse_hex("0x9ce21dad67e0f0af2599b41b515a7f7018059418bab892a7b68f283d489abc4b"); + auto amount = store(uint256_t(10000000)); + auto balance = store(uint256_t(100000000)); + auto feePayerBalance = store(uint256_t(1000000)); + std::string nonce = "0000000000000000"; + std::string asset_nonce = "0000000000000000"; + Proto::SigningInput input; + + input.set_from("NULSd6Hgj7ZoVgsPN9ybB4C1N2TbvkgLc8Z9H"); + input.set_to("NULSd6Hgied7ym6qMEfVzZanMaa9qeqA6TZSe"); + input.set_amount(amount.data(), amount.size()); + input.set_chain_id(9); + input.set_idassets_id(1); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_balance(balance.data(), balance.size()); + input.set_timestamp(1569228280); + input.set_nonce(asset_nonce.data(), asset_nonce.size()); + input.set_fee_payer("NULSd6Hgj7ZoVgsPN9ybB4C1N2TbvkgLc8Z9H"); + input.set_fee_payer_nonce(nonce.data(), nonce.size()); + input.set_fee_payer_balance(feePayerBalance.data(), feePayerBalance.size()); + + Proto::SigningOutput output; + output = Signer::sign(input); + + EXPECT_EQ(hex(output.encoded()), + "0200f885885d0000d20217010001f7ec6473df12e751d64cf20a8baa7edd50810f810900010080969800" + "000000000000000000000000000000000000000000000000000000000800000000000000000017010001" + "f7ec6473df12e751d64cf20a8baa7edd50810f8101000100a08601000000000000000000000000000000" + "0000000000000000000000000000080000000000000000000117010001f05e7878971f3374515eabb6f1" + "6d75219d8873120900010080969800000000000000000000000000000000000000000000000000000000" + "000000000000000000692103958b790c331954ed367d37bac901de5c2f06ac8368b37d7bd6cd5ae143c1" + "d7e346304402204629e7e39708468a405f8d8dd208d0133a686beb5d3ae829b7a2f5867c74480902203d" + "0dffac8189b1caa9087e480fd57581e8c382cc4e17034b492dd2178dac851d"); +} + +TEST(NULSSigner, SignWithFeePayer) { + auto privateKey = + parse_hex("0x48c91cd24a27a1cdc791022ff39316444229db1c466b3b1841b40c919dee3002"); + auto feePayerPrivateKey = + parse_hex("0x9401fd554cb700777e57b05338f9ff47597add8b23ce9f1c8e041e9b4e2116b6"); + auto amount = store(uint256_t(100000)); + auto balance = store(uint256_t(1000000)); + auto feePayerBalance = store(uint256_t(1000000)); + std::string nonce = "0000000000000000"; + Proto::SigningInput input; + + input.set_from("NULSd6HgYx7bdWWv7PxYhYeTRBhD6kZs1o5Ac"); + input.set_to("NULSd6HgaHjzhMEUjd4T5DFnLz9EvV4TntrdV"); + input.set_amount(amount.data(), amount.size()); + input.set_chain_id(1); + input.set_idassets_id(1); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_balance(balance.data(), balance.size()); + input.set_timestamp(1660632991); + input.set_nonce(nonce.data(), nonce.size()); + + // fee payer + input.set_fee_payer("NULSd6HgYj81NrQBFZYXvyQhHCJCkGYWDTNeA"); + input.set_fee_payer_nonce("0000000000000000"); + input.set_fee_payer_private_key(feePayerPrivateKey.data(), feePayerPrivateKey.size()); + input.set_fee_payer_balance(feePayerBalance.data(), feePayerBalance.size()); + + Proto::SigningOutput output; + output = Signer::sign(input); + + EXPECT_EQ( + hex(output.encoded()), + "02009f3ffb620000d202170100014f019a4227bff89d51590fbf53fbd98d994485f801000100a0860100000000" + "00000000000000000000000000000000000000000000000000080000000000000000001701000152a6388c8bf5" + "4e8fcd73cc824813bfef0912299b01000100a08601000000000000000000000000000000000000000000000000" + "0000000000080000000000000000000117010001686a3c9cd2ac45aee0ef825b0443d1eb209368b701000100a0" + "860100000000000000000000000000000000000000000000000000000000000000000000000000d22102764370" + "693450d654d6fc061d1d4dbfbe0c95715fd3cde7e15df073ab0983b8c8463044022040b5820b93fd5e272f2a00" + "518af45a722571834934ba20d9a866de8e6d6409ab022003c610c647648444c1e2193634b2c51817a5a6ac3fe7" + "81da1a9ea773506afd8221025910df09ca768909cce9224d182401044c7b5bd44b0adee2ec5f2e64446573ff46" + "30440220140e46b260942a8475f38df39bf54a2eea56c77199fc7ba775aa4b7f147d0d2102206c82705cba509f" + "37ba0e35520a97bccb71a9e35cadcb8d95dd7fde5c8aa9e428"); +} + +TEST(NULSSigner, SignTokenWithFeePayer) { + auto privateKey = + parse_hex("0x48c91cd24a27a1cdc791022ff39316444229db1c466b3b1841b40c919dee3002"); + + auto feePayerPrivateKey = + parse_hex("0x9401fd554cb700777e57b05338f9ff47597add8b23ce9f1c8e041e9b4e2116b6"); + auto amount = store(uint256_t(100000)); + auto balance = store(uint256_t(900000)); + auto feePayerBalance = store(uint256_t(1000000)); + std::string asset_nonce = "0000000000000000"; + Proto::SigningInput input; + + input.set_from("NULSd6HgYx7bdWWv7PxYhYeTRBhD6kZs1o5Ac"); + input.set_to("NULSd6HgaHjzhMEUjd4T5DFnLz9EvV4TntrdV"); + input.set_amount(amount.data(), amount.size()); + input.set_chain_id(9); + input.set_idassets_id(1); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_balance(balance.data(), balance.size()); + input.set_timestamp(1660636748); + input.set_nonce(asset_nonce.data(), asset_nonce.size()); + + // fee payer + input.set_fee_payer("NULSd6HgYj81NrQBFZYXvyQhHCJCkGYWDTNeA"); + input.set_fee_payer_nonce("e05d03df6ede0e22"); + input.set_fee_payer_private_key(feePayerPrivateKey.data(), feePayerPrivateKey.size()); + input.set_fee_payer_balance(feePayerBalance.data(), feePayerBalance.size()); + + Proto::SigningOutput output; + output = Signer::sign(input); + + EXPECT_EQ( + hex(output.encoded()), + "02004c4efb620000d2021701000152a6388c8bf54e8fcd73cc824813bfef0912299b09000100a0860100000000" + "0000000000000000000000000000000000000000000000000008000000000000000000170100014f019a4227bf" + "f89d51590fbf53fbd98d994485f801000100a08601000000000000000000000000000000000000000000000000" + "000000000008e05d03df6ede0e22000117010001686a3c9cd2ac45aee0ef825b0443d1eb209368b709000100a0" + "860100000000000000000000000000000000000000000000000000000000000000000000000000d32102764370" + "693450d654d6fc061d1d4dbfbe0c95715fd3cde7e15df073ab0983b8c8463044022025cb5b40bda4e6fc0ba719" + "bb0c1a907b2a0faa91316ef2c836310d49f906b6a802207d530a56a6c56d974036c86125da06d6e47f9d8ba154" + "4ac3e620cebd883a707821025910df09ca768909cce9224d182401044c7b5bd44b0adee2ec5f2e64446573ff47" + "3045022100ff6f45a1c3856f9ea954baca6b2988295bbb22c958f87f0d3baf9989930549530220426ecb152051" + "3710b99ab50e1f6c7e21b0175adef08aa05070bb9bfca8a001d8"); +} + +TEST(NULSSigner, InsufficientBalance) { + auto privateKey = + parse_hex("0x48c91cd24a27a1cdc791022ff39316444229db1c466b3b1841b40c919dee3002"); + auto feePayerPrivateKey = + parse_hex("0x9401fd554cb700777e57b05338f9ff47597add8b23ce9f1c8e041e9b4e2116b6"); + auto amount = store(uint256_t(10000000)); + auto balance = store(uint256_t(100)); + auto feePayerBalance = store(uint256_t(1000000)); + std::string nonce = "0000000000000000"; + Proto::SigningInput input; + + input.set_from("NULSd6HgYx7bdWWv7PxYhYeTRBhD6kZs1o5Ac"); + input.set_to("NULSd6HgaHjzhMEUjd4T5DFnLz9EvV4TntrdV"); + input.set_amount(amount.data(), amount.size()); + input.set_chain_id(1); + input.set_idassets_id(1); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_balance(balance.data(), balance.size()); + input.set_timestamp(1660632991); + input.set_nonce(nonce.data(), nonce.size()); + + // fee payer + input.set_fee_payer("NULSd6HgYj81NrQBFZYXvyQhHCJCkGYWDTNeA"); + input.set_fee_payer_nonce("0000000000000000"); + input.set_fee_payer_private_key(feePayerPrivateKey.data(), feePayerPrivateKey.size()); + input.set_fee_payer_balance(feePayerBalance.data(), feePayerBalance.size()); + + Proto::SigningOutput output; + output = Signer::sign(input); + + EXPECT_EQ(output.error(), Common::Proto::Error_general); + EXPECT_EQ(output.error_message(), "User account balance not sufficient"); +} + +TEST(NULSSigner, InsufficientBalanceWithFeePayer) { + auto privateKey = + parse_hex("0x48c91cd24a27a1cdc791022ff39316444229db1c466b3b1841b40c919dee3002"); + auto feePayerPrivateKey = + parse_hex("0x9401fd554cb700777e57b05338f9ff47597add8b23ce9f1c8e041e9b4e2116b6"); + auto amount = store(uint256_t(10000000)); + auto balance = store(uint256_t(100000000)); + auto feePayerBalance = store(uint256_t(100)); + std::string nonce = "0000000000000000"; + Proto::SigningInput input; + + input.set_from("NULSd6HgYx7bdWWv7PxYhYeTRBhD6kZs1o5Ac"); + input.set_to("NULSd6HgaHjzhMEUjd4T5DFnLz9EvV4TntrdV"); + input.set_amount(amount.data(), amount.size()); + input.set_chain_id(1); + input.set_idassets_id(1); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_balance(balance.data(), balance.size()); + input.set_timestamp(1660632991); + input.set_nonce(nonce.data(), nonce.size()); + + // fee payer + input.set_fee_payer("NULSd6HgYj81NrQBFZYXvyQhHCJCkGYWDTNeA"); + input.set_fee_payer_nonce("0000000000000000"); + input.set_fee_payer_private_key(feePayerPrivateKey.data(), feePayerPrivateKey.size()); + input.set_fee_payer_balance(feePayerBalance.data(), feePayerBalance.size()); + + Proto::SigningOutput output; + output = Signer::sign(input); + + EXPECT_EQ(output.error(), Common::Proto::Error_general); + EXPECT_EQ(output.error_message(), "fee payer balance not sufficient"); +} + +TEST(NULSSigner, TokenInsufficientBalance) { + auto privateKey = + parse_hex("0x9ce21dad67e0f0af2599b41b515a7f7018059418bab892a7b68f283d489abc4b"); + auto amount = store(uint256_t(10000000)); + auto balance = store(uint256_t(100000000)); + auto feePayerBalance = store(uint256_t(100)); + std::string nonce = "0000000000000000"; + std::string asset_nonce = "0000000000000000"; + Proto::SigningInput input; + + input.set_from("NULSd6Hgj7ZoVgsPN9ybB4C1N2TbvkgLc8Z9H"); + input.set_to("NULSd6Hgied7ym6qMEfVzZanMaa9qeqA6TZSe"); + input.set_amount(amount.data(), amount.size()); + input.set_chain_id(9); + input.set_idassets_id(1); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_balance(balance.data(), balance.size()); + input.set_timestamp(1569228280); + input.set_nonce(asset_nonce.data(), asset_nonce.size()); + input.set_fee_payer("NULSd6Hgj7ZoVgsPN9ybB4C1N2TbvkgLc8Z9H"); + input.set_fee_payer_nonce(nonce.data(), nonce.size()); + input.set_fee_payer_balance(feePayerBalance.data(), feePayerBalance.size()); + + Proto::SigningOutput output; + output = Signer::sign(input); + + EXPECT_EQ(output.error(), Common::Proto::Error_general); + EXPECT_EQ(output.error_message(), "fee payer balance not sufficient"); +} + +} // namespace TW::NULS::tests diff --git a/tests/chains/NULS/TWAnySignerTests.cpp b/tests/chains/NULS/TWAnySignerTests.cpp index 1081579b66b..6baa41031b6 100644 --- a/tests/chains/NULS/TWAnySignerTests.cpp +++ b/tests/chains/NULS/TWAnySignerTests.cpp @@ -5,9 +5,12 @@ // file LICENSE at the root of the source code distribution tree. #include "HexCoding.h" +#include "NULS/Address.h" +#include "PrivateKey.h" #include "proto/NULS.pb.h" -#include #include "uint256.h" +#include +#include #include "TestUtilities.h" #include @@ -15,7 +18,8 @@ namespace TW::NULS::tests { TEST(TWAnySignerNULS, Sign) { - auto privateKey = parse_hex("0x9ce21dad67e0f0af2599b41b515a7f7018059418bab892a7b68f283d489abc4b"); + auto privateKey = + parse_hex("0x9ce21dad67e0f0af2599b41b515a7f7018059418bab892a7b68f283d489abc4b"); auto amount = store(uint256_t(10000000)); auto balance = store(uint256_t(100000000)); std::string nonce = "0000000000000000"; @@ -34,7 +38,144 @@ TEST(TWAnySignerNULS, Sign) { Proto::SigningOutput output; ANY_SIGN(input, TWCoinTypeNULS); - EXPECT_EQ(hex(output.encoded()), "0200f885885d00008c0117010001f7ec6473df12e751d64cf20a8baa7edd50810f8101000100201d9a0000000000000000000000000000000000000000000000000000000000080000000000000000000117010001f05e7878971f3374515eabb6f16d75219d8873120100010080969800000000000000000000000000000000000000000000000000000000000000000000000000692103958b790c331954ed367d37bac901de5c2f06ac8368b37d7bd6cd5ae143c1d7e3463044022028019c0099e2233c7adb84bb03a9a5666ece4a5b65a026a090fa460f3679654702204df0fcb8762b5944b3aba033fa1a287ccb098150035dd8b66f52dc58d3d0843a"); + EXPECT_EQ( + hex(output.encoded()), + "0200f885885d00008c0117010001f7ec6473df12e751d64cf20a8baa7edd50810f8101000100201d9a00000000" + "00000000000000000000000000000000000000000000000000080000000000000000000117010001f05e787897" + "1f3374515eabb6f16d75219d887312010001008096980000000000000000000000000000000000000000000000" + "0000000000000000000000000000692103958b790c331954ed367d37bac901de5c2f06ac8368b37d7bd6cd5ae1" + "43c1d7e3463044022028019c0099e2233c7adb84bb03a9a5666ece4a5b65a026a090fa460f3679654702204df0" + "fcb8762b5944b3aba033fa1a287ccb098150035dd8b66f52dc58d3d0843a"); +} + +TEST(TWAnySignerNULS, SignToken) { + auto privateKey = + parse_hex("0x9ce21dad67e0f0af2599b41b515a7f7018059418bab892a7b68f283d489abc4b"); + auto amount = store(uint256_t(10000000)); + auto balance = store(uint256_t(100000000)); + auto assetBalance = store(uint256_t(100000000)); + std::string nonce = "0000000000000000"; + std::string asset_nonce = "0000000000000000"; + Proto::SigningInput input; + + input.set_from("NULSd6Hgj7ZoVgsPN9ybB4C1N2TbvkgLc8Z9H"); + input.set_to("NULSd6Hgied7ym6qMEfVzZanMaa9qeqA6TZSe"); + input.set_amount(amount.data(), amount.size()); + input.set_chain_id(9); + input.set_idassets_id(1); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_balance(assetBalance.data(), assetBalance.size()); + input.set_timestamp(1569228280); + input.set_nonce(asset_nonce.data(), asset_nonce.size()); + input.set_fee_payer("NULSd6Hgj7ZoVgsPN9ybB4C1N2TbvkgLc8Z9H"); + input.set_fee_payer_balance(balance.data(), balance.size()); + input.set_fee_payer_nonce(nonce.data(), nonce.size()); + + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNULS); + + EXPECT_EQ(hex(output.encoded()), + "0200f885885d0000d20217010001f7ec6473df12e751d64cf20a8baa7edd50810f810900010080969800" + "000000000000000000000000000000000000000000000000000000000800000000000000000017010001" + "f7ec6473df12e751d64cf20a8baa7edd50810f8101000100a08601000000000000000000000000000000" + "0000000000000000000000000000080000000000000000000117010001f05e7878971f3374515eabb6f1" + "6d75219d8873120900010080969800000000000000000000000000000000000000000000000000000000" + "000000000000000000692103958b790c331954ed367d37bac901de5c2f06ac8368b37d7bd6cd5ae143c1" + "d7e346304402204629e7e39708468a405f8d8dd208d0133a686beb5d3ae829b7a2f5867c74480902203d" + "0dffac8189b1caa9087e480fd57581e8c382cc4e17034b492dd2178dac851d"); } +// Tx: +// https://nulscan.io/transaction/info?hash=58694394d2e28e18f66cf61697c791774c44d5275dce60d5e05d03df6ede0e22 +TEST(TWAnySigner, SignWithFeePayer) { + auto privateKey = + parse_hex("0x48c91cd24a27a1cdc791022ff39316444229db1c466b3b1841b40c919dee3002"); + auto feePayerPrivateKey = + parse_hex("0x9401fd554cb700777e57b05338f9ff47597add8b23ce9f1c8e041e9b4e2116b6"); + auto pk = PrivateKey(privateKey); + auto amount = store(uint256_t(100000)); + auto balance = store(uint256_t(1000000)); + auto feePayerBalance = store(uint256_t(1000000)); + std::string nonce = "0000000000000000"; + Proto::SigningInput input; + + input.set_from("NULSd6HgYx7bdWWv7PxYhYeTRBhD6kZs1o5Ac"); + input.set_to("NULSd6HgaHjzhMEUjd4T5DFnLz9EvV4TntrdV"); + input.set_amount(amount.data(), amount.size()); + input.set_chain_id(1); + input.set_idassets_id(1); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_balance(balance.data(), balance.size()); + input.set_timestamp(1660632991); + input.set_nonce(nonce.data(), nonce.size()); + + // fee payer + input.set_fee_payer("NULSd6HgYj81NrQBFZYXvyQhHCJCkGYWDTNeA"); + input.set_fee_payer_nonce("0000000000000000"); + input.set_fee_payer_private_key(feePayerPrivateKey.data(), feePayerPrivateKey.size()); + input.set_fee_payer_balance(feePayerBalance.data(), feePayerBalance.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNULS); + + EXPECT_EQ( + hex(output.encoded()), + "02009f3ffb620000d202170100014f019a4227bff89d51590fbf53fbd98d994485f801000100a0860100000000" + "00000000000000000000000000000000000000000000000000080000000000000000001701000152a6388c8bf5" + "4e8fcd73cc824813bfef0912299b01000100a08601000000000000000000000000000000000000000000000000" + "0000000000080000000000000000000117010001686a3c9cd2ac45aee0ef825b0443d1eb209368b701000100a0" + "860100000000000000000000000000000000000000000000000000000000000000000000000000d22102764370" + "693450d654d6fc061d1d4dbfbe0c95715fd3cde7e15df073ab0983b8c8463044022040b5820b93fd5e272f2a00" + "518af45a722571834934ba20d9a866de8e6d6409ab022003c610c647648444c1e2193634b2c51817a5a6ac3fe7" + "81da1a9ea773506afd8221025910df09ca768909cce9224d182401044c7b5bd44b0adee2ec5f2e64446573ff46" + "30440220140e46b260942a8475f38df39bf54a2eea56c77199fc7ba775aa4b7f147d0d2102206c82705cba509f" + "37ba0e35520a97bccb71a9e35cadcb8d95dd7fde5c8aa9e428"); +} + +// Tx: +// https://nulscan.io/transaction/info?hash=210731f7b4a96884a2e04925e758586fb2edb07627621ba1be4de2c54b0868fc +TEST(TWAnySigner, SignTokenWithFeePayer) { + auto privateKey = + parse_hex("0x48c91cd24a27a1cdc791022ff39316444229db1c466b3b1841b40c919dee3002"); + auto feePayerPrivateKey = + parse_hex("0x9401fd554cb700777e57b05338f9ff47597add8b23ce9f1c8e041e9b4e2116b6"); + auto amount = store(uint256_t(100000)); + auto balance = store(uint256_t(900000)); + auto feePayerBalance = store(uint256_t(1000000)); + std::string asset_nonce = "0000000000000000"; + Proto::SigningInput input; + + input.set_from("NULSd6HgYx7bdWWv7PxYhYeTRBhD6kZs1o5Ac"); + input.set_to("NULSd6HgaHjzhMEUjd4T5DFnLz9EvV4TntrdV"); + input.set_amount(amount.data(), amount.size()); + input.set_chain_id(9); + input.set_idassets_id(1); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_balance(balance.data(), balance.size()); + input.set_timestamp(1660636748); + input.set_nonce(asset_nonce.data(), asset_nonce.size()); + + // fee payer + input.set_fee_payer("NULSd6HgYj81NrQBFZYXvyQhHCJCkGYWDTNeA"); + input.set_fee_payer_nonce("e05d03df6ede0e22"); + input.set_fee_payer_private_key(feePayerPrivateKey.data(), feePayerPrivateKey.size()); + input.set_fee_payer_balance(feePayerBalance.data(), feePayerBalance.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNULS); + + EXPECT_EQ( + hex(output.encoded()), + "02004c4efb620000d2021701000152a6388c8bf54e8fcd73cc824813bfef0912299b09000100a0860100000000" + "0000000000000000000000000000000000000000000000000008000000000000000000170100014f019a4227bf" + "f89d51590fbf53fbd98d994485f801000100a08601000000000000000000000000000000000000000000000000" + "000000000008e05d03df6ede0e22000117010001686a3c9cd2ac45aee0ef825b0443d1eb209368b709000100a0" + "860100000000000000000000000000000000000000000000000000000000000000000000000000d32102764370" + "693450d654d6fc061d1d4dbfbe0c95715fd3cde7e15df073ab0983b8c8463044022025cb5b40bda4e6fc0ba719" + "bb0c1a907b2a0faa91316ef2c836310d49f906b6a802207d530a56a6c56d974036c86125da06d6e47f9d8ba154" + "4ac3e620cebd883a707821025910df09ca768909cce9224d182401044c7b5bd44b0adee2ec5f2e64446573ff47" + "3045022100ff6f45a1c3856f9ea954baca6b2988295bbb22c958f87f0d3baf9989930549530220426ecb152051" + "3710b99ab50e1f6c7e21b0175adef08aa05070bb9bfca8a001d8"); +} } // namespace TW::NULS::tests diff --git a/tests/chains/NULS/TWCoinTypeTests.cpp b/tests/chains/NULS/TWCoinTypeTests.cpp index 68a18eae7d8..361db572e09 100644 --- a/tests/chains/NULS/TWCoinTypeTests.cpp +++ b/tests/chains/NULS/TWCoinTypeTests.cpp @@ -15,9 +15,9 @@ TEST(TWNULSCoinType, TWCoinType) { auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeNULS)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("303e0e42c28acc37ba952a1effd43daa1caec79928054f7abefb21c32e6fdc02")); auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeNULS, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("NULSd6HgdSjUZy7jKMZfvQ5QU6Z97oufGTGcF")); auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeNULS, accId.get())); auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeNULS)); auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeNULS)); @@ -27,8 +27,8 @@ TEST(TWNULSCoinType, TWCoinType) { ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeNULS)); ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeNULS)); assertStringsEqual(symbol, "NULS"); - assertStringsEqual(txUrl, "https://nulscan.io/transaction/info?hash=t123"); - assertStringsEqual(accUrl, "https://nulscan.io/address/info?address=a12"); + assertStringsEqual(txUrl, "https://nulscan.io/transaction/info?hash=303e0e42c28acc37ba952a1effd43daa1caec79928054f7abefb21c32e6fdc02"); + assertStringsEqual(accUrl, "https://nulscan.io/address/info?address=NULSd6HgdSjUZy7jKMZfvQ5QU6Z97oufGTGcF"); assertStringsEqual(id, "nuls"); assertStringsEqual(name, "NULS"); } diff --git a/tests/chains/NULS/TransactionCompilerTests.cpp b/tests/chains/NULS/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..8f78ac18771 --- /dev/null +++ b/tests/chains/NULS/TransactionCompilerTests.cpp @@ -0,0 +1,421 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" +#include "uint256.h" + +#include "proto/NULS.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +namespace TW::NULS::tests { + +TEST(NULSCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeNULS; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::NULS::Proto::SigningInput(); + auto from = std::string("NULSd6HgWabfcG6H7NDK2TJvtoU3wxY1YLKwJ"); + auto to = std::string("NULSd6Hgied7ym6qMEfVzZanMaa9qeqA6TZSe"); + uint32_t chainId = 1; + uint32_t idassetsId = 1; + auto amount = TW::store((TW::uint256_t)10000000); + auto amountStr = std::string(amount.begin(), amount.end()); + auto balance = TW::store((TW::uint256_t)100000000); + auto balanceStr = std::string(balance.begin(), balance.end()); + auto nonce = std::string("0000000000000000"); + input.set_from(from); + input.set_to(to); + input.set_amount(amountStr); + input.set_chain_id(chainId); + input.set_idassets_id(idassetsId); + input.set_nonce(nonce); + input.set_balance(balanceStr); + input.set_timestamp((uint32_t)1569228280); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + /// Step 2: Obtain preimage hash + const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + preSigningOutput.ParseFromArray(preImageHashesData.data(), (int)preImageHashesData.size()); + auto preImage = preSigningOutput.data(); + EXPECT_EQ(hex(preImage), + "0200f885885d00008c01170100012c177a01a7afbe98e094007b99476534fb7926b701000100201d9a00" + "000000000000000000000000000000000000000000000000000000000800000000000000000001170100" + "01f05e7878971f3374515eabb6f16d75219d887312010001008096980000000000000000000000000000" + "0000000000000000000000000000000000000000000000"); + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), + "8746b37cb4b443424d3093e8107c5dfd6c5318010bbffcc8e8ba7c1da60877fd"); + // Simulate signature, normally obtained from signature server + const Data publicKeyData = + parse_hex("033c87a3d9b812556b3034b6471cad5131a01e210c1d7ca06dd53b7d0aff0ee045"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1); + const auto signature = + parse_hex("a5234269eab6fe8a1510dd0cb36070a03464b48856e1ef2681dbb79a5ec656f92961ac01d401a6f8" + "49acc958c6c9653f49282f5a0916df036ea8766918bac19500"); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKeyData}); + const auto ExpectedEncoded = parse_hex( + "0200f885885d00008c01170100012c177a01a7afbe98e094007b99476534fb7926b701000100201d9a00000000" + "00000000000000000000000000000000000000000000000000080000000000000000000117010001f05e787897" + "1f3374515eabb6f16d75219d887312010001008096980000000000000000000000000000000000000000000000" + "00000000000000000000000000006a21033c87a3d9b812556b3034b6471cad5131a01e210c1d7ca06dd53b7d0a" + "ff0ee045473045022100a5234269eab6fe8a1510dd0cb36070a03464b48856e1ef2681dbb79a5ec656f9022029" + "61ac01d401a6f849acc958c6c9653f49282f5a0916df036ea8766918bac195"); + const auto ExpectedTx = std::string(ExpectedEncoded.begin(), ExpectedEncoded.end()); + EXPECT_EQ(outputData.size(), 259ul); + { + TW::NULS::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.encoded(), ExpectedTx); + EXPECT_EQ(output.encoded().size(), 256ul); + } + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::NULS::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + auto key = parse_hex("044014463e2ee3cc9c67a6f191dbac82288eb1d5c1111d21245bdc6a855082a1"); + signingInput.set_private_key(key.data(), key.size()); + + TW::NULS::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.encoded(), ExpectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, signature}, {publicKeyData}); + NULS::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_signatures_count); + } +} + +TEST(NULSCompiler, TokenCompileWithSignatures) { + const auto coin = TWCoinTypeNULS; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::NULS::Proto::SigningInput(); + auto from = std::string("NULSd6HgWabfcG6H7NDK2TJvtoU3wxY1YLKwJ"); + auto to = std::string("NULSd6Hgied7ym6qMEfVzZanMaa9qeqA6TZSe"); + uint32_t chainId = 9; + uint32_t idassetsId = 1; + auto amount = TW::store((TW::uint256_t)10000000); + auto amountStr = std::string(amount.begin(), amount.end()); + auto balance = TW::store((TW::uint256_t)100000000); + auto balanceStr = std::string(balance.begin(), balance.end()); + auto feePayerBalance = TW::store((TW::uint256_t)100000000); + auto feePayerBalanceStr = std::string(feePayerBalance.begin(), feePayerBalance.end()); + auto nonce = std::string("0000000000000000"); + auto asset_nonce = std::string("0000000000000000"); + input.set_from(from); + input.set_to(to); + input.set_amount(amountStr); + input.set_chain_id(chainId); + input.set_idassets_id(idassetsId); + input.set_nonce(asset_nonce.data(), asset_nonce.size()); + input.set_balance(balanceStr); + input.set_timestamp((uint32_t)1569228280); + input.set_fee_payer(from); + input.set_fee_payer_balance(feePayerBalanceStr); + input.set_fee_payer_nonce(nonce.data(), nonce.size()); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + /// Step 2: Obtain preimage hash + const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + preSigningOutput.ParseFromArray(preImageHashesData.data(), (int)preImageHashesData.size()); + auto preImage = preSigningOutput.data(); + EXPECT_EQ( + hex(preImage), + "0200f885885d0000d202170100012c177a01a7afbe98e094007b99476534fb7926b70900010080969800000000" + "0000000000000000000000000000000000000000000000000008000000000000000000170100012c177a01a7af" + "be98e094007b99476534fb7926b701000100a08601000000000000000000000000000000000000000000000000" + "0000000000080000000000000000000117010001f05e7878971f3374515eabb6f16d75219d8873120900010080" + "969800000000000000000000000000000000000000000000000000000000000000000000000000"); + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), + "9040642ce845b320453b2ccd6f80efc38fdf61ec8f0c12e0c16f6244ec2e0496"); + // Simulate signature, normally obtained from signature server + const Data publicKeyData = + parse_hex("033c87a3d9b812556b3034b6471cad5131a01e210c1d7ca06dd53b7d0aff0ee045"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1); + const auto signature = + parse_hex("5ddea604c6cdfcf6cbe32f5873937641676ee5f9aee3c40aa9857c59aefedff25b77429cf62307d4" + "3a6a79b4c106123e6232e3981032573770fe2726bf9fc07c00"); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + /// Step 3: Compile transaction info + const Data outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKeyData}); + const auto ExpectedEncoded = parse_hex( + "0200f885885d0000d202170100012c177a01a7afbe98e094007b99476534fb7926b70900010080969800000000" + "0000000000000000000000000000000000000000000000000008000000000000000000170100012c177a01a7af" + "be98e094007b99476534fb7926b701000100a08601000000000000000000000000000000000000000000000000" + "0000000000080000000000000000000117010001f05e7878971f3374515eabb6f16d75219d8873120900010080" + "9698000000000000000000000000000000000000000000000000000000000000000000000000006921033c87a3" + "d9b812556b3034b6471cad5131a01e210c1d7ca06dd53b7d0aff0ee04546304402205ddea604c6cdfcf6cbe32f" + "5873937641676ee5f9aee3c40aa9857c59aefedff202205b77429cf62307d43a6a79b4c106123e6232e3981032" + "573770fe2726bf9fc07c"); + const auto ExpectedTx = std::string(ExpectedEncoded.begin(), ExpectedEncoded.end()); + EXPECT_EQ(outputData.size(), 328ul); + + { + TW::NULS::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.encoded(), ExpectedTx); + EXPECT_EQ(output.encoded().size(), 325ul); + } + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::NULS::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + auto key = parse_hex("044014463e2ee3cc9c67a6f191dbac82288eb1d5c1111d21245bdc6a855082a1"); + signingInput.set_private_key(key.data(), key.size()); + + TW::NULS::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.encoded(), ExpectedTx); + } +} + +// Tx: +// https://nulscan.io/transaction/info?hash=58694394d2e28e18f66cf61697c791774c44d5275dce60d5e05d03df6ede0e22 +TEST(NULSCompiler, CompileWithSignaturesFeePayer) { + const auto coin = TWCoinTypeNULS; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::NULS::Proto::SigningInput(); + auto from = std::string("NULSd6HgYx7bdWWv7PxYhYeTRBhD6kZs1o5Ac"); + auto to = std::string("NULSd6HgaHjzhMEUjd4T5DFnLz9EvV4TntrdV"); + uint32_t chainId = 1; + uint32_t idassetsId = 1; + auto amount = TW::store((TW::uint256_t)100000); + auto amountStr = std::string(amount.begin(), amount.end()); + auto balance = TW::store((TW::uint256_t)1000000); + auto balanceStr = std::string(balance.begin(), balance.end()); + auto feePayerBalance = TW::store((TW::uint256_t)1000000); + auto feePayerBalanceStr = std::string(feePayerBalance.begin(), feePayerBalance.end()); + auto nonce = std::string("0000000000000000"); + input.set_from(from); + input.set_to(to); + input.set_amount(amountStr); + input.set_chain_id(chainId); + input.set_idassets_id(idassetsId); + input.set_nonce(nonce); + input.set_balance(balanceStr); + input.set_timestamp((uint32_t)1660632991); + // fee payer + input.set_fee_payer("NULSd6HgYj81NrQBFZYXvyQhHCJCkGYWDTNeA"); + input.set_fee_payer_nonce("0000000000000000"); + input.set_fee_payer_balance(feePayerBalanceStr); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + /// Step 2: Obtain preimage hash + const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + preSigningOutput.ParseFromArray(preImageHashesData.data(), (int)preImageHashesData.size()); + auto preImage = preSigningOutput.data(); + EXPECT_EQ( + hex(preImage), + "02009f3ffb620000d202170100014f019a4227bff89d51590fbf53fbd98d994485f801000100a0860100000000" + "00000000000000000000000000000000000000000000000000080000000000000000001701000152a6388c8bf5" + "4e8fcd73cc824813bfef0912299b01000100a08601000000000000000000000000000000000000000000000000" + "0000000000080000000000000000000117010001686a3c9cd2ac45aee0ef825b0443d1eb209368b701000100a0" + "860100000000000000000000000000000000000000000000000000000000000000000000000000"); + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), + "58694394d2e28e18f66cf61697c791774c44d5275dce60d5e05d03df6ede0e22"); + // Simulate signature, normally obtained from signature server + const Data publicKeyData = + parse_hex("02764370693450d654d6fc061d1d4dbfbe0c95715fd3cde7e15df073ab0983b8c8"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1); + const Data feePayerPublicKeyData = + parse_hex("025910df09ca768909cce9224d182401044c7b5bd44b0adee2ec5f2e64446573ff"); + const PublicKey feePayerPublicKey = PublicKey(feePayerPublicKeyData, TWPublicKeyTypeSECP256k1); + + const auto signature = + parse_hex("40b5820b93fd5e272f2a00518af45a722571834934ba20d9a866de8e6d6409ab03c610c647648444" + "c1e2193634b2c51817a5a6ac3fe781da1a9ea773506afd8201"); + const auto feePayerSignature = + parse_hex("140e46b260942a8475f38df39bf54a2eea56c77199fc7ba775aa4b7f147d0d216c82705cba509f37" + "ba0e35520a97bccb71a9e35cadcb8d95dd7fde5c8aa9e42800"); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + EXPECT_TRUE(feePayerPublicKey.verify(feePayerSignature, TW::data(preImageHash))); + /// Step 3: Compile transaction info + Data outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, feePayerSignature}, {publicKeyData, feePayerPublicKeyData}); + const auto ExpectedEncoded = parse_hex( + "02009f3ffb620000d202170100014f019a4227bff89d51590fbf53fbd98d994485f801000100a0860100000000" + "00000000000000000000000000000000000000000000000000080000000000000000001701000152a6388c8bf5" + "4e8fcd73cc824813bfef0912299b01000100a08601000000000000000000000000000000000000000000000000" + "0000000000080000000000000000000117010001686a3c9cd2ac45aee0ef825b0443d1eb209368b701000100a0" + "860100000000000000000000000000000000000000000000000000000000000000000000000000d22102764370" + "693450d654d6fc061d1d4dbfbe0c95715fd3cde7e15df073ab0983b8c8463044022040b5820b93fd5e272f2a00" + "518af45a722571834934ba20d9a866de8e6d6409ab022003c610c647648444c1e2193634b2c51817a5a6ac3fe7" + "81da1a9ea773506afd8221025910df09ca768909cce9224d182401044c7b5bd44b0adee2ec5f2e64446573ff46" + "30440220140e46b260942a8475f38df39bf54a2eea56c77199fc7ba775aa4b7f147d0d2102206c82705cba509f" + "37ba0e35520a97bccb71a9e35cadcb8d95dd7fde5c8aa9e428"); + const auto ExpectedTx = std::string(ExpectedEncoded.begin(), ExpectedEncoded.end()); + EXPECT_EQ(outputData.size(), 433ul); + TW::NULS::Proto::SigningOutput signingOutput; + ASSERT_TRUE(signingOutput.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(signingOutput.encoded(), ExpectedTx); + EXPECT_EQ(signingOutput.encoded().size(), 430ul); + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::NULS::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + auto key = parse_hex("0x48c91cd24a27a1cdc791022ff39316444229db1c466b3b1841b40c919dee3002"); + signingInput.set_private_key(key.data(), key.size()); + auto feePayerPrivateKey = + parse_hex("0x9401fd554cb700777e57b05338f9ff47597add8b23ce9f1c8e041e9b4e2116b6"); + signingInput.set_fee_payer_private_key(feePayerPrivateKey.data(), feePayerPrivateKey.size()); + + TW::NULS::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.encoded(), ExpectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, signature}, {publicKeyData}); + NULS::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_signatures_count); + } +} + +// Tx: +// https://nulscan.io/transaction/info?hash=210731f7b4a96884a2e04925e758586fb2edb07627621ba1be4de2c54b0868fc +TEST(NULSCompiler, TokenCompileWithSignaturesFeePayer) { + const auto coin = TWCoinTypeNULS; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::NULS::Proto::SigningInput(); + auto from = std::string("NULSd6HgYx7bdWWv7PxYhYeTRBhD6kZs1o5Ac"); + auto to = std::string("NULSd6HgaHjzhMEUjd4T5DFnLz9EvV4TntrdV"); + uint32_t chainId = 9; + uint32_t idassetsId = 1; + auto amount = TW::store((TW::uint256_t)100000); + auto amountStr = std::string(amount.begin(), amount.end()); + auto balance = TW::store((TW::uint256_t)900000); + auto balanceStr = std::string(balance.begin(), balance.end()); + auto feePayerBalance = TW::store((TW::uint256_t)1000000); + auto feePayerBalanceStr = std::string(feePayerBalance.begin(), feePayerBalance.end()); + auto nonce = std::string("0000000000000000"); + input.set_from(from); + input.set_to(to); + input.set_amount(amountStr); + input.set_chain_id(chainId); + input.set_idassets_id(idassetsId); + input.set_nonce(nonce); + input.set_balance(balanceStr); + input.set_timestamp((uint32_t)1660636748); + // fee payer + input.set_fee_payer("NULSd6HgYj81NrQBFZYXvyQhHCJCkGYWDTNeA"); + input.set_fee_payer_nonce("e05d03df6ede0e22"); + input.set_fee_payer_balance(feePayerBalanceStr); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + /// Step 2: Obtain preimage hash + const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + preSigningOutput.ParseFromArray(preImageHashesData.data(), (int)preImageHashesData.size()); + auto preImage = preSigningOutput.data(); + EXPECT_EQ( + hex(preImage), + "02004c4efb620000d2021701000152a6388c8bf54e8fcd73cc824813bfef0912299b09000100a0860100000000" + "0000000000000000000000000000000000000000000000000008000000000000000000170100014f019a4227bf" + "f89d51590fbf53fbd98d994485f801000100a08601000000000000000000000000000000000000000000000000" + "000000000008e05d03df6ede0e22000117010001686a3c9cd2ac45aee0ef825b0443d1eb209368b709000100a0" + "860100000000000000000000000000000000000000000000000000000000000000000000000000"); + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), + "210731f7b4a96884a2e04925e758586fb2edb07627621ba1be4de2c54b0868fc"); + // Simulate signature, normally obtained from signature server + const Data publicKeyData = + parse_hex("02764370693450d654d6fc061d1d4dbfbe0c95715fd3cde7e15df073ab0983b8c8"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1); + const Data feePayerPublicKeyData = + parse_hex("025910df09ca768909cce9224d182401044c7b5bd44b0adee2ec5f2e64446573ff"); + const PublicKey feePayerPublicKey = PublicKey(feePayerPublicKeyData, TWPublicKeyTypeSECP256k1); + + const auto signature = + parse_hex("25cb5b40bda4e6fc0ba719bb0c1a907b2a0faa91316ef2c836310d49f906b6a87d530a56a6c56d97" + "4036c86125da06d6e47f9d8ba1544ac3e620cebd883a707800"); + const auto feePayerSignature = + parse_hex("ff6f45a1c3856f9ea954baca6b2988295bbb22c958f87f0d3baf998993054953426ecb1520513710" + "b99ab50e1f6c7e21b0175adef08aa05070bb9bfca8a001d801"); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + EXPECT_TRUE(feePayerPublicKey.verify(feePayerSignature, TW::data(preImageHash))); + /// Step 3: Compile transaction info + Data outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, feePayerSignature}, {publicKeyData, feePayerPublicKeyData}); + const auto ExpectedEncoded = parse_hex( + "02004c4efb620000d2021701000152a6388c8bf54e8fcd73cc824813bfef0912299b09000100a0860100000000" + "0000000000000000000000000000000000000000000000000008000000000000000000170100014f019a4227bf" + "f89d51590fbf53fbd98d994485f801000100a08601000000000000000000000000000000000000000000000000" + "000000000008e05d03df6ede0e22000117010001686a3c9cd2ac45aee0ef825b0443d1eb209368b709000100a0" + "860100000000000000000000000000000000000000000000000000000000000000000000000000d32102764370" + "693450d654d6fc061d1d4dbfbe0c95715fd3cde7e15df073ab0983b8c8463044022025cb5b40bda4e6fc0ba719" + "bb0c1a907b2a0faa91316ef2c836310d49f906b6a802207d530a56a6c56d974036c86125da06d6e47f9d8ba154" + "4ac3e620cebd883a707821025910df09ca768909cce9224d182401044c7b5bd44b0adee2ec5f2e64446573ff47" + "3045022100ff6f45a1c3856f9ea954baca6b2988295bbb22c958f87f0d3baf9989930549530220426ecb152051" + "3710b99ab50e1f6c7e21b0175adef08aa05070bb9bfca8a001d8"); + const auto ExpectedTx = std::string(ExpectedEncoded.begin(), ExpectedEncoded.end()); + EXPECT_EQ(outputData.size(), 434ul); + TW::NULS::Proto::SigningOutput signingOutput; + ASSERT_TRUE(signingOutput.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(signingOutput.encoded(), ExpectedTx); + EXPECT_EQ(signingOutput.encoded().size(), 431ul); + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::NULS::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + auto key = parse_hex("0x48c91cd24a27a1cdc791022ff39316444229db1c466b3b1841b40c919dee3002"); + signingInput.set_private_key(key.data(), key.size()); + auto feePayerPrivateKey = + parse_hex("0x9401fd554cb700777e57b05338f9ff47597add8b23ce9f1c8e041e9b4e2116b6"); + signingInput.set_fee_payer_private_key(feePayerPrivateKey.data(), feePayerPrivateKey.size()); + + TW::NULS::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.encoded(), ExpectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, signature}, {publicKeyData}); + NULS::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_signatures_count); + } +} + +} // namespace TW::NULS::tests diff --git a/tests/chains/Nano/SignerTests.cpp b/tests/chains/Nano/SignerTests.cpp index c74247d6954..051146da28e 100644 --- a/tests/chains/Nano/SignerTests.cpp +++ b/tests/chains/Nano/SignerTests.cpp @@ -8,16 +8,13 @@ #include "HexCoding.h" #include "Nano/Signer.h" +#include "TestAccounts.h" #include using namespace TW; namespace TW::Nano::tests { -const std::string kPrivateKey{"173c40e97fe2afcd24187e74f6b603cb949a5365e72fbdd065a6b165e2189e34"}; -const std::string kRepOfficial1{"xrb_3arg3asgtigae3xckabaaewkx3bzsh7nwz7jkmjos79ihyaxwphhm6qgjps4"}; -const std::string kRepNanode{"xrb_1nanode8ngaakzbck8smq6ru9bethqwyehomf79sae1k7xd47dkidjqzffeg"}; - TEST(NanoSigner, sign1) { const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); @@ -207,4 +204,47 @@ TEST(NanoSigner, signInvalid7) { ASSERT_THROW(Signer signer(input), std::invalid_argument); } +TEST(NanoSigner, buildUnsignedTxBytes) { + const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Blake2b); + const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); + + auto input = Proto::SigningInput(); + input.set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); + input.set_link_block(linkBlock.data(), linkBlock.size()); + input.set_representative(kRepOfficial1); + input.set_balance("96242336390000000000000000000"); + + const auto unsignedTxBytes = Signer::buildUnsignedTxBytes(input); + ASSERT_EQ(hex(unsignedTxBytes), "f9a323153daefe041efb94d69b9669c882c935530ed953bbe8a665dfedda9696"); +} + +TEST(NanoSigner, buildSigningOutput) { + const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Blake2b); + const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); + + auto input = Proto::SigningInput(); + input.set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); + input.set_link_block(linkBlock.data(), linkBlock.size()); + input.set_representative(kRepOfficial1); + input.set_balance("96242336390000000000000000000"); + + const auto signature = parse_hex("d247f6b90383b24e612569c75a12f11242f6e03b4914eadc7d941577dcf54a3a7cb7f0a4aba4246a40d9ebb5ee1e00b4a0a834ad5a1e7bef24e11f62b95a9e09"); + + const auto out = Signer::buildSigningOutput(input, signature); + EXPECT_EQ(hex(out.signature()), "d247f6b90383b24e612569c75a12f11242f6e03b4914eadc7d941577dcf54a3a7cb7f0a4aba4246a40d9ebb5ee1e00b4a0a834ad5a1e7bef24e11f62b95a9e09"); + EXPECT_EQ(hex(out.block_hash()), "f9a323153daefe041efb94d69b9669c882c935530ed953bbe8a665dfedda9696"); + EXPECT_EQ( + "{\"account\":\"nano_1bhbsc9yuh15anq3owu1izw1nk7bhhqefrkhfo954fyt8dk1q911buk1kk4c\"," + "\"balance\":\"96242336390000000000000000000\"," + "\"link\":\"491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507\"," + "\"link_as_account\":\"nano_1kazsap8mc481zbqbcqjytpf9mmigj87qr5k5fhf97579t4k8fa94octjx6d\"," + "\"previous\":\"0000000000000000000000000000000000000000000000000000000000000000\"," + "\"representative\":\"nano_3arg3asgtigae3xckabaaewkx3bzsh7nwz7jkmjos79ihyaxwphhm6qgjps4\"," + "\"signature\":\"d247f6b90383b24e612569c75a12f11242f6e03b4914eadc7d941577dcf54a3a7cb7f0a4aba4246a40d9ebb5ee1e00b4a0a834ad5a1e7bef24e11f62b95a9e09\"," + "\"type\":\"state\"}", + out.json()); +} + } // namespace TW::Nano::tests diff --git a/tests/chains/Nano/TestAccounts.h b/tests/chains/Nano/TestAccounts.h new file mode 100644 index 00000000000..1df79e5138e --- /dev/null +++ b/tests/chains/Nano/TestAccounts.h @@ -0,0 +1,15 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +namespace TW::Nano::tests { + +const auto kPrivateKey = "173c40e97fe2afcd24187e74f6b603cb949a5365e72fbdd065a6b165e2189e34"; +const auto kRepOfficial1 = "xrb_3arg3asgtigae3xckabaaewkx3bzsh7nwz7jkmjos79ihyaxwphhm6qgjps4"; +const auto kRepNanode = "xrb_1nanode8ngaakzbck8smq6ru9bethqwyehomf79sae1k7xd47dkidjqzffeg"; + +} // namespace TW::Nano::tests diff --git a/tests/chains/Nano/TransactionCompilerTests.cpp b/tests/chains/Nano/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..508e1a8adf3 --- /dev/null +++ b/tests/chains/Nano/TransactionCompilerTests.cpp @@ -0,0 +1,108 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "TransactionCompiler.h" +#include "proto/Nano.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include +#include +#include + +#include "HexCoding.h" +#include "Nano/Signer.h" +#include "PrivateKey.h" +#include "PublicKey.h" + +#include "TestAccounts.h" +#include "TestUtilities.h" +#include + +using namespace TW; + +namespace TW::Nano::tests { + +TEST(NanoCompiler, CompileWithSignatures) { + /// Step 1 : Prepare transaction input(protobuf) + auto coin = TWCoinTypeNano; + const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Blake2b); + const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); + + auto input = Proto::SigningInput(); + input.set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); + input.set_link_block(linkBlock.data(), linkBlock.size()); + input.set_representative(kRepOfficial1); + input.set_balance("96242336390000000000000000000"); + auto inputString = input.SerializeAsString(); + auto localInputData = TW::Data(inputString.begin(), inputString.end()); + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, localInputData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(Common::Proto::OK, preSigningOutput.error()); + + auto preImage = data(preSigningOutput.data()); + auto preImageHash = data(preSigningOutput.data_hash()); + const auto ExpectedPreImageHash = "f9a323153daefe041efb94d69b9669c882c935530ed953bbe8a665dfedda9696"; + ASSERT_EQ(ExpectedPreImageHash, hex(preImageHash)); + // Verify signature (pubkey & hash & signature) + Data signature = parse_hex("d247f6b90383b24e612569c75a12f11242f6e03b4914eadc7d941577dcf54a3a7cb7f0a4aba4246a40d9ebb5ee1e00b4a0a834ad5a1e7bef24e11f62b95a9e09"); + EXPECT_TRUE(publicKey.verify(signature, preImageHash)); + /// Step 3 : Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, localInputData, {signature}, {publicKey.bytes}); + const auto ExpectedOutJson = "{\"account\":\"nano_1bhbsc9yuh15anq3owu1izw1nk7bhhqefrkhfo954fyt8dk1q911buk1kk4c\"," + "\"balance\":\"96242336390000000000000000000\"," + "\"link\":\"491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507\"," + "\"link_as_account\":\"nano_1kazsap8mc481zbqbcqjytpf9mmigj87qr5k5fhf97579t4k8fa94octjx6d\"," + "\"previous\":\"0000000000000000000000000000000000000000000000000000000000000000\"," + "\"representative\":\"nano_3arg3asgtigae3xckabaaewkx3bzsh7nwz7jkmjos79ihyaxwphhm6qgjps4\"," + "\"signature\":\"d247f6b90383b24e612569c75a12f11242f6e03b4914eadc7d941577dcf54a3a7cb7f0a4aba4246a40d9ebb5ee1e00b4a0a834ad5a1e7bef24e11f62b95a9e09\"," + "\"type\":\"state\"}"; + { + TW::Nano::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(hex(signature), hex(output.signature())); + EXPECT_EQ(ExpectedPreImageHash, hex(output.block_hash())); + EXPECT_EQ(ExpectedOutJson, output.json()); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::Nano::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(localInputData.data(), (int)localInputData.size())); + signingInput.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + TW::Nano::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + EXPECT_EQ(ExpectedOutJson, output.json()); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, localInputData, {signature, signature}, {publicKey.bytes}); + TW::Nano::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(0ul, output.json().size()); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, localInputData, {}, {}); + TW::Nano::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(0ul, output.json().size()); + EXPECT_EQ(Common::Proto::Error_invalid_params, output.error()); + } +} + +} // namespace TW::Nano::tests diff --git a/tests/chains/Nebl/AddressTests.cpp b/tests/chains/Nebl/AddressTests.cpp new file mode 100644 index 00000000000..18ba796ddd9 --- /dev/null +++ b/tests/chains/Nebl/AddressTests.cpp @@ -0,0 +1,55 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "Bitcoin/Address.h" +#include "PublicKey.h" +#include "PrivateKey.h" +#include +#include + +#include + +using namespace TW; +using namespace TW::Bitcoin; + +TEST(NeblAddress, Valid) { + ASSERT_TRUE(Address::isValid(std::string("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"))); + ASSERT_TRUE(TW::validateAddress(TWCoinTypeNebl, "NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc")); +} + +TEST(NeblAddress, Invalid) { + ASSERT_FALSE(Address::isValid(std::string("TZzJsL7VSUVeTcLb12LHr5tUW9bcx1T4G3"))); +} + +TEST(NeblAddress, FromPrivateKey) { + auto privateKey = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9")); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + auto address = Address(publicKey, TWCoinTypeP2pkhPrefix(TWCoinTypeNebl)); + ASSERT_EQ(address.string(), "NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); +} + +TEST(NeblAddress, FromPublicKey) { + auto publicKey = PublicKey(parse_hex("03787a4c5ff72dce6d97f9b6360dc302b2d8a833e8c570dcc124a96e5f564bb524"), TWPublicKeyTypeSECP256k1); + auto address = Address(publicKey, TWCoinTypeP2pkhPrefix(TWCoinTypeNebl)); + ASSERT_EQ(address.string(), "NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); + + auto addr = TW::deriveAddress(TWCoinTypeNebl, publicKey, TWDerivationBitcoinLegacy); + ASSERT_EQ(addr, "NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); +} + +TEST(NeblAddress, FromString) { + auto address = Address("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); + ASSERT_EQ(address.string(), "NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); + + auto data = TW::addressToData(TWCoinTypeNebl, "NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); + EXPECT_EQ(hex(data), "ae40b2142aba5ddd10f74d9440bfda8a36cbad5b"); + + // invalid address + data = TW::addressToData(TWCoinTypeNebl, "xZW297yHHzSdiiwqE1eN4bfm5tJULjVZ"); + EXPECT_EQ(data.size(), 0ul); +} diff --git a/tests/chains/Nebl/SignerTests.cpp b/tests/chains/Nebl/SignerTests.cpp new file mode 100644 index 00000000000..678c7b4012f --- /dev/null +++ b/tests/chains/Nebl/SignerTests.cpp @@ -0,0 +1,156 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Verge/Signer.h" +#include "Verge/Transaction.h" +#include "Verge/TransactionBuilder.h" +#include "Bitcoin/Address.h" +#include "Bitcoin/TransactionPlan.h" +#include "Bitcoin/TransactionSigner.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" + +#include + +#include +#include + +using namespace TW; +using namespace TW::Bitcoin; + + +TEST(NeblSigner, Sign) { + const int64_t amount = 1500000000; + const int64_t fee = 2000000; + const std::string toAddress = "NRrKgiZfT7jUdS3geoEBproA7hzZnDQAdr"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); + input.set_coin_type(TWCoinTypeNebl); + input.set_time(1656474580); + + auto txHash0 = parse_hex("a5a6e147da0f1b3f6dfd1081f91b0c6e31f030ae66c4be4cf4b0db0ac8b2407d"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(4294967294); + utxo0->set_amount(2500000000); + + auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9")); + auto script0 = Bitcoin::Script::lockScriptForAddress("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc", TWCoinTypeNebl); + ASSERT_EQ(hex(script0.bytes), "76a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto plan = TransactionBuilder::plan(input); + plan.amount = amount; + plan.fee = fee; + plan.change = 980000000; + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan.proto(); + + // Sign + auto result = Verge::Signer::sign(input); + + ASSERT_EQ(hex(result.encoded()), + "01000000d4cbbb62017d40b2c80adbb0f44cbec466ae30f0316e0c1bf98110fd6d3f1b0fda47e1a6a5000000006a47304402207f77f7ed50ec56447fd108b2a9a693b2ac9f62e99b59dfa914f242510943187602204618fd9195050c763eb93644e51344f6c00e4dd93aa41bb42bce42c9e4cc53b6012103787a4c5ff72dce6d97f9b6360dc302b2d8a833e8c570dcc124a96e5f564bb524feffffff02002f6859000000001976a914412033ed457c72ca70bab5fbfdc03256bd2ce07d88ac009d693a000000001976a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac00000000" + ); +} + +TEST(NeblSigner, SignAnyoneCanPay) { + const int64_t amount = 1500000000; + const int64_t fee = 2000000; + const std::string toAddress = "NRrKgiZfT7jUdS3geoEBproA7hzZnDQAdr"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAnyoneCanPay|TWBitcoinSigHashTypeSingle); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); + input.set_coin_type(TWCoinTypeNebl); + input.set_time(1656474580); + + auto txHash0 = parse_hex("a5a6e147da0f1b3f6dfd1081f91b0c6e31f030ae66c4be4cf4b0db0ac8b2407d"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(4294967294); + utxo0->set_amount(2500000000); + + auto script0 = Bitcoin::Script::lockScriptForAddress("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc", TWCoinTypeNebl); + EXPECT_EQ(hex(script0.bytes), "76a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + + auto txHash1 = parse_hex("29bd442521ea303afb09ad2583f589a6527c9218c050882b6b8527bbe4d11766"); + std::reverse(txHash1.begin(), txHash1.end()); + + auto utxo1 = input.add_utxo(); + utxo1->mutable_out_point()->set_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_index(0); + utxo1->mutable_out_point()->set_sequence(4294967294); + utxo1->set_amount(200000000); + + auto script1 = Bitcoin::Script::lockScriptForAddress("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc", TWCoinTypeNebl); + ASSERT_EQ(hex(script1.bytes), "76a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac"); + utxo1->set_script(script1.bytes.data(), script1.bytes.size()); + + auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9")); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto plan = TransactionBuilder::plan(input); + plan.amount = amount; + plan.fee = fee; + plan.change = 1000000000; + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan.proto(); + + // Sign + auto result = Bitcoin::TransactionSigner::sign(input); + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + ASSERT_EQ(hex(serialized), + "01000000d4cbbb62017d40b2c80adbb0f44cbec466ae30f0316e0c1bf98110fd6d3f1b0fda47e1a6a5000000006b483045022100827715814a853164cf04b7106ef508eeadc5d868616756d5f598eab977c3ea2d022024ab244bfda0d31a4454ec70bedc07391fd956c94bb80f6164dc45188e0507d6832103787a4c5ff72dce6d97f9b6360dc302b2d8a833e8c570dcc124a96e5f564bb524feffffff02002f6859000000001976a914412033ed457c72ca70bab5fbfdc03256bd2ce07d88ac00ca9a3b000000001976a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac00000000" + ); +} + +TEST(NeblSigner, SignWithError) { + const int64_t amount = 1500000000; + const std::string toAddress = "NRrKgiZfT7jUdS3geoEBproA7hzZnDQAdr"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); + input.set_coin_type(TWCoinTypeNebl); + input.set_time(1656474580); + + // Sign + auto result = Verge::Signer::sign(input); + + ASSERT_NE(result.error(), Common::Proto::OK); + + // PreImage + auto preResult = Verge::Signer::preImageHashes(input); + ASSERT_NE(preResult.error(), Common::Proto::OK); +} + diff --git a/tests/chains/Nebl/TWAnySignerTests.cpp b/tests/chains/Nebl/TWAnySignerTests.cpp new file mode 100644 index 00000000000..9e260dac610 --- /dev/null +++ b/tests/chains/Nebl/TWAnySignerTests.cpp @@ -0,0 +1,76 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include +#include + +#include "Bitcoin/Signer.h" +#include "Bitcoin/Address.h" +#include "Bitcoin/TransactionPlan.h" +#include "Bitcoin/TransactionSigner.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +using namespace TW; +using namespace TW::Bitcoin; + +TEST(TWAnySignerNebl, Sign) { + const int64_t amount = 1500000000; + const int64_t fee = 2000000; + const std::string toAddress = "NRrKgiZfT7jUdS3geoEBproA7hzZnDQAdr"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); + input.set_coin_type(TWCoinTypeNebl); + input.set_time(1656474580); + + auto txHash0 = parse_hex("a5a6e147da0f1b3f6dfd1081f91b0c6e31f030ae66c4be4cf4b0db0ac8b2407d"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(4294967294); + utxo0->set_amount(2500000000); + + auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9")); + auto script0 = Bitcoin::Script::lockScriptForAddress("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc", TWCoinTypeNebl); + ASSERT_EQ(hex(script0.bytes), "76a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + Bitcoin::Proto::TransactionPlan plan; + { + ANY_PLAN(input, plan, TWCoinTypeNebl); + + plan.set_amount(amount); + plan.set_fee(fee); + plan.set_change(980000000); + + *input.mutable_plan() = plan; + } + + Bitcoin::Proto::SigningOutput output; + { + ANY_SIGN(input, TWCoinTypeNebl); + ASSERT_EQ(output.error(), Common::Proto::OK); + } + + // Sign + ASSERT_EQ(hex(output.encoded()), + "01000000d4cbbb62017d40b2c80adbb0f44cbec466ae30f0316e0c1bf98110fd6d3f1b0fda47e1a6a5000000006a47304402207f77f7ed50ec56447fd108b2a9a693b2ac9f62e99b59dfa914f242510943187602204618fd9195050c763eb93644e51344f6c00e4dd93aa41bb42bce42c9e4cc53b6012103787a4c5ff72dce6d97f9b6360dc302b2d8a833e8c570dcc124a96e5f564bb524feffffff02002f6859000000001976a914412033ed457c72ca70bab5fbfdc03256bd2ce07d88ac009d693a000000001976a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac00000000" + ); +} diff --git a/tests/chains/Nebl/TWCoinTypeTests.cpp b/tests/chains/Nebl/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..d83b97e98ac --- /dev/null +++ b/tests/chains/Nebl/TWCoinTypeTests.cpp @@ -0,0 +1,33 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// +#include "TestUtilities.h" +#include +#include + + +TEST(TWNeblCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeNebl)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("53713d5208e2a1bc68ef8bd5c851f113cf5e1675d22ecf3406f38819ab9187b3")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeNebl, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeNebl, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeNebl)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeNebl)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeNebl), 8); + ASSERT_EQ(TWBlockchainVerge, TWCoinTypeBlockchain(TWCoinTypeNebl)); + ASSERT_EQ(0x70, TWCoinTypeP2shPrefix(TWCoinTypeNebl)); + ASSERT_EQ(0x35, TWCoinTypeP2pkhPrefix(TWCoinTypeNebl)); + assertStringsEqual(symbol, "NEBL"); + assertStringsEqual(txUrl, "https://explorer.nebl.io/tx/53713d5208e2a1bc68ef8bd5c851f113cf5e1675d22ecf3406f38819ab9187b3"); + assertStringsEqual(accUrl, "https://explorer.nebl.io/address/NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); + assertStringsEqual(id, "Nebl"); + assertStringsEqual(name, "Nebl"); +} \ No newline at end of file diff --git a/tests/chains/Nebl/TWNeblAddressTests.cpp b/tests/chains/Nebl/TWNeblAddressTests.cpp new file mode 100644 index 00000000000..146b9403082 --- /dev/null +++ b/tests/chains/Nebl/TWNeblAddressTests.cpp @@ -0,0 +1,22 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TWNebl, Address) { + auto string = STRING("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeNebl)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "ae40b2142aba5ddd10f74d9440bfda8a36cbad5b"); +} diff --git a/tests/chains/Nebl/TransactionBuilderTests.cpp b/tests/chains/Nebl/TransactionBuilderTests.cpp new file mode 100644 index 00000000000..5a4fd273950 --- /dev/null +++ b/tests/chains/Nebl/TransactionBuilderTests.cpp @@ -0,0 +1,57 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Verge/Transaction.h" +#include "Verge/TransactionBuilder.h" +#include "Bitcoin/TransactionPlan.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" + +#include + +#include +#include + +using namespace TW; +using namespace TW::Bitcoin; + +TEST(NeblTransactionBuilder, BuildWithTime) { + const int64_t amount = 1500000000; + const int64_t fee = 2000000; + const std::string toAddress = "NRrKgiZfT7jUdS3geoEBproA7hzZnDQAdr"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); + input.set_coin_type(TWCoinTypeNebl); + + auto txHash0 = parse_hex("a5a6e147da0f1b3f6dfd1081f91b0c6e31f030ae66c4be4cf4b0db0ac8b2407d"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(4294967294); + utxo0->set_amount(2500000000); + + auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9")); + auto script0 = Bitcoin::Script::lockScriptForAddress("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc", TWCoinTypeNebl); + ASSERT_EQ(hex(script0.bytes), "76a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto plan = TransactionBuilder::plan(input); + plan.amount = amount; + plan.fee = fee; + plan.change = 980000000; + + auto tx = Verge::TransactionBuilder::build(plan, input).payload(); + ASSERT_NE(tx.time, 0ul); +} \ No newline at end of file diff --git a/tests/chains/Nebl/TransactionCompilerTests.cpp b/tests/chains/Nebl/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..213232b07ac --- /dev/null +++ b/tests/chains/Nebl/TransactionCompilerTests.cpp @@ -0,0 +1,136 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Bitcoin.pb.h" +#include "Bitcoin/Script.h" + +#include +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + + + +TEST(NeblCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeNebl; + + // tx on mainnet + // https://Nebl-blockchain.info/tx/21314157b60ddacb842d2a749429c4112724b7a078adb9e77ba502ea2dd7c230 + + const int64_t amount = 100000000; + const int64_t fee = 226; + const std::string toAddress = "NRrKgiZfT7jUdS3geoEBproA7hzZnDQAdr"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); + input.set_coin_type(coin); + input.set_time(1584059579); + + auto txHash0 = parse_hex("ee839754c8e93d620cbec9a1c51e7b69016d00839741b03af2c039852d941212"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(1); + utxo0->mutable_out_point()->set_sequence(4294967295); + utxo0->set_amount(20000000000000); + + auto script0 = TW::Bitcoin::Script::lockScriptForAddress("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc", TWCoinTypeNebl); + ASSERT_EQ(hex(script0.bytes), "76a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + + auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9")); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + EXPECT_EQ(input.utxo_size(), 1); + // Plan + Bitcoin::Proto::TransactionPlan plan; + ANY_PLAN(input, plan, coin); + + plan.set_amount(amount); + plan.set_fee(fee); + plan.set_change(19999899999774); + + // Extend input with accepted plan + *input.mutable_plan() = plan; + + // Serialize input + const auto txInputData = data(input.SerializeAsString()); + EXPECT_GT(txInputData.size(), 0ul); + + /// Step 2: Obtain preimage hashes + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + TW::Bitcoin::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), (int)preImageHashes.size())); + + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[0].data_hash()), + "e7d7c67a125a506d46fa75f86a575360239d301a19621f9f231c46d889fe1a3b"); + + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[0].public_key_hash()), "ae40b2142aba5ddd10f74d9440bfda8a36cbad5b"); + + + + auto publicKeyHex = "03787a4c5ff72dce6d97f9b6360dc302b2d8a833e8c570dcc124a96e5f564bb524"; + auto publicKey = PublicKey(parse_hex(publicKeyHex), TWPublicKeyTypeSECP256k1); + + auto preImageHash = preSigningOutput.hash_public_keys()[0].data_hash(); + auto signature = parse_hex("3045022100a2dd4cbc8516a7b19516bce90fde7a3c4836c1277ddc61ca80d27d5711bcefc302200e049a3c2bdfd7ebacb7be48914a7cad8ea0db0695fb28ab645acdb12c413cb3"); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE( + publicKey.verifyAsDER(signature, TW::Data(preImageHash.begin(), preImageHash.end()))); + + // Simulate signatures, normally obtained from signature server. + std::vector signatureVec; + std::vector pubkeyVec; + signatureVec.push_back(signature); + pubkeyVec.push_back(publicKey.bytes); + + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, txInputData, signatureVec, pubkeyVec); + + const auto ExpectedTx = + "01000000bbd46a5e011212942d8539c0f23ab0419783006d01697b1ec5a1c9be0c623de9c8549783ee010000006b483045022100a2dd4cbc8516a7b19516bce90fde7a3c4836c1277ddc61ca80d27d5711bcefc302200e049a3c2bdfd7ebacb7be48914a7cad8ea0db0695fb28ab645acdb12c413cb3012103787a4c5ff72dce6d97f9b6360dc302b2d8a833e8c570dcc124a96e5f564bb524ffffffff0200e1f505000000001976a914412033ed457c72ca70bab5fbfdc03256bd2ce07d88ac1e5eef96301200001976a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac00000000"; + { + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: not enough signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {signature, signature}, pubkeyVec); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {}, {}); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} diff --git a/tests/chains/Nimiq/AddressTests.cpp b/tests/chains/Nimiq/AddressTests.cpp index 61065b7f453..937ce7ec4b8 100644 --- a/tests/chains/Nimiq/AddressTests.cpp +++ b/tests/chains/Nimiq/AddressTests.cpp @@ -15,6 +15,11 @@ using namespace TW; namespace TW::Nimiq::tests { +TEST(NimiqAddress, Create) { + EXPECT_ANY_THROW(new Address("")); + EXPECT_ANY_THROW(new Address(Data{})); +} + TEST(NimiqAddress, IsValid) { // No address ASSERT_FALSE(Address::isValid("")); @@ -26,6 +31,10 @@ TEST(NimiqAddress, IsValid) { ASSERT_FALSE(Address::isValid("NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0ML")); // Too long ASSERT_FALSE(Address::isValid("NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA 0MLA")); + // Is not Base32 + ASSERT_FALSE(Address::isValid("NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR ####")); + // Invalid checksum + ASSERT_FALSE(Address::isValid("NQXX 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA")); // Valid, without spaces ASSERT_TRUE(Address::isValid("NQ862H8FYGU5RM77QSN9LYLHC56ACYYR0MLA")); // Valid, normal format diff --git a/tests/chains/Oasis/TWAnySignerTests.cpp b/tests/chains/Oasis/TWAnySignerTests.cpp index 81b2e108c86..036cad243e6 100644 --- a/tests/chains/Oasis/TWAnySignerTests.cpp +++ b/tests/chains/Oasis/TWAnySignerTests.cpp @@ -35,4 +35,46 @@ TEST(TWAnySignerOasis, Sign) { hex(output.encoded())); } +TEST(TWAnySignerOasisEscrow, Sign) { + auto input = Proto::SigningInput(); + auto output = Proto::SigningOutput(); + auto& transfer = *input.mutable_escrow(); + + transfer.set_gas_price(0); + transfer.set_gas_amount("0"); + transfer.set_nonce(0); + transfer.set_account("oasis1qrrnesqpgc6rfy2m50eew5d7klqfqk69avhv4ak5"); + transfer.set_amount("10000000"); + transfer.set_context("oasis-core/consensus: tx for chain a245619497e580dd3bc1aa3256c07f68b8dcc13f92da115eadc3b231b083d3c4"); + + auto key = parse_hex("4f8b5676990b00e23d9904a92deb8d8f428ff289c8939926358f1d20537c21a0"); + input.set_private_key(key.data(), key.size()); + + ANY_SIGN(input, TWCoinTypeOasis); + + EXPECT_EQ("a2697369676e6174757265a2697369676e61747572655840f22235e307a45dbeb0c4201bee58b920c791d80356dc17ebe7fb878dbfe35a5e8e05ac8842c7f6cfaaf7a2b8898528f4cea7f9d501be1ce1275191c3333b00076a7075626c69635f6b6579582093d8f8a455f50527976a8aa87ebde38d5606efa86cb985d3fb466aff37000e3b73756e747275737465645f7261775f76616c75655864a463666565a2636761730066616d6f756e74410064626f6479a266616d6f756e744400989680676163636f756e745500c73cc001463434915ba3f39751beb7c0905b45eb656e6f6e636500666d6574686f64717374616b696e672e416464457363726f77", + hex(output.encoded())); +} + +TEST(TWAnySignerOasisReclaimEscrow, Sign) { + auto input = Proto::SigningInput(); + auto output = Proto::SigningOutput(); + auto& transfer = *input.mutable_reclaimescrow(); + + transfer.set_gas_price(0); + transfer.set_gas_amount("0"); + transfer.set_nonce(0); + transfer.set_account("oasis1qrrnesqpgc6rfy2m50eew5d7klqfqk69avhv4ak5"); + transfer.set_shares("10000000"); + transfer.set_context("oasis-core/consensus: tx for chain a245619497e580dd3bc1aa3256c07f68b8dcc13f92da115eadc3b231b083d3c4"); + + auto key = parse_hex("4f8b5676990b00e23d9904a92deb8d8f428ff289c8939926358f1d20537c21a0"); + input.set_private_key(key.data(), key.size()); + + ANY_SIGN(input, TWCoinTypeOasis); + + EXPECT_EQ("a2697369676e6174757265a2697369676e6174757265584048a3218b453a130dec2a1392556b5e03d54c6dab29600c50944e9bd0e5325b76f98ffe4e9f8b07590cd964480ce76b50d134035e73b03cba1adb7631ab67eb006a7075626c69635f6b6579582093d8f8a455f50527976a8aa87ebde38d5606efa86cb985d3fb466aff37000e3b73756e747275737465645f7261775f76616c75655868a463666565a2636761730066616d6f756e74410064626f6479a2667368617265734400989680676163636f756e745500c73cc001463434915ba3f39751beb7c0905b45eb656e6f6e636500666d6574686f64757374616b696e672e5265636c61696d457363726f77", + hex(output.encoded())); +} + } // namespace TW::Oasis::tests diff --git a/tests/chains/Oasis/TransactionCompilerTests.cpp b/tests/chains/Oasis/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..19b7f4a0e9b --- /dev/null +++ b/tests/chains/Oasis/TransactionCompilerTests.cpp @@ -0,0 +1,95 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Oasis.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +namespace TW::Oasis::tests { + +TEST(OasisCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeOasis; + /// Step 1: Prepare transaction input (protobuf) + auto privateKey = PrivateKey(parse_hex("4f8b5676990b00e23d9904a92deb8d8f428ff289c8939926358f1d20537c21a0")); + auto publicKey = privateKey.getPublicKey(publicKeyType(coin)); + + auto input = TW::Oasis::Proto::SigningInput(); + auto& transfer = *input.mutable_transfer(); + transfer.set_gas_price(0); + transfer.set_gas_amount("0"); + transfer.set_nonce(0); + transfer.set_to("oasis1qrrnesqpgc6rfy2m50eew5d7klqfqk69avhv4ak5"); + transfer.set_amount("10000000"); + transfer.set_context("oasis-core/consensus: tx for chain a245619497e580dd3bc1aa3256c07f68b8dcc13f92da115eadc3b231b083d3c4"); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, inputStrData); + + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), "373976e01fa0634a40ce8898a869f1056d862e3a0f26d8ae22ebeb5fdbcde9b3"); + + auto signature = parse_hex("6e51c18c9b2015c9b49414b3307336597f51ff331873d214ce2db81c9651a34d99529ccaa294a39ccd01c6b0bc2c2239d87c624e5ba4840cf99ac8f9283e240c"); + + /// Step 3: Compile transaction info + const auto expectedTx = "a2697369676e6174757265a2697369676e617475726558406e51c18c9b2015c9b49414b3307336597f51ff331873d214ce2db81c9651a34d99529ccaa294a39ccd01c6b0bc2c2239d87c624e5ba4840cf99ac8f9283e240c6a7075626c69635f6b6579582093d8f8a455f50527976a8aa87ebde38d5606efa86cb985d3fb466aff37000e3b73756e747275737465645f7261775f76616c7565585ea463666565a2636761730066616d6f756e74410064626f6479a262746f5500c73cc001463434915ba3f39751beb7c0905b45eb66616d6f756e744400989680656e6f6e636500666d6574686f64707374616b696e672e5472616e73666572"; + auto outputData = TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKey.bytes}); + + { + TW::Oasis::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(hex(output.encoded()), expectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::Oasis::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + signingInput.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + TW::Oasis::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.encoded()), expectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, signature}, {publicKey.bytes}); + Oasis::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {}, {}); + Oasis::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} + +} // namespace TW::Oasis::tests diff --git a/tests/chains/Ontology/AddressTests.cpp b/tests/chains/Ontology/AddressTests.cpp index 4e19eb55e20..95cc3147123 100644 --- a/tests/chains/Ontology/AddressTests.cpp +++ b/tests/chains/Ontology/AddressTests.cpp @@ -45,4 +45,21 @@ TEST(OntologyAddress, fromMultiPubKeys) { EXPECT_EQ("AYGWgijVZnrUa2tRoCcydsHUXR1111DgdW", multiAddress.string()); } +TEST(OntologyAddress, fromBytes) { + auto address = Address( + PublicKey(parse_hex("031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486"), TWPublicKeyTypeSECP256k1)); + EXPECT_EQ("AeicEjZyiXKgUeSBbYQHxsU1X3V5Buori5", address.string()); + + std::vector v(20); + + for (auto i = 0ul; i < address._data.size(); ++i) { + v[i] = address._data[i]; + } + auto address2 = Address(v); + EXPECT_EQ("AeicEjZyiXKgUeSBbYQHxsU1X3V5Buori5", address2.string()); + + v.pop_back(); + EXPECT_ANY_THROW(new Address(v)); +} + } // namespace TW::Ontology::tests diff --git a/tests/chains/Ontology/ParamsBuilderTests.cpp b/tests/chains/Ontology/ParamsBuilderTests.cpp index 74e2944411e..d90f3c7a698 100644 --- a/tests/chains/Ontology/ParamsBuilderTests.cpp +++ b/tests/chains/Ontology/ParamsBuilderTests.cpp @@ -11,7 +11,9 @@ #include "Ontology/Ont.h" #include "Ontology/ParamsBuilder.h" +#include #include +#include namespace TW::Ontology::tests { @@ -31,6 +33,7 @@ TEST(ParamsBuilder, pushInt) { 4294967296, 68719476735, 68719476736, + 281474976710655, 72057594037927935, 1152921504606846975}; std::vector codeVector{"00", @@ -48,6 +51,7 @@ TEST(ParamsBuilder, pushInt) { "050000000001", "05ffffffff0f", "050000000010", + "07ffffffffffff00", "08ffffffffffffff00", "08ffffffffffffff0f"}; for (auto index = 0ul; index < numVector.size(); index++) { diff --git a/tests/chains/Ontology/TWAnySignerTests.cpp b/tests/chains/Ontology/TWAnySignerTests.cpp index 13938ddff40..23569706dbd 100644 --- a/tests/chains/Ontology/TWAnySignerTests.cpp +++ b/tests/chains/Ontology/TWAnySignerTests.cpp @@ -237,4 +237,67 @@ TEST(TWAnySingerOntology, Oep4Transfer) { EXPECT_EQ("00d134120000c40900000000000050c3000000000000fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a4d02e9001496f688657b95be51c11a87b51adfda4ab69e9cbb1457e9d1a61f9aafa798b6c7fbeae35639681d7df653c1087472616e736665726733def739225d0f93dd2aed457d7b1fd074ec31ff00024140bd2923854d7b84b97a107bb3cddf18c8e3dddd2f36b41a1f5f5b23366484daa22871cfb819923fe01e9cb1e9ed16baa2b05c2feb76bcbe2ec125f72701c5e965232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac41406d638653597774ce45812ea2653250806b657b32b7c6ad3e027ddeba91e9a9da4bb5dacd23dafba868cb31bacb38b4a6ff2607682a426c1dc09b05a1e158d6cd2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac", hex(output.encoded())); } +TEST(TWAnySingerOntology, Oep4TokenBalanceOf) { + // curl -H "Content-Type: application/json" -X POST -d '{"Action":"sendrawtransaction", + // "Version":"1.0.0","00d1a119d4f700000000000000000000000000000000000000000000000000000000000000000000000036144a03aaf03d12fd4d46bfcc260bda73ecef33b83b51c10962616c616e63654f6667e77fb36f54874c29f503d301d91d8ab98eb2342f0000"}' + // http://polaris2.ont.io:20334/api/v1/transaction?preExec=1 + // + // {"Action":"sendrawtransaction","Desc":"SUCCESS","Error":0,"Result":{"State":1,"Gas":20000,"Result":"40922df506","Notify":[]},"Version":"1.0.0"} + auto input = Proto::SigningInput(); + input.set_contract("2f34b28eb98a1dd901d303f5294c87546fb37fe7"); + input.set_method("balanceOf"); + input.set_query_address("ANXE3XovCwBH1ckQnPc6vKYiTwRXyrVToD"); + input.set_nonce(4157872545); + auto data = Oep4TxBuilder::build(input); + auto rawTx = hex(data); + EXPECT_EQ("00d1a119d4f700000000000000000000000000000000000000000000000000000000000000000000000036144a03aaf03d12fd4d46bfcc260bda73ecef33b83b51c10962616c616e63654f6667e77fb36f54874c29f503d301d91d8ab98eb2342f0000", + rawTx); +} + +TEST(TWAnySingerOntology, Oep4TokenDecimals) { + // curl -H "Content-Type: application/json" -X POST -d '{"Action":"sendrawtransaction", + // "Version":"1.0.0","Data":"00d1b1050fb40000000000000000000000000000000000000000000000000000000000000000000000002000c108646563696d616c7367e77fb36f54874c29f503d301d91d8ab98eb2342f0000"}' + // http://polaris2.ont.io:20334/api/v1/transaction?preExec=1 + // + //{"Action":"sendrawtransaction","Desc":"SUCCESS","Error":0,"Result":{"State":1,"Gas":20000,"Result":"08","Notify":[]},"Version":"1.0.0"} + auto input = Proto::SigningInput(); + input.set_contract("2f34b28eb98a1dd901d303f5294c87546fb37fe7"); + input.set_method("decimals"); + input.set_nonce(3020883377); + auto data = Oep4TxBuilder::build(input); + auto rawTx = hex(data); + EXPECT_EQ("00d1b1050fb40000000000000000000000000000000000000000000000000000000000000000000000002000c108646563696d616c7367e77fb36f54874c29f503d301d91d8ab98eb2342f0000", + rawTx); +} + +TEST(TWAnySingerOntology, Oep4TokenTransfer) { + auto ownerPrivateKey = + parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); + auto payerPrivateKey = + parse_hex("4646464646464646464646464646464646464646464646464646464646464652"); + auto input = Proto::SigningInput(); + input.set_contract("2f34b28eb98a1dd901d303f5294c87546fb37fe7"); + input.set_method("transfer"); + input.set_nonce(2232822985); + input.set_owner_private_key(ownerPrivateKey.data(), ownerPrivateKey.size()); + input.set_payer_private_key(payerPrivateKey.data(), payerPrivateKey.size()); + input.set_to_address("ARR6PsaBwRttzCmyxCMhL7NmFk1LqExD7L"); + input.set_amount(1000); + input.set_gas_price(2500); + input.set_gas_limit(200); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeOntology); + + EXPECT_EQ("00d1c92c1685c409000000000000c80000000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df6" + "4d02e8031469c329fbb30a490979ea1a6f0b6a3a91235f6bd714fbacc8214765d457c8e3f2b5a1d3c498" + "1a2e9d2a53c1087472616e7366657267e77fb36f54874c29f503d301d91d8ab98eb2342f000241402b62" + "b4c6bc07667019e5c9a1fa1b83ca71ee23ddb763446406b1b03706bf50a6180b13e255a08ade7da376df" + "d34faee7f51c4f0056325fa79aaf7de0ef25d64e2321031bec1250aa8f78275f99a6663688f31085848d" + "0ed92f1203e447125f927b7486ac41408aa88ae92ea30a9e5059de8594f462af7dfa7545fffa6654e94e" + "edfb910bcd5452a26d1554d5d980db84d00dd330aab2fc68316660c8ae5af2c806085157e8ce232103d9" + "fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", + hex(output.encoded())); +} + } // namespace TW::Ontology::tests diff --git a/tests/chains/Ontology/TransactionCompilerTests.cpp b/tests/chains/Ontology/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..29b6cbdea17 --- /dev/null +++ b/tests/chains/Ontology/TransactionCompilerTests.cpp @@ -0,0 +1,162 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Ontology.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +namespace TW::Ontology::tests { + +TEST(OntologyCompiler, CompileWithSignatures) { + /// Prepare transaction input (protobuf) + const auto coin = TWCoinTypeOntology; + auto input = Ontology::Proto::SigningInput(); + input.set_method("transfer"); + input.set_owner_address("AaCTzuhEr6essEEKnSTuxD2GJkmGc4nuJp"); + input.set_to_address("AWBzyqpXcSpgrWyzR6qzUGWc9ZoYT3Bsvk"); + input.set_payer_address("AaCTzuhEr6essEEKnSTuxD2GJkmGc4nuJp"); + input.set_amount(1); + input.set_gas_price(3500); + input.set_gas_limit(20000); + input.set_nonce(1); + + /// Obtain preimage hash and check it + input.set_contract("ONT"); + auto ontTxInputData = data(input.SerializeAsString()); + const auto ontPreImageHashes = TransactionCompiler::preImageHashes(coin, ontTxInputData); + auto ontPreOutput = TxCompiler::Proto::PreSigningOutput(); + ontPreOutput.ParseFromArray(ontPreImageHashes.data(), (int)ontPreImageHashes.size()); + auto ontPreImageHash = ontPreOutput.data_hash(); + + input.set_contract("ONG"); + auto ongTxInputData = data(input.SerializeAsString()); + const auto ongPreImageHashes = TransactionCompiler::preImageHashes(coin, ongTxInputData); + auto ongPreOutput = TxCompiler::Proto::PreSigningOutput(); + ongPreOutput.ParseFromArray(ongPreImageHashes.data(), (int)ongPreImageHashes.size()); + auto ongPreImageHash = ongPreOutput.data_hash(); + + { + EXPECT_EQ(hex(ontPreImageHash), + "d3770eb50f1fcddc17ac9d59f1b7e69c4916dbbe4c484cc6ba27dd0792aeb943"); + EXPECT_EQ(hex(ongPreImageHash), + "788066583071cfd05a4a10e5b897b9b81d2363c16fd98128ddc81891535567af"); + } + + // Simulate signature, normally obtained from signature server + const auto publicKeyData = + parse_hex("038ea73c590f48c7d5a8ba544a928a0c8fb206aab60688793a054db9823602765a"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeNIST256p1); + const auto ontSignature = + parse_hex("b1678dfcda9b9b468d9a97a5b3021a680814180ca08cd17d9e3a9cf512b05a3b286fed9b8f635718" + "c0aabddc9fc1acfbc48561577e35ef92ee97d7fa86e14f47"); + const auto ongSignature = + parse_hex("d90c4d76e9d07d3e5c00e4a8768ce09ca66be05cfb7f48ad02632b4f08fcaa6f4e3f6f52eb4278c1" + "579065e54ea5e696b7711f071298576fa7050b21ae614bbe"); + + // Verify signature (pubkey & hash & signature) + { + EXPECT_TRUE(publicKey.verify(ontSignature, TW::data(ontPreImageHash))); + EXPECT_TRUE(publicKey.verify(ongSignature, TW::data(ongPreImageHash))); + } + + /// Compile transaction info + const Data ontOutputData = TransactionCompiler::compileWithSignatures( + coin, ontTxInputData, {ontSignature}, {publicKeyData}); + const Data ongOutputData = TransactionCompiler::compileWithSignatures( + coin, ongTxInputData, {ongSignature}, {publicKeyData}); + auto ontOutput = Ontology::Proto::SigningOutput(); + auto ongOutput = Ontology::Proto::SigningOutput(); + ontOutput.ParseFromArray(ontOutputData.data(), (int)ontOutputData.size()); + ongOutput.ParseFromArray(ongOutputData.data(), (int)ongOutputData.size()); + const auto ontExpectedTx = + "00d101000000ac0d000000000000204e000000000000ca18ec37ac94f19588926a5302ded54cd909a76e7100c6" + "6b14ca18ec37ac94f19588926a5302ded54cd909a76e6a7cc8149e21dda3257e18eb033d9451dda1d9ac8bcfa4" + "776a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000010068164f" + "6e746f6c6f67792e4e61746976652e496e766f6b6500014140b1678dfcda9b9b468d9a97a5b3021a680814180c" + "a08cd17d9e3a9cf512b05a3b286fed9b8f635718c0aabddc9fc1acfbc48561577e35ef92ee97d7fa86e14f4723" + "21038ea73c590f48c7d5a8ba544a928a0c8fb206aab60688793a054db9823602765aac"; + const auto ongExpectedTx = + "00d101000000ac0d000000000000204e000000000000ca18ec37ac94f19588926a5302ded54cd909a76e7100c6" + "6b14ca18ec37ac94f19588926a5302ded54cd909a76e6a7cc8149e21dda3257e18eb033d9451dda1d9ac8bcfa4" + "776a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000020068164f" + "6e746f6c6f67792e4e61746976652e496e766f6b6500014140d90c4d76e9d07d3e5c00e4a8768ce09ca66be05c" + "fb7f48ad02632b4f08fcaa6f4e3f6f52eb4278c1579065e54ea5e696b7711f071298576fa7050b21ae614bbe23" + "21038ea73c590f48c7d5a8ba544a928a0c8fb206aab60688793a054db9823602765aac"; + + { + EXPECT_EQ(hex(ontOutput.encoded()), ontExpectedTx); + EXPECT_EQ(hex(ongOutput.encoded()), ongExpectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, ontTxInputData, {ongSignature, ongSignature}, {publicKey.bytes}); + Ontology::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_signatures_count); + } + + { // Negative: empty signatures + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, ontTxInputData, {}, {}); + Ontology::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + + // OEP4Token transfer test case + input.set_method("transfer"); + input.set_contract("2f34b28eb98a1dd901d303f5294c87546fb37fe7"); + input.set_owner_address("Aa8QcHJ8tbRXyjpG6FHo7TysjKXxkd1Yf2"); + input.set_to_address("ARR6PsaBwRttzCmyxCMhL7NmFk1LqExD7L"); + input.set_payer_address("Aa8QcHJ8tbRXyjpG6FHo7TysjKXxkd1Yf2"); + input.set_amount(1000); + input.set_gas_price(2500); + input.set_gas_limit(20000); + input.set_nonce(1); + + auto oepTxInputData = data(input.SerializeAsString()); + const auto oepPreImageHashes = TransactionCompiler::preImageHashes(coin, oepTxInputData); + auto oepPreOutput = TxCompiler::Proto::PreSigningOutput(); + oepPreOutput.ParseFromArray(oepPreImageHashes.data(), (int)oepPreImageHashes.size()); + auto oepPreImageHash = oepPreOutput.data_hash(); + EXPECT_EQ(hex(oepPreImageHash), + "5be4a3be92a49ce2af800c94c7c44eeb8cd345c25541f63e545edc06bd72c0ed"); + + const auto oepPublicKeyData = + parse_hex("03932a08085b4bd7adcf8915f805ab35ad51f58ebbd09783b01bb4c44e503444f9"); + const PublicKey opePublicKey = PublicKey(oepPublicKeyData, TWPublicKeyTypeNIST256p1); + const auto oepSignature = + parse_hex("55aff2726c5e17dd6a6bbdaf5200442f4c9890a0cc044fb13d4a09918893dce261bb14eec2f578b590ed5c925f66bcfeddf794bee6a014c65e049f544953cb09"); + EXPECT_TRUE(opePublicKey.verify(oepSignature, TW::data(oepPreImageHash))); + + const Data oepOutputData = TransactionCompiler::compileWithSignatures( + coin, oepTxInputData, {oepSignature}, {oepPublicKeyData}); + auto oepOutput = Ontology::Proto::SigningOutput(); + oepOutput.ParseFromArray(oepOutputData.data(), (int)oepOutputData.size()); + const auto oepExpectedTx = + "00d101000000c409000000000000204e000000000000c9546dcef4038ce3b64e79d079b3c97a8931c7174d02e8" + "031469c329fbb30a490979ea1a6f0b6a3a91235f6bd714c9546dcef4038ce3b64e79d079b3c97a8931c71753c1" + "087472616e7366657267e77fb36f54874c29f503d301d91d8ab98eb2342f0001414055aff2726c5e17dd6a6bbd" + "af5200442f4c9890a0cc044fb13d4a09918893dce261bb14eec2f578b590ed5c925f66bcfeddf794bee6a014c6" + "5e049f544953cb09232103932a08085b4bd7adcf8915f805ab35ad51f58ebbd09783b01bb4c44e503444f9ac"; + EXPECT_EQ(hex(oepOutput.encoded()), oepExpectedTx); +} + +} // namespace TW::Ontology::tests diff --git a/tests/chains/Optimism/TWCoinTypeTests.cpp b/tests/chains/Optimism/TWCoinTypeTests.cpp index 7c35b7d5c76..50eadccbad3 100644 --- a/tests/chains/Optimism/TWCoinTypeTests.cpp +++ b/tests/chains/Optimism/TWCoinTypeTests.cpp @@ -4,6 +4,9 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. // +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// #include "TestUtilities.h" #include diff --git a/tests/chains/Pivx/AddressTests.cpp b/tests/chains/Pivx/AddressTests.cpp new file mode 100644 index 00000000000..f6075bc5f1a --- /dev/null +++ b/tests/chains/Pivx/AddressTests.cpp @@ -0,0 +1,53 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include + +#include "Coin.h" +#include "HexCoding.h" +#include "HDWallet.h" +#include "Bitcoin/Address.h" +#include "PublicKey.h" +#include +#include + +using namespace TW; +using namespace TW::Bitcoin; + +TEST(PivxAddress, Valid) { + ASSERT_TRUE(Address::isValid(std::string("DLCk9wuF3r8CRbawUhrDpDHXAGfkHAC7if"))); + ASSERT_TRUE(Address::isValid(std::string("DSHhD6WxT5RfG2deUzVfuC1jDuRfdDpsSo"))); +} + +TEST(PivxAddress, FromPublicKey) { + const auto publicKey = PublicKey(parse_hex("0273132ffeafdc0eec3aaef5684a320a677cc77ba43df81de944af4a7fc6de66c9"), TWPublicKeyTypeSECP256k1); + const auto address = Address(publicKey, TWCoinTypeP2pkhPrefix(TWCoinTypePivx)); + EXPECT_EQ(address.string(), "D6E4vLacYhKc6RVxLNQg5j8rtWbAH8fybH"); + + auto wallet = HDWallet("shoot island position soft burden budget tooth cruel issue economy destroy above", ""); + auto addr = wallet.deriveAddress(TWCoinTypePivx); + EXPECT_EQ(addr, "D81AqC8zKma3Cht4TbVuh4jyVVyLkZULCm"); +} + +TEST(PivxAddress, FromString) { + auto address = Address("D6E4vLacYhKc6RVxLNQg5j8rtWbAH8fybH"); + ASSERT_EQ(address.string(), "D6E4vLacYhKc6RVxLNQg5j8rtWbAH8fybH"); + + address = Address("DSHhD6WxT5RfG2deUzVfuC1jDuRfdDpsSo"); + ASSERT_EQ(address.string(), "DSHhD6WxT5RfG2deUzVfuC1jDuRfdDpsSo"); +} + +TEST(PivxAddress, AddressData) { + const auto publicKey = PublicKey(parse_hex("0273132ffeafdc0eec3aaef5684a320a677cc77ba43df81de944af4a7fc6de66c9"), TWPublicKeyTypeSECP256k1); + auto address = TW::deriveAddress(TWCoinTypePivx, publicKey); + + auto data = TW::addressToData(TWCoinTypePivx, "D6E4vLacYhKc6RVxLNQg5j8rtWbAH8fybH"); + ASSERT_EQ(hex(data), "0be8da968f9bc6c1c16b8c635544e757aade7013"); + ASSERT_EQ(data, TW::addressToData(TWCoinTypePivx, address)); + + data = TW::addressToData(TWCoinTypePivx, "1G15VvshDxwFTnahZZECJfFwEkq9fP79"); // invalid address + ASSERT_EQ(data.size(), 0ul); +} \ No newline at end of file diff --git a/tests/chains/Pivx/TWAnyAddressTests.cpp b/tests/chains/Pivx/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..d4e0840d9cd --- /dev/null +++ b/tests/chains/Pivx/TWAnyAddressTests.cpp @@ -0,0 +1,29 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TWPivx, Address) { + auto string = STRING("DSHhD6WxT5RfG2deUzVfuC1jDuRfdDpsSo"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypePivx)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "e7fae8ee6ecabaab1252f3b27679cb34f2406169"); + + string = STRING("D6E4vLacYhKc6RVxLNQg5j8rtWbAH8fybH"); + addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypePivx)); + string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "0be8da968f9bc6c1c16b8c635544e757aade7013"); +} diff --git a/tests/chains/Pivx/TWCoinTypeTests.cpp b/tests/chains/Pivx/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..c73ad2b3390 --- /dev/null +++ b/tests/chains/Pivx/TWCoinTypeTests.cpp @@ -0,0 +1,28 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWPivxCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypePivx)); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypePivx)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypePivx)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypePivx), 8); + ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypePivx)); + ASSERT_EQ(13, TWCoinTypeP2shPrefix(TWCoinTypePivx)); + ASSERT_EQ(30, TWCoinTypeP2pkhPrefix(TWCoinTypePivx)); + assertStringsEqual(symbol, "PIVX"); + assertStringsEqual(id, "pivx"); + assertStringsEqual(name, "Pivx"); +} diff --git a/tests/chains/Pivx/TransactionCompilerTests.cpp b/tests/chains/Pivx/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..efaf999e356 --- /dev/null +++ b/tests/chains/Pivx/TransactionCompilerTests.cpp @@ -0,0 +1,154 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Bitcoin.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include +#include + +#include "Bitcoin/Script.h" +#include "Bitcoin/TransactionInput.h" +#include "Bitcoin/TransactionPlan.h" +#include "Bitcoin/TransactionSigner.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(PivxCompiler, CompileWithSignatures) { + // tx on mainnet + // https://pivx.ccore.online/transaction/e5376c954c748926c373eb08df50ad72b3869be230c659689f9d83c150efd6be + + const auto coin = TWCoinTypePivx; + const int64_t amount = 87090; + const int64_t fee = 2910; + const std::string toAddress = "D6MrY5B9oZaCYNaXCbt2uvmjKC1nPgrgrq"; + + auto toScript = Bitcoin::Script::lockScriptForAddress(toAddress, coin); + ASSERT_EQ(hex(toScript.bytes), "76a9140d61d810a1ae2a9c4638808dd73b64e3ea54caf488ac"); + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("DFsBL73ZaDAJkzv9DeBLEzX8Jh6kkmkfzV"); + input.set_coin_type(coin); + + auto txHash0 = parse_hex("1eda07bd98ea04d322d65facaed830024e264e356810e55111cf6d7c26dff3de"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(1); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo0->set_amount(26910000); + + auto utxoAddr0 = "D6E4vLacYhKc6RVxLNQg5j8rtWbAH8fybH"; + auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, coin); + ASSERT_EQ(hex(script0.bytes), "76a9140be8da968f9bc6c1c16b8c635544e757aade701388ac"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + EXPECT_EQ(input.utxo_size(), 1); + + // Plan + Bitcoin::Proto::TransactionPlan plan; + ANY_PLAN(input, plan, coin); + + plan.set_amount(amount); + plan.set_fee(fee); + plan.set_change(26820000); + + // Extend input with accepted plan + *input.mutable_plan() = plan; + + // Serialize input + const auto txInputData = data(input.SerializeAsString()); + EXPECT_GT((int)txInputData.size(), 0); + + /// Step 2: Obtain preimage hashes + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + TW::Bitcoin::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), (int)preImageHashes.size())); + + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[0].data_hash()), + "7d1af92dd981db6512142aa84c38385664d6751cc858a57e4adee34e6cae1449"); + + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[0].public_key_hash()), "0be8da968f9bc6c1c16b8c635544e757aade7013"); + + auto publicKeyHex = "0273132ffeafdc0eec3aaef5684a320a677cc77ba43df81de944af4a7fc6de66c9"; + auto publicKey = PublicKey(parse_hex(publicKeyHex), TWPublicKeyTypeSECP256k1); + auto preImageHash = preSigningOutput.hash_public_keys()[0].data_hash(); + auto signature = parse_hex("304402202bd34158770290fb304ec85d8a92671e003681e20fb64a346ac5bd8d3686571402207c64b662d85367a949cc275a15aa10713f91815c37cf2979408dc1aa9ddbf4ab"); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE( + publicKey.verifyAsDER(signature, TW::Data(preImageHash.begin(), preImageHash.end()))); + + // Simulate signatures, normally obtained from signature server. + std::vector signatureVec; + std::vector pubkeyVec; + signatureVec.push_back(signature); + pubkeyVec.push_back(publicKey.bytes); + + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, txInputData, signatureVec, pubkeyVec); + + const auto ExpectedTx = + "0100000001def3df267c6dcf1151e51068354e264e0230d8aeac5fd622d304ea98bd07da1e010000006a47304402202bd34158770290fb304ec85d8a92671e003681e20fb64a346ac5bd8d3686571402207c64b662d85367a949cc275a15aa10713f91815c37cf2979408dc1aa9ddbf4ab01210273132ffeafdc0eec3aaef5684a320a677cc77ba43df81de944af4a7fc6de66c9ffffffff0232540100000000001976a9140d61d810a1ae2a9c4638808dd73b64e3ea54caf488aca03d9901000000001976a91475a6ba23a1faaceed874538fe42362b72a1a156588ac00000000"; + { + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: not enough signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {signature, signature}, pubkeyVec); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {}, {}); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + { // Negative: invalid public key + const auto publicKeyBlake = + parse_hex("b689ab808542e13f3d2ec56fe1efe43a1660dcadc73ce489fde7df98dd8ce5d9"); + EXPECT_EXCEPTION( + TransactionCompiler::compileWithSignatures( + coin, txInputData, signatureVec, {publicKeyBlake}), + "Invalid public key"); + } + { // Negative: wrong signature (formally valid) + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, + {parse_hex("415502201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b349022" + "00a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f3b51")}, + pubkeyVec); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_signing); + } +} diff --git a/tests/chains/Polkadot/ScaleCodecTests.cpp b/tests/chains/Polkadot/ScaleCodecTests.cpp index 95d11a60303..d61ba217b63 100644 --- a/tests/chains/Polkadot/ScaleCodecTests.cpp +++ b/tests/chains/Polkadot/ScaleCodecTests.cpp @@ -8,6 +8,7 @@ #include "HexCoding.h" #include "Polkadot/ScaleCodec.h" #include "Kusama/Address.h" +#include "uint256.h" #include @@ -35,7 +36,7 @@ TEST(PolkadotCodec, EncodeCompact) { ASSERT_EQ(hex(encodeCompact(72057594037927935)), "0fffffffffffffff"); ASSERT_EQ(hex(encodeCompact(72057594037927936)), "130000000000000001"); - + ASSERT_EQ(hex(encodeCompact(18446744073709551615u)), "13ffffffffffffffff"); } @@ -69,11 +70,27 @@ TEST(PolkadotCodec, EncodeVectorAccountIds) { ASSERT_EQ(hex(encoded), "08008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000e33fdfb980e4499e5c3576e742a563b6a4fc0f6f598b1917fd7a6fe393ffc72"); } +TEST(PolkadotCodec, EncodeVectorAccountIdsKusama) { + auto addresses = std::vector{ + SS58Address("FoQJpPyadYccjavVdTWxpxU7rUEaYhfLCPwXgkfD6Zat9QP", TWSS58AddressTypeKusama), + SS58Address("CtwdfrhECFs3FpvCGoiE4hwRC4UsSiM8WL899HjRdQbfYZY", TWSS58AddressTypeKusama)}; + auto encoded = encodeAccountIds(addresses, false); + ASSERT_EQ(hex(encoded), "08008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000e33fdfb980e4499e5c3576e742a563b6a4fc0f6f598b1917fd7a6fe393ffc72"); +} + TEST(PolkadotCodec, EncodeEra) { auto era1 = encodeEra(429119, 8); auto era2 = encodeEra(428861, 4); + auto era3 = encodeEra(4246319, 64); ASSERT_EQ(hex(era1), "7200"); ASSERT_EQ(hex(era2), "1100"); + EXPECT_EQ(hex(era3), "f502"); +} + +TEST(PolkadotCodec, CountBytes) { + EXPECT_EQ(size_t(1), countBytes(uint256_t(0))); + EXPECT_EQ(size_t(1), countBytes(uint256_t(1))); + EXPECT_EQ(size_t(2), countBytes(uint256_t("0x1ff"))); } } // namespace TW::Polkadot::tests diff --git a/tests/chains/Polkadot/SignerTests.cpp b/tests/chains/Polkadot/SignerTests.cpp index d93db4a644e..820078907d5 100644 --- a/tests/chains/Polkadot/SignerTests.cpp +++ b/tests/chains/Polkadot/SignerTests.cpp @@ -4,11 +4,11 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "Polkadot/Signer.h" -#include "Polkadot/Extrinsic.h" +#include "HexCoding.h" #include "Polkadot/Address.h" +#include "Polkadot/Extrinsic.h" #include "Polkadot/SS58Address.h" -#include "HexCoding.h" +#include "Polkadot/Signer.h" #include "PrivateKey.h" #include "PublicKey.h" #include "proto/Polkadot.pb.h" @@ -156,7 +156,7 @@ TEST(PolkadotSigner, SignBond_8da66d) { auto stakingCall = input.mutable_staking_call(); auto bond = stakingCall->mutable_bond(); auto value = store(uint256_t(11000000000)); // 1.1 - bond->set_controller(addressThrow2); // myself + bond->set_controller(addressThrow2); // myself bond->set_value(value.data(), value.size()); bond->set_reward_destination(Proto::RewardDestination::STASH); @@ -258,7 +258,7 @@ TEST(PolkadotSigner, SignChill) { input.set_transaction_version(3); auto stakingCall = input.mutable_staking_call(); - auto __attribute__((unused)) &chill = *stakingCall->mutable_chill(); + auto __attribute__((unused))& chill = *stakingCall->mutable_chill(); auto output = Signer::sign(input); ASSERT_EQ(hex(output.encoded()), "9d018488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0088b5e1cd93ba74b82e329f95e1b22660385970182172b2ae280801fdd1ee5652cf7bf319e5e176ccc299dd8eb1e7fccb0ea7717efaf4aacd7640789dd09c1e070000000706"); @@ -337,4 +337,4 @@ TEST(PolkadotSigner, SignChillAndUnbond) { ASSERT_EQ(hex(output.encoded()), "d10184008361bd08ddca5fda28b5e2aa84dc2621de566e23e089e555a42194c3eaf2da7900c891ba102db672e378945d74cf7f399226a76b43cab502436971599255451597fc2599902e4b62c7ce85ecc3f653c693fef3232be620984b5bb5bcecbbd7b209d50318001a02080706070207004d446617"); } -} // namespace Polkadot::tests +} // namespace TW::Polkadot::tests diff --git a/tests/chains/Polkadot/TWCoinTypeTests.cpp b/tests/chains/Polkadot/TWCoinTypeTests.cpp index ecb4d0e8c65..6bda66d2f96 100644 --- a/tests/chains/Polkadot/TWCoinTypeTests.cpp +++ b/tests/chains/Polkadot/TWCoinTypeTests.cpp @@ -14,7 +14,6 @@ TEST(TWPolkadotCoinType, TWCoinType) { auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypePolkadot)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xb96f97d8ee508f420e606e1a6dcc74b88844713ddec2bd7cf4e3aa6b1d6beef4")); auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypePolkadot, txId.get())); auto accId = WRAPS(TWStringCreateWithUTF8Bytes("13hJFqnkqQbmgnGQteGntjMjTdmTBRE8Z93JqxsrpgT7Yjd2")); diff --git a/tests/chains/Polkadot/TransactionCompilerTests.cpp b/tests/chains/Polkadot/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..71ddb332c7b --- /dev/null +++ b/tests/chains/Polkadot/TransactionCompilerTests.cpp @@ -0,0 +1,95 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Polkadot.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +namespace TW::Polkadot::tests { + +TEST(PolkadotCompiler, CompileWithSignatures) { + /// Prepare transaction input (protobuf) + const auto coin = TWCoinTypePolkadot; + auto input = Polkadot::Proto::SigningInput(); + auto block_hash = parse_hex("40cee3c3b7f8422f4c512e9ebebdeeff1c28e81cc678ee4864d945d641e05f9b"); + auto genesis_hash = parse_hex("91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"); + input.set_block_hash(block_hash.data(), block_hash.size()); + input.set_genesis_hash(genesis_hash.data(), genesis_hash.size()); + input.set_nonce(0); + input.set_spec_version(25); + input.set_transaction_version(5); + input.set_network(ss58Prefix(TWCoinTypePolkadot)); + + auto era = input.mutable_era(); + era->set_block_number(5898150); + era->set_period(10000); + + auto call = input.mutable_balance_call(); + auto tx = call->mutable_transfer(); + auto value = parse_hex("210fdc0c00"); + tx->set_to_address("15JWiQUmczAFU3hrZrD2gDyuJdL2BbFaX9yngivb1UWiBJWA"); + tx->set_value(value.data(), value.size()); + + auto txInputData = data(input.SerializeAsString()); + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + auto preOutput = TxCompiler::Proto::PreSigningOutput(); + preOutput.ParseFromArray(preImageHashes.data(), (int)preImageHashes.size()); + auto preImageHash = preOutput.data_hash(); + + { + EXPECT_EQ(hex(preImageHash), "0500be4c21aa92dcba057e9b719ce1de970f774f064c09b13a3ea3009affb8cb5ec707000cdc0f219dfe0000190000000500000091b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c340cee3c3b7f8422f4c512e9ebebdeeff1c28e81cc678ee4864d945d641e05f9b"); + } + + // Simulate signature, normally obtained from signature server + const auto publicKeyData = parse_hex("d84accbb64934815506288fafbfc7d275e64aa4e3cd9c5392db6e83b13256bf3"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeED25519); + const auto signature = parse_hex("fb43727477caaa12542b9060856816d42eedef6ebf2e98e4f8dff4355fe384751925833c4a26b2fed1707aebe655cb3317504a61ee59697c086f7baa6ca06a09"); + + // Verify signature (pubkey & hash & signature) + { + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + } + + const auto expectedTx = "390284d84accbb64934815506288fafbfc7d275e64aa4e3cd9c5392db6e83b13256bf300fb43727477caaa12542b9060856816d42eedef6ebf2e98e4f8dff4355fe384751925833c4a26b2fed1707aebe655cb3317504a61ee59697c086f7baa6ca06a099dfe00000500be4c21aa92dcba057e9b719ce1de970f774f064c09b13a3ea3009affb8cb5ec707000cdc0f21"; + { + /// Compile transaction info + const Data outputData = TransactionCompiler::compileWithSignatures(coin, txInputData, {signature}, {publicKeyData}); + auto output = Polkadot::Proto::SigningOutput(); + output.ParseFromArray(outputData.data(), (int)outputData.size()); + EXPECT_EQ(hex(output.encoded()), expectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {signature, signature}, {publicKey.bytes}); + Polkadot::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {}, {}); + Polkadot::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} + +} // namespace TW::Polkadot::tests diff --git a/tests/chains/Polygon/TWCoinTypeTests.cpp b/tests/chains/Polygon/TWCoinTypeTests.cpp index 282276d2763..992e52df91d 100644 --- a/tests/chains/Polygon/TWCoinTypeTests.cpp +++ b/tests/chains/Polygon/TWCoinTypeTests.cpp @@ -4,6 +4,9 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. // +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// #include "TestUtilities.h" #include diff --git a/tests/chains/Ronin/AddressTests.cpp b/tests/chains/Ronin/AddressTests.cpp new file mode 100644 index 00000000000..bc2c439fb21 --- /dev/null +++ b/tests/chains/Ronin/AddressTests.cpp @@ -0,0 +1,17 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Ronin/Address.h" + +#include + +namespace TW::Ronin::tests { + +TEST(RoninAddress, Create) { + EXPECT_ANY_THROW(new TW::Ronin::Address("")); +} + +} // namespace TW::Ronin::tests diff --git a/tests/chains/Ronin/TWCoinTypeTests.cpp b/tests/chains/Ronin/TWCoinTypeTests.cpp index a1414eaae66..ab90321df83 100644 --- a/tests/chains/Ronin/TWCoinTypeTests.cpp +++ b/tests/chains/Ronin/TWCoinTypeTests.cpp @@ -4,6 +4,9 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. // +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// #include "TestUtilities.h" #include diff --git a/tests/chains/Solana/AddressTests.cpp b/tests/chains/Solana/AddressTests.cpp index e260deb1617..1a3196f5d69 100644 --- a/tests/chains/Solana/AddressTests.cpp +++ b/tests/chains/Solana/AddressTests.cpp @@ -18,16 +18,35 @@ using namespace TW; namespace TW::Solana::tests { TEST(SolanaAddress, FromPublicKey) { - const auto addressString = "2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"; - const auto publicKey = PublicKey(Base58::decode(addressString), TWPublicKeyTypeED25519); - const auto address = Address(publicKey); - ASSERT_EQ(addressString, address.string()); + { + const auto addressString = "2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"; + const auto publicKey = PublicKey(Base58::decode(addressString), TWPublicKeyTypeED25519); + const auto address = Address(publicKey); + ASSERT_EQ(addressString, address.string()); + } + { + const auto privateKey = PrivateKey(parse_hex("a1269039e4ffdf43687852d7247a295f0b5bc55e6dda031cffaa3295ca0a9d7a")); + const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); + EXPECT_ANY_THROW(new Address(publicKey)); + } +} + +TEST(SolanaAddress, FromData) { + { + const auto address = Address(parse_hex("18f9d8d877393bbbe8d697a8a2e52879cc7e84f467656d1cce6bab5a8d2637ec")); + ASSERT_EQ("2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST", address.string()); + } + { + EXPECT_ANY_THROW(new Address(Data{})); + } } TEST(SolanaAddress, FromString) { string addressString = "2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"; const auto address = Address(addressString); ASSERT_EQ(address.string(), addressString); + + EXPECT_ANY_THROW(new Address("4h4bzCLCV8")); } TEST(SolanaAddress, isValid) { diff --git a/tests/chains/Solana/SignerTests.cpp b/tests/chains/Solana/SignerTests.cpp index 5890001cdd8..97a3a304cc2 100644 --- a/tests/chains/Solana/SignerTests.cpp +++ b/tests/chains/Solana/SignerTests.cpp @@ -4,11 +4,11 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "Solana/Signer.h" -#include "Solana/Transaction.h" -#include "Solana/Program.h" #include "HexCoding.h" #include "PublicKey.h" +#include "Solana/Program.h" +#include "Solana/Signer.h" +#include "Solana/Transaction.h" #include @@ -414,4 +414,245 @@ TEST(SolanaSigner, SignTransferToken_3vZ67C) { EXPECT_EQ(transaction.serialize(), expectedString); } +TEST(SolanaSigner, SignCreateNonceAccount) { + const auto privateKey = + PrivateKey(parse_hex("044014463e2ee3cc9c67a6f191dbac82288eb1d5c1111d21245bdc6a855082a1")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + ASSERT_EQ(Data(publicKey.bytes.begin(), publicKey.bytes.end()), + Base58::decode("sp6VUqq1nDEuU83bU2hstmEYrJNipJYpwS7gZ7Jv7ZH")); + const auto nonceAccountPrivateKey = + PrivateKey(parse_hex("2a9737aca3cde2dc0b4f3ae3487e3a90000490cb39fbc979da32b974ff5d7490")); + const auto nonceAccountPublicKey = nonceAccountPrivateKey.getPublicKey(TWPublicKeyTypeED25519); + ASSERT_EQ(Data(nonceAccountPublicKey.bytes.begin(), nonceAccountPublicKey.bytes.end()), + Base58::decode("6vNrYDm6EHcvBALY7HywuDWpTSc6uGt3y2nf5MuG1TmJ")); + + const auto from = Address(publicKey); + const auto nonceAccount = Address(nonceAccountPublicKey); + auto recentBlockhash = Base58::decode("mFmK2xFMhzJJaUN5cctfdCizE9dtgcSASSEDh1Yzmat"); + uint64_t rent = 10000000; + auto message = LegacyMessage::createNonceAccount( + /* owner */ from, + /* new nonce_account */ nonceAccount, + /* rent */ rent, + /* recent_blockhash */ recentBlockhash); + auto transaction = VersionedTransaction(VersionedMessage(message)); + // auto transaction = Transaction(from, to, 42, recentBlockhash); + + std::vector signerKeys; + signerKeys.push_back(privateKey); + signerKeys.push_back(nonceAccountPrivateKey); + Signer::sign(signerKeys, transaction); + + std::vector expectedSignatures; + auto expectedSignature1 = Base58::decode( + "3dbiGHLsFqnwA1PXx7xmoikzv6v9g9BXvZts2126qyE163BypurkvgbDiF5RmrEZRiT2MG88v6xwyJTkhhDRuFc9"); + expectedSignatures.push_back(expectedSignature1); + auto expectedSignature2 = Base58::decode( + "jFq4PbbEM1fuPbq5CkUYgzs7a21g6rvFkfLJAUUGP5QMKYhHBE6nB1dqtwaJsABgyUvrR8QjT2Ej73cXNz7Vur1"); + expectedSignatures.push_back(expectedSignature2); + ASSERT_EQ(transaction.signatures, expectedSignatures); + + auto expectedString = + "3wu6xJSbb2NysVgi7pdfMgwVBT1knAdeCr9NR8EktJLoByzM4s9SMto2PPmrnbRqPtHwnpAKxXkC4vqyWY2dRBgdGG" + "CC1bep6qN5nSLVzpPYAWUSq5cd4gfYMAVriFYRRNHmYUnEq8vMn4vjiECmZoHrpabBj8HpXGqYBo87sbZa8ZPCxUcB" + "71hxXiHWZHj2rovx2kr75Uuv1buWXyW6M8uR4UNvQcPPvzVbwBG82RjDYTuancMSAxmrVNR8GLBQNhrCCYrZyte3EW" + "gEyMQxxfW8T3xNXqnbgdfvFJ3UjRBxXj3hrmv17xEivTjfs81aG2AAi24yiYrk8ep7eQqwDHVSArsrynnwVKVNUcCQ" + "CnSy7fuiuS7FweFX8DEN1K9BrfecHyWrF15fYzhkmWSs64aH6ZTYHWPv5znhFKYmAuopGwbsBEb2j5p8NS3iJZ2skb" + "2wi47n1rpLZfoCHWKxNiikkDUJTGQNcSDrGUMfeW5aGubJrCfecPKEo9Wo9kd36iSsxYPYSWNKrz2HTooa1rCRhqjX" + "D8dyX3bXGV8TK6W2sEgf4JkcDnNoWQLbindcP8XR"; + ASSERT_EQ(transaction.serialize(), expectedString); +} + +TEST(SolanaSigner, SignWithdrawNonceAccount) { + const auto privateKey = + PrivateKey(parse_hex("044014463e2ee3cc9c67a6f191dbac82288eb1d5c1111d21245bdc6a855082a1")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + ASSERT_EQ(Data(publicKey.bytes.begin(), publicKey.bytes.end()), + Base58::decode("sp6VUqq1nDEuU83bU2hstmEYrJNipJYpwS7gZ7Jv7ZH")); + + const auto from = Address(publicKey); + const auto nonceAccount = Address("6vNrYDm6EHcvBALY7HywuDWpTSc6uGt3y2nf5MuG1TmJ"); + auto recentBlockhash = Base58::decode("5ccb7sRth3CP8fghmarFycr6VQX3NcfyDJsMFtmdkdU8"); + auto to = Address("3UVYmECPPMZSCqWKfENfuoTv51fTDTWicX9xmBD2euKe"); + uint64_t value = 10000000; + auto message = + LegacyMessage::createWithdrawNonceAccount(from, nonceAccount, to, value, recentBlockhash); + auto transaction = VersionedTransaction(VersionedMessage(message)); + + std::vector signerKeys; + signerKeys.push_back(privateKey); + Signer::sign(signerKeys, transaction); + + std::vector expectedSignatures; + auto expectedSignature = Base58::decode( + "MxbTCAUmBLiESDLK1NiK5ab41mL2SpAPKSbvGdYQQD5eKgAJRdFEJ8MV9HqBhDQHdsS2LG3QMQQVJp51ekGu6KM"); + expectedSignatures.push_back(expectedSignature); + ASSERT_EQ(transaction.signatures, expectedSignatures); + + auto expectedString = + "7gdEdDymvtfPfVgVvCTPzafmZc1Z8Zu4uXgJDLm8KGpLyPHysxFGjtFzimZDmGtNhQCh22Ygv3ZtPZmSbANbafikR3" + "S1tvujatHW9gMo35jveq7TxwcGoNSqc7tnH85hkEZwnDryVaiKRvtCeH3dgFE9YqPHxiBuZT5eChHJvVNb9iTTdMsJ" + "XMusRtzeRV45CvrLKUvsAH7SSWHYW6bGow5TbEJie4buuz2rnbeVG5cxaZ6vyG2nJWHNuDPWZJTRi1MFEwHoxst3a5" + "jQPv9UrG9rNZFCw4uZizVcG6HEqHWgQBu8gVpYpzFCX5SrhjGPZpbK3YmHhUEMEpJx3Fn7jX7Kt4t3hhhrieXppoqK" + "NuqjeNVjfEf3Q8dJRfuVMLdXYbmitCVTPQzYKWBR6ERqWLYoAVqjoAS2pRUw1nrqi1HR"; + ASSERT_EQ(transaction.serialize(), expectedString); +} + +TEST(SolanaSigner, SignCreateTokenAccountAndTransfer) { + const auto privateKeySigner = + PrivateKey(Base58::decode("9YtuoD4sH4h88CVM8DSnkfoAaLY7YeGC2TarDJ8eyMS5")); + const auto publicKeySigner = privateKeySigner.getPublicKey(TWPublicKeyTypeED25519); + auto signer = Address(publicKeySigner); + EXPECT_EQ(signer.string(), "B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); + + auto token = Address("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"); + auto senderTokenAddress = Address("5sS5Z8GAdVHqZKRqEvpDauHvvLgbDveiyfi81uh25mrf"); + auto recipientMainAddress = Address("3WUX9wASxyScbA7brDipioKfXS1XEYkQ4vo3Kej9bKei"); + auto recipientTokenAddress = Address("BwTAyrCEdkjNyGSGVNSSGh6phn8KQaLN638Evj7PVQfJ"); + uint64_t amount = 4000; + uint8_t decimals = 6; + auto recentBlockhash = Base58::decode("AfzzEC8NVXoxKoHdjXLDVzqwqvvZmgPuqyJqjuHiPY1D"); + + auto message = LegacyMessage::createTokenCreateAndTransfer(signer, recipientMainAddress, token, + recipientTokenAddress, senderTokenAddress, + amount, decimals, recentBlockhash); + auto transaction = VersionedTransaction(VersionedMessage(message)); + + std::vector signerKeys; + signerKeys.push_back(privateKeySigner); + Signer::sign(signerKeys, transaction); + + std::vector expectedSignatures; + auto expectedSignature = Base58::decode( + "pL1m11UEDWn3jMkrNMqLeGwNpKzmhQzJiYaCocgPy7vXKA1tnvEjJbuVq9hTeM9kqMAmxhRpwRY157jDgkRdUZw"); + expectedSignatures.push_back(expectedSignature); + EXPECT_EQ(transaction.signatures, expectedSignatures); + + auto expectedString = + "2qkvFTcMk9kPaHtd7idJ1gJc4zTkuYDUJsC67kXvHjv3zwEyUx92QyhhSeBjL6h3Zaisj2nvTWid2UD1N9hbg9Ty7v" + "SHLc7mcFVvy3yJmN9tz99iLKsf15rEeKUk3crXWLtKZEpcXJurN7vrxKwjQJnVob2RjyxwVfho1oNZ72BHvqToRM1W" + "2KbcYhxK4d9zB4QY5tR2dzgCHWqAjf9Yov3y9mPBYCQBtw2GewrVMDbg5TK81E9BaWer3BcEafc3NCnRfcFEp7ZUXs" + "GAgJYx32uUoJPP8ByTqBsp2JWgHyZhoz1WUUYRqWKZthzotErVetjSik4h5GcXk9Nb6kzqEf4nKEZ22eyrP5dv3eZM" + "uGUUpMYUT9uF16T72s4TTwqiWDPFkidD33tACx74JKGoDraHEvEeAPrv6iUmC675kMuAV4EtVspVc5SnKXgRWRxb4d" + "cH3k7K4ckjSxYZwg8UhTXUgPxA936jBr2HeQuPLmNVn2muA1HfL2DnyrobUP9vHpbL3HHgM2fckeXy8LAcjnoE9TTa" + "AKX32wo5xoMj9wJmmtcU6YbXN4KgZ"; + EXPECT_EQ(transaction.serialize(), expectedString); +} + +TEST(SolanaSigner, SignAdvanceNonceAccount) { + const auto privateKeySigner = + PrivateKey(Base58::decode("9YtuoD4sH4h88CVM8DSnkfoAaLY7YeGC2TarDJ8eyMS5")); + const auto publicKeySigner = privateKeySigner.getPublicKey(TWPublicKeyTypeED25519); + auto signer = Address(publicKeySigner); + EXPECT_EQ(signer.string(), "B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); + + auto nonceAccountAddress = Address("6vNrYDm6EHcvBALY7HywuDWpTSc6uGt3y2nf5MuG1TmJ"); + auto recentBlockhash = Base58::decode("4KQLRUfd7GEVXxAeDqwtuGTdwKd9zMfPycyAG3wJsdck"); + + auto message = LegacyMessage::advanceNonceAccount(signer, nonceAccountAddress, recentBlockhash); + auto transaction = VersionedTransaction(VersionedMessage(message)); + + std::vector signerKeys; + signerKeys.push_back(privateKeySigner); + Signer::sign(signerKeys, transaction); + + std::vector expectedSignatures; + auto expectedSignature = Base58::decode( + "2gwuvwJ3mdEsjA8Gid6FXYuSwa2AAyFY6Btw8ifwSc2SPsfKBnD859C5mX4tLy6zQFHhKxSMMsW49o3dbJNiXDMo"); + expectedSignatures.push_back(expectedSignature); + EXPECT_EQ(transaction.signatures, expectedSignatures); + + auto expectedString = + "7YPgNzjCnUd2zBb6ZC6bf1YaoLjhJPHixLUdTjqMjq1YdzADJCx2wsTTBFFrqDKSHXEL6ntRq8NVJTQMGzGH5AQRKw" + "tKtutehxesmtzkZCPY9ADZ4ijFyveLmTt7kjZXX7ZWVoUmKAqiaYsPTex728uMBSRJpV4zRw2yKGdQRHTKy2QFEb9a" + "cwLjmrbEgoyzPCarxjPhw21QZnNcy8RiYJB2mzZ9nvhrD5d2jB5TtdiroQPgTSdKFzkNEd7hJUKpqUppjDFcNHGK73" + "FE9pCP2dKxCLH8Wfaez8bLtopjmWun9cbikxo7LZsarYzMXvxwZmerRd1"; + EXPECT_EQ(transaction.serialize(), expectedString); +} + +TEST(SolanaSigner, SignTokenTransferWithExternalFeePayer) { + const auto privateKeySigner = + PrivateKey(Base58::decode("9YtuoD4sH4h88CVM8DSnkfoAaLY7YeGC2TarDJ8eyMS5")); + const auto publicKeySigner = privateKeySigner.getPublicKey(TWPublicKeyTypeED25519); + const auto feePayerPrivateKeySigner = PrivateKey(Base58::decode("66ApBuKpo2uSzpjGBraHq7HP8UZMUJzp3um8FdEjkC9c")); + const auto feePayerPublicKeySigner = feePayerPrivateKeySigner.getPublicKey(TWPublicKeyTypeED25519); + + auto signer = Address(publicKeySigner); + EXPECT_EQ(signer.string(), "B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); + auto feePayerSigner = Address(feePayerPublicKeySigner); + EXPECT_EQ(feePayerSigner.string(), "Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ"); + + auto token = Address("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"); + auto senderTokenAddress = Address("5sS5Z8GAdVHqZKRqEvpDauHvvLgbDveiyfi81uh25mrf"); + auto recipientTokenAddress = Address("AZapcpAZtEL1gQuC87F2L1hSfAZnAvNy1hHtJ8DJzySN"); + auto recentBlockhash = Base58::decode("GwB5uixiTQUG2Pvo6fWAaNQmz41Jt4WMEPD7b83wvHkX"); + + auto message = LegacyMessage::createTokenTransfer(signer, token, senderTokenAddress, recipientTokenAddress, 4000, 6, recentBlockhash, "", {}, "", feePayerSigner.string()); + auto transaction = VersionedTransaction(VersionedMessage(message)); + + std::vector signerKeys; + signerKeys.push_back(feePayerPrivateKeySigner); + signerKeys.push_back(privateKeySigner); + Signer::sign(signerKeys, transaction); + + std::vector expectedSignatures; + auto expectedSignature1 = Base58::decode( + "2LbovMDuKoR2LFcV5NbK9bCQZcTG99W6VE1urvdWFWvRhNg9ocDGhayyeBGisoqZgYZtcD3b6LJDTmPx9Gp3T6qd"); + expectedSignatures.push_back(expectedSignature1); + auto expectedSignature2 = Base58::decode( + "2hUHMS9rwbUbXrpC7sK7utL2M4soTyQ7EX3sBYvdee9wraJvYoPH2XjovHDn8eRFY8z5uCx9DCj2Zfjpmzfa81Db"); + expectedSignatures.push_back(expectedSignature2); + EXPECT_EQ(transaction.signatures, expectedSignatures); + + auto expectedString = + // https://explorer.solana.com/tx/2LbovMDuKoR2LFcV5NbK9bCQZcTG99W6VE1urvdWFWvRhNg9ocDGhayyeBGisoqZgYZtcD3b6LJDTmPx9Gp3T6qd?cluster=devnet + "qjgNVBmoPDHNTN2ENQfxNVE57jWXpqdmu5GQX4msA7iK8ZRAnKpvbusQagv8CZGyNYti23p9jBsjTSx75ZU26UW5vgC8D88pusW8W5dp1ERo5DSfurMSYJ6afgQHdcuzn7exb8znSm6uV4y1cWgBRcuAGdg3wRpVhP8HEB1EeKgzjYVWvMSy6yR7qVrSL6BxHG6eiAMyahLFbEt4qBqLEdxxY7Dt4DyydVYmG2ZVtheaMHD3ACwCjpyPLXj399wxSgGXQQFGtzEJQw9awVezmJ4wZk6W4dDpXQvdKYaqUvwTwRZsQB5o2iekPWZXR9xvHiMLjMVBPzYgcU14ZSaCbqSNVv2pAJxP1sMvxZMNMzZPttPxCsDDGq9biC7exXwzesXSnZ3rsgEYeZtkUiBHAxR4rYqBpA6VzLs1bPx8MPTvr9mhNi2ezMBbg2nEfHV6Fz7H7rEY2g3jDtRz35Vmgits8s9RKi3kb73WtGUieRiXjiqkNhpvKkST1oEYRQ9"; + EXPECT_EQ(transaction.serialize(), expectedString); +} + +TEST(SolanaSigner, SignCreateTokenAccountAndTransferWithExternalFeePayer) { + const auto privateKeySigner = + PrivateKey(Base58::decode("9YtuoD4sH4h88CVM8DSnkfoAaLY7YeGC2TarDJ8eyMS5")); + const auto publicKeySigner = privateKeySigner.getPublicKey(TWPublicKeyTypeED25519); + const auto feePayerPrivateKeySigner = PrivateKey(Base58::decode("66ApBuKpo2uSzpjGBraHq7HP8UZMUJzp3um8FdEjkC9c")); + const auto feePayerPublicKeySigner = feePayerPrivateKeySigner.getPublicKey(TWPublicKeyTypeED25519); + + auto signer = Address(publicKeySigner); + EXPECT_EQ(signer.string(), "B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); + auto feePayerSigner = Address(feePayerPublicKeySigner); + EXPECT_EQ(feePayerSigner.string(), "Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ"); + + auto token = Address("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"); + auto senderTokenAddress = Address("5sS5Z8GAdVHqZKRqEvpDauHvvLgbDveiyfi81uh25mrf"); + auto recipientMainAddress = Address("E54BymfdhXQtzDSGtgiJayauEMdB2gJjPqoDjzfbCXwj"); + auto recipientTokenAddress = Address("Ay7G7Yb7ZCebCQdG39FxD1nFNDtqFWJCBgfd5Ek7DDcS"); + uint64_t amount = 4000; + uint8_t decimals = 6; + auto recentBlockhash = Base58::decode("EsnN3ksLV6RLBW7qqgrvm2diwVJNTzL9QCuzm6tqWsU8"); + + auto message = LegacyMessage::createTokenCreateAndTransfer(signer, recipientMainAddress, token, + recipientTokenAddress, senderTokenAddress, + amount, decimals, recentBlockhash, "", {}, "", feePayerSigner.string()); + auto transaction = VersionedTransaction(VersionedMessage(message)); + + std::vector signerKeys; + signerKeys.push_back(feePayerPrivateKeySigner); + signerKeys.push_back(privateKeySigner); + Signer::sign(signerKeys, transaction); + + std::vector expectedSignatures; + auto expectedSignature1 = Base58::decode( + "7GZGFT2VA4dpJBBwjiMqj1o8yChDvoCsqnQ7xz4GxY513W3efxRqbB8y7g4tH2GEGQRx4vCkKipG1sMaDEen1A2"); + expectedSignatures.push_back(expectedSignature1); + auto expectedSignature2 = Base58::decode( + "3n7RHTCBAtnFVuDn5eRbyQB24h6AqajJi5nGMPrfnUVFUDh2Cb8AoaJ7mVtjnv73V4HaJCzSwCLAj3zcGEaFftWZ"); + expectedSignatures.push_back(expectedSignature2); + EXPECT_EQ(transaction.signatures, expectedSignatures); + + // https://explorer.solana.com/tx/7GZGFT2VA4dpJBBwjiMqj1o8yChDvoCsqnQ7xz4GxY513W3efxRqbB8y7g4tH2GEGQRx4vCkKipG1sMaDEen1A2?cluster=devnet + auto expectedString = + "5sxFkQYd2FvqRU64N79A6xjJKNkgUsEEg2wKgai2NiK7A7hF3q5GYEbjQsYBG9S2MejwTENbwHzvypaa3D3cEkxvVTg19aJFWdCtXQiz42QF5fN2MuAb6eJR4KHFnzCtxxnYGtN9swZ5B5cMSPCffCRZeUTe3kooRmbTYPvSaemU6reVSM7X2beoFKPd2svrLFa8XnvhBwL9EiFWQ9WhHB2cDV7KozCnJAW9kdNDR4RbfFQxboANGo3ZGE5ddcZ6YdomATKze1TtHj2qzJEJRwxsRr3iM3iNFb4Eav5Q2n71KUriRf73mo44GQUPbQ2LvpZKf4V6M2PzxJwzBo7FiFZurPmsanT3U5efEsKnnueddbiLHedc8JXc1d3Z53sFxVGJpsGA8RR6thse9wUvaEWqXVtPbNA6NMao9DFGD6Dudza9pJXSobPc7mDHZmVmookf5vi6Lb9Y1Q4EgcEPQmbaDnKGGB6uGfZe629i3iKXRzAd2dB7mKfffhDadZ8S1eYGT3dhddV3ExRxcqDP9BAGQT3rkRw1JpeSSi7ziYMQ3vn4t3okdgQSq6rrpbPDUNG8tLSHFMAq3ydnh4Cb4ECKkYoz9SFAnXACUu4mWETxijuKMK9kHrTqPGk9weHTzobzCC8q8fcPWV3TcyUyMxsbVxh5q1p5h5tWfD9td5TZJ2HEUbTop2dA53ZF"; + EXPECT_EQ(transaction.serialize(), expectedString); +} + } // namespace TW::Solana::tests diff --git a/tests/chains/Solana/TWAnySignerTests.cpp b/tests/chains/Solana/TWAnySignerTests.cpp index f4c8f45c56e..a3a867c29ee 100644 --- a/tests/chains/Solana/TWAnySignerTests.cpp +++ b/tests/chains/Solana/TWAnySignerTests.cpp @@ -6,9 +6,10 @@ #include "Base58.h" #include "HexCoding.h" -#include "proto/Solana.pb.h" +#include "PrivateKey.h" #include "Solana/Address.h" #include "Solana/Program.h" +#include "proto/Solana.pb.h" #include "PrivateKey.h" #include "TestUtilities.h" #include @@ -413,4 +414,186 @@ TEST(TWAnySignerSolana, SignJSON) { assertStringsEqual(result, expectedString1); } +TEST(TWAnySignerSolana, SignCreateNonceAccount) { + const auto privateKeyData = parse_hex("044014463e2ee3cc9c67a6f191dbac82288eb1d5c1111d21245bdc6a855082a1"); + const auto privateKey = PrivateKey(privateKeyData); + EXPECT_EQ(Address(PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519)).string(), "sp6VUqq1nDEuU83bU2hstmEYrJNipJYpwS7gZ7Jv7ZH"); + const auto nonceAccountPrivateKeyData = parse_hex("2a9737aca3cde2dc0b4f3ae3487e3a90000490cb39fbc979da32b974ff5d7490"); + const auto nonceAccountPrivateKey = PrivateKey(nonceAccountPrivateKeyData); + EXPECT_EQ(Address(PrivateKey(nonceAccountPrivateKey).getPublicKey(TWPublicKeyTypeED25519)).string(), "6vNrYDm6EHcvBALY7HywuDWpTSc6uGt3y2nf5MuG1TmJ"); + + auto input = Solana::Proto::SigningInput(); + auto& message = *input.mutable_create_nonce_account(); + auto nonceAccount = std::string("6vNrYDm6EHcvBALY7HywuDWpTSc6uGt3y2nf5MuG1TmJ"); + auto sender = std::string("sp6VUqq1nDEuU83bU2hstmEYrJNipJYpwS7gZ7Jv7ZH"); + uint64_t rent = 10000000; + input.set_private_key(privateKeyData.data(), privateKeyData.size()); + input.set_recent_blockhash(std::string("mFmK2xFMhzJJaUN5cctfdCizE9dtgcSASSEDh1Yzmat")); + message.set_nonce_account_private_key(nonceAccountPrivateKeyData.data(), nonceAccountPrivateKeyData.size()); + message.set_rent(rent); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeSolana); + + auto expectedString = + "3wu6xJSbb2NysVgi7pdfMgwVBT1knAdeCr9NR8EktJLoByzM4s9SMto2PPmrnbRqPtHwnpAKxXkC4vqyWY2dRBgdGG" + "CC1bep6qN5nSLVzpPYAWUSq5cd4gfYMAVriFYRRNHmYUnEq8vMn4vjiECmZoHrpabBj8HpXGqYBo87sbZa8ZPCxUcB" + "71hxXiHWZHj2rovx2kr75Uuv1buWXyW6M8uR4UNvQcPPvzVbwBG82RjDYTuancMSAxmrVNR8GLBQNhrCCYrZyte3EW" + "gEyMQxxfW8T3xNXqnbgdfvFJ3UjRBxXj3hrmv17xEivTjfs81aG2AAi24yiYrk8ep7eQqwDHVSArsrynnwVKVNUcCQ" + "CnSy7fuiuS7FweFX8DEN1K9BrfecHyWrF15fYzhkmWSs64aH6ZTYHWPv5znhFKYmAuopGwbsBEb2j5p8NS3iJZ2skb" + "2wi47n1rpLZfoCHWKxNiikkDUJTGQNcSDrGUMfeW5aGubJrCfecPKEo9Wo9kd36iSsxYPYSWNKrz2HTooa1rCRhqjX" + "D8dyX3bXGV8TK6W2sEgf4JkcDnNoWQLbindcP8XR"; + ASSERT_EQ(output.encoded(), expectedString); +} + +TEST(TWAnySignerSolana, SignWithdrawNonceAccount) { + const auto privateKeyData = parse_hex("044014463e2ee3cc9c67a6f191dbac82288eb1d5c1111d21245bdc6a855082a1"); + const auto privateKey = PrivateKey(privateKeyData); + EXPECT_EQ(Address(PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519)).string(), "sp6VUqq1nDEuU83bU2hstmEYrJNipJYpwS7gZ7Jv7ZH"); + + auto input = TW::Solana::Proto::SigningInput(); + auto& message = *input.mutable_withdraw_nonce_account(); + auto nonceAccount = std::string("6vNrYDm6EHcvBALY7HywuDWpTSc6uGt3y2nf5MuG1TmJ"); + auto recipient = std::string("3UVYmECPPMZSCqWKfENfuoTv51fTDTWicX9xmBD2euKe"); + uint64_t value = 10000000; + input.set_private_key(privateKeyData.data(), privateKeyData.size()); + input.set_recent_blockhash(std::string("5ccb7sRth3CP8fghmarFycr6VQX3NcfyDJsMFtmdkdU8")); + message.set_nonce_account(nonceAccount); + message.set_recipient(recipient); + message.set_value(value); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeSolana); + + auto expectedString = + "7gdEdDymvtfPfVgVvCTPzafmZc1Z8Zu4uXgJDLm8KGpLyPHysxFGjtFzimZDmGtNhQCh22Ygv3ZtPZmSbANbafikR3" + "S1tvujatHW9gMo35jveq7TxwcGoNSqc7tnH85hkEZwnDryVaiKRvtCeH3dgFE9YqPHxiBuZT5eChHJvVNb9iTTdMsJ" + "XMusRtzeRV45CvrLKUvsAH7SSWHYW6bGow5TbEJie4buuz2rnbeVG5cxaZ6vyG2nJWHNuDPWZJTRi1MFEwHoxst3a5" + "jQPv9UrG9rNZFCw4uZizVcG6HEqHWgQBu8gVpYpzFCX5SrhjGPZpbK3YmHhUEMEpJx3Fn7jX7Kt4t3hhhrieXppoqK" + "NuqjeNVjfEf3Q8dJRfuVMLdXYbmitCVTPQzYKWBR6ERqWLYoAVqjoAS2pRUw1nrqi1HR"; + ASSERT_EQ(output.encoded(), expectedString); +} + +TEST(TWAnySignerSolana, SignCreateTokenAccountAndTransfer) { + const auto privateKeyData = Base58::decode("9YtuoD4sH4h88CVM8DSnkfoAaLY7YeGC2TarDJ8eyMS5"); + const auto privateKey = PrivateKey(privateKeyData); + EXPECT_EQ(Address(PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519)).string(), "B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); + + auto input = TW::Solana::Proto::SigningInput(); + auto& message = *input.mutable_create_and_transfer_token_transaction(); + auto signer = std::string("B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); + auto token = std::string("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"); + auto senderTokenAddress = std::string("5sS5Z8GAdVHqZKRqEvpDauHvvLgbDveiyfi81uh25mrf"); + auto recipientMainAddress = std::string("3WUX9wASxyScbA7brDipioKfXS1XEYkQ4vo3Kej9bKei"); + auto recipientTokenAddress = std::string("BwTAyrCEdkjNyGSGVNSSGh6phn8KQaLN638Evj7PVQfJ"); + uint64_t amount = 4000; + uint8_t decimals = 6; + input.set_private_key(privateKeyData.data(), privateKeyData.size()); + input.set_recent_blockhash(std::string("AfzzEC8NVXoxKoHdjXLDVzqwqvvZmgPuqyJqjuHiPY1D")); + message.set_recipient_main_address(recipientMainAddress); + message.set_token_mint_address(token); + message.set_recipient_token_address(recipientTokenAddress); + message.set_sender_token_address(senderTokenAddress); + message.set_amount(amount); + message.set_decimals(decimals); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeSolana); + + auto expectedString = + "2qkvFTcMk9kPaHtd7idJ1gJc4zTkuYDUJsC67kXvHjv3zwEyUx92QyhhSeBjL6h3Zaisj2nvTWid2UD1N9hbg9Ty7v" + "SHLc7mcFVvy3yJmN9tz99iLKsf15rEeKUk3crXWLtKZEpcXJurN7vrxKwjQJnVob2RjyxwVfho1oNZ72BHvqToRM1W" + "2KbcYhxK4d9zB4QY5tR2dzgCHWqAjf9Yov3y9mPBYCQBtw2GewrVMDbg5TK81E9BaWer3BcEafc3NCnRfcFEp7ZUXs" + "GAgJYx32uUoJPP8ByTqBsp2JWgHyZhoz1WUUYRqWKZthzotErVetjSik4h5GcXk9Nb6kzqEf4nKEZ22eyrP5dv3eZM" + "uGUUpMYUT9uF16T72s4TTwqiWDPFkidD33tACx74JKGoDraHEvEeAPrv6iUmC675kMuAV4EtVspVc5SnKXgRWRxb4d" + "cH3k7K4ckjSxYZwg8UhTXUgPxA936jBr2HeQuPLmNVn2muA1HfL2DnyrobUP9vHpbL3HHgM2fckeXy8LAcjnoE9TTa" + "AKX32wo5xoMj9wJmmtcU6YbXN4KgZ"; + ASSERT_EQ(output.encoded(), expectedString); +} + +TEST(TWAnySignerSolana, SignAdvanceNonceAccount) { + const auto privateKeyData = Base58::decode("9YtuoD4sH4h88CVM8DSnkfoAaLY7YeGC2TarDJ8eyMS5"); + const auto privateKey = PrivateKey(privateKeyData); + EXPECT_EQ(Address(PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519)).string(), "B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); + + auto input = TW::Solana::Proto::SigningInput(); + auto& message = *input.mutable_advance_nonce_account(); + auto nonceAccount = std::string("6vNrYDm6EHcvBALY7HywuDWpTSc6uGt3y2nf5MuG1TmJ"); + input.set_private_key(privateKeyData.data(), privateKeyData.size()); + input.set_recent_blockhash(std::string("4KQLRUfd7GEVXxAeDqwtuGTdwKd9zMfPycyAG3wJsdck")); + message.set_nonce_account(nonceAccount); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeSolana); + + auto expectedString = + "7YPgNzjCnUd2zBb6ZC6bf1YaoLjhJPHixLUdTjqMjq1YdzADJCx2wsTTBFFrqDKSHXEL6ntRq8NVJTQMGzGH5AQRKw" + "tKtutehxesmtzkZCPY9ADZ4ijFyveLmTt7kjZXX7ZWVoUmKAqiaYsPTex728uMBSRJpV4zRw2yKGdQRHTKy2QFEb9a" + "cwLjmrbEgoyzPCarxjPhw21QZnNcy8RiYJB2mzZ9nvhrD5d2jB5TtdiroQPgTSdKFzkNEd7hJUKpqUppjDFcNHGK73" + "FE9pCP2dKxCLH8Wfaez8bLtopjmWun9cbikxo7LZsarYzMXvxwZmerRd1"; + ASSERT_EQ(output.encoded(), expectedString); +} + +TEST(TWAnySignerSolana, SignTokenTransferWithExternalFeePayer) { + const auto privateKeyData = Base58::decode("9YtuoD4sH4h88CVM8DSnkfoAaLY7YeGC2TarDJ8eyMS5"); + ASSERT_EQ(Address(PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519)).string(), "B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); + const auto feePayerPrivateKeyData = Base58::decode("66ApBuKpo2uSzpjGBraHq7HP8UZMUJzp3um8FdEjkC9c"); + ASSERT_EQ(Address(PrivateKey(feePayerPrivateKeyData).getPublicKey(TWPublicKeyTypeED25519)).string(), "Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ"); + + auto input = Solana::Proto::SigningInput(); + auto& message = *input.mutable_token_transfer_transaction(); + message.set_token_mint_address("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"); + message.set_sender_token_address("5sS5Z8GAdVHqZKRqEvpDauHvvLgbDveiyfi81uh25mrf"); + message.set_recipient_token_address("AZapcpAZtEL1gQuC87F2L1hSfAZnAvNy1hHtJ8DJzySN"); + message.set_amount(4000); // 0.004 + message.set_decimals(6); + input.set_private_key(privateKeyData.data(), privateKeyData.size()); + input.set_recent_blockhash("H4gZ56AdmHfZj1F36oWrxDJMUJ8ph7XdTHtmsbtHZshG"); + input.set_fee_payer("Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ"); + input.set_fee_payer_private_key(feePayerPrivateKeyData.data(), feePayerPrivateKeyData.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeSolana); + + auto expectedString = + // https://explorer.solana.com/tx/3KbvREZUat76wgWMtnJfWbJL74Vzh4U2eabVJa3Z3bb2fPtW8AREP5pbmRwUrxZCESbTomWpL41PeKDcPGbojsej?cluster=devnet + "ushDP6dNZWq32FASGqdnw7E8x14zFDAEZBViTyevUptV9yb4WSgpYzEGCxt9sXkrEtHVyww4F4GdcGZbPEaQTjAeyX5KGvHHDhWoPeFNnECzjUTuPj35dpF7zJ75Jx3ADfLQtUyzu5w7812fQvhwBwP8XDm3btqSG4VLWeQU5XuVqpg33Mq1L9zkGHQ8PZ4WkgNuSrC584EVnDcFE4rZsUtAv2jFTMjinQJB1qQEGTCHbjgdtJt8PzmXGXeczNyisPsEDrhZUw3g7RFYsgBDB1RFe1TxspzbWmxwr6CNPkGVsopmS6cbvSG9ejXY8xRYaswP7knAoPXwYk26yetoA824mzdv9vJ2RYpyK72EyCqFfFidm8MrJjFR49KwV3HRQKxZzYcvhYuJhR15GKBUWAQvYJTQQWArTi7pr7m84wNsV1mCUjrYsCVK47QtMAYvWU4toTxfgThngfF47awnpcSxfy8ggbwamq7qcaSH6cQVk1LPGo1iB5YxitSbaXP"; + ASSERT_EQ(output.encoded(), expectedString); +} + +TEST(TWAnySignerSolana, SignCreateTokenAccountAndTransferWithExternalFeePayer) { + const auto privateKeyData = Base58::decode("9YtuoD4sH4h88CVM8DSnkfoAaLY7YeGC2TarDJ8eyMS5"); + const auto privateKey = PrivateKey(privateKeyData); + EXPECT_EQ(Address(PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519)).string(), "B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); + const auto feePayerPrivateKeyData = Base58::decode("66ApBuKpo2uSzpjGBraHq7HP8UZMUJzp3um8FdEjkC9c"); + EXPECT_EQ(Address(PrivateKey(feePayerPrivateKeyData).getPublicKey(TWPublicKeyTypeED25519)).string(), "Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ"); + + auto input = TW::Solana::Proto::SigningInput(); + auto& message = *input.mutable_create_and_transfer_token_transaction(); + auto signer = std::string("B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); + auto token = std::string("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"); + auto senderTokenAddress = std::string("5sS5Z8GAdVHqZKRqEvpDauHvvLgbDveiyfi81uh25mrf"); + auto recipientMainAddress = std::string("E54BymfdhXQtzDSGtgiJayauEMdB2gJjPqoDjzfbCXwj"); + auto recipientTokenAddress = std::string("Ay7G7Yb7ZCebCQdG39FxD1nFNDtqFWJCBgfd5Ek7DDcS"); + uint64_t amount = 4000; + uint8_t decimals = 6; + input.set_private_key(privateKeyData.data(), privateKeyData.size()); + input.set_recent_blockhash(std::string("EsnN3ksLV6RLBW7qqgrvm2diwVJNTzL9QCuzm6tqWsU8")); + input.set_fee_payer("Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ"); + input.set_fee_payer_private_key(feePayerPrivateKeyData.data(), feePayerPrivateKeyData.size()); + message.set_recipient_main_address(recipientMainAddress); + message.set_token_mint_address(token); + message.set_recipient_token_address(recipientTokenAddress); + message.set_sender_token_address(senderTokenAddress); + message.set_amount(amount); + message.set_decimals(decimals); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeSolana); + + // https://explorer.solana.com/tx/7GZGFT2VA4dpJBBwjiMqj1o8yChDvoCsqnQ7xz4GxY513W3efxRqbB8y7g4tH2GEGQRx4vCkKipG1sMaDEen1A2?cluster=devnet + auto expectedString = + "5sxFkQYd2FvqRU64N79A6xjJKNkgUsEEg2wKgai2NiK7A7hF3q5GYEbjQsYBG9S2MejwTENbwHzvypaa3D3cEkxvVTg19aJFWdCtXQiz42QF5fN2MuAb6eJR4KHFnzCtxxnYGtN9swZ5B5cMSPCffCRZeUTe3kooRmbTYPvSaemU6reVSM7X2beoFKPd2svrLFa8XnvhBwL9EiFWQ9WhHB2cDV7KozCnJAW9kdNDR4RbfFQxboANGo3ZGE5ddcZ6YdomATKze1TtHj2qzJEJRwxsRr3iM3iNFb4Eav5Q2n71KUriRf73mo44GQUPbQ2LvpZKf4V6M2PzxJwzBo7FiFZurPmsanT3U5efEsKnnueddbiLHedc8JXc1d3Z53sFxVGJpsGA8RR6thse9wUvaEWqXVtPbNA6NMao9DFGD6Dudza9pJXSobPc7mDHZmVmookf5vi6Lb9Y1Q4EgcEPQmbaDnKGGB6uGfZe629i3iKXRzAd2dB7mKfffhDadZ8S1eYGT3dhddV3ExRxcqDP9BAGQT3rkRw1JpeSSi7ziYMQ3vn4t3okdgQSq6rrpbPDUNG8tLSHFMAq3ydnh4Cb4ECKkYoz9SFAnXACUu4mWETxijuKMK9kHrTqPGk9weHTzobzCC8q8fcPWV3TcyUyMxsbVxh5q1p5h5tWfD9td5TZJ2HEUbTop2dA53ZF"; + ASSERT_EQ(output.encoded(), expectedString); +} } // namespace TW::Solana::tests diff --git a/tests/chains/Solana/TWCoinTypeTests.cpp b/tests/chains/Solana/TWCoinTypeTests.cpp index 5072deba1e2..a2e4de41f0f 100644 --- a/tests/chains/Solana/TWCoinTypeTests.cpp +++ b/tests/chains/Solana/TWCoinTypeTests.cpp @@ -12,6 +12,7 @@ #include #include + TEST(TWSolanaCoinType, TWCoinType) { auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeSolana)); auto txId = WRAPS(TWStringCreateWithUTF8Bytes("5LmxrEKGchhMuYfw6Qut6CbsvE9pVfb8YvwZKvWssSesDVjHioBCmWKSJQh1WhvcM6CpemhpHNmEMA2a36rzwTa8")); diff --git a/tests/chains/Solana/TransactionCompilerTests.cpp b/tests/chains/Solana/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..853508d719a --- /dev/null +++ b/tests/chains/Solana/TransactionCompilerTests.cpp @@ -0,0 +1,551 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Base58.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Solana.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +namespace TW::Solana::tests { + +TEST(SolanaCompiler, CompileTransferWithSignatures) { + const auto coin = TWCoinTypeSolana; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::Solana::Proto::SigningInput(); + auto& message = *input.mutable_transfer_transaction(); + auto recipient = std::string("3UVYmECPPMZSCqWKfENfuoTv51fTDTWicX9xmBD2euKe"); + auto sender = std::string("sp6VUqq1nDEuU83bU2hstmEYrJNipJYpwS7gZ7Jv7ZH"); + input.set_sender(sender); + input.set_recent_blockhash(std::string("TPJFTN4CjBn12HiBfAbGUhpD9zGvRSm2RcheFRA4Fyv")); + message.set_recipient(recipient); + message.set_value((uint64_t)1000); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + /// Step 2: Obtain preimage hash + const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::Solana::Proto::PreSigningOutput(); + preSigningOutput.ParseFromArray(preImageHashesData.data(), (int)preImageHashesData.size()); + ASSERT_EQ(preSigningOutput.signers_size(), 1); + auto signer = preSigningOutput.signers(0); + EXPECT_EQ(signer, sender); + auto preImageHash = preSigningOutput.data(); + EXPECT_EQ(hex(preImageHash), + "010001030d044a62d0a4dfe5a037a15b59fa4d4d0d3ab81103a2c10a6da08a4d058611c024c255a8bc3e" + "8496217a2cd2a1894b9b9dcace04fcd9c0d599acdaaea40a1b6100000000000000000000000000000000" + "0000000000000000000000000000000006c25012cc11a599a45b3b2f7f8a7c65b0547fa0bb67170d7a0c" + "d1eda4e2c9e501020200010c02000000e803000000000000"); + // Simulate signature, normally obtained from signature server + const Data publicKeyData = + parse_hex("0d044a62d0a4dfe5a037a15b59fa4d4d0d3ab81103a2c10a6da08a4d058611c0"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeED25519); + const auto signature = + parse_hex("a8c610697087eaf8a34b3facbe06f8e9bb9603bb03270dad021ffcd2fc37b6e9efcdcb78b227401f" + "000eb9231c67685240890962e44a17fd27fc2ff7b971df03"); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKeyData}); + const auto ExpectedTx = + "5bWxVCP5fuzkKSGby9hnsLranszQJR2evJGTfBrpDQ4rJceW1WxKNrWqVPBsN2QCAGmE6W7VaYkyWjv39HhGrr1Ne2" + "QSUuHZdyyn7hK4pxzLPMgPG8fY1XvXdppWMaKMLmhriLkckzGKJMaE3pWBRFBKzigXY28714uUNndb7S9hVakxa59h" + "rLph39CMgAkcj6b8KYvJEkb1YdYytHSZNGi4kVVTNqiicNgPdf1gmG6qz9zVtnqj9JtaD2efdS8qxsKnvNWSgb8Xxb" + "T6dwyp7msUUi7d27cYaPTpK"; + EXPECT_EQ(outputData.size(), 296ul); + { + Solana::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.encoded(), ExpectedTx); + EXPECT_EQ(output.encoded().size(), 293ul); + } + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + Solana::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + auto key = parse_hex("044014463e2ee3cc9c67a6f191dbac82288eb1d5c1111d21245bdc6a855082a1"); + signingInput.set_private_key(key.data(), key.size()); + + Solana::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.encoded(), ExpectedTx); + } +} + +TEST(SolanaCompiler, CompileCreateNonceAccountWithSignatures) { + const auto coin = TWCoinTypeSolana; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::Solana::Proto::SigningInput(); + auto& message = *input.mutable_create_nonce_account(); + auto nonceAccount = std::string("6vNrYDm6EHcvBALY7HywuDWpTSc6uGt3y2nf5MuG1TmJ"); + auto sender = std::string("sp6VUqq1nDEuU83bU2hstmEYrJNipJYpwS7gZ7Jv7ZH"); + uint64_t rent = 10000000; + input.set_sender(sender); + input.set_recent_blockhash(std::string("mFmK2xFMhzJJaUN5cctfdCizE9dtgcSASSEDh1Yzmat")); + message.set_nonce_account(nonceAccount); + message.set_rent(rent); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + /// Step 2: Obtain preimage hash + const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::Solana::Proto::PreSigningOutput(); + preSigningOutput.ParseFromArray(preImageHashesData.data(), (int)preImageHashesData.size()); + ASSERT_EQ(preSigningOutput.signers_size(), 2); + auto signer1 = preSigningOutput.signers(0); + EXPECT_EQ(signer1, sender); + auto signer2 = preSigningOutput.signers(1); + EXPECT_EQ(signer2, nonceAccount); + auto preImageHash = preSigningOutput.data(); + EXPECT_EQ( + hex(preImageHash), + "020003050d044a62d0a4dfe5a037a15b59fa4d4d0d3ab81103a2c10a6da08a4d058611c057f6ed937bb447a670" + "0c9684d2e182b1a6661838a86cca7d0aac18be2e098b2106a7d517192c568ee08a845f73d29788cf035c3145b2" + "1ab344d8062ea940000006a7d517192c5c51218cc94c3d4af17f58daee089ba1fd44e3dbd98a00000000000000" + "00000000000000000000000000000000000000000000000000000000000b563fd13b46e844f12f54fa8a0e78c4" + "4d95dbae4953368b7135f1e0de111cb50204020001340000000080969800000000005000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000040301020324060000000d044a62d0a4" + "dfe5a037a15b59fa4d4d0d3ab81103a2c10a6da08a4d058611c0"); + // Simulate signature, normally obtained from signature server + const Data publicKeyData = Base58::decode(sender); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeED25519); + const Data nonceAccountPublicKeyData = Base58::decode(nonceAccount); + const PublicKey nonceAccountPublicKey = + PublicKey(nonceAccountPublicKeyData, TWPublicKeyTypeED25519); + const auto signature = Base58::decode( + "3dbiGHLsFqnwA1PXx7xmoikzv6v9g9BXvZts2126qyE163BypurkvgbDiF5RmrEZRiT2MG88v6xwyJTkhhDRuFc9"); + const auto nonceAccountSignature = Base58::decode( + "jFq4PbbEM1fuPbq5CkUYgzs7a21g6rvFkfLJAUUGP5QMKYhHBE6nB1dqtwaJsABgyUvrR8QjT2Ej73cXNz7Vur1"); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + EXPECT_TRUE(nonceAccountPublicKey.verify(nonceAccountSignature, TW::data(preImageHash))); + /// Step 3: Compile transaction info + auto outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, nonceAccountSignature}, + {publicKeyData, nonceAccountPublicKeyData}); + const auto ExpectedTx = + "3wu6xJSbb2NysVgi7pdfMgwVBT1knAdeCr9NR8EktJLoByzM4s9SMto2PPmrnbRqPtHwnpAKxXkC4vqyWY2dRBgdGG" + "CC1bep6qN5nSLVzpPYAWUSq5cd4gfYMAVriFYRRNHmYUnEq8vMn4vjiECmZoHrpabBj8HpXGqYBo87sbZa8ZPCxUcB" + "71hxXiHWZHj2rovx2kr75Uuv1buWXyW6M8uR4UNvQcPPvzVbwBG82RjDYTuancMSAxmrVNR8GLBQNhrCCYrZyte3EW" + "gEyMQxxfW8T3xNXqnbgdfvFJ3UjRBxXj3hrmv17xEivTjfs81aG2AAi24yiYrk8ep7eQqwDHVSArsrynnwVKVNUcCQ" + "CnSy7fuiuS7FweFX8DEN1K9BrfecHyWrF15fYzhkmWSs64aH6ZTYHWPv5znhFKYmAuopGwbsBEb2j5p8NS3iJZ2skb" + "2wi47n1rpLZfoCHWKxNiikkDUJTGQNcSDrGUMfeW5aGubJrCfecPKEo9Wo9kd36iSsxYPYSWNKrz2HTooa1rCRhqjX" + "D8dyX3bXGV8TK6W2sEgf4JkcDnNoWQLbindcP8XR"; + + EXPECT_EQ(outputData.size(), 583ul); + + { + Solana::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.encoded(), ExpectedTx); + EXPECT_EQ(output.encoded().size(), 580ul); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + Solana::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + auto key = parse_hex("044014463e2ee3cc9c67a6f191dbac82288eb1d5c1111d21245bdc6a855082a1"); + auto nonceAccountKey = + parse_hex("2a9737aca3cde2dc0b4f3ae3487e3a90000490cb39fbc979da32b974ff5d7490"); + signingInput.set_private_key(key.data(), key.size()); + auto& aMessage = *signingInput.mutable_create_nonce_account(); + aMessage.set_nonce_account_private_key(nonceAccountKey.data(), nonceAccountKey.size()); + aMessage.set_rent(rent); + + Solana::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.encoded(), ExpectedTx); + } +} + +TEST(SolanaCompiler, CompileWithdrawNonceAccountWithSignatures) { + const auto coin = TWCoinTypeSolana; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::Solana::Proto::SigningInput(); + auto& message = *input.mutable_withdraw_nonce_account(); + auto nonceAccount = std::string("6vNrYDm6EHcvBALY7HywuDWpTSc6uGt3y2nf5MuG1TmJ"); + auto sender = std::string("sp6VUqq1nDEuU83bU2hstmEYrJNipJYpwS7gZ7Jv7ZH"); + auto recipient = std::string("3UVYmECPPMZSCqWKfENfuoTv51fTDTWicX9xmBD2euKe"); + uint64_t value = 10000000; + input.set_sender(sender); + input.set_recent_blockhash(std::string("5ccb7sRth3CP8fghmarFycr6VQX3NcfyDJsMFtmdkdU8")); + message.set_nonce_account(nonceAccount); + message.set_recipient(recipient); + message.set_value(value); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + /// Step 2: Obtain preimage hash + const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::Solana::Proto::PreSigningOutput(); + preSigningOutput.ParseFromArray(preImageHashesData.data(), (int)preImageHashesData.size()); + ASSERT_EQ(preSigningOutput.signers_size(), 1); + auto signer = preSigningOutput.signers(0); + EXPECT_EQ(signer, sender); + auto preImageHash = preSigningOutput.data(); + EXPECT_EQ(hex(preImageHash), + "010003060d044a62d0a4dfe5a037a15b59fa4d4d0d3ab81103a2c10a6da08a4d058611c057f6ed937bb4" + "47a6700c9684d2e182b1a6661838a86cca7d0aac18be2e098b2124c255a8bc3e8496217a2cd2a1894b9b" + "9dcace04fcd9c0d599acdaaea40a1b6106a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8" + "062ea940000006a7d517192c5c51218cc94c3d4af17f58daee089ba1fd44e3dbd98a0000000000000000" + "00000000000000000000000000000000000000000000000000000000448e50d73f42e3163f5926922aad" + "d2bca6bdd91f97b3eb7b750e2cecfd810f6d01050501020304000c050000008096980000000000"); + // Simulate signature, normally obtained from signature server + const Data publicKeyData = Base58::decode(sender); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeED25519); + const auto signature = Base58::decode( + "MxbTCAUmBLiESDLK1NiK5ab41mL2SpAPKSbvGdYQQD5eKgAJRdFEJ8MV9HqBhDQHdsS2LG3QMQQVJp51ekGu6KM"); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + /// Step 3: Compile transaction info + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature}, + {publicKeyData}); + const auto ExpectedTx = + "7gdEdDymvtfPfVgVvCTPzafmZc1Z8Zu4uXgJDLm8KGpLyPHysxFGjtFzimZDmGtNhQCh22Ygv3ZtPZmSbANbafikR3" + "S1tvujatHW9gMo35jveq7TxwcGoNSqc7tnH85hkEZwnDryVaiKRvtCeH3dgFE9YqPHxiBuZT5eChHJvVNb9iTTdMsJ" + "XMusRtzeRV45CvrLKUvsAH7SSWHYW6bGow5TbEJie4buuz2rnbeVG5cxaZ6vyG2nJWHNuDPWZJTRi1MFEwHoxst3a5" + "jQPv9UrG9rNZFCw4uZizVcG6HEqHWgQBu8gVpYpzFCX5SrhjGPZpbK3YmHhUEMEpJx3Fn7jX7Kt4t3hhhrieXppoqK" + "NuqjeNVjfEf3Q8dJRfuVMLdXYbmitCVTPQzYKWBR6ERqWLYoAVqjoAS2pRUw1nrqi1HR"; + EXPECT_EQ(outputData.size(), 431ul); + { + Solana::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.encoded(), ExpectedTx); + EXPECT_EQ(output.encoded().size(), 428ul); + } + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + Solana::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + auto key = parse_hex("044014463e2ee3cc9c67a6f191dbac82288eb1d5c1111d21245bdc6a855082a1"); + signingInput.set_private_key(key.data(), key.size()); + + Solana::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.encoded(), ExpectedTx); + } +} + +TEST(SolanaCompiler, CompileCreateTokenAccountAndTransferWithSignatures) { + const auto coin = TWCoinTypeSolana; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::Solana::Proto::SigningInput(); + auto& message = *input.mutable_create_and_transfer_token_transaction(); + auto sender = std::string("B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); + auto token = std::string("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"); + auto senderTokenAddress = std::string("5sS5Z8GAdVHqZKRqEvpDauHvvLgbDveiyfi81uh25mrf"); + auto recipientMainAddress = std::string("3WUX9wASxyScbA7brDipioKfXS1XEYkQ4vo3Kej9bKei"); + auto recipientTokenAddress = std::string("BwTAyrCEdkjNyGSGVNSSGh6phn8KQaLN638Evj7PVQfJ"); + uint64_t amount = 4000; + uint8_t decimals = 6; + input.set_sender(sender); + input.set_recent_blockhash(std::string("AfzzEC8NVXoxKoHdjXLDVzqwqvvZmgPuqyJqjuHiPY1D")); + message.set_recipient_main_address(recipientMainAddress); + message.set_token_mint_address(token); + message.set_recipient_token_address(recipientTokenAddress); + message.set_sender_token_address(senderTokenAddress); + message.set_amount(amount); + message.set_decimals(decimals); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + /// Step 2: Obtain preimage hash + const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::Solana::Proto::PreSigningOutput(); + preSigningOutput.ParseFromArray(preImageHashesData.data(), (int)preImageHashesData.size()); + ASSERT_EQ(preSigningOutput.signers_size(), 1); + auto signer = preSigningOutput.signers(0); + EXPECT_EQ(signer, sender); + auto preImageHash = preSigningOutput.data(); + EXPECT_EQ( + hex(preImageHash), + "0100060994c3890fa8d4bc04ab2a676d2cafea5cdc899ecd95a9cbe593e9df258759685aa287d36838b4ef65c2" + "c460d1a52498453c259cd6a35ca91064aaead28187ca69485a24ffb4070461bb6d7f1c8b758c6b2dc90029d551" + "b5fd4eacd82d65e302202544558bb05c2698f88852a7925c5c0ee5ea8711ddb3fe1262150283eee811633b442c" + "b3912157f13a933d0134282d032b5ffecd01a2dbf1b7790608df002ea700000000000000000000000000000000" + "0000000000000000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857e" + "ff00a906a7d517192c5c51218cc94c3d4af17f58daee089ba1fd44e3dbd98a000000008c97258f4e2489f1bb3d" + "1029148e0d830b5a1399daff1084048e7bd8dbe9f8598fb6d19edbbae20ebbc767fba1da4d4b40a4b97479fe52" + "6a82325cba7cee506802080700010304050607000604020401000a0ca00f00000000000006"); + // Simulate signature, normally obtained from signature server + const Data publicKeyData = Base58::decode(sender); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeED25519); + const auto signature = Base58::decode( + "pL1m11UEDWn3jMkrNMqLeGwNpKzmhQzJiYaCocgPy7vXKA1tnvEjJbuVq9hTeM9kqMAmxhRpwRY157jDgkRdUZw"); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKeyData}); + const auto ExpectedTx = + "2qkvFTcMk9kPaHtd7idJ1gJc4zTkuYDUJsC67kXvHjv3zwEyUx92QyhhSeBjL6h3Zaisj2nvTWid2UD1N9hbg9Ty7v" + "SHLc7mcFVvy3yJmN9tz99iLKsf15rEeKUk3crXWLtKZEpcXJurN7vrxKwjQJnVob2RjyxwVfho1oNZ72BHvqToRM1W" + "2KbcYhxK4d9zB4QY5tR2dzgCHWqAjf9Yov3y9mPBYCQBtw2GewrVMDbg5TK81E9BaWer3BcEafc3NCnRfcFEp7ZUXs" + "GAgJYx32uUoJPP8ByTqBsp2JWgHyZhoz1WUUYRqWKZthzotErVetjSik4h5GcXk9Nb6kzqEf4nKEZ22eyrP5dv3eZM" + "uGUUpMYUT9uF16T72s4TTwqiWDPFkidD33tACx74JKGoDraHEvEeAPrv6iUmC675kMuAV4EtVspVc5SnKXgRWRxb4d" + "cH3k7K4ckjSxYZwg8UhTXUgPxA936jBr2HeQuPLmNVn2muA1HfL2DnyrobUP9vHpbL3HHgM2fckeXy8LAcjnoE9TTa" + "AKX32wo5xoMj9wJmmtcU6YbXN4KgZ"; + EXPECT_EQ(outputData.size(), 572ul); + { + Solana::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.encoded(), ExpectedTx); + EXPECT_EQ(output.encoded().size(), 569ul); + } + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + Solana::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + auto key = Base58::decode("9YtuoD4sH4h88CVM8DSnkfoAaLY7YeGC2TarDJ8eyMS5"); + signingInput.set_private_key(key.data(), key.size()); + + Solana::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.encoded(), ExpectedTx); + } +} + +TEST(SolanaCompiler, SolanaCompileAdvanceNonceAccountWithSignatures) { + const auto coin = TWCoinTypeSolana; + /// Step 1: Prepare transaction input (protobuf) + auto sender = std::string("B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); + auto input = TW::Solana::Proto::SigningInput(); + auto& message = *input.mutable_advance_nonce_account(); + auto nonceAccount = std::string("6vNrYDm6EHcvBALY7HywuDWpTSc6uGt3y2nf5MuG1TmJ"); + input.set_sender(sender); + input.set_recent_blockhash(std::string("4KQLRUfd7GEVXxAeDqwtuGTdwKd9zMfPycyAG3wJsdck")); + message.set_nonce_account(nonceAccount); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + /// Step 2: Obtain preimage hash + const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::Solana::Proto::PreSigningOutput(); + preSigningOutput.ParseFromArray(preImageHashesData.data(), (int)preImageHashesData.size()); + ASSERT_EQ(preSigningOutput.signers_size(), 1); + auto signer = preSigningOutput.signers(0); + EXPECT_EQ(signer, sender); + auto preImageHash = preSigningOutput.data(); + EXPECT_EQ( + hex(preImageHash), + "0100020494c3890fa8d4bc04ab2a676d2cafea5cdc899ecd95a9cbe593e9df258759685a57f6ed937bb447a670" + "0c9684d2e182b1a6661838a86cca7d0aac18be2e098b2106a7d517192c568ee08a845f73d29788cf035c3145b2" + "1ab344d8062ea940000000000000000000000000000000000000000000000000000000000000000000003149e6" + "70959884ea98bb33bca21c9505f1fc17b1d51ca59555a5d58c93f0f9c90103030102000404000000"); + // Simulate signature, normally obtained from signature server + const Data publicKeyData = Base58::decode(sender); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeED25519); + const auto signature = Base58::decode( + "2gwuvwJ3mdEsjA8Gid6FXYuSwa2AAyFY6Btw8ifwSc2SPsfKBnD859C5mX4tLy6zQFHhKxSMMsW49o3dbJNiXDMo"); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKeyData}); + const auto ExpectedTx = + "7YPgNzjCnUd2zBb6ZC6bf1YaoLjhJPHixLUdTjqMjq1YdzADJCx2wsTTBFFrqDKSHXEL6ntRq8NVJTQMGzGH5AQRKw" + "tKtutehxesmtzkZCPY9ADZ4ijFyveLmTt7kjZXX7ZWVoUmKAqiaYsPTex728uMBSRJpV4zRw2yKGdQRHTKy2QFEb9a" + "cwLjmrbEgoyzPCarxjPhw21QZnNcy8RiYJB2mzZ9nvhrD5d2jB5TtdiroQPgTSdKFzkNEd7hJUKpqUppjDFcNHGK73" + "FE9pCP2dKxCLH8Wfaez8bLtopjmWun9cbikxo7LZsarYzMXvxwZmerRd1"; + EXPECT_EQ(outputData.size(), 330ul); + { + + Solana::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.encoded(), ExpectedTx); + EXPECT_EQ(output.encoded().size(), 327ul); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + Solana::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + auto key = Base58::decode("9YtuoD4sH4h88CVM8DSnkfoAaLY7YeGC2TarDJ8eyMS5"); + signingInput.set_private_key(key.data(), key.size()); + + Solana::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.encoded(), ExpectedTx); + } +} + +TEST(SolanaCompiler, CompileCreateTokenAccountAndTransferWithExternalFeePayerWithSignatures) { + const auto coin = TWCoinTypeSolana; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::Solana::Proto::SigningInput(); + auto& message = *input.mutable_create_and_transfer_token_transaction(); + auto sender = std::string("B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); + auto token = std::string("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"); + auto senderTokenAddress = std::string("5sS5Z8GAdVHqZKRqEvpDauHvvLgbDveiyfi81uh25mrf"); + auto recipientMainAddress = std::string("E54BymfdhXQtzDSGtgiJayauEMdB2gJjPqoDjzfbCXwj"); + auto recipientTokenAddress = std::string("Ay7G7Yb7ZCebCQdG39FxD1nFNDtqFWJCBgfd5Ek7DDcS"); + auto feePayer = std::string("Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ"); + uint64_t amount = 4000; + uint8_t decimals = 6; + input.set_sender(sender); + input.set_recent_blockhash(std::string("EsnN3ksLV6RLBW7qqgrvm2diwVJNTzL9QCuzm6tqWsU8")); + input.set_fee_payer(feePayer); + message.set_recipient_main_address(recipientMainAddress); + message.set_token_mint_address(token); + message.set_recipient_token_address(recipientTokenAddress); + message.set_sender_token_address(senderTokenAddress); + message.set_amount(amount); + message.set_decimals(decimals); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + /// Step 2: Obtain preimage hash + const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::Solana::Proto::PreSigningOutput(); + preSigningOutput.ParseFromArray(preImageHashesData.data(), (int)preImageHashesData.size()); + ASSERT_EQ(preSigningOutput.signers_size(), 2); + auto feepayerSigner = preSigningOutput.signers(0); + EXPECT_EQ(feepayerSigner, feePayer); + auto signer = preSigningOutput.signers(1); + EXPECT_EQ(signer, sender); + auto preImageHash = preSigningOutput.data(); + EXPECT_EQ( + hex(preImageHash), + "0200060acb2af089b56a557737bc1718e0cbf232cf5b02e14ee0aa7c6675233f5f6f9b5794c3890fa8d4bc04ab2a676d2cafea5cdc899ecd95a9cbe593e9df258759685a9418c9576a9c00c6bd8fc223f471573f7172488de10aa84dbf63c53a20bae717485a24ffb4070461bb6d7f1c8b758c6b2dc90029d551b5fd4eacd82d65e30220c231dc02f482980f7d9915c1ecf53374091d38c060b49487f9c5d932e077ed763b442cb3912157f13a933d0134282d032b5ffecd01a2dbf1b7790608df002ea7000000000000000000000000000000000000000000000000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a906a7d517192c5c51218cc94c3d4af17f58daee089ba1fd44e3dbd98a000000008c97258f4e2489f1bb3d1029148e0d830b5a1399daff1084048e7bd8dbe9f859ce2a4331bce3670e6ea8bedff5908c6d91f833a31a7fdeac16978c261a1801d502090700020405060708000704030502010a0ca00f00000000000006"); + // Simulate signature, normally obtained from signature server + // Verify signature (pubkey & hash & signature) + const Data feePayerPublicKeyData = Base58::decode(feePayer); + const PublicKey feePayerPublicKey = PublicKey(feePayerPublicKeyData, TWPublicKeyTypeED25519); + const auto signature1 = Base58::decode( + "7GZGFT2VA4dpJBBwjiMqj1o8yChDvoCsqnQ7xz4GxY513W3efxRqbB8y7g4tH2GEGQRx4vCkKipG1sMaDEen1A2"); + EXPECT_TRUE(feePayerPublicKey.verify(signature1, TW::data(preImageHash))); + + const Data publicKeyData = Base58::decode(sender); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeED25519); + const auto signature2 = Base58::decode( + "3n7RHTCBAtnFVuDn5eRbyQB24h6AqajJi5nGMPrfnUVFUDh2Cb8AoaJ7mVtjnv73V4HaJCzSwCLAj3zcGEaFftWZ"); + EXPECT_TRUE(publicKey.verify(signature2, TW::data(preImageHash))); + + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature1, signature2}, {feePayerPublicKeyData, publicKeyData}); + const auto ExpectedTx = + "5sxFkQYd2FvqRU64N79A6xjJKNkgUsEEg2wKgai2NiK7A7hF3q5GYEbjQsYBG9S2MejwTENbwHzvypaa3D3cEkxvVTg19aJFWdCtXQiz42QF5fN2MuAb6eJR4KHFnzCtxxnYGtN9swZ5B5cMSPCffCRZeUTe3kooRmbTYPvSaemU6reVSM7X2beoFKPd2svrLFa8XnvhBwL9EiFWQ9WhHB2cDV7KozCnJAW9kdNDR4RbfFQxboANGo3ZGE5ddcZ6YdomATKze1TtHj2qzJEJRwxsRr3iM3iNFb4Eav5Q2n71KUriRf73mo44GQUPbQ2LvpZKf4V6M2PzxJwzBo7FiFZurPmsanT3U5efEsKnnueddbiLHedc8JXc1d3Z53sFxVGJpsGA8RR6thse9wUvaEWqXVtPbNA6NMao9DFGD6Dudza9pJXSobPc7mDHZmVmookf5vi6Lb9Y1Q4EgcEPQmbaDnKGGB6uGfZe629i3iKXRzAd2dB7mKfffhDadZ8S1eYGT3dhddV3ExRxcqDP9BAGQT3rkRw1JpeSSi7ziYMQ3vn4t3okdgQSq6rrpbPDUNG8tLSHFMAq3ydnh4Cb4ECKkYoz9SFAnXACUu4mWETxijuKMK9kHrTqPGk9weHTzobzCC8q8fcPWV3TcyUyMxsbVxh5q1p5h5tWfD9td5TZJ2HEUbTop2dA53ZF"; + EXPECT_EQ(outputData.size(), 703ul); + { + Solana::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.encoded(), ExpectedTx); + EXPECT_EQ(output.encoded().size(), 700ul); + } + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + Solana::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + auto key = Base58::decode("9YtuoD4sH4h88CVM8DSnkfoAaLY7YeGC2TarDJ8eyMS5"); + signingInput.set_private_key(key.data(), key.size()); + auto feePayerKey = Base58::decode("66ApBuKpo2uSzpjGBraHq7HP8UZMUJzp3um8FdEjkC9c"); + signingInput.set_fee_payer_private_key(feePayerKey.data(), feePayerKey.size()); + + Solana::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.encoded(), ExpectedTx); + } +} + +TEST(SolanaCompiler, CompileTokenTransferWithExternalFeePayerWithSignatures) { + const auto coin = TWCoinTypeSolana; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::Solana::Proto::SigningInput(); + auto& message = *input.mutable_token_transfer_transaction(); + auto sender = std::string("B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); + auto token = std::string("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"); + auto senderTokenAddress = std::string("5sS5Z8GAdVHqZKRqEvpDauHvvLgbDveiyfi81uh25mrf"); + // auto recipientMainAddress = std::string("E54BymfdhXQtzDSGtgiJayauEMdB2gJjPqoDjzfbCXwj"); + auto recipientTokenAddress = std::string("AZapcpAZtEL1gQuC87F2L1hSfAZnAvNy1hHtJ8DJzySN"); + auto feePayer = std::string("Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ"); + uint64_t amount = 4000; + uint8_t decimals = 6; + input.set_sender(sender); + input.set_recent_blockhash(std::string("GwB5uixiTQUG2Pvo6fWAaNQmz41Jt4WMEPD7b83wvHkX")); + input.set_fee_payer(feePayer); + message.set_token_mint_address(token); + message.set_recipient_token_address(recipientTokenAddress); + message.set_sender_token_address(senderTokenAddress); + message.set_amount(amount); + message.set_decimals(decimals); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + /// Step 2: Obtain preimage hash + const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::Solana::Proto::PreSigningOutput(); + preSigningOutput.ParseFromArray(preImageHashesData.data(), (int)preImageHashesData.size()); + ASSERT_EQ(preSigningOutput.signers_size(), 2); + auto feepayerSigner = preSigningOutput.signers(0); + EXPECT_EQ(feepayerSigner, feePayer); + auto signer = preSigningOutput.signers(1); + EXPECT_EQ(signer, sender); + auto preImageHash = preSigningOutput.data(); + EXPECT_EQ( + hex(preImageHash), + "02000206cb2af089b56a557737bc1718e0cbf232cf5b02e14ee0aa7c6675233f5f6f9b5794c3890fa8d4bc04ab2a676d2cafea5cdc899ecd95a9cbe593e9df258759685a485a24ffb4070461bb6d7f1c8b758c6b2dc90029d551b5fd4eacd82d65e302208e12027e9261a6a276b5ff00ddecfda567ff3ae510a5b47045086ad1d50cab573b442cb3912157f13a933d0134282d032b5ffecd01a2dbf1b7790608df002ea706ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9ecc01200a43c87ad04ab8b382c0934a54e585e7dcd9cdef6d1cacd52718981c4010504020403010a0ca00f00000000000006"); + // Simulate signature, normally obtained from signature server + // Verify signature (pubkey & hash & signature) + const Data feePayerPublicKeyData = Base58::decode(feePayer); + const PublicKey feePayerPublicKey = PublicKey(feePayerPublicKeyData, TWPublicKeyTypeED25519); + const auto signature1 = Base58::decode( + "2LbovMDuKoR2LFcV5NbK9bCQZcTG99W6VE1urvdWFWvRhNg9ocDGhayyeBGisoqZgYZtcD3b6LJDTmPx9Gp3T6qd"); + EXPECT_TRUE(feePayerPublicKey.verify(signature1, TW::data(preImageHash))); + + const Data publicKeyData = Base58::decode(sender); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeED25519); + const auto signature2 = Base58::decode( + "2hUHMS9rwbUbXrpC7sK7utL2M4soTyQ7EX3sBYvdee9wraJvYoPH2XjovHDn8eRFY8z5uCx9DCj2Zfjpmzfa81Db"); + EXPECT_TRUE(publicKey.verify(signature2, TW::data(preImageHash))); + + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature1, signature2}, {feePayerPublicKeyData, publicKeyData}); + const auto ExpectedTx = + "qjgNVBmoPDHNTN2ENQfxNVE57jWXpqdmu5GQX4msA7iK8ZRAnKpvbusQagv8CZGyNYti23p9jBsjTSx75ZU26UW5vgC8D88pusW8W5dp1ERo5DSfurMSYJ6afgQHdcuzn7exb8znSm6uV4y1cWgBRcuAGdg3wRpVhP8HEB1EeKgzjYVWvMSy6yR7qVrSL6BxHG6eiAMyahLFbEt4qBqLEdxxY7Dt4DyydVYmG2ZVtheaMHD3ACwCjpyPLXj399wxSgGXQQFGtzEJQw9awVezmJ4wZk6W4dDpXQvdKYaqUvwTwRZsQB5o2iekPWZXR9xvHiMLjMVBPzYgcU14ZSaCbqSNVv2pAJxP1sMvxZMNMzZPttPxCsDDGq9biC7exXwzesXSnZ3rsgEYeZtkUiBHAxR4rYqBpA6VzLs1bPx8MPTvr9mhNi2ezMBbg2nEfHV6Fz7H7rEY2g3jDtRz35Vmgits8s9RKi3kb73WtGUieRiXjiqkNhpvKkST1oEYRQ9"; + EXPECT_EQ(outputData.size(), 514ul); + { + Solana::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.encoded(), ExpectedTx); + EXPECT_EQ(output.encoded().size(), 511ul); + } + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + Solana::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + auto key = Base58::decode("9YtuoD4sH4h88CVM8DSnkfoAaLY7YeGC2TarDJ8eyMS5"); + signingInput.set_private_key(key.data(), key.size()); + auto feePayerKey = Base58::decode("66ApBuKpo2uSzpjGBraHq7HP8UZMUJzp3um8FdEjkC9c"); + signingInput.set_fee_payer_private_key(feePayerKey.data(), feePayerKey.size()); + + Solana::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.encoded(), ExpectedTx); + } +} + +} // namespace TW::Solana::tests diff --git a/tests/chains/Solana/TransactionTests.cpp b/tests/chains/Solana/TransactionTests.cpp index 6f531a1a78a..6a7a53f1e1e 100644 --- a/tests/chains/Solana/TransactionTests.cpp +++ b/tests/chains/Solana/TransactionTests.cpp @@ -5,11 +5,12 @@ // file LICENSE at the root of the source code distribution tree. #include "Solana/Address.h" -#include "Solana/Transaction.h" #include "Solana/Program.h" -#include "HexCoding.h" +#include "Solana/Transaction.h" +#include "Base58.h" #include "BinaryCoding.h" +#include "HexCoding.h" #include @@ -74,7 +75,12 @@ TEST(SolanaTransaction, TransferWithMemoAndReferenceTransaction) { transaction.signatures.clear(); transaction.signatures.push_back(signature); - auto expectedString = "3pzQEdU38uMQgegTyRsRLi23NK4YokgZeSVLXYzFB7HShqZZH8FdBLqj6CeA2d2L8oR9KF2UaJPWbE8YBFmSdaafegoSXJtyj7ciwTjk5ieSXnPXtqH1TEcnMntZATg7gKpeFg6iehqdSUtZuQD1PGmHA1TrzzqLpRSRrc1sqPz8EpSJcQr1Y41B1XCEAfSJDfcuNKrfFrnQaVtRz6tseQfd9uXNYNuR1NQSepWdav5wQiohLUMDiZtxuwb7FQkQ68WE1FDsHmd4JpbWKmDEjz7HFyQY37vf6NBJyX5qWJpFMSg5qGKWvhNCDM32yM4A7HhPeoTWEywE5CXcNmQqdbRt4BzF1A11uqv4etWj"; + auto expectedString = + "3pzQEdU38uMQgegTyRsRLi23NK4YokgZeSVLXYzFB7HShqZZH8FdBLqj6CeA2d2L8oR9KF2UaJPWbE8YBFmSdaafeg" + "oSXJtyj7ciwTjk5ieSXnPXtqH1TEcnMntZATg7gKpeFg6iehqdSUtZuQD1PGmHA1TrzzqLpRSRrc1sqPz8EpSJcQr1" + "Y41B1XCEAfSJDfcuNKrfFrnQaVtRz6tseQfd9uXNYNuR1NQSepWdav5wQiohLUMDiZtxuwb7FQkQ68WE1FDsHmd4Jp" + "bWKmDEjz7HFyQY37vf6NBJyX5qWJpFMSg5qGKWvhNCDM32yM4A7HhPeoTWEywE5CXcNmQqdbRt4BzF1A11uqv4etW" + "j"; EXPECT_EQ(transaction.serialize(), expectedString); } @@ -91,7 +97,17 @@ TEST(SolanaTransaction, StakeSerializeTransactionV2) { transaction.signatures.clear(); transaction.signatures.push_back(signature); - auto expectedString = "W1EAswaWK7mF4r9eZ2hHBZnfPnqLuNPiYkEMzFbwQsgSQu6XbSTL9AN92iyMbAMxPoRpt9ipUyztrmszAnm688N3k7uhiKn2osm9nxi6YkGLfu31jHTSu7mn3RtmenV3qopfPDAM92cXrzUhKGWtQ6cATeQh8i8ZfHpmjyuik7Eg3SQ4sa2543CmcozzjmTTWThThuLdvFCZJzBeRBFWLujqjbs5mA66XVtiDwsEqYByznoo4BN45XUHxnZebmPfo4hi5sf27UkhzPHik371BGxbVDexQp4y5nCEHy8ybfNCvMPLr2SEBiWSifwPkmwYN3hGCkBpqLoHCCiRcyJuRHW8hSDFR4JPQ3Xe3FGfpgbayaawZigUnFuPGSpoGrURZRoLCzc6V4ApqcJbmzFhg5zJz2yTX5GvQSYWLFnTKbPYcgBNpdyMLJTivonrKtgkfdymZVKjDwnHUApC7WD4L9mqzTf1dzR61Fxhu3Rdh8ECiVEDgB1wkWZWkTKEdANmtaYLKCMUs3n4VhuZbSFLEiTg7yRWM2pjBgiBB4qywbF7SE75UtzSFCaDnn27mKkxRBqZEGEgfpEoK2AxjsiCZEZxfLeyZFbwWe7xasmNiXr6CnAQhwsmxJk79h7SYmaje76JLxHVX5gbQmLfn5bc1xthS3YhteSovQ8xYq1jiHCfsXRwbxKrNA4kVMiSa6spoU9AhFL8cDAZjAqoU4YRwBihZVhXSFCRnYAK8FabzEv1M44EeHX1sfMG8T1U7y3DEjom7jv6rqZfLumWpbXDTqanB7zTbTjGyDcBBf21edjpZzBZ7osS5fTVYJ5mZBSvjjhuGkUgZZWgYozAKvdyyrJH6UdcPvNm2XgMRYJxqyCin1zhCeQ25vK1H8Jj"; + auto expectedString = + "W1EAswaWK7mF4r9eZ2hHBZnfPnqLuNPiYkEMzFbwQsgSQu6XbSTL9AN92iyMbAMxPoRpt9ipUyztrmszAnm688N3k7" + "uhiKn2osm9nxi6YkGLfu31jHTSu7mn3RtmenV3qopfPDAM92cXrzUhKGWtQ6cATeQh8i8ZfHpmjyuik7Eg3SQ4sa25" + "43CmcozzjmTTWThThuLdvFCZJzBeRBFWLujqjbs5mA66XVtiDwsEqYByznoo4BN45XUHxnZebmPfo4hi5sf27UkhzP" + "Hik371BGxbVDexQp4y5nCEHy8ybfNCvMPLr2SEBiWSifwPkmwYN3hGCkBpqLoHCCiRcyJuRHW8hSDFR4JPQ3Xe3FGf" + "pgbayaawZigUnFuPGSpoGrURZRoLCzc6V4ApqcJbmzFhg5zJz2yTX5GvQSYWLFnTKbPYcgBNpdyMLJTivonrKtgkfd" + "ymZVKjDwnHUApC7WD4L9mqzTf1dzR61Fxhu3Rdh8ECiVEDgB1wkWZWkTKEdANmtaYLKCMUs3n4VhuZbSFLEiTg7yRW" + "M2pjBgiBB4qywbF7SE75UtzSFCaDnn27mKkxRBqZEGEgfpEoK2AxjsiCZEZxfLeyZFbwWe7xasmNiXr6CnAQhwsmxJ" + "k79h7SYmaje76JLxHVX5gbQmLfn5bc1xthS3YhteSovQ8xYq1jiHCfsXRwbxKrNA4kVMiSa6spoU9AhFL8cDAZjAqo" + "U4YRwBihZVhXSFCRnYAK8FabzEv1M44EeHX1sfMG8T1U7y3DEjom7jv6rqZfLumWpbXDTqanB7zTbTjGyDcBBf21ed" + "jpZzBZ7osS5fTVYJ5mZBSvjjhuGkUgZZWgYozAKvdyyrJH6UdcPvNm2XgMRYJxqyCin1zhCeQ25vK1H8Jj"; EXPECT_EQ(transaction.serialize(), expectedString); } @@ -108,7 +124,17 @@ TEST(SolanaTransaction, StakeSerializeTransactionV1) { transaction.signatures.clear(); transaction.signatures.push_back(signature); - auto expectedString = "W1EAswaWK7mF4r9eZ2hHBZnfPnqLuNPiYkEMzFbwQsgSQu6XbSTL9AN92iyMbAMxPoRpt9ipUyztrmszAnm688N3k7uhiKn2osm9nxi6YkGLfu31jHTSu7mn3RtmenV3qopfPDAM7jtGoYQFb7eFVbujUb6tbeQ9UqLJq1sJ7uMZ4wqecmQPouDmJnpmJk4CHMzLnPNTwyGmGio6sYAS3xKZ7DFXvjwGPuD8PyYHSfdPro1p3jy9igPZNAbQ6fgK7LL3sERKCUdvPy7k14xgHbtsVy2mu54LY5c8F9sFst2uzQiTsXRTdjPFAyCVwB5pccNVotCrJ6Q2aKSC2D2knVH7LgWzSBMSreJG75xyATneu922wSzz7QJDieqhDtdePtSbPtoCdtPNmDfdaeDbHxVAxMios9F7RSRmH2dq86NfWDvF8TuEbYY7gPnygz6jGvwfqSSoSnY8TnUhhceC7wJSMc8Hcf1kyfi8dqKm7rF57YjnrQoMmL5bWqJLKoJtdfFu24ceQN21k38U2tUMWJaBASWukgTJUbNSCemNPZt4P3cNbeB3L1wBj4GEYXVTbTFYKME5JscU5RsnkMJZZ1PgxSi63HT4hwQLok4c18UdJgzMFu1njpZj3Sw76mwV3ea7ruHnP4yyM3YhUGbNjpx5fAcnvdLcXChdsgeUpJhutME6V86Rk2EEskoJeD3qNWi3hvfQx172hZRHyKyr29Ts1uLQxcMJq7oeQUxvTfXxSe6cBuPJUDFkAET3qpS7rWM7rvQQ8rDLQF5QvcJnrYTq12pVgw28WXdgi45811a7DWHGuwHRj5FJdLQAHkKe4EXVeTCdbYHREVwuyTJgAvb8SXjRE5a9n3qpRDr7iEd5UDZKB5HgvMsMYWh5"; + auto expectedString = + "W1EAswaWK7mF4r9eZ2hHBZnfPnqLuNPiYkEMzFbwQsgSQu6XbSTL9AN92iyMbAMxPoRpt9ipUyztrmszAnm688N3k7" + "uhiKn2osm9nxi6YkGLfu31jHTSu7mn3RtmenV3qopfPDAM7jtGoYQFb7eFVbujUb6tbeQ9UqLJq1sJ7uMZ4wqecmQP" + "ouDmJnpmJk4CHMzLnPNTwyGmGio6sYAS3xKZ7DFXvjwGPuD8PyYHSfdPro1p3jy9igPZNAbQ6fgK7LL3sERKCUdvPy" + "7k14xgHbtsVy2mu54LY5c8F9sFst2uzQiTsXRTdjPFAyCVwB5pccNVotCrJ6Q2aKSC2D2knVH7LgWzSBMSreJG75xy" + "ATneu922wSzz7QJDieqhDtdePtSbPtoCdtPNmDfdaeDbHxVAxMios9F7RSRmH2dq86NfWDvF8TuEbYY7gPnygz6jGv" + "wfqSSoSnY8TnUhhceC7wJSMc8Hcf1kyfi8dqKm7rF57YjnrQoMmL5bWqJLKoJtdfFu24ceQN21k38U2tUMWJaBASWu" + "kgTJUbNSCemNPZt4P3cNbeB3L1wBj4GEYXVTbTFYKME5JscU5RsnkMJZZ1PgxSi63HT4hwQLok4c18UdJgzMFu1njp" + "Zj3Sw76mwV3ea7ruHnP4yyM3YhUGbNjpx5fAcnvdLcXChdsgeUpJhutME6V86Rk2EEskoJeD3qNWi3hvfQx172hZRH" + "yKyr29Ts1uLQxcMJq7oeQUxvTfXxSe6cBuPJUDFkAET3qpS7rWM7rvQQ8rDLQF5QvcJnrYTq12pVgw28WXdgi45811" + "a7DWHGuwHRj5FJdLQAHkKe4EXVeTCdbYHREVwuyTJgAvb8SXjRE5a9n3qpRDr7iEd5UDZKB5HgvMsMYWh5"; ASSERT_EQ(transaction.serialize(), expectedString); } @@ -147,7 +173,12 @@ TEST(SolanaTransaction, CreateTokenAccountTransaction) { auto expectedString = // test data obtained from spl-token create-account - "CKzRLx3AQeVeLQ7T4hss2rdbUpuAHdbwXDazxtRnSKBuncCk3WnYgy7XTrEiya19MJviYHYdTxi9gmWJY8qnR2vHVnH2DbPiKA8g72rD3VvMnjosGUBBvCwbBLge6FeQdgczMyRo9n5PcHvg9yJBTJaEEvuewyBVHwCGyGQci7eYd26xtZtCjAjwcTq4gGr3NZbeRW6jZp6j6APuew7jys4MKYRV4xPodua1TZFCkyWZr1XKzmPh7KTavtN5VzPDA8rbsvoEjHnKzjB2Bszs6pDjcBFSHyQqGsHoF8XPD35BLfjDghNtBmf9cFqo5axa6oSjANAuYg6cMSP4Hy28waSj8isr6gQjE315hWi3W1swwwPcn322gYZx6aMAcmjczaxX9aktpHYgZxixF7cYWEHxJs5QUK9mJePu9Xc6yW75UB4Ynx6dUgaSTEUzoQthF2TN3xXwu1"; + "CKzRLx3AQeVeLQ7T4hss2rdbUpuAHdbwXDazxtRnSKBuncCk3WnYgy7XTrEiya19MJviYHYdTxi9gmWJY8qnR2vHVn" + "H2DbPiKA8g72rD3VvMnjosGUBBvCwbBLge6FeQdgczMyRo9n5PcHvg9yJBTJaEEvuewyBVHwCGyGQci7eYd26xtZtC" + "jAjwcTq4gGr3NZbeRW6jZp6j6APuew7jys4MKYRV4xPodua1TZFCkyWZr1XKzmPh7KTavtN5VzPDA8rbsvoEjHnKzj" + "B2Bszs6pDjcBFSHyQqGsHoF8XPD35BLfjDghNtBmf9cFqo5axa6oSjANAuYg6cMSP4Hy28waSj8isr6gQjE315hWi3" + "W1swwwPcn322gYZx6aMAcmjczaxX9aktpHYgZxixF7cYWEHxJs5QUK9mJePu9Xc6yW75UB4Ynx6dUgaSTEUzoQthF2" + "TN3xXwu1"; EXPECT_EQ(transaction.serialize(), expectedString); } @@ -175,7 +206,403 @@ TEST(SolanaTransaction, TransferTokenTransaction_3vZ67C) { auto expectedString = // https://explorer.solana.com/tx/3vZ67CGoRYkuT76TtpP2VrtTPBfnvG2xj6mUTvvux46qbnpThgQDgm27nC3yQVUZrABFjT9Qo7vA74tCjtV5P9Xg // test data obtained from spl-token transfer - "PGfKqEaH2zZXDMZLcU6LUKdBSzU1GJWJ1CJXtRYCxaCH7k8uok38WSadZfrZw3TGejiau7nSpan2GvbK26hQim24jRe2AupmcYJFrgsdaCt1Aqs5kpGjPqzgj9krgxTZwwob3xgC1NdHK5BcNwhxwRtrCphGEH7zUFpGFrFrHzgpf2KY8FvPiPELQyxzTBuyNtjLjMMreehSKShEjD9Xzp1QeC1pEF8JL6vUKzxMXuveoEYem8q8JiWszYzmTMfDk13JPgv7pXFGMqDV3yNGCLsWccBeSFKN4UKECre6x2QbUEiKGkHkMc4zQwwyD8tGmEMBAGm339qdANssEMNpDeJp2LxLDStSoWShHnotcrH7pUa94xCVvCPPaomF"; + "PGfKqEaH2zZXDMZLcU6LUKdBSzU1GJWJ1CJXtRYCxaCH7k8uok38WSadZfrZw3TGejiau7nSpan2GvbK26hQim24jR" + "e2AupmcYJFrgsdaCt1Aqs5kpGjPqzgj9krgxTZwwob3xgC1NdHK5BcNwhxwRtrCphGEH7zUFpGFrFrHzgpf2KY8FvP" + "iPELQyxzTBuyNtjLjMMreehSKShEjD9Xzp1QeC1pEF8JL6vUKzxMXuveoEYem8q8JiWszYzmTMfDk13JPgv7pXFGMq" + "DV3yNGCLsWccBeSFKN4UKECre6x2QbUEiKGkHkMc4zQwwyD8tGmEMBAGm339qdANssEMNpDeJp2LxLDStSoWShHnot" + "crH7pUa94xCVvCPPaomF"; + EXPECT_EQ(transaction.serialize(), expectedString); +} + +TEST(SolanaTransaction, CreateNonceAccountTransaction) { + auto sender = Address("sp6VUqq1nDEuU83bU2hstmEYrJNipJYpwS7gZ7Jv7ZH"); + auto nonceAccount = Address("6vNrYDm6EHcvBALY7HywuDWpTSc6uGt3y2nf5MuG1TmJ"); + uint64_t rent = 10000000; + auto recentBlockhash = Base58::decode("mFmK2xFMhzJJaUN5cctfdCizE9dtgcSASSEDh1Yzmat"); + auto message = LegacyMessage::createNonceAccount(sender, nonceAccount, rent, recentBlockhash); + EXPECT_EQ(message.header.numRequiredSignatures, 2); + EXPECT_EQ(message.header.numReadOnlySignedAccounts, 0); + EXPECT_EQ(message.header.numReadOnlyUnsignedAccounts, 3); + ASSERT_EQ(message.accountKeys.size(), 5ul); + ASSERT_EQ(message.instructions.size(), 2ul); + EXPECT_EQ(message.instructions[0].programId.string(), "11111111111111111111111111111111"); + ASSERT_EQ(message.instructions[0].accounts.size(), 2ul); + EXPECT_EQ(message.instructions[1].programId.string(), "11111111111111111111111111111111"); + ASSERT_EQ(message.instructions[1].accounts.size(), 3ul); + auto transaction = Transaction(message); + transaction.signatures.clear(); + transaction.signatures.push_back(Base58::decode("3dbiGHLsFqnwA1PXx7xmoikzv6v9g9BXvZts2126qyE163Bypur" + "kvgbDiF5RmrEZRiT2MG88v6xwyJTkhhDRuFc9")); + transaction.signatures.push_back(Base58::decode( + "jFq4PbbEM1fuPbq5CkUYgzs7a21g6rvFkfLJAUUGP5QMKYhHBE6nB1dqtwaJsABgyUvrR8QjT2Ej73cXNz7Vur1")); + auto expectedString = + "3wu6xJSbb2NysVgi7pdfMgwVBT1knAdeCr9NR8EktJLoByzM4s9SMto2PPmrnbRqPtHwnpAKxXkC4vqyWY2dRBgdGG" + "CC1bep6qN5nSLVzpPYAWUSq5cd4gfYMAVriFYRRNHmYUnEq8vMn4vjiECmZoHrpabBj8HpXGqYBo87sbZa8ZPCxUcB" + "71hxXiHWZHj2rovx2kr75Uuv1buWXyW6M8uR4UNvQcPPvzVbwBG82RjDYTuancMSAxmrVNR8GLBQNhrCCYrZyte3EW" + "gEyMQxxfW8T3xNXqnbgdfvFJ3UjRBxXj3hrmv17xEivTjfs81aG2AAi24yiYrk8ep7eQqwDHVSArsrynnwVKVNUcCQ" + "CnSy7fuiuS7FweFX8DEN1K9BrfecHyWrF15fYzhkmWSs64aH6ZTYHWPv5znhFKYmAuopGwbsBEb2j5p8NS3iJZ2skb" + "2wi47n1rpLZfoCHWKxNiikkDUJTGQNcSDrGUMfeW5aGubJrCfecPKEo9Wo9kd36iSsxYPYSWNKrz2HTooa1rCRhqjX" + "D8dyX3bXGV8TK6W2sEgf4JkcDnNoWQLbindcP8XR"; + EXPECT_EQ(transaction.serialize(), expectedString); +} + +TEST(SolanaTransaction, CreateWithdrawNonceAccountToSelf) { + auto authorizer = Address("sp6VUqq1nDEuU83bU2hstmEYrJNipJYpwS7gZ7Jv7ZH"); + auto nonceAccount = Address("6vNrYDm6EHcvBALY7HywuDWpTSc6uGt3y2nf5MuG1TmJ"); + uint64_t value = 10000000; + auto recentBlockhash = Base58::decode("5W1cvn4AtWsm2NJfJ52DoXdG36pdS3wHY3Gmkut9sENH"); + auto message = LegacyMessage::createWithdrawNonceAccount(authorizer, nonceAccount, authorizer, value, + recentBlockhash); + EXPECT_EQ(message.header.numRequiredSignatures, 1); + EXPECT_EQ(message.header.numReadOnlySignedAccounts, 0); + EXPECT_EQ(message.header.numReadOnlyUnsignedAccounts, 3); + ASSERT_EQ(message.accountKeys.size(), 5ul); + ASSERT_EQ(message.instructions.size(), 1ul); + EXPECT_EQ(message.instructions[0].programId.string(), "11111111111111111111111111111111"); + ASSERT_EQ(message.instructions[0].accounts.size(), 5ul); + auto transaction = Transaction(message); + transaction.signatures.clear(); + transaction.signatures.push_back(Base58::decode("3JtHs3Ra2LyYCf7a3HFri3ZDa3D4c2G6x3EiyiCDpWjb11QPYfH" + "nU6gqkQaHURdD2aKUzVSBa3JHfzsbiUPi4iUb")); + auto expectedString = + "XVVzGHC7YUTfBwbaQ8y8S7ypc8WCgjpnLd8Yxcp2Vg3SAgtYWsfiFBfrwS6CbwSQa5QBLQ997ohS9wHSX8wyzQdkN6" + "k8ZidSMHdjkHSdFnHGoJBijCv3gURRti2XiaY7aQoKQUPKvwQn31TEKRBgGUQDMtLn9yFqVZ7UMGXtvwvwG7h55VCp" + "Sb32Saus6rSVfNTAfc4UAmwvkbo2gk9rTF5eT9U81uzG2XsBZYSmyqYJQxqaf5ySQD77mwnJo9TpwCX6KfFXnjqyav" + "LFMZb8FeEuXcq17MQKf55w6gqiSssXvQov62GtrUVaVqGg5KYvSTa98Kmu2XHPQcJpgCepft32F6wy2bfDUHjooLqL" + "pp6ZjYqnnWfvVEuSqL6UFjiF"; + EXPECT_EQ(transaction.serialize(), expectedString); +} + +TEST(SolanaTransaction, CreateWithdrawNonceAccount) { + auto authorizer = Address("sp6VUqq1nDEuU83bU2hstmEYrJNipJYpwS7gZ7Jv7ZH"); + auto nonceAccount = Address("6vNrYDm6EHcvBALY7HywuDWpTSc6uGt3y2nf5MuG1TmJ"); + auto to = Address("3UVYmECPPMZSCqWKfENfuoTv51fTDTWicX9xmBD2euKe"); + uint64_t value = 10000000; + auto recentBlockhash = Base58::decode("5ccb7sRth3CP8fghmarFycr6VQX3NcfyDJsMFtmdkdU8"); + auto message = + LegacyMessage::createWithdrawNonceAccount(authorizer, nonceAccount, to, value, recentBlockhash); + EXPECT_EQ(message.header.numRequiredSignatures, 1); + EXPECT_EQ(message.header.numReadOnlySignedAccounts, 0); + EXPECT_EQ(message.header.numReadOnlyUnsignedAccounts, 3); + ASSERT_EQ(message.accountKeys.size(), 6ul); + ASSERT_EQ(message.instructions.size(), 1ul); + EXPECT_EQ(message.instructions[0].programId.string(), "11111111111111111111111111111111"); + ASSERT_EQ(message.instructions[0].accounts.size(), 5ul); + auto transaction = Transaction(message); + transaction.signatures.clear(); + transaction.signatures.push_back(Base58::decode( + "MxbTCAUmBLiESDLK1NiK5ab41mL2SpAPKSbvGdYQQD5eKgAJRdFEJ8MV9HqBhDQHdsS2LG3QMQQVJp51ekGu6KM")); + auto expectedString = + "7gdEdDymvtfPfVgVvCTPzafmZc1Z8Zu4uXgJDLm8KGpLyPHysxFGjtFzimZDmGtNhQCh22Ygv3ZtPZmSbANbafikR3" + "S1tvujatHW9gMo35jveq7TxwcGoNSqc7tnH85hkEZwnDryVaiKRvtCeH3dgFE9YqPHxiBuZT5eChHJvVNb9iTTdMsJ" + "XMusRtzeRV45CvrLKUvsAH7SSWHYW6bGow5TbEJie4buuz2rnbeVG5cxaZ6vyG2nJWHNuDPWZJTRi1MFEwHoxst3a5" + "jQPv9UrG9rNZFCw4uZizVcG6HEqHWgQBu8gVpYpzFCX5SrhjGPZpbK3YmHhUEMEpJx3Fn7jX7Kt4t3hhhrieXppoqK" + "NuqjeNVjfEf3Q8dJRfuVMLdXYbmitCVTPQzYKWBR6ERqWLYoAVqjoAS2pRUw1nrqi1HR"; + EXPECT_EQ(transaction.serialize(), expectedString); +} + +TEST(SolanaTransaction, TransferWithDurableNonce) { + auto from = Address("sp6VUqq1nDEuU83bU2hstmEYrJNipJYpwS7gZ7Jv7ZH"); + auto to = Address("3UVYmECPPMZSCqWKfENfuoTv51fTDTWicX9xmBD2euKe"); + auto nonceAccount = "ALAaqqt4Cc8hWH22GT2L16xKNAn6gv7XCTF7JkbfWsc"; + uint64_t value = 1000; + auto recentBlockhash = Base58::decode("5ycoKxPRpW2GdD4byZuMptHU3VU5MgUCh6NLGQ2U8VE5"); + auto message = LegacyMessage::createTransfer(from, to, value, recentBlockhash, "", {}, nonceAccount); + EXPECT_EQ(message.header.numRequiredSignatures, 1); + EXPECT_EQ(message.header.numReadOnlySignedAccounts, 0); + EXPECT_EQ(message.header.numReadOnlyUnsignedAccounts, 2); + ASSERT_EQ(message.accountKeys.size(), 5ul); + ASSERT_EQ(message.instructions.size(), 2ul); + EXPECT_EQ(message.instructions[0].programId.string(), "11111111111111111111111111111111"); + ASSERT_EQ(message.instructions[0].accounts.size(), 3ul); + EXPECT_EQ(message.instructions[1].programId.string(), "11111111111111111111111111111111"); + ASSERT_EQ(message.instructions[1].accounts.size(), 2ul); + auto transaction = Transaction(message); + transaction.signatures.clear(); + transaction.signatures.push_back(Base58::decode("4c4LhPFsDQdwZnVqKtgDC1GVR63GU5REFvonAW3RF7Quo4b1YbU" + "rsXMmwYCfD3LGrUawThPaAixCUKnoGdMMQ7BQ")); + + auto expectedString = + "6zRqmNP5waeyartbf8GuQrWxdSy4SCYBTEmGhiXfYNxQTuUrvrBjia18YoCM367AQZWZ5yTjcN6FaXuaPWju7aVZNF" + "jyqpuMZLNEbpm8ZNmKP4Na2VzR59iAdSPEZGTPuesZEniNMAD7ZSux6fayxgwrEwMWjeiskFQEwdvFzKNHfNLbjoVp" + "dSTxhKiqfbwxnFBpBxNE4nqMj3bUR37cYJAFoDFokxy23HGpV93V9mbGG89aLBNQnd9LKTjpYFv49VMd48mptUd7uy" + "rRwZLMneew2Bxq3PLsj9SaJyCWbsnqYj6bBahhsErz67PJTJepx4BEhqRxHGUSbpeNiL7qyERri1GZsXhN8fgU3nPi" + "Yr7tMMxuLAoUFRMJ79HCex7vxhf7SapvcP"; + ASSERT_EQ(transaction.serialize(), expectedString); +} + +TEST(SolanaTransaction, CreateNonceAccountWithDurableNonce) { + auto from = Address("sp6VUqq1nDEuU83bU2hstmEYrJNipJYpwS7gZ7Jv7ZH"); + auto newNonceAccount = Address("6vNrYDm6EHcvBALY7HywuDWpTSc6uGt3y2nf5MuG1TmJ"); + auto nonceAccount = "ALAaqqt4Cc8hWH22GT2L16xKNAn6gv7XCTF7JkbfWsc"; + uint64_t rent = 10000000; + auto recentBlockhash = Base58::decode("E6hvnoXU9QmfWaibMk9NuT6QRZdfzbs96WGc2hhttqXQ"); + auto message = + LegacyMessage::createNonceAccount(from, newNonceAccount, rent, recentBlockhash, nonceAccount); + EXPECT_EQ(message.header.numRequiredSignatures, 2); + EXPECT_EQ(message.header.numReadOnlySignedAccounts, 0); + EXPECT_EQ(message.header.numReadOnlyUnsignedAccounts, 3); + ASSERT_EQ(message.accountKeys.size(), 6ul); + ASSERT_EQ(message.instructions.size(), 3ul); + EXPECT_EQ(message.instructions[0].programId.string(), "11111111111111111111111111111111"); + ASSERT_EQ(message.instructions[0].accounts.size(), 3ul); + EXPECT_EQ(message.instructions[1].programId.string(), "11111111111111111111111111111111"); + ASSERT_EQ(message.instructions[1].accounts.size(), 2ul); + EXPECT_EQ(message.instructions[2].programId.string(), "11111111111111111111111111111111"); + ASSERT_EQ(message.instructions[2].accounts.size(), 3ul); + auto transaction = Transaction(message); + transaction.signatures.clear(); + transaction.signatures.push_back(Base58::decode("5wTTTpoXKTzyhL3XbToCt1y12Cf5pcD8xH5NPmGW2y8Dcshgvs2" + "rY2QQ2b23DvXbheBBr3UTixpT4vYyStgDFPdq")); + transaction.signatures.push_back(Base58::decode("31nF9z8GG8ZkgUPSHmEqikMKhweo4LwVJpJACoLCEah8mJk469m" + "tFA4DxEeiyJBnZSumgbR1aFS7p4kqX1F42brK")); + + auto expectedString = + "Fr8FzXoH7h6Xo2La6SE49BEPzRX6f93Qn1cFA5E8n6z2GJtZdTU2BfyYGr1zv21Zkq7h68Z3Q96VnFyUVVd1hTWeq6" + "tHDamF1JK5L23yEeUXpEWv1KziWvG9XbxfseHUyWETQck7SY2HbsT4KSjRX9suDaBh68Bu8c96CVN7KtgYPhUrKP62" + "dAMHsf5qo7MESFN8wKJto94ANNCbQMzPmhig9nfiAfvfz9CqV4nbnSiqBGwo2XoyPknDK8RJ1UmA5ptfZ6w6Fy4UmJ" + "bQZWuZwpUrkEkfgLMNJ36McHkGAnjpyzq9gMtzb33xSjx1BqnbWXkKJdi8HyQAHTtvtqPz7DMsW9qx5fu3TNz6iC8Y" + "HG2HiynFCRjTtc2aH1rpJ9TLdFQEK8WrhdMFr3yW27cg6NB3JUFopUkDg2k5FwtzFyCdfifwebD7eswVNnqjoZxW59" + "fHgY3BrBH8uNst8YAQWvRH77y5L6imVmFhezU5JUb5sF58gR1D8eAQhUcHueakZb5FkFCaMeioTpKrVGgcSNe9zkBM" + "uquoUR3t4MVTiUSLa815qKoBCRmdexQDBt5RQbdQhYyVWn3ovjdhkwDGBU2zywRvottGCcEStQrUrSQDg1tMVKxX5G" + "3sBtxYf"; + EXPECT_EQ(transaction.serialize(), expectedString); +} + +TEST(SolanaTransaction, WithdrawNonceAccountToSelfWithDurableNonce) { + auto authorizer = Address("sp6VUqq1nDEuU83bU2hstmEYrJNipJYpwS7gZ7Jv7ZH"); + auto withdrawNonceAccount = Address("6vNrYDm6EHcvBALY7HywuDWpTSc6uGt3y2nf5MuG1TmJ"); + auto nonceAccount = "ALAaqqt4Cc8hWH22GT2L16xKNAn6gv7XCTF7JkbfWsc"; + uint64_t value = 10000000; + auto recentBlockhash = Base58::decode("5EtRPR4sTWRSwNUE5a5SnKB46ZqTJH8vgF1qZFTKGHvw"); + auto message = LegacyMessage::createWithdrawNonceAccount(authorizer, withdrawNonceAccount, authorizer, + value, recentBlockhash, nonceAccount); + EXPECT_EQ(message.header.numRequiredSignatures, 1); + EXPECT_EQ(message.header.numReadOnlySignedAccounts, 0); + EXPECT_EQ(message.header.numReadOnlyUnsignedAccounts, 3); + ASSERT_EQ(message.accountKeys.size(), 6ul); + ASSERT_EQ(message.instructions.size(), 2ul); + EXPECT_EQ(message.instructions[0].programId.string(), "11111111111111111111111111111111"); + ASSERT_EQ(message.instructions[0].accounts.size(), 3ul); + EXPECT_EQ(message.instructions[1].programId.string(), "11111111111111111111111111111111"); + ASSERT_EQ(message.instructions[1].accounts.size(), 5ul); + auto transaction = Transaction(message); + transaction.signatures.clear(); + transaction.signatures.push_back(Base58::decode("5LsWV8w5CZT4uUDY63rxwcBgGczoVeNtYTPrchP7KZwPpJD7qB2" + "jFEmWVUVtnJGUWn36TGM1bGpnoYGgGWNYZGhA")); + auto expectedString = + "3rxbwm6dSX4SbFa7yitVQnoUGWkmRuQtg3V13a2jEAPfZZACCXZX2UFgWFpPqE7KfZSYhd5QE9TLzyikCwcmSBHhKX" + "jMp4oktQXwRT66YaCK8rJdNzBUuS1D9tgHMkLUWKAR7ZRyWd3XvtQhe7nWD6YF6TRGoKPSuwsZAArBxogA7YddmEUK" + "Psr2qjSKbjg7X5BbNceFwjEFAiafuizdSt7eGJHB5m9zJeYct8LCanTwJwyEVu1T9HTsgjW9hqHehqhCiHP46KGo63" + "o7WAoappZvM4EJZemu4GfM6F6H48bPXF2z1QJz17wE6BYeMXfXuGkCRt5jYxrjdKuqvTDYV34X1HjZYUdrkW6mQotW" + "DY3bS6zyAt784Vwzk2uiA8ytmWMbC24coUVwPSPGwZ92WJ6BpVCCtGDxLzp4CkahRu78UNWzdcEwPG6AUf"; + EXPECT_EQ(transaction.serialize(), expectedString); +} + +TEST(SolanaTransaction, WithdrawNonceAccountWithDurableNonce) { + auto authorizer = Address("sp6VUqq1nDEuU83bU2hstmEYrJNipJYpwS7gZ7Jv7ZH"); + auto withdrawNonceAccount = Address("6vNrYDm6EHcvBALY7HywuDWpTSc6uGt3y2nf5MuG1TmJ"); + auto nonceAccount = "ALAaqqt4Cc8hWH22GT2L16xKNAn6gv7XCTF7JkbfWsc"; + auto to = Address("3UVYmECPPMZSCqWKfENfuoTv51fTDTWicX9xmBD2euKe"); + uint64_t value = 10000000; + auto recentBlockhash = Base58::decode("55F6jiKRh9Wyq7EZuur7nqTYwewNF6LcucuCHoK1N4HV"); + auto message = LegacyMessage::createWithdrawNonceAccount(authorizer, withdrawNonceAccount, to, value, + recentBlockhash, nonceAccount); + EXPECT_EQ(message.header.numRequiredSignatures, 1); + EXPECT_EQ(message.header.numReadOnlySignedAccounts, 0); + EXPECT_EQ(message.header.numReadOnlyUnsignedAccounts, 3); + ASSERT_EQ(message.accountKeys.size(), 7ul); + ASSERT_EQ(message.instructions.size(), 2ul); + EXPECT_EQ(message.instructions[0].programId.string(), "11111111111111111111111111111111"); + ASSERT_EQ(message.instructions[0].accounts.size(), 3ul); + EXPECT_EQ(message.instructions[1].programId.string(), "11111111111111111111111111111111"); + ASSERT_EQ(message.instructions[1].accounts.size(), 5ul); + auto transaction = Transaction(message); + transaction.signatures.clear(); + transaction.signatures.push_back(Base58::decode("2vzszn8rJbYAusA8F3aUo2x1pNeM8Us2uZm6ibeXiHA4XmZA557" + "yafbA3YPJ991Usd8Jbw1ov4rNYNw1VRrVjDnz")); + auto expectedString = + "djXotMMJaCX6HXkAshsTVDJfiG8JgryAVzWtPcamqaZGnnZztXbRrf4SvTtyu15ohTDTVMYak6BvwaoQcsGepzNs6k" + "QE68cgc1LngdtDJLaB5JceX2PmVC1WBK9nLYukdoCgmDrBFiJmkjSsouoFsyXqF7Cgswpq5PqdFqTboTvon6bbQt28" + "ST5cZ87G832kFMmrLtFXzrQC5MaqKxFYFuiy6rKDKWHh9Xyc1aqTmiWiwPkuoCGsxYmWyAjGmG8mSyjRwuLRLfAmjB" + "zAgSxFh36qKk5bZiwNwcktg4Ndsg6fdn39XdusfJYKvhWBjatmofk5PJ6eWjFaZbscmpdxcq15yNGRxKaD55e89JSa" + "2n3pe4wwaJ9SyMaY1trvu1YXx6NxpfhPDEBniMnSE32Ko3tpoQhmxLoMVXbz8pngCYNZUwTbPYmT9K9DXysJYsfnk9" + "FRoxVKJugskV91cyjKX1TeW4iosedQbiXdy"; + EXPECT_EQ(transaction.serialize(), expectedString); +} + +TEST(SolanaTransaction, CreateTokenAccountAndTransfer) { + auto signer = Address("B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); + auto token = Address("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"); + auto senderTokenAddress = Address("5sS5Z8GAdVHqZKRqEvpDauHvvLgbDveiyfi81uh25mrf"); + auto recipientMainAddress = Address("3WUX9wASxyScbA7brDipioKfXS1XEYkQ4vo3Kej9bKei"); + auto recipientTokenAddress = Address("BwTAyrCEdkjNyGSGVNSSGh6phn8KQaLN638Evj7PVQfJ"); + uint64_t amount = 4000; + uint8_t decimals = 6; + auto recentBlockhash = Base58::decode("AfzzEC8NVXoxKoHdjXLDVzqwqvvZmgPuqyJqjuHiPY1D"); + auto message = LegacyMessage::createTokenCreateAndTransfer(signer, recipientMainAddress, token, + recipientTokenAddress, senderTokenAddress, + amount, decimals, recentBlockhash); + EXPECT_EQ(message.header.numRequiredSignatures, 1); + EXPECT_EQ(message.header.numReadOnlySignedAccounts, 0); + EXPECT_EQ(message.header.numReadOnlyUnsignedAccounts, 6); + ASSERT_EQ(message.accountKeys.size(), 9ul); + ASSERT_EQ(message.instructions.size(), 2ul); + EXPECT_EQ(message.instructions[0].programId.string(), + "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); + ASSERT_EQ(message.instructions[0].accounts.size(), 7ul); + EXPECT_EQ(message.instructions[1].programId.string(), + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); + ASSERT_EQ(message.instructions[1].accounts.size(), 4ul); + auto transaction = Transaction(message); + transaction.signatures.clear(); + transaction.signatures.push_back(Base58::decode( + "pL1m11UEDWn3jMkrNMqLeGwNpKzmhQzJiYaCocgPy7vXKA1tnvEjJbuVq9hTeM9kqMAmxhRpwRY157jDgkRdUZw")); + auto expectedString = + "2qkvFTcMk9kPaHtd7idJ1gJc4zTkuYDUJsC67kXvHjv3zwEyUx92QyhhSeBjL6h3Zaisj2nvTWid2UD1N9hbg9Ty7v" + "SHLc7mcFVvy3yJmN9tz99iLKsf15rEeKUk3crXWLtKZEpcXJurN7vrxKwjQJnVob2RjyxwVfho1oNZ72BHvqToRM1W" + "2KbcYhxK4d9zB4QY5tR2dzgCHWqAjf9Yov3y9mPBYCQBtw2GewrVMDbg5TK81E9BaWer3BcEafc3NCnRfcFEp7ZUXs" + "GAgJYx32uUoJPP8ByTqBsp2JWgHyZhoz1WUUYRqWKZthzotErVetjSik4h5GcXk9Nb6kzqEf4nKEZ22eyrP5dv3eZM" + "uGUUpMYUT9uF16T72s4TTwqiWDPFkidD33tACx74JKGoDraHEvEeAPrv6iUmC675kMuAV4EtVspVc5SnKXgRWRxb4d" + "cH3k7K4ckjSxYZwg8UhTXUgPxA936jBr2HeQuPLmNVn2muA1HfL2DnyrobUP9vHpbL3HHgM2fckeXy8LAcjnoE9TTa" + "AKX32wo5xoMj9wJmmtcU6YbXN4KgZ"; + EXPECT_EQ(transaction.serialize(), expectedString); +} + +TEST(SolanaTransaction, CreateTokenAccountAndTransferWithDurableNonce) { + auto signer = Address("B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); + auto token = Address("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"); + auto senderTokenAddress = Address("5sS5Z8GAdVHqZKRqEvpDauHvvLgbDveiyfi81uh25mrf"); + auto recipientMainAddress = Address("3UVYmECPPMZSCqWKfENfuoTv51fTDTWicX9xmBD2euKe"); + auto recipientTokenAddress = Address("93hbN3brRjZqRQTT9Xx6rAHVDFZFWD9ragFDXvDbTEjr"); + auto nonceAccount = "6vNrYDm6EHcvBALY7HywuDWpTSc6uGt3y2nf5MuG1TmJ"; + uint64_t amount = 4000; + uint8_t decimals = 6; + auto recentBlockhash = Base58::decode("AaYfEmGQpfJWypZ8MNmBHTep1dwCHVYDRHuZ3gVFiJpY"); + auto message = LegacyMessage::createTokenCreateAndTransfer( + signer, recipientMainAddress, token, recipientTokenAddress, senderTokenAddress, amount, + decimals, recentBlockhash, "", {}, nonceAccount); + EXPECT_EQ(message.header.numRequiredSignatures, 1); + EXPECT_EQ(message.header.numReadOnlySignedAccounts, 0); + EXPECT_EQ(message.header.numReadOnlyUnsignedAccounts, 7); + ASSERT_EQ(message.accountKeys.size(), 11ul); + ASSERT_EQ(message.instructions.size(), 3ul); + EXPECT_EQ(message.instructions[0].programId.string(), "11111111111111111111111111111111"); + ASSERT_EQ(message.instructions[0].accounts.size(), 3ul); + EXPECT_EQ(message.instructions[1].programId.string(), + "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); + ASSERT_EQ(message.instructions[1].accounts.size(), 7ul); + EXPECT_EQ(message.instructions[2].programId.string(), + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); + ASSERT_EQ(message.instructions[2].accounts.size(), 4ul); + auto transaction = Transaction(message); + transaction.signatures.clear(); + transaction.signatures.push_back(Base58::decode( + "Gw1REivWi3j3cm17evPbxcdoJ1YY1jC7xcQERcmAWCtLx8WKsFoDDWTun4WYfUqSzEfuKjxiYKPvv2eAJWs7N1b")); + auto expectedString = + "388uZws6GfA9aiH1LPsYBijGBEfLEgqe6q5NWVYhsmjXjrgZB4cScGuvja6nBL3i6qg6HA4a8ptW6aHsNKVdcBWKhj" + "ZjaTPH5heEThzwEsMDfnH2PWAUbqfiFgMZQRCkhyCj57hGUR7hBFPELfz3DBw5qMz1tnP9gU6KTqHUomu5UaadLHb2" + "v5mbgTRcsMm3yDp2tzMwrp53VqvFNmHSau4ot4kdNL1jqEJC68Fj4ku6fMQaFSPyAeLQRF45ofYsFCa65fmtb4gBpq" + "WUdqWLv5Dy6xQUQUDsin8qpEVds6unXw5f63UjZeD7XQdC6Vz5aq3e6P9ug8L41xc1rbuRU3Kp4arUKyqTsHMQ2dxM" + "hPwEJLkHd4mFqqUWpYFTdfLFaNGU22hEkvP1esHUzaaGDmzAozbS96oaFw2jbHRRJtL8VjoA1aokGFFThM6M6mExuy" + "8GhUXdGjxDFU83Dan1URmHMGBRC4J9RMZip9s1sktJw9Rj5Std9KVT8T7m4MxTVTx4QoBw6KAf6PgNHyHPtZSc7kzo" + "CxDYNo2Myxvy8D95zk9YMp1MxeZXTDQ2aJuhWvfHhhrwgcQasAxRzbnJ9oehebVUNEcZEFsfnCgYuUmxWUemoKZnE1" + "bNMCvERVkT5fKQ36e1rt5vTC2iES9jzr3hDC1Pk1"; + EXPECT_EQ(transaction.serialize(), expectedString); +} + +TEST(SolanaTransaction, AdvanceNonceAccount) { + auto authorizer = Address("B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); + auto nonceAccountAddress = Address("6vNrYDm6EHcvBALY7HywuDWpTSc6uGt3y2nf5MuG1TmJ"); + auto recentBlockhash = Base58::decode("4KQLRUfd7GEVXxAeDqwtuGTdwKd9zMfPycyAG3wJsdck"); + auto message = LegacyMessage::advanceNonceAccount(authorizer, nonceAccountAddress, recentBlockhash); + EXPECT_EQ(message.header.numRequiredSignatures, 1); + EXPECT_EQ(message.header.numReadOnlySignedAccounts, 0); + EXPECT_EQ(message.header.numReadOnlyUnsignedAccounts, 2); + ASSERT_EQ(message.accountKeys.size(), 4ul); + ASSERT_EQ(message.instructions.size(), 1ul); + EXPECT_EQ(message.instructions[0].programId.string(), "11111111111111111111111111111111"); + ASSERT_EQ(message.instructions[0].accounts.size(), 3ul); + auto transaction = Transaction(message); + transaction.signatures.clear(); + transaction.signatures.push_back(Base58::decode("2gwuvwJ3mdEsjA8Gid6FXYuSwa2AAyFY6Btw8ifwSc2SPsfKBnD" + "859C5mX4tLy6zQFHhKxSMMsW49o3dbJNiXDMo")); + auto expectedString = + "7YPgNzjCnUd2zBb6ZC6bf1YaoLjhJPHixLUdTjqMjq1YdzADJCx2wsTTBFFrqDKSHXEL6ntRq8NVJTQMGzGH5AQRKw" + "tKtutehxesmtzkZCPY9ADZ4ijFyveLmTt7kjZXX7ZWVoUmKAqiaYsPTex728uMBSRJpV4zRw2yKGdQRHTKy2QFEb9a" + "cwLjmrbEgoyzPCarxjPhw21QZnNcy8RiYJB2mzZ9nvhrD5d2jB5TtdiroQPgTSdKFzkNEd7hJUKpqUppjDFcNHGK73" + "FE9pCP2dKxCLH8Wfaez8bLtopjmWun9cbikxo7LZsarYzMXvxwZmerRd1"; + EXPECT_EQ(transaction.serialize(), expectedString); +} + +TEST(SolanaTransaction, TransferTokenTransactionWithExternalPayer) { + auto signer = Address("B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); + auto feePayer = Address("Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ"); + auto token = Address("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"); + auto senderTokenAddress = Address("5sS5Z8GAdVHqZKRqEvpDauHvvLgbDveiyfi81uh25mrf"); + auto recipientTokenAddress = Address("AZapcpAZtEL1gQuC87F2L1hSfAZnAvNy1hHtJ8DJzySN"); + uint64_t amount = 4000; + uint8_t decimals = 6; + auto recentBlockhash = Base58::decode("GwB5uixiTQUG2Pvo6fWAaNQmz41Jt4WMEPD7b83wvHkX"); + auto message = + LegacyMessage::createTokenTransfer(signer, token, senderTokenAddress, recipientTokenAddress, + amount, decimals, recentBlockhash, "", {}, "", feePayer.string()); + EXPECT_EQ(message.header.numRequiredSignatures, 2); + EXPECT_EQ(message.header.numReadOnlySignedAccounts, 0); + EXPECT_EQ(message.header.numReadOnlyUnsignedAccounts, 2); + ASSERT_EQ(message.accountKeys.size(), 6ul); + ASSERT_EQ(message.instructions.size(), 1ul); + EXPECT_EQ(message.instructions[0].programId.string(), "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); + ASSERT_EQ(message.instructions[0].accounts.size(), 4ul); + auto transaction = Transaction(message); + transaction.signatures.clear(); + auto signature1 = Base58::decode( + "2LbovMDuKoR2LFcV5NbK9bCQZcTG99W6VE1urvdWFWvRhNg9ocDGhayyeBGisoqZgYZtcD3b6LJDTmPx9Gp3T6qd"); + auto signature2 = Base58::decode( + "2hUHMS9rwbUbXrpC7sK7utL2M4soTyQ7EX3sBYvdee9wraJvYoPH2XjovHDn8eRFY8z5uCx9DCj2Zfjpmzfa81Db"); + transaction.signatures.push_back(signature1); + transaction.signatures.push_back(signature2); + + auto expectedString = + // https://explorer.solana.com/tx/2LbovMDuKoR2LFcV5NbK9bCQZcTG99W6VE1urvdWFWvRhNg9ocDGhayyeBGisoqZgYZtcD3b6LJDTmPx9Gp3T6qd?cluster=devnet + "qjgNVBmoPDHNTN2ENQfxNVE57jWXpqdmu5GQX4msA7iK8ZRAnKpvbusQagv8CZGyNYti23p9jBsjTSx75ZU26UW5vgC8D88pusW8W5dp1ERo5DSfurMSYJ6afgQHdcuzn7exb8znSm6uV4y1cWgBRcuAGdg3wRpVhP8HEB1EeKgzjYVWvMSy6yR7qVrSL6BxHG6eiAMyahLFbEt4qBqLEdxxY7Dt4DyydVYmG2ZVtheaMHD3ACwCjpyPLXj399wxSgGXQQFGtzEJQw9awVezmJ4wZk6W4dDpXQvdKYaqUvwTwRZsQB5o2iekPWZXR9xvHiMLjMVBPzYgcU14ZSaCbqSNVv2pAJxP1sMvxZMNMzZPttPxCsDDGq9biC7exXwzesXSnZ3rsgEYeZtkUiBHAxR4rYqBpA6VzLs1bPx8MPTvr9mhNi2ezMBbg2nEfHV6Fz7H7rEY2g3jDtRz35Vmgits8s9RKi3kb73WtGUieRiXjiqkNhpvKkST1oEYRQ9"; + EXPECT_EQ(transaction.serialize(), expectedString); +} + +TEST(SolanaTransaction, CreateTokenAccountAndTransferWithExternalFeePayer) { + auto signer = Address("B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); + auto feePayer = Address("Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ"); + auto token = Address("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"); + auto senderTokenAddress = Address("5sS5Z8GAdVHqZKRqEvpDauHvvLgbDveiyfi81uh25mrf"); + auto recipientMainAddress = Address("E54BymfdhXQtzDSGtgiJayauEMdB2gJjPqoDjzfbCXwj"); + auto recipientTokenAddress = Address("Ay7G7Yb7ZCebCQdG39FxD1nFNDtqFWJCBgfd5Ek7DDcS"); + uint64_t amount = 4000; + uint8_t decimals = 6; + auto recentBlockhash = Base58::decode("EsnN3ksLV6RLBW7qqgrvm2diwVJNTzL9QCuzm6tqWsU8"); + auto message = LegacyMessage::createTokenCreateAndTransfer( + signer, recipientMainAddress, token, recipientTokenAddress, senderTokenAddress, amount, + decimals, recentBlockhash, "", {}, "", feePayer.string()); + EXPECT_EQ(message.header.numRequiredSignatures, 2); + EXPECT_EQ(message.header.numReadOnlySignedAccounts, 0); + EXPECT_EQ(message.header.numReadOnlyUnsignedAccounts, 6); + ASSERT_EQ(message.accountKeys.size(), 10ul); + ASSERT_EQ(message.instructions.size(), 2ul); + EXPECT_EQ(message.instructions[0].programId.string(), + "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); + ASSERT_EQ(message.instructions[0].accounts.size(), 7ul); + EXPECT_EQ(message.instructions[1].programId.string(), + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); + ASSERT_EQ(message.instructions[1].accounts.size(), 4ul); + auto transaction = Transaction(message); + transaction.signatures.clear(); + transaction.signatures.push_back(Base58::decode( + "7GZGFT2VA4dpJBBwjiMqj1o8yChDvoCsqnQ7xz4GxY513W3efxRqbB8y7g4tH2GEGQRx4vCkKipG1sMaDEen1A2")); + transaction.signatures.push_back(Base58::decode( + "3n7RHTCBAtnFVuDn5eRbyQB24h6AqajJi5nGMPrfnUVFUDh2Cb8AoaJ7mVtjnv73V4HaJCzSwCLAj3zcGEaFftWZ")); + + // https://explorer.solana.com/tx/7GZGFT2VA4dpJBBwjiMqj1o8yChDvoCsqnQ7xz4GxY513W3efxRqbB8y7g4tH2GEGQRx4vCkKipG1sMaDEen1A2?cluster=devnet + auto expectedString = + "5sxFkQYd2FvqRU64N79A6xjJKNkgUsEEg2wKgai2NiK7A7hF3q5GYEbjQsYBG9S2MejwTENbwHzvypaa3D3cEkxvVTg19aJFWdCtXQiz42QF5fN2MuAb6eJR4KHFnzCtxxnYGtN9swZ5B5cMSPCffCRZeUTe3kooRmbTYPvSaemU6reVSM7X2beoFKPd2svrLFa8XnvhBwL9EiFWQ9WhHB2cDV7KozCnJAW9kdNDR4RbfFQxboANGo3ZGE5ddcZ6YdomATKze1TtHj2qzJEJRwxsRr3iM3iNFb4Eav5Q2n71KUriRf73mo44GQUPbQ2LvpZKf4V6M2PzxJwzBo7FiFZurPmsanT3U5efEsKnnueddbiLHedc8JXc1d3Z53sFxVGJpsGA8RR6thse9wUvaEWqXVtPbNA6NMao9DFGD6Dudza9pJXSobPc7mDHZmVmookf5vi6Lb9Y1Q4EgcEPQmbaDnKGGB6uGfZe629i3iKXRzAd2dB7mKfffhDadZ8S1eYGT3dhddV3ExRxcqDP9BAGQT3rkRw1JpeSSi7ziYMQ3vn4t3okdgQSq6rrpbPDUNG8tLSHFMAq3ydnh4Cb4ECKkYoz9SFAnXACUu4mWETxijuKMK9kHrTqPGk9weHTzobzCC8q8fcPWV3TcyUyMxsbVxh5q1p5h5tWfD9td5TZJ2HEUbTop2dA53ZF"; EXPECT_EQ(transaction.serialize(), expectedString); } diff --git a/tests/chains/Stellar/AddressTests.cpp b/tests/chains/Stellar/AddressTests.cpp index 25fd4ca8878..3b9b7f5cfd9 100644 --- a/tests/chains/Stellar/AddressTests.cpp +++ b/tests/chains/Stellar/AddressTests.cpp @@ -21,12 +21,18 @@ TEST(StellarAddress, FromPublicKey) { const auto address = Address(publicKey); auto str = hex(address.bytes); ASSERT_EQ(string("GAB6EDWGWSRZUYUYCWXAFQFBHE5ZEJPDXCIMVZC3LH2C7IU35FTI2NOQ"), address.string()); + + const auto privateKey = PrivateKey(parse_hex("94d1a980d5e528067d44bf8a60d646f556e40ca71e17cd4ead2d56f89e4bd20f")); + const auto publicKey2 = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); + EXPECT_ANY_THROW(new Address(publicKey2)); } TEST(StellarAddress, FromString) { string stellarAddress = "GAB6EDWGWSRZUYUYCWXAFQFBHE5ZEJPDXCIMVZC3LH2C7IU35FTI2NOQ"; const auto address = Address(stellarAddress); ASSERT_EQ(address.string(), stellarAddress); + + EXPECT_ANY_THROW(new Address("")); } TEST(StellarAddress, isValid) { diff --git a/tests/chains/Stellar/TransactionCompilerTests.cpp b/tests/chains/Stellar/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..94ea746502c --- /dev/null +++ b/tests/chains/Stellar/TransactionCompilerTests.cpp @@ -0,0 +1,95 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Stellar.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(StellarCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeStellar; + /// Step 1: Prepare transaction input (protobuf) + TW::Stellar::Proto::SigningInput input; + auto privateKey = + PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722")); + auto publicKey = privateKey.getPublicKey(::publicKeyType(coin)); + + input.set_passphrase(TWStellarPassphrase_Stellar); + input.set_account("GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI"); + input.set_fee(1000); + input.set_sequence(2); + input.mutable_op_payment()->set_destination( + "GDCYBNRRPIHLHG7X7TKPUPAZ7WVUXCN3VO7WCCK64RIFV5XM5V5K4A52"); + input.mutable_op_payment()->set_amount(10000000); + auto &memoText = *input.mutable_memo_text(); + memoText.set_text("Hello, world!"); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, inputStrData); + + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), + "1e8786a0162630b2393e0f6c51f16a2d7860715023cb19bf25cad14490b1f8f3"); + + auto signature = parse_hex("5042574491827aaccbce1e2964c05098caba06194beb35e595aabfec9f788516a83" + "3f755f18144f4a2eedb3123d180f44e7c16037d00857c5c5b7033ebac2c01"); + + /// Step 3: Compile transaction info + const auto tx = "AAAAAAmpZryqzBA+OIlrquP4wvBsIf1H3U+GT/" + "DTP5gZ31yiAAAD6AAAAAAAAAACAAAAAAAAAAEAAAANSGVsbG8sIHdvcmxkIQAAAAAAAAEAAAAAAAAA" + "AQAAAADFgLYxeg6zm/" + "f81Po8Gf2rS4m7q79hCV7kUFr27O16rgAAAAAAAAAAAJiWgAAAAAAAAAABGd9cogAAAEBQQldEkYJ6" + "rMvOHilkwFCYyroGGUvrNeWVqr/sn3iFFqgz91XxgUT0ou7bMSPRgPROfBYDfQCFfFxbcDPrrCwB"; + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {}); + { + TW::Stellar::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.signature(), tx); + } + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::Stellar::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + signingInput.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + TW::Stellar::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.signature(), tx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, signature}, {}); + Stellar::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.signature().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_signatures_count); + } +} diff --git a/tests/chains/Stellar/TransactionTests.cpp b/tests/chains/Stellar/TransactionTests.cpp index 13f50e8dbdb..1de448313d9 100644 --- a/tests/chains/Stellar/TransactionTests.cpp +++ b/tests/chains/Stellar/TransactionTests.cpp @@ -6,12 +6,10 @@ #include "TestUtilities.h" -#include "Stellar/Signer.h" #include "HexCoding.h" -#include "PrivateKey.h" +#include "Stellar/Signer.h" #include #include -#include "BinaryCoding.h" #include @@ -42,12 +40,12 @@ TEST(StellarTransaction, sign) { TEST(StellarTransaction, signWithMemoText) { auto privateKey = PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722")); - auto input = Proto::SigningInput(); + auto input = TW::Stellar::Proto::SigningInput(); input.set_passphrase(TWStellarPassphrase_Stellar); input.set_account("GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI"); input.set_fee(1000); input.set_sequence(2); - auto memoText = Proto::MemoText(); + auto memoText = TW::Stellar::Proto::MemoText(); memoText.set_text("Hello, world!"); *input.mutable_memo_text() = memoText; input.mutable_op_payment()->set_destination("GDCYBNRRPIHLHG7X7TKPUPAZ7WVUXCN3VO7WCCK64RIFV5XM5V5K4A52"); @@ -62,12 +60,12 @@ TEST(StellarTransaction, signWithMemoText) { TEST(StellarTransaction, signWithMemoHash) { auto privateKey = PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722")); - auto input = Proto::SigningInput(); + auto input = TW::Stellar::Proto::SigningInput(); input.set_passphrase(TWStellarPassphrase_Stellar); input.set_account("GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI"); input.set_fee(1000); input.set_sequence(2); - auto memoHash = Proto::MemoHash(); + auto memoHash = TW::Stellar::Proto::MemoHash(); auto fromHex = parse_hex("315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3"); memoHash.set_hash(fromHex.data(), fromHex.size()); *input.mutable_memo_hash() = memoHash; @@ -83,12 +81,12 @@ TEST(StellarTransaction, signWithMemoHash) { TEST(StellarTransaction, signWithMemoReturn) { auto privateKey = PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722")); - auto input = Proto::SigningInput(); + auto input = TW::Stellar::Proto::SigningInput(); input.set_passphrase(TWStellarPassphrase_Stellar); input.set_account("GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI"); input.set_fee(1000); input.set_sequence(2); - auto memoHash = Proto::MemoHash(); + auto memoHash = TW::Stellar::Proto::MemoHash(); auto fromHex = parse_hex("315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3"); memoHash.set_hash(fromHex.data(), fromHex.size()); *input.mutable_memo_return_hash() = memoHash; @@ -104,12 +102,12 @@ TEST(StellarTransaction, signWithMemoReturn) { TEST(StellarTransaction, signWithMemoID) { auto privateKey = PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722")); - auto input = Proto::SigningInput(); + auto input = TW::Stellar::Proto::SigningInput(); input.set_passphrase(TWStellarPassphrase_Stellar); input.set_account("GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI"); input.set_fee(1000); input.set_sequence(2); - auto memoId = Proto::MemoId(); + auto memoId = TW::Stellar::Proto::MemoId(); memoId.set_id(1234567890); *input.mutable_memo_id() = memoId; input.mutable_op_payment()->set_destination("GDCYBNRRPIHLHG7X7TKPUPAZ7WVUXCN3VO7WCCK64RIFV5XM5V5K4A52"); @@ -124,12 +122,12 @@ TEST(StellarTransaction, signWithMemoID) { TEST(StellarTransaction, signAcreateAccount) { auto privateKey = PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722")); - auto input = Proto::SigningInput(); + auto input = TW::Stellar::Proto::SigningInput(); input.set_passphrase(TWStellarPassphrase_Stellar); input.set_account("GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI"); input.set_fee(1000); input.set_sequence(2); - auto memoId = Proto::MemoId(); + auto memoId = TW::Stellar::Proto::MemoId(); memoId.set_id(1234567890); *input.mutable_memo_id() = memoId; input.mutable_op_create_account()->set_destination("GDCYBNRRPIHLHG7X7TKPUPAZ7WVUXCN3VO7WCCK64RIFV5XM5V5K4A52"); diff --git a/tests/chains/Stratis/TWCoinTypeTests.cpp b/tests/chains/Stratis/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..c2b85b47792 --- /dev/null +++ b/tests/chains/Stratis/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWStratisCoinType, TWCoinType) { + const auto coin = TWCoinTypeStratis; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("3923df87e83859dec8b87a414cbb1529113788c512a4d0c283e1394c274f0bfb")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("XWqnSWzQA5kDAS727UTYtXkdcbKc8mEvyN")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "stratis"); + assertStringsEqual(name, "Stratis"); + assertStringsEqual(symbol, "STRAX"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 8); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainBitcoin); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x8c); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(txUrl, "https://explorer.rutanio.com/strax/explorer/transaction/3923df87e83859dec8b87a414cbb1529113788c512a4d0c283e1394c274f0bfb"); + assertStringsEqual(accUrl, "https://explorer.rutanio.com/strax/explorer/address/XWqnSWzQA5kDAS727UTYtXkdcbKc8mEvyN"); +} diff --git a/tests/chains/Stratis/TWStratisTests.cpp b/tests/chains/Stratis/TWStratisTests.cpp new file mode 100644 index 00000000000..4b15ca2b480 --- /dev/null +++ b/tests/chains/Stratis/TWStratisTests.cpp @@ -0,0 +1,74 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "TestUtilities.h" + +#include +#include +#include +#include +#include +#include + +#include + +TEST(Stratis, LegacyAddress) { + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730").get())); + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); + auto address = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeStratis))); + auto addressString = WRAPS(TWBitcoinAddressDescription(address.get())); + assertStringsEqual(addressString, "XMEd53bqmNitpFX1cUd1tV6LRME4pcuaPe"); +} + +TEST(Stratis, Address) { + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625").get())); + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); + auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeStratis)); + auto string = WRAPS(TWAnyAddressDescription(address.get())); + + assertStringsEqual(string, "strax1qytnqzjknvv03jwfgrsmzt0ycmwqgl0as6zdq3n"); +} + +TEST(Stratis, LockScriptForLegacyAddress) { + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("XWqnSWzQA5kDAS727UTYtXkdcbKc8mEvyN").get(), TWCoinTypeStratis)); + auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); + assertHexEqual(scriptData, "76a914d5d068b60f3b63a5a59cc7b8609ac85b76b1896388ac"); +} + +TEST(Stratis, LockScriptForAddress) { + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("strax1qqktrryxg23qjxmnhmz9xsp8w4kkfqv7c2xl6t7").get(), TWCoinTypeStratis)); + auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); + assertHexEqual(scriptData, "001405963190c85441236e77d88a6804eeadac9033d8"); +} + +TEST(Stratis, ExtendedKeys) { + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic( + STRING("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal").get(), + STRING("TREZOR").get() + )); + + // .bip44 + auto xprv = WRAPS(TWHDWalletGetExtendedPrivateKey(wallet.get(), TWPurposeBIP44, TWCoinTypeStratis, TWHDVersionXPRV)); + auto xpub = WRAPS(TWHDWalletGetExtendedPublicKey(wallet.get(), TWPurposeBIP44, TWCoinTypeStratis, TWHDVersionXPUB)); + + assertStringsEqual(xprv, "xprv9xyQ71PNhXBFdiY9xAs76X1Y4YzejPv9qe6tKBQ4pEemnmk6b4iFnV1BpJThm2en26emssc558vqHPujDyBKDdSkrNtQiHwbzpQNobWyvh9"); + assertStringsEqual(xpub, "xpub6BxkWWvGXtjYrCcd4CQ7TexGcaq98re1Cs2V7ZogNaBkfa5F8c2WLHKffYrwmJdNQztsd3oJvdmHuhN79c8qKpASRtavBsbcUq1R5SxeQtq"); +} + +TEST(Stratis, DeriveFromXpub) { + auto xpub = STRING("xpub6BxkWWvGXtjYrCcd4CQ7TexGcaq98re1Cs2V7ZogNaBkfa5F8c2WLHKffYrwmJdNQztsd3oJvdmHuhN79c8qKpASRtavBsbcUq1R5SxeQtq"); + auto pubKey2 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeStratis, STRING("m/44'/105105'/0'/0/2").get())); + auto pubKey9 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeStratis, STRING("m/44'/105105'/0'/0/9").get())); + + auto address2 = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(pubKey2.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeStratis))); + auto address2String = WRAPS(TWBitcoinAddressDescription(address2.get())); + + auto address9 = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(pubKey9.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeStratis))); + auto address9String = WRAPS(TWBitcoinAddressDescription(address9.get())); + + assertStringsEqual(address2String, "XC4QM1nSbHrLb8sWMf4qXcphocqSAMNLng"); + assertStringsEqual(address9String, "XM4ixdCpyqF86RhKwWRyUXFxXHNypRXiyL"); +} \ No newline at end of file diff --git a/tests/chains/Substrate/AddressTests.cpp b/tests/chains/Substrate/AddressTests.cpp new file mode 100644 index 00000000000..3769cee87ac --- /dev/null +++ b/tests/chains/Substrate/AddressTests.cpp @@ -0,0 +1,67 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "HexCoding.h" +#include "Substrate/Address.h" +#include "TestUtilities.h" + +using namespace TW; +namespace TW::Substrate { + +TEST(SubstrateAddress, Validation) { + ASSERT_TRUE(Address::isValid("cEYtw6AVMB27hFUs4gVukajLM7GqxwxUfJkbPY3rNToHMcCgb", 64)); + ASSERT_FALSE(Address::isValid("JCViCkwMdGWKpf7Wogb8EFtDmaYTEZGEg6ah4svUPGnnpc7A", 64)); + + // polymesh + ASSERT_TRUE(Address::isValid("2DxwekgWwK7sqVeuXGmaXLZUvwnewLTs2rvU2CFKLgvvYwCG", 12)); + ASSERT_FALSE(Address::isValid("JCViCkwMdGWKpf7Wogb8EFtDmaYTEZGEg6ah4svUPGnnpc7A", 12)); +} + +TEST(SubstrateAddress, FromPublicKey) { + auto publicKey = PublicKey(parse_hex("0x92fd9c237030356e26cfcc4568dc71055d5ec92dfe0ff903767e00611971bad3"), TWPublicKeyTypeED25519); + auto addressPolkadot = Address(publicKey, 0); + ASSERT_EQ(addressPolkadot.string(), "14KjL5vGAYJCbKgZJmFKDSjewtBpvaxx9YvRZvi7qmb5s8CC"); + + auto addressAstar = Address(publicKey, 5); + ASSERT_EQ(addressAstar.string(), "ZG2d3dH5zfqNchsqReS6x4nBJuJCW7Z6Fh5eLvdA3ZXGkPd"); + + auto addressParallel = Address(publicKey, 172); + ASSERT_EQ(addressParallel.string(), "p8EGHjWt7e1MYoD7V6WXvbPZWK9GSJiiK85kv2R7Ur7FisPUL"); + + // polymesh + publicKey = PublicKey(parse_hex("849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f83"), TWPublicKeyTypeED25519); + auto addressPolymesh = Address(publicKey, 12); + ASSERT_EQ(addressPolymesh.string(), "2FSoQykVV3uWe5ChZuazMDHBoaZmCPPuoYx5KHL5VqXooDQW"); +} + +TEST(SubstrateAddress, FromString) { + auto addressKusama = Address("Fu3r514w83euSVV7q1MyFGWErUR2xDzXS2goHzimUn4S12D", 2); + ASSERT_EQ(addressKusama.string(), "Fu3r514w83euSVV7q1MyFGWErUR2xDzXS2goHzimUn4S12D"); + + auto addressParallel = Address("p8EGHjWt7e1MYoD7V6WXvbPZWK9GSJiiK85kv2R7Ur7FisPUL", 172); + ASSERT_EQ(addressParallel.string(), "p8EGHjWt7e1MYoD7V6WXvbPZWK9GSJiiK85kv2R7Ur7FisPUL"); + + // polymesh + auto addressPolymesh = Address("2FSoQykVV3uWe5ChZuazMDHBoaZmCPPuoYx5KHL5VqXooDQW", 12); + ASSERT_EQ(addressPolymesh.string(), "2FSoQykVV3uWe5ChZuazMDHBoaZmCPPuoYx5KHL5VqXooDQW"); +} + +TEST(SubstrateAddress, Keybytes) { + auto pubKeyBytes = parse_hex("0x92fd9c237030356e26cfcc4568dc71055d5ec92dfe0ff903767e00611971bad3"); + auto publicKey = PublicKey(pubKeyBytes, TWPublicKeyTypeED25519); + + auto addressAstar = Address(publicKey, 5); + ASSERT_EQ(addressAstar.keyBytes(), pubKeyBytes); + + auto addressParallel = Address(publicKey, 172); + ASSERT_EQ(addressParallel.keyBytes(), pubKeyBytes); + + // polymesh + auto addressPolymesh = Address(publicKey, 12); + ASSERT_EQ(addressPolymesh.keyBytes(), pubKeyBytes); +} + +} // namespace TW::Substrate \ No newline at end of file diff --git a/tests/chains/Substrate/ExtrinsicTests.cpp b/tests/chains/Substrate/ExtrinsicTests.cpp new file mode 100644 index 00000000000..81455e26c43 --- /dev/null +++ b/tests/chains/Substrate/ExtrinsicTests.cpp @@ -0,0 +1,140 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Data.h" +#include "HexCoding.h" +#include "Substrate/Extrinsic.h" +#include "proto/Substrate.pb.h" +#include "uint256.h" + +#include + +using namespace std; +using namespace TW; + +namespace TW::Substrate::tests { + +TEST(SubstrateExtrinsic, Polymesh_encodeTransferWithMemo) { + Substrate::Proto::SigningInput input; + input.set_network(12); + input.set_multi_address(true); + + auto* transfer = input.mutable_balance_call()->mutable_transfer(); + transfer->set_module_index(0x05); + transfer->set_method_index(0x01); + transfer->set_to_address("2EB7wW2fYfFskkSx2d65ivn34ewpuEjcowfJYBL79ty5FsZF"); + + auto value = store(1); + transfer->set_value(std::string(value.begin(), value.end())); + transfer->set_memo("MEMO PADDED WITH SPACES"); + + auto result = Substrate::Extrinsic(input).encodeCall(); + EXPECT_EQ(hex(result), "0501004c6c63e3dc083959f876788716b78885460b5f3c7ed9379f8d5f408e08639e0204014d454d4f20504144444544205749544820535041434553000000000000000000"); +} + +TEST(SubstrateExtrinsic, Polymesh_encodeAuthorizationJoinIdentity) { + Substrate::Proto::SigningInput input; + input.set_network(12); + input.set_multi_address(true); + + auto* identity = input.mutable_polymesh_call()->mutable_authorization_call()->mutable_join_identity(); + identity->set_module_index(0x07); + identity->set_method_index(0x0d); + identity->set_target("2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc"); + + auto result = Substrate::Extrinsic(input).encodeCall(); + EXPECT_EQ(hex(result), "070d0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320501000100010000"); + + auto* authData = identity->mutable_data(); + authData->mutable_asset()->set_data({0x00}); + authData->mutable_extrinsic()->set_data({0x00}); + authData->mutable_portfolio()->set_data({0x00}); + + EXPECT_EQ(hex(result), hex(Substrate::Extrinsic(input).encodeCall())); + + // clear data + authData->clear_asset(); + authData->clear_extrinsic(); + authData->clear_portfolio(); + + EXPECT_EQ(hex(Substrate::Extrinsic(input).encodeCall()), "070d0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320500000000"); +} + +TEST(SubstrateExtrinsic, Polymesh_encodeIdentity) { + Substrate::Proto::SigningInput input; + input.set_network(12); + input.set_multi_address(true); + + auto* key = input.mutable_polymesh_call()->mutable_identity_call()->mutable_join_identity_as_key(); + key->set_module_index(0x07); + key->set_method_index(0x05); + key->set_auth_id(4875); + + auto result = Substrate::Extrinsic(input).encodeCall(); + EXPECT_EQ(hex(result), "07050b13000000000000"); +} + +TEST(SubstrateExtrinsic, Statemint_encodeAssetTransfer) { + // tx on mainnet + // https://statemint.subscan.io/extrinsic/2619512-2 + + Substrate::Proto::SigningInput input; + input.set_network(0); + input.set_multi_address(true); + + auto* transfer = input.mutable_balance_call()->mutable_asset_transfer(); + transfer->set_module_index(0x32); + transfer->set_method_index(0x05); + transfer->set_to_address("14ixj163bkk2UEKLEXsEWosuFNuijpqEWZbX5JzN4yMHbUVD"); + + auto value = store(999500000); + transfer->set_value(std::string(value.begin(), value.end())); + transfer->set_asset_id(1984); + + auto result = Substrate::Extrinsic(input).encodeCall(); + // clang-format off + EXPECT_EQ(hex(result), "3205" + "011f" + "00" + "a4b558a0342ae6e379a7ed00d23ff505f1101646cb279844496ad608943eda0d" + "82a34cee"); + // clang-format on +} + +TEST(SubstrateExtrinsic, Statemint_encodeBatchAssetTransfer) { + // tx on mainnet + // https://statemint.subscan.io/extrinsic/2571849-2 + + Substrate::Proto::SigningInput input; + input.set_network(0); + input.set_multi_address(true); + + auto* transfer = input.mutable_balance_call()->mutable_batch_asset_transfer(); + transfer->set_module_index(0x28); + transfer->set_method_index(0x00); + transfer->set_fee_asset_id(0x00); + auto* t = transfer->add_transfers(); + t->set_to_address("13wQDQTMM6E9g5WD27e6UsWWTwHLaW763FQxnkbVaoKmsBQy"); + + auto value = store(808081); + t->set_module_index(0x32); + t->set_method_index(0x06); + t->set_value(std::string(value.begin(), value.end())); + t->set_asset_id(1984); + + auto result = Substrate::Extrinsic(input).encodeCall(); + // clang-format off + EXPECT_EQ(hex(result), "2800" + "04" + "3206" + "011f" + "00" + "81f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c91" + "46523100"); + // clang-format on +} + +} // namespace TW::Substrate::tests \ No newline at end of file diff --git a/tests/chains/Substrate/SignerTests.cpp b/tests/chains/Substrate/SignerTests.cpp new file mode 100644 index 00000000000..c8fe2561ca9 --- /dev/null +++ b/tests/chains/Substrate/SignerTests.cpp @@ -0,0 +1,300 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Substrate/Signer.h" +#include "Data.h" +#include "HexCoding.h" +#include "PublicKey.h" +#include "proto/Substrate.pb.h" +#include "uint256.h" + +#include + +using namespace std; +using namespace TW; + +namespace TW::Substrate::tests { + +Proto::SigningInput buildSigningInput() { + // tx on mainnet + // https://polymesh.subscan.io/extrinsic/0x9a4283cc38f7e769c53ad2d1c5cf292fc85a740ec1c1aa80c180847e51928650 + + Substrate::Proto::SigningInput input; + input.set_network(12); + input.set_multi_address(true); + auto blockHash = parse_hex("898bba6413c38f79a284aec8749f297f6c8734c501f67517b5a6aadc338d1102"); + auto genesisHash = parse_hex("6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063"); + input.set_block_hash(std::string(blockHash.begin(), blockHash.end())); + input.set_genesis_hash(std::string(genesisHash.begin(), genesisHash.end())); + input.set_nonce(1ul); + input.set_spec_version(3010u); + input.set_transaction_version(2u); + + auto* era = input.mutable_era(); + era->set_block_number(4298130ul); + era->set_period(64ul); + + auto* transfer = input.mutable_balance_call()->mutable_transfer(); + transfer->set_module_index(0x05); + transfer->set_method_index(0x01); + transfer->set_to_address("2FSoQykVV3uWe5ChZuazMDHBoaZmCPPuoYx5KHL5VqXooDQW"); + + auto value = store(1000000); + transfer->set_value(std::string(value.begin(), value.end())); + transfer->set_memo("MEMO PADDED WITH SPACES"); + return input; +} + +TEST(SubstrateSigner, signaturePreImage) { + auto input = buildSigningInput(); + auto preImage = Signer::signaturePreImage(input); + ASSERT_EQ(hex(preImage), "050100849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f8302093d00014d454d4f2050414444454420574954482053504143455300000000000000000025010400c20b0000020000006fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063898bba6413c38f79a284aec8749f297f6c8734c501f67517b5a6aadc338d1102"); +} + +TEST(SubstrateSigner, encodeTransaction) { + auto input = buildSigningInput(); + auto publicKey = parse_hex("4322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee"); + auto signature = parse_hex("0791ee378775eaff34ef7e529ab742f0d81d281fdf20ace0aa765ca484f5909c4eea0a59c8dbbc534c832704924b424ba3230c38acd0ad5360cef023ca2a420f"); + auto encoded = Signer::encodeTransaction(input, publicKey, signature); + ASSERT_EQ(hex(encoded), "bd0284004322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee000791ee378775eaff34ef7e529ab742f0d81d281fdf20ace0aa765ca484f5909c4eea0a59c8dbbc534c832704924b424ba3230c38acd0ad5360cef023ca2a420f25010400050100849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f8302093d00014d454d4f20504144444544205749544820535041434553000000000000000000"); +} + +TEST(SubstrateSigner, encodeTransaction_Add_authorization) { + // tx on mainnet + // https://polymesh.subscan.io/extrinsic/0x7d9b9109027b36b72d37ba0648cb70e5254524d3d6752cc6b41601f4bdfb1af0 + + Substrate::Proto::SigningInput input; + input.set_network(12); + input.set_multi_address(true); + auto blockHash = parse_hex("ce0c2109db498e45abf8fd447580dcfa7b7a07ffc2bfb1a0fbdd1af3e8816d2b"); + auto genesisHash = parse_hex("6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063"); + input.set_block_hash(std::string(blockHash.begin(), blockHash.end())); + input.set_genesis_hash(std::string(genesisHash.begin(), genesisHash.end())); + input.set_nonce(5ul); + input.set_spec_version(3010u); + input.set_transaction_version(2u); + + auto* era = input.mutable_era(); + era->set_block_number(4395451ul); + era->set_period(64ul); + + auto* ji = input.mutable_polymesh_call()->mutable_authorization_call()->mutable_join_identity(); + ji->set_module_index(0x07); + ji->set_method_index(0x0d); + ji->set_target("2HEVN4PHYKj7B1krQ9bctAQXZxHQQkANVNCcfbdYk2gZ4cBR"); + + auto publicKey = parse_hex("4322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee"); + auto signature = parse_hex("81e6561e4391862b5da961d7033baced1c4b25f0e27f938b02321af1118e0b859e1c2bd5607576a258f2c2befbc5f397ea4adb62938f30eb73c8060ab0eabf01"); + auto encoded = Signer::encodeTransaction(input, publicKey, signature); + ASSERT_EQ(hex(encoded), "490284004322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee0081e6561e4391862b5da961d7033baced1c4b25f0e27f938b02321af1118e0b859e1c2bd5607576a258f2c2befbc5f397ea4adb62938f30eb73c8060ab0eabf01b5031400070d01d3b2f1c41b9b4522eb3e23329b81aca6cc0231167ecfa3580c5a71ff6d0610540501000100010000"); +} + +TEST(SubstrateSigner, encodeTransaction_JoinIdentityAsKey) { + // tx on mainnet + // https://polymesh.subscan.io/extrinsic/0x9d7297d8b38af5668861996cb115f321ed681989e87024fda64eae748c2dc542 + + Substrate::Proto::SigningInput input; + input.set_network(12); + input.set_multi_address(true); + auto blockHash = parse_hex("45c80153c47f5d16acc7a66d473870e8d4574437a7d8c813f47da74cae3812c2"); + auto genesisHash = parse_hex("6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063"); + input.set_block_hash(std::string(blockHash.begin(), blockHash.end())); + input.set_genesis_hash(std::string(genesisHash.begin(), genesisHash.end())); + input.set_nonce(0ul); + input.set_spec_version(3010u); + input.set_transaction_version(2u); + + auto* era = input.mutable_era(); + era->set_block_number(4395527ul); + era->set_period(64ul); + + auto* key = input.mutable_polymesh_call()->mutable_identity_call()->mutable_join_identity_as_key(); + key->set_module_index(0x07); + key->set_method_index(0x05); + key->set_auth_id(21435); + + auto publicKey = parse_hex("d3b2f1c41b9b4522eb3e23329b81aca6cc0231167ecfa3580c5a71ff6d061054"); + auto signature = parse_hex("7f5adbb2749e2f0ace29b409c41dd717681495b1f22dc5358311646a9fb8af8a173fc47f1b19748fb56831c2128773e2976986685adee83c741ab49934d80006"); + auto encoded = Signer::encodeTransaction(input, publicKey, signature); + ASSERT_EQ(hex(encoded), "c5018400d3b2f1c41b9b4522eb3e23329b81aca6cc0231167ecfa3580c5a71ff6d061054007f5adbb2749e2f0ace29b409c41dd717681495b1f22dc5358311646a9fb8af8a173fc47f1b19748fb56831c2128773e2976986685adee83c741ab49934d80006750000000705bb53000000000000"); +} + +TEST(SubstrateSigner, Statemint_encodeTransaction_transfer) { + // tx on mainnet + // https://statemint.subscan.io/extrinsic/2686030-2 + + Substrate::Proto::SigningInput input; + input.set_network(0); + input.set_multi_address(true); + auto blockHash = parse_hex("68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f"); + auto genesisHash = parse_hex("68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f"); + input.set_block_hash(std::string(blockHash.begin(), blockHash.end())); + input.set_genesis_hash(std::string(genesisHash.begin(), genesisHash.end())); + input.set_nonce(0ul); + input.set_spec_version(9320u); + input.set_transaction_version(9u); + + auto* transfer = input.mutable_balance_call()->mutable_asset_transfer(); + transfer->set_module_index(0x32); + transfer->set_method_index(0x05); + transfer->set_to_address("12q4hq1dgqHZVGzHbwZmqq1cFwatN15Visfd7YmUiMB5ZWkH"); + + auto value = store(100000); + transfer->set_value(std::string(value.begin(), value.end())); + transfer->set_asset_id(1984); + transfer->set_fee_asset_id(0x00); // native token + + auto publicKey = parse_hex("81f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c91"); + auto signature = parse_hex("e0ae36a5ceaaa7ff53fadfecc8a285a436b15e39c43ea09e8897f34fa3fe55133028eb7d8a9ea2cd42ff1c786e945cd47a02243454ecb39c81acc3409d96f903"); + auto encoded = Signer::encodeTransaction(input, publicKey, signature); + ASSERT_EQ(hex(encoded), "4102840081f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c9100e0ae36a5ceaaa7ff53fadfecc8a285a436b15e39c43ea09e8897f34fa3fe55133028eb7d8a9ea2cd42ff1c786e945cd47a02243454ecb39c81acc3409d96f903000000003205011f0050e47b3c8aef60bc4fc744d8d979cb0eb2d45fa25c2e9da74e1e5ebd9e117518821a0600"); +} + +TEST(SubstrateSigner, Statemint_encodeTransaction_transfer_keep_alive) { + // tx on mainnet + // https://statemint.subscan.io/extrinsic/2686081-2 + + Substrate::Proto::SigningInput input; + input.set_network(0); + input.set_multi_address(true); + auto blockHash = parse_hex("e8f10f9a841dc73578148c763afa17638670c8655542172a80af2e03bf3cbe62"); + auto genesisHash = parse_hex("68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f"); + input.set_block_hash(std::string(blockHash.begin(), blockHash.end())); + input.set_genesis_hash(std::string(genesisHash.begin(), genesisHash.end())); + input.set_nonce(2ul); + input.set_spec_version(9320u); + input.set_transaction_version(9u); + + auto* era = input.mutable_era(); + era->set_block_number(2686056ul); + era->set_period(64ul); + + auto* transfer = input.mutable_balance_call()->mutable_asset_transfer(); + transfer->set_module_index(0x32); + transfer->set_method_index(0x06); + transfer->set_to_address("12q4hq1dgqHZVGzHbwZmqq1cFwatN15Visfd7YmUiMB5ZWkH"); + + auto value = store(100000); + transfer->set_value(std::string(value.begin(), value.end())); + transfer->set_asset_id(1984); + transfer->set_fee_asset_id(0x00); // native token + + auto publicKey = parse_hex("81f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c91"); + auto signature = parse_hex("68c40526bd9e56e340bfc9385ea463afce34e5c49be75b5946974d9ef6a357f90842036cd1b811b60882ae7183aa23545ef5825aafc8aaa6274d71a03414dc0a"); + auto encoded = Signer::encodeTransaction(input, publicKey, signature); + ASSERT_EQ(hex(encoded), "4502840081f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c910068c40526bd9e56e340bfc9385ea463afce34e5c49be75b5946974d9ef6a357f90842036cd1b811b60882ae7183aa23545ef5825aafc8aaa6274d71a03414dc0a85020800003206011f0050e47b3c8aef60bc4fc744d8d979cb0eb2d45fa25c2e9da74e1e5ebd9e117518821a0600"); +} + +TEST(SubstrateSigner, Statemint_encodeTransaction_batch_transfer_keep_alive) { + // tx on mainnet + // https://statemint.subscan.io/extrinsic/2711054-2 + + Substrate::Proto::SigningInput input; + input.set_network(0); + input.set_multi_address(true); + auto blockHash = parse_hex("c8a2e9492f822f8c07f3717a00e36f68a3090a878b07998724ec1f178f4cf514"); + auto genesisHash = parse_hex("68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f"); + input.set_block_hash(std::string(blockHash.begin(), blockHash.end())); + input.set_genesis_hash(std::string(genesisHash.begin(), genesisHash.end())); + input.set_nonce(3ul); + input.set_spec_version(9320u); + input.set_transaction_version(9u); + + auto* era = input.mutable_era(); + era->set_block_number(2711016ul); + era->set_period(64ul); + + auto* transfer = input.mutable_balance_call()->mutable_batch_asset_transfer(); + transfer->set_module_index(0x28); + transfer->set_method_index(0x00); + transfer->set_fee_asset_id(0x00); + + auto* t1 = transfer->add_transfers(); + t1->set_module_index(0x32); + t1->set_method_index(0x06); + t1->set_to_address("12q4hq1dgqHZVGzHbwZmqq1cFwatN15Visfd7YmUiMB5ZWkH"); + auto value = store(100000); + t1->set_value(std::string(value.begin(), value.end())); + t1->set_asset_id(1984); + t1->set_fee_asset_id(0x00); // native token + + auto* t2 = transfer->add_transfers(); + t2->set_module_index(0x32); + t2->set_method_index(0x06); + t2->set_to_address("12q4hq1dgqHZVGzHbwZmqq1cFwatN15Visfd7YmUiMB5ZWkH"); + t2->set_value(std::string(value.begin(), value.end())); + t2->set_asset_id(1984); + t2->set_fee_asset_id(0x00); // native token + + auto publicKey = parse_hex("81f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c91"); + auto signature = parse_hex("e1d541271965858ff2ba1a1296f0b4d28c8cbcaddf0ea06a9866869caeca3d16eff1265591d11b46d66882493079fde9e425cd941f166260135e9d81f7daf60c"); + auto encoded = Signer::encodeTransaction(input, publicKey, signature); + ASSERT_EQ(hex(encoded), "f502840081f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c9100e1d541271965858ff2ba1a1296f0b4d28c8cbcaddf0ea06a9866869caeca3d16eff1265591d11b46d66882493079fde9e425cd941f166260135e9d81f7daf60c85020c00002800083206011f0050e47b3c8aef60bc4fc744d8d979cb0eb2d45fa25c2e9da74e1e5ebd9e117518821a06003206011f0050e47b3c8aef60bc4fc744d8d979cb0eb2d45fa25c2e9da74e1e5ebd9e117518821a0600"); +} + +TEST(SubstrateSigner, Statemint_encodeTransaction_dot_transfer_keep_alive) { + // tx on mainnet + // https://statemint.subscan.io/extrinsic/2789245-2 + + Substrate::Proto::SigningInput input; + input.set_network(0); + input.set_multi_address(true); + auto blockHash = parse_hex("68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f"); + auto genesisHash = parse_hex("68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f"); + input.set_block_hash(std::string(blockHash.begin(), blockHash.end())); + input.set_genesis_hash(std::string(genesisHash.begin(), genesisHash.end())); + input.set_nonce(7ul); + input.set_spec_version(9320u); + input.set_transaction_version(9u); + + auto* transfer = input.mutable_balance_call()->mutable_asset_transfer(); + transfer->set_module_index(0x0a); + transfer->set_method_index(0x03); + transfer->set_to_address("12q4hq1dgqHZVGzHbwZmqq1cFwatN15Visfd7YmUiMB5ZWkH"); + + auto value = store(100000); + transfer->set_value(std::string(value.begin(), value.end())); + transfer->set_asset_id(0x00); + transfer->set_fee_asset_id(0x00); // native token + + auto publicKey = parse_hex("81f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c91"); + auto signature = parse_hex("c4f7cb46605986ff6dd1a192736feddd8ae468a10b1b458eadfa855ed6b59ad442a96c18e7109ad594d11ba2fd52920545f8a450234e9b03ee3e8f59a8f06f00"); + auto encoded = Signer::encodeTransaction(input, publicKey, signature); + ASSERT_EQ(hex(encoded), "3902840081f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c9100c4f7cb46605986ff6dd1a192736feddd8ae468a10b1b458eadfa855ed6b59ad442a96c18e7109ad594d11ba2fd52920545f8a450234e9b03ee3e8f59a8f06f00001c00000a030050e47b3c8aef60bc4fc744d8d979cb0eb2d45fa25c2e9da74e1e5ebd9e117518821a0600"); +} + +TEST(SubstrateSigner, Statemint_encodeTransaction_usdt_transfer_keep_alive) { + // tx on mainnet + // https://statemint.subscan.io/extrinsic/2789377-2 + + Substrate::Proto::SigningInput input; + input.set_network(0); + input.set_multi_address(true); + auto blockHash = parse_hex("68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f"); + auto genesisHash = parse_hex("68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f"); + input.set_block_hash(std::string(blockHash.begin(), blockHash.end())); + input.set_genesis_hash(std::string(genesisHash.begin(), genesisHash.end())); + input.set_nonce(8ul); + input.set_spec_version(9320u); + input.set_transaction_version(9u); + + auto* transfer = input.mutable_balance_call()->mutable_asset_transfer(); + transfer->set_module_index(0x32); + transfer->set_method_index(0x06); + transfer->set_to_address("12q4hq1dgqHZVGzHbwZmqq1cFwatN15Visfd7YmUiMB5ZWkH"); + + auto value = store(100000); + transfer->set_value(std::string(value.begin(), value.end())); + transfer->set_asset_id(1984); + transfer->set_fee_asset_id(1984); + + auto publicKey = parse_hex("81f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c91"); + auto signature = parse_hex("d22583408806c005a24caf16f2084691f4c6dcb6015e6645adc86fc1474369b0e0b7dbcc0ef25b17eae43844aff6fb42a0b279a19e822c76043cac015be5e40a"); + auto encoded = Signer::encodeTransaction(input, publicKey, signature); + ASSERT_EQ(hex(encoded), "5102840081f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c9100d22583408806c005a24caf16f2084691f4c6dcb6015e6645adc86fc1474369b0e0b7dbcc0ef25b17eae43844aff6fb42a0b279a19e822c76043cac015be5e40a00200001c00700003206011f0050e47b3c8aef60bc4fc744d8d979cb0eb2d45fa25c2e9da74e1e5ebd9e117518821a0600"); +} + +} // namespace TW::Substrate::tests \ No newline at end of file diff --git a/tests/chains/Substrate/TWSubstrateAddressTests.cpp b/tests/chains/Substrate/TWSubstrateAddressTests.cpp new file mode 100644 index 00000000000..8d8e8cb05d0 --- /dev/null +++ b/tests/chains/Substrate/TWSubstrateAddressTests.cpp @@ -0,0 +1,60 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "TestUtilities.h" + +#include "Data.h" +#include "HexCoding.h" +#include +#include +#include + +#include + +namespace TW::Substrate::tests { + +const char* validAddrStr1 = "14KjL5vGAYJCbKgZJmFKDSjewtBpvaxx9YvRZvi7qmb5s8CC"; +const char* publicKeyDataStr1 = "92fd9c237030356e26cfcc4568dc71055d5ec92dfe0ff903767e00611971bad3"; +const char* invalidAddrStr1 = "12345678"; + +TEST(TWSubstrateAddress, CreateAndDelete) { + { + TWSubstrateAddress* addr = TWSubstrateAddressCreateWithString(STRING(validAddrStr1).get(), 0); + EXPECT_TRUE(addr != nullptr); + TWSubstrateAddressDelete(addr); + } + { + auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA(publicKeyDataStr1).get(), TWPublicKeyTypeED25519)); + TWSubstrateAddress* addr = TWSubstrateAddressCreateWithPublicKey(publicKey.get(), 0); + EXPECT_TRUE(addr != nullptr); + TWSubstrateAddressDelete(addr); + } +} + +TEST(TWSubstrateAddress, AddressEqual) { + auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA(publicKeyDataStr1).get(), TWPublicKeyTypeED25519)); + auto addr1 = WRAP(TWSubstrateAddress, TWSubstrateAddressCreateWithPublicKey(publicKey.get(), 0)); + EXPECT_TRUE(addr1.get() != nullptr); + + auto addr2 = WRAP(TWSubstrateAddress, TWSubstrateAddressCreateWithString(STRING(validAddrStr1).get(), 0)); + EXPECT_TRUE(addr2.get() != nullptr); + ASSERT_TRUE(TWSubstrateAddressEqual(addr1.get(), addr2.get())); +} + +TEST(TWSubstrateAddress, IsValidString) { + ASSERT_TRUE(TWSubstrateAddressIsValidString(STRING(validAddrStr1).get(), 0)); + ASSERT_FALSE(TWSubstrateAddressIsValidString(STRING(invalidAddrStr1).get(), 0)); +} + +TEST(TWSubstrateAddress, AddressDescription) { + + auto addr1 = WRAP(TWSubstrateAddress, TWSubstrateAddressCreateWithString(STRING(validAddrStr1).get(), 0)); + EXPECT_TRUE(addr1.get() != nullptr); + auto addrStr1 = std::string(TWStringUTF8Bytes(WRAPS(TWSubstrateAddressDescription(addr1.get())).get())); + EXPECT_TRUE(addrStr1 == validAddrStr1); +} + +} // namespace TW::Substrate::tests \ No newline at end of file diff --git a/tests/chains/Syscoin/TWCoinTypeTests.cpp b/tests/chains/Syscoin/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..484ced710d5 --- /dev/null +++ b/tests/chains/Syscoin/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWSyscoinCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeSyscoin)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("19e043f76f6ffc960f5fe93ecec37bc37a58ae7525d7e9cd6ed40f71f0da60eb")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeSyscoin, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("sys1qh3gvhnzq2ch7w8g04x8zksr2mz7r90x7ksmu40")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeSyscoin, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeSyscoin)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeSyscoin)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeSyscoin), 8); + ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeSyscoin)); + ASSERT_EQ(0x5, TWCoinTypeP2shPrefix(TWCoinTypeSyscoin)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeSyscoin)); + assertStringsEqual(symbol, "SYS"); + assertStringsEqual(txUrl, "https://sys1.bcfn.ca/tx/19e043f76f6ffc960f5fe93ecec37bc37a58ae7525d7e9cd6ed40f71f0da60eb"); + assertStringsEqual(accUrl, "https://sys1.bcfn.ca/address/sys1qh3gvhnzq2ch7w8g04x8zksr2mz7r90x7ksmu40"); + assertStringsEqual(id, "syscoin"); + assertStringsEqual(name, "Syscoin"); +} diff --git a/tests/chains/Syscoin/TWSyscoinTests.cpp b/tests/chains/Syscoin/TWSyscoinTests.cpp new file mode 100644 index 00000000000..8c8419d7b6d --- /dev/null +++ b/tests/chains/Syscoin/TWSyscoinTests.cpp @@ -0,0 +1,87 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "TestUtilities.h" + +#include +#include +#include +#include +#include +#include + +#include + +TEST(Syscoin, LegacyAddress) { + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730").get())); + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); + auto address = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeSyscoin))); + auto addressString = WRAPS(TWBitcoinAddressDescription(address.get())); + assertStringsEqual(addressString, "SXBPFk2PFDAP13qyKSdC4yptsJ8kJRAT3T"); +} + +TEST(Syscoin, Address) { + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625").get())); + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); + auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeSyscoin)); + auto string = WRAPS(TWAnyAddressDescription(address.get())); + + assertStringsEqual(string, "sys1qytnqzjknvv03jwfgrsmzt0ycmwqgl0as7lkcf7"); +} + +TEST(Syscoin, LockScriptForLegacyAddress) { + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("SXBPFk2PFDAP13qyKSdC4yptsJ8kJRAT3T").get(), TWCoinTypeSyscoin)); + auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); + assertHexEqual(scriptData, "76a9146c70e57df7b18eeb0198be9e254737ecd336ed8888ac"); +} + +TEST(Syscoin, LockScriptForAddress) { + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("sys1qytnqzjknvv03jwfgrsmzt0ycmwqgl0as7lkcf7").get(), TWCoinTypeSyscoin)); + auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); + assertHexEqual(scriptData, "001422e6014ad3631f1939281c3625bc98db808fbfb0"); +} + +TEST(Syscoin, ExtendedKeys) { + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic( + STRING("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal").get(), + STRING("TREZOR").get() + )); + + // .bip44 + auto xprv = WRAPS(TWHDWalletGetExtendedPrivateKey(wallet.get(), TWPurposeBIP44, TWCoinTypeSyscoin, TWHDVersionXPRV)); + auto xpub = WRAPS(TWHDWalletGetExtendedPublicKey(wallet.get(), TWPurposeBIP44, TWCoinTypeSyscoin, TWHDVersionXPUB)); + + assertStringsEqual(xprv, "xprv9yFNgN7z81uG6QtwFt7gvbmLeDGeGfS2ar3DunwEkZcC7uLBXyy4eaaV3ir769zMLe3eHuTaGUtWVXwp6dkunLsfmA7bf3XqEFpTjHxSijx"); + assertStringsEqual(xpub, "xpub6CEj5sesxPTZJtyQMuehHji5CF78g89sx4xpiBLrJu9AzhfL5XHKCNtxtzPzyGxqb6jMbZfmbHeSGZZArL4hLttmdC6KEMuiWy7VocTYjzR"); + + // .bip49 + auto yprv = WRAPS(TWHDWalletGetExtendedPrivateKey(wallet.get(), TWPurposeBIP49, TWCoinTypeSyscoin, TWHDVersionYPRV)); + auto ypub = WRAPS(TWHDWalletGetExtendedPublicKey(wallet.get(), TWPurposeBIP49, TWCoinTypeSyscoin, TWHDVersionYPUB)); + + assertStringsEqual(yprv, "yprvAJAofBFEEQ1DLJJVMkPr4pufHLUKZ2VSbtHqPpphEgwgfvG8exgadM8vtW8AW52N7tqU4qM8JHk5xZkq3icnzoph5QA5kRVHBnhXuRMGw2b"); + assertStringsEqual(ypub, "ypub6XAA4gn84mZWYnNxTmvrRxrPqNJoxVDHy7DSCDEJo2UfYibHCVzqB9TQjmL2TKSEZVFmTNcmdJXunEu6oV2AFQNeiszjzcnX4nbG27s4SgS"); + + // .bip84 + auto zprv = WRAPS(TWHDWalletGetExtendedPrivateKey(wallet.get(), TWPurposeBIP84, TWCoinTypeSyscoin, TWHDVersionZPRV)); + auto zpub = WRAPS(TWHDWalletGetExtendedPublicKey(wallet.get(), TWPurposeBIP84, TWCoinTypeSyscoin, TWHDVersionZPUB)); + assertStringsEqual(zprv, "zprvAcdCiLx9ooAFnC1hXh7stnobLnnu7u25rqfLeJ9v632xdCXJrc8KvgNk2eZeQQbPQHvcUpsfJzgyDkRdfnkT6vjpYqkxFv1LsPxQ7uFwLGy"); + assertStringsEqual(zpub, "zpub6qcZ7rV3eAiYzg6AdietFvkKtpdPXMjwE4awSgZXeNZwVzrTQ9SaUUhDswmdA4A5riyimx322es7niQvJ1Fbi3mJNSVz3d3f9GBsYBb8Wky"); +} + +TEST(Syscoin, DeriveFromZpub) { + auto zpub = STRING("zpub6sCFp8chadVDXVt7GRmQFpq8B7W8wMLdFDto1hXu2jLZtvkFhRnwScXARNfrGSeyhR8DBLJnaUUkBbkmB2GwUYkecEAMUcbUpFQV4v7PXcs"); + auto pubKey4 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeSyscoin, STRING("m/44'/2'/0'/0/4").get())); + auto pubKey11 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeSyscoin, STRING("m/44'/2'/0'/0/11").get())); + + auto address4 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey4.get(), TWCoinTypeSyscoin)); + auto address4String = WRAPS(TWAnyAddressDescription(address4.get())); + + auto address11 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey11.get(), TWCoinTypeSyscoin)); + auto address11String = WRAPS(TWAnyAddressDescription(address11.get())); + + assertStringsEqual(address4String, "sys1qcgnevr9rp7aazy62m4gen0tfzlssa52a2z04vc"); + assertStringsEqual(address11String, "sys1qy072y8968nzp6mz3j292h8lp72d678fchhmyta"); +} diff --git a/tests/chains/TBinance/AddressTests.cpp b/tests/chains/TBinance/AddressTests.cpp new file mode 100644 index 00000000000..259a0f9505a --- /dev/null +++ b/tests/chains/TBinance/AddressTests.cpp @@ -0,0 +1,42 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "HexCoding.h" +#include "Binance/Address.h" +#include "PublicKey.h" +#include "PrivateKey.h" +#include +#include + +using namespace TW; +using namespace TW::Binance; + +TEST(TBinanceAddress, Valid) { + ASSERT_TRUE(Address::isValid(TWCoinTypeTBinance, "tbnb18mtcq20875cr0p7l4upm0u5zx4r9jpj2kfu9f8")); +} + +TEST(TBinanceAddress, Invalid) { + ASSERT_FALSE(Address::isValid(TWCoinTypeTBinance, "tbnb18mtcq20875cr0p7l4upm0u5zx4r9jpj2kfu9aa")); + ASSERT_FALSE(Address::isValid(TWCoinTypeTBinance, "bnb1h8xf9htasu9aclra954dnnve8fgcda4ae7qfa8")); +} + +TEST(TBinanceAddress, FromPrivateKey) { + auto privateKey = PrivateKey(parse_hex("fc75070f2d9939be82a378ec9a47912cb6df458055f51da022021f42a6bb5ee8")); + auto address = Address(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1), TWCoinTypeTBinance); + ASSERT_EQ(address.string(), "tbnb18mtcq20875cr0p7l4upm0u5zx4r9jpj2kfu9f8"); +} + +TEST(TBinanceAddress, FromPublicKey) { + auto publicKey = PublicKey(parse_hex("02d32c10f9f4b72d0213123d58257e0558e164e5373e719aee73ce5505852c1692"), TWPublicKeyTypeSECP256k1); + auto address = Address(publicKey, TWCoinTypeTBinance); + ASSERT_EQ(address.string(), "tbnb18mtcq20875cr0p7l4upm0u5zx4r9jpj2kfu9f8"); +} + +TEST(TBinanceAddress, FromString) { + Address address(TWCoinTypeTBinance); + Address::decode("tbnb18mtcq20875cr0p7l4upm0u5zx4r9jpj2kfu9f8", address); + ASSERT_EQ(address.string(), "tbnb18mtcq20875cr0p7l4upm0u5zx4r9jpj2kfu9f8"); +} diff --git a/tests/chains/TBinance/TWAnyAddressTests.cpp b/tests/chains/TBinance/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..8d88ba5e971 --- /dev/null +++ b/tests/chains/TBinance/TWAnyAddressTests.cpp @@ -0,0 +1,22 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TWTBinance, Address) { + auto string = STRING("tbnb18mtcq20875cr0p7l4upm0u5zx4r9jpj2kfu9f8"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeTBinance)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "3ed78029e7f5303787dfaf03b7f282354659064a"); +} diff --git a/tests/chains/TBinance/TWCoinTypeTests.cpp b/tests/chains/TBinance/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..564992756e3 --- /dev/null +++ b/tests/chains/TBinance/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWTBinanceCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeTBinance)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("92E9DA1B6D603667E2DE83C0AC0C1D9E6D65405AD642DA794421C64A82A078D0")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeTBinance, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("tbnb1c2cxgv3cklswxlvqr9anm6mlp6536qnd36txgr")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeTBinance, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeTBinance)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeTBinance)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeTBinance), 8); + ASSERT_EQ(TWBlockchainBinance, TWCoinTypeBlockchain(TWCoinTypeTBinance)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeTBinance)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeTBinance)); + assertStringsEqual(symbol, "BNB"); + assertStringsEqual(txUrl, "https://testnet-explorer.binance.org/tx/92E9DA1B6D603667E2DE83C0AC0C1D9E6D65405AD642DA794421C64A82A078D0"); + assertStringsEqual(accUrl, "https://testnet-explorer.binance.org/address/tbnb1c2cxgv3cklswxlvqr9anm6mlp6536qnd36txgr"); + assertStringsEqual(id, "tbinance"); + assertStringsEqual(name, "TBNB"); +} diff --git a/tests/chains/Tezos/AddressTests.cpp b/tests/chains/Tezos/AddressTests.cpp index adb6fb3f606..62d0a8d4801 100644 --- a/tests/chains/Tezos/AddressTests.cpp +++ b/tests/chains/Tezos/AddressTests.cpp @@ -22,21 +22,28 @@ namespace TW::Tezos::tests { TEST(TezosAddress, forge_tz1) { auto input = Address("tz1eZwq8b5cvE2bPKokatLkVMzkxz24z3Don"); - auto expected = "00cfa4aae60f5d9389752d41e320da224d43287fe2"; + auto expected = "0000cfa4aae60f5d9389752d41e320da224d43287fe2"; ASSERT_EQ(input.forge(), parse_hex(expected)); } TEST(TezosAddress, forge_tz2) { auto input = Address("tz2Rh3NYeLxrqTuvaZJmaMiVMqCajeXMWtYo"); - auto expected = "01be99dd914e38388ec80432818b517759e3524f16"; + auto expected = "0001be99dd914e38388ec80432818b517759e3524f16"; ASSERT_EQ(input.forge(), parse_hex(expected)); } TEST(TezosAddress, forge_tz3) { auto input = Address("tz3RDC3Jdn4j15J7bBHZd29EUee9gVB1CxD9"); - auto expected = "02358cbffa97149631cfb999fa47f0035fb1ea8636"; + auto expected = "0002358cbffa97149631cfb999fa47f0035fb1ea8636"; + + ASSERT_EQ(input.forge(), parse_hex(expected)); +} + +TEST(TezosAddress, forge_kt1) { + auto input = Address("KT1XnTn74bUtxHfDtBmm2bGZAQfhPbvKWR8o"); + auto expected = "01fe810959c3d6127a41cbd471e7cb4e91a61b780b00"; ASSERT_EQ(input.forge(), parse_hex(expected)); } @@ -54,10 +61,12 @@ TEST(TezosAddress, isInvalid) { } TEST(TezosAddress, isValid) { - std::array validAddresses{ + std::array validAddresses { "tz1Yju7jmmsaUiG9qQLoYv35v5pHgnWoLWbt", "tz2PdGc7U5tiyqPgTSgqCDct94qd6ovQwP6u", - "tz3VEZ4k6a4Wx42iyev6i2aVAptTRLEAivNN"}; + "tz3VEZ4k6a4Wx42iyev6i2aVAptTRLEAivNN", + "KT1XnTn74bUtxHfDtBmm2bGZAQfhPbvKWR8o", + }; for (auto& address : validAddresses) { ASSERT_TRUE(Address::isValid(address)); diff --git a/tests/chains/Tezos/ForgingTests.cpp b/tests/chains/Tezos/ForgingTests.cpp index 8571152dbe2..fb50b907dca 100644 --- a/tests/chains/Tezos/ForgingTests.cpp +++ b/tests/chains/Tezos/ForgingTests.cpp @@ -99,7 +99,7 @@ TEST(Forging, forge_tz3) { ASSERT_EQ(output, parse_hex(expected)); } -TEST(Forging, ForgePublicKey) { +TEST(Forging, ForgeED25519PublicKey) { auto expected = "00311f002e899cdd9a52d96cb8be18ea2bbab867c505da2b44ce10906f511cff95"; auto privateKey = PrivateKey(parse_hex("c6377a4cc490dc913fc3f0d9cf67d293a32df4547c46cb7e9e33c3b7b97c64d8")); @@ -133,6 +133,15 @@ TEST(Forging, ForgeMichelsonFA12) { auto v = FA12ParameterToMichelson(data); ASSERT_EQ(hex(forgeMichelson(v)), "07070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555007070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555000bb01"); } +TEST(Forging, ForgeSECP256k1PublicKey) { + auto expected = "0102b4ac9056d20c52ac11b0d7e83715dd3eac851cfc9cb64b8546d9ea0d4bb3bdfe"; + + auto privateKey = PrivateKey(parse_hex("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a")); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + auto output = forgePublicKey(publicKey); + + ASSERT_EQ(hex(output), expected); +} TEST(TezosTransaction, forgeTransaction) { auto transactionOperationData = new TW::Tezos::Proto::TransactionOperationData(); diff --git a/tests/chains/Tezos/TransactionCompilerTests.cpp b/tests/chains/Tezos/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..b9e5836ecff --- /dev/null +++ b/tests/chains/Tezos/TransactionCompilerTests.cpp @@ -0,0 +1,113 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Tezos.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TezosCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeTezos; + + /// Step 1: Prepare transaction input (protobuf) + auto privateKey = + PrivateKey(parse_hex("2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6f")); + auto publicKey = privateKey.getPublicKey(::publicKeyType(coin)); + auto revealKey = parse_hex("311f002e899cdd9a52d96cb8be18ea2bbab867c505da2b44ce10906f511cff95"); + + TW::Tezos::Proto::SigningInput input; + auto& operations = *input.mutable_operation_list(); + operations.set_branch("BL8euoCWqNCny9AR3AKjnpi38haYMxjei1ZqNHuXMn19JSQnoWp"); + + auto& reveal = *operations.add_operations(); + auto& revealData = *reveal.mutable_reveal_operation_data(); + revealData.set_public_key(revealKey.data(), revealKey.size()); + reveal.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); + reveal.set_fee(1272); + reveal.set_counter(30738); + reveal.set_gas_limit(10100); + reveal.set_storage_limit(257); + reveal.set_kind(Tezos::Proto::Operation::REVEAL); + + auto& transaction = *operations.add_operations(); + auto& txData = *transaction.mutable_transaction_operation_data(); + txData.set_amount(1); + txData.set_destination("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); + transaction.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); + transaction.set_fee(1272); + transaction.set_counter(30739); + transaction.set_gas_limit(10100); + transaction.set_storage_limit(257); + transaction.set_kind(Tezos::Proto::Operation::TRANSACTION); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, inputStrData); + + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), + "12e4f8b17ad3b316a5a56960db76c7d6505dbf2fff66106be75c8d6753daac0e"); + + auto signature = parse_hex("0217034271b815e5f0c0a881342838ce49d7b48cdf507c72b1568c69a10db70c987" + "74cdad1a74df760763e25f760ff13afcbbf3a1f2c833a0beeb9576a579c05"); + + /// Step 3: Compile transaction info + const auto tx = + "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016b0081faa75f741ef614b0e35f" + "cc8c90dfa3b0b95721f80992f001f44e810200311f002e899cdd9a52d96cb8be18ea2bbab867c505da2b44ce10" + "906f511cff956c0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80993f001f44e810201000081faa75f74" + "1ef614b0e35fcc8c90dfa3b0b95721000217034271b815e5f0c0a881342838ce49d7b48cdf507c72b1568c69a1" + "0db70c98774cdad1a74df760763e25f760ff13afcbbf3a1f2c833a0beeb9576a579c05"; + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {}); + + { + TW::Tezos::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(hex(output.encoded()), tx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::Tezos::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + signingInput.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + TW::Tezos::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.encoded()), tx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, signature}, {}); + Tezos::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_signatures_count); + } +} \ No newline at end of file diff --git a/tests/chains/Theta/TransactionCompilerTests.cpp b/tests/chains/Theta/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..677c63b9c9d --- /dev/null +++ b/tests/chains/Theta/TransactionCompilerTests.cpp @@ -0,0 +1,99 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" +#include "uint256.h" + +#include "proto/Theta.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(ThetaCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeTheta; + /// Step 1: Prepare transaction input (protobuf) + const auto pkFrom = + PrivateKey(parse_hex("0x93a90ea508331dfdf27fb79757d4250b4e84954927ba0073cd67454ac432c737")); + const auto publicKey = pkFrom.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + TW::Theta::Proto::SigningInput input; + input.set_chain_id("privatenet"); + input.set_to_address("0x9F1233798E905E173560071255140b4A8aBd3Ec6"); + auto amount = store(uint256_t(10)); + input.set_theta_amount(amount.data(), amount.size()); + auto tfuelAmount = store(uint256_t(20)); + input.set_tfuel_amount(tfuelAmount.data(), tfuelAmount.size()); + auto fee = store(uint256_t(1000000000000)); + input.set_fee(fee.data(), fee.size()); + input.set_sequence(1); + std::string pubkeyStr(publicKey.bytes.begin(), publicKey.bytes.end()); + input.set_public_key(pubkeyStr); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, inputStrData); + + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + EXPECT_EQ(hex(preSigningOutput.data_hash()), + "2dc419e9919e65f129453419dc72a6bee99b2281dfddf754807a5c212ae35678"); + + // Simulate signature, normally obtained from signature server + const auto publicKeyData = publicKey.bytes; + const auto signature = + parse_hex("5190868498d587d074d57298f41853d0109d997f15ddf617f471eb8cbb7fff267cb8fe9134ccdef0" + "53ec7cabd18070325c9c436efe1abbacd14eb7561d3fc10501"); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preSigningOutput.data_hash()))); + + /// Step 3: Compile transaction info + auto expectedTx = "02f887c78085e8d4a51000f863f861942e833968e5bb786ae419c4d13189fb081cc43babc70a" + "85e8d4a5101401b8415190868498d587d074d57298f41853d0109d997f15ddf617f471eb8cbb" + "7fff267cb8fe9134ccdef053ec7cabd18070325c9c436efe1abbacd14eb7561d3fc10501d9d8" + "949f1233798e905e173560071255140b4a8abd3ec6c20a14"; + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKeyData}); + + { + Theta::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(hex(output.encoded()), expectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + Theta::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + signingInput.set_private_key(pkFrom.bytes.data(), pkFrom.bytes.size()); + + Theta::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.encoded()), expectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, signature}, {publicKeyData}); + Theta::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } +} diff --git a/tests/chains/Tron/AddressTests.cpp b/tests/chains/Tron/AddressTests.cpp index 9ddf01cd19e..c250ff1d07a 100644 --- a/tests/chains/Tron/AddressTests.cpp +++ b/tests/chains/Tron/AddressTests.cpp @@ -4,34 +4,36 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "Tron/Address.h" #include "HexCoding.h" #include "PrivateKey.h" +#include "Tron/Address.h" #include namespace TW::Tron { -TEST(TronAddress, FromPrivateKey) { - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); +TEST(TronAddress, FromPublicKey) { + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); const auto address = Address(publicKey); - ASSERT_EQ(address.string(), "TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); -} -TEST(TronAddress, FromPublicKey) { - const auto privateKey = PrivateKey(parse_hex("BE88DF1D0BF30A923CB39C3BB953178BAAF3726E8D3CE81E7C8462E046E0D835")); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); - const auto address = Address(publicKey); + const auto privateKey2 = PrivateKey(parse_hex("BE88DF1D0BF30A923CB39C3BB953178BAAF3726E8D3CE81E7C8462E046E0D835")); + const auto publicKey2 = privateKey2.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + const auto address2 = Address(publicKey2); + ASSERT_EQ(address2.string(), "THRF3GuPnvvPzKoaT8pJex5XHmo8NNbCb3"); - ASSERT_EQ(address.string(), "THRF3GuPnvvPzKoaT8pJex5XHmo8NNbCb3"); + const auto privateKey3 = PrivateKey(parse_hex("BE88DF1D0BF30A923CB39C3BB953178BAAF3726E8D3CE81E7C8462E046E0D835")); + const auto publicKey3 = privateKey3.getPublicKey(TWPublicKeyTypeED25519); + EXPECT_ANY_THROW(new Address(publicKey3)); } TEST(TronAddress, Invalid) { ASSERT_FALSE(Address::isValid(std::string("abc"))); ASSERT_FALSE(Address::isValid(std::string("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"))); ASSERT_FALSE(Address::isValid(std::string("175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W"))); + ASSERT_FALSE(Address::isValid(std::string("2MegQ6oqSda2tTagdEzBA"))); + ASSERT_TRUE(Address::isValid(std::string("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"))); } TEST(TronAddress, InitWithString) { diff --git a/tests/chains/Tron/TransactionCompilerTests.cpp b/tests/chains/Tron/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..117bce53cf2 --- /dev/null +++ b/tests/chains/Tron/TransactionCompilerTests.cpp @@ -0,0 +1,112 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/TransactionCompiler.pb.h" +#include "proto/Tron.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TronCompiler, CompileWithSignatures) { + const auto privateKey = + PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + const auto coin = TWCoinTypeTron; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::Tron::Proto::SigningInput(); + auto& transaction = *input.mutable_transaction(); + + auto& transfer = *transaction.mutable_transfer_asset(); + transfer.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); + transfer.set_to_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); + transfer.set_amount(4); + transfer.set_asset_name("1000959"); + transaction.set_timestamp(1539295479000); + transaction.set_expiration(1541890116000 + 10 * 60 * 60 * 1000); + + auto& blockHeader = *transaction.mutable_block_header(); + blockHeader.set_timestamp(1541890116000); + const auto txTrieRoot = + parse_hex("845ab51bf63c2c21ee71a4dc0ac3781619f07a7cd05e1e0bd8ba828979332ffa"); + blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); + const auto parentHash = + parse_hex("00000000003cb800a7e69e9144e3d16f0cf33f33a95c7ce274097822c67243c1"); + blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); + blockHeader.set_number(3979265); + const auto witnessAddress = parse_hex("41b487cdc02de90f15ac89a68c82f44cbfe3d915ea"); + blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); + blockHeader.set_version(3); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + preSigningOutput.ParseFromArray(preImageHashesData.data(), (int)preImageHashesData.size()); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), + "546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb"); + auto signature = parse_hex("77f5eabde31e739d34a66914540f1756981dc7d782c9656f5e14e53b59a15371603" + "a183aa12124adeee7991bf55acc8e488a6ca04fb393b1a8ac16610eeafdfc00"); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preSigningOutput.data_hash()))); + /// Step 3: Compile transaction info + const auto tx = + "{\"raw_data\":{\"contract\":[{\"parameter\":{\"type_url\":\"type.googleapis.com/" + "protocol.TransferAssetContract\",\"value\":{\"amount\":4,\"asset_name\":" + "\"31303030393539\",\"owner_address\":\"415cd0fb0ab3ce40f3051414c604b27756e69e43db\",\"to_" + "address\":\"41521ea197907927725ef36d70f25f850d1659c7c7\"}},\"type\":" + "\"TransferAssetContract\"}],\"expiration\":1541926116000,\"ref_block_bytes\":\"b801\"," + "\"ref_block_hash\":\"0e2bc08d550f5f58\",\"timestamp\":1539295479000},\"signature\":[" + "\"77f5eabde31e739d34a66914540f1756981dc7d782c9656f5e14e53b59a15371603a183aa12124adeee7991b" + "f55acc8e488a6ca04fb393b1a8ac16610eeafdfc00\"],\"txID\":" + "\"546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb\"}"; + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKey.bytes}); + + { + TW::Tron::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.json(), tx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::Tron::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + signingInput.set_private_key(privateKey.bytes.data(), 32); + + TW::Tron::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.json(), tx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, signature}, {publicKey.bytes}); + Tron::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.json().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } +} diff --git a/tests/chains/VeChain/TransactionCompilerTests.cpp b/tests/chains/VeChain/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..e9745a8ee81 --- /dev/null +++ b/tests/chains/VeChain/TransactionCompilerTests.cpp @@ -0,0 +1,97 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/TransactionCompiler.pb.h" +#include "proto/VeChain.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(VechainCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeVeChain; + + /// Step 1: Prepare transaction input (protobuf) + TW::VeChain::Proto::SigningInput input; + PrivateKey privateKey = + PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + auto publicKey = privateKey.getPublicKey(publicKeyType(coin)); + + input.set_chain_tag(1); + input.set_block_ref(1); + input.set_expiration(1); + input.set_gas_price_coef(0); + input.set_gas(21000); + input.set_nonce(1); + + auto& clause = *input.add_clauses(); + auto amount = parse_hex("31303030"); // 1000 + clause.set_to("0x3535353535353535353535353535353535353535"); + clause.set_value(amount.data(), amount.size()); + + auto stringInput = input.SerializeAsString(); + auto dataInput = TW::Data(stringInput.begin(), stringInput.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, dataInput); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImage = preSigningOutput.data(); + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImage), + "e7010101dcdb943535353535353535353535353535353535353535843130303080808252088001c0"); + EXPECT_EQ(hex(preImageHash), + "a1b8ef3af3d8c74e97ac6cd732916a8f4c38c0905c8b70d2fa598edf1f62ea04"); + + /// Step 3: Sign + TW::Data signature; + { + TW::VeChain::Proto::SigningOutput output; + ANY_SIGN(input, coin); + ASSERT_EQ(hex(output.encoded()), + "f86a010101dcdb943535353535353535353535353535353535353535843130303080808252088001c0b8" + "41bf8edf9600e645b5abd677cb52f585e7f655d1361075d511b37f707a9f31da6702d28739933b264527" + "a1d05b046f5b74044b88c30c3f5a09d616bd7a4af4901601"); + + signature = data(output.signature()); + /// Step 4: Verify signature + ASSERT_TRUE(publicKey.verify(signature, data(preImageHash.data()))); + } + { + const Data outputData = + TransactionCompiler::compileWithSignatures(coin, dataInput, {signature}, {publicKey.bytes}); + + TW::VeChain::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + ASSERT_EQ(hex(output.encoded()), + "f86a010101dcdb943535353535353535353535353535353535353535843130303080808252088001c0b8" + "41bf8edf9600e645b5abd677cb52f585e7f655d1361075d511b37f707a9f31da6702d28739933b264527" + "a1d05b046f5b74044b88c30c3f5a09d616bd7a4af4901601"); + } + + { // Negative: more than one signatures + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, dataInput, {signature, signature}, {publicKey.bytes}); + VeChain::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } +} diff --git a/tests/chains/Verge/AddressTests.cpp b/tests/chains/Verge/AddressTests.cpp new file mode 100644 index 00000000000..17b54ab2ef9 --- /dev/null +++ b/tests/chains/Verge/AddressTests.cpp @@ -0,0 +1,62 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "Bitcoin/Address.h" +#include "PublicKey.h" +#include "PrivateKey.h" +#include +#include + +#include + +using namespace TW; +using namespace TW::Bitcoin; + +TEST(VergeAddress, Valid) { + ASSERT_TRUE(Address::isValid(std::string("DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E"))); + ASSERT_TRUE(TW::validateAddress(TWCoinTypeVerge, "DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E")); + ASSERT_TRUE(TW::validateAddress(TWCoinTypeVerge, "vg1qujpe553lzgyg95g7k0w6zwscuy0ae022h4q4zg")); +} + +TEST(VergeAddress, Invalid) { + ASSERT_FALSE(Address::isValid(std::string("DRyNFvJaybnF22UfMS6NR1Qav3mqxPj234"))); +} + +TEST(VergeAddress, FromPrivateKey) { + auto privateKey = PrivateKey(parse_hex("693dfe6f3ed717573eb10c24ebe5eb592fa3c239245cd499c487eb7b8ea7ed3a")); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + auto address = Address(publicKey, TWCoinTypeP2pkhPrefix(TWCoinTypeVerge)); + ASSERT_EQ(address.string(), "DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E"); + + auto addr = TW::deriveAddress(TWCoinTypeVerge, publicKey, TWDerivationBitcoinSegwit); + ASSERT_EQ(addr, "vg1qujpe553lzgyg95g7k0w6zwscuy0ae022h4q4zg"); +} + +TEST(VergeAddress, FromPublicKey) { + auto publicKey = PublicKey(parse_hex("034f3eb727ca1eba84a0d22839a483a1120ee6a1da0d5087dde527b5ff912c1694"), TWPublicKeyTypeSECP256k1); + auto address = Address(publicKey, TWCoinTypeP2pkhPrefix(TWCoinTypeVerge)); + ASSERT_EQ(address.string(), "D8rBdwBfz5wvLhmHvRkXnNzeeihQgxkLmL"); + + auto addr = TW::deriveAddress(TWCoinTypeVerge, publicKey, TWDerivationBitcoinLegacy); + ASSERT_EQ(addr, "D8rBdwBfz5wvLhmHvRkXnNzeeihQgxkLmL"); +} + +TEST(VergeAddress, FromString) { + auto address = Address("DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E"); + ASSERT_EQ(address.string(), "DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E"); + + auto data = TW::addressToData(TWCoinTypeVerge, "DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E"); + EXPECT_EQ(hex(data), "e4839a523f120882d11eb3dda13a18e11fdcbd4a"); + + data = TW::addressToData(TWCoinTypeVerge, "vg1qujpe553lzgyg95g7k0w6zwscuy0ae022h4q4zg"); + EXPECT_EQ(hex(data), "e4839a523f120882d11eb3dda13a18e11fdcbd4a"); + + // invalid address + data = TW::addressToData(TWCoinTypeVerge, "vg1qujpe553lzgyg95g7k0w6zwscuy0ae022h4q4"); + EXPECT_EQ(data.size(), 0ul); +} diff --git a/tests/chains/Verge/SignerTests.cpp b/tests/chains/Verge/SignerTests.cpp new file mode 100644 index 00000000000..cce6831c583 --- /dev/null +++ b/tests/chains/Verge/SignerTests.cpp @@ -0,0 +1,201 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Verge/Signer.h" +#include "Verge/Transaction.h" +#include "Verge/TransactionBuilder.h" +#include "Bitcoin/Address.h" +#include "Bitcoin/TransactionPlan.h" +#include "Bitcoin/TransactionSigner.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" + +#include + +#include +#include + +using namespace TW; +using namespace TW::Bitcoin; + + +TEST(VergeSigner, Sign) { + const int64_t amount = 1500000000; + const int64_t fee = 2000000; + const std::string toAddress = "DQYMMpqPrnWYZaikKGTQqk5ydUaQw8nkdD"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("DAkEo5pNELZav7MRwBfEwHRG1aChgSUw6c"); + input.set_coin_type(TWCoinTypeVerge); + input.set_time(1656474580); + + auto txHash0 = parse_hex("a5a6e147da0f1b3f6dfd1081f91b0c6e31f030ae66c4be4cf4b0db0ac8b2407d"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(4294967294); + utxo0->set_amount(2500000000); + + auto utxoKey0 = PrivateKey(parse_hex("693dfe6f3ed717573eb10c24ebe5eb592fa3c239245cd499c487eb7b8ea7ed3a")); + auto script0 = Bitcoin::Script::lockScriptForAddress("DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E", TWCoinTypeVerge); + ASSERT_EQ(hex(script0.bytes), "76a914e4839a523f120882d11eb3dda13a18e11fdcbd4a88ac"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto plan = TransactionBuilder::plan(input); + plan.amount = amount; + plan.fee = fee; + plan.change = 980000000; + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan.proto(); + + // Sign + auto result = Verge::Signer::sign(input); + + ASSERT_EQ(hex(result.encoded()), + "01000000d4cbbb62017d40b2c80adbb0f44cbec466ae30f0316e0c1bf98110fd6d3f1b0fda47e1a6a5000000006b483045022100bf1d0e5f84e70e699f45dd4822ecdbbfb1687e61ac749354a76f2afa2e13f76602202d4f5cda7177282b58f80163fead42300468670d03c5f4bb1db3b9596f2dcea301210220ee0423797a856fdd2e85876a60bf10f8696e6ae83e744f498f2173237fe23dfeffffff02002f6859000000001976a914d4d05406c3ca73cf887911f80c852a1c0773615088ac009d693a000000001976a9143d7e143a8b3c8a4aa2f51104da380edeb6c3fc2088ac00000000" + ); +} + +TEST(VergeSigner, SignAnyoneCanPay) { + const int64_t amount = 1500000000; + const int64_t fee = 2000000; + const std::string toAddress = "DQYMMpqPrnWYZaikKGTQqk5ydUaQw8nkdD"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAnyoneCanPay|TWBitcoinSigHashTypeSingle); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("DAkEo5pNELZav7MRwBfEwHRG1aChgSUw6c"); + input.set_coin_type(TWCoinTypeVerge); + input.set_time(1656474580); + + auto txHash0 = parse_hex("a5a6e147da0f1b3f6dfd1081f91b0c6e31f030ae66c4be4cf4b0db0ac8b2407d"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(4294967294); + utxo0->set_amount(2500000000); + + auto script0 = Bitcoin::Script::lockScriptForAddress("vg1qujpe553lzgyg95g7k0w6zwscuy0ae022h4q4zg", TWCoinTypeVerge); + EXPECT_EQ(hex(script0.bytes), "0014e4839a523f120882d11eb3dda13a18e11fdcbd4a"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + + auto txHash1 = parse_hex("29bd442521ea303afb09ad2583f589a6527c9218c050882b6b8527bbe4d11766"); + std::reverse(txHash1.begin(), txHash1.end()); + + auto utxo1 = input.add_utxo(); + utxo1->mutable_out_point()->set_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_index(0); + utxo1->mutable_out_point()->set_sequence(4294967294); + utxo1->set_amount(200000000); + + auto script1 = Bitcoin::Script::lockScriptForAddress("DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E", TWCoinTypeVerge); + ASSERT_EQ(hex(script1.bytes), "76a914e4839a523f120882d11eb3dda13a18e11fdcbd4a88ac"); + utxo1->set_script(script1.bytes.data(), script1.bytes.size()); + + auto utxoKey0 = PrivateKey(parse_hex("693dfe6f3ed717573eb10c24ebe5eb592fa3c239245cd499c487eb7b8ea7ed3a")); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto plan = TransactionBuilder::plan(input); + plan.amount = amount; + plan.fee = fee; + plan.change = 1000000000; + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan.proto(); + + // Sign + auto result = Bitcoin::TransactionSigner::sign(input); + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + ASSERT_EQ(hex(serialized), + "01000000d4cbbb620001017d40b2c80adbb0f44cbec466ae30f0316e0c1bf98110fd6d3f1b0fda47e1a6a50000000000feffffff02002f6859000000001976a914d4d05406c3ca73cf887911f80c852a1c0773615088ac00ca9a3b000000001976a9143d7e143a8b3c8a4aa2f51104da380edeb6c3fc2088ac02473044022016413b2d31c16d185cdf7c0ae343b14eee586124a8fa65bfaaec6a35eeb54e13022073e3d73d251d97fd951201ab184cdb101627317866e199ac0963b83b17e5f3bf83210220ee0423797a856fdd2e85876a60bf10f8696e6ae83e744f498f2173237fe23d00000000" + ); +} + +TEST(VergeSigner, SignSegwit) { + const int64_t amount = 1500000000; + const int64_t fee = 2000000; + const std::string toAddress = "DQYMMpqPrnWYZaikKGTQqk5ydUaQw8nkdD"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("DAkEo5pNELZav7MRwBfEwHRG1aChgSUw6c"); + input.set_coin_type(TWCoinTypeVerge); + input.set_time(1656474580); + + auto txHash0 = parse_hex("a5a6e147da0f1b3f6dfd1081f91b0c6e31f030ae66c4be4cf4b0db0ac8b2407d"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(4294967294); + utxo0->set_amount(2500000000); + + auto script0 = Bitcoin::Script::lockScriptForAddress("vg1qujpe553lzgyg95g7k0w6zwscuy0ae022h4q4zg", TWCoinTypeVerge); + EXPECT_EQ(hex(script0.bytes), "0014e4839a523f120882d11eb3dda13a18e11fdcbd4a"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + + auto utxoKey0 = PrivateKey(parse_hex("693dfe6f3ed717573eb10c24ebe5eb592fa3c239245cd499c487eb7b8ea7ed3a")); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto plan = TransactionBuilder::plan(input); + plan.amount = amount; + plan.fee = fee; + plan.change = 980000000; + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan.proto(); + + // Sign + auto result = Verge::Signer::sign(input); + + ASSERT_EQ(hex(result.encoded()), + "01000000d4cbbb620001017d40b2c80adbb0f44cbec466ae30f0316e0c1bf98110fd6d3f1b0fda47e1a6a50000000000feffffff02002f6859000000001976a914d4d05406c3ca73cf887911f80c852a1c0773615088ac009d693a000000001976a9143d7e143a8b3c8a4aa2f51104da380edeb6c3fc2088ac024730440220657132a334ffbb15f6bbcd11da743756534c2c345195e19c007d67224f09703f022036cebc6442e212be80b74d5992cfd70355e603c1e538e84e02fecf49f82f2f8a01210220ee0423797a856fdd2e85876a60bf10f8696e6ae83e744f498f2173237fe23d00000000" + ); +} + +TEST(VergeSigner, SignWithError) { + const int64_t amount = 1500000000; + const std::string toAddress = "DQYMMpqPrnWYZaikKGTQqk5ydUaQw8nkdD"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("DAkEo5pNELZav7MRwBfEwHRG1aChgSUw6c"); + input.set_coin_type(TWCoinTypeVerge); + input.set_time(1656474580); + + // Sign + auto result = Verge::Signer::sign(input); + + ASSERT_NE(result.error(), Common::Proto::OK); + + // PreImage + auto preResult = Verge::Signer::preImageHashes(input); + ASSERT_NE(preResult.error(), Common::Proto::OK); +} \ No newline at end of file diff --git a/tests/chains/Verge/TWAnyAddressTests.cpp b/tests/chains/Verge/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..1ede3bd3b36 --- /dev/null +++ b/tests/chains/Verge/TWAnyAddressTests.cpp @@ -0,0 +1,22 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TWVerge, Address) { + auto string = STRING("DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeVerge)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "e4839a523f120882d11eb3dda13a18e11fdcbd4a"); +} diff --git a/tests/chains/Verge/TWAnySignerTests.cpp b/tests/chains/Verge/TWAnySignerTests.cpp new file mode 100644 index 00000000000..961d262fe26 --- /dev/null +++ b/tests/chains/Verge/TWAnySignerTests.cpp @@ -0,0 +1,76 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include +#include + +#include "Bitcoin/Signer.h" +#include "Bitcoin/Address.h" +#include "Bitcoin/TransactionPlan.h" +#include "Bitcoin/TransactionSigner.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +using namespace TW; +using namespace TW::Bitcoin; + +TEST(TWAnySignerVerge, Sign) { + const int64_t amount = 1500000000; + const int64_t fee = 2000000; + const std::string toAddress = "DQYMMpqPrnWYZaikKGTQqk5ydUaQw8nkdD"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("DAkEo5pNELZav7MRwBfEwHRG1aChgSUw6c"); + input.set_coin_type(TWCoinTypeVerge); + input.set_time(1656474580); + + auto txHash0 = parse_hex("a5a6e147da0f1b3f6dfd1081f91b0c6e31f030ae66c4be4cf4b0db0ac8b2407d"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(4294967294); + utxo0->set_amount(2500000000); + + auto utxoKey0 = PrivateKey(parse_hex("693dfe6f3ed717573eb10c24ebe5eb592fa3c239245cd499c487eb7b8ea7ed3a")); + auto script0 = Bitcoin::Script::lockScriptForAddress("DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E", TWCoinTypeVerge); + ASSERT_EQ(hex(script0.bytes), "76a914e4839a523f120882d11eb3dda13a18e11fdcbd4a88ac"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + Bitcoin::Proto::TransactionPlan plan; + { + ANY_PLAN(input, plan, TWCoinTypeVerge); + + plan.set_amount(amount); + plan.set_fee(fee); + plan.set_change(980000000); + + *input.mutable_plan() = plan; + } + + Bitcoin::Proto::SigningOutput output; + { + ANY_SIGN(input, TWCoinTypeVerge); + ASSERT_EQ(output.error(), Common::Proto::OK); + } + + // Sign + ASSERT_EQ(hex(output.encoded()), + "01000000d4cbbb62017d40b2c80adbb0f44cbec466ae30f0316e0c1bf98110fd6d3f1b0fda47e1a6a5000000006b483045022100bf1d0e5f84e70e699f45dd4822ecdbbfb1687e61ac749354a76f2afa2e13f76602202d4f5cda7177282b58f80163fead42300468670d03c5f4bb1db3b9596f2dcea301210220ee0423797a856fdd2e85876a60bf10f8696e6ae83e744f498f2173237fe23dfeffffff02002f6859000000001976a914d4d05406c3ca73cf887911f80c852a1c0773615088ac009d693a000000001976a9143d7e143a8b3c8a4aa2f51104da380edeb6c3fc2088ac00000000" + ); +} diff --git a/tests/chains/Verge/TWCoinTypeTests.cpp b/tests/chains/Verge/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..725863b58a9 --- /dev/null +++ b/tests/chains/Verge/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWVergeCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeVerge)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("8c99979a2b25a46659bff35b238aab1c3158f736f215d99526429c7c96203581")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeVerge, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("DFre88gd87bAZQdnS7dbBLwT6GWiGFMQB6")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeVerge, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeVerge)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeVerge)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeVerge), 6); + ASSERT_EQ(TWBlockchainVerge, TWCoinTypeBlockchain(TWCoinTypeVerge)); + ASSERT_EQ(0x21, TWCoinTypeP2shPrefix(TWCoinTypeVerge)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeVerge)); + assertStringsEqual(symbol, "XVG"); + assertStringsEqual(txUrl, "https://verge-blockchain.info/tx/8c99979a2b25a46659bff35b238aab1c3158f736f215d99526429c7c96203581"); + assertStringsEqual(accUrl, "https://verge-blockchain.info/address/DFre88gd87bAZQdnS7dbBLwT6GWiGFMQB6"); + assertStringsEqual(id, "verge"); + assertStringsEqual(name, "Verge"); +} diff --git a/tests/chains/Verge/TransactionBuilderTests.cpp b/tests/chains/Verge/TransactionBuilderTests.cpp new file mode 100644 index 00000000000..283dfd94d07 --- /dev/null +++ b/tests/chains/Verge/TransactionBuilderTests.cpp @@ -0,0 +1,57 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Verge/Transaction.h" +#include "Verge/TransactionBuilder.h" +#include "Bitcoin/TransactionPlan.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" + +#include + +#include +#include + +using namespace TW; +using namespace TW::Bitcoin; + +TEST(VergeTransactionBuilder, BuildWithTime) { + const int64_t amount = 1500000000; + const int64_t fee = 2000000; + const std::string toAddress = "DQYMMpqPrnWYZaikKGTQqk5ydUaQw8nkdD"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("DAkEo5pNELZav7MRwBfEwHRG1aChgSUw6c"); + input.set_coin_type(TWCoinTypeVerge); + + auto txHash0 = parse_hex("a5a6e147da0f1b3f6dfd1081f91b0c6e31f030ae66c4be4cf4b0db0ac8b2407d"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(4294967294); + utxo0->set_amount(2500000000); + + auto utxoKey0 = PrivateKey(parse_hex("693dfe6f3ed717573eb10c24ebe5eb592fa3c239245cd499c487eb7b8ea7ed3a")); + auto script0 = Bitcoin::Script::lockScriptForAddress("DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E", TWCoinTypeVerge); + ASSERT_EQ(hex(script0.bytes), "76a914e4839a523f120882d11eb3dda13a18e11fdcbd4a88ac"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto plan = TransactionBuilder::plan(input); + plan.amount = amount; + plan.fee = fee; + plan.change = 980000000; + + auto tx = Verge::TransactionBuilder::build(plan, input).payload(); + ASSERT_NE(tx.time, 0ul); +} \ No newline at end of file diff --git a/tests/chains/Verge/TransactionCompilerTests.cpp b/tests/chains/Verge/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..c3d95f45b85 --- /dev/null +++ b/tests/chains/Verge/TransactionCompilerTests.cpp @@ -0,0 +1,127 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Bitcoin.pb.h" + +#include +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(VergeCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeVerge; + + // tx on mainnet + // https://verge-blockchain.info/tx/21314157b60ddacb842d2a749429c4112724b7a078adb9e77ba502ea2dd7c230 + + const int64_t amount = 9999995000000; + const int64_t fee = 120850; + const std::string toAddress = "DQZboqURLgrBzBz4Kfbs3yV6fZ3DrNFRjQ"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("DCUWt5ctZcPdPMYPV2o1xK1kqv7jNwxu4h"); + input.set_coin_type(coin); + input.set_time(1584059579); + + auto txHash0 = parse_hex("ee839754c8e93d620cbec9a1c51e7b69016d00839741b03af2c039852d941212"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(1); + utxo0->mutable_out_point()->set_sequence(4294967295); + utxo0->set_amount(20000000000000); + + auto script0 = parse_hex("76a91479471b92b3c94b37544fff430556043d9acd53b188ac"); + utxo0->set_script(script0.data(), script0.size()); + + EXPECT_EQ(input.utxo_size(), 1); + + // Plan + Bitcoin::Proto::TransactionPlan plan; + ANY_PLAN(input, plan, coin); + + plan.set_amount(amount); + plan.set_fee(fee); + plan.set_change(10000004879150); + + // Extend input with accepted plan + *input.mutable_plan() = plan; + + // Serialize input + const auto txInputData = data(input.SerializeAsString()); + EXPECT_GT(txInputData.size(), 0ul); + + /// Step 2: Obtain preimage hashes + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + TW::Bitcoin::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), (int)preImageHashes.size())); + + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[0].data_hash()), + "f7498449e2b8d33d4ff00c72b05c820e5262f43360d9f38455dcfd8f6425c9b2"); + + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[0].public_key_hash()), "79471b92b3c94b37544fff430556043d9acd53b1"); + + auto publicKeyHex = "02b2655122379a375a47e7a204a9dc4572cec5dbe4db4c51fea0c9fa03061fdb0b"; + auto publicKey = PublicKey(parse_hex(publicKeyHex), TWPublicKeyTypeSECP256k1); + auto preImageHash = preSigningOutput.hash_public_keys()[0].data_hash(); + auto signature = parse_hex("3044022039e18d10ab4793d0564cfa675286d2ffd016b8f936c696fd3b72267b621dcd400220653d4761be6b12261629c4240033a08d9767a5f16851dc91a190c8a8d25ecbe0"); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE( + publicKey.verifyAsDER(signature, TW::Data(preImageHash.begin(), preImageHash.end()))); + + // Simulate signatures, normally obtained from signature server. + std::vector signatureVec; + std::vector pubkeyVec; + signatureVec.push_back(signature); + pubkeyVec.push_back(publicKey.bytes); + + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, txInputData, signatureVec, pubkeyVec); + + const auto ExpectedTx = + "01000000bbd46a5e011212942d8539c0f23ab0419783006d01697b1ec5a1c9be0c623de9c8549783ee010000006a473044022039e18d10ab4793d0564cfa675286d2ffd016b8f936c696fd3b72267b621dcd400220653d4761be6b12261629c4240033a08d9767a5f16851dc91a190c8a8d25ecbe0012102b2655122379a375a47e7a204a9dc4572cec5dbe4db4c51fea0c9fa03061fdb0bffffffff02c054264e180900001976a914d50cce1f1449ac5630a0a731cbfcf7d7208a6e7d88ac2e13bd4e180900001976a91450751a6dc46f7068ac3c6350f6a85f7c20fd5e2988ac00000000"; + { + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: not enough signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {signature, signature}, pubkeyVec); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {}, {}); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} diff --git a/tests/chains/WAX/TWAnySignerTests.cpp b/tests/chains/WAX/TWAnySignerTests.cpp index 44e92e8d53a..7e6d2f4300b 100644 --- a/tests/chains/WAX/TWAnySignerTests.cpp +++ b/tests/chains/WAX/TWAnySignerTests.cpp @@ -41,6 +41,7 @@ TEST(TWAnySignerWAX, Sign) { input.set_memo("sent from wallet-core"); input.set_private_key(key.data(), key.size()); input.set_private_key_type(Proto::KeyType::MODERNK1); + input.set_expiration(1670507804 + 30); // https://wax.bloks.io/transaction/4548f7b28ee608663caea61234049ac0018415e02dd0abcea1c215c8da00d10a { diff --git a/tests/chains/Waves/AddressTests.cpp b/tests/chains/Waves/AddressTests.cpp index a2e2fab7442..509cbc7f75c 100644 --- a/tests/chains/Waves/AddressTests.cpp +++ b/tests/chains/Waves/AddressTests.cpp @@ -36,6 +36,9 @@ TEST(WavesAddress, FromPrivateKey) { const auto address = Address(publicKeyCurve25519); ASSERT_EQ(address.string(), "3P2uzAzX9XTu1t32GkWw68YFFLwtapWvDds"); + + const auto publicKeySECP256k1 = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_ANY_THROW(new Address(publicKeySECP256k1)); } TEST(WavesAddress, FromPublicKey) { @@ -45,6 +48,13 @@ TEST(WavesAddress, FromPublicKey) { const auto address = Address(publicKey); ASSERT_EQ(address.string(), "3P2uzAzX9XTu1t32GkWw68YFFLwtapWvDds"); + + const auto address2 = Address(Data(address.bytes.begin(), address.bytes.end())); + ASSERT_EQ(address2.string(), "3P2uzAzX9XTu1t32GkWw68YFFLwtapWvDds"); +} + +TEST(WavesAddress, FromData) { + EXPECT_ANY_THROW(new Address(Data{})); } TEST(WavesAddress, Invalid) { diff --git a/tests/chains/XRP/BinaryCodingTests.cpp b/tests/chains/XRP/BinaryCodingTests.cpp new file mode 100644 index 00000000000..ed39af8c588 --- /dev/null +++ b/tests/chains/XRP/BinaryCodingTests.cpp @@ -0,0 +1,30 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "HexCoding.h" +#include "XRP/BinaryCoding.h" + +#include "TestUtilities.h" +#include + +namespace TW::Ripple::tests { +using namespace std; + +TEST(RippleBinaryCoding, encodeVariableLength) { + Data data; + encodeVariableLength(180, data); + EXPECT_EQ(hex(data), "b4"); + + data.clear(); + encodeVariableLength(12080, data); + EXPECT_EQ(hex(data), "2e6f"); + + data.clear(); + encodeVariableLength(12580, data); + EXPECT_EQ(hex(data), "000063"); +} + +} // namespace TW::Ripple::tests diff --git a/tests/chains/XRP/TWRippleAddressTests.cpp b/tests/chains/XRP/TWRippleAddressTests.cpp index 2254878d89a..13a4f51c51d 100644 --- a/tests/chains/XRP/TWRippleAddressTests.cpp +++ b/tests/chains/XRP/TWRippleAddressTests.cpp @@ -11,7 +11,13 @@ #include -TEST(TWRipple, ExtendedKeys) { +namespace TW::Ripple::tests { + +const char* validAddrStr1 = "X76UnYEMbQfEs3mUqgtjp4zFy9exgThRj7XVZ6UxsdrBptF"; +const char* publicKeyDataStr1 = "0303E20EC6B4A39A629815AE02C0A1393B9225E3B890CAE45B59F42FA29BE9668D"; +const char* invalidAddrStr1 = "12345678"; + +TEST(TWRippleXAddress, ExtendedKeys) { auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic( STRING("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal").get(), STRING("TREZOR").get())); @@ -31,3 +37,42 @@ TEST(TWRipple, XAddress) { EXPECT_EQ(TWRippleXAddressTag(xAddress.get()), 12345ul); assertStringsEqual(WRAPS(TWRippleXAddressDescription(xAddress.get())), "XVfvixWZQKkcenFRYApCjpTUyJ4BePTe3jJv7beatUZvQYh"); } + +TEST(TWRippleXAddress, CreateAndDelete) { + { + TWRippleXAddress* addr = TWRippleXAddressCreateWithString(STRING(validAddrStr1).get()); + EXPECT_TRUE(addr != nullptr); + TWRippleXAddressDelete(addr); + } + { + auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA(publicKeyDataStr1).get(), TWPublicKeyTypeSECP256k1)); + TWRippleXAddress* addr = TWRippleXAddressCreateWithPublicKey(publicKey.get(), 12345); + EXPECT_TRUE(addr != nullptr); + TWRippleXAddressDelete(addr); + } +} + +TEST(TWRippleXAddress, AddressEqual) { + auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA(publicKeyDataStr1).get(), TWPublicKeyTypeSECP256k1)); + auto addr1 = WRAP(TWRippleXAddress, TWRippleXAddressCreateWithPublicKey(publicKey.get(), 12345)); + EXPECT_TRUE(addr1.get() != nullptr); + + auto addr2 = WRAP(TWRippleXAddress, TWRippleXAddressCreateWithString(STRING(validAddrStr1).get())); + EXPECT_TRUE(addr2.get() != nullptr); + ASSERT_TRUE(TWRippleXAddressEqual(addr1.get(), addr2.get())); +} + +TEST(TWRippleXAddress, IsValidString) { + ASSERT_TRUE(TWRippleXAddressIsValidString(STRING(validAddrStr1).get())); + ASSERT_FALSE(TWRippleXAddressIsValidString(STRING(invalidAddrStr1).get())); +} + +TEST(TWRippleXAddress, AddressDescription) { + + auto addr1 = WRAP(TWRippleXAddress, TWRippleXAddressCreateWithString(STRING(validAddrStr1).get())); + EXPECT_TRUE(addr1.get() != nullptr); + auto addrStr1 = std::string(TWStringUTF8Bytes(WRAPS(TWRippleXAddressDescription(addr1.get())).get())); + EXPECT_TRUE(addrStr1 == validAddrStr1); +} + +} // namespace TW::Ripple::tests diff --git a/tests/chains/XRP/TransactionCompilerTests.cpp b/tests/chains/XRP/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..8408624f083 --- /dev/null +++ b/tests/chains/XRP/TransactionCompilerTests.cpp @@ -0,0 +1,99 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Ripple.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +namespace TW::Ripple::tests { + +TEST(RippleCompiler, CompileRippleWithSignatures) { + const auto coin = TWCoinTypeXRP; + /// Step 1: Prepare transaction input (protobuf) + auto key = parse_hex("acf1bbf6264e699da0cc65d17ac03fcca6ded1522d19529df7762db46097ff9f"); + auto input = TW::Ripple::Proto::SigningInput(); + auto privateKey = TW::PrivateKey(key); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + + input.mutable_op_payment()->set_amount(1000000); + input.set_fee(10); + input.set_sequence(75674534); + input.set_last_ledger_sequence(75674797); + input.set_account("rGV1v1xw23PHcRn4Km4tF8R2mfh6yTZkcP"); + input.mutable_op_payment()->set_destination("rNLpgsBTCwiaZAnHe2ZViAN1GcXZtYW6rg"); + input.set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + /// Step 2: Obtain preimage hash + const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + preSigningOutput.ParseFromArray(preImageHashesData.data(), (int)preImageHashesData.size()); + auto preImage = preSigningOutput.data(); + EXPECT_EQ(hex(preImage), "535458001200002200000000240482b3a6201b0482b4ad6140000000000f424068400000000000000a7321027efc5f15071d2ae5e73ee09a0c17456c5d9170a41d67e3297c554829199be80b8114aa000c09c692ef1f82787e51e22833149941ea2083149232ef60695add51f0f84534cc4084e4fdfc698e"); + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), "86ef78df7a4aad29e6b3730f7965c1bd5ccd2439426cb738d7c494a64cfaf4af"); + // Simulate signature, normally obtained from signature server + const auto signature = privateKey.signAsDER(TW::data(preImageHash)); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verifyAsDER(signature, TW::data(preImageHash))); + + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKey.bytes}); + const auto ExpectedTx = std::string("1200002200000000240482b3a6201b0482b4ad6140000000000f424068400000000000000a7321027efc5f15071d2ae5e73ee09a0c17456c5d9170a41d67e3297c554829199be80b74473045022100e1c746c3aeebc8278c627ee4c2ce5cae97e3856292c7fe5388f803920230a37b02207d2eccb76cd35dd379d6b24c2cabd786e62d34a564cf083e863176109c5b6bb48114aa000c09c692ef1f82787e51e22833149941ea2083149232ef60695add51f0f84534cc4084e4fdfc698e"); + + { + TW::Ripple::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + ASSERT_EQ(output.error(), TW::Common::Proto::SigningError::OK); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::Ripple::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + signingInput.set_private_key(key.data(), key.size()); + + TW::Ripple::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: not enough signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, signature}, {publicKey.bytes}); + Ripple::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {}, {}); + Ripple::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} + +} // namespace TW::Ripple::tests diff --git a/tests/chains/Zcash/TWZcashTransactionTests.cpp b/tests/chains/Zcash/TWZcashTransactionTests.cpp index 858f163bb70..e12df1f1e88 100644 --- a/tests/chains/Zcash/TWZcashTransactionTests.cpp +++ b/tests/chains/Zcash/TWZcashTransactionTests.cpp @@ -9,6 +9,7 @@ #include "Bitcoin/OutPoint.h" #include "Bitcoin/Script.h" +#include "Zcash/Signer.h" #include "Zcash/TransactionBuilder.h" #include "Bitcoin/TransactionSigner.h" #include "HexCoding.h" @@ -79,6 +80,24 @@ TEST(TWZcashTransaction, Encode) { auto sighash = transaction.getSignatureHash(scriptCode, 0, TWBitcoinSigHashTypeAll, 0x02faf080, Bitcoin::BASE); ASSERT_EQ(hex(sighash), "f3148f80dfab5e573d5edfe7a850f5fd39234f80b5429d3a57edcc11e34c585b"); + + // AnyoneCanPay|none + preImage = transaction.getPreImage(scriptCode, 0, TWBitcoinSigHashType(TWBitcoinSigHashTypeAnyoneCanPay | TWBitcoinSigHashTypeNone), 0x02faf080); + EXPECT_EQ(hex(preImage), + "0400008085202f8900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000029b0040048b00400000000000000000082000000a8c685478265f4c14dada651969c45a65e1aeb8cd6791f2f5bb6a1d9952104d9010000001976a914507173527b4c3318a2aecd793bf1cfed705950cf88ac80f0fa0200000000feffffff" + ); + + sighash = transaction.getSignatureHash(scriptCode, 0, TWBitcoinSigHashTypeAnyoneCanPay, 0x02faf080, Bitcoin::BASE); + EXPECT_EQ(hex(sighash), "f0bde4facddbc11f5e9ed2f5d5038083bec4a61627a2715a5ee9be7fb3152e9b"); + + // AnyoneCanPay|Single + preImage = transaction.getPreImage(scriptCode, 0, TWBitcoinSigHashType(TWBitcoinSigHashTypeAnyoneCanPay | TWBitcoinSigHashTypeSingle), 0x02faf080); + EXPECT_EQ(hex(preImage), + "0400008085202f890000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000055986938e432f825904fe288aa4feca1fe7eafa24aecd1bd6a9a739536b50a5469be00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000029b0040048b00400000000000000000083000000a8c685478265f4c14dada651969c45a65e1aeb8cd6791f2f5bb6a1d9952104d9010000001976a914507173527b4c3318a2aecd793bf1cfed705950cf88ac80f0fa0200000000feffffff" + ); + + sighash = transaction.getSignatureHash(scriptCode, 0, TWBitcoinSigHashType(TWBitcoinSigHashTypeAnyoneCanPay | TWBitcoinSigHashTypeSingle), 0x02faf080, Bitcoin::BASE); + EXPECT_EQ(hex(sighash), "1e747b6a4a96aa9e7c1d7968221ec916bd30b514f8bca14b6f74d7c11c0742c2"); } TEST(TWZcashTransaction, SaplingSigning) { @@ -88,6 +107,7 @@ TEST(TWZcashTransaction, SaplingSigning) { const int64_t fee = 6000; auto input = Bitcoin::Proto::SigningInput(); + input.set_coin_type(TWCoinTypeZcash); input.set_hash_type(TWBitcoinSigHashTypeAll); input.set_amount(amount); input.set_byte_fee(1); @@ -186,3 +206,23 @@ TEST(TWZcashTransaction, BlossomSigning) { signedTx.encode(serialized); ASSERT_EQ(hex(serialized), "0400008085202f8901de8c02c79c01018bd91dbc6b293eba03945be25762994409209a06d95c828123000000006b483045022100e6e5071811c08d0c2e81cb8682ee36a8c6b645f5c08747acd3e828de2a4d8a9602200b13b36a838c7e8af81f2d6e7e694ede28833a480cfbaaa68a47187655298a7f0121024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382ffffffff01cf440000000000001976a914c3bacb129d85288a3deb5890ca9b711f7f71392688ac00000000000000000000000000000000000000"); } + +TEST(TWZcashTransaction, SigningWithError) { + const int64_t amount = 17615; + const std::string toAddress = "t1biXYN8wJahR76SqZTe1LBzTLf3JAsmT93"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_coin_type(TWCoinTypeZcash); + + // Sign + auto result = Zcash::Signer::sign(input); + ASSERT_NE(result.error(), Common::Proto::OK); + + // PreImageHashes + auto preResult = Zcash::Signer::preImageHashes(input); + ASSERT_NE(preResult.error(), Common::Proto::OK); +} \ No newline at end of file diff --git a/tests/chains/Zcash/TransactionCompilerTests.cpp b/tests/chains/Zcash/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..dc67ff52266 --- /dev/null +++ b/tests/chains/Zcash/TransactionCompilerTests.cpp @@ -0,0 +1,114 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Bitcoin.pb.h" + +#include +#include + +#include "Zcash/Signer.h" +#include "Zcash/Transaction.h" +#include "Zcash/TransactionBuilder.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(ZcashCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeZcash; + + // tx on mainnet + // https://explorer.zcha.in/transactions/387939ff8eb07dd264376eeef2e126394ab139802b1d80e92b21c1a2ae54fe92 + const int64_t amount = 17615; + const int64_t fee = 10000; + const std::string toAddress = "t1biXYN8wJahR76SqZTe1LBzTLf3JAsmT93"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_coin_type(TWCoinTypeZcash); + + auto txHash0 = parse_hex("2381825cd9069a200944996257e25b9403ba3e296bbc1dd98b01019cc7028cde"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo0->set_amount(27615); + + // real key 1p "m/44'/133'/0'/0/14" + auto utxoKey0 = PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646")); + auto utxoAddr0 = TW::deriveAddress(TWCoinTypeZcash, utxoKey0); + auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, TWCoinTypeZcash); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto plan = Zcash::TransactionBuilder::plan(input); + plan.amount = amount; + plan.fee = fee; + plan.change = 0; + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan.proto(); + + // build preimage + const auto txInputData = data(input.SerializeAsString()); + EXPECT_GT(txInputData.size(), 0ul); + + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + ASSERT_GT(preImageHashes.size(), 0ul); + + Bitcoin::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + ASSERT_EQ(preSigningOutput.hash_public_keys().size(), 1); + + auto preImageHash = data(preSigningOutput.hash_public_keys()[0].data_hash()); + EXPECT_EQ(hex(preImageHash), + "1472faba6529ac6d88f87f6ab881e438c3c8a17482b4a82ef13212333868258a"); + + // compile + auto publicKey = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); + TW::Data signature = parse_hex("3045022100e6e5071811c08d0c2e81cb8682ee36a8c6b645f5c08747acd3e828de2a4d8a9602200b13b36a838c7e8af81f2d6e7e694ede28833a480cfbaaa68a47187655298a7f"); + auto outputData = + TransactionCompiler::compileWithSignatures(coin, txInputData, {signature}, {publicKey.bytes}); + Bitcoin::Proto::SigningOutput signingOutput; + ASSERT_TRUE(signingOutput.ParseFromArray(outputData.data(), (int)outputData.size())); + ASSERT_EQ(hex(signingOutput.encoded()), "0400008085202f8901de8c02c79c01018bd91dbc6b293eba03945be25762994409209a06d95c828123000000006b483045022100e6e5071811c08d0c2e81cb8682ee36a8c6b645f5c08747acd3e828de2a4d8a9602200b13b36a838c7e8af81f2d6e7e694ede28833a480cfbaaa68a47187655298a7f0121024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382ffffffff01cf440000000000001976a914c3bacb129d85288a3deb5890ca9b711f7f71392688ac00000000000000000000000000000000000000"); + + { + auto result = Zcash::Signer::sign(input); + ASSERT_EQ(hex(result.encoded()), hex(signingOutput.encoded())); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {signature, signature}, {publicKey.bytes}); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {}, {}); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} diff --git a/tests/chains/Zelcash/TWZelcashTransactionTests.cpp b/tests/chains/Zelcash/TWZelcashTransactionTests.cpp index 431fe61c435..6206875a0ac 100644 --- a/tests/chains/Zelcash/TWZelcashTransactionTests.cpp +++ b/tests/chains/Zelcash/TWZelcashTransactionTests.cpp @@ -86,6 +86,7 @@ TEST(TWZelcashTransaction, Signing) { const int64_t fee = 2260; auto input = Bitcoin::Proto::SigningInput(); + input.set_coin_type(TWCoinTypeZelcash); input.set_hash_type(TWBitcoinSigHashTypeAll); input.set_amount(amount); input.set_byte_fee(1); diff --git a/tests/chains/Zen/AddressTests.cpp b/tests/chains/Zen/AddressTests.cpp new file mode 100644 index 00000000000..eac89795832 --- /dev/null +++ b/tests/chains/Zen/AddressTests.cpp @@ -0,0 +1,46 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "HexCoding.h" +#include "HDWallet.h" +#include "Zen/Address.h" +#include "PublicKey.h" +#include "PrivateKey.h" +#include +#include + +using namespace TW; +using namespace TW::Zen; + +TEST(ZenAddress, Valid) { + ASSERT_TRUE(Address::isValid("znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg")); + ASSERT_TRUE(Address::isValid("zshX5BAgUvNgM1VoBVKZyFVVozTDjjJvRxJ")); +} + +TEST(ZenAddress, Invalid) { + ASSERT_FALSE(Address::isValid("znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5abs")); +} + +TEST(ZenAddress, FromPrivateKey) { + auto privateKey = PrivateKey(parse_hex("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a")); + auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + auto address = Address(pubKey); + ASSERT_EQ(address.string(), "znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); +} + +TEST(ZenAddress, FromPublicKey) { + auto publicKey = PublicKey(parse_hex("02b4ac9056d20c52ac11b0d7e83715dd3eac851cfc9cb64b8546d9ea0d4bb3bdfe"), TWPublicKeyTypeSECP256k1); + auto address = Address(publicKey); + ASSERT_EQ(address.string(), "znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); +} + +TEST(ZenAddress, FromString) { + auto address = Address("znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); + ASSERT_EQ(address.string(), "znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); + + address = Address("zshX5BAgUvNgM1VoBVKZyFVVozTDjjJvRxJ"); + ASSERT_EQ(address.string(), "zshX5BAgUvNgM1VoBVKZyFVVozTDjjJvRxJ"); +} diff --git a/tests/chains/Zen/SignerTests.cpp b/tests/chains/Zen/SignerTests.cpp new file mode 100644 index 00000000000..5ffabe1b339 --- /dev/null +++ b/tests/chains/Zen/SignerTests.cpp @@ -0,0 +1,111 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Zen/Signer.h" +#include "Zen/Address.h" +#include "Zen/TransactionBuilder.h" + +#include "Bitcoin/TransactionPlan.h" +#include "Bitcoin/TransactionSigner.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "proto/Bitcoin.pb.h" + +#include + +#include + +using namespace TW; +using namespace TW::Zen; + +TEST(ZenSigner, Sign) { + const int64_t amount = 10000; + const std::string toAddress = "zngBGZGKaaBamofSuFw5WMnvU2HQAtwGeb5"; + + auto blockHash = parse_hex("0000000004561422697a29d424d925334db5ef2e80232306a1ad3fd35f72dc81"); + std::reverse(blockHash.begin(), blockHash.end()); + auto blockHeight = 1147624; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); + input.set_coin_type(TWCoinTypeZen); + + auto txHash0 = parse_hex("62dea4b87fd66ca8e75a199c93131827ed40fb96cd8412e3476540abb5139ea3"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo0->set_amount(17600); + + auto utxoKey0 = PrivateKey(parse_hex("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a")); + auto utxoAddr0 = TW::deriveAddress(TWCoinTypeZen, utxoKey0); + ASSERT_EQ(utxoAddr0, "znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); + auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, TWCoinTypeZen, blockHash, blockHeight); + ASSERT_EQ(hex(script0.bytes), "76a914cf83669620de8bbdf2cefcdc5b5113195603c56588ac2081dc725fd33fada1062323802eefb54d3325d924d4297a69221456040000000003e88211b4"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto plan = Signer::plan(input); + ASSERT_EQ(plan.fee(), 226); + plan.set_preblockhash(blockHash.data(), (int)blockHash.size()); + plan.set_preblockheight(blockHeight); + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan; + + // Sign + auto result = Bitcoin::TransactionSigner::sign(input); + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + ASSERT_EQ(hex(serialized), + "0100000001a39e13b5ab406547e31284cd96fb40ed271813939c195ae7a86cd67fb8a4de62000000006a473044022014d687c0bee0b7b584db2eecbbf73b545ee255c42b8edf0944665df3fa882cfe02203bce2412d93c5a56cb4806ddd8297ff05f8fc121306e870bae33377a58a02f05012102b4ac9056d20c52ac11b0d7e83715dd3eac851cfc9cb64b8546d9ea0d4bb3bdfeffffffff0210270000000000003f76a914a58d22659b1082d1fa8698fc51996b43281bfce788ac2081dc725fd33fada1062323802eefb54d3325d924d4297a69221456040000000003e88211b4ce1c0000000000003f76a914cf83669620de8bbdf2cefcdc5b5113195603c56588ac2081dc725fd33fada1062323802eefb54d3325d924d4297a69221456040000000003e88211b400000000" + ); +} + +TEST(ZenSigner, SignWithError) { + const int64_t amount = 10000; + const std::string toAddress = "zngBGZGKaaBamofSuFw5WMnvU2HQAtwGeb5"; + + auto blockHash = parse_hex("0000000004561422697a29d424d925334db5ef2e80232306a1ad3fd35f72dc81"); + std::reverse(blockHash.begin(), blockHash.end()); + auto blockHeight = 1147624; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); + input.set_coin_type(TWCoinTypeZen); + + auto plan = Signer::plan(input); + plan.set_preblockhash(blockHash.data(), (int)blockHash.size()); + plan.set_preblockheight(blockHeight); + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan; + + // Sign + auto result = Zen::Signer::sign(input); + + ASSERT_NE(result.error(), Common::Proto::OK); + + // PreImageHash + auto preResult = Zen::Signer::preImageHashes(input); + + ASSERT_NE(preResult.error(), Common::Proto::OK); +} \ No newline at end of file diff --git a/tests/chains/Zen/TWAnyAddressTests.cpp b/tests/chains/Zen/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..5063cc4a4f4 --- /dev/null +++ b/tests/chains/Zen/TWAnyAddressTests.cpp @@ -0,0 +1,22 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TWZen, Address) { + auto string = STRING("znfexeyosWvMG93AjJx6CkRzKtS2aBdDgAx"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeZen)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "9fd1b64dad29d82b151206f66057bab1dae2f517"); +} diff --git a/tests/chains/Zen/TWAnySignerTests.cpp b/tests/chains/Zen/TWAnySignerTests.cpp new file mode 100644 index 00000000000..aa8845c4fe8 --- /dev/null +++ b/tests/chains/Zen/TWAnySignerTests.cpp @@ -0,0 +1,81 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include +#include + +#include "Bitcoin/Signer.h" +#include "Bitcoin/Address.h" +#include "Bitcoin/TransactionPlan.h" +#include "Bitcoin/TransactionSigner.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TWAnySignerZen, Sign) { + const int64_t amount = 10000; + const std::string toAddress = "zngBGZGKaaBamofSuFw5WMnvU2HQAtwGeb5"; + + auto blockHash = parse_hex("0000000004561422697a29d424d925334db5ef2e80232306a1ad3fd35f72dc81"); + std::reverse(blockHash.begin(), blockHash.end()); + auto blockHeight = 1147624; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); + input.set_coin_type(TWCoinTypeZen); + + auto txHash0 = parse_hex("62dea4b87fd66ca8e75a199c93131827ed40fb96cd8412e3476540abb5139ea3"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo0->set_amount(17600); + + auto utxoKey0 = PrivateKey(parse_hex("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a")); + auto utxoAddr0 = TW::deriveAddress(TWCoinTypeZen, utxoKey0); + ASSERT_EQ(utxoAddr0, "znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); + auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, TWCoinTypeZen, blockHash, blockHeight); + ASSERT_EQ(hex(script0.bytes), "76a914cf83669620de8bbdf2cefcdc5b5113195603c56588ac2081dc725fd33fada1062323802eefb54d3325d924d4297a69221456040000000003e88211b4"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + Bitcoin::Proto::TransactionPlan plan; + { + ANY_PLAN(input, plan, TWCoinTypeZen); + + ASSERT_EQ(plan.fee(), 226); + + plan.set_preblockhash(blockHash.data(), (int)blockHash.size()); + plan.set_preblockheight(blockHeight); + + *input.mutable_plan() = plan; + } + + Bitcoin::Proto::SigningOutput output; + { + ANY_SIGN(input, TWCoinTypeZen); + ASSERT_EQ(output.error(), Common::Proto::OK); + } + + // Sign + ASSERT_EQ(hex(output.encoded()), + "0100000001a39e13b5ab406547e31284cd96fb40ed271813939c195ae7a86cd67fb8a4de62000000006a473044022014d687c0bee0b7b584db2eecbbf73b545ee255c42b8edf0944665df3fa882cfe02203bce2412d93c5a56cb4806ddd8297ff05f8fc121306e870bae33377a58a02f05012102b4ac9056d20c52ac11b0d7e83715dd3eac851cfc9cb64b8546d9ea0d4bb3bdfeffffffff0210270000000000003f76a914a58d22659b1082d1fa8698fc51996b43281bfce788ac2081dc725fd33fada1062323802eefb54d3325d924d4297a69221456040000000003e88211b4ce1c0000000000003f76a914cf83669620de8bbdf2cefcdc5b5113195603c56588ac2081dc725fd33fada1062323802eefb54d3325d924d4297a69221456040000000003e88211b400000000" + ); +} diff --git a/tests/chains/Zen/TWCoinTypeTests.cpp b/tests/chains/Zen/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..5fc0aebbad9 --- /dev/null +++ b/tests/chains/Zen/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWZenCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeZen)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("b7f548640766fb024247accf4e01bec37d88d49c4900357edc84d49a09ff4430")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeZen, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("znRchPtvEyJJUwGbCALqyjwHJb1Gx6z4H4j")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeZen, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeZen)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeZen)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeZen), 8); + ASSERT_EQ(TWBlockchainZen, TWCoinTypeBlockchain(TWCoinTypeZen)); + ASSERT_EQ(0x96, TWCoinTypeP2shPrefix(TWCoinTypeZen)); + ASSERT_EQ(0x20, TWCoinTypeStaticPrefix(TWCoinTypeZen)); + assertStringsEqual(symbol, "ZEN"); + assertStringsEqual(txUrl, "https://explorer.horizen.io/tx/b7f548640766fb024247accf4e01bec37d88d49c4900357edc84d49a09ff4430"); + assertStringsEqual(accUrl, "https://explorer.horizen.io/address/znRchPtvEyJJUwGbCALqyjwHJb1Gx6z4H4j"); + assertStringsEqual(id, "zen"); + assertStringsEqual(name, "Zen"); +} diff --git a/tests/chains/Zen/TransactionBuilderTests.cpp b/tests/chains/Zen/TransactionBuilderTests.cpp new file mode 100644 index 00000000000..8575d8ed089 --- /dev/null +++ b/tests/chains/Zen/TransactionBuilderTests.cpp @@ -0,0 +1,95 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Bitcoin/Transaction.h" +#include "Bitcoin/TransactionPlan.h" +#include "Bitcoin/TransactionSigner.h" +#include "Zen/Address.h" +#include "Zen/Signer.h" +#include "Zen/TransactionBuilder.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" + +#include + +#include +#include + +using namespace TW; +using namespace TW::Bitcoin; + +TEST(ZenTransactionBuilder, Build) { + const int64_t amount = 10000; + const std::string toAddress = "zngBGZGKaaBamofSuFw5WMnvU2HQAtwGeb5"; + + auto blockHash = parse_hex("0000000004561422697a29d424d925334db5ef2e80232306a1ad3fd35f72dc81"); + std::reverse(blockHash.begin(), blockHash.end()); + auto blockHeight = 1147624; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); + input.set_coin_type(TWCoinTypeZen); + + Data opScript = parse_hex("00010203"); + input.set_output_op_return(std::string(opScript.begin(), opScript.end())); + + auto eo = input.add_extra_outputs(); + eo->set_to_address("znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); + eo->set_amount(7000); + + auto txHash0 = parse_hex("62dea4b87fd66ca8e75a199c93131827ed40fb96cd8412e3476540abb5139ea3"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo0->set_amount(17600); + + auto utxoKey0 = PrivateKey(parse_hex("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a")); + auto utxoAddr0 = TW::deriveAddress(TWCoinTypeZen, utxoKey0); + ASSERT_EQ(utxoAddr0, "znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); + auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, TWCoinTypeZen, blockHash, blockHeight); + ASSERT_EQ(hex(script0.bytes), "76a914cf83669620de8bbdf2cefcdc5b5113195603c56588ac2081dc725fd33fada1062323802eefb54d3325d924d4297a69221456040000000003e88211b4"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto plan = Bitcoin::TransactionSigner::plan(input); + ASSERT_EQ(plan.fee, 294); + plan.preBlockHash = blockHash; + plan.preBlockHeight = blockHeight; + plan.useMaxAmount = true; + + // plan1 + auto result = Zen::TransactionBuilder::build(plan, input).payload(); + + ASSERT_GT(result.outputs.size(), 0ul); + ASSERT_EQ(result.outputs[0].value, plan.amount); + + // plan2 + plan.useMaxAmount = false; + result = Zen::TransactionBuilder::build(plan, input).payload(); + + ASSERT_EQ(result.outputs.size(), 4ul); + ASSERT_EQ(result.outputs[3].value, 7000); +} + +TEST(ZenTransactionBuilder, BuildScript) { + auto blockHash = parse_hex("0000000004561422697a29d424d925334db5ef2e80232306a1ad3fd35f72dc81"); + std::reverse(blockHash.begin(), blockHash.end()); + auto blockHeight = 1147624; + + // invalid address + auto result = Zen::TransactionBuilder::prepareOutputWithScript( + "DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E", + 10000, TWCoinTypeZen, blockHash, blockHeight); + ASSERT_FALSE(result.has_value()); +} \ No newline at end of file diff --git a/tests/chains/Zen/TransactionCompilerTests.cpp b/tests/chains/Zen/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..6e9c81a35d3 --- /dev/null +++ b/tests/chains/Zen/TransactionCompilerTests.cpp @@ -0,0 +1,117 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Bitcoin.pb.h" + +#include +#include + +#include "Zen/Signer.h" +#include "Zen/TransactionBuilder.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(ZenCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeZen; + + const int64_t amount = 200000; + const std::string toAddress = "znma8BydGx1p7SZ17g5JMMWXqSoRSE7BNdQ"; + + auto blockHash = parse_hex("000000000396ef95695b498168964e1733aca9fe47bb4f9b2851dcd0ec0edad0"); + std::reverse(blockHash.begin(), blockHash.end()); + auto blockHeight = 1163482; + + auto sblockHash = parse_hex("0000000002906dc9ef21c60d08cd03d192cba94de66095c63082d8e7e9436d40"); + std::reverse(sblockHash.begin(), sblockHash.end()); + auto sblockHeight = 1163438; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("zncug4MEDrunR5WgdWfGB1t9Bjp8RCpKxA6"); + input.set_coin_type(coin); + input.set_lock_time(1163772); + + auto txHash0 = parse_hex("89f799d7aaf17dbc619f5c68aa5a5ae55ceec779f9009203a87359217405f8d8"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(1); + utxo0->mutable_out_point()->set_sequence(4294967294); + utxo0->set_amount(1249057); + + auto utxoAddr0 = "znj6M9EbCmU7UKN2zgAQ8j1GwUnr4QbZBYt"; + // build utxo scriptPubKey + // check 89f799d7aaf17dbc619f5c68aa5a5ae55ceec779f9009203a87359217405f8d8,1 + auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, coin, sblockHash, sblockHeight); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + + auto plan = Zen::TransactionBuilder::plan(input); + ASSERT_EQ(plan.fee, 226); + plan.preBlockHash = blockHash; + plan.preBlockHeight = blockHeight; + plan.fee = 302; + plan.change = 1249057 - plan.amount - plan.fee; + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan.proto(); + + // build preimage + const auto txInputData = data(input.SerializeAsString()); + EXPECT_GT(txInputData.size(), 0ul); + + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + ASSERT_GT(preImageHashes.size(), 0ul); + + Bitcoin::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + ASSERT_EQ(preSigningOutput.hash_public_keys().size(), 1); + + auto preImageHash = data(preSigningOutput.hash_public_keys()[0].data_hash()); + EXPECT_EQ(hex(preImageHash), + "882e2e61e740ff3d5889995679bf3dcda1b872e0d93be23c89a4fd4e3837f200"); + + // compile + auto publicKey = PublicKey(parse_hex("02806408d2f6d5095bb73e89f9edbe02fe81853f25c541d33da4422c6916c1d0e1"), TWPublicKeyTypeSECP256k1); + TW::Data signature = parse_hex("3045022100b27a4d10a4c5e758c4a379ccf7050eae6d8d4dacf5c65894d024de5ab947d4640220194ffccb29c95fe0ae3fb91a40276536494102891c6c5a9aee6063106fa55d30"); + auto outputData = + TransactionCompiler::compileWithSignatures(coin, txInputData, {signature}, {publicKey.bytes}); + Bitcoin::Proto::SigningOutput signingOutput; + ASSERT_TRUE(signingOutput.ParseFromArray(outputData.data(), (int)outputData.size())); + // txid: 0fc555f8e205e66576f760d99270eaa6d60480c0e816209b2058387b65c2a000 + ASSERT_EQ(hex(signingOutput.encoded()), "0100000001d8f80574215973a8039200f979c7ee5ce55a5aaa685c9f61bc7df1aad799f789010000006b483045022100b27a4d10a4c5e758c4a379ccf7050eae6d8d4dacf5c65894d024de5ab947d4640220194ffccb29c95fe0ae3fb91a40276536494102891c6c5a9aee6063106fa55d30012102806408d2f6d5095bb73e89f9edbe02fe81853f25c541d33da4422c6916c1d0e1feffffff02400d0300000000003f76a914e0b858909b6b2c14996658085ed907abd880d32d88ac20d0da0eecd0dc51289b4fbb47fea9ac33174e966881495b6995ef96030000000003dac011b4b3001000000000003f76a91481b1b83b2ae8a4cddd72750dc5252c4bddd4e57e88ac20d0da0eecd0dc51289b4fbb47fea9ac33174e966881495b6995ef96030000000003dac011b4fcc11100"); + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {signature, signature}, {publicKey.bytes}); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {}, {}); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} diff --git a/tests/chains/Zilliqa/TWAnySignerTests.cpp b/tests/chains/Zilliqa/TWAnySignerTests.cpp index 227a10caf80..c96d0f1ecc1 100644 --- a/tests/chains/Zilliqa/TWAnySignerTests.cpp +++ b/tests/chains/Zilliqa/TWAnySignerTests.cpp @@ -5,8 +5,8 @@ // file LICENSE at the root of the source code distribution tree. #include "HexCoding.h" -#include "uint256.h" #include "proto/Zilliqa.pb.h" +#include "uint256.h" #include "TestUtilities.h" #include #include @@ -14,7 +14,7 @@ namespace TW::Zilliqa::tests { TEST(TWAnySignerZilliqa, Sign) { - auto input = Proto::SigningInput(); + auto input = TW::Zilliqa::Proto::SigningInput(); auto& tx = *input.mutable_transaction(); auto& transfer = *tx.mutable_transfer(); auto key = parse_hex("0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748"); @@ -29,7 +29,7 @@ TEST(TWAnySignerZilliqa, Sign) { input.set_private_key(key.data(), key.size()); transfer.set_amount(amount.data(), amount.size()); - Proto::SigningOutput output; + TW::Zilliqa::Proto::SigningOutput output; ANY_SIGN(input, TWCoinTypeZilliqa); EXPECT_EQ(hex(output.signature()), "001fa4df08c11a4a79e96e69399ee48eeecc78231a78b0355a8ca783c77c139436e37934fecc2252ed8dac00e235e22d18410461fb896685c4270642738ed268"); diff --git a/tests/common/CoinAddressDerivationTests.cpp b/tests/common/CoinAddressDerivationTests.cpp index 9776fa4c4b1..57e03107cb3 100644 --- a/tests/common/CoinAddressDerivationTests.cpp +++ b/tests/common/CoinAddressDerivationTests.cpp @@ -97,6 +97,10 @@ TEST(Coin, DeriveAddress) { EXPECT_EQ(address, "t1b9xfAk3kZp5Qk3rinDPq7zzLkJGHTChDS"); break; + case TWCoinTypeKomodo: + EXPECT_EQ(address, "RSZYjMDCP4q3t7NAFXPPnqEGrMZn971pdB"); + break; + case TWCoinTypeAeternity: EXPECT_EQ(address, "ak_2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5FeVfcw"); break; @@ -112,12 +116,18 @@ TEST(Coin, DeriveAddress) { case TWCoinTypeBinance: EXPECT_EQ(address, "bnb1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0mlq0d0"); break; + case TWCoinTypeTBinance: + EXPECT_EQ(address, "tbnb1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z042ftd7"); + break; case TWCoinTypeBitcoin: EXPECT_EQ(address, "bc1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z00ppggv"); break; case TWCoinTypeBitcoinCash: EXPECT_EQ(address, "bitcoincash:qz7eyzytkl5z6cg6nw20hd62pyyp22mcfuardfd2vn"); break; + case TWCoinTypeBitcoinDiamond: + EXPECT_EQ(address, "1JHMeqKunF2Up6zxnMQGhJu5667BXz98YQ"); + break; case TWCoinTypeBitcoinGold: EXPECT_EQ(address, "btg1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z0eg8day"); break; @@ -179,6 +189,9 @@ TEST(Coin, DeriveAddress) { case TWCoinTypeICON: EXPECT_EQ(address, "hx4728fc65c31728f0d3538b8783b5394b31a136b9"); break; + case TWCoinTypeIOST: + EXPECT_EQ(address, "H4JcMPicKkHcxxDjkyyrLoQj7Kcibd9t815ak4UvTr9M"); + break; case TWCoinTypeIoTeX: EXPECT_EQ(address, "io1nk9x9ajk4rgkzhqjjn7hr6w0k0jg2kj0zgdt6h"); break; @@ -218,6 +231,9 @@ TEST(Coin, DeriveAddress) { case TWCoinTypeOsmosis: EXPECT_EQ(address, "osmo1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z03qvn6n"); break; + case TWCoinTypePivx: + EXPECT_EQ(address, "DNRTC6GZ5evmM7BZWwPqF54fyDqUqULMyu"); + break; case TWCoinTypePolkadot: EXPECT_EQ(address, "16PpFrXrC6Ko3pYcyMAx6gPMp3mFFaxgyYMt4G5brkgNcSz8"); break; @@ -233,6 +249,9 @@ TEST(Coin, DeriveAddress) { case TWCoinTypeSolana: EXPECT_EQ(address, "H4JcMPicKkHcxxDjkyyrLoQj7Kcibd9t815ak4UvTr9M"); break; + case TWCoinTypeSyscoin: + EXPECT_EQ(address, "sys1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z083sjh7"); + break; case TWCoinTypeTHORChain: EXPECT_EQ(address, "thor1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0luxce7"); break; @@ -242,6 +261,9 @@ TEST(Coin, DeriveAddress) { case TWCoinTypeTron: EXPECT_EQ(address, "TQLCsShbQNXMTVCjprY64qZmEA4rBarpQp"); break; + case TWCoinTypeVerge: + EXPECT_EQ(address, "DNRTC6GZ5evmM7BZWwPqF54fyDqUqULMyu"); + break; case TWCoinTypeViacoin: EXPECT_EQ(address, "via1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z09y9mn2"); break; @@ -251,15 +273,24 @@ TEST(Coin, DeriveAddress) { case TWCoinTypeXRP: EXPECT_EQ(address, "rJHMeqKu8Ep7Fazx8MQG6JunaafBXz93YQ"); break; + case TWCoinTypeZen: + EXPECT_EQ(address, "zniNGeFxXRpY6RDGVdfdmbcvcFb1rrLdnFz"); + break; case TWCoinTypeZilliqa: EXPECT_EQ(address, "zil1j2cvtd7j9n7fnxfv2r3neucjw8tp4xz9sp07v4"); break; + case TWCoinTypeStratis: + EXPECT_EQ(address, "strax1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z0rvt20n"); + break; case TWCoinTypeNervos: EXPECT_EQ(address, "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqtsqfsf77ae0wn5a7795hs2ydv83g6hl4qleywxw"); break; case TWCoinTypeAptos: EXPECT_EQ(address, "0xce2fd04ac9efa74f17595e5785e847a2399d7e637f5e8179244f76191f653276"); break; + case TWCoinTypeNebl: + EXPECT_EQ(address, "NdCKqb8BQoavA5PZ5b4APxKmSpmBA6yMSi"); + break; case TWCoinTypeSui: EXPECT_EQ(address, "0x870deb25d5c0a4d7250d52d5cd58dacca2d51eb2a120a979b13384cd52e21e1b"); break; diff --git a/tests/common/CoinAddressValidationTests.cpp b/tests/common/CoinAddressValidationTests.cpp index 383329f393f..7920ceb8c20 100644 --- a/tests/common/CoinAddressValidationTests.cpp +++ b/tests/common/CoinAddressValidationTests.cpp @@ -412,6 +412,11 @@ TEST(Coin, ValidateAddressEverscale) { ASSERT_EQ(normalizeAddress(TWCoinTypeEverscale, "0:83A0352908060FA87839195D8A763A8D9AB28F8FA41468832B398A719CC6469A"), "0:83a0352908060fa87839195d8a763a8d9ab28f8fa41468832b398a719cc6469a"); } +TEST(Coin, ValidateAddressNebl) { + EXPECT_TRUE(validateAddress(TWCoinTypeNebl, "NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc")); + EXPECT_TRUE(validateAddress(TWCoinTypeNebl, "NidLccuLD8J4oK25PwPg5ipLj5L9VVrwi5")); +} + TEST(Coin, ValidateAddressTheOpenNetwork) { EXPECT_TRUE(validateAddress(TWCoinTypeTON, "0:8a8627861a5dd96c9db3ce0807b122da5ed473934ce7568a5b4b1c361cbb28ae")); EXPECT_FALSE(validateAddress(TWCoinTypeTON, "8a8627861a5dd96c9db3ce0807b122da5ed473934ce7568a5b4b1c361cbb28ae")); diff --git a/tests/common/Keystore/StoredKeyTests.cpp b/tests/common/Keystore/StoredKeyTests.cpp index ad1e1c03e68..9a1fc98e8db 100644 --- a/tests/common/Keystore/StoredKeyTests.cpp +++ b/tests/common/Keystore/StoredKeyTests.cpp @@ -6,15 +6,15 @@ #include "Keystore/StoredKey.h" +#include "Bitcoin/Address.h" #include "Coin.h" -#include "HexCoding.h" #include "Data.h" -#include "PrivateKey.h" +#include "HexCoding.h" #include "Mnemonic.h" -#include "Bitcoin/Address.h" +#include "PrivateKey.h" -#include #include +#include extern std::string TESTS_ROOT; @@ -189,7 +189,7 @@ TEST(StoredKey, AccountGetDoesntChange) { vector coins = {coinTypeBc, coinTypeEth, coinTypeBnb}; // retrieve multiple accounts, which will be created vector accounts; - for (auto coin: coins) { + for (auto coin : coins) { std::optional account = key.account(coin, &wallet); accounts.push_back(*account); @@ -340,6 +340,15 @@ TEST(StoredKey, LoadPBKDF2Key) { EXPECT_EQ(hex(std::get(payload.params.kdfParams).salt), "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"); EXPECT_EQ(hex(payload.decrypt(TW::data("testpassword"))), "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"); + + auto j = std::get(payload.params.kdfParams).json(); + auto expected = R"|({"c":262144,"dklen":32,"salt":"ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"})|"; + EXPECT_EQ(j.dump(), expected); +} + +TEST(StoredKey, RandomPBKDF2Param) { + auto p = PBKDF2Parameters(); + ASSERT_TRUE(p.salt.size() == 32ul); } TEST(StoredKey, LoadLegacyMnemonic) { @@ -463,7 +472,7 @@ TEST(StoredKey, DecodingBitcoinAddress) { EXPECT_EQ(key.accounts[0].address, "3PWazDi9n1Hfyq9gXFxDxzADNL8RNYyK2y"); } - + TEST(StoredKey, RemoveAccount) { auto key = StoredKey::load(testDataPath("legacy-mnemonic.json")); EXPECT_EQ(key.accounts.size(), 2ul); @@ -592,7 +601,7 @@ TEST(StoredKey, CreateMultiAccounts) { // Multiple accounts for the same coin const auto coin = TWCoinTypeBitcoin; const auto btc1 = key.account(coin, &wallet); - + EXPECT_TRUE(btc1.has_value()); EXPECT_EQ(btc1->address, expectedBtc1); EXPECT_EQ(btc1->derivationPath.string(), "m/84'/0'/0'/0/0"); @@ -621,7 +630,7 @@ TEST(StoredKey, CreateMultiAccounts) { // Multiple accounts for the same coin const auto coin = TWCoinTypeBitcoin; const auto btc2 = key.account(coin, TWDerivationBitcoinLegacy, wallet); - + EXPECT_EQ(btc2.address, expectedBtc2); EXPECT_EQ(btc2.derivationPath.string(), "m/44'/0'/0'/0/0"); EXPECT_EQ(btc2.extendedPublicKey, "xpub6CR52eaUuVb4kXAVyHC2i5ZuqJ37oWNPZFtjXaazFPXZD45DwWBYEBLdrF7fmCR9pgBuCA9Q57zZfyJjDUBDNtWkhWuGHNYKLgDHpqrHsxV"); diff --git a/tests/common/LiquidStaking/LiquidStakingTests.cpp b/tests/common/LiquidStaking/LiquidStakingTests.cpp index b9f5f802b25..e454bcb4f6b 100644 --- a/tests/common/LiquidStaking/LiquidStakingTests.cpp +++ b/tests/common/LiquidStaking/LiquidStakingTests.cpp @@ -601,7 +601,7 @@ namespace TW::LiquidStaking::tests { assertJSONEqual(tx.serialized(), expectedJson); EXPECT_EQ(hex(tx.signature()), "836991d5e7f677080402e435e4687985e8e294892e57283b25dd99c169a56e263952595daf03b30fc066c2d7b3fa8db549b79f192dcc48585b7be943b7cb58b1"); EXPECT_EQ(tx.json(), ""); - EXPECT_EQ(tx.error(), ""); + EXPECT_EQ(tx.error_message(), ""); }; { @@ -676,7 +676,7 @@ namespace TW::LiquidStaking::tests { assertJSONEqual(tx.serialized(), expectedJson); EXPECT_EQ(TW::Base64::encode(data(tx.signature())), "p/zhNig+rCdfDbavR2iTZ9imVQW7TTdOUh+mhP7CpNkXWUc8L5FMCgmhvrHnQKd+8aWAkUERBg8P4Jy/rn1VUA=="); EXPECT_EQ(tx.json(), ""); - EXPECT_EQ(tx.error(), ""); + EXPECT_EQ(tx.error_message(), ""); }; { diff --git a/tests/common/PublicKeyTests.cpp b/tests/common/PublicKeyTests.cpp index def4b0391d1..4b452e24adc 100644 --- a/tests/common/PublicKeyTests.cpp +++ b/tests/common/PublicKeyTests.cpp @@ -309,7 +309,7 @@ TEST(PublicKeyTests, Recover) { const auto publicKey = PublicKey::recover(signature, message); EXPECT_EQ(publicKey.type, TWPublicKeyTypeSECP256k1Extended); EXPECT_EQ(hex(publicKey.bytes), - "0456d8089137b1fd0d890f8c7d4a04d0fd4520a30b19518ee87bd168ea12ed8090329274c4c6c0d9df04515776f2741eeffc30235d596065d718c3973e19711ad0"); + "0456d8089137b1fd0d890f8c7d4a04d0fd4520a30b19518ee87bd168ea12ed8090329274c4c6c0d9df04515776f2741eeffc30235d596065d718c3973e19711ad0"); } const auto privateKey = PrivateKey(parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904")); diff --git a/tests/common/TransactionCompilerTests.cpp b/tests/common/TransactionCompilerTests.cpp deleted file mode 100644 index 44d0f7d40c6..00000000000 --- a/tests/common/TransactionCompilerTests.cpp +++ /dev/null @@ -1,437 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include "TransactionCompiler.h" -#include "Coin.h" -#include "proto/Common.pb.h" -#include "proto/Binance.pb.h" -#include "proto/Bitcoin.pb.h" -#include "proto/Ethereum.pb.h" -#include "proto/TransactionCompiler.pb.h" - -#include -#include "Bitcoin/Script.h" -#include "Bitcoin/SegwitAddress.h" - -#include "HexCoding.h" -#include "PrivateKey.h" -#include "PublicKey.h" -#include "uint256.h" -#include - -#include "TestUtilities.h" -#include - -using namespace TW; - -TEST(TransactionCompiler, BinanceCompileWithSignatures) { - /// Step 1: Prepare transaction input (protobuf) - const auto coin = TWCoinTypeBinance; - const auto txInputData = TransactionCompiler::buildInput( - coin, - "bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", // from - "bnb1hlly02l6ahjsgxw9wlcswnlwdhg4xhx38yxpd5", // to - "1", // amount - "BNB", // asset - "", // memo - "Binance-Chain-Nile" // testnet chainId - ); - - { - // Check, by parsing - EXPECT_EQ(txInputData.size(), 88ul); - Binance::Proto::SigningInput input; - ASSERT_TRUE(input.ParseFromArray(txInputData.data(), (int)txInputData.size())); - EXPECT_EQ(input.chain_id(), "Binance-Chain-Nile"); - EXPECT_TRUE(input.has_send_order()); - ASSERT_EQ(input.send_order().inputs_size(), 1); - EXPECT_EQ(hex(data(input.send_order().inputs(0).address())), "40c2979694bbc961023d1d27be6fc4d21a9febe6"); - } - - /// Step 2: Obtain preimage hash - const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); - ASSERT_GT(preImageHashes.size(), 0ul); - - TxCompiler::Proto::PreSigningOutput preSigningOutput; - ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); - ASSERT_EQ(preSigningOutput.error(), 0); - - auto preImageHash = data(preSigningOutput.data_hash()); - EXPECT_EQ(hex(preImageHash), "3f3fece9059e714d303a9a1496ddade8f2c38fa78fc4cc2e505c5dbb0ea678d1"); - - // Simulate signature, normally obtained from signature server - const auto publicKeyData = parse_hex("026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e502"); - const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1); - const auto signature = parse_hex("1b1181faec30b60a2ddaa2804c253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5366bb8b041781a6771248550d04094c3d7a504f9e8310679"); - - // Verify signature (pubkey & hash & signature) - { - EXPECT_TRUE(publicKey.verify(signature, preImageHash)); - } - - /// Step 3: Compile transaction info - const Data outputData = TransactionCompiler::compileWithSignatures(coin, txInputData, {signature}, {publicKeyData}); - - const auto ExpectedTx = "b801f0625dee0a462a2c87fa0a1f0a1440c2979694bbc961023d1d27be6fc4d21a9febe612070a03424e421001121f0a14bffe47abfaede50419c577f1074fee6dd1535cd112070a03424e421001126a0a26eb5ae98721026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e50212401b1181faec30b60a2ddaa2804c253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5366bb8b041781a6771248550d04094c3d7a504f9e8310679"; - { - EXPECT_EQ(outputData.size(), 189ul); - Binance::Proto::SigningOutput output; - ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); - - EXPECT_EQ(hex(output.encoded()), ExpectedTx); - } - - { // Double check: check if simple signature process gives the same result. Note that private keys were not used anywhere up to this point. - Binance::Proto::SigningInput input; - ASSERT_TRUE(input.ParseFromArray(txInputData.data(), (int)txInputData.size())); - auto key = parse_hex("95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832"); - input.set_private_key(key.data(), key.size()); - - Binance::Proto::SigningOutput output; - ANY_SIGN(input, coin); - - ASSERT_EQ(hex(output.encoded()), ExpectedTx); - } -} - -TEST(TransactionCompiler, BitcoinCompileWithSignatures) { - // Test external signining with a Bitcoin transaction with 3 input UTXOs, all used, but only using 2 public keys. - // Three signatures are neeeded. This illustrates that order of UTXOs/hashes is not always the same. - - const auto revUtxoHash0 = parse_hex("07c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa8"); - const auto revUtxoHash1 = parse_hex("d6892a5aa54e3b8fe430efd23f49a8950733aaa9d7c915d9989179f48dd1905e"); - const auto revUtxoHash2 = parse_hex("6021efcf7555f90627364339fc921139dd40a06ccb2cb2a2a4f8f4ea7a2dc74d"); - const auto inPubKey0 = parse_hex("024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382"); - const auto inPubKey1 = parse_hex("0217142f69535e4dad0dc7060df645c55a174cc1bfa5b9eb2e59aad2ae96072dfc"); - const auto inPubKeyHash0 = parse_hex("bd92088bb7e82d611a9b94fbb74a0908152b784f"); - const auto inPubKeyHash1 = parse_hex("6641abedacf9483b793afe1718689cc9420bbb1c"); - - // Test data: Input UTXO infos - struct UtxoInfo { - Data revUtxoHash; - Data publicKey; - long amount; - int index; - }; - std::vector utxoInfos = { - // first - UtxoInfo {revUtxoHash0, inPubKey0, 600'000, 0}, - // second UTXO, with same pubkey - UtxoInfo {revUtxoHash1, inPubKey0, 500'000, 1}, - // third UTXO, with different pubkey - UtxoInfo {revUtxoHash2, inPubKey1, 400'000, 0}, - }; - - // Signature infos, indexed by pubkeyhash+hash - struct SignatureInfo { - Data signature; - Data publicKey; - }; - std::map signatureInfos = { - { - hex(inPubKeyHash0) + "+" + "a296bead4172007be69b21971a790e076388666c162a9505698415f1b003ebd7", - { - parse_hex("304402201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b34902200a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f2a40"), - inPubKey0, - } - }, - { - hex(inPubKeyHash1) + "+" + "505f527f00e15fcc5a2d2416c9970beb57dfdfaca99e572a01f143b24dd8fab6", - { - parse_hex("3044022041294880caa09bb1b653775310fcdd1458da6b8e7d7fae34e37966414fe115820220646397c9d2513edc5974ecc336e9b287de0cdf071c366f3b3dc3ff309213e4e4"), - inPubKey1, - } - }, - { - hex(inPubKeyHash0) + "+" + "60ed6e9371e5ddc72fd88e46a12cb2f68516ebd307c0fd31b1b55cf767272101", - { - parse_hex("30440220764e3d5b3971c4b3e70b23fb700a7462a6fe519d9830e863a1f8388c402ad0b102207e777f7972c636961f92375a2774af3b7a2a04190251bbcb31d19c70927952dc"), - inPubKey0, - } - }, - }; - - const auto coin = TWCoinTypeBitcoin; - const auto ownAddress = "bc1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z00ppggv"; - - // Setup input for Plan - Bitcoin::Proto::SigningInput signingInput; - signingInput.set_coin_type(coin); - signingInput.set_hash_type(TWBitcoinSigHashTypeAll); - signingInput.set_amount(1'200'000); - signingInput.set_use_max_amount(false); - signingInput.set_byte_fee(1); - signingInput.set_to_address("bc1q2dsdlq3343vk29runkgv4yc292hmq53jedfjmp"); - signingInput.set_change_address(ownAddress); - - // process UTXOs - int count = 0; - for (auto& u: utxoInfos) { - const auto publicKey = PublicKey(u.publicKey, TWPublicKeyTypeSECP256k1); - const auto address = Bitcoin::SegwitAddress(publicKey, "bc"); - if (count == 0) EXPECT_EQ(address.string(), ownAddress); - if (count == 1) EXPECT_EQ(address.string(), ownAddress); - if (count == 2) EXPECT_EQ(address.string(), "bc1qveq6hmdvl9yrk7f6lct3s6yue9pqhwcuxedggg"); - - const auto utxoScript = Bitcoin::Script::lockScriptForAddress(address.string(), coin); - if (count == 0) EXPECT_EQ(hex(utxoScript.bytes), "0014bd92088bb7e82d611a9b94fbb74a0908152b784f"); - if (count == 1) EXPECT_EQ(hex(utxoScript.bytes), "0014bd92088bb7e82d611a9b94fbb74a0908152b784f"); - if (count == 2) EXPECT_EQ(hex(utxoScript.bytes), "00146641abedacf9483b793afe1718689cc9420bbb1c"); - - Data keyHash; - EXPECT_TRUE(utxoScript.matchPayToWitnessPublicKeyHash(keyHash)); - if (count == 0) EXPECT_EQ(hex(keyHash), hex(inPubKeyHash0)); - if (count == 1) EXPECT_EQ(hex(keyHash), hex(inPubKeyHash0)); - if (count == 2) EXPECT_EQ(hex(keyHash), hex(inPubKeyHash1)); - - const auto redeemScript = Bitcoin::Script::buildPayToPublicKeyHash(keyHash); - if (count == 0) EXPECT_EQ(hex(redeemScript.bytes), "76a914bd92088bb7e82d611a9b94fbb74a0908152b784f88ac"); - if (count == 1) EXPECT_EQ(hex(redeemScript.bytes), "76a914bd92088bb7e82d611a9b94fbb74a0908152b784f88ac"); - if (count == 2) EXPECT_EQ(hex(redeemScript.bytes), "76a9146641abedacf9483b793afe1718689cc9420bbb1c88ac"); - (*signingInput.mutable_scripts())[hex(keyHash)] = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); - - auto utxo = signingInput.add_utxo(); - utxo->set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); - utxo->set_amount(u.amount); - utxo->mutable_out_point()->set_hash(std::string(u.revUtxoHash.begin(), u.revUtxoHash.end())); - utxo->mutable_out_point()->set_index(u.index); - utxo->mutable_out_point()->set_sequence(UINT32_MAX); - - ++count; - } - EXPECT_EQ(count, 3); - EXPECT_EQ(signingInput.utxo_size(), 3); - - // Plan - Bitcoin::Proto::TransactionPlan plan; - ANY_PLAN(signingInput, plan, coin); - - // At this point plan can be checked, assume it is accepted unmodified - EXPECT_EQ(plan.amount(), 1'200'000); - EXPECT_EQ(plan.fee(), 277); - EXPECT_EQ(plan.change(), 299'723); - ASSERT_EQ(plan.utxos_size(), 3); - // Note that UTXOs happen to be in reverse order compared to the input - EXPECT_EQ(hex(plan.utxos(0).out_point().hash()), hex(revUtxoHash2)); - EXPECT_EQ(hex(plan.utxos(1).out_point().hash()), hex(revUtxoHash1)); - EXPECT_EQ(hex(plan.utxos(2).out_point().hash()), hex(revUtxoHash0)); - - // Extend input with accepted plan - *signingInput.mutable_plan() = plan; - - // Serialize input - const auto txInputData = data(signingInput.SerializeAsString()); - EXPECT_EQ((int)txInputData.size(), 692); - - /// Step 2: Obtain preimage hashes - const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); - TW::Bitcoin::Proto::PreSigningOutput preSigningOutput; - ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), (int)preImageHashes.size())); - - ASSERT_EQ(preSigningOutput.error(), 0); - EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[0].data_hash()), "505f527f00e15fcc5a2d2416c9970beb57dfdfaca99e572a01f143b24dd8fab6"); - EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[1].data_hash()), "a296bead4172007be69b21971a790e076388666c162a9505698415f1b003ebd7"); - EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[2].data_hash()), "60ed6e9371e5ddc72fd88e46a12cb2f68516ebd307c0fd31b1b55cf767272101"); - EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[0].public_key_hash()), hex(inPubKeyHash1)); - EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[1].public_key_hash()), hex(inPubKeyHash0)); - EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[2].public_key_hash()), hex(inPubKeyHash0)); - - // Simulate signatures, normally they are obtained from external source, e.g. a signature server. - std::vector signatureVec; - std::vector pubkeyVec; - for (const auto& h: preSigningOutput.hash_public_keys()) { - const auto& preImageHash = h.data_hash(); - const auto& pubkeyhash = h.public_key_hash(); - - const std::string key = hex(pubkeyhash) + "+" + hex(preImageHash); - const auto sigInfoFind = signatureInfos.find(key); - ASSERT_TRUE(sigInfoFind != signatureInfos.end()); - const auto& sigInfo = std::get<1>(*sigInfoFind); - const auto& publicKeyData = sigInfo.publicKey; - const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1); - const auto signature = sigInfo.signature; - - signatureVec.push_back(signature); - pubkeyVec.push_back(publicKeyData); - - // Verify signature (pubkey & hash & signature) - EXPECT_TRUE(publicKey.verifyAsDER(signature, TW::Data(preImageHash.begin(), preImageHash.end()))); - } - - /// Step 3: Compile transaction info - const Data compileWithSignatures = TransactionCompiler::compileWithSignatures(coin, txInputData, signatureVec, pubkeyVec); - - const auto ExpectedTx = "010000000001036021efcf7555f90627364339fc921139dd40a06ccb2cb2a2a4f8f4ea7a2dc74d0000000000ffffffffd6892a5aa54e3b8fe430efd23f49a8950733aaa9d7c915d9989179f48dd1905e0100000000ffffffff07c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa80000000000ffffffff02804f1200000000001600145360df8231ac5965147c9d90ca930a2aafb05232cb92040000000000160014bd92088bb7e82d611a9b94fbb74a0908152b784f02473044022041294880caa09bb1b653775310fcdd1458da6b8e7d7fae34e37966414fe115820220646397c9d2513edc5974ecc336e9b287de0cdf071c366f3b3dc3ff309213e4e401210217142f69535e4dad0dc7060df645c55a174cc1bfa5b9eb2e59aad2ae96072dfc0247304402201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b34902200a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f2a400121024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382024730440220764e3d5b3971c4b3e70b23fb700a7462a6fe519d9830e863a1f8388c402ad0b102207e777f7972c636961f92375a2774af3b7a2a04190251bbcb31d19c70927952dc0121024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb49338200000000"; - { - EXPECT_EQ(compileWithSignatures.size(), 786ul); - Bitcoin::Proto::SigningOutput output; - ASSERT_TRUE(output.ParseFromArray(compileWithSignatures.data(), (int)compileWithSignatures.size())); - - EXPECT_EQ(output.encoded().size(), 518ul); - EXPECT_EQ(hex(output.encoded()), ExpectedTx); - } - - { // Double check: check if simple signature process gives the same result. Note that private keys were not used anywhere up to this point. - Bitcoin::Proto::SigningInput input; - ASSERT_TRUE(input.ParseFromArray(txInputData.data(), (int)txInputData.size())); - - // 2 private keys are needed (despite >2 UTXOs) - auto key0 = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); - auto key1 = parse_hex("7878787878787878787878787878787878787878787878787878787878787878"); - EXPECT_EQ(hex(PrivateKey(key0).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), hex(inPubKey0)); - EXPECT_EQ(hex(PrivateKey(key1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), hex(inPubKey1)); - *input.add_private_key() = std::string(key0.begin(), key0.end()); - *input.add_private_key() = std::string(key1.begin(), key1.end()); - - Bitcoin::Proto::SigningOutput output; - ANY_SIGN(input, coin); - - ASSERT_EQ(hex(output.encoded()), ExpectedTx); - } - - { // Negative: not enough signatures - const Data outputData = TransactionCompiler::compileWithSignatures(coin, txInputData, {signatureVec[0]}, pubkeyVec); - EXPECT_GT(outputData.size(), 1ul); - Bitcoin::Proto::SigningOutput output; - ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); - EXPECT_EQ(output.encoded().size(), 0ul); - EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); - } - { // Negative: invalid public key - const auto publicKeyBlake = parse_hex("b689ab808542e13f3d2ec56fe1efe43a1660dcadc73ce489fde7df98dd8ce5d9"); - EXPECT_EXCEPTION(TransactionCompiler::compileWithSignatures(coin, txInputData, signatureVec, - {pubkeyVec[0], pubkeyVec[1], publicKeyBlake}), "Invalid public key"); - } - { // Negative: wrong signature (formally valid) - const Data outputData = TransactionCompiler::compileWithSignatures(coin, txInputData, - {parse_hex("415502201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b34902200a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f3b51"), - signatureVec[1], signatureVec[2]}, - pubkeyVec); - EXPECT_EQ(outputData.size(), 2ul); - Bitcoin::Proto::SigningOutput output; - ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); - EXPECT_EQ(output.encoded().size(), 0ul); - EXPECT_EQ(output.error(), Common::Proto::Error_signing); - } -} - -TEST(TransactionCompiler, EthereumCompileWithSignatures) { - /// Step 1: Prepare transaction input (protobuf) - const auto coin = TWCoinTypeEthereum; - const auto txInputData0 = TransactionCompiler::buildInput( - coin, - "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F", // from - "0x3535353535353535353535353535353535353535", // to - "1000000000000000000", // amount - "ETH", // asset - "", // memo - "" // chainId - ); - - // Check, by parsing - EXPECT_EQ((int)txInputData0.size(), 61); - Ethereum::Proto::SigningInput signingInput; - ASSERT_TRUE(signingInput.ParseFromArray(txInputData0.data(), (int)txInputData0.size())); - EXPECT_EQ(hex(signingInput.chain_id()), "01"); - EXPECT_EQ(signingInput.to_address(), "0x3535353535353535353535353535353535353535"); - ASSERT_TRUE(signingInput.transaction().has_transfer()); - EXPECT_EQ(hex(signingInput.transaction().transfer().amount()), "0de0b6b3a7640000"); - - // Set a few other values - const auto nonce = store(uint256_t(11)); - const auto gasPrice = store(uint256_t(20000000000)); - const auto gasLimit = store(uint256_t(21000)); - signingInput.set_nonce(nonce.data(), nonce.size()); - signingInput.set_gas_price(gasPrice.data(), gasPrice.size()); - signingInput.set_gas_limit(gasLimit.data(), gasLimit.size()); - signingInput.set_tx_mode(Ethereum::Proto::Legacy); - - // Serialize back, this shows how to serialize input protobuf to byte array - const auto txInputData = data(signingInput.SerializeAsString()); - EXPECT_EQ((int)txInputData.size(), 75); - - /// Step 2: Obtain preimage hash - const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); - ASSERT_GT(preImageHashes.size(), 0ul); - - TxCompiler::Proto::PreSigningOutput preSigningOutput; - ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); - ASSERT_EQ(preSigningOutput.error(), 0); - - auto preImageHash = data(preSigningOutput.data_hash()); - EXPECT_EQ(hex(preImageHash), "15e180a6274b2f6a572b9b51823fce25ef39576d10188ecdcd7de44526c47217"); - - // Simulate signature, normally obtained from signature server - const Data publicKeyData = parse_hex("044bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382ce28cab79ad7119ee1ad3ebcdb98a16805211530ecc6cfefa1b88e6dff99232a"); - const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1Extended); - const auto signature = parse_hex("360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07b53bd9d1376e15a191d844db458893b928f3efbfee90c9febf51ab84c9796677900"); - - // Verify signature (pubkey & hash & signature) - { - EXPECT_TRUE(publicKey.verify(signature, preImageHash)); - } - - /// Step 3: Compile transaction info - const Data outputData = TransactionCompiler::compileWithSignatures(coin, txInputData, {signature}, {publicKeyData}); - - const auto ExpectedTx = "f86c0b8504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a0360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07ba053bd9d1376e15a191d844db458893b928f3efbfee90c9febf51ab84c97966779"; - { - EXPECT_EQ(outputData.size(), 217ul); - Ethereum::Proto::SigningOutput output; - ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); - - EXPECT_EQ(output.encoded().size(), 110ul); - EXPECT_EQ(hex(output.encoded()), ExpectedTx); - } - - { // Double check: check if simple signature process gives the same result. Note that private keys were not used anywhere up to this point. - Ethereum::Proto::SigningInput input; - ASSERT_TRUE(input.ParseFromArray(txInputData.data(), (int)txInputData.size())); - auto key = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); - input.set_private_key(key.data(), key.size()); - - Ethereum::Proto::SigningOutput output; - ANY_SIGN(input, coin); - - ASSERT_EQ(hex(output.encoded()), ExpectedTx); - } -} - -TEST(TransactionCompiler, EthereumBuildTransactionInput) { - const auto coin = TWCoinTypeEthereum; - const auto txInputData0 = TransactionCompiler::buildInput( - coin, - "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F", // from - "0x3535353535353535353535353535353535353535", // to - "1000000000000000000", // amount - "ETH", // asset - "Memo", // memo - "05" // chainId - ); - - // Check, by parsing - EXPECT_EQ((int)txInputData0.size(), 61); - Ethereum::Proto::SigningInput input; - ASSERT_TRUE(input.ParseFromArray(txInputData0.data(), (int)txInputData0.size())); - EXPECT_EQ(hex(input.chain_id()), "05"); - EXPECT_EQ(input.to_address(), "0x3535353535353535353535353535353535353535"); - ASSERT_TRUE(input.transaction().has_transfer()); - EXPECT_EQ(hex(input.transaction().transfer().amount()), "0de0b6b3a7640000"); -} - -TEST(TransactionCompiler, EthereumBuildTransactionInputInvalidAddress) { - const auto coin = TWCoinTypeEthereum; - EXPECT_EXCEPTION(TransactionCompiler::buildInput( - coin, - "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F", // from - "__INVALID_ADDRESS__", // to - "1000000000000000000", // amount - "ETH", // asset - "", // memo - "" // chainId - ), "Invalid to address"); -} diff --git a/tests/interface/TWCoinTypeTests.cpp b/tests/interface/TWCoinTypeTests.cpp index 57b171b5f84..fa80b967903 100644 --- a/tests/interface/TWCoinTypeTests.cpp +++ b/tests/interface/TWCoinTypeTests.cpp @@ -7,6 +7,7 @@ #include "TestUtilities.h" #include +#include #include @@ -66,6 +67,7 @@ TEST(TWCoinType, TWPurpose) { ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeRavencoin)); ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeWaves)); ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeNEO)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeNebl)); } TEST(TWCoinType, TWHDVersion) { @@ -138,6 +140,20 @@ TEST(TWCoinType, TWPublicKeyType) { ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeRavencoin)); ASSERT_EQ(TWPublicKeyTypeCURVE25519, TWCoinTypePublicKeyType(TWCoinTypeWaves)); ASSERT_EQ(TWPublicKeyTypeNIST256p1, TWCoinTypePublicKeyType(TWCoinTypeNEO)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeNebl)); +} + +TEST(TWCoinType, ValidateAddress) { + ASSERT_TRUE(TWCoinTypeValidate(TWCoinTypeBitcoin, STRING("12dNaXQtN5Asn2YFwT1cvciCrJa525fAe4").get())); + ASSERT_TRUE(TWCoinTypeValidate(TWCoinTypeBitcoin, STRING("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4").get())); +} + +TEST(TWCoinType, DeriveAddress) { + auto pkData = DATA("0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798"); + auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(pkData.get(), TWPublicKeyTypeSECP256k1)); + + auto address = WRAPS(TWCoinTypeDeriveAddressFromPublicKeyAndDerivation(TWCoinTypeBitcoin, publicKey.get(), TWDerivationBitcoinSegwit)); + assertStringsEqual(address, "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"); } TEST(TWCoinType, TWCoinTypeDerivationPath) { diff --git a/tests/interface/TWHDWalletTests.cpp b/tests/interface/TWHDWalletTests.cpp index 3bdd2d040dc..2fd888d767a 100644 --- a/tests/interface/TWHDWalletTests.cpp +++ b/tests/interface/TWHDWalletTests.cpp @@ -81,6 +81,11 @@ TEST(HDWallet, CreateFromMnemonicNoPassword) { assertSeedEq(wallet, "354c22aedb9a37407adc61f657a6f00d10ed125efa360215f36c6919abd94d6dbc193a5f9c495e21ee74118661e327e84a5f5f11fa373ec33b80897d4697557d"); } +TEST(HDWallet, CreateFromMnemonicCheck) { + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonicCheck(gWords.get(), STRING("").get(), false)); + assertSeedEq(wallet, "354c22aedb9a37407adc61f657a6f00d10ed125efa360215f36c6919abd94d6dbc193a5f9c495e21ee74118661e327e84a5f5f11fa373ec33b80897d4697557d"); +} + TEST(HDWallet, CreateFromStrengthInvalid) { auto wallet = WRAP(TWHDWallet, TWHDWalletCreate(64, STRING("").get())); ASSERT_EQ(wallet.get(), nullptr); diff --git a/tests/interface/TWHRPTests.cpp b/tests/interface/TWHRPTests.cpp index 443d138588b..279ccd82b15 100644 --- a/tests/interface/TWHRPTests.cpp +++ b/tests/interface/TWHRPTests.cpp @@ -123,4 +123,5 @@ TEST(TWHPR, HPRByCoinType) { ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeRavencoin)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeWaves)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeNEO)); + ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeNebl)); } diff --git a/tests/interface/TWStringTests.cpp b/tests/interface/TWStringTests.cpp index e318468e07d..fc2f06adfc8 100644 --- a/tests/interface/TWStringTests.cpp +++ b/tests/interface/TWStringTests.cpp @@ -21,3 +21,19 @@ TEST(StringTests, HexNumber) { auto string = WRAPS(TWStringCreateWithHexData(data.get())); ASSERT_STREQ(TWStringUTF8Bytes(string.get()), "deadbeef"); } + +TEST(StringTests, GetChar) { + uint8_t bytes[] = { 0xde, 0xad, 0xbe, 0xef }; + auto data = WRAPD(TWDataCreateWithBytes(bytes, 4)); + auto string = WRAPS(TWStringCreateWithHexData(data.get())); + ASSERT_STREQ(TWStringUTF8Bytes(string.get()), "deadbeef"); + + ASSERT_EQ(TWStringGet(string.get(), 0), 'd'); + ASSERT_EQ(TWStringGet(string.get(), 1), 'e'); + ASSERT_EQ(TWStringGet(string.get(), 2), 'a'); + ASSERT_EQ(TWStringGet(string.get(), 3), 'd'); + ASSERT_EQ(TWStringGet(string.get(), 4), 'b'); + ASSERT_EQ(TWStringGet(string.get(), 5), 'e'); + ASSERT_EQ(TWStringGet(string.get(), 6), 'e'); + ASSERT_EQ(TWStringGet(string.get(), 7), 'f'); +} \ No newline at end of file diff --git a/tests/interface/TWTransactionCompilerTests.cpp b/tests/interface/TWTransactionCompilerTests.cpp index b33b64a8826..ceea527b389 100644 --- a/tests/interface/TWTransactionCompilerTests.cpp +++ b/tests/interface/TWTransactionCompilerTests.cpp @@ -4,17 +4,21 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include -#include -#include #include "proto/Binance.pb.h" #include "proto/Bitcoin.pb.h" #include "proto/Ethereum.pb.h" +#include "proto/NULS.pb.h" +#include "proto/Ripple.pb.h" +#include "proto/Solana.pb.h" #include "proto/TransactionCompiler.pb.h" +#include +#include +#include -#include #include "Bitcoin/Script.h" #include "Bitcoin/SegwitAddress.h" +#include "NULS/Address.h" +#include #include "HexCoding.h" #include "PrivateKey.h" @@ -25,9 +29,9 @@ #include "TestUtilities.h" #include -#include #include #include +#include using namespace TW; @@ -48,11 +52,13 @@ TEST(TWTransactionCompiler, ExternalSignatureSignBinance) { // Check, by parsing EXPECT_EQ((int)TWDataSize(txInputData.get()), 88); Binance::Proto::SigningInput input; - ASSERT_TRUE(input.ParseFromArray(TWDataBytes(txInputData.get()), (int)TWDataSize(txInputData.get()))); + ASSERT_TRUE(input.ParseFromArray(TWDataBytes(txInputData.get()), + (int)TWDataSize(txInputData.get()))); EXPECT_EQ(input.chain_id(), "Binance-Chain-Nile"); EXPECT_TRUE(input.has_send_order()); ASSERT_EQ(input.send_order().inputs_size(), 1); - EXPECT_EQ(hex(data(input.send_order().inputs(0).address())), "40c2979694bbc961023d1d27be6fc4d21a9febe6"); + EXPECT_EQ(hex(data(input.send_order().inputs(0).address())), + "40c2979694bbc961023d1d27be6fc4d21a9febe6"); } /// Step 2: Obtain preimage hash @@ -61,41 +67,49 @@ TEST(TWTransactionCompiler, ExternalSignatureSignBinance) { TxCompiler::Proto::PreSigningOutput preSigningOutput; ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHash.data(), int(preImageHash.size()))); - ASSERT_EQ(preSigningOutput.error(), 0); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + const auto preImageHashData = data(preSigningOutput.data_hash()); EXPECT_EQ(hex(preImageHashData), "3f3fece9059e714d303a9a1496ddade8f2c38fa78fc4cc2e505c5dbb0ea678d1"); // Simulate signature, normally obtained from signature server - const auto publicKeyData = parse_hex("026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e502"); + const auto publicKeyData = + parse_hex("026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e502"); const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1); - const auto signature = parse_hex("1b1181faec30b60a2ddaa2804c253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5366bb8b041781a6771248550d04094c3d7a504f9e8310679"); + const auto signature = + parse_hex("1b1181faec30b60a2ddaa2804c253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5" + "366bb8b041781a6771248550d04094c3d7a504f9e8310679"); // Verify signature (pubkey & hash & signature) - { - EXPECT_TRUE(publicKey.verify(signature, preImageHashData)); - } + { EXPECT_TRUE(publicKey.verify(signature, preImageHashData)); } /// Step 3: Compile transaction info const auto outputData = WRAPD(TWTransactionCompilerCompileWithSignatures( - coin, - txInputData.get(), + coin, txInputData.get(), WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&signature)).get(), - WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&publicKeyData)).get()) - ); - - const auto ExpectedTx = "b801f0625dee0a462a2c87fa0a1f0a1440c2979694bbc961023d1d27be6fc4d21a9febe612070a03424e421001121f0a14bffe47abfaede50419c577f1074fee6dd1535cd112070a03424e421001126a0a26eb5ae98721026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e50212401b1181faec30b60a2ddaa2804c253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5366bb8b041781a6771248550d04094c3d7a504f9e8310679"; + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&publicKeyData)).get())); + + const auto ExpectedTx = + "b801f0625dee0a462a2c87fa0a1f0a1440c2979694bbc961023d1d27be6fc4d21a9febe612070a03424e421001" + "121f0a14bffe47abfaede50419c577f1074fee6dd1535cd112070a03424e421001126a0a26eb5ae98721026a35" + "920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e50212401b1181faec30b60a2ddaa2804c" + "253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5366bb8b041781a6771248550d04094c3d7a5" + "04f9e8310679"; { EXPECT_EQ(TWDataSize(outputData.get()), 189ul); Binance::Proto::SigningOutput output; - ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), (int)TWDataSize(outputData.get()))); + ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), + (int)TWDataSize(outputData.get()))); EXPECT_EQ(hex(output.encoded()), ExpectedTx); } - { // Double check: check if simple signature process gives the same result. Note that private keys were not used anywhere up to this point. + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. Binance::Proto::SigningInput input; - ASSERT_TRUE(input.ParseFromArray(TWDataBytes(txInputData.get()), (int)TWDataSize(txInputData.get()))); + ASSERT_TRUE(input.ParseFromArray(TWDataBytes(txInputData.get()), + (int)TWDataSize(txInputData.get()))); auto key = parse_hex("95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832"); input.set_private_key(key.data(), key.size()); @@ -111,13 +125,13 @@ TEST(TWTransactionCompiler, ExternalSignatureSignEthereum) { const auto coin = TWCoinTypeEthereum; const auto txInputData0 = WRAPD(TWTransactionCompilerBuildInput( coin, - STRING("0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F").get(), // from - STRING("0x3535353535353535353535353535353535353535").get(), // to - STRING("1000000000000000000").get(), // amount - STRING("ETH").get(), // asset - STRING("").get(), // memo - STRING("").get() // chainId - )); + STRING("0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F").get(), // from + STRING("0x3535353535353535353535353535353535353535").get(), // to + STRING("1000000000000000000").get(), // amount + STRING("ETH").get(), // asset + STRING("").get(), // memo + STRING("").get() // chainId + )); // Check, by parsing EXPECT_EQ((int)TWDataSize(txInputData0.get()), 61); @@ -148,41 +162,48 @@ TEST(TWTransactionCompiler, ExternalSignatureSignEthereum) { TxCompiler::Proto::PreSigningOutput preSigningOutput; ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHash.data(), int(preImageHash.size()))); - ASSERT_EQ(preSigningOutput.error(), 0); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + const auto preImageHashData = data(preSigningOutput.data_hash()); EXPECT_EQ(hex(preImageHashData), "15e180a6274b2f6a572b9b51823fce25ef39576d10188ecdcd7de44526c47217"); // Simulate signature, normally obtained from signature server - const auto publicKeyData = parse_hex("044bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382ce28cab79ad7119ee1ad3ebcdb98a16805211530ecc6cfefa1b88e6dff99232a"); + const auto publicKeyData = + parse_hex("044bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382ce28cab79ad711" + "9ee1ad3ebcdb98a16805211530ecc6cfefa1b88e6dff99232a"); const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1Extended); - const auto signature = parse_hex("360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07b53bd9d1376e15a191d844db458893b928f3efbfee90c9febf51ab84c9796677900"); + const auto signature = + parse_hex("360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07b53bd9d1376e15a19" + "1d844db458893b928f3efbfee90c9febf51ab84c9796677900"); // Verify signature (pubkey & hash & signature) - { - EXPECT_TRUE(publicKey.verify(signature, preImageHashData)); - } + { EXPECT_TRUE(publicKey.verify(signature, preImageHashData)); } /// Step 3: Compile transaction info const auto outputData = WRAPD(TWTransactionCompilerCompileWithSignatures( - coin, - txInputData.get(), + coin, txInputData.get(), WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&signature)).get(), - WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&publicKeyData)).get()) - ); + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&publicKeyData)).get())); - const auto ExpectedTx = "f86c0b8504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a0360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07ba053bd9d1376e15a191d844db458893b928f3efbfee90c9febf51ab84c97966779"; + const auto ExpectedTx = + "f86c0b8504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a0" + "360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07ba053bd9d1376e15a191d844db4" + "58893b928f3efbfee90c9febf51ab84c97966779"; { EXPECT_EQ(TWDataSize(outputData.get()), 217ul); Ethereum::Proto::SigningOutput output; - ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), (int)TWDataSize(outputData.get()))); + ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), + (int)TWDataSize(outputData.get()))); EXPECT_EQ(output.encoded().size(), 110ul); EXPECT_EQ(hex(output.encoded()), ExpectedTx); } - { // Double check: check if simple signature process gives the same result. Note that private keys were not used anywhere up to this point. + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. Ethereum::Proto::SigningInput input; - ASSERT_TRUE(input.ParseFromArray(TWDataBytes(txInputData.get()), (int)TWDataSize(txInputData.get()))); + ASSERT_TRUE(input.ParseFromArray(TWDataBytes(txInputData.get()), + (int)TWDataSize(txInputData.get()))); auto key = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); input.set_private_key(key.data(), key.size()); @@ -202,11 +223,16 @@ TEST(TWTransactionCompiler, ExternalSignatureSignBitcoin) { // Test external signining with a Bitcoin transaction with 3 input UTXOs, all used, but only using 2 public keys. // Three signatures are neeeded. This illustrates that order of UTXOs/hashes is not always the same. - const auto revUtxoHash0 = parse_hex("07c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa8"); - const auto revUtxoHash1 = parse_hex("d6892a5aa54e3b8fe430efd23f49a8950733aaa9d7c915d9989179f48dd1905e"); - const auto revUtxoHash2 = parse_hex("6021efcf7555f90627364339fc921139dd40a06ccb2cb2a2a4f8f4ea7a2dc74d"); - const auto inPubKey0 = parse_hex("024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382"); - const auto inPubKey1 = parse_hex("0217142f69535e4dad0dc7060df645c55a174cc1bfa5b9eb2e59aad2ae96072dfc"); + const auto revUtxoHash0 = + parse_hex("07c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa8"); + const auto revUtxoHash1 = + parse_hex("d6892a5aa54e3b8fe430efd23f49a8950733aaa9d7c915d9989179f48dd1905e"); + const auto revUtxoHash2 = + parse_hex("6021efcf7555f90627364339fc921139dd40a06ccb2cb2a2a4f8f4ea7a2dc74d"); + const auto inPubKey0 = + parse_hex("024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382"); + const auto inPubKey1 = + parse_hex("0217142f69535e4dad0dc7060df645c55a174cc1bfa5b9eb2e59aad2ae96072dfc"); const auto inPubKeyHash0 = parse_hex("bd92088bb7e82d611a9b94fbb74a0908152b784f"); const auto inPubKeyHash1 = parse_hex("6641abedacf9483b793afe1718689cc9420bbb1c"); @@ -219,11 +245,11 @@ TEST(TWTransactionCompiler, ExternalSignatureSignBitcoin) { }; std::vector utxoInfos = { // first - UtxoInfo {revUtxoHash0, inPubKey0, 600'000, 0}, + UtxoInfo{revUtxoHash0, inPubKey0, 600'000, 0}, // second UTXO, with same pubkey - UtxoInfo {revUtxoHash1, inPubKey0, 500'000, 1}, + UtxoInfo{revUtxoHash1, inPubKey0, 500'000, 1}, // third UTXO, with different pubkey - UtxoInfo {revUtxoHash2, inPubKey1, 400'000, 0}, + UtxoInfo{revUtxoHash2, inPubKey1, 400'000, 0}, }; // Signature infos, indexed by pubkeyhash+hash @@ -232,27 +258,27 @@ TEST(TWTransactionCompiler, ExternalSignatureSignBitcoin) { Data publicKey; }; std::map signatureInfos = { - { - hex(inPubKeyHash0) + "+" + "a296bead4172007be69b21971a790e076388666c162a9505698415f1b003ebd7", - { - parse_hex("304402201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b34902200a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f2a40"), - inPubKey0, - } - }, - { - hex(inPubKeyHash1) + "+" + "505f527f00e15fcc5a2d2416c9970beb57dfdfaca99e572a01f143b24dd8fab6", - { - parse_hex("3044022041294880caa09bb1b653775310fcdd1458da6b8e7d7fae34e37966414fe115820220646397c9d2513edc5974ecc336e9b287de0cdf071c366f3b3dc3ff309213e4e4"), - inPubKey1, - } - }, - { - hex(inPubKeyHash0) + "+" + "60ed6e9371e5ddc72fd88e46a12cb2f68516ebd307c0fd31b1b55cf767272101", - { - parse_hex("30440220764e3d5b3971c4b3e70b23fb700a7462a6fe519d9830e863a1f8388c402ad0b102207e777f7972c636961f92375a2774af3b7a2a04190251bbcb31d19c70927952dc"), - inPubKey0, - } - }, + {hex(inPubKeyHash0) + "+" + + "a296bead4172007be69b21971a790e076388666c162a9505698415f1b003ebd7", + { + parse_hex("304402201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b349022" + "00a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f2a40"), + inPubKey0, + }}, + {hex(inPubKeyHash1) + "+" + + "505f527f00e15fcc5a2d2416c9970beb57dfdfaca99e572a01f143b24dd8fab6", + { + parse_hex("3044022041294880caa09bb1b653775310fcdd1458da6b8e7d7fae34e37966414fe11582022" + "0646397c9d2513edc5974ecc336e9b287de0cdf071c366f3b3dc3ff309213e4e4"), + inPubKey1, + }}, + {hex(inPubKeyHash0) + "+" + + "60ed6e9371e5ddc72fd88e46a12cb2f68516ebd307c0fd31b1b55cf767272101", + { + parse_hex("30440220764e3d5b3971c4b3e70b23fb700a7462a6fe519d9830e863a1f8388c402ad0b1022" + "07e777f7972c636961f92375a2774af3b7a2a04190251bbcb31d19c70927952dc"), + inPubKey0, + }}, }; const auto coin = TWCoinTypeBitcoin; @@ -270,23 +296,32 @@ TEST(TWTransactionCompiler, ExternalSignatureSignBitcoin) { // process UTXOs int count = 0; - for (auto& u: utxoInfos) { + for (auto& u : utxoInfos) { const auto publicKey = PublicKey(u.publicKey, TWPublicKeyTypeSECP256k1); const auto address = Bitcoin::SegwitAddress(publicKey, "bc"); - if (count == 0) EXPECT_EQ(address.string(), ownAddress); - if (count == 1) EXPECT_EQ(address.string(), ownAddress); - if (count == 2) EXPECT_EQ(address.string(), "bc1qveq6hmdvl9yrk7f6lct3s6yue9pqhwcuxedggg"); + if (count == 0) + EXPECT_EQ(address.string(), ownAddress); + if (count == 1) + EXPECT_EQ(address.string(), ownAddress); + if (count == 2) + EXPECT_EQ(address.string(), "bc1qveq6hmdvl9yrk7f6lct3s6yue9pqhwcuxedggg"); const auto utxoScript = Bitcoin::Script::lockScriptForAddress(address.string(), coin); - if (count == 0) EXPECT_EQ(hex(utxoScript.bytes), "0014bd92088bb7e82d611a9b94fbb74a0908152b784f"); - if (count == 1) EXPECT_EQ(hex(utxoScript.bytes), "0014bd92088bb7e82d611a9b94fbb74a0908152b784f"); - if (count == 2) EXPECT_EQ(hex(utxoScript.bytes), "00146641abedacf9483b793afe1718689cc9420bbb1c"); + if (count == 0) + EXPECT_EQ(hex(utxoScript.bytes), "0014bd92088bb7e82d611a9b94fbb74a0908152b784f"); + if (count == 1) + EXPECT_EQ(hex(utxoScript.bytes), "0014bd92088bb7e82d611a9b94fbb74a0908152b784f"); + if (count == 2) + EXPECT_EQ(hex(utxoScript.bytes), "00146641abedacf9483b793afe1718689cc9420bbb1c"); Data keyHash; EXPECT_TRUE(utxoScript.matchPayToWitnessPublicKeyHash(keyHash)); - if (count == 0) EXPECT_EQ(hex(keyHash), hex(inPubKeyHash0)); - if (count == 1) EXPECT_EQ(hex(keyHash), hex(inPubKeyHash0)); - if (count == 2) EXPECT_EQ(hex(keyHash), hex(inPubKeyHash1)); + if (count == 0) + EXPECT_EQ(hex(keyHash), hex(inPubKeyHash0)); + if (count == 1) + EXPECT_EQ(hex(keyHash), hex(inPubKeyHash0)); + if (count == 2) + EXPECT_EQ(hex(keyHash), hex(inPubKeyHash1)); const auto redeemScript = Bitcoin::Script::buildPayToPublicKeyHash(keyHash); if (count == 0) EXPECT_EQ(hex(redeemScript.bytes), "76a914bd92088bb7e82d611a9b94fbb74a0908152b784f88ac"); @@ -297,7 +332,8 @@ TEST(TWTransactionCompiler, ExternalSignatureSignBitcoin) { auto utxo = signingInput.add_utxo(); utxo->set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); utxo->set_amount(u.amount); - utxo->mutable_out_point()->set_hash(std::string(u.revUtxoHash.begin(), u.revUtxoHash.end())); + utxo->mutable_out_point()->set_hash( + std::string(u.revUtxoHash.begin(), u.revUtxoHash.end())); utxo->mutable_out_point()->set_index(u.index); utxo->mutable_out_point()->set_sequence(UINT32_MAX); @@ -326,7 +362,6 @@ TEST(TWTransactionCompiler, ExternalSignatureSignBitcoin) { // Serialize signingInput const auto txInputDataData = data(signingInput.SerializeAsString()); const auto txInputData = WRAPD(TWDataCreateWithBytes(txInputDataData.data(), txInputDataData.size())); - EXPECT_EQ((int)TWDataSize(txInputData.get()), 692); /// Step 2: Obtain preimage hashes const auto preImageHashes = WRAPD(TWTransactionCompilerPreImageHashes(coin, txInputData.get())); @@ -364,14 +399,26 @@ TEST(TWTransactionCompiler, ExternalSignatureSignBitcoin) { } /// Step 3: Compile transaction info const auto outputData = WRAPD(TWTransactionCompilerCompileWithSignatures( - coin, txInputData.get(), signatureVec.get(), pubkeyVec.get() - )); - - const auto ExpectedTx = "010000000001036021efcf7555f90627364339fc921139dd40a06ccb2cb2a2a4f8f4ea7a2dc74d0000000000ffffffffd6892a5aa54e3b8fe430efd23f49a8950733aaa9d7c915d9989179f48dd1905e0100000000ffffffff07c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa80000000000ffffffff02804f1200000000001600145360df8231ac5965147c9d90ca930a2aafb05232cb92040000000000160014bd92088bb7e82d611a9b94fbb74a0908152b784f02473044022041294880caa09bb1b653775310fcdd1458da6b8e7d7fae34e37966414fe115820220646397c9d2513edc5974ecc336e9b287de0cdf071c366f3b3dc3ff309213e4e401210217142f69535e4dad0dc7060df645c55a174cc1bfa5b9eb2e59aad2ae96072dfc0247304402201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b34902200a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f2a400121024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382024730440220764e3d5b3971c4b3e70b23fb700a7462a6fe519d9830e863a1f8388c402ad0b102207e777f7972c636961f92375a2774af3b7a2a04190251bbcb31d19c70927952dc0121024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb49338200000000"; + coin, txInputData.get(), signatureVec.get(), pubkeyVec.get())); + + const auto ExpectedTx = + "010000000001036021efcf7555f90627364339fc921139dd40a06ccb2cb2a2a4f8f4ea7a2dc74d0000000000ff" + "ffffffd6892a5aa54e3b8fe430efd23f49a8950733aaa9d7c915d9989179f48dd1905e0100000000ffffffff07" + "c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa80000000000ffffffff02804f1200" + "000000001600145360df8231ac5965147c9d90ca930a2aafb05232cb92040000000000160014bd92088bb7e82d" + "611a9b94fbb74a0908152b784f02473044022041294880caa09bb1b653775310fcdd1458da6b8e7d7fae34e379" + "66414fe115820220646397c9d2513edc5974ecc336e9b287de0cdf071c366f3b3dc3ff309213e4e40121021714" + "2f69535e4dad0dc7060df645c55a174cc1bfa5b9eb2e59aad2ae96072dfc0247304402201857bc6e6e48b46046" + "a4bd204136fc77e24c240943fb5a1f0e86387aae59b34902200a7f31478784e51c49f46ef072745a4f263d7efd" + "bc9c6784aa2571ff4f6f2a400121024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb49" + "3382024730440220764e3d5b3971c4b3e70b23fb700a7462a6fe519d9830e863a1f8388c402ad0b102207e777f" + "7972c636961f92375a2774af3b7a2a04190251bbcb31d19c70927952dc0121024bc2a31265153f07e70e0bab08" + "724e6b85e217f8cd628ceb62974247bb49338200000000"; { EXPECT_EQ(TWDataSize(outputData.get()), 786ul); Bitcoin::Proto::SigningOutput output; - ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), (int)TWDataSize(outputData.get()))); + ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), + (int)TWDataSize(outputData.get()))); EXPECT_EQ(output.encoded().size(), 518ul); EXPECT_EQ(hex(output.encoded()), ExpectedTx); @@ -384,8 +431,10 @@ TEST(TWTransactionCompiler, ExternalSignatureSignBitcoin) { // 2 private keys are needed (despite >2 UTXOs) auto key0 = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); auto key1 = parse_hex("7878787878787878787878787878787878787878787878787878787878787878"); - EXPECT_EQ(hex(PrivateKey(key0).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), hex(inPubKey0)); - EXPECT_EQ(hex(PrivateKey(key1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), hex(inPubKey1)); + EXPECT_EQ(hex(PrivateKey(key0).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), + hex(inPubKey0)); + EXPECT_EQ(hex(PrivateKey(key1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), + hex(inPubKey1)); *input.add_private_key() = std::string(key0.begin(), key0.end()); *input.add_private_key() = std::string(key1.begin(), key1.end()); @@ -395,3 +444,351 @@ TEST(TWTransactionCompiler, ExternalSignatureSignBitcoin) { ASSERT_EQ(hex(output.encoded()), ExpectedTx); } } + +TEST(TWTransactionCompiler, ExternalSignatureSignSolana) { + const auto coin = TWCoinTypeSolana; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::Solana::Proto::SigningInput(); + auto& message = *input.mutable_transfer_transaction(); + auto recipient = std::string("3UVYmECPPMZSCqWKfENfuoTv51fTDTWicX9xmBD2euKe"); + auto sender = std::string("sp6VUqq1nDEuU83bU2hstmEYrJNipJYpwS7gZ7Jv7ZH"); + input.set_sender(sender); + input.set_recent_blockhash(std::string("TPJFTN4CjBn12HiBfAbGUhpD9zGvRSm2RcheFRA4Fyv")); + message.set_recipient(recipient); + message.set_value((uint64_t)1000); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + const auto txInputData = WRAPD(TWDataCreateWithBytes(inputStrData.data(), inputStrData.size())); + + /// Step 2: Obtain preimage hash + const auto preImageHashes = WRAPD(TWTransactionCompilerPreImageHashes(coin, txInputData.get())); + auto preImageHashData = data(TWDataBytes(preImageHashes.get()), TWDataSize(preImageHashes.get())); + + auto preSigningOutput = TW::Solana::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + ASSERT_EQ(preSigningOutput.signers_size(), 1); + auto signer = preSigningOutput.signers(0); + EXPECT_EQ(signer, sender); + auto preImageHash = preSigningOutput.data(); + EXPECT_EQ(hex(preImageHash), + "010001030d044a62d0a4dfe5a037a15b59fa4d4d0d3ab81103a2c10a6da08a4d058611c024c255a8bc3e" + "8496217a2cd2a1894b9b9dcace04fcd9c0d599acdaaea40a1b6100000000000000000000000000000000" + "0000000000000000000000000000000006c25012cc11a599a45b3b2f7f8a7c65b0547fa0bb67170d7a0c" + "d1eda4e2c9e501020200010c02000000e803000000000000"); + + // Simulate signature, normally obtained from signature server + const auto publicKeyData = + parse_hex("0d044a62d0a4dfe5a037a15b59fa4d4d0d3ab81103a2c10a6da08a4d058611c0"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeED25519); + const auto signature = + parse_hex("a8c610697087eaf8a34b3facbe06f8e9bb9603bb03270dad021ffcd2fc37b6e9efcdcb78b227401f" + "000eb9231c67685240890962e44a17fd27fc2ff7b971df03"); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + + /// Step 3: Compile transaction info + const auto outputData = WRAPD(TWTransactionCompilerCompileWithSignatures( + coin, txInputData.get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&signature)).get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&publicKeyData)).get())); + + const auto ExpectedTx = + "5bWxVCP5fuzkKSGby9hnsLranszQJR2evJGTfBrpDQ4rJceW1WxKNrWqVPBsN2QCAGmE6W7VaYkyWjv39HhGrr1Ne2" + "QSUuHZdyyn7hK4pxzLPMgPG8fY1XvXdppWMaKMLmhriLkckzGKJMaE3pWBRFBKzigXY28714uUNndb7S9hVakxa59h" + "rLph39CMgAkcj6b8KYvJEkb1YdYytHSZNGi4kVVTNqiicNgPdf1gmG6qz9zVtnqj9JtaD2efdS8qxsKnvNWSgb8Xxb" + "T6dwyp7msUUi7d27cYaPTpK"; + { + EXPECT_EQ(TWDataSize(outputData.get()), 296ul); + Solana::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), + (int)TWDataSize(outputData.get()))); + EXPECT_EQ(output.encoded(), ExpectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + Solana::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(TWDataBytes(txInputData.get()), + (int)TWDataSize(txInputData.get()))); + auto key = parse_hex("044014463e2ee3cc9c67a6f191dbac82288eb1d5c1111d21245bdc6a855082a1"); + signingInput.set_private_key(key.data(), key.size()); + + Solana::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.encoded(), ExpectedTx); + } +} + +TEST(TWTransactionCompiler, ExternalSignatureSignNULS) { + const auto coin = TWCoinTypeNULS; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::NULS::Proto::SigningInput(); + auto from = std::string("NULSd6HgWabfcG6H7NDK2TJvtoU3wxY1YLKwJ"); + auto to = std::string("NULSd6Hgied7ym6qMEfVzZanMaa9qeqA6TZSe"); + uint32_t chainId = 1; + uint32_t idassetsId = 1; + auto amount = TW::store((TW::uint256_t)10000000); + auto amountStr = std::string(amount.begin(), amount.end()); + auto balance = TW::store((TW::uint256_t)100000000); + auto balanceStr = std::string(balance.begin(), balance.end()); + auto nonce = std::string("0000000000000000"); + input.set_from(from); + input.set_to(to); + input.set_amount(amountStr); + input.set_chain_id(chainId); + input.set_idassets_id(idassetsId); + input.set_nonce(nonce); + input.set_balance(balanceStr); + input.set_timestamp((uint32_t)1569228280); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + const auto txInputData = WRAPD(TWDataCreateWithBytes(inputStrData.data(), inputStrData.size())); + + /// Step 2: Obtain preimage hash + const auto preImageHashes = WRAPD(TWTransactionCompilerPreImageHashes(coin, txInputData.get())); + auto preImageHashData = data(TWDataBytes(preImageHashes.get()), TWDataSize(preImageHashes.get())); + + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImage = preSigningOutput.data(); + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImage), + "0200f885885d00008c01170100012c177a01a7afbe98e094007b99476534fb7926b701000100201d9a00" + "000000000000000000000000000000000000000000000000000000000800000000000000000001170100" + "01f05e7878971f3374515eabb6f16d75219d887312010001008096980000000000000000000000000000" + "0000000000000000000000000000000000000000000000"); + EXPECT_EQ(hex(preImageHash), + "8746b37cb4b443424d3093e8107c5dfd6c5318010bbffcc8e8ba7c1da60877fd"); + // Simulate signature, normally obtained from signature server + const Data publicKeyData = + parse_hex("033c87a3d9b812556b3034b6471cad5131a01e210c1d7ca06dd53b7d0aff0ee045"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1); + const auto signature = + parse_hex("a5234269eab6fe8a1510dd0cb36070a03464b48856e1ef2681dbb79a5ec656f92961ac01d401a6f8" + "49acc958c6c9653f49282f5a0916df036ea8766918bac19500"); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + + /// Step 3: Compile transaction info + const auto outputData = WRAPD(TWTransactionCompilerCompileWithSignatures( + coin, txInputData.get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&signature)).get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&publicKeyData)).get())); + + const auto ExpectedEncoded = parse_hex( + "0200f885885d00008c01170100012c177a01a7afbe98e094007b99476534fb7926b701000100201d9a00000000" + "00000000000000000000000000000000000000000000000000080000000000000000000117010001f05e787897" + "1f3374515eabb6f16d75219d887312010001008096980000000000000000000000000000000000000000000000" + "00000000000000000000000000006a21033c87a3d9b812556b3034b6471cad5131a01e210c1d7ca06dd53b7d0a" + "ff0ee045473045022100a5234269eab6fe8a1510dd0cb36070a03464b48856e1ef2681dbb79a5ec656f9022029" + "61ac01d401a6f849acc958c6c9653f49282f5a0916df036ea8766918bac195"); + const auto ExpectedTx = std::string(ExpectedEncoded.begin(), ExpectedEncoded.end()); + { + EXPECT_EQ(TWDataSize(outputData.get()), 259ul); + NULS::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), + (int)TWDataSize(outputData.get()))); + EXPECT_EQ(output.encoded(), ExpectedTx); + EXPECT_EQ(output.encoded().size(), 256ul); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + NULS::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(TWDataBytes(txInputData.get()), + (int)TWDataSize(txInputData.get()))); + auto key = parse_hex("044014463e2ee3cc9c67a6f191dbac82288eb1d5c1111d21245bdc6a855082a1"); + signingInput.set_private_key(key.data(), key.size()); + + NULS::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.encoded(), ExpectedTx); + } +} + +// TEST(TWTransactionCompiler, ExternalSignatureSignRipple) { +// const auto coin = TWCoinTypeXRP; +// /// Step 1: Prepare transaction input (protobuf) +// auto key = parse_hex("ba005cd605d8a02e3d5dfd04234cef3a3ee4f76bfbad2722d1fb5af8e12e6764"); +// auto input = TW::Ripple::Proto::SigningInput(); +// auto privateKey = TW::PrivateKey(key); +// auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); +// input.set_amount(29000000); +// input.set_fee(200000); +// input.set_sequence(1); +// input.set_account("rDpysuumkweqeC7XdNgYNtzL5GxbdsmrtF"); +// input.set_destination("rU893viamSnsfP3zjzM2KPxjqZjXSXK6VF"); +// input.set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); +// auto inputString = input.SerializeAsString(); +// auto inputStrData = TW::Data(inputString.begin(), inputString.end()); +// const auto txInputData = WRAPD(TWDataCreateWithBytes(inputStrData.data(), inputStrData.size())); + +// /// Step 2: Obtain preimage hash +// const auto preImageHashes = WRAPD(TWTransactionCompilerPreImageHashes(coin, txInputData.get())); +// auto preImageHashData = data(TWDataBytes(preImageHashes.get()), TWDataSize(preImageHashes.get())); + +// auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); +// ASSERT_TRUE( +// preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); +// ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); +// // preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size()); +// auto preImage = preSigningOutput.data(); +// EXPECT_EQ(hex(preImage), +// "5354580012000022800000002400000001614000000001ba8140684000000000030d407321026cc34b92" +// "cefb3a4537b3edb0b6044c04af27c01583c577823ecc69a9a21119b681148400b6b6d08d5d495653d73e" +// "da6804c249a5148883148132e4e20aecf29090ac428a9c43f230a829220d"); +// auto preImageHash = preSigningOutput.data_hash(); +// EXPECT_EQ(hex(preImageHash), +// "8624dbbd5da9ccc8f7a50faf8af8709837db72f51a50cac15a6cd28ce6107b3d"); +// // Simulate signature, normally obtained from signature server +// const auto signature = privateKey.sign(parse_hex("8624dbbd5da9ccc8f7a50faf8af8709837db72f51a50cac15a6cd28ce6107b3d"), TWCurveSECP256k1); +// // Verify signature (pubkey & hash & signature) +// EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); +// /// Step 3: Compile transaction info +// const auto outputData = WRAPD(TWTransactionCompilerCompileWithSignatures( +// coin, txInputData.get(), +// WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&signature)).get(), +// WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&publicKey.bytes)).get())); + +// const auto ExpectedTx = std::string( +// "12000022800000002400000001614000000001ba8140684000000000030d407321026cc34b92cefb3a45" +// "37b3edb0b6044c04af27c01583c577823ecc69a9a21119b6744630440220067f20b3eebfc7107dd0bcc7" +// "2337a236ac3be042c0469f2341d76694a17d4bb9022048393d7ee7dcb729783b33f5038939ddce1bb833" +// "7e66d752974626854556bbb681148400b6b6d08d5d495653d73eda6804c249a5148883148132e4e20aec" +// "f29090ac428a9c43f230a829220d"); +// EXPECT_EQ(TWDataSize(outputData.get()), 185ul); + +// { +// TW::Ripple::Proto::SigningOutput output; +// ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), +// (int)TWDataSize(outputData.get()))); + +// EXPECT_EQ(hex(output.encoded()), ExpectedTx); +// EXPECT_EQ(output.encoded().size(), 182ul); +// ASSERT_EQ(output.error(), TW::Common::Proto::SigningError::OK); +// } + +// { // Double check: check if simple signature process gives the same result. Note that private +// // keys were not used anywhere up to this point. +// Ripple::Proto::SigningInput signingInput; +// ASSERT_TRUE(signingInput.ParseFromArray(TWDataBytes(txInputData.get()), +// (int)TWDataSize(txInputData.get()))); +// signingInput.set_private_key(key.data(), key.size()); + +// Ripple::Proto::SigningOutput output; +// ANY_SIGN(signingInput, coin); + +// ASSERT_EQ(hex(output.encoded()), ExpectedTx); +// } +// } + +TEST(TWTransactionCompiler, ExternalSignatureSignNULSToken) { + const auto coin = TWCoinTypeNULS; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::NULS::Proto::SigningInput(); + auto from = std::string("NULSd6HgWabfcG6H7NDK2TJvtoU3wxY1YLKwJ"); + auto to = std::string("NULSd6Hgied7ym6qMEfVzZanMaa9qeqA6TZSe"); + uint32_t chainId = 9; + uint32_t idassetsId = 1; + auto amount = TW::store((TW::uint256_t)10000000); + auto amountStr = std::string(amount.begin(), amount.end()); + auto balance = TW::store((TW::uint256_t)100000000); + auto balanceStr = std::string(balance.begin(), balance.end()); + auto feePayerBalance = TW::store((TW::uint256_t)100000000); + auto feePayerBalanceStr = std::string(feePayerBalance.begin(), feePayerBalance.end()); + auto nonce = std::string("0000000000000000"); + auto asset_nonce = std::string("0000000000000000"); + input.set_from(from); + input.set_to(to); + input.set_amount(amountStr); + input.set_chain_id(chainId); + input.set_idassets_id(idassetsId); + input.set_nonce(asset_nonce.data(), asset_nonce.size()); + input.set_balance(balanceStr); + input.set_fee_payer(from); + input.set_fee_payer_nonce(nonce.data(), nonce.size()); + input.set_fee_payer_balance(feePayerBalanceStr); + input.set_timestamp((uint32_t)1569228280); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + const auto txInputData = WRAPD(TWDataCreateWithBytes(inputStrData.data(), inputStrData.size())); + + /// Step 2: Obtain preimage hash + const auto preImageHashes = WRAPD(TWTransactionCompilerPreImageHashes(coin, txInputData.get())); + auto preImageHashData = + data(TWDataBytes(preImageHashes.get()), TWDataSize(preImageHashes.get())); + + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImage = preSigningOutput.data(); + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ( + hex(preImage), + "0200f885885d0000d202170100012c177a01a7afbe98e094007b99476534fb7926b70900010080969800000000" + "0000000000000000000000000000000000000000000000000008000000000000000000170100012c177a01a7af" + "be98e094007b99476534fb7926b701000100a08601000000000000000000000000000000000000000000000000" + "0000000000080000000000000000000117010001f05e7878971f3374515eabb6f16d75219d8873120900010080" + "969800000000000000000000000000000000000000000000000000000000000000000000000000"); + EXPECT_EQ(hex(preImageHash), + "9040642ce845b320453b2ccd6f80efc38fdf61ec8f0c12e0c16f6244ec2e0496"); + // Simulate signature, normally obtained from signature server + const Data publicKeyData = + parse_hex("033c87a3d9b812556b3034b6471cad5131a01e210c1d7ca06dd53b7d0aff0ee045"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1); + const auto signature = + parse_hex("5ddea604c6cdfcf6cbe32f5873937641676ee5f9aee3c40aa9857c59aefedff25b77429cf62307d4" + "3a6a79b4c106123e6232e3981032573770fe2726bf9fc07c00"); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + + /// Step 3: Compile transaction info + const auto outputData = WRAPD(TWTransactionCompilerCompileWithSignatures( + coin, txInputData.get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&signature)).get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&publicKeyData)).get())); + + const auto ExpectedEncoded = parse_hex( + "0200f885885d0000d202170100012c177a01a7afbe98e094007b99476534fb7926b70900010080969800000000" + "0000000000000000000000000000000000000000000000000008000000000000000000170100012c177a01a7af" + "be98e094007b99476534fb7926b701000100a08601000000000000000000000000000000000000000000000000" + "0000000000080000000000000000000117010001f05e7878971f3374515eabb6f16d75219d8873120900010080" + "9698000000000000000000000000000000000000000000000000000000000000000000000000006921033c87a3" + "d9b812556b3034b6471cad5131a01e210c1d7ca06dd53b7d0aff0ee04546304402205ddea604c6cdfcf6cbe32f" + "5873937641676ee5f9aee3c40aa9857c59aefedff202205b77429cf62307d43a6a79b4c106123e6232e3981032" + "573770fe2726bf9fc07c"); + const auto ExpectedTx = std::string(ExpectedEncoded.begin(), ExpectedEncoded.end()); + { + EXPECT_EQ(TWDataSize(outputData.get()), 328ul); + NULS::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), + (int)TWDataSize(outputData.get()))); + EXPECT_EQ(output.encoded(), ExpectedTx); + EXPECT_EQ(output.encoded().size(), 325ul); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + NULS::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(TWDataBytes(txInputData.get()), + (int)TWDataSize(txInputData.get()))); + auto key = parse_hex("044014463e2ee3cc9c67a6f191dbac82288eb1d5c1111d21245bdc6a855082a1"); + signingInput.set_private_key(key.data(), key.size()); + + NULS::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.encoded(), ExpectedTx); + } +} diff --git a/tools/build-and-test b/tools/build-and-test index 0bc769ce9b8..d02b887e424 100755 --- a/tools/build-and-test +++ b/tools/build-and-test @@ -24,4 +24,7 @@ build/trezor-crypto/crypto/tests/TrezorCryptoTests echo "#### Running unit tests... ####" FILTER="*" +if [ -n "$1" ]; then + FILTER="*$1*" +fi build/tests/tests --gtest_filter="$FILTER" diff --git a/tools/coverage b/tools/coverage index 5680fe2a35c..88b246fecd7 100755 --- a/tools/coverage +++ b/tools/coverage @@ -38,6 +38,7 @@ else fi lcov --remove coverage.info '/usr/*' --output-file coverage.info +lcov --remove coverage.info '/opt/*' --output-file coverage.info lcov --remove coverage.info '/Applications/*' --output-file coverage.info lcov --remove coverage.info '*/build/*' --output-file coverage.info lcov --remove coverage.info '*.pb.cc' --output-file coverage.info diff --git a/tools/test b/tools/test new file mode 100755 index 00000000000..97f79e9d566 --- /dev/null +++ b/tools/test @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# +# Perform full build and runs the tests. +# Prerequisite: workspace with dependencies installed, see bootstrap.sh + +# Fail if any commands fails +set -e + +make -Cbuild -j12 tests + +echo "#### Running unit tests... ####" +FILTER="*" +if [ -n "$1" ]; then + FILTER="*$1*" +fi +build/tests/tests --gtest_filter="$FILTER" diff --git a/wasm/.gitignore b/wasm/.gitignore index c1327fb6af0..13d2d8098a4 100644 --- a/wasm/.gitignore +++ b/wasm/.gitignore @@ -2,4 +2,5 @@ dist/ generated/ wallet-core.d.ts lib/wallet-core.js +lib/wallet-core.d.ts lib/wallet-core.wasm diff --git a/wasm/src/keystore/index.ts b/wasm/src/keystore/index.ts index 68931378db0..499adb36cdb 100644 --- a/wasm/src/keystore/index.ts +++ b/wasm/src/keystore/index.ts @@ -8,3 +8,4 @@ export { Default } from "./default-impl"; export * from "./types"; export { FileSystemStorage } from "./fs-storage"; export { ExtensionStorage } from "./extension-storage"; +