From 92fc9bd54d3feaff5e482250eb0eac7d7cefe376 Mon Sep 17 00:00:00 2001 From: Ryan Croote Date: Mon, 23 Oct 2023 09:53:38 -0400 Subject: [PATCH] [Internet Computer] Add Internet Computer (ICP) Chain (#3470) --- .../blockchains/CoinAddressDerivationTests.kt | 1 + .../TestInternetComputerAddress.kt | 31 + .../TestInternetComputerSigner.kt | 81 + docs/registry.md | 1 + include/TrustWalletCore/TWBlockchain.h | 1 + include/TrustWalletCore/TWCoinType.h | 1 + .../core/test/CoinAddressDerivationTests.kt | 1 + registry.json | 31 +- rust/Cargo.lock | 85 +- rust/Cargo.toml | 1 + .../tests/tw_any_address_ffi_tests.rs | 18 + rust/tw_coin_registry/Cargo.toml | 1 + rust/tw_coin_registry/src/blockchain_type.rs | 2 + rust/tw_coin_registry/src/dispatcher.rs | 4 + rust/tw_encoding/Cargo.toml | 5 + rust/tw_encoding/fuzz/Cargo.toml | 8 + .../fuzz/fuzz_targets/cbor_encode_decode.rs | 23 + rust/tw_encoding/src/cbor.rs | 69 + rust/tw_encoding/src/lib.rs | 1 + rust/tw_hash/fuzz/fuzz_targets/hash_fuzz.rs | 1 + rust/tw_hash/src/crc32.rs | 52 + rust/tw_hash/src/lib.rs | 1 + rust/tw_hash/src/sha2.rs | 6 +- rust/tw_internet_computer/Cargo.toml | 19 + rust/tw_internet_computer/build.rs | 54 + rust/tw_internet_computer/fuzz/.gitignore | 4 + rust/tw_internet_computer/fuzz/Cargo.lock | 1595 +++++++++++++++++ rust/tw_internet_computer/fuzz/Cargo.toml | 31 + .../tw_internet_computer_transfer.rs | 70 + rust/tw_internet_computer/src/address.rs | 184 ++ rust/tw_internet_computer/src/context.rs | 77 + rust/tw_internet_computer/src/entry.rs | 102 ++ rust/tw_internet_computer/src/lib.rs | 12 + .../src/protocol/envelope.rs | 237 +++ .../src/protocol/identity.rs | 116 ++ rust/tw_internet_computer/src/protocol/mod.rs | 35 + .../src/protocol/principal.rs | 256 +++ .../src/protocol/request_id.rs | 127 ++ .../src/protocol/rosetta.rs | 54 + rust/tw_internet_computer/src/signer.rs | 78 + .../src/transactions/mod.rs | 47 + .../src/transactions/proto/ledger.proto | 315 ++++ .../src/transactions/proto/types.proto | 19 + .../src/transactions/transfer.rs | 247 +++ rust/tw_keypair/Cargo.toml | 3 +- rust/tw_keypair/src/ecdsa/secp256k1/public.rs | 23 + src/Coin.cpp | 5 +- src/InternetComputer/Entry.cpp | 33 + src/InternetComputer/Entry.h | 27 + src/proto/InternetComputer.proto | 46 + .../Blockchains/InternetComputerTests.swift | 78 + swift/Tests/CoinAddressDerivationTests.swift | 3 + .../InternetComputer/TWAnyAddressTests.cpp | 20 + .../InternetComputer/TWAnySignerTests.cpp | 36 + .../InternetComputer/TWCoinTypeTests.cpp | 34 + tests/common/CoinAddressDerivationTests.cpp | 3 + .../tests/Blockchain/InternetComputer.test.ts | 139 ++ 57 files changed, 4534 insertions(+), 20 deletions(-) create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerAddress.kt create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerSigner.kt create mode 100644 rust/tw_encoding/fuzz/fuzz_targets/cbor_encode_decode.rs create mode 100644 rust/tw_encoding/src/cbor.rs create mode 100644 rust/tw_hash/src/crc32.rs create mode 100644 rust/tw_internet_computer/Cargo.toml create mode 100644 rust/tw_internet_computer/build.rs create mode 100644 rust/tw_internet_computer/fuzz/.gitignore create mode 100644 rust/tw_internet_computer/fuzz/Cargo.lock create mode 100644 rust/tw_internet_computer/fuzz/Cargo.toml create mode 100644 rust/tw_internet_computer/fuzz/fuzz_targets/tw_internet_computer_transfer.rs create mode 100644 rust/tw_internet_computer/src/address.rs create mode 100644 rust/tw_internet_computer/src/context.rs create mode 100644 rust/tw_internet_computer/src/entry.rs create mode 100644 rust/tw_internet_computer/src/lib.rs create mode 100644 rust/tw_internet_computer/src/protocol/envelope.rs create mode 100644 rust/tw_internet_computer/src/protocol/identity.rs create mode 100644 rust/tw_internet_computer/src/protocol/mod.rs create mode 100644 rust/tw_internet_computer/src/protocol/principal.rs create mode 100644 rust/tw_internet_computer/src/protocol/request_id.rs create mode 100644 rust/tw_internet_computer/src/protocol/rosetta.rs create mode 100644 rust/tw_internet_computer/src/signer.rs create mode 100644 rust/tw_internet_computer/src/transactions/mod.rs create mode 100644 rust/tw_internet_computer/src/transactions/proto/ledger.proto create mode 100644 rust/tw_internet_computer/src/transactions/proto/types.proto create mode 100644 rust/tw_internet_computer/src/transactions/transfer.rs create mode 100644 src/InternetComputer/Entry.cpp create mode 100644 src/InternetComputer/Entry.h create mode 100644 src/proto/InternetComputer.proto create mode 100644 swift/Tests/Blockchains/InternetComputerTests.swift create mode 100644 tests/chains/InternetComputer/TWAnyAddressTests.cpp create mode 100644 tests/chains/InternetComputer/TWAnySignerTests.cpp create mode 100644 tests/chains/InternetComputer/TWCoinTypeTests.cpp create mode 100644 wasm/tests/Blockchain/InternetComputer.test.ts 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 eb93523e4eb..89f21271a53 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 @@ -146,5 +146,6 @@ class CoinAddressDerivationTests { NOBLE -> assertEquals("noble142j9u5eaduzd7faumygud6ruhdwme98qc8l3wa", address) ROOTSTOCK -> assertEquals("0xA2D7065F94F838a3aB9C04D67B312056846424Df", address) SEI -> assertEquals("sei142j9u5eaduzd7faumygud6ruhdwme98qagm0sj", address) + INTERNETCOMPUTER -> assertEquals("b9a13d974ee9db036d5abc5b66ace23e513cb5676f3996626c7717c339a3ee87", address) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerAddress.kt new file mode 100644 index 00000000000..f4b9044e339 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerAddress.kt @@ -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. + +package com.trustwallet.core.app.blockchains.internetcomputer + +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 TestInternetComputerAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("ee42eaada903e20ef6e5069f0428d552475c1ea7ed940842da6448f6ef9d48e7".toHexByteArray()) + val pubkey = key.getPublicKeySecp256k1(false); + val address = AnyAddress(pubkey, CoinType.INTERNETCOMPUTER) + val expected = AnyAddress("2f25874478d06cf68b9833524a6390d0ba69c566b02f46626979a3d6a4153211", CoinType.INTERNETCOMPUTER) + + assertEquals(pubkey.data().toHex(), "0x048542e6fb4b17d6dfcac3948fe412c00d626728815ee7cc70509603f1bc92128a6e7548f3432d6248bc49ff44a1e50f6389238468d17f7d7024de5be9b181dbc8") + assertEquals(address.description(), expected.description()) + } +} \ No newline at end of file diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerSigner.kt new file mode 100644 index 00000000000..87a4261a158 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerSigner.kt @@ -0,0 +1,81 @@ +// 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. + +package com.trustwallet.core.app.blockchains.internetcomputer + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHex +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.* +import wallet.core.jni.proto.InternetComputer +import wallet.core.jni.proto.InternetComputer.SigningOutput + +class TestInternetComputerSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun InternetComputerTransactionSigning() { + val key = PrivateKey("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be".toHexByteArray()) + + val input = InternetComputer.SigningInput.newBuilder() + .setTransaction(InternetComputer.Transaction.newBuilder().apply { + transfer = InternetComputer.Transaction.Transfer.newBuilder().apply { + toAccountIdentifier = "943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a" + amount = 100000000 + memo = 0 + currentTimestampNanos = 1691709940000000000 + }.build() + }.build()) + .setPrivateKey(ByteString.copyFrom(key.data())) + val output = AnySigner.sign(input.build(), CoinType.INTERNETCOMPUTER, SigningOutput.parser()) + assertEquals(output.signedTransaction.toByteArray().toHex(), "0x81826b5452414e53414354494f4e81a266757064617465a367636f6e74656e74a66c726571756573745f747970656463616c6c6e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d026b63616e69737465725f69644a000000000000000201016b6d6574686f645f6e616d656773656e645f706263617267583b0a0012070a050880c2d72f2a220a20943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a3a0a088090caa5a3a78abd176d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f736967984013186f18b9181c189818b318a8186518b2186118d418971618b1187d18eb185818e01826182f1873183b185018cb185d18ef18d81839186418b3183218da1824182f184e18a01880182718c0189018c918a018fd18c418d9189e189818b318ef1874183b185118e118a51864185918e718ed18c71889186c1822182318ca6a726561645f7374617465a367636f6e74656e74a46c726571756573745f747970656a726561645f73746174656e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d0265706174687381824e726571756573745f7374617475735820e8fbc2d5b0bf837b3a369249143e50d4476faafb2dd620e4e982586a51ebcf1e6d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f7369679840182d182718201888188618ce187f0c182a187a18d718e818df18fb18d318d41118a5186a184b18341842185318d718e618e8187a1828186c186a183618461418f3183318bd18a618a718bc18d918c818b7189d186e1865188418ff18fd18e418e9187f181b18d705184818b21872187818d6181c161833184318a2") + } + + @Test + fun InternetComputerTransactionSigningWithInvalidToAccountIdentifier() { + val key = PrivateKey("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be".toHexByteArray()) + + val input = InternetComputer.SigningInput.newBuilder() + .setTransaction(InternetComputer.Transaction.newBuilder().apply { + transfer = InternetComputer.Transaction.Transfer.newBuilder().apply { + toAccountIdentifier = "643d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826b" + amount = 100000000 + memo = 0 + currentTimestampNanos = 1691709940000000000 + }.build() + }.build()) + .setPrivateKey(ByteString.copyFrom(key.data())) + val output = AnySigner.sign(input.build(), CoinType.INTERNETCOMPUTER, SigningOutput.parser()) + assertEquals(output.error.number, 16) + } + + @Test + fun InternetComputerTransactionSigningWithInvalidAmount() { + val key = PrivateKey("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be".toHexByteArray()) + + val input = InternetComputer.SigningInput.newBuilder() + .setTransaction(InternetComputer.Transaction.newBuilder().apply { + transfer = InternetComputer.Transaction.Transfer.newBuilder().apply { + toAccountIdentifier = "943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a" + amount = 0 + memo = 0 + currentTimestampNanos = 1691709940000000000 + }.build() + }.build()) + .setPrivateKey(ByteString.copyFrom(key.data())) + val output = AnySigner.sign(input.build(), CoinType.INTERNETCOMPUTER, SigningOutput.parser()) + assertEquals(output.error.number, 23) + } +} \ No newline at end of file diff --git a/docs/registry.md b/docs/registry.md index d1a15c32038..d569da1d826 100644 --- a/docs/registry.md +++ b/docs/registry.md @@ -36,6 +36,7 @@ This list is generated from [./registry.json](../registry.json) | 194 | EOS | EOS | | | | 195 | Tron | TRX | | | | 204 | OpBNB | BNB | | | +| 223 | Internet Computer | ICP | | | | 235 | FIO | FIO | | | | 242 | Nimiq | NIM | | | | 283 | Algorand | ALGO | | | diff --git a/include/TrustWalletCore/TWBlockchain.h b/include/TrustWalletCore/TWBlockchain.h index d715176ed7b..06a88beba2e 100644 --- a/include/TrustWalletCore/TWBlockchain.h +++ b/include/TrustWalletCore/TWBlockchain.h @@ -64,6 +64,7 @@ enum TWBlockchain { TWBlockchainTheOpenNetwork = 49, TWBlockchainSui = 50, TWBlockchainGreenfield = 51, + TWBlockchainInternetComputer = 52, }; TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWCoinType.h b/include/TrustWalletCore/TWCoinType.h index ad78ddfa90d..1b75255e921 100644 --- a/include/TrustWalletCore/TWCoinType.h +++ b/include/TrustWalletCore/TWCoinType.h @@ -177,6 +177,7 @@ enum TWCoinType { TWCoinTypeGreenfield = 5600, TWCoinTypeMantle = 5000, TWCoinTypeZenEON = 7332, + TWCoinTypeInternetComputer = 223, }; /// Returns the blockchain for a coin type. diff --git a/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt b/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt index dac9e182bf3..f5966bbd65c 100644 --- a/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt +++ b/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt @@ -142,5 +142,6 @@ class CoinAddressDerivationTests { Noble -> "noble142j9u5eaduzd7faumygud6ruhdwme98qc8l3wa" Rootstock -> "0xA2D7065F94F838a3aB9C04D67B312056846424Df" Sei -> "sei142j9u5eaduzd7faumygud6ruhdwme98qagm0sj" + InternetComputer -> "b9a13d974ee9db036d5abc5b66ace23e513cb5676f3996626c7717c339a3ee87" } } diff --git a/registry.json b/registry.json index 291ad9721db..6117b5d844e 100644 --- a/registry.json +++ b/registry.json @@ -1763,7 +1763,6 @@ "documentation": "https://developer.algorand.org/docs/algod-rest-paths" } }, - { "id": "iotex", "name": "IoTeX", @@ -4446,5 +4445,35 @@ "rpc": "https://neon-proxy-mainnet.solana.p2p.org/", "documentation": "https://docs.neonfoundation.io/docs/quick_start" } + }, + { + "id": "internet_computer", + "name": "Internet Computer", + "coinId": 223, + "symbol": "ICP", + "decimals": 8, + "blockchain": "InternetComputer", + "derivation": [ + { + "path": "m/44'/223'/1'/0/0", + "xpub": "xpub", + "xpriv": "xpriv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://dashboard.internetcomputer.org", + "txPath": "/transaction/", + "accountPath": "/account/", + "sampleTx": "9e32c54975adf84a1d98f19df41bbc34a752a899c32cc9c0000200b2c4308f85", + "sampleAccount": "529ea51c22e8d66e8302eabd9297b100fdb369109822248bb86939a671fbc55b" + }, + "info": { + "url": "https://internetcomputer.org", + "source": "https://github.com/dfinity/ic", + "rpc": "", + "documentation": "https://internetcomputer.org/docs" + } } ] diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 7ae598db1fd..035cd54ddad 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -296,6 +296,33 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "ciborium" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" + +[[package]] +name = "ciborium-ll" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "clap" version = "2.34.0" @@ -379,9 +406,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] name = "der" @@ -578,6 +605,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + [[package]] name = "hashbrown" version = "0.14.0" @@ -913,9 +946,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" dependencies = [ "unicode-ident", ] @@ -937,9 +970,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.26" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -1133,31 +1166,31 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" [[package]] name = "serde" -version = "1.0.164" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.9" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294" +checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.164" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.37", ] [[package]] @@ -1270,7 +1303,7 @@ checksum = "e6dc88f1f470d9de1001ffbb90d2344c9dd1a615f5467daf0574e2975dfd9ebd" dependencies = [ "starknet-curve", "starknet-ff", - "syn 2.0.15", + "syn 2.0.37", ] [[package]] @@ -1327,9 +1360,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.15" +version = "2.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" dependencies = [ "proc-macro2", "quote", @@ -1465,6 +1498,7 @@ dependencies = [ "tw_coin_entry", "tw_ethereum", "tw_evm", + "tw_internet_computer", "tw_keypair", "tw_memory", "tw_misc", @@ -1477,8 +1511,11 @@ version = "0.1.0" dependencies = [ "arbitrary", "bs58", + "ciborium", "data-encoding", "hex", + "serde", + "serde_bytes", "tw_memory", ] @@ -1534,6 +1571,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "tw_internet_computer" +version = "0.1.0" +dependencies = [ + "pb-rs", + "quick-protobuf", + "serde", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_proto", +] + [[package]] name = "tw_keypair" version = "0.1.0" @@ -1547,6 +1599,7 @@ dependencies = [ "k256", "lazy_static", "p256", + "pkcs8", "rfc6979", "ring", "serde", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index b8fdf347fca..e5c065055f1 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -8,6 +8,7 @@ members = [ "tw_ethereum", "tw_evm", "tw_hash", + "tw_internet_computer", "tw_keypair", "tw_memory", "tw_misc", diff --git a/rust/tw_any_coin/tests/tw_any_address_ffi_tests.rs b/rust/tw_any_coin/tests/tw_any_address_ffi_tests.rs index 829e2b97af0..0449c69ca59 100644 --- a/rust/tw_any_coin/tests/tw_any_address_ffi_tests.rs +++ b/rust/tw_any_coin/tests/tw_any_address_ffi_tests.rs @@ -38,6 +38,9 @@ fn test_any_address_derive() { BlockchainType::Bitcoin => "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X", BlockchainType::Ethereum => "0xAc1ec44E4f0ca7D172B7803f6836De87Fb72b309", BlockchainType::Ronin => "ronin:Ac1ec44E4f0ca7D172B7803f6836De87Fb72b309", + BlockchainType::InternetComputer => { + "290cc7c359f44c8516fc169c5ed4f0f3ae2e24bf5de0d4c51f5e7545b5474faa" + }, BlockchainType::Unsupported => unreachable!(), }; @@ -71,6 +74,10 @@ fn test_any_address_normalize_eth() { "0xb16db98b365b1f89191996942612b14f1da4bd5f", "ronin:b16Db98B365B1f89191996942612B14F1Da4Bd5f", ), + BlockchainType::InternetComputer => ( + "290CC7C359F44C8516FC169C5ED4F0F3AE2E24BF5DE0D4C51F5E7545B5474FAA", + "290cc7c359f44c8516fc169c5ed4f0f3ae2e24bf5de0d4c51f5e7545b5474faa", + ), BlockchainType::Unsupported => unreachable!(), }; @@ -109,6 +116,12 @@ fn test_any_address_is_valid_coin() { "ronin:b16db98b365b1f89191996942612b14f1da4bd5f", "ronin:b16Db98B365B1f89191996942612B14F1Da4Bd5f", ], + BlockchainType::InternetComputer => vec![ + "fb257577279ecac604d4780214af95aa6adc3a814f6f3d6d7ac844d1deca500a", + "e90c48d54847f4758f1d6b589a1db2500757a49a6722d4f775e050107b4b752d", + "a7c5baf393aed527ef6fb3869fbf84dd4e562edf9b04bd8f9bfbd6b8e6a22776", + "4cb2ca5cfcfa1d952f8cd7f0ec46c96e1023ab057b83a2c7ce236b9e71ccca0b", + ], _ => unreachable!(), }; @@ -129,6 +142,11 @@ fn test_any_address_is_valid_coin_invalid() { BlockchainType::Ethereum | BlockchainType::Ronin => { vec!["b16Db98B365B1f89191996942612B14F1Da4Bd5f"] }, + BlockchainType::InternetComputer => vec![ + "3357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278b", + "3357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278bce", + "553357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278", + ], BlockchainType::Unsupported => unreachable!(), }; diff --git a/rust/tw_coin_registry/Cargo.toml b/rust/tw_coin_registry/Cargo.toml index a66a537a37d..4ad4e6d87de 100644 --- a/rust/tw_coin_registry/Cargo.toml +++ b/rust/tw_coin_registry/Cargo.toml @@ -11,6 +11,7 @@ tw_bitcoin = { path = "../tw_bitcoin" } tw_coin_entry = { path = "../tw_coin_entry" } tw_ethereum = { path = "../tw_ethereum" } tw_evm = { path = "../tw_evm" } +tw_internet_computer = { path = "../tw_internet_computer" } tw_keypair = { path = "../tw_keypair" } tw_memory = { path = "../tw_memory" } tw_misc = { path = "../tw_misc" } diff --git a/rust/tw_coin_registry/src/blockchain_type.rs b/rust/tw_coin_registry/src/blockchain_type.rs index 47a19ff54ce..72a4141d1ea 100644 --- a/rust/tw_coin_registry/src/blockchain_type.rs +++ b/rust/tw_coin_registry/src/blockchain_type.rs @@ -15,6 +15,7 @@ use std::str::FromStr; pub enum BlockchainType { Bitcoin, Ethereum, + InternetComputer, Ronin, Unsupported, } @@ -36,6 +37,7 @@ impl FromStr for BlockchainType { match s { "Bitcoin" => Ok(BlockchainType::Bitcoin), "Ethereum" => Ok(BlockchainType::Ethereum), + "InternetComputer" => Ok(BlockchainType::InternetComputer), "Ronin" => Ok(BlockchainType::Ronin), _ => Ok(BlockchainType::Unsupported), } diff --git a/rust/tw_coin_registry/src/dispatcher.rs b/rust/tw_coin_registry/src/dispatcher.rs index 9c47e279a02..e027054e443 100644 --- a/rust/tw_coin_registry/src/dispatcher.rs +++ b/rust/tw_coin_registry/src/dispatcher.rs @@ -13,6 +13,7 @@ use tw_bitcoin::entry::BitcoinEntry; use tw_coin_entry::coin_entry_ext::CoinEntryExt; use tw_ethereum::entry::EthereumEntry; use tw_evm::evm_entry::EvmEntryExt; +use tw_internet_computer::entry::InternetComputerEntry; use tw_ronin::entry::RoninEntry; pub type CoinEntryExtStaticRef = &'static dyn CoinEntryExt; @@ -20,12 +21,14 @@ pub type EvmEntryExtStaticRef = &'static dyn EvmEntryExt; const BITCOIN: BitcoinEntry = BitcoinEntry; const ETHEREUM: EthereumEntry = EthereumEntry; +const INTERNET_COMPUTER: InternetComputerEntry = InternetComputerEntry; const RONIN: RoninEntry = RoninEntry; pub fn blockchain_dispatcher(blockchain: BlockchainType) -> RegistryResult { match blockchain { BlockchainType::Bitcoin => Ok(&BITCOIN), BlockchainType::Ethereum => Ok(ÐEREUM), + BlockchainType::InternetComputer => Ok(&INTERNET_COMPUTER), BlockchainType::Ronin => Ok(&RONIN), BlockchainType::Unsupported => Err(RegistryError::Unsupported), } @@ -45,6 +48,7 @@ pub fn evm_dispatcher(coin: CoinType) -> RegistryResult { match item.blockchain { BlockchainType::Bitcoin => Err(RegistryError::Unsupported), BlockchainType::Ethereum => Ok(ÐEREUM), + BlockchainType::InternetComputer => Err(RegistryError::Unsupported), BlockchainType::Ronin => Ok(&RONIN), BlockchainType::Unsupported => Err(RegistryError::Unsupported), } diff --git a/rust/tw_encoding/Cargo.toml b/rust/tw_encoding/Cargo.toml index 2d32de903f4..2211e12bde9 100644 --- a/rust/tw_encoding/Cargo.toml +++ b/rust/tw_encoding/Cargo.toml @@ -6,6 +6,11 @@ edition = "2021" [dependencies] arbitrary = { version = "1", features = ["derive"], optional = true } bs58 = "0.4.0" +ciborium = "0.2.1" data-encoding = "2.3.3" hex = "0.4.3" +serde = { version = "1.0.136", features = ["derive"] } tw_memory = { path = "../tw_memory" } + +[dev-dependencies] +serde_bytes = "0.11.12" \ No newline at end of file diff --git a/rust/tw_encoding/fuzz/Cargo.toml b/rust/tw_encoding/fuzz/Cargo.toml index eaf660bc073..81d65cd9818 100644 --- a/rust/tw_encoding/fuzz/Cargo.toml +++ b/rust/tw_encoding/fuzz/Cargo.toml @@ -9,6 +9,8 @@ cargo-fuzz = true [dependencies] libfuzzer-sys = { version = "0.4", features = ["arbitrary-derive"] } +serde = { version = "1.0.136", features = ["derive"] } +serde_bytes = "0.11.12" [dependencies.tw_encoding] path = ".." @@ -32,3 +34,9 @@ name = "base_decode" path = "fuzz_targets/base_decode.rs" test = false doc = false + +[[bin]] +name = "cbor_encode_decode" +path = "fuzz_targets/cbor_encode_decode.rs" +test = false +doc = false diff --git a/rust/tw_encoding/fuzz/fuzz_targets/cbor_encode_decode.rs b/rust/tw_encoding/fuzz/fuzz_targets/cbor_encode_decode.rs new file mode 100644 index 00000000000..6c79c61db3c --- /dev/null +++ b/rust/tw_encoding/fuzz/fuzz_targets/cbor_encode_decode.rs @@ -0,0 +1,23 @@ +#![no_main] + +use libfuzzer_sys::{arbitrary, fuzz_target}; + +use serde::{Deserialize, Serialize}; + +use tw_encoding::cbor::{decode, encode}; + +#[derive(arbitrary::Arbitrary, Serialize, Deserialize, PartialEq, Debug)] +struct User { + name: String, + age: u64, + friends: Vec, + #[serde(with = "serde_bytes")] + address: Vec, + key: Vec, +} + +fuzz_target!(|se_user: User| { + let serialized = encode(&se_user).unwrap(); + let de_user = decode::(&serialized).unwrap(); + assert_eq!(se_user, de_user); +}); diff --git a/rust/tw_encoding/src/cbor.rs b/rust/tw_encoding/src/cbor.rs new file mode 100644 index 00000000000..4f5e954fd62 --- /dev/null +++ b/rust/tw_encoding/src/cbor.rs @@ -0,0 +1,69 @@ +use ciborium::{de, ser}; +use serde::{de::DeserializeOwned, Serialize}; + +type CborResult = Result; + +pub fn encode(message: &S) -> CborResult> { + let mut bytes = vec![]; + ser::into_writer(message, &mut bytes).map_err(|e| e.to_string())?; + Ok(bytes) +} + +pub fn decode(data: &[u8]) -> CborResult { + de::from_reader(data).map_err(|e| e.to_string()) +} + +#[cfg(test)] +mod test { + use serde::{Deserialize, Serialize}; + + use super::{decode, encode}; + + const SERIALIZED: [u8; 70] = [ + 165, 100, 110, 97, 109, 101, 101, 65, 108, 105, 99, 101, 99, 97, 103, 101, 24, 42, 103, + 102, 114, 105, 101, 110, 100, 115, 131, 99, 66, 111, 98, 101, 67, 97, 114, 111, 108, 100, + 68, 97, 118, 101, 103, 97, 100, 100, 114, 101, 115, 115, 68, 0, 1, 2, 3, 99, 107, 101, 121, + 138, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + ]; + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct User { + name: String, + age: u64, + friends: Vec, + #[serde(with = "serde_bytes")] + address: Vec, + key: Vec, + } + + fn test_user() -> User { + User { + name: "Alice".to_string(), + age: 42, + friends: vec!["Bob".to_string(), "Carol".to_string(), "Dave".to_string()], + address: vec![0, 1, 2, 3], + key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 0], + } + } + + #[test] + fn test_serialize() { + let user = test_user(); + let serialized = encode(&user).unwrap(); + assert_eq!(serialized, SERIALIZED); + } + + #[test] + fn test_deserialize() { + let user = decode::(&SERIALIZED).unwrap(); + assert_eq!(user, test_user()); + } + + #[test] + fn test_serialize_deserialize() { + let se_user = test_user(); + let serialized = encode(&se_user).unwrap(); + let de_user = decode::(&serialized).unwrap(); + assert_eq!(se_user, de_user); + } +} diff --git a/rust/tw_encoding/src/lib.rs b/rust/tw_encoding/src/lib.rs index 648781e8af3..3250e9ed4da 100644 --- a/rust/tw_encoding/src/lib.rs +++ b/rust/tw_encoding/src/lib.rs @@ -7,6 +7,7 @@ pub mod base32; pub mod base58; pub mod base64; +pub mod cbor; pub mod ffi; pub mod hex; diff --git a/rust/tw_hash/fuzz/fuzz_targets/hash_fuzz.rs b/rust/tw_hash/fuzz/fuzz_targets/hash_fuzz.rs index 505c0facbe9..ddd74bce7df 100644 --- a/rust/tw_hash/fuzz/fuzz_targets/hash_fuzz.rs +++ b/rust/tw_hash/fuzz/fuzz_targets/hash_fuzz.rs @@ -19,6 +19,7 @@ fuzz_target!(|input: HashInput<'_>| { tw_hash::blake::blake_256(input.data); tw_hash::blake2::blake2_b(input.data, input.hash_size).ok(); tw_hash::blake2::blake2_b_personal(input.data, input.hash_size, input.additional_data).ok(); + tw_hash::crc32::crc32(input.data); tw_hash::groestl::groestl_512(input.data); tw_hash::hmac::hmac_sha256(input.additional_data, input.data); tw_hash::ripemd::ripemd_160(input.data); diff --git a/rust/tw_hash/src/crc32.rs b/rust/tw_hash/src/crc32.rs new file mode 100644 index 00000000000..f413fd13a19 --- /dev/null +++ b/rust/tw_hash/src/crc32.rs @@ -0,0 +1,52 @@ +// 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. + +// Table taken from https://web.mit.edu/freebsd/head/sys/libkern/crc32.c (Public Domain code) +// This table is used to speed up the crc calculation. +const CRC32_TABLE: [u32; 256] = [ + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, +]; + +// Algorithm inspired by this old-style C implementation: +// https://web.mit.edu/freebsd/head/sys/libkern/crc32.c (Public Domain code) +pub fn crc32(input: &[u8]) -> u32 { + let mut c = u32::MAX; + for b in input { + c = CRC32_TABLE[((c ^ (*b as u32)) & 0xFF) as usize] ^ (c >> 8); + } + !c +} diff --git a/rust/tw_hash/src/lib.rs b/rust/tw_hash/src/lib.rs index 8c8e83c80bf..f7cfe16d911 100644 --- a/rust/tw_hash/src/lib.rs +++ b/rust/tw_hash/src/lib.rs @@ -6,6 +6,7 @@ pub mod blake; pub mod blake2; +pub mod crc32; pub mod ffi; pub mod groestl; pub mod hmac; diff --git a/rust/tw_hash/src/sha2.rs b/rust/tw_hash/src/sha2.rs index ad448769bbf..0ac313162c2 100644 --- a/rust/tw_hash/src/sha2.rs +++ b/rust/tw_hash/src/sha2.rs @@ -5,7 +5,11 @@ // file LICENSE at the root of the source code distribution tree. use crate::hash_wrapper::hasher; -use sha2::{Sha256, Sha512, Sha512_256}; +use sha2::{Sha224, Sha256, Sha512, Sha512_256}; + +pub fn sha224(input: &[u8]) -> Vec { + hasher::(input) +} pub fn sha256(input: &[u8]) -> Vec { hasher::(input) diff --git a/rust/tw_internet_computer/Cargo.toml b/rust/tw_internet_computer/Cargo.toml new file mode 100644 index 00000000000..d774d4946ae --- /dev/null +++ b/rust/tw_internet_computer/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "tw_internet_computer" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +quick-protobuf = "0.8.1" +serde = { version = "1.0.136", features = ["derive"] } +tw_coin_entry = { path = "../tw_coin_entry" } +tw_encoding = { path = "../tw_encoding" } +tw_hash = { path = "../tw_hash" } +tw_keypair = { path = "../tw_keypair" } +tw_memory = { path = "../tw_memory" } +tw_proto = { path = "../tw_proto" } + +[build-dependencies] +pb-rs = "0.10.0" diff --git a/rust/tw_internet_computer/build.rs b/rust/tw_internet_computer/build.rs new file mode 100644 index 00000000000..a163f826026 --- /dev/null +++ b/rust/tw_internet_computer/build.rs @@ -0,0 +1,54 @@ +// 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. + +use pb_rs::types::FileDescriptor; +use pb_rs::ConfigBuilder; +use std::path::{Path, PathBuf}; +use std::{env, fs}; + +fn main() { + let proto_ext = Some(Path::new("proto").as_os_str()); + + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()).join("proto"); + + let proto_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("src") + .join("transactions") + .join("proto"); + let proto_dir_str = proto_dir.to_str().expect("Invalid proto directory path"); + // Re-run this build.rs if the `proto` directory has been changed (i.e. a new file is added). + println!("cargo:rerun-if-changed={}", proto_dir_str); + + let protos: Vec<_> = fs::read_dir(&proto_dir) + .expect("Expected a valid directory with proto files") + .filter_map(|file| { + let file = file.ok()?; + if file.path().extension() != proto_ext { + return None; + } + + let path = file.path(); + let path_str = path.to_str().expect("Invalid Proto file name"); + println!("cargo:rerun-if-changed={}", path_str); + Some(path) + }) + .collect(); + + // Delete all old generated files before re-generating new ones + if out_dir.exists() { + fs::remove_dir_all(&out_dir).expect("Error removing out directory"); + } + fs::DirBuilder::new() + .create(&out_dir) + .expect("Error creating out directory"); + + let out_protos = ConfigBuilder::new(&protos, None, Some(&out_dir), &[proto_dir]) + .expect("Error configuring pb-rs builder") + .dont_use_cow(true) + .owned(true) + .build(); + FileDescriptor::run(&out_protos).expect("Error generating proto files"); +} diff --git a/rust/tw_internet_computer/fuzz/.gitignore b/rust/tw_internet_computer/fuzz/.gitignore new file mode 100644 index 00000000000..1a45eee7760 --- /dev/null +++ b/rust/tw_internet_computer/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/rust/tw_internet_computer/fuzz/Cargo.lock b/rust/tw_internet_computer/fuzz/Cargo.lock new file mode 100644 index 00000000000..887d622e7ea --- /dev/null +++ b/rust/tw_internet_computer/fuzz/Cargo.lock @@ -0,0 +1,1595 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "arbitrary" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "derivative", + "digest 0.10.7", + "itertools", + "num-bigint", + "num-traits", + "paste", + "rustc_version", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bigdecimal" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94d1988118c887f61418940e322d574e8a2dd67165f1f1556eaae22e4019c6af" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "ppv-lite86", +] + +[[package]] +name = "blake2" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a4e37d16930f5459780f5621038b6382b9bb37c19016f39fb6b5808d831f174" +dependencies = [ + "crypto-mac", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "blake2b-ref" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "294d17c72e0ba59fad763caa112368d0672083779cdebbb97164f4bb4c1e339a" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "jobserver", + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "ciborium" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" + +[[package]] +name = "ciborium-ll" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "const-oid" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "740fe28e594155f10cfc383984cbefd529d7396050557148f79cb0f621204124" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "data-encoding" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_arbitrary" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e0efad4403bfc52dc201159c4b842a246a14b98c64b55dfd0f2d89729dfeb8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.31", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "ecdsa" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "elliptic-curve" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "hkdf", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "groestl" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343cfc165f92a988fd60292f7a0bfde4352a5a0beff9fbec29251ca4e9676e4d" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2 0.10.7", + "signature", +] + +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.7", +] + +[[package]] +name = "parity-scale-codec" +version = "3.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d640b40e81625f53b59f9c5812d3fb9e7ce11971d3fb34268eb7a83adf98051" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ac464815d51fff2f64d690bf6ea4442d365e53495d50737685cfecfa3dd3f62" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pb-rs" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "354a34df9c65b596152598001c0fe3393379ec2db03ae30b9985659422e2607e" +dependencies = [ + "clap", + "env_logger", + "log", + "nom", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "primeorder" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c2fcef82c0ec6eefcc179b978446c399b3cdf73c392c35604e399eee6df1ee3" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "primitive-types" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f3486ccba82358b11a77516035647c34ba167dfa53312630de83b12bd4f3d66" +dependencies = [ + "fixed-hash", + "impl-codec", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-protobuf" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f" +dependencies = [ + "byteorder", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.10", +] + +[[package]] +name = "regex" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" + +[[package]] +name = "serde" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.31", +] + +[[package]] +name = "serde_json" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "spki" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "starknet-crypto" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693e6362f150f9276e429a910481fb7f3bcb8d6aa643743f587cfece0b374874" +dependencies = [ + "crypto-bigint", + "hex", + "hmac", + "num-bigint", + "num-integer", + "num-traits", + "rfc6979", + "sha2 0.10.7", + "starknet-crypto-codegen", + "starknet-curve 0.3.0", + "starknet-ff", + "zeroize", +] + +[[package]] +name = "starknet-crypto-codegen" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af6527b845423542c8a16e060ea1bc43f67229848e7cd4c4d80be994a84220ce" +dependencies = [ + "starknet-curve 0.4.0", + "starknet-ff", + "syn 2.0.31", +] + +[[package]] +name = "starknet-curve" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252610baff59e4c4332ce3569f7469c5d3f9b415a2240d698fb238b2b4fc0942" +dependencies = [ + "starknet-ff", +] + +[[package]] +name = "starknet-curve" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68a0d87ae56572abf83ddbfd44259a7c90dbeeee1629a1ffe223e7f9a8f3052" +dependencies = [ + "starknet-ff", +] + +[[package]] +name = "starknet-ff" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2cb1d9c0a50380cddab99cb202c6bfb3332728a2769bd0ca2ee80b0b390dd4" +dependencies = [ + "ark-ff", + "bigdecimal", + "crypto-bigint", + "getrandom 0.2.10", + "hex", + "serde", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tw_coin_entry" +version = "0.1.0" +dependencies = [ + "serde_json", + "tw_keypair", + "tw_memory", + "tw_misc", + "tw_number", + "tw_proto", +] + +[[package]] +name = "tw_encoding" +version = "0.1.0" +dependencies = [ + "bs58", + "ciborium", + "data-encoding", + "hex", + "serde", + "tw_memory", +] + +[[package]] +name = "tw_hash" +version = "0.1.0" +dependencies = [ + "blake-hash", + "blake2b-ref", + "digest 0.10.7", + "groestl", + "hmac", + "ripemd", + "serde", + "sha1", + "sha2 0.10.7", + "sha3", + "tw_encoding", + "tw_memory", + "zeroize", +] + +[[package]] +name = "tw_internet_computer" +version = "0.1.0" +dependencies = [ + "pb-rs", + "quick-protobuf", + "serde", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_proto", +] + +[[package]] +name = "tw_internet_computer-fuzz" +version = "0.0.0" +dependencies = [ + "libfuzzer-sys", + "tw_internet_computer", + "tw_keypair", +] + +[[package]] +name = "tw_keypair" +version = "0.1.0" +dependencies = [ + "blake2", + "curve25519-dalek", + "der", + "digest 0.9.0", + "ecdsa", + "k256", + "lazy_static", + "p256", + "pkcs8", + "rfc6979", + "serde", + "sha2 0.9.9", + "starknet-crypto", + "starknet-ff", + "tw_encoding", + "tw_hash", + "tw_memory", + "tw_misc", + "zeroize", +] + +[[package]] +name = "tw_memory" +version = "0.1.0" + +[[package]] +name = "tw_misc" +version = "0.1.0" +dependencies = [ + "zeroize", +] + +[[package]] +name = "tw_number" +version = "0.1.0" +dependencies = [ + "primitive-types", + "tw_hash", + "tw_memory", +] + +[[package]] +name = "tw_proto" +version = "0.1.0" +dependencies = [ + "pb-rs", + "quick-protobuf", + "tw_encoding", + "tw_memory", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.31", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.31", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winnow" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.31", +] diff --git a/rust/tw_internet_computer/fuzz/Cargo.toml b/rust/tw_internet_computer/fuzz/Cargo.toml new file mode 100644 index 00000000000..a31848778a8 --- /dev/null +++ b/rust/tw_internet_computer/fuzz/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "tw_internet_computer-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = { version = "0.4", features = ["arbitrary-derive"] } + +[dependencies.tw_internet_computer] +path = ".." + + +[dependencies.tw_keypair] +path = "../../tw_keypair" + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[profile.release] +debug = 1 + +[[bin]] +name = "tw_internet_computer_transfer" +path = "fuzz_targets/tw_internet_computer_transfer.rs" +test = false +doc = false diff --git a/rust/tw_internet_computer/fuzz/fuzz_targets/tw_internet_computer_transfer.rs b/rust/tw_internet_computer/fuzz/fuzz_targets/tw_internet_computer_transfer.rs new file mode 100644 index 00000000000..cfd4b494762 --- /dev/null +++ b/rust/tw_internet_computer/fuzz/fuzz_targets/tw_internet_computer_transfer.rs @@ -0,0 +1,70 @@ +#![no_main] + +use libfuzzer_sys::{arbitrary, fuzz_target}; +use tw_internet_computer::{ + address::AccountIdentifier, + protocol::principal::Principal, + transactions::transfer::{transfer, TransferArgs}, +}; +use tw_keypair::ecdsa::secp256k1; + +#[derive(Debug, arbitrary::Arbitrary)] +struct ArbitraryTransferArgs { + memo: u64, + amount: u64, + max_fee: Option, + #[arbitrary(with = arbitrary_to_field)] + to: String, + current_timestamp_nanos: u64, +} + +fn arbitrary_to_field(u: &mut arbitrary::Unstructured) -> arbitrary::Result { + let mut buf = [0; 29]; + u.fill_buffer(&mut buf)?; + let principal = Principal::from_slice(&buf); + let account_identifier = AccountIdentifier::new(&principal); + Ok(account_identifier.to_hex()) +} + +impl From<&ArbitraryTransferArgs> for TransferArgs { + fn from(prev: &ArbitraryTransferArgs) -> TransferArgs { + TransferArgs { + memo: prev.memo, + amount: prev.amount, + max_fee: prev.max_fee, + to: prev.to.clone(), + current_timestamp_nanos: prev.current_timestamp_nanos, + } + } +} + +#[derive(Debug, arbitrary::Arbitrary)] +struct TWInternetComputerTransactionsTransferInput { + #[arbitrary(with = arbitrary_private_key)] + private: Vec, + #[arbitrary(with = arbitrary_canister_id)] + canister_id: Principal, + args: ArbitraryTransferArgs, +} + +fn arbitrary_private_key(u: &mut arbitrary::Unstructured) -> arbitrary::Result> { + let mut buf = [0; 32]; + u.fill_buffer(&mut buf)?; + Ok(Vec::from(buf.as_slice())) +} + +fn arbitrary_canister_id(u: &mut arbitrary::Unstructured) -> arbitrary::Result { + let mut buf = [0; 29]; + u.fill_buffer(&mut buf)?; + let principal = Principal::from_slice(&buf); + Ok(principal) +} + +fuzz_target!(|input: TWInternetComputerTransactionsTransferInput| { + let Ok(private_key) = secp256k1::PrivateKey::try_from(input.private.as_slice()) else { + return; + }; + + let args = TransferArgs::from(&input.args); + transfer(private_key, input.canister_id, args).ok(); +}); diff --git a/rust/tw_internet_computer/src/address.rs b/rust/tw_internet_computer/src/address.rs new file mode 100644 index 00000000000..17ba0ed3ee4 --- /dev/null +++ b/rust/tw_internet_computer/src/address.rs @@ -0,0 +1,184 @@ +// 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. + +use tw_coin_entry::{ + coin_entry::CoinAddress, + error::{AddressError, AddressResult}, +}; +use tw_encoding::hex; +use tw_hash::{crc32::crc32, sha2::sha224, H256}; +use tw_keypair::ecdsa::secp256k1::PublicKey; + +use crate::protocol::principal::Principal; + +pub trait IcpAddress: std::str::FromStr + Into { + fn from_str_optional(s: &str) -> AddressResult> { + if s.is_empty() { + return Ok(None); + } + + Self::from_str(s).map(Some) + } +} + +/// The ICP ledger keeps track of accounts using account identifiers. +/// An account identifier is created by `SHA-224` hashing: +/// * \x0Aaccount-id +/// * the owner's principal ID +/// * subaccount (32-bytes) +/// Then, +/// * CRC32 the SHA-224 hash +/// * Prepend the CRC32 to the SHA-224. +/// https://internetcomputer.org/docs/current/references/ledger/#_accounts +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AccountIdentifier { + bytes: H256, +} + +impl AccountIdentifier { + /// Create a default account identifier from the given principal owner. + pub fn new(owner: &Principal) -> Self { + let mut input = vec![]; + input.extend_from_slice(b"\x0Aaccount-id"); + input.extend_from_slice(owner.as_slice()); + input.extend_from_slice(&H256::new()[..]); + + let hash = sha224(&input); + let crc32_bytes = crc32(&hash).to_be_bytes(); + + let mut bytes = H256::new(); + bytes[0..4].copy_from_slice(&crc32_bytes); + bytes[4..32].copy_from_slice(&hash); + Self { bytes } + } + + /// Return the textual-encoded account identifier. + pub fn to_hex(&self) -> String { + hex::encode(self.bytes, false) + } + + /// Instantiate an account identifier from a hex-encoded string. + pub fn from_hex(hex_str: &str) -> AddressResult { + if hex_str.len() != 64 { + return Err(AddressError::FromHexError); + } + + let hex = H256::try_from( + hex::decode(hex_str) + .map_err(|_| AddressError::FromHexError)? + .as_slice(), + ) + .map_err(|_| AddressError::FromHexError)?; + + if !is_check_sum_valid(hex) { + return Err(AddressError::FromHexError); + } + + Ok(Self { bytes: hex }) + } +} + +impl From<&PublicKey> for AccountIdentifier { + /// Takes a Secp256k1 public key, DER-encodes the public key, + /// and creates a principal from the encoding. + fn from(public_key: &PublicKey) -> Self { + let principal = Principal::from(public_key); + AccountIdentifier::new(&principal) + } +} + +impl std::str::FromStr for AccountIdentifier { + type Err = AddressError; + + fn from_str(s: &str) -> Result { + AccountIdentifier::from_hex(s).map_err(|_| AddressError::FromHexError) + } +} + +impl std::fmt::Display for AccountIdentifier { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.to_hex()) + } +} + +impl CoinAddress for AccountIdentifier { + fn data(&self) -> tw_memory::Data { + self.bytes.into_vec() + } +} + +impl IcpAddress for AccountIdentifier {} + +impl AsRef<[u8]> for AccountIdentifier { + fn as_ref(&self) -> &[u8] { + self.bytes.as_slice() + } +} + +fn is_check_sum_valid(hash: H256) -> bool { + let found_checksum = &hash[0..4]; + let expected_checksum = crc32(&hash[4..]).to_be_bytes(); + found_checksum == expected_checksum +} + +#[cfg(test)] +mod test { + use tw_keypair::ecdsa::secp256k1::PublicKey; + + use super::*; + + const VALID_ADDRESSES: [&str; 10] = [ + "fb257577279ecac604d4780214af95aa6adc3a814f6f3d6d7ac844d1deca500a", + "e90c48d54847f4758f1d6b589a1db2500757a49a6722d4f775e050107b4b752d", + "a7c5baf393aed527ef6fb3869fbf84dd4e562edf9b04bd8f9bfbd6b8e6a22776", + "4cb2ca5cfcfa1d952f8cd7f0ec46c96e1023ab057b83a2c7ce236b9e71ccca0b", + "a93fff2708a6305e8946a0a06cbf559da01a758da58a615d404037b08ea96181", + "6e66c78a45cec01bcd0efd6dd142a82dc63b1a591c4ccb3c5877cd4d667747b4", + "c4ca697b46bb89ebf19eef3ad7b5e7bfa73315c0433a68a12a67f60fe017b9ad", + "7c513ec0de7347555b75cfefe29e56689de36636321fb0c8addf24a4f934ff0b", + "f61e15cdcaf0325bbaeb9a23a9f49d5447b33e6feee9763c2fdfe3a986142912", + "bb3357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278", + ]; + + const TOO_SHORT_ADDRESS: &str = + "3357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278b"; + const TOO_LONG_ADDRESS: &str = + "3357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278bce"; + const INVALID_CHECKSUM_ADDRESS: &str = + "553357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278"; + + const PUBLIC_KEY_HEX: &str = "048542e6fb4b17d6dfcac3948fe412c00d626728815ee7cc70509603f1bc92128a6e7548f3432d6248bc49ff44a1e50f6389238468d17f7d7024de5be9b181dbc8"; + + #[test] + fn from_hex() { + assert!(VALID_ADDRESSES + .iter() + .all(|s| AccountIdentifier::from_hex(s).is_ok())); + + assert!( + AccountIdentifier::from_hex(TOO_SHORT_ADDRESS).is_err(), + "Address is too short" + ); + assert!( + AccountIdentifier::from_hex(TOO_LONG_ADDRESS).is_err(), + "Address is too long" + ); + assert!( + AccountIdentifier::from_hex(INVALID_CHECKSUM_ADDRESS).is_err(), + "Invalid checksum" + ); + } + + #[test] + fn from_public_key() { + let public_key = PublicKey::try_from(PUBLIC_KEY_HEX).expect("Failed to populate key"); + let address = AccountIdentifier::from(&public_key); + assert_eq!( + address.to_hex(), + "2f25874478d06cf68b9833524a6390d0ba69c566b02f46626979a3d6a4153211" + ); + } +} diff --git a/rust/tw_internet_computer/src/context.rs b/rust/tw_internet_computer/src/context.rs new file mode 100644 index 00000000000..45e2a3d5e5d --- /dev/null +++ b/rust/tw_internet_computer/src/context.rs @@ -0,0 +1,77 @@ +// 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. + +use crate::{ + address::{AccountIdentifier, IcpAddress}, + protocol::principal::Principal, +}; + +pub trait InternetComputerContext { + type Address: IcpAddress; + + fn get_canister_id() -> Principal; +} + +#[derive(Default)] +pub struct StandardInternetComputerContext; + +impl InternetComputerContext for StandardInternetComputerContext { + type Address = AccountIdentifier; + + fn get_canister_id() -> Principal { + // ICP Ledger Canister + Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai").unwrap() + } +} + +#[cfg(test)] +mod test { + + use std::marker::PhantomData; + + use tw_coin_entry::error::AddressResult; + + use super::*; + + const TEST_OWNER_PRINCIPAL_ID: &str = + "t4u4z-y3dur-j63pk-nw4rv-yxdbt-agtt6-nygn7-ywh6y-zm2f4-sdzle-3qe"; + const TEST_TEXTUAL_ICP_ADDRESS: &str = + "943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a"; + const TEXTUAL_ICP_LEDGER_CANISTER_ID: &str = "ryjl3-tyaaa-aaaaa-aaaba-cai"; + + pub struct ContextTest { + _phantom: PhantomData, + } + + impl ContextTest { + fn get_canister_id() -> Principal { + Context::get_canister_id() + } + + fn account_identifier_optional(s: &str) -> AddressResult> { + Context::Address::from_str_optional(s) + } + } + + #[test] + fn standard_internet_computer_context_canister_address() { + let owner = Principal::from_text(TEST_OWNER_PRINCIPAL_ID).unwrap(); + let expected_account_identifier = AccountIdentifier::new(&owner); + let account_identifier = + ContextTest::::account_identifier_optional( + TEST_TEXTUAL_ICP_ADDRESS, + ) + .unwrap() + .unwrap(); + assert_eq!(expected_account_identifier, account_identifier); + } + + #[test] + fn standard_internet_computer_context_canister_type() { + let ledger_canister_id = ContextTest::::get_canister_id(); + assert_eq!(ledger_canister_id.to_text(), TEXTUAL_ICP_LEDGER_CANISTER_ID); + } +} diff --git a/rust/tw_internet_computer/src/entry.rs b/rust/tw_internet_computer/src/entry.rs new file mode 100644 index 00000000000..a9542fbb8ea --- /dev/null +++ b/rust/tw_internet_computer/src/entry.rs @@ -0,0 +1,102 @@ +// 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. + +use std::str::FromStr; + +use tw_coin_entry::{ + coin_context::CoinContext, + coin_entry::CoinEntry, + error::{AddressError, AddressResult, SigningError}, + modules::{ + json_signer::NoJsonSigner, message_signer::NoMessageSigner, plan_builder::NoPlanBuilder, + }, + prefix::NoPrefix, + signing_output_error, +}; + +use tw_proto::{ + Common::Proto::SigningError as CommonError, InternetComputer::Proto, + TxCompiler::Proto as CompilerProto, +}; + +use crate::{address::AccountIdentifier, context::StandardInternetComputerContext, signer::Signer}; + +pub struct InternetComputerEntry; + +impl CoinEntry for InternetComputerEntry { + type AddressPrefix = NoPrefix; + + type Address = AccountIdentifier; + + type SigningInput<'a> = Proto::SigningInput<'a>; + + type SigningOutput = Proto::SigningOutput<'static>; + + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + + type JsonSigner = NoJsonSigner; + + type PlanBuilder = NoPlanBuilder; + + type MessageSigner = NoMessageSigner; + + #[inline] + fn parse_address( + &self, + _coin: &dyn CoinContext, + address: &str, + _prefix: Option, + ) -> AddressResult { + Self::Address::from_str(address) + } + + #[inline] + fn derive_address( + &self, + _coin: &dyn tw_coin_entry::coin_context::CoinContext, + public_key: tw_keypair::tw::PublicKey, + _derivation: tw_coin_entry::derivation::Derivation, + _prefix: Option, + ) -> tw_coin_entry::error::AddressResult { + let secp256k1_public_key = public_key + .to_secp256k1() + .ok_or(AddressError::PublicKeyTypeMismatch)?; + Ok(Self::Address::from(secp256k1_public_key)) + } + + #[inline] + fn sign( + &self, + _coin: &dyn tw_coin_entry::coin_context::CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::SigningOutput { + Signer::::sign_proto(input) + } + + fn preimage_hashes( + &self, + _coin: &dyn tw_coin_entry::coin_context::CoinContext, + _input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + signing_output_error!( + CompilerProto::PreSigningOutput, + SigningError(CommonError::Error_not_supported) + ) + } + + fn compile( + &self, + _coin: &dyn tw_coin_entry::coin_context::CoinContext, + _input: Self::SigningInput<'_>, + _signatures: Vec, + _public_keys: Vec, + ) -> Self::SigningOutput { + signing_output_error!( + Proto::SigningOutput, + SigningError(CommonError::Error_not_supported) + ) + } +} diff --git a/rust/tw_internet_computer/src/lib.rs b/rust/tw_internet_computer/src/lib.rs new file mode 100644 index 00000000000..c32ab978e54 --- /dev/null +++ b/rust/tw_internet_computer/src/lib.rs @@ -0,0 +1,12 @@ +// 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. + +pub mod address; +pub mod context; +pub mod entry; +pub mod protocol; +pub mod signer; +pub mod transactions; diff --git a/rust/tw_internet_computer/src/protocol/envelope.rs b/rust/tw_internet_computer/src/protocol/envelope.rs new file mode 100644 index 00000000000..cfa00cdd274 --- /dev/null +++ b/rust/tw_internet_computer/src/protocol/envelope.rs @@ -0,0 +1,237 @@ +// 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. + +use std::collections::BTreeMap; + +use serde::{Serialize, Serializer}; + +use super::{ + principal::Principal, + request_id::{hash_of_map, RawHttpRequestVal, RequestId}, +}; + +pub trait RepresentationHashable { + fn request_id(&self) -> RequestId; +} + +#[derive(Debug, Clone, Serialize)] +pub struct Envelope { + pub content: C, + #[serde(skip_serializing_if = "Option::is_none")] + pub sender_pubkey: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub sender_sig: Option>, +} + +/// A replicated call to a canister method, whether update or query. +#[derive(Debug, Clone, Serialize)] +#[serde(tag = "request_type", rename = "call")] +pub struct EnvelopeCallContent { + /// A random series of bytes to uniquely identify this message. + #[serde( + default, + skip_serializing_if = "Option::is_none", + serialize_with = "serialize_nonce" + )] + pub nonce: Option>, + /// A nanosecond timestamp after which this request is no longer valid. + pub ingress_expiry: u64, + /// The principal that is sending this request. + pub sender: Principal, + /// The ID of the canister to be called. + pub canister_id: Principal, + /// The name of the canister method to be called. + pub method_name: String, + /// The argument to pass to the canister method. + #[serde(serialize_with = "serialize_arg")] + pub arg: Vec, +} + +impl RepresentationHashable for EnvelopeCallContent { + fn request_id(&self) -> RequestId { + let mut map = vec![ + ( + "request_type".to_string(), + RawHttpRequestVal::String("call".to_string()), + ), + ( + "canister_id".to_string(), + RawHttpRequestVal::Bytes(self.canister_id.as_slice().to_vec()), + ), + ( + "method_name".to_string(), + RawHttpRequestVal::String(self.method_name.to_string()), + ), + ( + "arg".to_string(), + RawHttpRequestVal::Bytes(self.arg.clone()), + ), + ( + "ingress_expiry".to_string(), + RawHttpRequestVal::U64(self.ingress_expiry), + ), + ( + "sender".to_string(), + RawHttpRequestVal::Bytes(self.sender.as_slice().to_vec()), + ), + ] + .into_iter() + .collect::>(); + + if let Some(some_nonce) = &self.nonce { + map.insert( + "nonce".to_string(), + RawHttpRequestVal::Bytes(some_nonce.clone()), + ); + } + RequestId(hash_of_map(&map)) + } +} + +/// A request for information from the [IC state tree](https://internetcomputer.org/docs/current/references/ic-interface-spec#state-tree). +#[derive(Debug, Clone, Serialize)] +#[serde(tag = "request_type", rename = "read_state")] +pub struct EnvelopeReadStateContent { + /// A nanosecond timestamp after which this request is no longer valid. + pub ingress_expiry: u64, + /// The principal that is sending this request. + pub sender: Principal, + /// A list of paths within the state tree to fetch. + pub paths: Vec>, +} + +impl RepresentationHashable for EnvelopeReadStateContent { + fn request_id(&self) -> RequestId { + let map = vec![ + ( + "request_type".to_string(), + RawHttpRequestVal::String("read_state".to_string()), + ), + ( + "ingress_expiry".to_string(), + RawHttpRequestVal::U64(self.ingress_expiry), + ), + ( + "paths".to_string(), + RawHttpRequestVal::Array( + self.paths + .iter() + .map(|p| { + RawHttpRequestVal::Array( + p.iter() + .map(|b| RawHttpRequestVal::Bytes(b.as_slice().to_vec())) + .collect(), + ) + }) + .collect(), + ), + ), + ( + "sender".to_string(), + RawHttpRequestVal::Bytes(self.sender.as_slice().to_vec()), + ), + ] + .into_iter() + .collect::>(); + + RequestId(hash_of_map(&map)) + } +} + +fn serialize_arg(arg: &[u8], s: S) -> Result +where + S: Serializer, +{ + s.serialize_bytes(arg) +} + +fn serialize_nonce(nonce: &Option>, s: S) -> Result +where + S: Serializer, +{ + match nonce { + Some(nonce) => s.serialize_bytes(nonce), + None => s.serialize_none(), + } +} + +#[derive(Debug, Clone)] +pub struct Label(Vec); + +impl Label { + #[inline] + pub fn as_slice(&self) -> &[u8] { + self.0.as_slice() + } +} + +impl From<&str> for Label { + fn from(value: &str) -> Self { + Label(value.as_bytes().to_vec()) + } +} + +impl From for Label { + fn from(value: RequestId) -> Self { + Label(value.0.as_slice().to_vec()) + } +} + +// Serialization +impl serde::Serialize for Label { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_bytes(self.0.as_slice()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn representation_independent_hash_call_or_query() { + let content = EnvelopeCallContent { + ingress_expiry: 1685570400000000000, + sender: Principal::anonymous(), + canister_id: Principal::from_slice(&[0, 0, 0, 0, 0, 0, 4, 210]), + method_name: "hello".to_string(), + arg: b"DIDL\x00\xFD*".to_vec(), + nonce: None, + }; + + assert_eq!( + tw_encoding::hex::encode(content.request_id().0, false), + "1d1091364d6bb8a6c16b203ee75467d59ead468f523eb058880ae8ec80e2b101" + ); + } + + #[test] + fn representation_independent_hash_read_state() { + let content = EnvelopeCallContent { + ingress_expiry: 1685570400000000000, + sender: Principal::anonymous(), + canister_id: Principal::from_slice(&[0, 0, 0, 0, 0, 0, 4, 210]), + method_name: "hello".to_string(), + arg: b"DIDL\x00\xFD*".to_vec(), + nonce: None, + }; + let update_request_id = content.request_id(); + + let content = EnvelopeReadStateContent { + ingress_expiry: 1685570400000000000, + sender: Principal::anonymous(), + paths: vec![vec![ + Label::from("request_status"), + Label::from(update_request_id), + ]], + }; + let request_id = content.request_id(); + assert_eq!( + tw_encoding::hex::encode(request_id.0, false), + "3cde0f14a953c3afbe1335f22e861bb62389f1449beca02707ab197e6829c2a3" + ); + } +} diff --git a/rust/tw_internet_computer/src/protocol/identity.rs b/rust/tw_internet_computer/src/protocol/identity.rs new file mode 100644 index 00000000000..ed8ce0f7cbb --- /dev/null +++ b/rust/tw_internet_computer/src/protocol/identity.rs @@ -0,0 +1,116 @@ +// 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. + +use tw_hash::H256; +use tw_keypair::{ecdsa::secp256k1::PrivateKey, traits::SigningKeyTrait, KeyPairError}; + +use super::principal::Principal; + +#[derive(Debug)] +pub enum SigningError { + Failed(KeyPairError), +} + +/// Contains the signature and the associated DER-encoded public key from a call to +/// [Identity::sign]. +pub struct IdentitySignature { + pub signature: Vec, + pub public_key: Vec, +} + +/// An identity is a simple way to abstract away signing for envelopes. +/// When creating a request to the IC, the sender and signature are required +/// for authentication purposes. The sender is derived from a DER-encode public key +/// and the signature is created using the private key. +pub struct Identity { + private_key: PrivateKey, + der_encoded_public_key: Vec, +} + +impl Identity { + /// Gets the public key and DER-encodes it and returns an Identity. + pub fn new(private_key: PrivateKey) -> Self { + let public_key = private_key.public(); + let der_encoded_public_key = public_key.der_encoded(); + + Self { + private_key, + der_encoded_public_key, + } + } + + /// Returns the principal of the private key. + /// Sender represents who is sending the request. + pub fn sender(&self) -> Principal { + Principal::self_authenticating(&self.der_encoded_public_key) + } + + /// Signs the given content with the private key. + /// The signatures are encoded as the concatenation of the 32-byte big endian + /// encodings of the two values r and s. + /// + /// See: https://internetcomputer.org/docs/current/references/ic-interface-spec#ecdsa + pub fn sign(&self, content: H256) -> Result { + let signature = self + .private_key + .sign(content) + .map_err(SigningError::Failed)?; + + let r = signature.r(); + let s = signature.s(); + let mut bytes = [0u8; 64]; + bytes[..32].clone_from_slice(r.as_slice()); + bytes[32..].clone_from_slice(s.as_slice()); + + let signature = IdentitySignature { + public_key: self.der_encoded_public_key.clone(), + signature: bytes.to_vec(), //Signature bytes + }; + + Ok(signature) + } +} + +#[cfg(test)] +mod test { + + use tw_encoding::hex; + + use super::*; + + /// Test that the sender is derived from the private key. + #[test] + fn sender() { + let private_key = PrivateKey::try_from( + "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be", + ) + .unwrap(); + let identity = Identity::new(private_key); + let sender = identity.sender(); + assert_eq!( + sender.to_text(), + "hpikg-6exdt-jn33w-ndty3-fc7jc-tl2lr-buih3-cs3y7-tftkp-sfp62-gqe" + ) + } + + /// Test signing with the identity. + #[test] + fn sign() { + let private_key = PrivateKey::try_from( + "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be", + ) + .unwrap(); + let der_encoded_public_key = private_key.public().der_encoded(); + let identity = Identity::new(private_key); + let content = H256::new(); + let signature = identity.sign(content).unwrap(); + assert_eq!( + hex::encode(signature.signature, false), + "17c0974ee2ae621099389a5e4d0f960925d2e0e23658df03069308fb8edcb7bb120a338ada3e2ede7f41f6ed2f424a8a4f2c8fb68260f27d4f1bf96d19094b9f" + ); + assert_eq!(der_encoded_public_key, signature.public_key); + } +} diff --git a/rust/tw_internet_computer/src/protocol/mod.rs b/rust/tw_internet_computer/src/protocol/mod.rs new file mode 100644 index 00000000000..f21353b57b7 --- /dev/null +++ b/rust/tw_internet_computer/src/protocol/mod.rs @@ -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. + +pub mod envelope; +pub mod identity; +pub mod principal; +pub mod request_id; +pub mod rosetta; + +use std::time::Duration; + +/// This constant defines the maximum amount of time an ingress message can wait +/// to start executing after submission before it is expired. Hence, if an +/// ingress message is submitted at time `t` and it has not been scheduled for +/// execution till time `t+MAX_INGRESS_TTL`, it will be expired. +/// +/// At the time of writing, this constant is also used to control how long the +/// status of a completed ingress message (IngressStatus ∈ [Completed, Failed]) +/// is maintained by the IC before it is deleted from the ingress history. +const MAX_INGRESS_TTL: Duration = Duration::from_secs(5 * 60); + +/// Duration subtracted from `MAX_INGRESS_TTL` by +/// `expiry_time_from_now()` when creating an ingress message. +const PERMITTED_DRIFT: Duration = Duration::from_secs(60); + +/// An upper limit on the validity of the request, expressed in nanoseconds since 1970-01-01. +pub fn get_ingress_expiry(current_timestamp_duration: Duration) -> u64 { + current_timestamp_duration + .saturating_add(MAX_INGRESS_TTL) + .saturating_sub(PERMITTED_DRIFT) + .as_nanos() as u64 +} diff --git a/rust/tw_internet_computer/src/protocol/principal.rs b/rust/tw_internet_computer/src/protocol/principal.rs new file mode 100644 index 00000000000..61680e14f48 --- /dev/null +++ b/rust/tw_internet_computer/src/protocol/principal.rs @@ -0,0 +1,256 @@ +// 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. + +/// Taken from candid crate and modified to rely upon built-in crc32 and SHA224 functionality. +use std::fmt::Write; + +use tw_hash::{crc32::crc32, sha2::sha224}; +use tw_keypair::ecdsa::secp256k1::PublicKey; + +/// An error happened while encoding, decoding or serializing a [`Principal`]. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum PrincipalError { + BytesTooLong, + InvalidBase32, + TextTooShort, + TextTooLong, + CheckSequenceNotMatch, + AbnormalGrouped(Principal), +} + +/// Generic ID on Internet Computer. +/// +/// Principals are generic identifiers for canisters, users +/// and possibly other concepts in the future. +/// As far as most uses of the IC are concerned they are +/// opaque binary blobs with a length between 0 and 29 bytes, +/// and there is intentionally no mechanism to tell canister ids and user ids apart. +/// +/// Note a principal is not necessarily tied with a public key-pair, +/// yet we need at least a key-pair of a related principal to sign +/// requests. +/// +/// A Principal can be serialized to a byte array ([`Vec`]) or a text +/// representation, but the inner structure of the byte representation +/// is kept private. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Principal { + /// Length. + len: u8, + + /// The content buffer. When returning slices this should always be sized according to + /// `len`. + bytes: [u8; Self::MAX_LENGTH_IN_BYTES], +} + +impl Principal { + const MAX_LENGTH_IN_BYTES: usize = 29; + const CRC_LENGTH_IN_BYTES: usize = 4; + + const SELF_AUTHENTICATING_TAG: u8 = 2; + const ANONYMOUS_TAG: u8 = 4; + + /// Construct a [`Principal`] of the IC management canister + pub const fn management_canister() -> Self { + Self { + len: 0, + bytes: [0; Self::MAX_LENGTH_IN_BYTES], + } + } + + /// Construct a self-authenticating ID from public key + pub fn self_authenticating>(public_key: P) -> Self { + let public_key = public_key.as_ref(); + let hash = sha224(public_key); + let mut bytes = [0; Self::MAX_LENGTH_IN_BYTES]; + bytes[..Self::MAX_LENGTH_IN_BYTES - 1].copy_from_slice(hash.as_slice()); + bytes[Self::MAX_LENGTH_IN_BYTES - 1] = Self::SELF_AUTHENTICATING_TAG; + + Self { + len: Self::MAX_LENGTH_IN_BYTES as u8, + bytes, + } + } + + /// Construct an anonymous ID. + pub const fn anonymous() -> Self { + let mut bytes = [0; Self::MAX_LENGTH_IN_BYTES]; + bytes[0] = Self::ANONYMOUS_TAG; + Self { len: 1, bytes } + } + + /// Construct a [`Principal`] from a slice of bytes. + /// + /// # Panics + /// + /// Panics if the slice is longer than 29 bytes. + pub fn from_slice(slice: &[u8]) -> Self { + match Self::try_from_slice(slice) { + Ok(v) => v, + _ => panic!("slice length exceeds capacity"), + } + } + + /// Construct a [`Principal`] from a slice of bytes. + pub fn try_from_slice(slice: &[u8]) -> Result { + const MAX_LENGTH_IN_BYTES: usize = Principal::MAX_LENGTH_IN_BYTES; + if slice.len() > MAX_LENGTH_IN_BYTES { + return Err(PrincipalError::BytesTooLong); + } + + let mut bytes = [0; MAX_LENGTH_IN_BYTES]; + bytes[0..slice.len()].copy_from_slice(slice); + + Ok(Self { + len: slice.len() as u8, + bytes, + }) + } + + /// Parse a [`Principal`] from text representation. + pub fn from_text>(text: S) -> Result { + // Strategy: Parse very liberally, then pretty-print and compare output + // This is both simpler and yields better error messages + + let mut s = text.as_ref().to_string(); + s.make_ascii_uppercase(); + s.retain(|c| c != '-'); + + let bytes = tw_encoding::base32::decode(&s, None, false) + .map_err(|_| PrincipalError::InvalidBase32)?; + + if bytes.len() < Self::CRC_LENGTH_IN_BYTES { + return Err(PrincipalError::TextTooShort); + } + + let crc_bytes = &bytes[..Self::CRC_LENGTH_IN_BYTES]; + let data_bytes = &bytes[Self::CRC_LENGTH_IN_BYTES..]; + if data_bytes.len() > Self::MAX_LENGTH_IN_BYTES { + return Err(PrincipalError::TextTooLong); + } + + if crc32(data_bytes).to_be_bytes() != crc_bytes { + return Err(PrincipalError::CheckSequenceNotMatch); + } + + // Already checked data_bytes.len() <= MAX_LENGTH_IN_BYTES + // safe to unwrap here + let result = Self::try_from_slice(data_bytes).unwrap(); + let expected = format!("{result}"); + + // In the Spec: + // The textual representation is conventionally printed with lower case letters, + // but parsed case-insensitively. + if text.as_ref().to_ascii_lowercase() != expected { + return Err(PrincipalError::AbnormalGrouped(result)); + } + Ok(result) + } + + /// Convert [`Principal`] to text representation. + pub fn to_text(&self) -> String { + format!("{self}") + } + + /// Return the [`Principal`]'s underlying slice of bytes. + #[inline] + pub fn as_slice(&self) -> &[u8] { + &self.bytes[..self.len as usize] + } +} + +impl std::str::FromStr for Principal { + type Err = PrincipalError; + + fn from_str(s: &str) -> Result { + Principal::from_text(s) + } +} + +impl From<&PublicKey> for Principal { + /// Takes a Secp256k1 public key, DER-encodes the public key, + /// and creates a principal from the encoding. + fn from(public_key: &PublicKey) -> Self { + Self::self_authenticating(public_key.der_encoded()) + } +} + +impl std::fmt::Display for Principal { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let blob: &[u8] = self.as_slice(); + + // calc checksum + let checksum = crc32(blob); + + // combine blobs + let mut bytes = vec![]; + bytes.extend_from_slice(&checksum.to_be_bytes()); + bytes.extend_from_slice(blob); + + // base32 + let mut s = + tw_encoding::base32::encode(&bytes, None, false).map_err(|_| std::fmt::Error)?; + s.make_ascii_lowercase(); + + // write out string with dashes + let mut s = s.as_str(); + while s.len() > 5 { + f.write_str(&s[..5])?; + f.write_char('-')?; + s = &s[5..]; + } + f.write_str(s) + } +} + +impl TryFrom<&str> for Principal { + type Error = PrincipalError; + + fn try_from(s: &str) -> Result { + Principal::from_text(s) + } +} + +impl TryFrom> for Principal { + type Error = PrincipalError; + + fn try_from(bytes: Vec) -> Result { + Self::try_from(bytes.as_slice()) + } +} + +impl TryFrom<&Vec> for Principal { + type Error = PrincipalError; + + fn try_from(bytes: &Vec) -> Result { + Self::try_from(bytes.as_slice()) + } +} + +impl TryFrom<&[u8]> for Principal { + type Error = PrincipalError; + + fn try_from(bytes: &[u8]) -> Result { + Self::try_from_slice(bytes) + } +} + +impl AsRef<[u8]> for Principal { + fn as_ref(&self) -> &[u8] { + self.as_slice() + } +} + +// Serialization +impl serde::Serialize for Principal { + fn serialize(&self, serializer: S) -> Result { + if serializer.is_human_readable() { + self.to_text().serialize(serializer) + } else { + serializer.serialize_bytes(self.as_slice()) + } + } +} diff --git a/rust/tw_internet_computer/src/protocol/request_id.rs b/rust/tw_internet_computer/src/protocol/request_id.rs new file mode 100644 index 00000000000..5f9d2bcf74b --- /dev/null +++ b/rust/tw_internet_computer/src/protocol/request_id.rs @@ -0,0 +1,127 @@ +// 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. + +use std::collections::BTreeMap; + +use serde::{Deserialize, Serialize}; +use tw_hash::{sha2::sha256, H256}; + +const DOMAIN_IC_REQUEST: &[u8; 11] = b"\x0Aic-request"; + +/// When signing requests or querying the status of a request +/// (see Request status) in the state tree, the user identifies +/// the request using a request id, which is the +/// representation-independent hash of the content map of the +/// original request. A request id must have length of 32 bytes. +pub struct RequestId(pub(crate) H256); + +impl RequestId { + /// Create the prehash from the request ID. + /// See: https://internetcomputer.org/docs/current/references/ic-interface-spec#envelope-authentication + pub fn sig_data(&self) -> H256 { + let mut sig_data = vec![]; + sig_data.extend_from_slice(DOMAIN_IC_REQUEST); + sig_data.extend_from_slice(self.0.as_slice()); + H256::try_from(sha256(&sig_data).as_slice()).unwrap_or_else(|_| H256::new()) + } +} + +/// The different types of values supported in `RawHttpRequest`. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub enum RawHttpRequestVal { + Bytes(#[serde(serialize_with = "serialize_bytes")] Vec), + String(String), + U64(u64), + Array(Vec), +} + +fn serialize_bytes(bytes: &[u8], s: S) -> Result +where + S: serde::Serializer, +{ + s.serialize_bytes(bytes) +} + +fn hash_string(value: String) -> Vec { + sha256(value.as_bytes()) +} + +fn hash_bytes(value: Vec) -> Vec { + sha256(value.as_slice()) +} + +fn hash_u64(value: u64) -> Vec { + // We need at most ⌈ 64 / 7 ⌉ = 10 bytes to encode a 64 bit + // integer in LEB128. + let mut buf = [0u8; 10]; + let mut n = value; + let mut i = 0; + + loop { + let byte = (n & 0x7f) as u8; + n >>= 7; + + if n == 0 { + buf[i] = byte; + break; + } else { + buf[i] = byte | 0x80; + i += 1; + } + } + + hash_bytes(buf[..=i].to_vec()) +} + +// arrays, encoded as the concatenation of the hashes of the encodings of the +// array elements. +fn hash_array(elements: Vec) -> Vec { + let mut buffer = vec![]; + elements + .into_iter() + // Hash the encoding of all the array elements. + .for_each(|e| { + let mut hashed_val = hash_val(e); + buffer.append(&mut hashed_val); + }); + sha256(&buffer) +} + +fn hash_val(val: RawHttpRequestVal) -> Vec { + match val { + RawHttpRequestVal::String(string) => hash_string(string), + RawHttpRequestVal::Bytes(bytes) => hash_bytes(bytes), + RawHttpRequestVal::U64(integer) => hash_u64(integer), + RawHttpRequestVal::Array(elements) => hash_array(elements), + } +} + +fn hash_key_val(key: String, val: RawHttpRequestVal) -> Vec { + let mut key_hash = hash_string(key); + let mut val_hash = hash_val(val); + key_hash.append(&mut val_hash); + key_hash +} + +/// Describes `hash_of_map` as specified in the public spec. +/// See: https://internetcomputer.org/docs/current/references/ic-interface-spec#hash-of-map +pub fn hash_of_map(map: &BTreeMap) -> H256 { + let mut hashes: Vec> = Vec::new(); + for (key, val) in map.iter() { + hashes.push(hash_key_val(key.to_string(), val.clone())); + } + + // Computes hash by first sorting by "field name" hash, which is the + // same as sorting by concatenation of H(field name) · H(field value) + // (although in practice it's actually more stable in the presence of + // duplicated field names). Then concatenate all the hashes. + hashes.sort(); + + let buffer = hashes.into_iter().flatten().collect::>(); + let hash = sha256(&buffer); + + H256::try_from(hash.as_slice()).unwrap_or_else(|_| H256::new()) +} diff --git a/rust/tw_internet_computer/src/protocol/rosetta.rs b/rust/tw_internet_computer/src/protocol/rosetta.rs new file mode 100644 index 00000000000..45e2a0be888 --- /dev/null +++ b/rust/tw_internet_computer/src/protocol/rosetta.rs @@ -0,0 +1,54 @@ +// 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. + +use super::envelope::{Envelope, EnvelopeCallContent, EnvelopeReadStateContent}; +use serde::Serialize; + +/// The types of requests that are available from the Rosetta node. +/// This enum is truncated to include support only for the +/// operations that this crate can currently perform. +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub enum RequestType { + // Aliases for backwards compatibility + #[serde(rename = "TRANSACTION")] + #[serde(alias = "Send")] + Send, +} + +/// The type (encoded as CBOR) returned by the Rosetta node's +/// /construction/combine endpoint. It contains the +/// IC calls to submit the transaction and to check the result. +pub type SignedTransaction = Vec; + +/// A vector of update/read-state calls for different ingress windows +/// of the same call. +pub type Request = (RequestType, Vec); + +#[derive(Debug, Clone)] +pub enum EnvelopePairError { + InvalidUpdateEnvelope, + InvalidReadStateEnvelope, +} + +/// A signed IC update call and the corresponding read-state call for +/// a particular ingress window. +#[derive(Debug, Clone, Serialize)] +pub struct EnvelopePair { + update: Envelope, + read_state: Envelope, +} + +impl EnvelopePair { + pub fn new( + update_envelope: Envelope, + read_state_envelope: Envelope, + ) -> Result { + Ok(Self { + update: update_envelope, + read_state: read_state_envelope, + }) + } +} diff --git a/rust/tw_internet_computer/src/signer.rs b/rust/tw_internet_computer/src/signer.rs new file mode 100644 index 00000000000..113dde0c27f --- /dev/null +++ b/rust/tw_internet_computer/src/signer.rs @@ -0,0 +1,78 @@ +// 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. + +use std::marker::PhantomData; + +use tw_coin_entry::{ + error::{SigningError, SigningResult}, + signing_output_error, +}; +use tw_keypair::ecdsa::secp256k1; +use tw_proto::{Common::Proto::SigningError as CommonError, InternetComputer::Proto}; + +use crate::{ + context::InternetComputerContext, + protocol::identity, + transactions::{self, sign_transaction}, +}; + +impl From for SigningError { + fn from(error: transactions::SignTransactionError) -> Self { + match error { + transactions::SignTransactionError::InvalidArguments => { + SigningError(CommonError::Error_invalid_params) + }, + transactions::SignTransactionError::Identity(identity_error) => match identity_error { + identity::SigningError::Failed(_) => SigningError(CommonError::Error_signing), + }, + transactions::SignTransactionError::InvalidEnvelopePair + | transactions::SignTransactionError::EncodingArgsFailed => { + SigningError(CommonError::Error_internal) + }, + transactions::SignTransactionError::InvalidToAccountIdentifier => { + SigningError(CommonError::Error_invalid_address) + }, + transactions::SignTransactionError::InvalidAmount => { + SigningError(CommonError::Error_invalid_requested_token_amount) + }, + } + } +} + +pub struct Signer { + _phantom: PhantomData, +} + +impl Signer { + #[inline] + pub fn sign_proto(input: Proto::SigningInput<'_>) -> Proto::SigningOutput<'static> { + Self::sign_proto_impl(input) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn sign_proto_impl( + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + let private_key = secp256k1::PrivateKey::try_from(input.private_key.as_ref())?; + + let Some(ref transaction) = input.transaction else { + return Err(SigningError(CommonError::Error_invalid_params)); + }; + + let canister_id = Context::get_canister_id(); + let signed_transaction = + sign_transaction(private_key, canister_id, &transaction.transaction_oneof) + .map_err(SigningError::from)?; + + let cbor_encoded_signed_transaction = tw_encoding::cbor::encode(&signed_transaction) + .map_err(|_| SigningError(CommonError::Error_internal))?; + + Ok(Proto::SigningOutput { + signed_transaction: cbor_encoded_signed_transaction.into(), + ..Proto::SigningOutput::default() + }) + } +} diff --git a/rust/tw_internet_computer/src/transactions/mod.rs b/rust/tw_internet_computer/src/transactions/mod.rs new file mode 100644 index 00000000000..e401c7e08f5 --- /dev/null +++ b/rust/tw_internet_computer/src/transactions/mod.rs @@ -0,0 +1,47 @@ +// 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. + +pub mod transfer; + +pub mod proto { + include!(concat!(env!("OUT_DIR"), "/proto/mod.rs")); +} + +use tw_keypair::ecdsa::secp256k1::PrivateKey; +use tw_proto::InternetComputer::Proto::mod_Transaction::OneOftransaction_oneof as Tx; + +use crate::protocol::{identity, principal::Principal, rosetta}; + +#[derive(Debug)] +pub enum SignTransactionError { + InvalidAmount, + InvalidArguments, + Identity(identity::SigningError), + EncodingArgsFailed, + InvalidToAccountIdentifier, + InvalidEnvelopePair, +} + +pub fn sign_transaction( + private_key: PrivateKey, + canister_id: Principal, + transaction: &Tx, +) -> Result { + match transaction { + Tx::transfer(transfer_args) => transfer::transfer( + private_key, + canister_id, + transfer::TransferArgs { + memo: transfer_args.memo, + amount: transfer_args.amount, + max_fee: None, + to: transfer_args.to_account_identifier.to_string(), + current_timestamp_nanos: transfer_args.current_timestamp_nanos, + }, + ), + Tx::None => Err(SignTransactionError::InvalidArguments), + } +} diff --git a/rust/tw_internet_computer/src/transactions/proto/ledger.proto b/rust/tw_internet_computer/src/transactions/proto/ledger.proto new file mode 100644 index 00000000000..726d2ffd74b --- /dev/null +++ b/rust/tw_internet_computer/src/transactions/proto/ledger.proto @@ -0,0 +1,315 @@ +// 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. + +// -*- c-basic-offset: 2 -*- +// Source: https://github.com/dfinity/ic/blob/master/rs/rosetta-api/icp_ledger/proto/ic_ledger/pb/v1/types.proto +// Commit Hash: 703eb96fea44ad2c82740e4360a526a2a127a960 +// Striped annotations related to the use of hardware wallets. + +syntax = "proto3"; + +package ic_ledger.pb.v1; + +import "./types.proto"; + +// Annotations related to the use of hardware wallets. The annotated messages are +// parsed on hardware wallets and marked fields are displayed in a trusted user +// interface (TUI). We must not, for instance, add fields that would change the +// semantics of the message such that old hardware wallets would not display +// appropriate information to users. + +// ** LEDGER CANISTER ENDPOINTS + +// Initialise the ledger canister +message LedgerInit { + AccountIdentifier minting_account = 1; + repeated Account initial_values = 2; + ic_base_types.pb.v1.PrincipalId archive_canister = 3; + uint32 max_message_size_bytes = 4; +} + +// The format of values serialized to/from the stable memory during and upgrade +message LedgerUpgrade {} + +// Make a payment +message SendRequest { + Memo memo = 1; + Payment payment = 2; + Tokens max_fee = 3; + Subaccount from_subaccount = 4; + AccountIdentifier to = 5; + BlockIndex created_at = 6; + TimeStamp created_at_time = 7; +} + +message SendResponse { + BlockIndex resulting_height = 1; +} + +// Notify a canister that it has received a payment +message NotifyRequest { + BlockIndex block_height = 1; + Tokens max_fee = 2; + Subaccount from_subaccount = 3; + ic_base_types.pb.v1.PrincipalId to_canister = 4; + Subaccount to_subaccount = 5; +} + +message NotifyResponse {} + +message TransactionNotificationRequest { + ic_base_types.pb.v1.PrincipalId from = 1; + Subaccount from_subaccount = 2; + ic_base_types.pb.v1.PrincipalId to = 3; + Subaccount to_subaccount = 4; + BlockIndex block_height = 5; + Tokens amount = 6; + Memo memo = 7; +} + +message TransactionNotificationResponse { + bytes response = 1; +} + +message CyclesNotificationResponse { + oneof response { + ic_base_types.pb.v1.PrincipalId created_canister_id = 1; + Refund refund = 2; + ToppedUp topped_up = 3; + } +} + +// Get the balance of an account +message AccountBalanceRequest { + AccountIdentifier account = 1; +} + +message AccountBalanceResponse { + Tokens balance = 1; +} + +// Get the length of the chain with a certification +message TipOfChainRequest {} + +message TipOfChainResponse { + Certification certification = 1; + BlockIndex chain_length = 2; +} + +// How many Tokens are there not in the minting account +message TotalSupplyRequest {} + +message TotalSupplyResponse { + Tokens total_supply = 1; +} + +// Archive any blocks older than this +message LedgerArchiveRequest { + TimeStamp timestamp = 1; +} + +// * Shared Endpoints * + +// Get a single block +message BlockRequest { + uint64 block_height = 1; +} + +message EncodedBlock { + bytes block = 1; +} + +message BlockResponse { + oneof block_content { + EncodedBlock block = 1; + ic_base_types.pb.v1.PrincipalId canister_id = 2; + } +} + +// Get a set of blocks +message GetBlocksRequest { + uint64 start = 1; + uint64 length = 2; +} + +message Refund { + BlockIndex refund = 2; + string error = 3; +} + +message ToppedUp {} + +message EncodedBlocks { + repeated EncodedBlock blocks = 1; +} + +message GetBlocksResponse { + oneof get_blocks_content { + EncodedBlocks blocks = 1; + string error = 2; + } +} + +// Iterate through blocks +message IterBlocksRequest { + uint64 start = 1; + uint64 length = 2; +} + +message IterBlocksResponse { + repeated EncodedBlock blocks = 1; +} + +message ArchiveIndexEntry { + uint64 height_from = 1; + uint64 height_to = 2; + ic_base_types.pb.v1.PrincipalId canister_id = 3; +} + +message ArchiveIndexResponse { + repeated ArchiveIndexEntry entries = 1; +} + +// ** ARCHIVE CANISTER ENDPOINTS ** + +// * Archive canister * +// Init the archive canister +message ArchiveInit { + uint32 node_max_memory_size_bytes = 1; + uint32 max_message_size_bytes = 2; +} + +// Add blocks to the archive canister +message ArchiveAddRequest { + repeated Block blocks = 1; +} + +message ArchiveAddResponse {} + +// Fetch a list of all of the archive nodes +message GetNodesRequest {} + +message GetNodesResponse { + repeated ic_base_types.pb.v1.PrincipalId nodes = 1; +} + +// ** BASIC TYPES ** +message Tokens { + uint64 e8s = 1; +} + +message Payment { + Tokens receiver_gets = 1; +} + +message BlockIndex { + uint64 height = 1; +} + +// This is the +message Block { + Hash parent_hash = 1; + TimeStamp timestamp = 2; + Transaction transaction = 3; +} + +message Hash { + bytes hash = 1; +} + +message Account { + AccountIdentifier identifier = 1; + Tokens balance = 2; +} + +message Transaction { + oneof transfer { + Burn burn = 1; + Mint mint = 2; + Send send = 3; + } + Memo memo = 4; + Icrc1Memo icrc1_memo = 7; + BlockIndex created_at = 5; // obsolete + TimeStamp created_at_time = 6; +} + +message Send { + // The meaning of the [from] field depends on the transaction type: + // - Transfer: [from] is the source account. + // - TransferFrom: [from] is the approver. + // - Approve: [from] is the approver. + AccountIdentifier from = 1; + // The meaning of the [to] field depends on the transaction type: + // - Transfer: [to] is the destination account. + // - TransferFrom: [to] is the destination account. + // - Approve: [to] is the default account id of the approved principal. + AccountIdentifier to = 2; + // If the transaction type is Approve, the amount must be zero. + Tokens amount = 3; + Tokens max_fee = 4; + + // We represent metadata of new operation types as submessages for + // backward compatibility with old clients. + oneof extension { + Approve approve = 5; + TransferFrom transfer_from = 6; + } +} + +message TransferFrom { + // The default account id of the principal who sent the transaction. + AccountIdentifier spender = 1; +} + +message Approve { + Tokens allowance = 1; + TimeStamp expires_at = 2; + Tokens expected_allowance = 3; +} + +message Mint { + AccountIdentifier to = 2; + Tokens amount = 3; +} + +message Burn { + AccountIdentifier from = 1; + Tokens amount = 3; +} + +message AccountIdentifier { + // Can contain either: + // * the 32 byte identifier (4 byte checksum + 28 byte hash) + // * the 28 byte hash + bytes hash = 1; +} + +message Subaccount { + bytes sub_account = 1; +} + +message Memo { + uint64 memo = 1; +} + +message Icrc1Memo { + bytes memo = 1; +} + +message TimeStamp { + uint64 timestamp_nanos = 1; +} + +message Certification { + bytes certification = 1; +} + +message TransferFeeRequest {} + +message TransferFeeResponse { + Tokens transfer_fee = 1; +} \ No newline at end of file diff --git a/rust/tw_internet_computer/src/transactions/proto/types.proto b/rust/tw_internet_computer/src/transactions/proto/types.proto new file mode 100644 index 00000000000..2c63ceb555e --- /dev/null +++ b/rust/tw_internet_computer/src/transactions/proto/types.proto @@ -0,0 +1,19 @@ +// 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. + +// Source: https://github.com/dfinity/ic/blob/master/rs/rosetta-api/icp_ledger/proto/ic_ledger/pb/v1/types.proto +// Commit Hash: 703eb96fea44ad2c82740e4360a526a2a127a960 +// Striped annotations related to the use of hardware wallets. + +syntax = "proto3"; + +package ic_base_types.pb.v1; + +// A PB container for a PrincipalId, which uniquely identifies +// a principal. +message PrincipalId { + bytes serialized_id = 1; +} \ No newline at end of file diff --git a/rust/tw_internet_computer/src/transactions/transfer.rs b/rust/tw_internet_computer/src/transactions/transfer.rs new file mode 100644 index 00000000000..0b57b56d058 --- /dev/null +++ b/rust/tw_internet_computer/src/transactions/transfer.rs @@ -0,0 +1,247 @@ +// 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. + +use std::time::Duration; + +use tw_keypair::ecdsa::secp256k1::PrivateKey; + +use crate::{ + address::AccountIdentifier, + protocol::{ + envelope::{ + Envelope, EnvelopeCallContent, EnvelopeReadStateContent, Label, RepresentationHashable, + }, + get_ingress_expiry, + identity::Identity, + principal::Principal, + request_id::RequestId, + rosetta, + }, + transactions::proto::ic_ledger::pb::v1::{ + AccountIdentifier as ProtoAccountIdentifier, Memo, Payment, SendRequest, TimeStamp, Tokens, + }, +}; + +use super::SignTransactionError; + +/// Arguments to be used with [transfer] to create a signed transaction enveloper pair. +#[derive(Clone, Debug)] +pub struct TransferArgs { + /// The memo field is used as a method to help identify the transaction. + pub memo: u64, + /// The amount of ICP to send as e8s. + pub amount: u64, + /// The maximum fee will to be paid to complete the transfer. + /// If not provided, the minimum fee will be applied to the transaction. Currently 10_000 e8s (0.00010000 ICP). + pub max_fee: Option, + /// The address to send the amount to. + pub to: String, + /// The current timestamp in nanoseconds. + pub current_timestamp_nanos: u64, +} + +impl TryFrom for SendRequest { + type Error = SignTransactionError; + + fn try_from(args: TransferArgs) -> Result { + let current_timestamp_duration = Duration::from_nanos(args.current_timestamp_nanos); + let timestamp_nanos = current_timestamp_duration.as_nanos() as u64; + + let to_account_identifier = AccountIdentifier::from_hex(&args.to) + .map_err(|_| SignTransactionError::InvalidToAccountIdentifier)?; + let to_hash = to_account_identifier.as_ref().to_vec(); + + let request = Self { + memo: Some(Memo { memo: args.memo }), + payment: Some(Payment { + receiver_gets: Some(Tokens { e8s: args.amount }), + }), + max_fee: args.max_fee.map(|fee| Tokens { e8s: fee }), + from_subaccount: None, + to: Some(ProtoAccountIdentifier { hash: to_hash }), + created_at: None, + created_at_time: Some(TimeStamp { timestamp_nanos }), + }; + Ok(request) + } +} + +/// The endpoint on the ledger canister that is used to make transfers. +const METHOD_NAME: &str = "send_pb"; + +/// Given a secp256k1 private key, the canister ID of an ICP-based ledger canister, and the actual transfer args, +/// this function creates a signed transaction to be sent to a Rosetta API node. +pub fn transfer( + private_key: PrivateKey, + canister_id: Principal, + args: TransferArgs, +) -> Result { + if args.amount < 1 { + return Err(SignTransactionError::InvalidAmount); + } + + let current_timestamp_duration = Duration::from_nanos(args.current_timestamp_nanos); + let ingress_expiry = get_ingress_expiry(current_timestamp_duration); + let identity = Identity::new(private_key); + + // Encode the arguments for the ledger `send_pb` endpoint. + let send_request = SendRequest::try_from(args)?; + let arg = + tw_proto::serialize(&send_request).map_err(|_| SignTransactionError::EncodingArgsFailed)?; + // Create the update envelope. + let (request_id, update_envelope) = + create_update_envelope(&identity, canister_id, arg, ingress_expiry)?; + + // Create the read state envelope. + let (_, read_state_envelope) = + create_read_state_envelope(&identity, request_id, ingress_expiry)?; + + // Create a new EnvelopePair with the update call and read_state envelopes. + let envelope_pair = rosetta::EnvelopePair::new(update_envelope, read_state_envelope) + .map_err(|_| SignTransactionError::InvalidEnvelopePair)?; + + // Create a signed transaction containing the envelope pair. + let request: rosetta::Request = (rosetta::RequestType::Send, vec![envelope_pair]); + Ok(vec![request]) +} + +#[inline] +fn create_update_envelope( + identity: &Identity, + canister_id: Principal, + arg: Vec, + ingress_expiry: u64, +) -> Result<(RequestId, Envelope), SignTransactionError> { + let sender = identity.sender(); + let content = EnvelopeCallContent { + nonce: None, + ingress_expiry, + sender, + canister_id, + method_name: METHOD_NAME.to_string(), + arg, + }; + + let request_id = content.request_id(); + let signature = identity + .sign(request_id.sig_data()) + .map_err(SignTransactionError::Identity)?; + + let env = Envelope { + content, + sender_pubkey: Some(signature.public_key), + sender_sig: Some(signature.signature), + }; + Ok((request_id, env)) +} + +#[inline] +fn create_read_state_envelope( + identity: &Identity, + update_request_id: RequestId, + ingress_expiry: u64, +) -> Result<(RequestId, Envelope), SignTransactionError> { + let sender = identity.sender(); + + let content = EnvelopeReadStateContent { + ingress_expiry, + sender, + paths: vec![vec![ + Label::from("request_status"), + Label::from(update_request_id), + ]], + }; + + let request_id = content.request_id(); + let signature = identity + .sign(request_id.sig_data()) + .map_err(SignTransactionError::Identity)?; + + let env = Envelope { + content, + sender_pubkey: Some(signature.public_key), + sender_sig: Some(signature.signature), + }; + Ok((request_id, env)) +} + +#[cfg(test)] +mod test { + use tw_encoding::hex; + + use crate::address::AccountIdentifier; + + use super::*; + + pub const SIGNED_TRANSACTION: &str = "81826b5452414e53414354494f4e81a266757064617465a367636f6e74656e74a66c726571756573745f747970656463616c6c6e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d026b63616e69737465725f69644a000000000000000201016b6d6574686f645f6e616d656773656e645f706263617267583b0a0012070a050880c2d72f2a220a20943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a3a0a088090caa5a3a78abd176d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f736967984013186f18b9181c189818b318a8186518b2186118d418971618b1187d18eb185818e01826182f1873183b185018cb185d18ef18d81839186418b3183218da1824182f184e18a01880182718c0189018c918a018fd18c418d9189e189818b318ef1874183b185118e118a51864185918e718ed18c71889186c1822182318ca6a726561645f7374617465a367636f6e74656e74a46c726571756573745f747970656a726561645f73746174656e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d0265706174687381824e726571756573745f7374617475735820e8fbc2d5b0bf837b3a369249143e50d4476faafb2dd620e4e982586a51ebcf1e6d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f7369679840182d182718201888188618ce187f0c182a187a18d718e818df18fb18d318d41118a5186a184b18341842185318d718e618e8187a1828186c186a183618461418f3183318bd18a618a718bc18d918c818b7189d186e1865188418ff18fd18e418e9187f181b18d705184818b21872187818d6181c161833184318a2"; + + fn make_transfer_args() -> TransferArgs { + let current_timestamp_nanos = Duration::from_secs(1_691_709_940).as_nanos() as u64; + let owner = + Principal::from_text("t4u4z-y3dur-j63pk-nw4rv-yxdbt-agtt6-nygn7-ywh6y-zm2f4-sdzle-3qe") + .unwrap(); + let to_account_identifier = AccountIdentifier::new(&owner); + + TransferArgs { + memo: 0, + amount: 100_000_000, + max_fee: None, + to: to_account_identifier.to_hex(), + current_timestamp_nanos, + } + } + + #[test] + fn transfer_successful() { + let private_key = PrivateKey::try_from( + "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be", + ) + .unwrap(); + let canister_id = Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai").unwrap(); + let transfer_args = make_transfer_args(); + + let signed_transaction = transfer(private_key, canister_id, transfer_args).unwrap(); + // Encode the signed transaction. + let cbor_encoded_signed_transaction = + tw_encoding::cbor::encode(&signed_transaction).unwrap(); + let hex_encoded_signed_transaction = hex::encode(&cbor_encoded_signed_transaction, false); + assert_eq!(hex_encoded_signed_transaction, SIGNED_TRANSACTION); + } + + #[test] + fn transfer_invalid_amount() { + let private_key = PrivateKey::try_from( + "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be", + ) + .unwrap(); + let canister_id = Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai").unwrap(); + let mut transfer_args = make_transfer_args(); + transfer_args.amount = 0; + + let signed_transaction = transfer(private_key, canister_id, transfer_args); + assert!(matches!( + signed_transaction, + Err(SignTransactionError::InvalidAmount) + )); + } + + #[test] + fn transfer_invalid_to_account_identifier() { + let private_key = PrivateKey::try_from( + "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be", + ) + .unwrap(); + let canister_id = Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai").unwrap(); + let mut transfer_args = make_transfer_args(); + transfer_args.to = "invalid".to_string(); + + let signed_transaction = transfer(private_key, canister_id, transfer_args); + assert!(matches!( + signed_transaction, + Err(SignTransactionError::InvalidToAccountIdentifier) + )); + } +} diff --git a/rust/tw_keypair/Cargo.toml b/rust/tw_keypair/Cargo.toml index 0328396aedf..52b0a350fa8 100644 --- a/rust/tw_keypair/Cargo.toml +++ b/rust/tw_keypair/Cargo.toml @@ -20,8 +20,9 @@ zeroize = "1.6.0" # ECDSA specific: ecdsa = "0.16.6" der = "0.7.3" -k256 = { version = "0.13.0", features = ["ecdh", "ecdsa", "schnorr", "std"], default-features = false } +k256 = { version = "0.13.0", features = ["ecdh", "ecdsa", "pkcs8", "schnorr", "std"], default-features = false } p256 = { version = "0.13.0", features = ["ecdsa", "std"], default-features = false } +pkcs8 = "0.10.2" rfc6979 = "0.4.0" # ED25519 specific: blake2 = "0.9" diff --git a/rust/tw_keypair/src/ecdsa/secp256k1/public.rs b/rust/tw_keypair/src/ecdsa/secp256k1/public.rs index 68a48621a6e..92dc6b0cc52 100644 --- a/rust/tw_keypair/src/ecdsa/secp256k1/public.rs +++ b/rust/tw_keypair/src/ecdsa/secp256k1/public.rs @@ -7,6 +7,7 @@ use crate::ecdsa::secp256k1::{Signature, VerifySignature}; use crate::traits::VerifyingKeyTrait; use crate::{KeyPairError, KeyPairResult}; +use der::Document; use k256::ecdsa::signature::hazmat::PrehashVerifier; use k256::ecdsa::VerifyingKey; use tw_encoding::hex; @@ -57,6 +58,28 @@ impl PublicKey { let (_prefix, public): (Hash<1>, H512) = self.uncompressed().split(); public } + + /// Returns the public key as DER-encoded bytes. + pub fn der_encoded(&self) -> Vec { + let compressed = false; + let public_key_bytes = self.public.to_encoded_point(compressed); + let subject_public_key = + der::asn1::BitStringRef::new(0, public_key_bytes.as_bytes()).unwrap(); + + let oid: pkcs8::ObjectIdentifier = pkcs8::ObjectIdentifier::new_unwrap("1.2.840.10045.2.1"); + let associated_oid: pkcs8::ObjectIdentifier = + pkcs8::ObjectIdentifier::new_unwrap("1.3.132.0.10"); + let spki = pkcs8::SubjectPublicKeyInfo { + algorithm: pkcs8::spki::AlgorithmIdentifier { + oid, + parameters: Some(associated_oid), + }, + subject_public_key, + }; + + let doc = Document::try_from(&spki).unwrap(); + doc.into_vec() + } } impl VerifyingKeyTrait for PublicKey { diff --git a/src/Coin.cpp b/src/Coin.cpp index d7a0d45a2a8..29b91e2e9a7 100644 --- a/src/Coin.cpp +++ b/src/Coin.cpp @@ -65,6 +65,7 @@ #include "TheOpenNetwork/Entry.h" #include "Sui/Entry.h" #include "Greenfield/Entry.h" +#include "InternetComputer/Entry.h" // end_of_coin_includes_marker_do_not_modify using namespace TW; @@ -121,6 +122,7 @@ Hedera::Entry HederaDP; TheOpenNetwork::Entry tonDP; Sui::Entry SuiDP; Greenfield::Entry GreenfieldDP; +InternetComputer::Entry InternetComputerDP; // end_of_coin_dipatcher_declarations_marker_do_not_modify CoinEntry* coinDispatcher(TWCoinType coinType) { @@ -179,6 +181,7 @@ CoinEntry* coinDispatcher(TWCoinType coinType) { case TWBlockchainTheOpenNetwork: entry = &tonDP; break; case TWBlockchainSui: entry = &SuiDP; break; case TWBlockchainGreenfield: entry = &GreenfieldDP; break; + case TWBlockchainInternetComputer: entry = &InternetComputerDP; break; // end_of_coin_dipatcher_switch_marker_do_not_modify default: entry = nullptr; break; @@ -237,7 +240,7 @@ namespace TW::internal { assert(dispatcher != nullptr); return dispatcher->normalizeAddress(coin, address); } -} +} // namespace TW::internal std::string TW::normalizeAddress(TWCoinType coin, const string& address) {; if (!TW::validateAddress(coin, address)) { diff --git a/src/InternetComputer/Entry.cpp b/src/InternetComputer/Entry.cpp new file mode 100644 index 00000000000..a4d7e6bff38 --- /dev/null +++ b/src/InternetComputer/Entry.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. + +#include "Entry.h" + +namespace TW::InternetComputer { + +// Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. + +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { + return validateAddressRust(coin, address, addressPrefix); +} + +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { + return deriveAddressRust(coin, publicKey, derivation, addressPrefix); +} + +void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + signRust(dataIn, coin, dataOut); +} + +TW::Data Entry::preImageHashes(TWCoinType coin, const Data& txInputData) const { + return preImageHashesRust(coin, txInputData); +} + +void Entry::compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + compileRust(coin, txInputData, signatures, publicKeys, dataOut); +} + +} // namespace TW::InternetComputer diff --git a/src/InternetComputer/Entry.h b/src/InternetComputer/Entry.h new file mode 100644 index 00000000000..e997bddb2d6 --- /dev/null +++ b/src/InternetComputer/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::InternetComputer { + +/// Entry point for implementation of InternetComputer 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; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + 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::InternetComputer diff --git a/src/proto/InternetComputer.proto b/src/proto/InternetComputer.proto new file mode 100644 index 00000000000..00cd513baf0 --- /dev/null +++ b/src/proto/InternetComputer.proto @@ -0,0 +1,46 @@ +// 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. + +syntax = "proto3"; + +package TW.InternetComputer.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Common.proto"; + +// Internet Computer Transactions +message Transaction { + + // ICP ledger transfer arguments + message Transfer { + string to_account_identifier = 1; + uint64 amount = 2; + uint64 memo = 3; + uint64 current_timestamp_nanos = 4; + } + + // Payload transfer + oneof transaction_oneof { + Transfer transfer = 1; + } +} + +// Input data necessary to create a signed transaction. +message SigningInput { + bytes private_key = 1; + Transaction transaction = 2; +} + +// Transaction signing output. +message SigningOutput { + // Signed and encoded transaction bytes. + // NOTE: Before sending to the Rosetta node, this value should be hex-encoded before using with the JSON structure. + bytes signed_transaction = 1; + + Common.Proto.SigningError error = 2; + + string error_message = 3; +} \ No newline at end of file diff --git a/swift/Tests/Blockchains/InternetComputerTests.swift b/swift/Tests/Blockchains/InternetComputerTests.swift new file mode 100644 index 00000000000..0ea025dd0c9 --- /dev/null +++ b/swift/Tests/Blockchains/InternetComputerTests.swift @@ -0,0 +1,78 @@ +// 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. + +import WalletCore +import XCTest + +class InternetComputerTests: XCTestCase { + // TODO: Check and finalize implementation + + func testAddress() { + // TODO: Check and finalize implementation + + let key = PrivateKey(data: Data(hexString: "ee42eaada903e20ef6e5069f0428d552475c1ea7ed940842da6448f6ef9d48e7")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: false) + let address = AnyAddress(publicKey: pubkey, coin: .internetComputer) + let addressFromString = AnyAddress(string: "2f25874478d06cf68b9833524a6390d0ba69c566b02f46626979a3d6a4153211", coin: .internetComputer)! + + XCTAssertEqual(pubkey.data.hexString, "048542e6fb4b17d6dfcac3948fe412c00d626728815ee7cc70509603f1bc92128a6e7548f3432d6248bc49ff44a1e50f6389238468d17f7d7024de5be9b181dbc8") + XCTAssertEqual(address.description, addressFromString.description) + } + + func testSign() { + let key = PrivateKey(data: Data(hexString: "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be")!)! + let input = InternetComputerSigningInput.with { + $0.privateKey = key.data + $0.transaction = InternetComputerTransaction.with { + $0.transfer = InternetComputerTransaction.Transfer.with { + $0.toAccountIdentifier = "943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a" + $0.amount = 100000000 + $0.memo = 0 + $0.currentTimestampNanos = 1691709940000000000 + } + } + + } + let output: InternetComputerSigningOutput = AnySigner.sign(input: input, coin: .internetComputer) + XCTAssertEqual(output.signedTransaction.hexString, "81826b5452414e53414354494f4e81a266757064617465a367636f6e74656e74a66c726571756573745f747970656463616c6c6e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d026b63616e69737465725f69644a000000000000000201016b6d6574686f645f6e616d656773656e645f706263617267583b0a0012070a050880c2d72f2a220a20943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a3a0a088090caa5a3a78abd176d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f736967984013186f18b9181c189818b318a8186518b2186118d418971618b1187d18eb185818e01826182f1873183b185018cb185d18ef18d81839186418b3183218da1824182f184e18a01880182718c0189018c918a018fd18c418d9189e189818b318ef1874183b185118e118a51864185918e718ed18c71889186c1822182318ca6a726561645f7374617465a367636f6e74656e74a46c726571756573745f747970656a726561645f73746174656e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d0265706174687381824e726571756573745f7374617475735820e8fbc2d5b0bf837b3a369249143e50d4476faafb2dd620e4e982586a51ebcf1e6d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f7369679840182d182718201888188618ce187f0c182a187a18d718e818df18fb18d318d41118a5186a184b18341842185318d718e618e8187a1828186c186a183618461418f3183318bd18a618a718bc18d918c818b7189d186e1865188418ff18fd18e418e9187f181b18d705184818b21872187818d6181c161833184318a2") + } + + func testSignWithInvalidToAccountIdentifier() { + let key = PrivateKey(data: Data(hexString: "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be")!)! + let input = InternetComputerSigningInput.with { + $0.privateKey = key.data + $0.transaction = InternetComputerTransaction.with { + $0.transfer = InternetComputerTransaction.Transfer.with { + $0.toAccountIdentifier = "643d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826b" + $0.amount = 100000000 + $0.memo = 0 + $0.currentTimestampNanos = 1691709940000000000 + } + } + + } + let output: InternetComputerSigningOutput = AnySigner.sign(input: input, coin: .internetComputer) + XCTAssertEqual(output.error.rawValue, 16) + } + + func testSignWithInvalidAmount() { + let key = PrivateKey(data: Data(hexString: "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be")!)! + let input = InternetComputerSigningInput.with { + $0.privateKey = key.data + $0.transaction = InternetComputerTransaction.with { + $0.transfer = InternetComputerTransaction.Transfer.with { + $0.toAccountIdentifier = "943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a" + $0.amount = 0 + $0.memo = 0 + $0.currentTimestampNanos = 1691709940000000000 + } + } + + } + let output: InternetComputerSigningOutput = AnySigner.sign(input: input, coin: .internetComputer) + XCTAssertEqual(output.error.rawValue, 23) + } +} diff --git a/swift/Tests/CoinAddressDerivationTests.swift b/swift/Tests/CoinAddressDerivationTests.swift index 14e069f08b4..395a7a6e2ab 100644 --- a/swift/Tests/CoinAddressDerivationTests.swift +++ b/swift/Tests/CoinAddressDerivationTests.swift @@ -383,6 +383,9 @@ class CoinAddressDerivationTests: XCTestCase { case .sei: let expectedResult = "sei142j9u5eaduzd7faumygud6ruhdwme98qagm0sj" assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .internetComputer: + let expectedResult = "b9a13d974ee9db036d5abc5b66ace23e513cb5676f3996626c7717c339a3ee87" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) @unknown default: fatalError() } diff --git a/tests/chains/InternetComputer/TWAnyAddressTests.cpp b/tests/chains/InternetComputer/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..013ff0f158e --- /dev/null +++ b/tests/chains/InternetComputer/TWAnyAddressTests.cpp @@ -0,0 +1,20 @@ +// 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 + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TWInternetComputer, Address) { + auto string = STRING("58b26ace22a36a0011608a130e84c7cf34ba469c38d24ccf606152ce7de91f4e"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeInternetComputer)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); +} diff --git a/tests/chains/InternetComputer/TWAnySignerTests.cpp b/tests/chains/InternetComputer/TWAnySignerTests.cpp new file mode 100644 index 00000000000..71e4ba77e62 --- /dev/null +++ b/tests/chains/InternetComputer/TWAnySignerTests.cpp @@ -0,0 +1,36 @@ +// 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/Common.pb.h" +#include "proto/InternetComputer.pb.h" +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +namespace TW::InternetComputer { + +TEST(TWAnySignerInternetComputer, Sign) { + auto key = parse_hex("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be"); + auto input = Proto::SigningInput(); + input.set_private_key(key.data(), key.size()); + input.mutable_transaction()->mutable_transfer()->set_to_account_identifier("943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a"); + input.mutable_transaction()->mutable_transfer()->set_amount(100'000'000); + input.mutable_transaction()->mutable_transfer()->set_memo(0); + input.mutable_transaction()->mutable_transfer()->set_current_timestamp_nanos(1'691'709'940'000'000'000); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeInternetComputer); + + const auto signed_transaction = output.signed_transaction(); + const auto hex_encoded = hex(signed_transaction); + ASSERT_EQ(hex_encoded, "81826b5452414e53414354494f4e81a266757064617465a367636f6e74656e74a66c726571756573745f747970656463616c6c6e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d026b63616e69737465725f69644a000000000000000201016b6d6574686f645f6e616d656773656e645f706263617267583b0a0012070a050880c2d72f2a220a20943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a3a0a088090caa5a3a78abd176d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f736967984013186f18b9181c189818b318a8186518b2186118d418971618b1187d18eb185818e01826182f1873183b185018cb185d18ef18d81839186418b3183218da1824182f184e18a01880182718c0189018c918a018fd18c418d9189e189818b318ef1874183b185118e118a51864185918e718ed18c71889186c1822182318ca6a726561645f7374617465a367636f6e74656e74a46c726571756573745f747970656a726561645f73746174656e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d0265706174687381824e726571756573745f7374617475735820e8fbc2d5b0bf837b3a369249143e50d4476faafb2dd620e4e982586a51ebcf1e6d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f7369679840182d182718201888188618ce187f0c182a187a18d718e818df18fb18d318d41118a5186a184b18341842185318d718e618e8187a1828186c186a183618461418f3183318bd18a618a718bc18d918c818b7189d186e1865188418ff18fd18e418e9187f181b18d705184818b21872187818d6181c161833184318a2"); +} + +} // namespace TW::InternetComputer diff --git a/tests/chains/InternetComputer/TWCoinTypeTests.cpp b/tests/chains/InternetComputer/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..ea7bbc624d1 --- /dev/null +++ b/tests/chains/InternetComputer/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(TWInternetComputerCoinType, TWCoinType) { + const auto coin = TWCoinTypeInternetComputer; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("9e32c54975adf84a1d98f19df41bbc34a752a899c32cc9c0000200b2c4308f85")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("529ea51c22e8d66e8302eabd9297b100fdb369109822248bb86939a671fbc55b")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "internet_computer"); + assertStringsEqual(name, "Internet Computer"); + assertStringsEqual(symbol, "ICP"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 8); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainInternetComputer); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(txUrl, "https://dashboard.internetcomputer.org/transaction/9e32c54975adf84a1d98f19df41bbc34a752a899c32cc9c0000200b2c4308f85"); + assertStringsEqual(accUrl, "https://dashboard.internetcomputer.org/account/529ea51c22e8d66e8302eabd9297b100fdb369109822248bb86939a671fbc55b"); +} diff --git a/tests/common/CoinAddressDerivationTests.cpp b/tests/common/CoinAddressDerivationTests.cpp index 9ee3b5e73b4..233e4962907 100644 --- a/tests/common/CoinAddressDerivationTests.cpp +++ b/tests/common/CoinAddressDerivationTests.cpp @@ -378,6 +378,9 @@ TEST(Coin, DeriveAddress) { case TWCoinTypeSei: EXPECT_EQ(address, "sei1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z05hw42q"); break; + case TWCoinTypeInternetComputer: + EXPECT_EQ(address, "cb3aa6a0471a417fc33d8e71f1d241750dfa29b4dc8f084265ce1301fb03b65b"); + break; // no default branch here, intentionally, to better notice any missing coins } } diff --git a/wasm/tests/Blockchain/InternetComputer.test.ts b/wasm/tests/Blockchain/InternetComputer.test.ts new file mode 100644 index 00000000000..1adf5a8fa52 --- /dev/null +++ b/wasm/tests/Blockchain/InternetComputer.test.ts @@ -0,0 +1,139 @@ +// 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 "mocha"; +import { assert } from "chai"; +import { Buffer } from "buffer"; +import { TW } from "../../dist"; +import Long = require("long"); + +describe("InternetComputer", () => { + + it("test address", () => { + const { PrivateKey, HexCoding, AnyAddress, CoinType, Curve } = globalThis.core; + const privateKeyBytes = HexCoding.decode("ee42eaada903e20ef6e5069f0428d552475c1ea7ed940842da6448f6ef9d48e7"); + + assert.isTrue(PrivateKey.isValid(privateKeyBytes, Curve.secp256k1)); + + const privateKey = PrivateKey.createWithData(privateKeyBytes); + const publicKey = privateKey.getPublicKeySecp256k1(false); + + assert.equal( + HexCoding.encode(publicKey.data()), + "0x048542e6fb4b17d6dfcac3948fe412c00d626728815ee7cc70509603f1bc92128a6e7548f3432d6248bc49ff44a1e50f6389238468d17f7d7024de5be9b181dbc8" + ); + + const address = AnyAddress.createWithPublicKey(publicKey, CoinType.internetComputer); + + assert.equal(address.description(), "2f25874478d06cf68b9833524a6390d0ba69c566b02f46626979a3d6a4153211"); + + privateKey.delete(); + publicKey.delete(); + address.delete(); + }); + + it("test sign", () => { + const { HexCoding, AnySigner, CoinType } = globalThis.core; + const privateKeyBytes = HexCoding.decode("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be"); + + const input = TW.InternetComputer.Proto.SigningInput.create({ + transaction: TW.InternetComputer.Proto.Transaction.create({ + transfer: TW.InternetComputer.Proto.Transaction.Transfer.create({ + toAccountIdentifier: "943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a", + amount: new Long(100000000), + memo: new Long(0), + currentTimestampNanos: Long.fromString("1691709940000000000") + }) + }), + privateKey: privateKeyBytes + }); + + const encoded = TW.InternetComputer.Proto.SigningInput.encode(input).finish(); + + const outputData = AnySigner.sign(encoded, CoinType.internetComputer); + const output = TW.InternetComputer.Proto.SigningOutput.decode(outputData); + const signedTransaction = HexCoding.encode(output.signedTransaction); + + assert.equal(signedTransaction, "0x81826b5452414e53414354494f4e81a266757064617465a367636f6e74656e74a66c726571756573745f747970656463616c6c6e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d026b63616e69737465725f69644a000000000000000201016b6d6574686f645f6e616d656773656e645f706263617267583b0a0012070a050880c2d72f2a220a20943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a3a0a088090caa5a3a78abd176d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f736967984013186f18b9181c189818b318a8186518b2186118d418971618b1187d18eb185818e01826182f1873183b185018cb185d18ef18d81839186418b3183218da1824182f184e18a01880182718c0189018c918a018fd18c418d9189e189818b318ef1874183b185118e118a51864185918e718ed18c71889186c1822182318ca6a726561645f7374617465a367636f6e74656e74a46c726571756573745f747970656a726561645f73746174656e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d0265706174687381824e726571756573745f7374617475735820e8fbc2d5b0bf837b3a369249143e50d4476faafb2dd620e4e982586a51ebcf1e6d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f7369679840182d182718201888188618ce187f0c182a187a18d718e818df18fb18d318d41118a5186a184b18341842185318d718e618e8187a1828186c186a183618461418f3183318bd18a618a718bc18d918c818b7189d186e1865188418ff18fd18e418e9187f181b18d705184818b21872187818d6181c161833184318a2"); + }); + + it("test sign with invalid private key", () => { + const { HexCoding, AnySigner, CoinType } = globalThis.core; + const privateKeyBytes = HexCoding.decode("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7000000"); + + const input = TW.InternetComputer.Proto.SigningInput.create({ + transaction: TW.InternetComputer.Proto.Transaction.create({ + transfer: TW.InternetComputer.Proto.Transaction.Transfer.create({ + toAccountIdentifier: "943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a", + amount: new Long(100000000), + memo: new Long(0), + currentTimestampNanos: Long.fromString("1691709940000000000") + }) + }), + privateKey: privateKeyBytes + }); + + const encoded = TW.InternetComputer.Proto.SigningInput.encode(input).finish(); + + const outputData = AnySigner.sign(encoded, CoinType.internetComputer); + const output = TW.InternetComputer.Proto.SigningOutput.decode(outputData); + const signedTransaction = HexCoding.encode(output.signedTransaction); + + // Invalid private key + assert.equal(output.error, 15); + }); + + it("test sign with invalid to account identifier", () => { + const { HexCoding, AnySigner, CoinType } = globalThis.core; + const privateKeyBytes = HexCoding.decode("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be"); + + const input = TW.InternetComputer.Proto.SigningInput.create({ + transaction: TW.InternetComputer.Proto.Transaction.create({ + transfer: TW.InternetComputer.Proto.Transaction.Transfer.create({ + toAccountIdentifier: "643d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826b", + amount: new Long(100000000), + memo: new Long(0), + currentTimestampNanos: Long.fromString("1691709940000000000") + }) + }), + privateKey: privateKeyBytes + }); + + const encoded = TW.InternetComputer.Proto.SigningInput.encode(input).finish(); + + const outputData = AnySigner.sign(encoded, CoinType.internetComputer); + const output = TW.InternetComputer.Proto.SigningOutput.decode(outputData); + const signedTransaction = HexCoding.encode(output.signedTransaction); + + // Invalid account identifier + assert.equal(output.error, 16); + }); + + it("test sign with invalid amount", () => { + const { HexCoding, AnySigner, CoinType } = globalThis.core; + const privateKeyBytes = HexCoding.decode("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be"); + + const input = TW.InternetComputer.Proto.SigningInput.create({ + transaction: TW.InternetComputer.Proto.Transaction.create({ + transfer: TW.InternetComputer.Proto.Transaction.Transfer.create({ + toAccountIdentifier: "943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a", + amount: new Long(0), + memo: new Long(0), + currentTimestampNanos: Long.fromString("1691709940000000000") + }) + }), + privateKey: privateKeyBytes + }); + + const encoded = TW.InternetComputer.Proto.SigningInput.encode(input).finish(); + + const outputData = AnySigner.sign(encoded, CoinType.internetComputer); + const output = TW.InternetComputer.Proto.SigningOutput.decode(outputData); + + assert.equal(output.error, 23); + assert.equal(output.errorMessage, 'Invalid input token amount'); + }); +}); \ No newline at end of file