From 7c7e5f8774fa982499b1c30f9bbe7e3ca253a67f Mon Sep 17 00:00:00 2001 From: ozzxzzo Date: Mon, 17 Oct 2022 13:07:43 +0800 Subject: [PATCH 1/3] Update 3.0.4 --- .clang-format | 4 +- .gitignore | 10 +- CMakeLists.txt | 146 +- Dockerfile | 39 +- Package.swift | 17 +- README.md | 69 +- WalletCore.podspec | 9 +- android/app/build.gradle | 9 +- .../blockchains/CoinAddressDerivationTests.kt | 19 +- .../core/app/blockchains/TestCoinType.kt | 16 +- .../app/blockchains/aptos/TestAptosAddress.kt | 28 + .../app/blockchains/aptos/TestAptosSigner.kt | 58 + .../bandchain/TestBandChainSigner.kt | 4 +- .../bluzelle/TestBluzelleSigner.kt | 4 +- .../blockchains/cardano/TestCardanoAddress.kt | 8 +- .../blockchains/cardano/TestCardanoSigning.kt | 264 + .../cosmos/TestCosmosTransactions.kt | 124 +- .../cryptoorg/TestCryptoorgSigner.kt | 15 +- .../blockchains/elrond/TestElrondAddress.kt | 6 +- .../blockchains/elrond/TestElrondSigner.kt | 125 +- .../everscale/TestEverscaleAddress.kt | 28 + .../everscale/TestEverscaleSigner.kt | 48 + .../app/blockchains/juno/TestJunoAddress.kt | 35 + .../blockchains/kava/TestKavaTransactions.kt | 4 +- .../kcc/TestKuCoinCommunityChainAddress.kt | 31 + .../app/blockchains/near/TestNEARSigner.kt | 4 +- .../blockchains/nervos/TestNervosAddress.kt | 29 + .../blockchains/nervos/TestNervosSigner.kt | 69 + .../app/blockchains/oasis/TestOasisSigner.kt | 8 +- .../blockchains/osmosis/TestOsmosisAddress.kt | 31 + .../blockchains/osmosis/TestOsmosisSigner.kt | 78 + .../polkadot/TestPolkadotSigner.kt | 31 + .../TestSmartBitcoinCashAddress.kt | 31 + .../blockchains/stellar/TestStellarAddress.kt | 2 +- .../blockchains/stellar/TestStellarSigner.kt | 1 - .../blockchains/terra/TestTerraClassicTxs.kt | 209 + .../terra/TestTerraTransactions.kt | 76 +- .../app/blockchains/tezos/TestTezosSigner.kt | 122 +- .../thorchain/TestTHORChainSigner.kt | 29 +- .../thorchain/TestTHORSwapSigning.kt | 71 + .../app/blockchains/waves/TestWavesAddress.kt | 2 +- .../trustwallet/core/app/utils/TestBase32.kt | 35 + .../trustwallet/core/app/utils/TestBase64.kt | 34 + .../trustwallet/core/app/utils/TestData.kt | 29 +- .../core/app/utils/TestPrivateKey.kt | 16 +- android/build.gradle | 4 +- .../gradle/wrapper/gradle-wrapper.properties | 6 +- android/trustwalletcore/build.gradle | 13 +- bootstrap.sh | 25 +- cmake/CompilerWarnings.cmake | 95 + cmake/FindHostPackage.cmake | 9 + cmake/PVS-Studio.cmake | 615 ++ cmake/Protobuf.cmake | 82 +- cmake/StandardSettings.cmake | 86 + cmake/StaticAnalyzers.cmake | 37 + codegen/.rubocop.yml | 13 +- codegen/bin/codegen | 15 + codegen/bin/coins | 29 +- codegen/bin/cointests | 33 +- codegen/bin/newcoin | 25 +- codegen/lib/code_generator.rb | 29 +- codegen/lib/coin_test_gen.rb | 22 +- codegen/lib/entity_decl.rb | 5 +- codegen/lib/enum_decl.rb | 5 +- codegen/lib/function_decl.rb | 5 +- codegen/lib/parser.rb | 75 +- codegen/lib/templates/CoinInfoData.cpp.erb | 34 +- codegen/lib/templates/TWCoinTypeTests.cpp.erb | 37 +- codegen/lib/templates/TWDerivation.h.erb | 33 + codegen/lib/templates/TWEthereumChainID.h.erb | 26 + .../{jni/header.erb => copyright_header.erb} | 2 +- codegen/lib/templates/cpp/class.erb | 37 + .../lib/templates/cpp/class_properties.erb | 22 + codegen/lib/templates/cpp/enum.erb | 32 + codegen/lib/templates/cpp/header.erb | 41 + codegen/lib/templates/cpp/includes.erb | 21 + codegen/lib/templates/cpp/method.erb | 6 + codegen/lib/templates/cpp/method_call.erb | 5 + codegen/lib/templates/cpp/method_forward.erb | 29 + .../lib/templates/cpp/parameter_access.erb | 8 + codegen/lib/templates/cpp/static_method.erb | 6 + codegen/lib/templates/hrp.h.erb | 2 +- codegen/lib/templates/java/class.erb | 11 +- codegen/lib/templates/java/enum.erb | 1 + codegen/lib/templates/java/header.erb | 2 +- codegen/lib/templates/java/struct.erb | 11 +- codegen/lib/templates/jni_c.erb | 2 +- codegen/lib/templates/newcoin/Address.cpp.erb | 6 +- codegen/lib/templates/newcoin/Address.h.erb | 7 +- .../templates/newcoin/AddressTests.cpp.erb | 7 +- .../lib/templates/newcoin/AddressTests.kt.erb | 2 +- codegen/lib/templates/newcoin/Entry.cpp.erb | 11 +- codegen/lib/templates/newcoin/Entry.h.erb | 5 +- codegen/lib/templates/newcoin/Proto.erb | 2 +- codegen/lib/templates/newcoin/Signer.cpp.erb | 8 +- codegen/lib/templates/newcoin/Signer.h.erb | 7 +- .../lib/templates/newcoin/SignerTests.cpp.erb | 7 +- .../lib/templates/newcoin/SignerTests.kt.erb | 2 +- .../templates/newcoin/TWAddressTests.cpp.erb | 4 +- .../templates/newcoin/TWSignerTests.cpp.erb | 4 +- codegen/lib/templates/newcoin/Tests.swift.erb | 2 +- codegen/lib/templates/registry.md.erb | 2 +- codegen/lib/templates/swift/class.erb | 2 + .../lib/templates/swift/class_properties.erb | 1 + codegen/lib/templates/swift/enum.erb | 1 + .../lib/templates/swift/enum_extension.erb | 2 +- codegen/lib/templates/swift/header.erb | 8 - codegen/lib/templates/swift/method.erb | 1 + .../lib/templates/swift/parameter_access.erb | 8 +- codegen/lib/templates/swift/static_method.erb | 1 + codegen/lib/templates/swift/struct.erb | 2 + .../lib/templates/swift/struct_properties.erb | 1 + codegen/lib/templates/ts/class_d.erb | 22 + codegen/lib/templates/ts/enum_d.erb | 12 + codegen/lib/templates/wasm_cpp.erb | 7 + codegen/lib/templates/wasm_d_ts.erb | 5 + codegen/lib/templates/wasm_h.erb | 7 + codegen/lib/ts_helper.rb | 93 + codegen/lib/wasm_cpp_helper.rb | 116 + codegen/test/test_jni_helper.rb | 2 +- codegen/test/test_parser.rb | 6 +- coverage.stats | 2 +- docs/registry-fields.md | 196 + docs/registry.md | 45 +- include/TrustWalletCore/TWAES.h | 33 +- include/TrustWalletCore/TWAESPaddingMode.h | 3 +- include/TrustWalletCore/TWAccount.h | 55 +- include/TrustWalletCore/TWAnyAddress.h | 59 +- include/TrustWalletCore/TWAnySigner.h | 27 +- include/TrustWalletCore/TWBase.h | 8 + include/TrustWalletCore/TWBase32.h | 54 + include/TrustWalletCore/TWBase58.h | 19 +- include/TrustWalletCore/TWBase64.h | 47 + include/TrustWalletCore/TWBitcoinAddress.h | 39 +- .../TrustWalletCore/TWBitcoinMessageSigner.h | 43 + include/TrustWalletCore/TWBitcoinScript.h | 160 +- .../TrustWalletCore/TWBitcoinSigHashType.h | 9 + include/TrustWalletCore/TWBlockchain.h | 12 +- include/TrustWalletCore/TWCardano.h | 34 + include/TrustWalletCore/TWCoinType.h | 98 +- .../TrustWalletCore/TWCoinTypeConfiguration.h | 22 + include/TrustWalletCore/TWCurve.h | 2 +- include/TrustWalletCore/TWData.h | 93 +- include/TrustWalletCore/TWDataVector.h | 62 + include/TrustWalletCore/TWDerivationPath.h | 103 + .../TrustWalletCore/TWDerivationPathIndex.h | 53 + include/TrustWalletCore/TWEthereumAbi.h | 17 +- .../TrustWalletCore/TWEthereumAbiFunction.h | 364 +- include/TrustWalletCore/TWEthereumAbiValue.h | 54 +- include/TrustWalletCore/TWEthereumChainID.h | 37 - include/TrustWalletCore/TWFIOAccount.h | 11 + .../TrustWalletCore/TWGroestlcoinAddress.h | 24 +- include/TrustWalletCore/TWHDVersion.h | 14 +- include/TrustWalletCore/TWHDWallet.h | 204 +- include/TrustWalletCore/TWHash.h | 78 +- include/TrustWalletCore/TWMnemonic.h | 12 +- include/TrustWalletCore/TWNEARAccount.h | 11 + include/TrustWalletCore/TWNervosAddress.h | 71 + include/TrustWalletCore/TWPBKDF2.h | 38 + include/TrustWalletCore/TWPrivateKey.h | 112 +- include/TrustWalletCore/TWPrivateKeyType.h | 20 + include/TrustWalletCore/TWPublicKey.h | 70 +- include/TrustWalletCore/TWPublicKeyType.h | 2 +- include/TrustWalletCore/TWPurpose.h | 6 +- include/TrustWalletCore/TWRippleXAddress.h | 27 +- include/TrustWalletCore/TWSS58AddressType.h | 6 +- include/TrustWalletCore/TWSegwitAddress.h | 40 +- include/TrustWalletCore/TWSolanaAddress.h | 19 +- include/TrustWalletCore/TWStellarMemoType.h | 3 +- include/TrustWalletCore/TWStellarPassphrase.h | 1 + .../TrustWalletCore/TWStellarVersionByte.h | 9 +- include/TrustWalletCore/TWStoredKey.h | 184 +- .../TWStoredKeyEncryptionLevel.h | 26 + include/TrustWalletCore/TWString.h | 49 +- .../{TWEthereumFee.h => TWTHORChainSwap.h} | 15 +- .../TrustWalletCore/TWTransactionCompiler.h | 63 + jni/java/wallet/core/java/AnySigner.java | 6 +- protobuf-plugin/CMakeLists.txt | 2 +- protobuf-plugin/swift_typealias.cc | 41 +- registry.json | 1275 ++- samples/android/app/build.gradle | 6 +- .../android/app/src/main/AndroidManifest.xml | 2 +- samples/android/build.gradle | 16 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- samples/cpp/CMakeLists.txt | 47 +- samples/cpp/sample.cpp | 185 +- samples/go/README.md | 52 +- samples/go/compile.sh | 27 + samples/go/core/bitcoin.go | 54 + samples/go/core/coin.go | 28 + samples/go/core/datavector.go | 31 + samples/go/core/mnemonic.go | 14 + samples/go/core/publicKey.go | 41 + samples/go/core/transaction.go | 28 + samples/go/core/transactionHelper.go | 49 + samples/go/core/wallet.go | 54 + samples/go/dev-console/.gitignore | 1 + samples/go/dev-console/README.md | 12 + samples/go/dev-console/cli/address.go | 23 + samples/go/dev-console/cli/completer.go | 37 + samples/go/dev-console/cli/create_wallet.go | 118 + samples/go/dev-console/cli/delete_wallet.go | 41 + samples/go/dev-console/cli/executor.go | 28 + samples/go/dev-console/cli/help.go | 36 + samples/go/dev-console/cli/load_wallet.go | 77 + samples/go/dev-console/cmd/cli.go | 15 + samples/go/dev-console/go.mod | 20 + samples/go/dev-console/go.sum | 39 + samples/go/dev-console/native/cgo.go | 14 + .../go/dev-console/native/packaged/.gitignore | 2 + .../go/dev-console/native/packaged/.gitkeep | 0 .../native/packaged/include/.gitkeep | 0 .../native/packaged/include/dummy.go | 2 + .../dev-console/native/packaged/lib/.gitkeep | 0 .../dev-console/native/packaged/lib/dummy.go | 2 + samples/go/dev-console/native/twaccount.go | 44 + samples/go/dev-console/native/twcoin.go | 24 + samples/go/dev-console/native/twdata.go | 29 + samples/go/dev-console/native/twmnemonic.go | 10 + samples/go/dev-console/native/twstoredkey.go | 54 + samples/go/dev-console/native/twstring.go | 31 + samples/go/dev-console/native/twwallet.go | 44 + samples/go/dev-console/prepare.sh | 10 + samples/go/dev-console/wallet/wallet.go | 41 + samples/go/go.mod | 4 +- samples/go/go.sum | 24 +- samples/go/main.go | 144 +- samples/go/protos/binance/Binance.pb.go | 2965 ++++++ samples/go/protos/bitcoin/Bitcoin.pb.go | 1375 ++- samples/go/protos/common/Common.pb.go | 236 + .../protos/common/TransactionCompiler.pb.go | 188 + samples/go/protos/ethereum/Ethereum.pb.go | 1122 ++ samples/go/sample/external_signing.go | 240 + samples/go/types/twdata.go | 4 +- samples/go/types/twstring.go | 2 +- samples/node/index.ts | 39 + samples/node/package-lock.json | 546 + samples/node/package.json | 19 + samples/node/tsconfig.json | 23 + samples/osx/cocoapods/Podfile | 1 + samples/osx/cocoapods/Podfile.lock | 22 +- .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../WalletCoreExample/ViewController.swift | 7 +- samples/typescript/devconsole.ts/.gitignore | 3 + samples/typescript/devconsole.ts/README.md | 34 + .../devconsole.ts/package-lock.json | 1491 +++ samples/typescript/devconsole.ts/package.json | 28 + .../samplescripts/privkeysign.sampleinput.txt | 12 + .../samplescripts/protoeth.sampleinput.txt | 26 + .../solanaaddress.sampleinput.txt | 13 + .../samplescripts/wallets.sampleinput.txt | 51 + samples/typescript/devconsole.ts/src/index.ts | 366 + .../test/data/privpubkey.testinput.txt | 26 + .../typescript/devconsole.ts/tsconfig.json | 20 + samples/wasm/.gitignore | 2 + samples/wasm/README.md | 42 + samples/wasm/index.html | 1319 +++ src/Aeternity/Address.cpp | 8 +- src/Aeternity/Address.h | 2 + src/Aeternity/Entry.cpp | 10 +- src/Aeternity/Entry.h | 9 +- src/Aeternity/Signer.cpp | 12 +- src/Aeternity/Signer.h | 8 +- src/Aeternity/Transaction.cpp | 5 +- src/Aeternity/Transaction.h | 2 +- src/Aion/Address.cpp | 5 +- src/Aion/Entry.cpp | 17 +- src/Aion/Entry.h | 10 +- src/Aion/RLP.h | 2 +- src/Aion/Signer.cpp | 7 +- src/Aion/Signer.h | 2 +- src/Aion/Transaction.cpp | 8 +- src/Aion/Transaction.h | 2 +- src/Algorand/Address.cpp | 8 +- src/Algorand/Address.h | 2 +- src/Algorand/AssetTransfer.cpp | 7 +- src/Algorand/AssetTransfer.h | 4 +- src/Algorand/BaseTransaction.h | 3 +- src/Algorand/BinaryCoding.h | 2 - src/Algorand/Entry.cpp | 15 +- src/Algorand/Entry.h | 13 +- src/Algorand/OptInAssetTransaction.cpp | 7 +- src/Algorand/OptInAssetTransaction.h | 8 +- src/Algorand/Signer.cpp | 21 +- src/Algorand/Signer.h | 2 +- src/Algorand/Transfer.cpp | 18 +- src/Algorand/Transfer.h | 6 +- src/AnyAddress.cpp | 32 + src/AnyAddress.h | 41 + src/Aptos/Address.cpp | 88 + src/Aptos/Address.h | 59 + src/Aptos/Entry.cpp | 26 + src/Aptos/Entry.h | 22 + src/Aptos/MoveTypes.cpp | 152 + src/Aptos/MoveTypes.h | 107 + src/Aptos/Signer.cpp | 208 + src/Aptos/Signer.h | 27 + src/Aptos/TransactionBuilder.h | 100 + src/Aptos/TransactionPayload.cpp | 57 + src/Aptos/TransactionPayload.h | 46 + src/BCS.cpp | 42 + src/BCS.h | 222 + src/Base32.h | 5 +- src/Base58.cpp | 4 +- src/Base58.h | 8 +- src/Base58Address.h | 2 +- src/Base64.cpp | 12 +- src/Base64.h | 2 +- src/Bech32.cpp | 21 +- src/Bech32.h | 2 + src/Bech32Address.cpp | 40 +- src/Bech32Address.h | 6 +- src/Binance/Address.cpp | 25 +- src/Binance/Address.h | 14 +- src/Binance/Entry.cpp | 95 +- src/Binance/Entry.h | 18 +- src/Binance/Serialization.cpp | 38 +- src/Binance/Signer.cpp | 39 +- src/Binance/Signer.h | 10 +- src/BinaryCoding.h | 7 +- src/Bitcoin/Address.h | 2 +- src/Bitcoin/Amount.h | 11 - src/Bitcoin/CashAddress.cpp | 116 +- src/Bitcoin/CashAddress.h | 59 +- src/Bitcoin/Entry.cpp | 211 +- src/Bitcoin/Entry.h | 41 +- src/Bitcoin/FeeCalculator.cpp | 26 +- src/Bitcoin/FeeCalculator.h | 40 +- src/Bitcoin/InputSelector.cpp | 97 +- src/Bitcoin/InputSelector.h | 45 +- src/Bitcoin/MessageSigner.cpp | 111 + src/Bitcoin/MessageSigner.h | 61 + src/Bitcoin/OpCodes.h | 198 +- src/Bitcoin/OutPoint.cpp | 9 +- src/Bitcoin/OutPoint.h | 38 +- src/Bitcoin/Script.cpp | 72 +- src/Bitcoin/Script.h | 23 +- src/Bitcoin/SegwitAddress.cpp | 15 +- src/Bitcoin/SegwitAddress.h | 17 +- src/Bitcoin/SigHashType.h | 3 +- src/Bitcoin/SignatureBuilder.cpp | 166 +- src/Bitcoin/SignatureBuilder.h | 43 +- src/Bitcoin/SignatureVersion.h | 2 +- src/Bitcoin/Signer.cpp | 32 +- src/Bitcoin/Signer.h | 13 +- src/Bitcoin/SigningInput.cpp | 18 +- src/Bitcoin/SigningInput.h | 2 + src/Bitcoin/Transaction.cpp | 54 +- src/Bitcoin/Transaction.h | 15 +- src/Bitcoin/TransactionBuilder.cpp | 25 +- src/Bitcoin/TransactionBuilder.h | 6 + src/Bitcoin/TransactionInput.cpp | 6 +- src/Bitcoin/TransactionInput.h | 7 +- src/Bitcoin/TransactionOutput.cpp | 6 +- src/Bitcoin/TransactionOutput.h | 7 +- src/Bitcoin/TransactionPlan.h | 6 +- src/Bitcoin/TransactionSigner.cpp | 31 +- src/Bitcoin/TransactionSigner.h | 12 +- src/Cardano/AddressV2.cpp | 57 +- src/Cardano/AddressV2.h | 9 +- src/Cardano/AddressV3.cpp | 263 +- src/Cardano/AddressV3.h | 89 +- src/Cardano/Entry.cpp | 29 +- src/Cardano/Entry.h | 11 +- src/Cardano/Signer.cpp | 535 + src/Cardano/Signer.h | 54 + src/Cardano/Transaction.cpp | 316 + src/Cardano/Transaction.h | 180 + src/Cbor.cpp | 52 +- src/Cbor.h | 18 +- src/Coin.cpp | 232 +- src/Coin.h | 72 +- src/CoinEntry.h | 56 +- src/Cosmos/Address.h | 15 +- src/Cosmos/Entry.cpp | 33 +- src/Cosmos/Entry.h | 23 +- ...erialization.cpp => JsonSerialization.cpp} | 51 +- src/Cosmos/JsonSerialization.h | 31 + src/Cosmos/Protobuf/.clang-tidy | 6 + src/Cosmos/Protobuf/.gitignore | 3 + src/Cosmos/Protobuf/authz_tx.proto | 32 + src/Cosmos/Protobuf/bank_tx.proto | 13 + src/Cosmos/Protobuf/coin.proto | 16 + src/Cosmos/Protobuf/cosmwasm_wasm_v1_tx.proto | 19 + src/Cosmos/Protobuf/crypto_multisig.proto | 15 + .../Protobuf/crypto_secp256k1_keys.proto | 15 + src/Cosmos/Protobuf/distribution_tx.proto | 11 + src/Cosmos/Protobuf/ethermint_keys.proto | 9 + src/Cosmos/Protobuf/gov_tx.proto | 24 + .../ibc_applications_transfer_tx.proto | 28 + src/Cosmos/Protobuf/ibc_core_client.proto | 14 + src/Cosmos/Protobuf/staking_tx.proto | 31 + .../Protobuf/terra_wasm_v1beta1_tx.proto | 21 + src/Cosmos/Protobuf/thorchain_bank_tx.proto | 14 + src/Cosmos/Protobuf/tx.proto | 195 + src/Cosmos/Protobuf/tx_signing.proto | 89 + src/Cosmos/ProtobufSerialization.cpp | 457 + src/Cosmos/ProtobufSerialization.h | 37 + src/Cosmos/Serialization.h | 28 - src/Cosmos/Signer.cpp | 64 +- src/Cosmos/Signer.h | 15 +- src/Crc.cpp | 26 +- src/Crc.h | 48 +- src/Data.cpp | 13 +- src/Data.h | 4 +- src/Decred/Address.cpp | 16 +- src/Decred/Address.h | 2 +- src/Decred/Entry.cpp | 20 +- src/Decred/Entry.h | 12 +- src/Decred/OutPoint.cpp | 6 +- src/Decred/OutPoint.h | 2 +- src/Decred/Signer.cpp | 49 +- src/Decred/Signer.h | 4 +- src/Decred/Transaction.cpp | 18 +- src/Decred/Transaction.h | 2 +- src/Decred/TransactionInput.cpp | 6 +- src/Decred/TransactionInput.h | 2 +- src/Decred/TransactionOutput.cpp | 6 +- src/Decred/TransactionOutput.h | 2 +- src/Defer.h | 19 + src/DerivationPath.h | 64 +- src/EOS/Action.cpp | 17 +- src/EOS/Action.h | 2 - src/EOS/Address.cpp | 37 +- src/EOS/Address.h | 2 +- src/EOS/Asset.cpp | 22 +- src/EOS/Entry.cpp | 13 +- src/EOS/Entry.h | 9 +- src/EOS/Name.cpp | 21 +- src/EOS/Name.h | 6 +- src/EOS/PackedTransaction.cpp | 13 +- src/EOS/Serialization.h | 2 +- src/EOS/Signer.cpp | 19 +- src/EOS/Signer.h | 2 +- src/EOS/Transaction.cpp | 36 +- src/EOS/Transaction.h | 4 +- src/Elrond/Address.cpp | 4 +- src/Elrond/Address.h | 4 +- src/Elrond/Codec.cpp | 45 + src/Elrond/Codec.h | 29 + src/Elrond/Entry.cpp | 21 +- src/Elrond/Entry.h | 14 +- src/Elrond/GasEstimator.cpp | 50 + src/Elrond/GasEstimator.h | 24 + src/Elrond/NetworkConfig.cpp | 85 + src/Elrond/NetworkConfig.h | 54 + src/Elrond/Serialization.cpp | 75 +- src/Elrond/Serialization.h | 12 +- src/Elrond/Signer.cpp | 18 +- src/Elrond/Signer.h | 2 +- src/Elrond/Transaction.cpp | 16 + src/Elrond/Transaction.h | 31 + src/Elrond/TransactionFactory.cpp | 151 + src/Elrond/TransactionFactory.h | 57 + src/Encrypt.h | 2 +- src/Ethereum/ABI/Array.cpp | 83 +- src/Ethereum/ABI/Array.h | 51 +- src/Ethereum/ABI/Bytes.cpp | 11 +- src/Ethereum/ABI/Function.cpp | 21 +- src/Ethereum/ABI/ParamAddress.cpp | 15 +- src/Ethereum/ABI/ParamBase.h | 2 +- src/Ethereum/ABI/ParamFactory.cpp | 23 +- src/Ethereum/ABI/ParamFactory.h | 10 +- src/Ethereum/ABI/ParamNumber.cpp | 29 +- src/Ethereum/ABI/ParamStruct.cpp | 69 +- src/Ethereum/ABI/ParamStruct.h | 6 +- src/Ethereum/ABI/Parameters.cpp | 17 +- src/Ethereum/ABI/Parameters.h | 8 +- src/Ethereum/ABI/Tuple.cpp | 10 +- src/Ethereum/ABI/Tuple.h | 2 +- src/Ethereum/ABI/ValueEncoder.cpp | 2 +- src/Ethereum/Address.cpp | 11 +- src/Ethereum/Address.h | 7 +- src/Ethereum/AddressChecksum.cpp | 12 +- src/Ethereum/AddressChecksum.h | 8 +- src/Ethereum/ContractCall.cpp | 15 +- src/Ethereum/Entry.cpp | 74 +- src/Ethereum/Entry.h | 42 +- src/Ethereum/Fee.cpp | 130 - src/Ethereum/RLP.cpp | 37 +- src/Ethereum/RLP.h | 2 +- src/Ethereum/Signer.cpp | 328 +- src/Ethereum/Signer.h | 21 +- src/Ethereum/Transaction.cpp | 135 +- src/Ethereum/Transaction.h | 4 + src/Everscale/Address.cpp | 77 + src/Everscale/Address.h | 51 + src/Everscale/Cell.cpp | 405 + src/Everscale/Cell.h | 70 + src/Everscale/CellBuilder.cpp | 260 + src/Everscale/CellBuilder.h | 55 + src/Everscale/CellSlice.cpp | 59 + src/Everscale/CellSlice.h | 36 + src/Everscale/Entry.cpp | 36 + src/Everscale/Entry.h | 23 + src/Everscale/Messages.cpp | 146 + src/Everscale/Messages.h | 81 + src/Everscale/Signer.cpp | 69 + src/Everscale/Signer.h | 25 + src/Everscale/Wallet.cpp | 82 + src/Everscale/Wallet.h | 77 + .../Address.cpp => Everscale/WorkchainType.h} | 13 +- src/FIO/Action.cpp | 2 +- src/FIO/Action.h | 4 +- src/FIO/Actor.cpp | 35 +- src/FIO/Address.cpp | 7 +- src/FIO/Address.h | 2 +- src/FIO/Encryption.h | 24 +- src/FIO/Entry.cpp | 13 +- src/FIO/Entry.h | 9 +- src/FIO/NewFundsRequest.h | 2 +- src/FIO/Signer.cpp | 4 +- src/FIO/Signer.h | 4 +- src/FIO/Transaction.h | 2 +- src/FIO/TransactionBuilder.cpp | 2 +- src/FIO/TransactionBuilder.h | 2 +- src/Filecoin/Address.cpp | 21 +- src/Filecoin/Entry.cpp | 15 +- src/Filecoin/Entry.h | 13 +- src/Filecoin/Signer.cpp | 10 +- src/Filecoin/Signer.h | 7 +- src/Filecoin/Transaction.cpp | 12 +- src/Generated/.clang-tidy | 6 + src/Groestlcoin/Address.cpp | 16 +- src/Groestlcoin/Address.h | 2 +- src/Groestlcoin/Entry.cpp | 20 +- src/Groestlcoin/Entry.h | 11 +- src/Groestlcoin/Signer.cpp | 7 +- src/Groestlcoin/Transaction.h | 4 +- src/HDWallet.cpp | 180 +- src/HDWallet.h | 53 +- src/Harmony/Address.cpp | 3 +- src/Harmony/Address.h | 2 +- src/Harmony/Entry.cpp | 22 +- src/Harmony/Entry.h | 14 +- src/Harmony/Signer.cpp | 73 +- src/Harmony/Signer.h | 11 +- src/Harmony/Staking.cpp | 4 - src/Harmony/Staking.h | 76 +- src/Harmony/Transaction.cpp | 9 - src/Hash.cpp | 46 +- src/Hash.h | 58 +- src/Icon/Address.cpp | 10 +- src/Icon/Address.h | 2 +- src/Icon/Entry.cpp | 11 +- src/Icon/Entry.h | 9 +- src/Icon/Signer.cpp | 9 +- src/Icon/Signer.h | 2 +- src/IoTeX/Address.cpp | 4 +- src/IoTeX/Address.h | 2 +- src/IoTeX/Entry.cpp | 11 +- src/IoTeX/Entry.h | 9 +- src/IoTeX/Signer.cpp | 9 +- src/Keystore/AESParameters.cpp | 7 +- src/Keystore/AESParameters.h | 2 +- src/Keystore/Account.cpp | 39 +- src/Keystore/Account.h | 27 +- src/Keystore/EncryptionParameters.cpp | 150 +- src/Keystore/EncryptionParameters.h | 98 +- src/Keystore/PBKDF2Parameters.cpp | 11 +- src/Keystore/PBKDF2Parameters.h | 2 +- src/Keystore/ScryptParameters.cpp | 48 +- src/Keystore/ScryptParameters.h | 25 +- src/Keystore/StoredKey.cpp | 337 +- src/Keystore/StoredKey.h | 96 +- src/Kusama/Address.h | 4 +- src/Kusama/Entry.cpp | 18 +- src/Kusama/Entry.h | 10 +- src/Mnemonic.cpp | 13 +- src/NEAR/Account.cpp | 6 +- src/NEAR/Address.cpp | 11 +- src/NEAR/Address.h | 2 +- src/NEAR/Entry.cpp | 19 +- src/NEAR/Entry.h | 12 +- src/NEAR/Serialization.cpp | 98 +- src/NEAR/Serialization.h | 2 +- src/NEAR/Signer.cpp | 8 +- src/NEO/Address.cpp | 15 +- src/NEO/Address.h | 2 +- src/NEO/CoinReference.h | 7 +- src/NEO/Constants.h | 16 + src/NEO/Entry.cpp | 21 +- src/NEO/Entry.h | 12 +- src/NEO/ISerializable.h | 2 +- src/NEO/MinerTransaction.h | 2 +- src/NEO/ReadData.cpp | 4 +- src/NEO/ReadData.h | 4 +- src/NEO/Script.h | 2 +- src/NEO/Signer.cpp | 18 +- src/NEO/Signer.h | 2 +- src/NEO/Transaction.cpp | 48 +- src/NEO/Transaction.h | 2 +- src/NEO/TransactionAttribute.h | 99 +- src/NEO/TransactionOutput.h | 15 +- src/NEO/Witness.h | 2 +- src/NULS/Address.cpp | 19 +- src/NULS/BinaryCoding.h | 17 +- src/NULS/Entry.cpp | 11 +- src/NULS/Entry.h | 9 +- src/NULS/Signer.cpp | 24 +- src/Nano/Address.cpp | 20 +- src/Nano/Entry.cpp | 20 +- src/Nano/Entry.h | 14 +- src/Nano/Signer.h | 2 +- src/Nebulas/Address.cpp | 10 +- src/Nebulas/Entry.cpp | 10 +- src/Nebulas/Entry.h | 9 +- src/Nebulas/Signer.cpp | 28 +- src/Nebulas/Signer.h | 2 +- src/Nebulas/Transaction.cpp | 94 +- src/Nervos/Address.cpp | 177 + src/Nervos/Address.h | 80 + src/Nervos/Cell.h | 108 + src/Nervos/CellDep.cpp | 39 + src/Nervos/CellDep.h | 45 + src/Nervos/CellInput.cpp | 22 + src/Nervos/CellInput.h | 36 + src/Nervos/CellOutput.cpp | 28 + src/Nervos/CellOutput.h | 53 + src/Nervos/Constants.h | 53 + src/Nervos/Entry.cpp | 33 + src/Nervos/Entry.h | 28 + src/{Ethereum/Fee.h => Nervos/HeaderDep.h} | 15 +- src/Nervos/OutPoint.cpp | 22 + src/Nervos/OutPoint.h | 54 + src/Nervos/Script.cpp | 49 + src/Nervos/Script.h | 107 + src/Nervos/Serialization.h | 60 + src/Nervos/Signer.cpp | 55 + src/Nervos/Signer.h | 26 + src/Nervos/Transaction.cpp | 212 + src/Nervos/Transaction.h | 76 + src/Nervos/TransactionPlan.cpp | 328 + src/Nervos/TransactionPlan.h | 123 + src/Nervos/Witness.cpp | 29 + src/Nervos/Witness.h | 33 + src/Nimiq/Address.cpp | 10 +- src/Nimiq/Address.h | 2 +- src/Nimiq/Entry.cpp | 14 +- src/Nimiq/Entry.h | 9 +- src/Nimiq/Signer.cpp | 5 +- src/Nimiq/Signer.h | 2 +- src/Nimiq/Transaction.cpp | 7 +- src/NumericLiteral.h | 14 + src/Oasis/Address.cpp | 16 +- src/Oasis/Address.h | 2 +- src/Oasis/Entry.cpp | 11 +- src/Oasis/Entry.h | 9 +- src/Oasis/Signer.cpp | 9 +- src/Oasis/Signer.h | 2 +- src/Oasis/Transaction.cpp | 9 +- src/Ontology/Address.cpp | 17 +- src/Ontology/Address.h | 6 +- src/Ontology/Asset.h | 15 +- src/Ontology/Entry.cpp | 13 +- src/Ontology/Entry.h | 11 +- src/Ontology/Oep4.cpp | 69 + src/Ontology/Oep4.h | 41 + src/Ontology/Oep4TxBuilder.cpp | 51 + src/Ontology/Oep4TxBuilder.h | 29 + src/Ontology/Ong.cpp | 33 +- src/Ontology/Ong.h | 4 +- src/Ontology/OngTxBuilder.cpp | 17 +- src/Ontology/OngTxBuilder.h | 2 +- src/Ontology/Ont.cpp | 23 +- src/Ontology/Ont.h | 4 +- src/Ontology/OntTxBuilder.cpp | 15 +- src/Ontology/OntTxBuilder.h | 2 +- src/Ontology/OpCode.h | 5 +- src/Ontology/ParamsBuilder.cpp | 68 +- src/Ontology/ParamsBuilder.h | 32 +- src/Ontology/SigData.cpp | 7 +- src/Ontology/SigData.h | 4 +- src/Ontology/Signer.cpp | 23 +- src/Ontology/Signer.h | 17 +- src/Ontology/Transaction.cpp | 12 +- src/Ontology/Transaction.h | 2 +- src/Polkadot/Address.h | 4 +- src/Polkadot/Entry.cpp | 18 +- src/Polkadot/Entry.h | 10 +- src/Polkadot/Extrinsic.cpp | 248 +- src/Polkadot/Extrinsic.h | 2 +- src/Polkadot/SS58Address.cpp | 117 + src/Polkadot/SS58Address.h | 64 + src/Polkadot/ScaleCodec.h | 4 +- src/Polkadot/Signer.cpp | 5 +- src/Polkadot/Signer.h | 2 +- src/PrivateKey.cpp | 161 +- src/PrivateKey.h | 44 +- src/PublicKey.cpp | 93 +- src/PublicKey.h | 37 +- src/Result.h | 6 +- src/Ronin/Address.cpp | 55 + src/Ronin/Address.h | 31 + src/Ronin/Entry.cpp | 42 + src/Ronin/Entry.h | 25 + src/SS58Address.h | 97 - src/Solana/Address.cpp | 7 +- src/Solana/Entry.cpp | 18 +- src/Solana/Entry.h | 14 +- src/Solana/Program.cpp | 38 +- src/Solana/Signer.cpp | 279 +- src/Solana/Signer.h | 2 +- src/Solana/Transaction.cpp | 27 +- src/Solana/Transaction.h | 191 +- src/Stellar/Address.cpp | 6 +- src/Stellar/Address.h | 2 +- src/Stellar/Entry.cpp | 11 +- src/Stellar/Entry.h | 9 +- src/Stellar/Signer.cpp | 96 +- src/Stellar/Signer.h | 6 +- src/THORChain/Entry.cpp | 9 +- src/THORChain/Entry.h | 9 +- src/THORChain/Signer.cpp | 7 +- src/THORChain/Signer.h | 2 +- src/THORChain/Swap.cpp | 233 + src/THORChain/Swap.h | 52 + src/THORChain/TWSwap.cpp | 92 + src/Tezos/Address.cpp | 13 +- src/Tezos/Address.h | 4 +- src/Tezos/BinaryCoding.cpp | 14 +- src/Tezos/BinaryCoding.h | 10 +- src/Tezos/Entry.cpp | 15 +- src/Tezos/Entry.h | 15 +- src/Tezos/Forging.cpp | 191 +- src/Tezos/Forging.h | 23 +- src/Tezos/Michelson.cpp | 32 + src/Tezos/Michelson.h | 56 + src/Tezos/OperationList.cpp | 11 +- src/Tezos/OperationList.h | 13 +- src/Tezos/Signer.cpp | 12 +- src/Tezos/Signer.h | 4 +- src/Theta/Entry.cpp | 20 +- src/Theta/Entry.h | 9 +- src/Theta/Signer.cpp | 8 +- src/Theta/Signer.h | 7 +- src/Theta/Transaction.cpp | 24 +- src/Theta/Transaction.h | 10 +- src/TransactionCompiler.cpp | 38 + src/TransactionCompiler.h | 34 + src/Tron/Address.cpp | 4 +- src/Tron/Address.h | 2 +- src/Tron/Entry.cpp | 10 +- src/Tron/Entry.h | 9 +- src/Tron/Serialization.cpp | 148 +- src/Tron/Serialization.h | 2 +- src/Tron/Signer.cpp | 29 +- src/Tron/Signer.h | 2 +- src/VeChain/Clause.h | 2 +- src/VeChain/Entry.cpp | 22 +- src/VeChain/Entry.h | 9 +- src/VeChain/Signer.cpp | 5 +- src/VeChain/Signer.h | 7 +- src/VeChain/Transaction.cpp | 8 +- src/VeChain/Transaction.h | 2 +- src/Wasm.h | 19 + src/Waves/Address.cpp | 11 +- src/Waves/Address.h | 2 +- src/Waves/Entry.cpp | 11 +- src/Waves/Entry.h | 9 +- src/Waves/Signer.cpp | 9 +- src/Waves/Signer.h | 2 +- src/Waves/Transaction.cpp | 72 +- src/Waves/Transaction.h | 2 +- src/{Ripple => XRP}/Address.cpp | 6 +- src/{Ripple => XRP}/Address.h | 2 +- src/{Ripple => XRP}/BinaryCoding.h | 0 src/{Ripple => XRP}/Entry.cpp | 13 +- src/{Ripple => XRP}/Entry.h | 9 +- src/{Ripple => XRP}/Signer.cpp | 37 +- src/{Ripple => XRP}/Signer.h | 2 +- src/{Ripple => XRP}/Transaction.cpp | 14 +- src/{Ripple => XRP}/Transaction.h | 2 +- src/{Ripple => XRP}/XAddress.cpp | 17 +- src/{Ripple => XRP}/XAddress.h | 4 +- src/XXHash64.h | 204 - src/Zcash/Entry.cpp | 20 +- src/Zcash/Entry.h | 12 +- src/Zcash/Signer.cpp | 5 +- src/Zcash/TAddress.h | 7 +- src/Zcash/Transaction.cpp | 35 +- src/Zcash/Transaction.h | 4 +- src/Zilliqa/Address.cpp | 6 +- src/Zilliqa/Address.h | 2 +- src/Zilliqa/AddressChecksum.cpp | 13 +- src/Zilliqa/AddressChecksum.h | 2 +- src/Zilliqa/Entry.cpp | 22 +- src/Zilliqa/Entry.h | 14 +- src/Zilliqa/Signer.cpp | 10 +- src/Zilliqa/Signer.h | 2 +- src/algorithm/erase.h | 33 + src/algorithm/sort_copy.h | 27 + src/algorithm/to_array.h | 21 + src/concepts/tw_concepts.h | 19 + src/interface/TWAccount.cpp | 29 +- src/interface/TWAnyAddress.cpp | 221 +- src/interface/TWBase32.cpp | 42 + src/interface/TWBase64.cpp | 37 + src/interface/TWBitcoinAddress.cpp | 14 +- src/interface/TWBitcoinMessageSigner.cpp | 22 + src/interface/TWBitcoinScript.cpp | 32 +- ...TWBitcoin.cpp => TWBitcoinSigHashType.cpp} | 0 src/interface/TWCardano.cpp | 31 + src/interface/TWCoinType.cpp | 14 + src/interface/TWData.cpp | 40 +- src/interface/TWDataVector.cpp | 54 + src/interface/TWDerivationPath.cpp | 65 + src/interface/TWDerivationPathIndex.cpp | 32 + src/interface/TWEthereumAbi.cpp | 18 +- src/interface/TWEthereumAbiFunction.cpp | 195 +- src/interface/TWEthereumAbiValue.cpp | 20 +- src/interface/TWEthereumFee.cpp | 31 - src/interface/TWFIOAccount.cpp | 9 +- src/interface/TWGroestlcoinAddress.cpp | 22 +- src/interface/TWHDWallet.cpp | 22 + src/interface/TWHash.cpp | 13 +- src/interface/TWNEARAccount.cpp | 7 +- src/interface/TWNervosAddress.cpp | 51 + src/interface/TWPBKDF2.cpp | 48 + src/interface/TWPrivateKey.cpp | 16 +- src/interface/TWPublicKey.cpp | 14 +- src/interface/TWRippleXAddress.cpp | 9 +- src/interface/TWSegwitAddress.cpp | 11 +- src/interface/TWSolanaAddress.cpp | 9 +- src/interface/TWStoredKey.cpp | 64 +- src/interface/TWTransactionCompiler.cpp | 69 + src/memory/memzero_wrapper.h | 23 + src/operators/equality_comparable.h | 23 + src/proto/.clang-tidy | 6 + src/proto/Aeternity.proto | 7 +- src/proto/Aion.proto | 12 +- src/proto/Algorand.proto | 25 +- src/proto/Aptos.proto | 153 + src/proto/Binance.proto | 302 +- src/proto/Bitcoin.proto | 64 +- src/proto/Cardano.proto | 202 + src/proto/Common.proto | 71 +- src/proto/Cosmos.proto | 305 +- src/proto/Decred.proto | 18 +- src/proto/EOS.proto | 13 +- src/proto/Elrond.proto | 101 +- src/proto/Ethereum.proto | 55 +- src/proto/Everscale.proto | 60 + src/proto/FIO.proto | 23 +- src/proto/Filecoin.proto | 11 +- src/proto/Harmony.proto | 66 +- src/proto/Icon.proto | 6 +- src/proto/IoTeX.proto | 267 +- src/proto/NEAR.proto | 61 +- src/proto/NEO.proto | 37 +- src/proto/NULS.proto | 73 +- src/proto/Nano.proto | 9 +- src/proto/Nebulas.proto | 47 +- src/proto/Nervos.proto | 208 + src/proto/Nimiq.proto | 8 +- src/proto/Oasis.proto | 13 +- src/proto/Ontology.proto | 11 +- src/proto/Polkadot.proto | 58 +- src/proto/Ripple.proto | 17 +- src/proto/Solana.proto | 71 +- src/proto/Stellar.proto | 60 +- src/proto/THORChainSwap.proto | 97 + src/proto/Tezos.proto | 54 +- src/proto/Theta.proto | 10 +- src/proto/TransactionCompiler.proto | 21 + src/proto/Tron.proto | 57 +- src/proto/VeChain.proto | 7 +- src/proto/Waves.proto | 31 +- src/proto/Zilliqa.proto | 12 +- src/uint256.h | 12 +- .swiftlint.yml => swift/.swiftlint.yml | 0 swift/Gemfile | 6 +- swift/Gemfile.lock | 170 +- swift/Podfile | 1 - swift/Podfile.lock | 8 +- swift/Sources/AnySigner.swift | 34 + swift/Sources/DerivationPath.Index.swift | 49 - swift/Sources/DerivationPath.swift | 117 - swift/Sources/Dummy.cpp | 2 +- .../Sources/Extensions/Account+Codable.swift | 69 + .../Sources/Extensions/AddressProtocol.swift | 1 + .../Extensions/DerivationPath+Extension.swift | 119 + swift/Sources/TWCardano.swift | 15 + swift/Sources/Types/UniversalAssetID.swift | 2 +- swift/Sources/Wallet.swift | 16 + swift/Tests/Addresses/JunoAddressTests.swift | 25 + swift/Tests/Base32Tests.swift | 38 + swift/Tests/Base64Tests.swift | 38 + swift/Tests/Blockchains/AlgorandTests.swift | 33 +- swift/Tests/Blockchains/AptosTests.swift | 48 + swift/Tests/Blockchains/AvalancheTests.swift | 16 + swift/Tests/Blockchains/BandChainTests.swift | 18 +- swift/Tests/Blockchains/BitcoinTests.swift | 9 + swift/Tests/Blockchains/BluzelleTests.swift | 4 +- swift/Tests/Blockchains/CardanoTests.swift | 214 +- swift/Tests/Blockchains/CosmosTests.swift | 264 +- swift/Tests/Blockchains/CronosTests.swift | 17 + swift/Tests/Blockchains/CryptoorgTests.swift | 54 +- swift/Tests/Blockchains/Data | 2 +- swift/Tests/Blockchains/ECashTests.swift | 91 + swift/Tests/Blockchains/ElrondTests.swift | 106 +- .../Tests/Blockchains/EthereumAbiTests.swift | 8 + .../Tests/Blockchains/EthereumFeeTests.swift | 68 - swift/Tests/Blockchains/EthereumTests.swift | 3 +- swift/Tests/Blockchains/EverscaleTests.swift | 54 + swift/Tests/Blockchains/EvmosTests.swift | 76 + swift/Tests/Blockchains/KavaTests.swift | 18 +- .../KuCoinCommunityChainTests.swift | 22 + swift/Tests/Blockchains/NervosTests.swift | 123 + swift/Tests/Blockchains/OasisTests.swift | 2 +- swift/Tests/Blockchains/OsmosisTests.swift | 63 + swift/Tests/Blockchains/PolkadotTests.swift | 26 + swift/Tests/Blockchains/RoninTests.swift | 4 +- .../Blockchains/SmartBitcoinCashTests.swift | 22 + .../Blockchains/THORChainSwapTests.swift | 99 + swift/Tests/Blockchains/THORChainTests.swift | 121 +- .../Tests/Blockchains/TerraClassicTests.swift | 605 ++ swift/Tests/Blockchains/TerraTests.swift | 468 +- swift/Tests/Blockchains/TezosTests.swift | 87 + swift/Tests/Blockchains/ZcoinTests.swift | 8 +- swift/Tests/CoinAddressDerivationTests.swift | 45 +- swift/Tests/CoinTypeTests.swift | 6 + swift/Tests/DerivationPathTests.swift | 31 +- swift/Tests/HDWalletTests.swift | 8 +- swift/Tests/HashTests.swift | 7 - swift/Tests/Keystore/KeyStoreTests.swift | 51 +- swift/Tests/Keystore/KeystoreKeyTests.swift | 21 + swift/Tests/PBKDF2Tests.swift | 42 + swift/Tests/PrivateKeyTests.swift | 4 +- swift/Tests/TransactionCompilerTests.swift | 176 + swift/Tests/UniversalAssetIDTests.swift | 2 +- swift/common-xcframework.yml | 11 +- swift/cpp.xcconfig.in | 2 +- swift/project.yml | 22 +- swift/protobuf | 2 +- swift/protobuf.patch | 30 - tests/CMakeLists.txt | 22 +- tests/Cardano/AddressTests.cpp | 377 - tests/Cardano/TWCardanoAddressTests.cpp | 38 - tests/CoinAddressDerivationTests.cpp | 125 - tests/Cosmos/SignerTests.cpp | 100 - tests/Cosmos/StakingTests.cpp | 162 - tests/CryptoOrg/TWAnySignerTests.cpp | 85 - tests/Elrond/SignerTests.cpp | 93 - tests/Elrond/TestAccounts.h | 17 - tests/Ethereum/FeeTests.cpp | 52 - tests/Keystore/StoredKeyTests.cpp | 386 - tests/NEAR/SerializationTests.cpp | 43 - tests/NEAR/TWAnySignerTests.cpp | 39 - tests/THORChain/SignerTests.cpp | 55 - tests/Tezos/TWAnySignerTests.cpp | 60 - tests/{ => chains}/Aeternity/AddressTests.cpp | 12 +- tests/{ => chains}/Aeternity/SignerTests.cpp | 7 +- .../Aeternity/TWAeternityAddressTests.cpp | 2 +- .../Aeternity/TWAnySignerTests.cpp | 11 +- .../Aeternity/TWCoinTypeTests.cpp | 2 +- .../Aeternity/TransactionTests.cpp | 17 +- tests/{ => chains}/Aion/AddressTests.cpp | 5 +- tests/{ => chains}/Aion/RLPTests.cpp | 9 +- tests/{ => chains}/Aion/SignerTests.cpp | 7 +- tests/{ => chains}/Aion/TWAnySignerTests.cpp | 11 +- tests/{ => chains}/Aion/TWCoinTypeTests.cpp | 2 +- tests/{ => chains}/Aion/TransactionTests.cpp | 9 +- tests/{ => chains}/Algorand/AddressTests.cpp | 9 +- tests/{ => chains}/Algorand/SignerTests.cpp | 34 +- .../Algorand/TWAnySignerTests.cpp | 11 +- .../{ => chains}/Algorand/TWCoinTypeTests.cpp | 2 +- tests/chains/Aptos/AddressTests.cpp | 54 + tests/chains/Aptos/MoveTypesTests.cpp | 60 + tests/chains/Aptos/SignerTests.cpp | 379 + tests/chains/Aptos/TWAnySignerTests.cpp | 63 + tests/chains/Aptos/TWAptosAddressTests.cpp | 34 + tests/chains/Aptos/TWCoinTypeTests.cpp | 35 + .../chains/Aptos/TransactionPayloadTests.cpp | 32 + .../{ => chains}/Arbitrum/TWCoinTypeTests.cpp | 4 +- tests/chains/Aurora/TWCoinTypeTests.cpp | 34 + .../Avalanche/TWCoinTypeTests.cpp | 6 +- .../BandChain/TWCoinTypeTests.cpp | 2 +- tests/{ => chains}/Binance/SignerTests.cpp | 0 .../{ => chains}/Binance/TWAnySignerTests.cpp | 34 +- .../{ => chains}/Binance/TWCoinTypeTests.cpp | 6 +- .../BinanceSmartChain/SignerTests.cpp | 24 +- .../BinanceSmartChain/TWAnyAddressTests.cpp | 2 +- .../BinanceSmartChain/TWCoinTypeTests.cpp | 4 +- .../Bitcoin/BitcoinAddressTests.cpp | 26 +- .../Bitcoin/BitcoinScriptTests.cpp | 54 +- .../Bitcoin/FeeCalculatorTests.cpp | 14 +- .../Bitcoin/InputSelectorTests.cpp | 36 +- tests/chains/Bitcoin/MessageSignerTests.cpp | 167 + .../Bitcoin/SegwitAddressTests.cpp | 71 +- .../Bitcoin/TWBitcoinAddressTests.cpp | 2 +- .../Bitcoin/TWBitcoinScriptTests.cpp | 25 +- .../Bitcoin/TWBitcoinSigningTests.cpp | 295 +- .../Bitcoin/TWBitcoinTransactionTests.cpp | 14 +- .../{ => chains}/Bitcoin/TWCoinTypeTests.cpp | 2 +- .../Bitcoin/TWSegwitAddressTests.cpp | 24 +- .../Bitcoin/TransactionPlanTests.cpp | 95 +- .../Bitcoin/TxComparisonHelper.cpp | 57 +- .../{ => chains}/Bitcoin/TxComparisonHelper.h | 10 +- .../BitcoinCash/TWBitcoinCashTests.cpp | 33 +- .../BitcoinCash/TWCoinTypeTests.cpp | 2 +- .../BitcoinGold/TWAddressTests.cpp | 2 +- .../BitcoinGold/TWBitcoinGoldTests.cpp | 22 +- .../BitcoinGold/TWCoinTypeTests.cpp | 2 +- .../BitcoinGold/TWSegwitAddressTests.cpp | 11 +- .../BitcoinGold/TWSignerTests.cpp | 27 +- .../{ => chains}/Bluzelle/TWCoinTypeTests.cpp | 2 +- tests/chains/Boba/TWCoinTypeTests.cpp | 34 + .../{ => chains}/Callisto/TWCoinTypeTests.cpp | 6 +- tests/chains/Cardano/AddressTests.cpp | 510 + tests/chains/Cardano/SigningTests.cpp | 817 ++ tests/chains/Cardano/StakingTests.cpp | 351 + .../chains/Cardano/TWCardanoAddressTests.cpp | 74 + .../{ => chains}/Cardano/TWCoinTypeTests.cpp | 6 +- tests/chains/Cardano/TransactionTests.cpp | 126 + tests/{ => chains}/Celo/TWCoinTypeTests.cpp | 2 +- tests/{ => chains}/Cosmos/AddressTests.cpp | 0 tests/chains/Cosmos/Protobuf/.gitignore | 3 + tests/chains/Cosmos/Protobuf/Article.proto | 27 + tests/chains/Cosmos/ProtobufTests.cpp | 67 + tests/chains/Cosmos/SignerTests.cpp | 325 + tests/chains/Cosmos/StakingTests.cpp | 246 + .../{ => chains}/Cosmos/TWAnyAddressTests.cpp | 2 +- .../{ => chains}/Cosmos/TWAnySignerTests.cpp | 39 +- tests/{ => chains}/Cosmos/TWCoinTypeTests.cpp | 14 +- tests/chains/Cronos/TWAnyAddressTests.cpp | 21 + tests/chains/Cronos/TWCoinTypeTests.cpp | 28 + tests/{ => chains}/CryptoOrg/AddressTests.cpp | 7 +- tests/{ => chains}/CryptoOrg/SignerTests.cpp | 18 +- .../CryptoOrg/TWAnyAddressTests.cpp | 2 +- tests/chains/CryptoOrg/TWAnySignerTests.cpp | 132 + .../CryptoOrg/TWCoinTypeTests.cpp | 2 +- tests/{ => chains}/Dash/TWCoinTypeTests.cpp | 2 +- tests/{ => chains}/Dash/TWDashTests.cpp | 2 +- tests/{ => chains}/Decred/AddressTests.cpp | 7 +- tests/{ => chains}/Decred/SignerTests.cpp | 15 +- .../{ => chains}/Decred/TWAnySignerTests.cpp | 4 +- tests/{ => chains}/Decred/TWCoinTypeTests.cpp | 4 +- tests/{ => chains}/Decred/TWDecredTests.cpp | 2 +- .../{ => chains}/DigiByte/TWCoinTypeTests.cpp | 2 +- .../{ => chains}/DigiByte/TWDigiByteTests.cpp | 36 +- .../{ => chains}/Dogecoin/TWCoinTypeTests.cpp | 2 +- tests/{ => chains}/Dogecoin/TWDogeTests.cpp | 2 +- .../{HECO => chains/ECO}/TWCoinTypeTests.cpp | 2 +- tests/chains/ECash/TWCoinTypeTests.cpp | 34 + tests/chains/ECash/TWECashTests.cpp | 168 + tests/{ => chains}/EOS/AddressTests.cpp | 41 +- tests/{ => chains}/EOS/AssetTests.cpp | 7 +- tests/{ => chains}/EOS/NameTests.cpp | 14 +- tests/{ => chains}/EOS/SignatureTests.cpp | 21 +- tests/{ => chains}/EOS/TWAnySignerTests.cpp | 9 +- tests/{ => chains}/EOS/TWCoinTypeTests.cpp | 2 +- tests/{ => chains}/EOS/TransactionTests.cpp | 13 +- tests/{ => chains}/Elrond/AddressTests.cpp | 24 +- .../Elrond/SerializationTests.cpp | 69 +- tests/chains/Elrond/SignerTests.cpp | 234 + .../{ => chains}/Elrond/TWAnySignerTests.cpp | 40 +- tests/{ => chains}/Elrond/TWCoinTypeTests.cpp | 2 +- tests/chains/Elrond/TestAccounts.h | 16 + .../chains/Elrond/TransactionFactoryTests.cpp | 192 + .../{ => chains}/Ethereum/AbiStructTests.cpp | 164 +- tests/{ => chains}/Ethereum/AbiTests.cpp | 432 +- tests/{ => chains}/Ethereum/AddressTests.cpp | 20 +- .../Ethereum/ContractCallTests.cpp | 42 +- tests/{ => chains}/Ethereum/Data/1inch.json | 0 tests/{ => chains}/Ethereum/Data/custom.json | 0 .../Ethereum/Data/eip712_cryptofights.json | 0 .../Ethereum/Data/eip712_emptyArray.json | 108 + .../Ethereum/Data/eip712_emptyString.json | 132 + .../Ethereum/Data/eip712_rarible.json | 0 .../Ethereum/Data/eip712_snapshot_v4.json | 0 .../Ethereum/Data/eip712_walletconnect.json | 0 tests/{ => chains}/Ethereum/Data/ens.json | 0 tests/{ => chains}/Ethereum/Data/erc20.json | 0 tests/{ => chains}/Ethereum/Data/erc721.json | 0 .../Ethereum/Data/eth_feeHistory.json | 0 .../Ethereum/Data/eth_feeHistory2.json | 0 .../Ethereum/Data/eth_feeHistory3.json | 0 .../Ethereum/Data/eth_feeHistory4.json | 0 .../Ethereum/Data/getAmountsOut.json | 0 .../Ethereum/Data/kyber_proxy.json | 0 tests/chains/Ethereum/Data/seaport_712.json | 84 + .../Ethereum/Data/tuple_nested.json | 0 .../Ethereum/Data/uniswap_router_v2.json | 0 .../Ethereum/Data/zilliqa_data_tx.json | 0 tests/{ => chains}/Ethereum/RLPTests.cpp | 103 +- tests/{ => chains}/Ethereum/SignerTests.cpp | 0 .../Ethereum/TWAnySignerTests.cpp | 48 +- .../{ => chains}/Ethereum/TWCoinTypeTests.cpp | 4 +- .../Ethereum/TWEthereumAbiTests.cpp | 18 +- .../TWEthereumAbiValueDecoderTests.cpp | 2 +- .../TWEthereumAbiValueEncodeTests.cpp | 2 +- .../Ethereum/ValueDecoderTests.cpp | 34 +- .../Ethereum/ValueEncoderTests.cpp | 34 +- .../EthereumClassic/TWCoinTypeTests.cpp | 4 +- tests/chains/Everscale/AddressTests.cpp | 52 + tests/chains/Everscale/CellBuilderTest.cpp | 121 + tests/chains/Everscale/CellTests.cpp | 106 + tests/chains/Everscale/SignerTests.cpp | 88 + tests/chains/Everscale/TWAnyAddressTests.cpp | 30 + tests/chains/Everscale/TWAnySignerTests.cpp | 38 + tests/chains/Everscale/TWCoinTypeTests.cpp | 38 + tests/chains/Evmos/SignerTests.cpp | 126 + tests/chains/Evmos/TWAnyAddressTests.cpp | 39 + tests/chains/Evmos/TWCoinTypeTests.cpp | 32 + tests/{ => chains}/FIO/AddressTests.cpp | 9 +- tests/{ => chains}/FIO/EncryptionTests.cpp | 24 +- tests/{ => chains}/FIO/SignerTests.cpp | 18 +- tests/{ => chains}/FIO/TWCoinTypeTests.cpp | 2 +- tests/{ => chains}/FIO/TWFIOAccountTests.cpp | 2 +- tests/{ => chains}/FIO/TWFIOTests.cpp | 30 +- .../FIO/TransactionBuilderTests.cpp | 83 +- tests/{ => chains}/Fantom/TWCoinTypeTests.cpp | 2 +- tests/{ => chains}/Filecoin/AddressTests.cpp | 4 +- tests/{ => chains}/Filecoin/SignerTests.cpp | 0 .../Filecoin/TWAnySignerTests.cpp | 7 +- .../{ => chains}/Filecoin/TWCoinTypeTests.cpp | 2 +- .../Filecoin/TransactionTests.cpp | 0 .../Firo}/TWCoinTypeTests.cpp | 24 +- .../Firo}/TWZCoinAddressTests.cpp | 20 +- .../{ => chains}/GoChain/TWCoinTypeTests.cpp | 2 +- .../{ => chains}/Groestlcoin/AddressTests.cpp | 7 +- .../Groestlcoin/TWCoinTypeTests.cpp | 4 +- .../Groestlcoin/TWGroestlcoinSigningTests.cpp | 10 +- .../Groestlcoin/TWGroestlcoinTests.cpp | 2 +- tests/{ => chains}/Harmony/AddressTests.cpp | 5 +- tests/{ => chains}/Harmony/SignerTests.cpp | 0 tests/{ => chains}/Harmony/StakingTests.cpp | 0 .../Harmony/TWAnyAddressTests.cpp | 2 +- .../{ => chains}/Harmony/TWAnySignerTests.cpp | 7 +- .../{ => chains}/Harmony/TWCoinTypeTests.cpp | 2 +- .../Harmony/TWHarmonyStakingTests.cpp | 17 +- tests/{Icon => chains/ICON}/AddressTests.cpp | 7 +- .../ICON}/TWAnySignerTests.cpp | 11 +- tests/{ => chains}/ICON/TWCoinTypeTests.cpp | 2 +- tests/{ => chains}/IoTeX/AddressTests.cpp | 8 +- tests/{ => chains}/IoTeX/SignerTests.cpp | 0 tests/{ => chains}/IoTeX/StakingTests.cpp | 14 +- tests/{ => chains}/IoTeX/TWAnySignerTests.cpp | 10 +- tests/{ => chains}/IoTeX/TWCoinTypeTests.cpp | 2 +- tests/chains/Juno/TWAnyAddressTests.cpp | 50 + tests/{ => chains}/Kava/TWCoinTypeTests.cpp | 10 +- tests/chains/KavaEvm/TWCoinTypeTests.cpp | 37 + tests/{ => chains}/Kin/TWCoinTypeTests.cpp | 2 +- tests/chains/Klaytn/TWCoinTypeTests.cpp | 37 + .../KuCoinCommunityChain/TWCoinTypeTests.cpp | 34 + tests/{ => chains}/Kusama/AddressTests.cpp | 7 +- tests/{ => chains}/Kusama/SignerTests.cpp | 6 +- .../{ => chains}/Kusama/TWAnySignerTests.cpp | 7 +- tests/{ => chains}/Kusama/TWCoinTypeTests.cpp | 4 +- .../chains/Litecoin/LitecoinAddressTests.cpp | 41 + .../{ => chains}/Litecoin/TWCoinTypeTests.cpp | 2 +- .../{ => chains}/Litecoin/TWLitecoinTests.cpp | 11 +- tests/chains/Meter/TWCoinTypeTests.cpp | 34 + tests/chains/Metis/TWCoinTypeTests.cpp | 34 + .../{ => chains}/Monacoin/TWCoinTypeTests.cpp | 2 +- .../Monacoin/TWMonacoinAddressTests.cpp | 2 +- .../Monacoin/TWMonacoinTransactionTests.cpp | 13 +- tests/chains/Moonbeam/TWCoinTypeTests.cpp | 37 + tests/chains/Moonriver/TWCoinTypeTests.cpp | 37 + tests/{ => chains}/NEAR/AccountTests.cpp | 8 +- tests/{ => chains}/NEAR/AddressTests.cpp | 13 +- tests/chains/NEAR/SerializationTests.cpp | 278 + tests/{ => chains}/NEAR/SignerTests.cpp | 2 + tests/chains/NEAR/TWAnySignerTests.cpp | 71 + tests/{ => chains}/NEAR/TWCoinTypeTests.cpp | 2 +- .../{ => chains}/NEAR/TWNEARAccountTests.cpp | 2 +- tests/{ => chains}/NEO/AddressTests.cpp | 7 +- tests/{ => chains}/NEO/CoinReferenceTests.cpp | 17 +- tests/{ => chains}/NEO/SignerTests.cpp | 16 +- tests/{ => chains}/NEO/TWAnySignerTests.cpp | 13 +- tests/{ => chains}/NEO/TWCoinTypeTests.cpp | 2 +- tests/{ => chains}/NEO/TWNEOAddressTests.cpp | 2 +- .../NEO/TransactionAttributeTests.cpp | 46 +- .../NEO/TransactionOutputTests.cpp | 10 +- tests/{ => chains}/NEO/TransactionTests.cpp | 31 +- tests/{ => chains}/NEO/WitnessTests.cpp | 8 +- tests/{ => chains}/NULS/AddressTests.cpp | 12 +- tests/{ => chains}/NULS/TWAnySignerTests.cpp | 9 +- tests/{ => chains}/NULS/TWCoinTypeTests.cpp | 2 +- tests/{ => chains}/Nano/AddressTests.cpp | 7 +- tests/{ => chains}/Nano/SignerTests.cpp | 7 +- tests/{ => chains}/Nano/TWAnySignerTests.cpp | 7 +- tests/{ => chains}/Nano/TWCoinTypeTests.cpp | 4 +- .../{ => chains}/Nano/TWNanoAddressTests.cpp | 2 +- .../chains/NativeEvmos/TWAnyAddressTests.cpp | 39 + tests/chains/NativeEvmos/TWCoinTypeTests.cpp | 32 + tests/{ => chains}/Nebulas/AddressTests.cpp | 19 +- tests/{ => chains}/Nebulas/SignerTests.cpp | 0 .../{ => chains}/Nebulas/TWAnySignerTests.cpp | 21 +- .../{ => chains}/Nebulas/TWCoinTypeTests.cpp | 2 +- .../Nebulas/TWNebulasAddressTests.cpp | 2 +- .../{ => chains}/Nebulas/TransactionTests.cpp | 20 +- tests/chains/Nervos/AddressTests.cpp | 88 + tests/chains/Nervos/SignerTests.cpp | 929 ++ tests/chains/Nervos/TWAnyAddressTests.cpp | 70 + tests/chains/Nervos/TWAnySignerTests.cpp | 665 ++ tests/chains/Nervos/TWCoinTypeTests.cpp | 35 + tests/chains/Nervos/TWNervosAddressTests.cpp | 30 + tests/{ => chains}/Nimiq/AddressTests.cpp | 14 +- tests/{ => chains}/Nimiq/SignerTests.cpp | 0 tests/{ => chains}/Nimiq/TWAnySignerTests.cpp | 7 +- tests/{ => chains}/Nimiq/TWCoinTypeTests.cpp | 2 +- tests/{ => chains}/Nimiq/TransactionTests.cpp | 0 tests/chains/OKXChain/TWCoinTypeTests.cpp | 34 + tests/{ => chains}/Oasis/AddressTests.cpp | 21 +- tests/{ => chains}/Oasis/SignerTests.cpp | 11 +- tests/{ => chains}/Oasis/TWAnySignerTests.cpp | 9 +- tests/{ => chains}/Oasis/TWCoinTypeTests.cpp | 4 +- tests/{ => chains}/Ontology/AccountTests.cpp | 8 +- tests/{ => chains}/Ontology/AddressTests.cpp | 9 +- tests/chains/Ontology/Oep4Tests.cpp | 140 + tests/{ => chains}/Ontology/OngTests.cpp | 9 +- tests/{ => chains}/Ontology/OntTests.cpp | 9 +- .../Ontology/ParamsBuilderTests.cpp | 67 +- .../Ontology/TWAnySignerTests.cpp | 56 +- .../{ => chains}/Ontology/TWCoinTypeTests.cpp | 4 +- .../Ontology/TransactionTests.cpp | 23 +- .../{ => chains}/Optimism/TWCoinTypeTests.cpp | 4 +- tests/chains/Osmosis/AddressTests.cpp | 46 + tests/chains/Osmosis/SignerTests.cpp | 59 + tests/chains/Osmosis/TWAnyAddressTests.cpp | 28 + tests/chains/Osmosis/TWAnySignerTests.cpp | 57 + tests/chains/Osmosis/TWCoinTypeTests.cpp | 34 + .../POANetwork/TWCoinTypeTests.cpp | 2 +- tests/{ => chains}/Polkadot/AddressTests.cpp | 5 +- tests/chains/Polkadot/SS58AddressTests.cpp | 146 + .../{ => chains}/Polkadot/ScaleCodecTests.cpp | 5 +- tests/{ => chains}/Polkadot/SignerTests.cpp | 36 +- .../{ => chains}/Polkadot/TWCoinTypeTests.cpp | 3 +- .../{ => chains}/Polygon/TWCoinTypeTests.cpp | 2 +- tests/{ => chains}/Qtum/TWCoinTypeTests.cpp | 2 +- .../{ => chains}/Qtum/TWQtumAddressTests.cpp | 2 +- .../Ravencoin/TWCoinTypeTests.cpp | 2 +- .../Ravencoin/TWRavencoinTransactionTests.cpp | 19 +- tests/chains/Ronin/TWAnyAddressTests.cpp | 58 + tests/chains/Ronin/TWAnySignerTests.cpp | 48 + tests/{ => chains}/Ronin/TWCoinTypeTests.cpp | 4 +- .../SmartBitcoinCash/TWCoinTypeTests.cpp | 34 + tests/{ => chains}/Solana/AddressTests.cpp | 19 +- tests/{ => chains}/Solana/ProgramTests.cpp | 38 +- tests/{ => chains}/Solana/SignerTests.cpp | 22 +- .../{ => chains}/Solana/TWAnySignerTests.cpp | 65 +- tests/{ => chains}/Solana/TWCoinTypeTests.cpp | 2 +- .../Solana/TWSolanaAddressTests.cpp | 15 +- .../{ => chains}/Solana/TransactionTests.cpp | 41 +- tests/{ => chains}/Stellar/AddressTests.cpp | 7 +- .../{ => chains}/Stellar/TWAnySignerTests.cpp | 64 +- .../{ => chains}/Stellar/TWCoinTypeTests.cpp | 2 +- .../Stellar/TWStellarAddressTests.cpp | 2 +- .../{ => chains}/Stellar/TransactionTests.cpp | 12 +- tests/chains/THORChain/SignerTests.cpp | 204 + tests/chains/THORChain/SwapTests.cpp | 423 + .../THORChain/TWAnyAddressTests.cpp | 2 +- .../THORChain/TWAnySignerTests.cpp | 6 +- .../THORChain/TWCoinTypeTests.cpp | 4 +- tests/chains/THORChain/TWSwapTests.cpp | 230 + tests/chains/Terra/SignerTests.cpp | 454 + tests/{ => chains}/Terra/TWCoinTypeTests.cpp | 23 +- tests/chains/TerraV2/SignerTests.cpp | 362 + tests/chains/TerraV2/TWCoinTypeTests.cpp | 34 + tests/{ => chains}/Tezos/AddressTests.cpp | 28 +- tests/{ => chains}/Tezos/ForgingTests.cpp | 129 +- .../{ => chains}/Tezos/OperationListTests.cpp | 71 +- tests/{ => chains}/Tezos/PublicKeyTests.cpp | 5 +- tests/{ => chains}/Tezos/SignerTests.cpp | 13 +- tests/chains/Tezos/TWAnySignerTests.cpp | 127 + tests/{ => chains}/Tezos/TWCoinTypeTests.cpp | 2 +- tests/{ => chains}/Theta/SignerTests.cpp | 0 tests/{ => chains}/Theta/TWAnySignerTests.cpp | 11 +- tests/{ => chains}/Theta/TWCoinTypeTests.cpp | 2 +- tests/{ => chains}/Theta/TransactionTests.cpp | 7 +- .../ThunderToken/TWCoinTypeTests.cpp | 2 +- .../TomoChain/TWCoinTypeTests.cpp | 2 +- tests/{ => chains}/Tron/AddressTests.cpp | 0 .../{ => chains}/Tron/SerializationTests.cpp | 0 tests/{ => chains}/Tron/SignerTests.cpp | 0 tests/{ => chains}/Tron/TWAnySignerTests.cpp | 2 +- tests/{ => chains}/Tron/TWCoinTypeTests.cpp | 2 +- tests/{ => chains}/VeChain/SignerTests.cpp | 0 .../{ => chains}/VeChain/TWAnySignerTests.cpp | 9 +- .../{ => chains}/VeChain/TWCoinTypeTests.cpp | 2 +- .../{ => chains}/Viacoin/TWCoinTypeTests.cpp | 2 +- .../Viacoin/TWViacoinAddressTests.cpp | 2 +- .../{ => chains}/Wanchain/TWCoinTypeTests.cpp | 2 +- tests/{ => chains}/Waves/AddressTests.cpp | 7 +- tests/{ => chains}/Waves/LeaseTests.cpp | 109 +- tests/{ => chains}/Waves/SignerTests.cpp | 5 +- tests/{ => chains}/Waves/TWAnySignerTests.cpp | 11 +- tests/{ => chains}/Waves/TWCoinTypeTests.cpp | 2 +- tests/{ => chains}/Waves/TransactionTests.cpp | 43 +- tests/{Ripple => chains/XRP}/AddressTests.cpp | 33 +- .../XRP}/TWAnySignerTests.cpp | 16 +- .../XRP}/TWCoinTypeTests.cpp | 2 +- .../XRP}/TWRippleAddressTests.cpp | 17 +- .../XRP}/TransactionTests.cpp | 14 +- tests/{ => chains}/Zcash/AddressTests.cpp | 0 tests/{ => chains}/Zcash/TWCoinTypeTests.cpp | 4 +- .../Zcash/TWZcashAddressTests.cpp | 2 +- .../Zcash/TWZcashTransactionTests.cpp | 2 +- .../{ => chains}/Zelcash/TWCoinTypeTests.cpp | 4 +- .../Zelcash/TWZelcashAddressTests.cpp | 2 +- .../Zelcash/TWZelcashTransactionTests.cpp | 2 +- tests/{ => chains}/Zilliqa/AddressTests.cpp | 22 +- tests/{ => chains}/Zilliqa/SignatureTests.cpp | 6 +- tests/{ => chains}/Zilliqa/SignerTests.cpp | 15 +- .../{ => chains}/Zilliqa/TWAnySignerTests.cpp | 9 +- .../{ => chains}/Zilliqa/TWCoinTypeTests.cpp | 2 +- .../Zilliqa/TWZilliqaAddressTests.cpp | 2 +- tests/chains/ZkSyncV2/TWCoinTypeTests.cpp | 36 + tests/{ => chains}/xDai/TWCoinTypeTests.cpp | 4 +- tests/common/AnyAddressTests.cpp | 34 + tests/common/BCSTests.cpp | 165 + tests/{ => common}/Base64Tests.cpp | 9 +- tests/{ => common}/BaseEncoding.cpp | 21 +- tests/{ => common}/Bech32AddressTests.cpp | 22 +- tests/{ => common}/Bech32Tests.cpp | 2 +- tests/{ => common}/BinaryCodingTests.cpp | 4 - tests/{ => common}/CborTests.cpp | 236 +- tests/common/CoinAddressDerivationTests.cpp | 292 + .../CoinAddressValidationTests.cpp | 23 +- tests/common/DataTests.cpp | 96 + tests/{ => common}/EncryptTests.cpp | 51 +- .../common/HDWallet/HDWalletInternalTests.cpp | 156 + tests/{ => common}/HDWallet/HDWalletTests.cpp | 216 +- .../{ => common}/HDWallet/bip39_vectors.json | 0 tests/{ => common}/HashTests.cpp | 30 +- tests/{ => common}/HexCodingTests.cpp | 0 .../Keystore/Data/empty-accounts.json | 0 .../Data/ethereum-wallet-address-no-0x.json | 0 tests/{ => common}/Keystore/Data/key.json | 0 .../Keystore/Data/key_bitcoin.json | 0 .../Keystore/Data/legacy-mnemonic.json | 0 .../Keystore/Data/legacy-private-key.json | 0 .../{ => common}/Keystore/Data/livepeer.json | 0 .../Keystore/Data/missing-address.json | 0 .../Keystore/Data/myetherwallet.uu | 0 tests/{ => common}/Keystore/Data/pbkdf2.json | 0 tests/{ => common}/Keystore/Data/wallet.json | 0 tests/{ => common}/Keystore/Data/watch.json | 0 tests/{ => common}/Keystore/Data/web3j.json | 0 .../Keystore}/DerivationPathTests.cpp | 64 +- tests/common/Keystore/StoredKeyTests.cpp | 653 ++ tests/{ => common}/MnemonicTests.cpp | 0 tests/common/NumericLiteralTests.cpp | 14 + tests/{ => common}/PrivateKeyTests.cpp | 163 +- tests/{ => common}/PublicKeyTests.cpp | 172 +- .../TestUtilities.cpp} | 5 +- .../TestUtilities.h} | 7 +- tests/common/TransactionCompilerTests.cpp | 437 + tests/common/Uint256Tests.cpp | 114 + tests/{ => common}/WalletConsoleTests.cpp | 39 +- tests/common/algorithm/erase_tests.cpp | 27 + tests/common/algorithm/sort_copy_tests.cpp | 25 + tests/common/algorithm/to_array_tests.cpp | 23 + tests/common/memory/memzero_tests.cpp | 23 + .../operators/equality_comparable_tests.cpp | 23 + tests/interface/TWAESTests.cpp | 2 +- tests/interface/TWAccountTests.cpp | 37 + tests/interface/TWAnyAddressTests.cpp | 36 +- tests/interface/TWBase32Tests.cpp | 67 + tests/interface/TWBase58Tests.cpp | 2 +- tests/interface/TWBase64Tests.cpp | 56 + tests/interface/TWCoinTypeTests.cpp | 162 + tests/interface/TWDataTests.cpp | 10 +- tests/interface/TWDataVectorTests.cpp | 83 + tests/interface/TWDerivationPathTests.cpp | 64 + tests/interface/TWHDWalletTests.cpp | 144 +- tests/interface/TWHRPTests.cpp | 9 +- tests/interface/TWHashTests.cpp | 34 +- tests/interface/TWMnemonicTests.cpp | 2 +- tests/interface/TWPBKDF2Tests.cpp | 22 + tests/interface/TWPrivateKeyTests.cpp | 4 +- tests/interface/TWPublicKeyTests.cpp | 27 +- tests/interface/TWStoredKeyTests.cpp | 101 +- tests/interface/TWStringTests.cpp | 2 +- .../interface/TWTransactionCompilerTests.cpp | 397 + tests/main.cpp | 38 +- tools/android-test | 2 +- tools/build-and-test | 27 + tools/coverage | 2 +- tools/dependencies-version | 7 + tools/download-dependencies | 61 + tools/doxygen_convert_comments | 28 + tools/generate-files | 11 +- tools/gtest.patch | 11 - tools/gtest_mock.patch | 11 - tools/gtest_test.patch | 11 - tools/install-android-dependencies | 8 + tools/install-dependencies | 145 +- tools/install-wasm-dependencies | 15 + tools/ios-build | 2 +- tools/ios-doc | 22 + tools/ios-release | 3 +- tools/ios-test | 12 +- tools/ios-xcframework | 4 +- tools/ios-xcframework-release | 29 +- tools/library | 8 +- tools/lint-all | 3 +- tools/pvs-studio-analyze | 25 + tools/pvs-studio/config.cfg | 11 + tools/wasm-build | 22 + tools/wasm-set-version | 13 + tools/windows-build-and-test.ps1 | 25 + tools/windows-build.ps1 | 21 +- tools/windows-dependencies-version.ps1 | 14 + tools/windows-dependencies.ps1 | 299 +- tools/windows-download-dependencies.ps1 | 162 + tools/windows-doxygen_convert_comments.ps1 | 30 + tools/windows-generate.ps1 | 26 +- tools/windows-replace-file.pl | 241 + tools/windows-replace/.vs/CMake Overview | 0 .../windows-replace/.vs/ProjectSettings.json | 3 + tools/windows-replace/.vs/slnx.sqlite | Bin 0 -> 90112 bytes .../.vs/windows-replace/v16/Browse.VC.db | Bin 0 -> 655360 bytes .../.vs/windows-replace/v16/Browse.VC.db-shm | Bin 0 -> 32768 bytes .../.vs/windows-replace/v16/Browse.VC.db-wal | 0 .../.vs/windows-replace/v16/Browse.VC.opendb | Bin 0 -> 16 bytes tools/windows-replace/CMakeLists.txt | 160 + .../cmake/CompilerWarnings.cmake | 95 + tools/windows-replace/cmake/Protobuf.cmake | 220 + .../include/TrustWalletCore/TWAnySigner.h | 50 + .../include/TrustWalletCore/TWBase.h | 99 + .../include/TrustWalletCore/TWString.h | 73 + .../powerShell/windows-bootstrap.ps1 | 20 + .../powerShell/windows-build-and-test.ps1 | 25 + .../powerShell/windows-build.ps1 | 54 + .../windows-dependencies-version.ps1 | 14 + .../powerShell/windows-dependencies.ps1 | 158 + .../windows-download-dependencies.ps1 | 162 + .../windows-doxygen_convert_comments.ps1 | 31 + .../powerShell/windows-generate.ps1 | 91 + .../powerShell/windows-replace-file.pl | 241 + .../powerShell/windows-samples.ps1 | 28 + .../powerShell/windows-tests.ps1 | 14 + .../protobuf-plugin/CMakeLists.txt | 35 + tools/windows-replace/readme.md | 33 + tools/windows-replace/src/Aptos/MoveTypes.cpp | 152 + tools/windows-replace/src/Base32.h | 68 + tools/windows-replace/src/BinaryCoding.h | 120 + .../src/Ethereum/ABI/ParamFactory.cpp | 206 + tools/windows-replace/src/Everscale/Cell.cpp | 405 + tools/windows-replace/src/Everscale/Cell.h | 70 + .../windows-replace/src/Everscale/CellSlice.h | 36 + tools/windows-replace/src/HDWallet.cpp | 390 + .../src/Keystore/EncryptionParameters.cpp | 160 + tools/windows-replace/src/NULS/Address.cpp | 85 + .../src/interface/TWBitcoinScript.cpp | 165 + tools/windows-replace/tests/CMakeLists.txt | 69 + .../tests/chains/Aptos/MoveTypesTests.cpp | 60 + .../chains/Bitcoin/MessageSignerTests.cpp | 167 + .../tests/chains/Bitcoin/TestUtilities.h | 88 + .../tests/chains/Cardano/SigningTests.cpp | 817 ++ .../chains/Cardano/TWCardanoAddressTests.cpp | 74 + .../tests/chains/Polkadot/SignerTests.cpp | 340 + .../tests/chains/Zilliqa/SignerTests.cpp | 113 + .../windows-replace/tests/common/BCSTests.cpp | 165 + tools/windows-replace/tests/main.cpp | 61 + .../trezor-crypto/CMakeLists.txt | 96 + .../trezor-crypto/crypto/base32.c | 245 + .../trezor-crypto/crypto/base58.c | 294 + .../trezor-crypto/crypto/bignum.c | 1841 ++++ .../trezor-crypto/crypto/blake2b.c | 345 + .../trezor-crypto/crypto/blake2s.c | 329 + .../trezor-crypto/crypto/cardano.c | 328 + .../trezor-crypto/crypto/monero/base58.c | 255 + .../trezor-crypto/crypto/rand.c | 151 + .../trezor-crypto/crypto/sha3.c | 398 + .../trezor-crypto/crypto/shamir.c | 347 + .../trezor-crypto/crypto/tests/CMakeLists.txt | 25 + .../trezor-crypto/crypto/tests/test_check.c | 9472 +++++++++++++++++ .../trezor-crypto/include/TrezorCrypto/aes.h | 228 + .../ed25519-donna/ed25519-donna-portable.h | 38 + .../include/TrezorCrypto/endian.h | 132 + .../include/TrezorCrypto/groestl_internal.h | 510 + .../include/TrezorCrypto/nist256p1.h | 45 + .../trezor-crypto/include/TrezorCrypto/rand.h | 50 + .../walletconsole/CMakeLists.txt | 16 + .../walletconsole/lib/CMakeLists.txt | 12 + tools/windows-replace/windows-dragon-king.pl | 96 + tools/windows-samples.ps1 | 6 +- tools/windows-tests.ps1 | 2 +- trezor-crypto/CMakeLists.txt | 78 +- trezor-crypto/crypto/base32.c | 12 +- trezor-crypto/crypto/base58.c | 20 +- trezor-crypto/crypto/bignum.c | 8 +- trezor-crypto/crypto/bip32.c | 244 +- trezor-crypto/crypto/bip39.c | 9 +- trezor-crypto/crypto/blake256.c | 3 +- trezor-crypto/crypto/blake2b.c | 3 +- trezor-crypto/crypto/blake2s.c | 3 +- trezor-crypto/crypto/cardano.c | 328 + trezor-crypto/crypto/chacha20poly1305/LICENSE | 21 + .../crypto/chacha20poly1305/chacha_merged.c | 7 + trezor-crypto/crypto/chacha_drbg.c | 90 +- trezor-crypto/crypto/curves.c | 7 +- trezor-crypto/crypto/ecdsa.c | 149 +- trezor-crypto/crypto/ed25519-donna/ed25519.c | 124 +- trezor-crypto/crypto/monero/base58.c | 15 +- trezor-crypto/crypto/nem.c | 3 +- trezor-crypto/crypto/rand.c | 7 +- trezor-crypto/crypto/rfc6979.c | 22 +- trezor-crypto/crypto/sha2.c | 6 +- trezor-crypto/crypto/sha3.c | 5 +- trezor-crypto/crypto/shamir.c | 4 +- trezor-crypto/crypto/slip39.c | 151 + trezor-crypto/crypto/tests/CMakeLists.txt | 10 +- trezor-crypto/crypto/tests/test_check.c | 1554 +-- .../crypto/tests/test_check_cardano.h | 216 +- .../crypto/tests/test_check_zilliqa.h | 214 + trezor-crypto/crypto/tests/test_openssl.c | 11 +- trezor-crypto/crypto/tests/test_speed.c | 14 +- trezor-crypto/crypto/{schnorr.c => zilliqa.c} | 62 +- trezor-crypto/include/TrezorCrypto/aes.h | 2 +- trezor-crypto/include/TrezorCrypto/bip32.h | 17 +- trezor-crypto/include/TrezorCrypto/bip39.h | 33 +- trezor-crypto/include/TrezorCrypto/cardano.h | 63 + .../chacha20poly1305/ecrypt-sync.h | 11 +- .../include/TrezorCrypto/chacha_drbg.h | 31 +- trezor-crypto/include/TrezorCrypto/curves.h | 6 +- trezor-crypto/include/TrezorCrypto/ecdsa.h | 20 +- .../ed25519-donna/ed25519-blake2b.h | 2 +- .../ed25519-donna/ed25519-donna-portable.h | 2 + .../ed25519-donna/ed25519-keccak.h | 2 +- .../TrezorCrypto/ed25519-donna/ed25519-sha3.h | 2 +- trezor-crypto/include/TrezorCrypto/ed25519.h | 10 +- .../include/TrezorCrypto/groestl_internal.h | 5 +- .../include/TrezorCrypto/nist256p1.h | 4 +- trezor-crypto/include/TrezorCrypto/rand.h | 2 + trezor-crypto/include/TrezorCrypto/rfc6979.h | 3 +- trezor-crypto/include/TrezorCrypto/slip39.h | 47 + .../include/TrezorCrypto/slip39_wordlist.h | 1246 +++ .../TrezorCrypto/{schnorr.h => zilliqa.h} | 13 +- walletconsole/CMakeLists.txt | 21 +- walletconsole/lib/Address.cpp | 2 +- walletconsole/lib/Buffer.cpp | 4 +- walletconsole/lib/CMakeLists.txt | 13 +- walletconsole/lib/CommandExecutor.cpp | 2 +- walletconsole/lib/CommandExecutor.h | 2 +- walletconsole/lib/Keys.cpp | 8 +- walletconsole/lib/Util.cpp | 2 +- wasm/.gitignore | 5 + wasm/.mocharc.json | 8 + wasm/CMakeLists.txt | 44 + wasm/README.md | 7 + wasm/index.ts | 15 + wasm/package-lock.json | 2351 ++++ wasm/package.json | 46 + wasm/src/AnySigner.cpp | 44 + wasm/src/AnySigner.d.ts | 5 + wasm/src/HexCoding.cpp | 35 + wasm/src/HexCoding.d.ts | 4 + wasm/src/Random.cpp | 25 + wasm/src/WasmData.cpp | 30 + wasm/src/WasmData.h | 22 + wasm/src/WasmString.cpp | 22 + wasm/src/WasmString.h | 18 + wasm/src/keystore/default-impl.ts | 159 + wasm/src/keystore/extension-storage.ts | 72 + wasm/src/keystore/fs-storage.ts | 45 + .../src/keystore/index.ts | 9 +- wasm/src/keystore/types.ts | 93 + wasm/tests/AES.test.ts | 46 + wasm/tests/AnyAddress.test.ts | 30 + wasm/tests/Base32.test.ts | 55 + wasm/tests/Base64.test.ts | 42 + wasm/tests/Blockchain/Bitcoin.test.ts | 16 + wasm/tests/Blockchain/Ethereum.test.ts | 168 + wasm/tests/CoinType.test.ts | 22 + wasm/tests/HDWallet.test.ts | 34 + wasm/tests/HRP.test.ts | 18 + wasm/tests/Hash.test.ts | 42 + wasm/tests/HexCoding.test.ts | 23 + wasm/tests/KeyStore+extension.test.ts | 68 + wasm/tests/KeyStore+fs.test.ts | 70 + wasm/tests/Mnemonic.test.ts | 43 + wasm/tests/PBKDF2.test.ts | 24 + wasm/tests/StoredKey.test.ts | 40 + wasm/tests/initWasm.test.ts | 19 + wasm/tests/mock.ts | 53 + wasm/tests/setup.test.ts | 9 + wasm/tsconfig.json | 23 + windows-bootstrap.ps1 | 20 + 1583 files changed, 85771 insertions(+), 13353 deletions(-) create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/aptos/TestAptosAddress.kt create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/aptos/TestAptosSigner.kt create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoSigning.kt create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/everscale/TestEverscaleAddress.kt create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/everscale/TestEverscaleSigner.kt create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/juno/TestJunoAddress.kt create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kcc/TestKuCoinCommunityChainAddress.kt create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nervos/TestNervosAddress.kt create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nervos/TestNervosSigner.kt create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisAddress.kt create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisSigner.kt create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/smartbitcoincash/TestSmartBitcoinCashAddress.kt create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraClassicTxs.kt create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORSwapSigning.kt create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestBase32.kt create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestBase64.kt create mode 100644 cmake/CompilerWarnings.cmake create mode 100644 cmake/FindHostPackage.cmake create mode 100644 cmake/PVS-Studio.cmake create mode 100644 cmake/StandardSettings.cmake create mode 100644 cmake/StaticAnalyzers.cmake create mode 100644 codegen/lib/templates/TWDerivation.h.erb create mode 100644 codegen/lib/templates/TWEthereumChainID.h.erb rename codegen/lib/templates/{jni/header.erb => copyright_header.erb} (87%) create mode 100644 codegen/lib/templates/cpp/class.erb create mode 100644 codegen/lib/templates/cpp/class_properties.erb create mode 100644 codegen/lib/templates/cpp/enum.erb create mode 100644 codegen/lib/templates/cpp/header.erb create mode 100644 codegen/lib/templates/cpp/includes.erb create mode 100644 codegen/lib/templates/cpp/method.erb create mode 100644 codegen/lib/templates/cpp/method_call.erb create mode 100644 codegen/lib/templates/cpp/method_forward.erb create mode 100644 codegen/lib/templates/cpp/parameter_access.erb create mode 100644 codegen/lib/templates/cpp/static_method.erb delete mode 100644 codegen/lib/templates/swift/header.erb create mode 100644 codegen/lib/templates/ts/class_d.erb create mode 100644 codegen/lib/templates/ts/enum_d.erb create mode 100644 codegen/lib/templates/wasm_cpp.erb create mode 100644 codegen/lib/templates/wasm_d_ts.erb create mode 100644 codegen/lib/templates/wasm_h.erb create mode 100644 codegen/lib/ts_helper.rb create mode 100644 codegen/lib/wasm_cpp_helper.rb create mode 100644 docs/registry-fields.md create mode 100644 include/TrustWalletCore/TWBase32.h create mode 100644 include/TrustWalletCore/TWBase64.h create mode 100644 include/TrustWalletCore/TWBitcoinMessageSigner.h create mode 100644 include/TrustWalletCore/TWCardano.h create mode 100644 include/TrustWalletCore/TWDataVector.h create mode 100644 include/TrustWalletCore/TWDerivationPath.h create mode 100644 include/TrustWalletCore/TWDerivationPathIndex.h delete mode 100644 include/TrustWalletCore/TWEthereumChainID.h create mode 100644 include/TrustWalletCore/TWNervosAddress.h create mode 100644 include/TrustWalletCore/TWPBKDF2.h create mode 100644 include/TrustWalletCore/TWPrivateKeyType.h create mode 100644 include/TrustWalletCore/TWStoredKeyEncryptionLevel.h rename include/TrustWalletCore/{TWEthereumFee.h => TWTHORChainSwap.h} (60%) create mode 100644 include/TrustWalletCore/TWTransactionCompiler.h create mode 100644 samples/go/compile.sh create mode 100644 samples/go/core/bitcoin.go create mode 100644 samples/go/core/coin.go create mode 100644 samples/go/core/datavector.go create mode 100644 samples/go/core/mnemonic.go create mode 100644 samples/go/core/publicKey.go create mode 100644 samples/go/core/transaction.go create mode 100644 samples/go/core/transactionHelper.go create mode 100644 samples/go/core/wallet.go create mode 100644 samples/go/dev-console/.gitignore create mode 100644 samples/go/dev-console/README.md create mode 100644 samples/go/dev-console/cli/address.go create mode 100644 samples/go/dev-console/cli/completer.go create mode 100644 samples/go/dev-console/cli/create_wallet.go create mode 100644 samples/go/dev-console/cli/delete_wallet.go create mode 100644 samples/go/dev-console/cli/executor.go create mode 100644 samples/go/dev-console/cli/help.go create mode 100644 samples/go/dev-console/cli/load_wallet.go create mode 100644 samples/go/dev-console/cmd/cli.go create mode 100644 samples/go/dev-console/go.mod create mode 100644 samples/go/dev-console/go.sum create mode 100644 samples/go/dev-console/native/cgo.go create mode 100644 samples/go/dev-console/native/packaged/.gitignore create mode 100644 samples/go/dev-console/native/packaged/.gitkeep create mode 100644 samples/go/dev-console/native/packaged/include/.gitkeep create mode 100644 samples/go/dev-console/native/packaged/include/dummy.go create mode 100644 samples/go/dev-console/native/packaged/lib/.gitkeep create mode 100644 samples/go/dev-console/native/packaged/lib/dummy.go create mode 100644 samples/go/dev-console/native/twaccount.go create mode 100644 samples/go/dev-console/native/twcoin.go create mode 100644 samples/go/dev-console/native/twdata.go create mode 100644 samples/go/dev-console/native/twmnemonic.go create mode 100644 samples/go/dev-console/native/twstoredkey.go create mode 100644 samples/go/dev-console/native/twstring.go create mode 100644 samples/go/dev-console/native/twwallet.go create mode 100644 samples/go/dev-console/prepare.sh create mode 100644 samples/go/dev-console/wallet/wallet.go create mode 100644 samples/go/protos/binance/Binance.pb.go create mode 100644 samples/go/protos/common/Common.pb.go create mode 100644 samples/go/protos/common/TransactionCompiler.pb.go create mode 100644 samples/go/protos/ethereum/Ethereum.pb.go create mode 100644 samples/go/sample/external_signing.go create mode 100644 samples/node/index.ts create mode 100644 samples/node/package-lock.json create mode 100644 samples/node/package.json create mode 100644 samples/node/tsconfig.json create mode 100644 samples/osx/cocoapods/WalletCoreExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 samples/typescript/devconsole.ts/.gitignore create mode 100644 samples/typescript/devconsole.ts/README.md create mode 100644 samples/typescript/devconsole.ts/package-lock.json create mode 100644 samples/typescript/devconsole.ts/package.json create mode 100644 samples/typescript/devconsole.ts/samplescripts/privkeysign.sampleinput.txt create mode 100644 samples/typescript/devconsole.ts/samplescripts/protoeth.sampleinput.txt create mode 100644 samples/typescript/devconsole.ts/samplescripts/solanaaddress.sampleinput.txt create mode 100644 samples/typescript/devconsole.ts/samplescripts/wallets.sampleinput.txt create mode 100644 samples/typescript/devconsole.ts/src/index.ts create mode 100644 samples/typescript/devconsole.ts/test/data/privpubkey.testinput.txt create mode 100644 samples/typescript/devconsole.ts/tsconfig.json create mode 100644 samples/wasm/.gitignore create mode 100644 samples/wasm/README.md create mode 100644 samples/wasm/index.html create mode 100644 src/AnyAddress.cpp create mode 100644 src/AnyAddress.h create mode 100644 src/Aptos/Address.cpp create mode 100644 src/Aptos/Address.h create mode 100644 src/Aptos/Entry.cpp create mode 100644 src/Aptos/Entry.h create mode 100644 src/Aptos/MoveTypes.cpp create mode 100644 src/Aptos/MoveTypes.h create mode 100644 src/Aptos/Signer.cpp create mode 100644 src/Aptos/Signer.h create mode 100644 src/Aptos/TransactionBuilder.h create mode 100644 src/Aptos/TransactionPayload.cpp create mode 100644 src/Aptos/TransactionPayload.h create mode 100644 src/BCS.cpp create mode 100644 src/BCS.h create mode 100644 src/Bitcoin/MessageSigner.cpp create mode 100644 src/Bitcoin/MessageSigner.h create mode 100644 src/Cardano/Signer.cpp create mode 100644 src/Cardano/Signer.h create mode 100644 src/Cardano/Transaction.cpp create mode 100644 src/Cardano/Transaction.h rename src/Cosmos/{Serialization.cpp => JsonSerialization.cpp} (76%) create mode 100644 src/Cosmos/JsonSerialization.h create mode 100644 src/Cosmos/Protobuf/.clang-tidy create mode 100644 src/Cosmos/Protobuf/.gitignore create mode 100644 src/Cosmos/Protobuf/authz_tx.proto create mode 100644 src/Cosmos/Protobuf/bank_tx.proto create mode 100644 src/Cosmos/Protobuf/coin.proto create mode 100644 src/Cosmos/Protobuf/cosmwasm_wasm_v1_tx.proto create mode 100644 src/Cosmos/Protobuf/crypto_multisig.proto create mode 100644 src/Cosmos/Protobuf/crypto_secp256k1_keys.proto create mode 100644 src/Cosmos/Protobuf/distribution_tx.proto create mode 100644 src/Cosmos/Protobuf/ethermint_keys.proto create mode 100644 src/Cosmos/Protobuf/gov_tx.proto create mode 100644 src/Cosmos/Protobuf/ibc_applications_transfer_tx.proto create mode 100644 src/Cosmos/Protobuf/ibc_core_client.proto create mode 100644 src/Cosmos/Protobuf/staking_tx.proto create mode 100644 src/Cosmos/Protobuf/terra_wasm_v1beta1_tx.proto create mode 100644 src/Cosmos/Protobuf/thorchain_bank_tx.proto create mode 100644 src/Cosmos/Protobuf/tx.proto create mode 100644 src/Cosmos/Protobuf/tx_signing.proto create mode 100644 src/Cosmos/ProtobufSerialization.cpp create mode 100644 src/Cosmos/ProtobufSerialization.h delete mode 100644 src/Cosmos/Serialization.h create mode 100644 src/Defer.h create mode 100644 src/Elrond/Codec.cpp create mode 100644 src/Elrond/Codec.h create mode 100644 src/Elrond/GasEstimator.cpp create mode 100644 src/Elrond/GasEstimator.h create mode 100644 src/Elrond/NetworkConfig.cpp create mode 100644 src/Elrond/NetworkConfig.h create mode 100644 src/Elrond/Transaction.cpp create mode 100644 src/Elrond/Transaction.h create mode 100644 src/Elrond/TransactionFactory.cpp create mode 100644 src/Elrond/TransactionFactory.h delete mode 100644 src/Ethereum/Fee.cpp create mode 100644 src/Everscale/Address.cpp create mode 100644 src/Everscale/Address.h create mode 100644 src/Everscale/Cell.cpp create mode 100644 src/Everscale/Cell.h create mode 100644 src/Everscale/CellBuilder.cpp create mode 100644 src/Everscale/CellBuilder.h create mode 100644 src/Everscale/CellSlice.cpp create mode 100644 src/Everscale/CellSlice.h create mode 100644 src/Everscale/Entry.cpp create mode 100644 src/Everscale/Entry.h create mode 100644 src/Everscale/Messages.cpp create mode 100644 src/Everscale/Messages.h create mode 100644 src/Everscale/Signer.cpp create mode 100644 src/Everscale/Signer.h create mode 100644 src/Everscale/Wallet.cpp create mode 100644 src/Everscale/Wallet.h rename src/{Bitcoin/Address.cpp => Everscale/WorkchainType.h} (56%) create mode 100644 src/Generated/.clang-tidy delete mode 100644 src/Harmony/Transaction.cpp create mode 100644 src/NEO/Constants.h create mode 100644 src/Nervos/Address.cpp create mode 100644 src/Nervos/Address.h create mode 100644 src/Nervos/Cell.h create mode 100644 src/Nervos/CellDep.cpp create mode 100644 src/Nervos/CellDep.h create mode 100644 src/Nervos/CellInput.cpp create mode 100644 src/Nervos/CellInput.h create mode 100644 src/Nervos/CellOutput.cpp create mode 100644 src/Nervos/CellOutput.h create mode 100644 src/Nervos/Constants.h create mode 100644 src/Nervos/Entry.cpp create mode 100644 src/Nervos/Entry.h rename src/{Ethereum/Fee.h => Nervos/HeaderDep.h} (51%) create mode 100644 src/Nervos/OutPoint.cpp create mode 100644 src/Nervos/OutPoint.h create mode 100644 src/Nervos/Script.cpp create mode 100644 src/Nervos/Script.h create mode 100644 src/Nervos/Serialization.h create mode 100644 src/Nervos/Signer.cpp create mode 100644 src/Nervos/Signer.h create mode 100644 src/Nervos/Transaction.cpp create mode 100644 src/Nervos/Transaction.h create mode 100644 src/Nervos/TransactionPlan.cpp create mode 100644 src/Nervos/TransactionPlan.h create mode 100644 src/Nervos/Witness.cpp create mode 100644 src/Nervos/Witness.h create mode 100644 src/NumericLiteral.h create mode 100644 src/Ontology/Oep4.cpp create mode 100644 src/Ontology/Oep4.h create mode 100644 src/Ontology/Oep4TxBuilder.cpp create mode 100644 src/Ontology/Oep4TxBuilder.h create mode 100644 src/Polkadot/SS58Address.cpp create mode 100644 src/Polkadot/SS58Address.h create mode 100644 src/Ronin/Address.cpp create mode 100644 src/Ronin/Address.h create mode 100644 src/Ronin/Entry.cpp create mode 100644 src/Ronin/Entry.h delete mode 100644 src/SS58Address.h create mode 100644 src/THORChain/Swap.cpp create mode 100644 src/THORChain/Swap.h create mode 100644 src/THORChain/TWSwap.cpp create mode 100644 src/Tezos/Michelson.cpp create mode 100644 src/Tezos/Michelson.h create mode 100644 src/TransactionCompiler.cpp create mode 100644 src/TransactionCompiler.h create mode 100644 src/Wasm.h rename src/{Ripple => XRP}/Address.cpp (92%) rename src/{Ripple => XRP}/Address.h (98%) rename src/{Ripple => XRP}/BinaryCoding.h (100%) rename src/{Ripple => XRP}/Entry.cpp (56%) rename src/{Ripple => XRP}/Entry.h (54%) rename src/{Ripple => XRP}/Signer.cpp (62%) rename src/{Ripple => XRP}/Signer.h (97%) rename src/{Ripple => XRP}/Transaction.cpp (89%) rename src/{Ripple => XRP}/Transaction.h (99%) rename src/{Ripple => XRP}/XAddress.cpp (84%) rename src/{Ripple => XRP}/XAddress.h (96%) delete mode 100644 src/XXHash64.h create mode 100644 src/algorithm/erase.h create mode 100644 src/algorithm/sort_copy.h create mode 100644 src/algorithm/to_array.h create mode 100644 src/concepts/tw_concepts.h create mode 100644 src/interface/TWBase32.cpp create mode 100644 src/interface/TWBase64.cpp create mode 100644 src/interface/TWBitcoinMessageSigner.cpp rename src/interface/{TWBitcoin.cpp => TWBitcoinSigHashType.cpp} (100%) create mode 100644 src/interface/TWCardano.cpp create mode 100644 src/interface/TWDataVector.cpp create mode 100644 src/interface/TWDerivationPath.cpp create mode 100644 src/interface/TWDerivationPathIndex.cpp delete mode 100644 src/interface/TWEthereumFee.cpp create mode 100644 src/interface/TWNervosAddress.cpp create mode 100644 src/interface/TWPBKDF2.cpp create mode 100644 src/interface/TWTransactionCompiler.cpp create mode 100644 src/memory/memzero_wrapper.h create mode 100644 src/operators/equality_comparable.h create mode 100644 src/proto/.clang-tidy create mode 100644 src/proto/Aptos.proto create mode 100644 src/proto/Cardano.proto create mode 100644 src/proto/Everscale.proto create mode 100644 src/proto/Nervos.proto create mode 100644 src/proto/THORChainSwap.proto create mode 100644 src/proto/TransactionCompiler.proto rename .swiftlint.yml => swift/.swiftlint.yml (100%) delete mode 100644 swift/Sources/DerivationPath.Index.swift delete mode 100644 swift/Sources/DerivationPath.swift create mode 100644 swift/Sources/Extensions/Account+Codable.swift create mode 100644 swift/Sources/Extensions/DerivationPath+Extension.swift create mode 100644 swift/Sources/TWCardano.swift create mode 100644 swift/Tests/Addresses/JunoAddressTests.swift create mode 100644 swift/Tests/Base32Tests.swift create mode 100644 swift/Tests/Base64Tests.swift create mode 100644 swift/Tests/Blockchains/AptosTests.swift create mode 100644 swift/Tests/Blockchains/CronosTests.swift create mode 100644 swift/Tests/Blockchains/ECashTests.swift delete mode 100644 swift/Tests/Blockchains/EthereumFeeTests.swift create mode 100644 swift/Tests/Blockchains/EverscaleTests.swift create mode 100644 swift/Tests/Blockchains/EvmosTests.swift create mode 100644 swift/Tests/Blockchains/KuCoinCommunityChainTests.swift create mode 100644 swift/Tests/Blockchains/NervosTests.swift create mode 100644 swift/Tests/Blockchains/OsmosisTests.swift create mode 100644 swift/Tests/Blockchains/SmartBitcoinCashTests.swift create mode 100644 swift/Tests/Blockchains/THORChainSwapTests.swift create mode 100644 swift/Tests/Blockchains/TerraClassicTests.swift create mode 100644 swift/Tests/PBKDF2Tests.swift create mode 100644 swift/Tests/TransactionCompilerTests.swift delete mode 100644 swift/protobuf.patch delete mode 100644 tests/Cardano/AddressTests.cpp delete mode 100644 tests/Cardano/TWCardanoAddressTests.cpp delete mode 100644 tests/CoinAddressDerivationTests.cpp delete mode 100644 tests/Cosmos/SignerTests.cpp delete mode 100644 tests/Cosmos/StakingTests.cpp delete mode 100644 tests/CryptoOrg/TWAnySignerTests.cpp delete mode 100644 tests/Elrond/SignerTests.cpp delete mode 100644 tests/Elrond/TestAccounts.h delete mode 100644 tests/Ethereum/FeeTests.cpp delete mode 100644 tests/Keystore/StoredKeyTests.cpp delete mode 100644 tests/NEAR/SerializationTests.cpp delete mode 100644 tests/NEAR/TWAnySignerTests.cpp delete mode 100644 tests/THORChain/SignerTests.cpp delete mode 100644 tests/Tezos/TWAnySignerTests.cpp rename tests/{ => chains}/Aeternity/AddressTests.cpp (80%) rename tests/{ => chains}/Aeternity/SignerTests.cpp (97%) rename tests/{ => chains}/Aeternity/TWAeternityAddressTests.cpp (96%) rename tests/{ => chains}/Aeternity/TWAnySignerTests.cpp (90%) rename tests/{ => chains}/Aeternity/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Aeternity/TransactionTests.cpp (89%) rename tests/{ => chains}/Aion/AddressTests.cpp (95%) rename tests/{ => chains}/Aion/RLPTests.cpp (91%) rename tests/{ => chains}/Aion/SignerTests.cpp (95%) rename tests/{ => chains}/Aion/TWAnySignerTests.cpp (92%) rename tests/{ => chains}/Aion/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Aion/TransactionTests.cpp (93%) rename tests/{ => chains}/Algorand/AddressTests.cpp (96%) rename tests/{ => chains}/Algorand/SignerTests.cpp (96%) rename tests/{ => chains}/Algorand/TWAnySignerTests.cpp (96%) rename tests/{ => chains}/Algorand/TWCoinTypeTests.cpp (97%) create mode 100644 tests/chains/Aptos/AddressTests.cpp create mode 100644 tests/chains/Aptos/MoveTypesTests.cpp create mode 100644 tests/chains/Aptos/SignerTests.cpp create mode 100644 tests/chains/Aptos/TWAnySignerTests.cpp create mode 100644 tests/chains/Aptos/TWAptosAddressTests.cpp create mode 100644 tests/chains/Aptos/TWCoinTypeTests.cpp create mode 100644 tests/chains/Aptos/TransactionPayloadTests.cpp rename tests/{ => chains}/Arbitrum/TWCoinTypeTests.cpp (94%) create mode 100644 tests/chains/Aurora/TWCoinTypeTests.cpp rename tests/{ => chains}/Avalanche/TWCoinTypeTests.cpp (84%) rename tests/{ => chains}/BandChain/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Binance/SignerTests.cpp (100%) rename tests/{ => chains}/Binance/TWAnySignerTests.cpp (72%) rename tests/{ => chains}/Binance/TWCoinTypeTests.cpp (89%) rename tests/{ => chains}/BinanceSmartChain/SignerTests.cpp (84%) rename tests/{ => chains}/BinanceSmartChain/TWAnyAddressTests.cpp (96%) rename tests/{ => chains}/BinanceSmartChain/TWCoinTypeTests.cpp (96%) rename tests/{ => chains}/Bitcoin/BitcoinAddressTests.cpp (91%) rename tests/{ => chains}/Bitcoin/BitcoinScriptTests.cpp (92%) rename tests/{ => chains}/Bitcoin/FeeCalculatorTests.cpp (89%) rename tests/{ => chains}/Bitcoin/InputSelectorTests.cpp (96%) create mode 100644 tests/chains/Bitcoin/MessageSignerTests.cpp rename tests/{ => chains}/Bitcoin/SegwitAddressTests.cpp (71%) rename tests/{ => chains}/Bitcoin/TWBitcoinAddressTests.cpp (98%) rename tests/{ => chains}/Bitcoin/TWBitcoinScriptTests.cpp (92%) rename tests/{ => chains}/Bitcoin/TWBitcoinSigningTests.cpp (84%) rename tests/{ => chains}/Bitcoin/TWBitcoinTransactionTests.cpp (68%) rename tests/{ => chains}/Bitcoin/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Bitcoin/TWSegwitAddressTests.cpp (68%) rename tests/{ => chains}/Bitcoin/TransactionPlanTests.cpp (88%) rename tests/{ => chains}/Bitcoin/TxComparisonHelper.cpp (84%) rename tests/{ => chains}/Bitcoin/TxComparisonHelper.h (91%) rename tests/{ => chains}/BitcoinCash/TWBitcoinCashTests.cpp (83%) rename tests/{ => chains}/BitcoinCash/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/BitcoinGold/TWAddressTests.cpp (96%) rename tests/{ => chains}/BitcoinGold/TWBitcoinGoldTests.cpp (97%) rename tests/{ => chains}/BitcoinGold/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/BitcoinGold/TWSegwitAddressTests.cpp (83%) rename tests/{ => chains}/BitcoinGold/TWSignerTests.cpp (92%) rename tests/{ => chains}/Bluzelle/TWCoinTypeTests.cpp (97%) create mode 100644 tests/chains/Boba/TWCoinTypeTests.cpp rename tests/{ => chains}/Callisto/TWCoinTypeTests.cpp (87%) create mode 100644 tests/chains/Cardano/AddressTests.cpp create mode 100644 tests/chains/Cardano/SigningTests.cpp create mode 100644 tests/chains/Cardano/StakingTests.cpp create mode 100644 tests/chains/Cardano/TWCardanoAddressTests.cpp rename tests/{ => chains}/Cardano/TWCoinTypeTests.cpp (81%) create mode 100644 tests/chains/Cardano/TransactionTests.cpp rename tests/{ => chains}/Celo/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Cosmos/AddressTests.cpp (100%) create mode 100644 tests/chains/Cosmos/Protobuf/.gitignore create mode 100644 tests/chains/Cosmos/Protobuf/Article.proto create mode 100644 tests/chains/Cosmos/ProtobufTests.cpp create mode 100644 tests/chains/Cosmos/SignerTests.cpp create mode 100644 tests/chains/Cosmos/StakingTests.cpp rename tests/{ => chains}/Cosmos/TWAnyAddressTests.cpp (95%) rename tests/{ => chains}/Cosmos/TWAnySignerTests.cpp (54%) rename tests/{ => chains}/Cosmos/TWCoinTypeTests.cpp (65%) create mode 100644 tests/chains/Cronos/TWAnyAddressTests.cpp create mode 100644 tests/chains/Cronos/TWCoinTypeTests.cpp rename tests/{ => chains}/CryptoOrg/AddressTests.cpp (95%) rename tests/{ => chains}/CryptoOrg/SignerTests.cpp (95%) rename tests/{ => chains}/CryptoOrg/TWAnyAddressTests.cpp (96%) create mode 100644 tests/chains/CryptoOrg/TWAnySignerTests.cpp rename tests/{ => chains}/CryptoOrg/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Dash/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Dash/TWDashTests.cpp (95%) rename tests/{ => chains}/Decred/AddressTests.cpp (93%) rename tests/{ => chains}/Decred/SignerTests.cpp (98%) rename tests/{ => chains}/Decred/TWAnySignerTests.cpp (98%) rename tests/{ => chains}/Decred/TWCoinTypeTests.cpp (92%) rename tests/{ => chains}/Decred/TWDecredTests.cpp (98%) rename tests/{ => chains}/DigiByte/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/DigiByte/TWDigiByteTests.cpp (89%) rename tests/{ => chains}/Dogecoin/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Dogecoin/TWDogeTests.cpp (95%) rename tests/{HECO => chains/ECO}/TWCoinTypeTests.cpp (97%) create mode 100644 tests/chains/ECash/TWCoinTypeTests.cpp create mode 100644 tests/chains/ECash/TWECashTests.cpp rename tests/{ => chains}/EOS/AddressTests.cpp (71%) rename tests/{ => chains}/EOS/AssetTests.cpp (90%) rename tests/{ => chains}/EOS/NameTests.cpp (76%) rename tests/{ => chains}/EOS/SignatureTests.cpp (85%) rename tests/{ => chains}/EOS/TWAnySignerTests.cpp (94%) rename tests/{ => chains}/EOS/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/EOS/TransactionTests.cpp (91%) rename tests/{ => chains}/Elrond/AddressTests.cpp (78%) rename tests/{ => chains}/Elrond/SerializationTests.cpp (53%) create mode 100644 tests/chains/Elrond/SignerTests.cpp rename tests/{ => chains}/Elrond/TWAnySignerTests.cpp (55%) rename tests/{ => chains}/Elrond/TWCoinTypeTests.cpp (97%) create mode 100644 tests/chains/Elrond/TestAccounts.h create mode 100644 tests/chains/Elrond/TransactionFactoryTests.cpp rename tests/{ => chains}/Ethereum/AbiStructTests.cpp (87%) rename tests/{ => chains}/Ethereum/AbiTests.cpp (84%) rename tests/{ => chains}/Ethereum/AddressTests.cpp (84%) rename tests/{ => chains}/Ethereum/ContractCallTests.cpp (94%) rename tests/{ => chains}/Ethereum/Data/1inch.json (100%) rename tests/{ => chains}/Ethereum/Data/custom.json (100%) rename tests/{ => chains}/Ethereum/Data/eip712_cryptofights.json (100%) create mode 100644 tests/chains/Ethereum/Data/eip712_emptyArray.json create mode 100644 tests/chains/Ethereum/Data/eip712_emptyString.json rename tests/{ => chains}/Ethereum/Data/eip712_rarible.json (100%) rename tests/{ => chains}/Ethereum/Data/eip712_snapshot_v4.json (100%) rename tests/{ => chains}/Ethereum/Data/eip712_walletconnect.json (100%) rename tests/{ => chains}/Ethereum/Data/ens.json (100%) rename tests/{ => chains}/Ethereum/Data/erc20.json (100%) rename tests/{ => chains}/Ethereum/Data/erc721.json (100%) rename tests/{ => chains}/Ethereum/Data/eth_feeHistory.json (100%) rename tests/{ => chains}/Ethereum/Data/eth_feeHistory2.json (100%) rename tests/{ => chains}/Ethereum/Data/eth_feeHistory3.json (100%) rename tests/{ => chains}/Ethereum/Data/eth_feeHistory4.json (100%) rename tests/{ => chains}/Ethereum/Data/getAmountsOut.json (100%) rename tests/{ => chains}/Ethereum/Data/kyber_proxy.json (100%) create mode 100644 tests/chains/Ethereum/Data/seaport_712.json rename tests/{ => chains}/Ethereum/Data/tuple_nested.json (100%) rename tests/{ => chains}/Ethereum/Data/uniswap_router_v2.json (100%) rename tests/{ => chains}/Ethereum/Data/zilliqa_data_tx.json (100%) rename tests/{ => chains}/Ethereum/RLPTests.cpp (82%) rename tests/{ => chains}/Ethereum/SignerTests.cpp (100%) rename tests/{ => chains}/Ethereum/TWAnySignerTests.cpp (95%) rename tests/{ => chains}/Ethereum/TWCoinTypeTests.cpp (92%) rename tests/{ => chains}/Ethereum/TWEthereumAbiTests.cpp (97%) rename tests/{ => chains}/Ethereum/TWEthereumAbiValueDecoderTests.cpp (99%) rename tests/{ => chains}/Ethereum/TWEthereumAbiValueEncodeTests.cpp (98%) rename tests/{ => chains}/Ethereum/ValueDecoderTests.cpp (83%) rename tests/{ => chains}/Ethereum/ValueEncoderTests.cpp (87%) rename tests/{ => chains}/EthereumClassic/TWCoinTypeTests.cpp (92%) create mode 100644 tests/chains/Everscale/AddressTests.cpp create mode 100644 tests/chains/Everscale/CellBuilderTest.cpp create mode 100644 tests/chains/Everscale/CellTests.cpp create mode 100644 tests/chains/Everscale/SignerTests.cpp create mode 100644 tests/chains/Everscale/TWAnyAddressTests.cpp create mode 100644 tests/chains/Everscale/TWAnySignerTests.cpp create mode 100644 tests/chains/Everscale/TWCoinTypeTests.cpp create mode 100644 tests/chains/Evmos/SignerTests.cpp create mode 100644 tests/chains/Evmos/TWAnyAddressTests.cpp create mode 100644 tests/chains/Evmos/TWCoinTypeTests.cpp rename tests/{ => chains}/FIO/AddressTests.cpp (94%) rename tests/{ => chains}/FIO/EncryptionTests.cpp (97%) rename tests/{ => chains}/FIO/SignerTests.cpp (90%) rename tests/{ => chains}/FIO/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/FIO/TWFIOAccountTests.cpp (97%) rename tests/{ => chains}/FIO/TWFIOTests.cpp (91%) rename tests/{ => chains}/FIO/TransactionBuilderTests.cpp (83%) rename tests/{ => chains}/Fantom/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Filecoin/AddressTests.cpp (99%) rename tests/{ => chains}/Filecoin/SignerTests.cpp (100%) rename tests/{ => chains}/Filecoin/TWAnySignerTests.cpp (97%) rename tests/{ => chains}/Filecoin/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Filecoin/TransactionTests.cpp (100%) rename tests/{Zcoin => chains/Firo}/TWCoinTypeTests.cpp (69%) rename tests/{Zcoin => chains/Firo}/TWZCoinAddressTests.cpp (84%) rename tests/{ => chains}/GoChain/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Groestlcoin/AddressTests.cpp (93%) rename tests/{ => chains}/Groestlcoin/TWCoinTypeTests.cpp (92%) rename tests/{ => chains}/Groestlcoin/TWGroestlcoinSigningTests.cpp (98%) rename tests/{ => chains}/Groestlcoin/TWGroestlcoinTests.cpp (99%) rename tests/{ => chains}/Harmony/AddressTests.cpp (96%) rename tests/{ => chains}/Harmony/SignerTests.cpp (100%) rename tests/{ => chains}/Harmony/StakingTests.cpp (100%) rename tests/{ => chains}/Harmony/TWAnyAddressTests.cpp (95%) rename tests/{ => chains}/Harmony/TWAnySignerTests.cpp (97%) rename tests/{ => chains}/Harmony/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Harmony/TWHarmonyStakingTests.cpp (99%) rename tests/{Icon => chains/ICON}/AddressTests.cpp (96%) rename tests/{Icon => chains/ICON}/TWAnySignerTests.cpp (95%) rename tests/{ => chains}/ICON/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/IoTeX/AddressTests.cpp (89%) rename tests/{ => chains}/IoTeX/SignerTests.cpp (100%) rename tests/{ => chains}/IoTeX/StakingTests.cpp (98%) rename tests/{ => chains}/IoTeX/TWAnySignerTests.cpp (91%) rename tests/{ => chains}/IoTeX/TWCoinTypeTests.cpp (97%) create mode 100644 tests/chains/Juno/TWAnyAddressTests.cpp rename tests/{ => chains}/Kava/TWCoinTypeTests.cpp (77%) create mode 100644 tests/chains/KavaEvm/TWCoinTypeTests.cpp rename tests/{ => chains}/Kin/TWCoinTypeTests.cpp (97%) create mode 100644 tests/chains/Klaytn/TWCoinTypeTests.cpp create mode 100644 tests/chains/KuCoinCommunityChain/TWCoinTypeTests.cpp rename tests/{ => chains}/Kusama/AddressTests.cpp (95%) rename tests/{ => chains}/Kusama/SignerTests.cpp (95%) rename tests/{ => chains}/Kusama/TWAnySignerTests.cpp (94%) rename tests/{ => chains}/Kusama/TWCoinTypeTests.cpp (93%) create mode 100644 tests/chains/Litecoin/LitecoinAddressTests.cpp rename tests/{ => chains}/Litecoin/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Litecoin/TWLitecoinTests.cpp (95%) create mode 100644 tests/chains/Meter/TWCoinTypeTests.cpp create mode 100644 tests/chains/Metis/TWCoinTypeTests.cpp rename tests/{ => chains}/Monacoin/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Monacoin/TWMonacoinAddressTests.cpp (99%) rename tests/{ => chains}/Monacoin/TWMonacoinTransactionTests.cpp (87%) create mode 100644 tests/chains/Moonbeam/TWCoinTypeTests.cpp create mode 100644 tests/chains/Moonriver/TWCoinTypeTests.cpp rename tests/{ => chains}/NEAR/AccountTests.cpp (88%) rename tests/{ => chains}/NEAR/AddressTests.cpp (92%) create mode 100644 tests/chains/NEAR/SerializationTests.cpp rename tests/{ => chains}/NEAR/SignerTests.cpp (93%) create mode 100644 tests/chains/NEAR/TWAnySignerTests.cpp rename tests/{ => chains}/NEAR/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/NEAR/TWNEARAccountTests.cpp (96%) rename tests/{ => chains}/NEO/AddressTests.cpp (97%) rename tests/{ => chains}/NEO/CoinReferenceTests.cpp (70%) rename tests/{ => chains}/NEO/SignerTests.cpp (93%) rename tests/{ => chains}/NEO/TWAnySignerTests.cpp (97%) rename tests/{ => chains}/NEO/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/NEO/TWNEOAddressTests.cpp (96%) rename tests/{ => chains}/NEO/TransactionAttributeTests.cpp (72%) rename tests/{ => chains}/NEO/TransactionOutputTests.cpp (94%) rename tests/{ => chains}/NEO/TransactionTests.cpp (93%) rename tests/{ => chains}/NEO/WitnessTests.cpp (96%) rename tests/{ => chains}/NULS/AddressTests.cpp (95%) rename tests/{ => chains}/NULS/TWAnySignerTests.cpp (93%) rename tests/{ => chains}/NULS/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Nano/AddressTests.cpp (97%) rename tests/{ => chains}/Nano/SignerTests.cpp (99%) rename tests/{ => chains}/Nano/TWAnySignerTests.cpp (97%) rename tests/{ => chains}/Nano/TWCoinTypeTests.cpp (95%) rename tests/{ => chains}/Nano/TWNanoAddressTests.cpp (97%) create mode 100644 tests/chains/NativeEvmos/TWAnyAddressTests.cpp create mode 100644 tests/chains/NativeEvmos/TWCoinTypeTests.cpp rename tests/{ => chains}/Nebulas/AddressTests.cpp (87%) rename tests/{ => chains}/Nebulas/SignerTests.cpp (100%) rename tests/{ => chains}/Nebulas/TWAnySignerTests.cpp (80%) rename tests/{ => chains}/Nebulas/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Nebulas/TWNebulasAddressTests.cpp (98%) rename tests/{ => chains}/Nebulas/TransactionTests.cpp (94%) create mode 100644 tests/chains/Nervos/AddressTests.cpp create mode 100644 tests/chains/Nervos/SignerTests.cpp create mode 100644 tests/chains/Nervos/TWAnyAddressTests.cpp create mode 100644 tests/chains/Nervos/TWAnySignerTests.cpp create mode 100644 tests/chains/Nervos/TWCoinTypeTests.cpp create mode 100644 tests/chains/Nervos/TWNervosAddressTests.cpp rename tests/{ => chains}/Nimiq/AddressTests.cpp (88%) rename tests/{ => chains}/Nimiq/SignerTests.cpp (100%) rename tests/{ => chains}/Nimiq/TWAnySignerTests.cpp (93%) rename tests/{ => chains}/Nimiq/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Nimiq/TransactionTests.cpp (100%) create mode 100644 tests/chains/OKXChain/TWCoinTypeTests.cpp rename tests/{ => chains}/Oasis/AddressTests.cpp (85%) rename tests/{ => chains}/Oasis/SignerTests.cpp (66%) rename tests/{ => chains}/Oasis/TWAnySignerTests.cpp (64%) rename tests/{ => chains}/Oasis/TWCoinTypeTests.cpp (95%) rename tests/{ => chains}/Ontology/AccountTests.cpp (88%) rename tests/{ => chains}/Ontology/AddressTests.cpp (94%) create mode 100644 tests/chains/Ontology/Oep4Tests.cpp rename tests/{ => chains}/Ontology/OngTests.cpp (97%) rename tests/{ => chains}/Ontology/OntTests.cpp (96%) rename tests/{ => chains}/Ontology/ParamsBuilderTests.cpp (58%) rename tests/{ => chains}/Ontology/TWAnySignerTests.cpp (78%) rename tests/{ => chains}/Ontology/TWCoinTypeTests.cpp (94%) rename tests/{ => chains}/Ontology/TransactionTests.cpp (85%) rename tests/{ => chains}/Optimism/TWCoinTypeTests.cpp (94%) create mode 100644 tests/chains/Osmosis/AddressTests.cpp create mode 100644 tests/chains/Osmosis/SignerTests.cpp create mode 100644 tests/chains/Osmosis/TWAnyAddressTests.cpp create mode 100644 tests/chains/Osmosis/TWAnySignerTests.cpp create mode 100644 tests/chains/Osmosis/TWCoinTypeTests.cpp rename tests/{ => chains}/POANetwork/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Polkadot/AddressTests.cpp (96%) create mode 100644 tests/chains/Polkadot/SS58AddressTests.cpp rename tests/{ => chains}/Polkadot/ScaleCodecTests.cpp (98%) rename tests/{ => chains}/Polkadot/SignerTests.cpp (91%) rename tests/{ => chains}/Polkadot/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Polygon/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Qtum/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Qtum/TWQtumAddressTests.cpp (99%) rename tests/{ => chains}/Ravencoin/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Ravencoin/TWRavencoinTransactionTests.cpp (95%) create mode 100644 tests/chains/Ronin/TWAnyAddressTests.cpp create mode 100644 tests/chains/Ronin/TWAnySignerTests.cpp rename tests/{ => chains}/Ronin/TWCoinTypeTests.cpp (92%) create mode 100644 tests/chains/SmartBitcoinCash/TWCoinTypeTests.cpp rename tests/{ => chains}/Solana/AddressTests.cpp (91%) rename tests/{ => chains}/Solana/ProgramTests.cpp (76%) rename tests/{ => chains}/Solana/SignerTests.cpp (96%) rename tests/{ => chains}/Solana/TWAnySignerTests.cpp (84%) rename tests/{ => chains}/Solana/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Solana/TWSolanaAddressTests.cpp (69%) rename tests/{ => chains}/Solana/TransactionTests.cpp (85%) rename tests/{ => chains}/Stellar/AddressTests.cpp (95%) rename tests/{ => chains}/Stellar/TWAnySignerTests.cpp (66%) rename tests/{ => chains}/Stellar/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Stellar/TWStellarAddressTests.cpp (96%) rename tests/{ => chains}/Stellar/TransactionTests.cpp (97%) create mode 100644 tests/chains/THORChain/SignerTests.cpp create mode 100644 tests/chains/THORChain/SwapTests.cpp rename tests/{ => chains}/THORChain/TWAnyAddressTests.cpp (96%) rename tests/{ => chains}/THORChain/TWAnySignerTests.cpp (94%) rename tests/{ => chains}/THORChain/TWCoinTypeTests.cpp (93%) create mode 100644 tests/chains/THORChain/TWSwapTests.cpp create mode 100644 tests/chains/Terra/SignerTests.cpp rename tests/{ => chains}/Terra/TWCoinTypeTests.cpp (65%) create mode 100644 tests/chains/TerraV2/SignerTests.cpp create mode 100644 tests/chains/TerraV2/TWCoinTypeTests.cpp rename tests/{ => chains}/Tezos/AddressTests.cpp (78%) rename tests/{ => chains}/Tezos/ForgingTests.cpp (57%) rename tests/{ => chains}/Tezos/OperationListTests.cpp (75%) rename tests/{ => chains}/Tezos/PublicKeyTests.cpp (94%) rename tests/{ => chains}/Tezos/SignerTests.cpp (97%) create mode 100644 tests/chains/Tezos/TWAnySignerTests.cpp rename tests/{ => chains}/Tezos/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Theta/SignerTests.cpp (100%) rename tests/{ => chains}/Theta/TWAnySignerTests.cpp (94%) rename tests/{ => chains}/Theta/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Theta/TransactionTests.cpp (94%) rename tests/{ => chains}/ThunderToken/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/TomoChain/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Tron/AddressTests.cpp (100%) rename tests/{ => chains}/Tron/SerializationTests.cpp (100%) rename tests/{ => chains}/Tron/SignerTests.cpp (100%) rename tests/{ => chains}/Tron/TWAnySignerTests.cpp (97%) rename tests/{ => chains}/Tron/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/VeChain/SignerTests.cpp (100%) rename tests/{ => chains}/VeChain/TWAnySignerTests.cpp (90%) rename tests/{ => chains}/VeChain/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Viacoin/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Viacoin/TWViacoinAddressTests.cpp (99%) rename tests/{ => chains}/Wanchain/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Waves/AddressTests.cpp (98%) rename tests/{ => chains}/Waves/LeaseTests.cpp (60%) rename tests/{ => chains}/Waves/SignerTests.cpp (97%) rename tests/{ => chains}/Waves/TWAnySignerTests.cpp (90%) rename tests/{ => chains}/Waves/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Waves/TransactionTests.cpp (87%) rename tests/{Ripple => chains/XRP}/AddressTests.cpp (61%) rename tests/{Ripple => chains/XRP}/TWAnySignerTests.cpp (79%) rename tests/{Ripple => chains/XRP}/TWCoinTypeTests.cpp (97%) rename tests/{Ripple => chains/XRP}/TWRippleAddressTests.cpp (56%) rename tests/{Ripple => chains/XRP}/TransactionTests.cpp (96%) rename tests/{ => chains}/Zcash/AddressTests.cpp (100%) rename tests/{ => chains}/Zcash/TWCoinTypeTests.cpp (93%) rename tests/{ => chains}/Zcash/TWZcashAddressTests.cpp (99%) rename tests/{ => chains}/Zcash/TWZcashTransactionTests.cpp (99%) rename tests/{ => chains}/Zelcash/TWCoinTypeTests.cpp (92%) rename tests/{ => chains}/Zelcash/TWZelcashAddressTests.cpp (99%) rename tests/{ => chains}/Zelcash/TWZelcashTransactionTests.cpp (99%) rename tests/{ => chains}/Zilliqa/AddressTests.cpp (82%) rename tests/{ => chains}/Zilliqa/SignatureTests.cpp (82%) rename tests/{ => chains}/Zilliqa/SignerTests.cpp (87%) rename tests/{ => chains}/Zilliqa/TWAnySignerTests.cpp (96%) rename tests/{ => chains}/Zilliqa/TWCoinTypeTests.cpp (97%) rename tests/{ => chains}/Zilliqa/TWZilliqaAddressTests.cpp (97%) create mode 100644 tests/chains/ZkSyncV2/TWCoinTypeTests.cpp rename tests/{ => chains}/xDai/TWCoinTypeTests.cpp (95%) create mode 100644 tests/common/AnyAddressTests.cpp create mode 100644 tests/common/BCSTests.cpp rename tests/{ => common}/Base64Tests.cpp (95%) rename tests/{ => common}/BaseEncoding.cpp (86%) rename tests/{ => common}/Bech32AddressTests.cpp (90%) rename tests/{ => common}/Bech32Tests.cpp (99%) rename tests/{ => common}/BinaryCodingTests.cpp (98%) rename tests/{ => common}/CborTests.cpp (69%) create mode 100644 tests/common/CoinAddressDerivationTests.cpp rename tests/{ => common}/CoinAddressValidationTests.cpp (93%) create mode 100644 tests/common/DataTests.cpp rename tests/{ => common}/EncryptTests.cpp (81%) create mode 100644 tests/common/HDWallet/HDWalletInternalTests.cpp rename tests/{ => common}/HDWallet/HDWalletTests.cpp (58%) rename tests/{ => common}/HDWallet/bip39_vectors.json (100%) rename tests/{ => common}/HashTests.cpp (52%) rename tests/{ => common}/HexCodingTests.cpp (100%) rename tests/{ => common}/Keystore/Data/empty-accounts.json (100%) rename tests/{ => common}/Keystore/Data/ethereum-wallet-address-no-0x.json (100%) rename tests/{ => common}/Keystore/Data/key.json (100%) mode change 100755 => 100644 rename tests/{ => common}/Keystore/Data/key_bitcoin.json (100%) mode change 100755 => 100644 rename tests/{ => common}/Keystore/Data/legacy-mnemonic.json (100%) rename tests/{ => common}/Keystore/Data/legacy-private-key.json (100%) rename tests/{ => common}/Keystore/Data/livepeer.json (100%) rename tests/{ => common}/Keystore/Data/missing-address.json (100%) rename tests/{ => common}/Keystore/Data/myetherwallet.uu (100%) mode change 100755 => 100644 rename tests/{ => common}/Keystore/Data/pbkdf2.json (100%) rename tests/{ => common}/Keystore/Data/wallet.json (100%) mode change 100755 => 100644 rename tests/{ => common}/Keystore/Data/watch.json (100%) rename tests/{ => common}/Keystore/Data/web3j.json (100%) rename tests/{ => common/Keystore}/DerivationPathTests.cpp (51%) create mode 100644 tests/common/Keystore/StoredKeyTests.cpp rename tests/{ => common}/MnemonicTests.cpp (100%) create mode 100644 tests/common/NumericLiteralTests.cpp rename tests/{ => common}/PrivateKeyTests.cpp (71%) rename tests/{ => common}/PublicKeyTests.cpp (57%) rename tests/{interface/TWTestUtilities.cpp => common/TestUtilities.cpp} (84%) rename tests/{interface/TWTestUtilities.h => common/TestUtilities.h} (95%) create mode 100644 tests/common/TransactionCompilerTests.cpp create mode 100644 tests/common/Uint256Tests.cpp rename tests/{ => common}/WalletConsoleTests.cpp (97%) create mode 100644 tests/common/algorithm/erase_tests.cpp create mode 100644 tests/common/algorithm/sort_copy_tests.cpp create mode 100644 tests/common/algorithm/to_array_tests.cpp create mode 100644 tests/common/memory/memzero_tests.cpp create mode 100644 tests/common/operators/equality_comparable_tests.cpp create mode 100644 tests/interface/TWAccountTests.cpp create mode 100644 tests/interface/TWBase32Tests.cpp create mode 100644 tests/interface/TWBase64Tests.cpp create mode 100644 tests/interface/TWCoinTypeTests.cpp create mode 100644 tests/interface/TWDataVectorTests.cpp create mode 100644 tests/interface/TWDerivationPathTests.cpp create mode 100644 tests/interface/TWPBKDF2Tests.cpp create mode 100644 tests/interface/TWTransactionCompilerTests.cpp create mode 100644 tools/build-and-test create mode 100644 tools/dependencies-version create mode 100644 tools/download-dependencies create mode 100644 tools/doxygen_convert_comments delete mode 100644 tools/gtest.patch delete mode 100644 tools/gtest_mock.patch delete mode 100644 tools/gtest_test.patch create mode 100644 tools/install-android-dependencies create mode 100644 tools/install-wasm-dependencies create mode 100644 tools/ios-doc create mode 100644 tools/pvs-studio-analyze create mode 100644 tools/pvs-studio/config.cfg create mode 100644 tools/wasm-build create mode 100644 tools/wasm-set-version create mode 100644 tools/windows-build-and-test.ps1 create mode 100644 tools/windows-dependencies-version.ps1 create mode 100644 tools/windows-download-dependencies.ps1 create mode 100644 tools/windows-doxygen_convert_comments.ps1 create mode 100644 tools/windows-replace-file.pl create mode 100644 tools/windows-replace/.vs/CMake Overview create mode 100644 tools/windows-replace/.vs/ProjectSettings.json create mode 100644 tools/windows-replace/.vs/slnx.sqlite create mode 100644 tools/windows-replace/.vs/windows-replace/v16/Browse.VC.db create mode 100644 tools/windows-replace/.vs/windows-replace/v16/Browse.VC.db-shm create mode 100644 tools/windows-replace/.vs/windows-replace/v16/Browse.VC.db-wal create mode 100644 tools/windows-replace/.vs/windows-replace/v16/Browse.VC.opendb create mode 100644 tools/windows-replace/CMakeLists.txt create mode 100644 tools/windows-replace/cmake/CompilerWarnings.cmake create mode 100644 tools/windows-replace/cmake/Protobuf.cmake create mode 100644 tools/windows-replace/include/TrustWalletCore/TWAnySigner.h create mode 100644 tools/windows-replace/include/TrustWalletCore/TWBase.h create mode 100644 tools/windows-replace/include/TrustWalletCore/TWString.h create mode 100644 tools/windows-replace/powerShell/windows-bootstrap.ps1 create mode 100644 tools/windows-replace/powerShell/windows-build-and-test.ps1 create mode 100644 tools/windows-replace/powerShell/windows-build.ps1 create mode 100644 tools/windows-replace/powerShell/windows-dependencies-version.ps1 create mode 100644 tools/windows-replace/powerShell/windows-dependencies.ps1 create mode 100644 tools/windows-replace/powerShell/windows-download-dependencies.ps1 create mode 100644 tools/windows-replace/powerShell/windows-doxygen_convert_comments.ps1 create mode 100644 tools/windows-replace/powerShell/windows-generate.ps1 create mode 100644 tools/windows-replace/powerShell/windows-replace-file.pl create mode 100644 tools/windows-replace/powerShell/windows-samples.ps1 create mode 100644 tools/windows-replace/powerShell/windows-tests.ps1 create mode 100644 tools/windows-replace/protobuf-plugin/CMakeLists.txt create mode 100644 tools/windows-replace/readme.md create mode 100644 tools/windows-replace/src/Aptos/MoveTypes.cpp create mode 100644 tools/windows-replace/src/Base32.h create mode 100644 tools/windows-replace/src/BinaryCoding.h create mode 100644 tools/windows-replace/src/Ethereum/ABI/ParamFactory.cpp create mode 100644 tools/windows-replace/src/Everscale/Cell.cpp create mode 100644 tools/windows-replace/src/Everscale/Cell.h create mode 100644 tools/windows-replace/src/Everscale/CellSlice.h create mode 100644 tools/windows-replace/src/HDWallet.cpp create mode 100644 tools/windows-replace/src/Keystore/EncryptionParameters.cpp create mode 100644 tools/windows-replace/src/NULS/Address.cpp create mode 100644 tools/windows-replace/src/interface/TWBitcoinScript.cpp create mode 100644 tools/windows-replace/tests/CMakeLists.txt create mode 100644 tools/windows-replace/tests/chains/Aptos/MoveTypesTests.cpp create mode 100644 tools/windows-replace/tests/chains/Bitcoin/MessageSignerTests.cpp create mode 100644 tools/windows-replace/tests/chains/Bitcoin/TestUtilities.h create mode 100644 tools/windows-replace/tests/chains/Cardano/SigningTests.cpp create mode 100644 tools/windows-replace/tests/chains/Cardano/TWCardanoAddressTests.cpp create mode 100644 tools/windows-replace/tests/chains/Polkadot/SignerTests.cpp create mode 100644 tools/windows-replace/tests/chains/Zilliqa/SignerTests.cpp create mode 100644 tools/windows-replace/tests/common/BCSTests.cpp create mode 100644 tools/windows-replace/tests/main.cpp create mode 100644 tools/windows-replace/trezor-crypto/CMakeLists.txt create mode 100644 tools/windows-replace/trezor-crypto/crypto/base32.c create mode 100644 tools/windows-replace/trezor-crypto/crypto/base58.c create mode 100644 tools/windows-replace/trezor-crypto/crypto/bignum.c create mode 100644 tools/windows-replace/trezor-crypto/crypto/blake2b.c create mode 100644 tools/windows-replace/trezor-crypto/crypto/blake2s.c create mode 100644 tools/windows-replace/trezor-crypto/crypto/cardano.c create mode 100644 tools/windows-replace/trezor-crypto/crypto/monero/base58.c create mode 100644 tools/windows-replace/trezor-crypto/crypto/rand.c create mode 100644 tools/windows-replace/trezor-crypto/crypto/sha3.c create mode 100644 tools/windows-replace/trezor-crypto/crypto/shamir.c create mode 100644 tools/windows-replace/trezor-crypto/crypto/tests/CMakeLists.txt create mode 100644 tools/windows-replace/trezor-crypto/crypto/tests/test_check.c create mode 100644 tools/windows-replace/trezor-crypto/include/TrezorCrypto/aes.h create mode 100644 tools/windows-replace/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-donna-portable.h create mode 100644 tools/windows-replace/trezor-crypto/include/TrezorCrypto/endian.h create mode 100644 tools/windows-replace/trezor-crypto/include/TrezorCrypto/groestl_internal.h create mode 100644 tools/windows-replace/trezor-crypto/include/TrezorCrypto/nist256p1.h create mode 100644 tools/windows-replace/trezor-crypto/include/TrezorCrypto/rand.h create mode 100644 tools/windows-replace/walletconsole/CMakeLists.txt create mode 100644 tools/windows-replace/walletconsole/lib/CMakeLists.txt create mode 100644 tools/windows-replace/windows-dragon-king.pl create mode 100644 trezor-crypto/crypto/cardano.c create mode 100644 trezor-crypto/crypto/chacha20poly1305/LICENSE create mode 100644 trezor-crypto/crypto/slip39.c create mode 100644 trezor-crypto/crypto/tests/test_check_zilliqa.h rename trezor-crypto/crypto/{schnorr.c => zilliqa.c} (73%) create mode 100644 trezor-crypto/include/TrezorCrypto/cardano.h create mode 100644 trezor-crypto/include/TrezorCrypto/slip39.h create mode 100644 trezor-crypto/include/TrezorCrypto/slip39_wordlist.h rename trezor-crypto/include/TrezorCrypto/{schnorr.h => zilliqa.h} (77%) create mode 100644 wasm/.gitignore create mode 100644 wasm/.mocharc.json create mode 100644 wasm/CMakeLists.txt create mode 100644 wasm/README.md create mode 100644 wasm/index.ts create mode 100644 wasm/package-lock.json create mode 100644 wasm/package.json create mode 100644 wasm/src/AnySigner.cpp create mode 100644 wasm/src/AnySigner.d.ts create mode 100644 wasm/src/HexCoding.cpp create mode 100644 wasm/src/HexCoding.d.ts create mode 100644 wasm/src/Random.cpp create mode 100644 wasm/src/WasmData.cpp create mode 100644 wasm/src/WasmData.h create mode 100644 wasm/src/WasmString.cpp create mode 100644 wasm/src/WasmString.h create mode 100644 wasm/src/keystore/default-impl.ts create mode 100644 wasm/src/keystore/extension-storage.ts create mode 100644 wasm/src/keystore/fs-storage.ts rename src/Zcash/TAddress.cpp => wasm/src/keystore/index.ts (51%) create mode 100644 wasm/src/keystore/types.ts create mode 100644 wasm/tests/AES.test.ts create mode 100644 wasm/tests/AnyAddress.test.ts create mode 100644 wasm/tests/Base32.test.ts create mode 100644 wasm/tests/Base64.test.ts create mode 100644 wasm/tests/Blockchain/Bitcoin.test.ts create mode 100644 wasm/tests/Blockchain/Ethereum.test.ts create mode 100644 wasm/tests/CoinType.test.ts create mode 100644 wasm/tests/HDWallet.test.ts create mode 100644 wasm/tests/HRP.test.ts create mode 100644 wasm/tests/Hash.test.ts create mode 100644 wasm/tests/HexCoding.test.ts create mode 100644 wasm/tests/KeyStore+extension.test.ts create mode 100644 wasm/tests/KeyStore+fs.test.ts create mode 100644 wasm/tests/Mnemonic.test.ts create mode 100644 wasm/tests/PBKDF2.test.ts create mode 100644 wasm/tests/StoredKey.test.ts create mode 100644 wasm/tests/initWasm.test.ts create mode 100644 wasm/tests/mock.ts create mode 100644 wasm/tests/setup.test.ts create mode 100644 wasm/tsconfig.json create mode 100644 windows-bootstrap.ps1 diff --git a/.clang-format b/.clang-format index a8054814d54..c3a9fa31eb5 100644 --- a/.clang-format +++ b/.clang-format @@ -3,9 +3,11 @@ AllowShortFunctionsOnASingleLine: Inline AlwaysBreakTemplateDeclarations: Yes BasedOnStyle: LLVM BreakConstructorInitializers: BeforeComma -ColumnLimit: 100 +ColumnLimit: 0 ConstructorInitializerAllOnOneLineOrOnePerLine: true IndentWidth: 4 +IndentAccessModifiers: false +AccessModifierOffset: -4 PointerAlignment: Left IncludeCategories: - Regex: '^"\.\./' diff --git a/.gitignore b/.gitignore index db676afddf5..707a2517dee 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ cmake-build-debug/ .cquery_cache/ .cxx/ .cache/ +build_pvs_studio/ # Dependencies node_modules @@ -26,11 +27,18 @@ lib/protobuf # Generated files jni/cpp/generated jni/java/wallet/core/jni +jni/java/wallet/core/proto swift/Sources/Generated swift/wallet-core/ -src/Generated +src/Generated/*.cpp include/TrustWalletCore/TWHRP.h include/TrustWalletCore/TW*Proto.h +include/TrustWalletCore/TWDerivation.h +include/TrustWalletCore/TWEthereumChainID.h + +# Wasm +emsdk/ +wasm-build/ # Code coverage files coverage.info diff --git a/CMakeLists.txt b/CMakeLists.txt index 7434634139b..0886e36252f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,21 +1,12 @@ -cmake_minimum_required(VERSION 3.8 FATAL_ERROR) +# 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. -project(TrustWalletCore) - -include(GNUInstallDirs) +cmake_minimum_required(VERSION 3.18 FATAL_ERROR) -# Configure warnings -if(NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "MSVC")) - set(TW_CXX_WARNINGS "-Wshorten-64-to-32") -endif() -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TW_CXX_WARNINGS}") -set(CMAKE_EXPORT_COMPILE_COMMANDS 1) -set(CMAKE_POSITION_INDEPENDENT_CODE ON) -set(CMAKE_CXX_VISIBILITY_PRESET hidden) - -set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14" CACHE STRING "Minimum OS X deployment version" FORCE) +project(TrustWalletCore) if((NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")) AND (NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "MSVC"))) message(FATAL_ERROR "You should use clang or msvc compiler") @@ -23,35 +14,30 @@ endif() if ("$ENV{PREFIX}" STREQUAL "") set(PREFIX "${CMAKE_SOURCE_DIR}/build/local") -else() +else () set(PREFIX "$ENV{PREFIX}") -endif() +endif () -# Configure CCache if available -find_program(CCACHE_FOUND ccache) -if(CCACHE_FOUND) - set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) - set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) -endif(CCACHE_FOUND) +include(GNUInstallDirs) +include(cmake/StandardSettings.cmake) +include(cmake/CompilerWarnings.cmake) +include(cmake/StaticAnalyzers.cmake) +include(cmake/FindHostPackage.cmake) -include_directories(${PREFIX}/include) -link_directories(${PREFIX}/lib) +add_library(${PROJECT_NAME}_INTERFACE INTERFACE) +target_include_directories(${PROJECT_NAME}_INTERFACE INTERFACE ${PREFIX}/include) +target_link_directories(${PROJECT_NAME}_INTERFACE INTERFACE ${PREFIX}/lib) +set_project_warnings(${PROJECT_NAME}_INTERFACE) if(WIN32) add_definitions(-D_CRT_SECURE_NO_WARNINGS) # Disable strcpy warnings endif() - add_subdirectory(trezor-crypto) -macro(find_host_package) - set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) - set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER) - set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER) - find_package(${ARGN}) - set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY) - set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) - set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) -endmacro(find_host_package) +if (TW_COMPILE_WASM) + message(STATUS "Wasm build enabled") + add_subdirectory(wasm) +endif () find_host_package(Boost REQUIRED) @@ -75,41 +61,16 @@ if(WIN32) endif() endif() -option(CODE_COVERAGE "Enable coverage reporting" OFF) -if(CODE_COVERAGE AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -fprofile-arcs -ftest-coverage") - set(CMAKE_EXE_LINKER_FLAGS "-fprofile-arcs -ftest-coverage") -endif() -option(CLANG_TIDY "Enable static code analysis with (clang-tidy)" OFF) -if(CLANG_TIDY) - find_program(CLANG_TIDY_BIN NAMES clang-tidy-11) - if(CLANG_TIDY_BIN) - set(CMAKE_CXX_CLANG_TIDY clang-tidy-11;) - message("clang-tidy ${CMAKE_CXX_CLANG_TIDY} ${CLANG_TIDY_BIN}") - else() - message(FATAL_ERROR "Could not find clang-tidy") - endif() -endif() -option(CLANG_ASAN "Enable ASAN dynamic address sanitizer" OFF) -if(CLANG_ASAN) - # https://clang.llvm.org/docs/AddressSanitizer.html - # https://github.com/trustwallet/wallet-core/issues/1170 - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fno-omit-frame-pointer") - set(CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fsanitize=address -fno-omit-frame-pointer") - message("CLANG_ASAN on, ${CMAKE_CXX_FLAGS_DEBUG}") -endif() - -# Source files -if(${ANDROID}) +# Source files ** +if (${ANDROID}) message("Configuring for JNI") file(GLOB_RECURSE sources src/*.c src/*.cc src/*.cpp src/*.h jni/cpp/*.c jni/cpp/*.cpp jni/cpp/*.h jni/cpp/*.c) add_library(TrustWalletCore SHARED ${sources} ${PROTO_SRCS} ${PROTO_HDRS}) - find_library(log-lib log) - target_link_libraries(TrustWalletCore PRIVATE TrezorCrypto ${Protobuf_LIBRARIES} ${log-lib} Boost::boost) -else() + target_link_libraries(TrustWalletCore PUBLIC ${PROJECT_NAME}_INTERFACE PRIVATE TrezorCrypto ${Protobuf_LIBRARIES} ${log-lib} Boost::boost) +else () file(GLOB_RECURSE sources src/*.c src/*.cc src/*.cpp src/*.h) if(WIN32) file(GLOB_RECURSE headers include/TrustWalletCore/*.h) @@ -121,39 +82,62 @@ else() add_library(TrustWalletCore SHARED ${sources} ${headers} ${PROTO_SRCS} ${PROTO_HDRS}) target_compile_definitions(TrustWalletCore PRIVATE TW_EXPORT_LIBRARY) endif() - else() + else() message("Configuring standalone") add_library(TrustWalletCore ${sources} ${PROTO_SRCS} ${PROTO_HDRS}) endif() - target_link_libraries(TrustWalletCore PRIVATE TrezorCrypto ${Protobuf_LIBRARIES} Boost::boost) -endif() +target_link_libraries(TrustWalletCore PUBLIC ${PROJECT_NAME}_INTERFACE PRIVATE TrezorCrypto ${Protobuf_LIBRARIES} Boost::boost) +endif () + +if (TW_CODE_COVERAGE AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + target_enable_coverage(TrustWalletCore) +endif () + if(NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "MSVC")) target_compile_options(TrustWalletCore PRIVATE "-Wall") endif() -set_target_properties(TrustWalletCore - PROPERTIES - CXX_STANDARD 17 - CXX_STANDARD_REQUIRED ON -) +if (TW_CLANG_ASAN) + target_enable_asan(TrustWalletCore) +endif () # Define headers for this library. PUBLIC headers are used for compiling the # library, and will be added to consumers' build paths. target_include_directories(TrustWalletCore - PUBLIC + PUBLIC $ $ - PRIVATE + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}/jni/cpp ${CMAKE_CURRENT_SOURCE_DIR}/build/local/include -) + ) -if(NOT ANDROID AND NOT IOS_PLATFORM AND NOT (WIN32 AND NOT TW_STATIC_LIBRARY)) +if (TW_UNIT_TESTS AND NOT (WIN32 AND NOT TW_STATIC_LIBRARY)) add_subdirectory(tests) +endif () + +if (TW_BUILD_EXAMPLES AND NOT (WIN32 AND NOT TW_STATIC_LIBRARY)) add_subdirectory(walletconsole/lib) add_subdirectory(walletconsole) +endif () + +if (TW_ENABLE_PVS_STUDIO) + tw_add_pvs_studio_target(TrustWalletCore) +endif () + +if (TW_ENABLE_CLANG_TIDY) + tw_add_clang_tidy_target(TrustWalletCore) +endif () + +if (NOT ANDROID AND TW_UNITY_BUILD) + set_target_properties(TrustWalletCore PROPERTIES UNITY_BUILD ON) + file(GLOB_RECURSE PROTOBUF_SOURCE_FILES CONFIGURE_DEPENDS src/Cosmos/Protobuf/*.pb.cc src/proto/*.pb.cc) + foreach(file ${PROTOBUF_SOURCE_FILES}) + set_property(SOURCE ${file} PROPERTY SKIP_UNITY_BUILD_INCLUSION ON) + endforeach() + message(STATUS "Unity build activated") endif() configure_file(${CMAKE_CURRENT_SOURCE_DIR}/swift/cpp.xcconfig.in ${CMAKE_CURRENT_SOURCE_DIR}/swift/cpp.xcconfig @ONLY) @@ -167,8 +151,10 @@ install(TARGETS TrustWalletCore ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} ) -install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/WalletCore - FILES_MATCHING PATTERN "*.h") +install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/WalletCore + FILES_MATCHING PATTERN "*.h" +) install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) - diff --git a/Dockerfile b/Dockerfile index d78df6195dc..fae3ef89269 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:20.04 +FROM ubuntu:22.04 ENV DEBIAN_FRONTEND=noninteractive @@ -17,9 +17,10 @@ RUN apt-get update \ # Add latest cmake/boost SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc | apt-key add - \ - && apt-add-repository 'deb https://apt.kitware.com/ubuntu/ bionic main' \ - && apt-add-repository -y ppa:mhier/libboost-latest + && apt-add-repository 'deb https://apt.kitware.com/ubuntu/ bionic main' + +RUN wget http://archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.0g-2ubuntu4_amd64.deb && dpkg -i ./libssl1.1_1.1.0g-2ubuntu4_amd64.deb # Install required packages for dev RUN apt-get update \ && apt-get install -y \ @@ -27,16 +28,16 @@ RUN apt-get update \ libtool autoconf pkg-config \ ninja-build \ ruby-full \ - clang-10 \ - llvm-10 \ + clang-14 \ + llvm-14 \ libc++-dev libc++abi-dev \ - cmake \ - libboost1.74-dev \ + cmake \ + libboost-all-dev \ ccache \ && apt-get clean && rm -rf /var/lib/apt/lists/* -ENV CC=/usr/bin/clang-10 -ENV CXX=/usr/bin/clang++-10 +ENV CC=/usr/bin/clang-14 +ENV CXX=/usr/bin/clang++-14 # ↑ Setup build environment # ↓ Build and compile wallet core @@ -47,9 +48,25 @@ WORKDIR /wallet-core # Install dependencies RUN tools/install-dependencies -# Build: generate, cmake, and make +# Build: generate, cmake, and make lib RUN tools/generate-files \ && cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug \ - && make -Cbuild -j12 + && make -Cbuild -j12 TrustWalletCore + +# Build unit tester +RUN make -Cbuild -j12 tests + +# Download and Install Go +ENV GO_VERSION=1.16.12 +ENV GO_ARCH=amd64 +RUN wget "https://golang.org/dl/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz" \ + && tar -xf "go${GO_VERSION}.linux-${GO_ARCH}.tar.gz" \ + && chown -R root:root ./go \ + && mv -v ./go /usr/local \ + && ls /usr/local/go \ + && /usr/local/go/bin/go version \ + && rm "go${GO_VERSION}.linux-${GO_ARCH}.tar.gz" + +# Building GoLang sample app: cd samples/go && /usr/local/go/bin/go build -o main && ./main CMD ["/bin/bash"] diff --git a/Package.swift b/Package.swift index 114042ae861..7f6216bde07 100644 --- a/Package.swift +++ b/Package.swift @@ -1,27 +1,24 @@ // swift-tools-version:5.3 - import PackageDescription let package = Package( name: "WalletCore", platforms: [.iOS(.v13)], products: [ - .library( - name: "WalletCore", targets: ["WalletCore", "SwiftProtobuf"] - ) - ], - dependencies: [ + .library(name: "WalletCore", targets: ["WalletCore"]), + .library(name: "SwiftProtobuf", targets: ["SwiftProtobuf"]) ], + dependencies: [], targets: [ .binaryTarget( name: "WalletCore", - url: "https://github.com/trustwallet/wallet-core/releases/download/2.6.31/WalletCore.xcframework.zip", - checksum: "29d88807485f88992e00a5b7ed1ddf53ca57dacb89ba5e5368dc931102600879" + url: "https://github.com/trustwallet/wallet-core/releases/download/3.0.6/WalletCore.xcframework.zip", + checksum: "a3df0c2b30fc59ede0a2600266fc19b8c0cf655dbef3fb832488c8ddedcb6b93" ), .binaryTarget( name: "SwiftProtobuf", - url: "https://github.com/trustwallet/wallet-core/releases/download/2.6.31/SwiftProtobuf.xcframework.zip", - checksum: "d1035aa8a32f2483305bbe9cd3d5774bf6c1a65ce5a37213c9ee651c78873a55" + url: "https://github.com/trustwallet/wallet-core/releases/download/3.0.6/SwiftProtobuf.xcframework.zip", + checksum: "61fa8483d4bd43f1898db6997eff0279426f15f9e518e12db0d762ec5f927a9b" ) ] ) diff --git a/README.md b/README.md index 1065ca92e6b..36b798cb3cd 100644 --- a/README.md +++ b/README.md @@ -4,18 +4,19 @@ Trust Wallet Core is an open source, cross-platform, mobile-focused library implementing low-level cryptographic wallet functionality for a high number of blockchains. It is a core part of the popular [Trust Wallet](https://trustwallet.com), and some other projects. Most of the code is C++ with a set of strict C interfaces, and idiomatic interfaces for supported languages: -Swift for iOS and Java for Android. +Swift for iOS and Java (Kotlin) for Android. ![iOS CI](https://github.com/trustwallet/wallet-core/workflows/iOS%20CI/badge.svg) ![Android CI](https://github.com/trustwallet/wallet-core/workflows/Android%20CI/badge.svg) ![Linux CI](https://github.com/trustwallet/wallet-core/workflows/Linux%20CI/badge.svg) +![Wasm CI](https://github.com/trustwallet/wallet-core/workflows/Wasm%20CI/badge.svg) ![Docker CI](https://github.com/trustwallet/wallet-core/workflows/Docker%20CI/badge.svg) [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/trustwallet/wallet-core) ![GitHub](https://img.shields.io/github/license/TrustWallet/wallet-core.svg) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/trustwallet/wallet-core) +![SPM](https://img.shields.io/badge/SPM-ready-blue) ![Cocoapods](https://img.shields.io/cocoapods/v/TrustWalletCore.svg) -![Cocoapods platforms](https://img.shields.io/cocoapods/p/TrustWalletCore.svg) # Documentation @@ -23,7 +24,7 @@ For comprehensive documentation, see [developer.trustwallet.com](https://develop # Supported Blockchains -Wallet Core supports more than **50** blockchains: Bitcoin, Ethereum, Binance Chain, and most major blockchain platforms. +Wallet Core supports more than **60** blockchains: Bitcoin, Ethereum, BNB, Cosmos, Solana, and most major blockchain platforms. The full list is [here](docs/registry.md). # Building @@ -37,33 +38,59 @@ If you want to use wallet core in your project follow these instructions. ## Android -Future Android releases will be hosted on [GitHub packages](https://github.com/trustwallet/wallet-core/packages/700258), please checkout [this guide](https://docs.github.com/en/packages/guides/configuring-gradle-for-use-with-github-packages#installing-a-package) for more details. +Android releases are hosted on [GitHub packages](https://github.com/trustwallet/wallet-core/packages/700258), you need to add GitHub access token to install it. Please checkout [this installation guide](https://developer.trustwallet.com/wallet-core/integration-guide/android-guide#adding-library-dependency) or `build.gradle` from our [android sample](https://github.com/trustwallet/wallet-core/blob/master/samples/android/build.gradle) -Add this dependency to build.gradle and run `gradle install` +Don't forget replacing the version in the code with latest: ![GitHub release (latest by date)](https://img.shields.io/github/v/release/trustwallet/wallet-core) -```groovy -plugins { - id 'maven' -} +## iOS + +We currently support Swift Package Manager and CocoaPods (will discontinue in the future). + +### SPM -dependencies { - implementation 'com.trustwallet:wallet-core:x.y.z' -} +Download latest `Package.swift` from [GitHub Releases](https://github.com/trustwallet/wallet-core/releases) and put it in a local `WalletCore` folder. + +Add this line to the `dependencies` parameter in your `Package.swift`: + +```swift +.package(name: "WalletCore", path: "../WalletCore"), ``` -Replace x.y.z with latest version: -![GitHub release (latest by date)](https://img.shields.io/github/v/release/trustwallet/wallet-core) -## iOS +Or add remote url + `master` branch, it points to recent (not always latest) binary release. + +```swift +.package(name: "WalletCore", url: "https://github.com/trustwallet/wallet-core", .branchItem("master")), +``` -We currently support only CocoaPods. Add this line to your Podfile and run `pod install`: +Then add libraries to target's `dependencies`: + +```swift +.product(name: "WalletCore", package: "WalletCore"), +.product(name: "SwiftProtobuf", package: "WalletCore"), +``` + +### CocoaPods + +Add this line to your Podfile and run `pod install`: ```ruby pod 'TrustWalletCore' ``` +## NPM (beta) + +```js +npm install @trustwallet/wallet-core +``` + +## Go (beta) + +Please check out the [Go integration sample](https://github.com/trustwallet/wallet-core/tree/master/samples/go). + + # Projects -Projects using Trust Wallet Core. Add yours too! +Projects using Trust Wallet Core. Add yours too! [Trust Wallet](https://trustwallet.com) @@ -72,7 +99,15 @@ Projects using Trust Wallet Core. Add yours too! | [crypto.com](https://crypto.com) | [Alice](https://www.alicedapp.com/) | [Frontier](https://frontier.xyz/) +| [Tokenary](https://tokenary.io/) + +# Community + +There are a few community-maintained projects that extend Wallet Core to some additional platforms and languages. Note this is not an endorsement, please do your own research before using them: +- Flutter binding https://github.com/weishirongzhen/flutter_trust_wallet_core +- Python binding https://github.com/phuang/wallet-core-python +- Wallet Core on Windows https://github.com/kaetemi/wallet-core-windows # Contributing diff --git a/WalletCore.podspec b/WalletCore.podspec index 828e43ca136..42fe7431537 100644 --- a/WalletCore.podspec +++ b/WalletCore.podspec @@ -29,7 +29,7 @@ Pod::Spec.new do |s| end s.subspec 'Core' do |ss| - protobuf_source_dir = 'build/local/src/protobuf/protobuf-3.14.0' + protobuf_source_dir = 'build/local/src/protobuf/protobuf-3.19.2' include_dir = 'build/local/include' ss.source_files = 'src/**/*.{c,cc,cpp,h}', @@ -57,11 +57,15 @@ Pod::Spec.new do |s| "#{protobuf_source_dir}/src/google/protobuf/extension_set_heavy.cc", "#{protobuf_source_dir}/src/google/protobuf/field_mask.pb.cc", "#{protobuf_source_dir}/src/google/protobuf/generated_enum_util.cc", + "#{protobuf_source_dir}/src/google/protobuf/generated_message_bases.cc", "#{protobuf_source_dir}/src/google/protobuf/generated_message_reflection.cc", "#{protobuf_source_dir}/src/google/protobuf/generated_message_table_driven.cc", "#{protobuf_source_dir}/src/google/protobuf/generated_message_table_driven_lite.cc", + "#{protobuf_source_dir}/src/google/protobuf/generated_message_tctable_full.cc", + "#{protobuf_source_dir}/src/google/protobuf/generated_message_tctable_lite.cc", "#{protobuf_source_dir}/src/google/protobuf/generated_message_util.cc", "#{protobuf_source_dir}/src/google/protobuf/implicit_weak_message.cc", + "#{protobuf_source_dir}/src/google/protobuf/inlined_string_field.cc", "#{protobuf_source_dir}/src/google/protobuf/io/coded_stream.cc", "#{protobuf_source_dir}/src/google/protobuf/io/gzip_stream.cc", "#{protobuf_source_dir}/src/google/protobuf/io/io_win32.cc", @@ -78,6 +82,7 @@ Pod::Spec.new do |s| "#{protobuf_source_dir}/src/google/protobuf/parse_context.cc", "#{protobuf_source_dir}/src/google/protobuf/reflection_ops.cc", "#{protobuf_source_dir}/src/google/protobuf/repeated_field.cc", + "#{protobuf_source_dir}/src/google/protobuf/repeated_ptr_field.cc", "#{protobuf_source_dir}/src/google/protobuf/service.cc", "#{protobuf_source_dir}/src/google/protobuf/source_context.pb.cc", "#{protobuf_source_dir}/src/google/protobuf/struct.pb.cc", @@ -111,7 +116,6 @@ Pod::Spec.new do |s| "#{protobuf_source_dir}/src/google/protobuf/util/internal/protostream_objectsource.cc", "#{protobuf_source_dir}/src/google/protobuf/util/internal/protostream_objectwriter.cc", "#{protobuf_source_dir}/src/google/protobuf/util/internal/type_info.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/type_info_test_helper.cc", "#{protobuf_source_dir}/src/google/protobuf/util/internal/utility.cc", "#{protobuf_source_dir}/src/google/protobuf/util/json_util.cc", "#{protobuf_source_dir}/src/google/protobuf/util/message_differencer.cc", @@ -120,6 +124,7 @@ Pod::Spec.new do |s| "#{protobuf_source_dir}/src/google/protobuf/wire_format.cc", "#{protobuf_source_dir}/src/google/protobuf/wire_format_lite.cc", "#{protobuf_source_dir}/src/google/protobuf/wrappers.pb.cc" + ss.exclude_files = 'trezor-crypto/include/TrezorCrypto/base58.h', 'trezor-crypto/crypto/monero', diff --git a/android/app/build.gradle b/android/app/build.gradle index 22d53172334..f38c9acc235 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -2,12 +2,11 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' android { - compileSdkVersion 30 - ndkVersion '21.2.6472646' + compileSdkVersion 32 + ndkVersion '23.1.7779620' defaultConfig { applicationId "com.trustwallet.core.app" minSdkVersion 23 - targetSdkVersion 30 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -21,7 +20,7 @@ android { minifyEnabled false // limit platforms built for testing ndk { - abiFilters 'x86' + abiFilters 'x86', 'arm64-v8a' } } } @@ -43,7 +42,7 @@ dependencies { androidTestImplementation 'androidx.test:runner:1.3.0' androidTestImplementation 'android.arch.core:core-testing:1.1.1' - implementation 'io.grpc:grpc-protobuf:1.34.0' + implementation 'com.google.protobuf:protobuf-javalite:3.21.2' } repositories { mavenCentral() 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 87dd5ae2cc9..98f69f55d1b 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 @@ -40,8 +40,10 @@ class CoinAddressDerivationTests { CALLISTO -> assertEquals("0x3E6FFC80745E6669135a76F4A7ce6BCF02436e04", address) DASH -> assertEquals("XqHiz8EXYbTAtBEYs4pWTHh7ipEDQcNQeT", address) DIGIBYTE -> assertEquals("dgb1qtjgmerfqwdffyf8ghcrkgy52cghsqptynmyswu", address) - ETHEREUM, SMARTCHAIN, POLYGON, OPTIMISM, ARBITRUM, ECOCHAIN, AVALANCHECCHAIN, XDAI, - FANTOM, RONIN -> assertEquals("0x8f348F300873Fd5DA36950B2aC75a26584584feE", address) + ETHEREUM, SMARTCHAIN, POLYGON, OPTIMISM, ZKSYNC, ARBITRUM, ECOCHAIN, AVALANCHECCHAIN, XDAI, + FANTOM, CELO, CRONOSCHAIN, SMARTBITCOINCASH, KUCOINCOMMUNITYCHAIN, BOBA, METIS, + AURORA, EVMOS, MOONRIVER, MOONBEAM, KAVAEVM, KLAYTN, METER, OKXCHAIN -> assertEquals("0x8f348F300873Fd5DA36950B2aC75a26584584feE", address) + RONIN -> assertEquals("ronin:8f348F300873Fd5DA36950B2aC75a26584584feE", address) ETHEREUMCLASSIC -> assertEquals("0x078bA3228F3E6C08bEEac9A005de0b7e7089aD1c", address) GOCHAIN -> assertEquals("0x5940ce4A14210d4Ccd0ac206CE92F21828016aC2", address) GROESTLCOIN -> assertEquals("grs1qexwmshts5pdpeqglkl39zyl6693tmfwp0cue4j", address) @@ -57,7 +59,7 @@ class CoinAddressDerivationTests { VECHAIN -> assertEquals("0x1a553275dF34195eAf23942CB7328AcF9d48c160", address) WANCHAIN -> assertEquals("0xD5ca90b928279FE5D06144136a25DeD90127aC15", address) ZCASH -> assertEquals("t1YYnByMzdGhQv3W3rnjHMrJs6HH4Y231gy", address) - ZCOIN -> assertEquals("aEd5XFChyXobvEics2ppAqgK3Bgusjxtik", address) + FIRO -> assertEquals("aEd5XFChyXobvEics2ppAqgK3Bgusjxtik", address) NIMIQ -> assertEquals("NQ76 7AVR EHDA N05U X7SY XB14 XJU7 8ERV GM6H", address) STELLAR -> assertEquals("GA3H6I4C5XUBYGVB66KXR27JV5KS3APSTKRUWOIXZ5MVWZKVTLXWKZ2P", address) AION -> assertEquals("0xa0629f34c9ea4757ad0b275628d4d02e3db6c9009ba2ceeba76a5b55fb2ca42e", address) @@ -79,7 +81,7 @@ class CoinAddressDerivationTests { RAVENCOIN -> assertEquals("RHoCwPc2FCQqwToYnSiAb3SrCET4zEHsbS", address) WAVES -> assertEquals("3P63vkaHhyE9pPv9EfsjwGKqmZYcCRHys4n", address) AETERNITY -> assertEquals("ak_QDHJSfvHG9sDHBobaWt2TAGhuhipYjEqZEH34bWugpJfJc3GN", address) - TERRA -> assertEquals("terra1rh402g98t7sly8trzqw5cyracntlep6qe3smug", address) + TERRA, TERRAV2 -> assertEquals("terra1rh402g98t7sly8trzqw5cyracntlep6qe3smug", address) MONACOIN -> assertEquals("M9xFZzZdZhCDxpx42cM8bQHnLwaeX1aNja", address) FIO -> assertEquals("FIO7MN1LuSfFgrbVHmrt9cVa2FYAs857Ppr9dzvEXoD1miKSxm3n3", address) HARMONY -> assertEquals("one12fk20wmvgypdkn59n4hq8e3aa5899xfx4vsu09", address) @@ -88,7 +90,7 @@ class CoinAddressDerivationTests { KUSAMA -> assertEquals("G9xV2EatmrjRC1FLPexc3ddqNRRzCsAdURU8RFiAAJX6ppY", address) POLKADOT -> assertEquals("13nN6BGAoJwd7Nw1XxeBCx5YcBXuYnL94Mh7i3xBprqVSsFk", address) KAVA -> assertEquals("kava1drpa0x9ptz0fql3frv562rcrhj2nstuz3pas87", address) - CARDANO -> assertEquals("addr1snpa4z7ntyfszv7ckquprdw75w4qjqh0qmya9jtkpxxlzxghlqyvv7l0yjamh8fxraw06p3ua8sj2g2gv98v4849s43t9g2999kquuu5egnprk", address) + CARDANO -> assertEquals("addr1qyr8jjfnypp95eq74aqzn7ss687ehxclgj7mu6gratmg3mul2040vt35dypp042awzsjk5xm3zr3zm5qh7454uwdv08s84ray2", address) NEO -> assertEquals("AT6w7PJvwPcSqHvtbNBY2aHPDv12eW5Uuf", address) FILECOIN -> assertEquals("f1zzykebxldfcakj5wdb5n3n7priul522fnmjzori", address) ELROND -> assertEquals("erd1jfcy8aeru6vlx4fe6h3pc3vlpe2cnnur5zetxdhp879yagq7vqvs8na4f8", address) @@ -98,6 +100,11 @@ class CoinAddressDerivationTests { THORCHAIN -> assertEquals("thor1c8jd7ad9pcw4k3wkuqlkz4auv95mldr2kyhc65", address) BLUZELLE -> assertEquals("bluzelle1xccvees6ev4wm2r49rc6ptulsdxa8x8jfpmund", address) CRYPTOORG -> assertEquals("cro16fdf785ejm00jf9a24d23pzqzjh2h05klxjwu8", address) - CELO -> assertEquals("0xea1ac53e7Ccb5b47cdE341C118615Ef1862e3CF5", address) + OSMOSIS -> assertEquals("osmo142j9u5eaduzd7faumygud6ruhdwme98qclefqp", address) + ECASH -> assertEquals("ecash:qpelrdn7a0hcucjlf9ascz3lkxv7r3rffgzn6x5377", address) + NATIVEEVMOS -> assertEquals("evmos13u6g7vqgw074mgmf2ze2cadzvkz9snlwstd20d", address) + NERVOS -> assertEquals("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02wectaumxn0664yw2jd53lqk4mxg3", address) + EVERSCALE -> assertEquals("0:0c39661089f86ec5926ea7d4ee4223d634ba4ed6dcc2e80c7b6a8e6d59f79b04", address) + APTOS -> assertEquals("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", address) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestCoinType.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestCoinType.kt index 7d4fe6bda0a..f8fc2f3a5bf 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestCoinType.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestCoinType.kt @@ -3,14 +3,16 @@ package com.trustwallet.core.app.blockchains import wallet.core.jni.CoinType import wallet.core.jni.Curve import wallet.core.jni.Purpose +import wallet.core.jni.Derivation + import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse import org.junit.Test + class TestCoinType { init { - System.loadLibrary("TrustWalletCore"); + System.loadLibrary("TrustWalletCore") } @Test @@ -43,4 +45,14 @@ class TestCoinType { fun testCoinCurve() { assertEquals(Curve.SECP256K1, CoinType.BITCOIN.curve()) } + + @Test + fun testDerivationPath() { + var res = CoinType.createFromValue(CoinType.BITCOIN.value()).derivationPath().toString() + assertEquals(res, "m/84'/0'/0'/0/0") + res = CoinType.createFromValue(CoinType.BITCOIN.value()).derivationPathWithDerivation(Derivation.BITCOINLEGACY).toString() + assertEquals(res, "m/44'/0'/0'/0/0") + res = CoinType.createFromValue(CoinType.SOLANA.value()).derivationPathWithDerivation(Derivation.SOLANASOLANA).toString() + assertEquals(res, "m/44'/501'/0'/0'") + } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/aptos/TestAptosAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/aptos/TestAptosAddress.kt new file mode 100644 index 00000000000..180617ff5b1 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/aptos/TestAptosAddress.kt @@ -0,0 +1,28 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +package com.trustwallet.core.app.blockchains.aptos + +import org.junit.Assert.assertFalse +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestAptosAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val any = AnyAddress("0x6af7d07b8a541913dfa87a9f99628faa255c70241ef9ebd9b82a7e715ee13108", CoinType.APTOS) + assertEquals(any.coin(), CoinType.APTOS) + assertEquals(any.description(), "0x6af7d07b8a541913dfa87a9f99628faa255c70241ef9ebd9b82a7e715ee13108") + + assertFalse(AnyAddress.isValid("0xMQqpqMQgCBuiPkoXfgZZsJvuzCeI1zc00z6vHJj4", CoinType.APTOS)) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/aptos/TestAptosSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/aptos/TestAptosSigner.kt new file mode 100644 index 00000000000..6d52a4c9d1d --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/aptos/TestAptosSigner.kt @@ -0,0 +1,58 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +package com.trustwallet.core.app.blockchains.aptos + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHexByteArray +import com.trustwallet.core.app.utils.toHexBytes +import com.trustwallet.core.app.utils.toHexBytesInByteString +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.CoinType +import wallet.core.jni.proto.Aptos + +class TestAptosSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun AptosTransactionSigning() { + // Successfully broadcasted https://explorer.aptoslabs.com/txn/0xb4d62afd3862116e060dd6ad9848ccb50c2bc177799819f1d29c059ae2042467?network=devnet + val key = + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec".toHexBytesInByteString() + + val transfer = Aptos.TransferMessage.newBuilder().setAmount(1000) + .setTo("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30").build() + val signingInput = Aptos.SigningInput.newBuilder().setChainId(33) + .setSender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30") + .setSequenceNumber(99) + .setGasUnitPrice(100) + .setMaxGasAmount(3296766) + .setExpirationTimestampSecs(3664390082) + .setTransfer(transfer) + .setPrivateKey(key) + .build() + + val result = AnySigner.sign(signingInput, CoinType.APTOS, Aptos.SigningOutput.parser()) + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.rawTxn.toByteArray())), + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada0000000021" + ) + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.authenticator.signature.toByteArray())), + "5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01" + ) + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.encoded.toByteArray())), + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c405707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01" + ) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainSigner.kt index 0e25638c1de..662773bba9c 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainSigner.kt @@ -31,7 +31,7 @@ class TestBandChainSigner { val from = AnyAddress(publicKey, BANDCHAIN).description() val txAmount = Cosmos.Amount.newBuilder().apply { - amount = 1 + amount = "1" denom = "uband" }.build() @@ -46,7 +46,7 @@ class TestBandChainSigner { }.build() val feeAmount = Cosmos.Amount.newBuilder().apply { - amount = 200 + amount = "200" denom = "uband" }.build() diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bluzelle/TestBluzelleSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bluzelle/TestBluzelleSigner.kt index 033ee1eb707..ba4d7e99238 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bluzelle/TestBluzelleSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bluzelle/TestBluzelleSigner.kt @@ -36,7 +36,7 @@ class TestBluzelleSigner { val from = AnyAddress(publicKey, BLUZELLE).description() val txAmount = Cosmos.Amount.newBuilder().apply { - amount = 1 + amount = "1" denom = "ubnt" }.build() @@ -51,7 +51,7 @@ class TestBluzelleSigner { }.build() val feeAmount = Cosmos.Amount.newBuilder().apply { - amount = 1000 + amount = "1000" denom = "ubnt" }.build() diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoAddress.kt index c71dbe93ab6..6ac0f1c4d63 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoAddress.kt @@ -20,12 +20,12 @@ class TestCardanoAddress { @Test fun testAddress() { - val key = PrivateKey("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71effbf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4".toHexByteArray()) - val pubkey = key.publicKeyEd25519Extended + val key = PrivateKey("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71effbf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4639aadd8b6499ae39b78018b79255fbd8f585cbda9cbb9e907a72af86afb7a05d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7bed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a".toHexByteArray()) + val pubkey = key.publicKeyEd25519Cardano val address = AnyAddress(pubkey, CoinType.CARDANO) - val expected = AnyAddress("addr1s3tl64970vuthz2j0qkz7kd2ya5j3fxuhdnv333vu38e6c37e4dq80ek4raf7hs3adag2tzpuxz7895a2x8xde5f8jqa8lrjyuqfj5k50pm668", CoinType.CARDANO) + val expected = AnyAddress("addr1qx4z6twzknkkux0hhp0kq6hvdfutczp56g56y5em8r8mgvxalp7nkkk25vuspleke2zltaetmlwrfxv7t049cq9jmwjswmfw6t", CoinType.CARDANO) - assertEquals(pubkey.data().toHex(), "0x57fd54be7b38bb8952782c2f59aa276928a4dcbb66c8c62ce44f9d623ecd5a03bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4") + assertEquals(pubkey.data().toHex(), "0x57fd54be7b38bb8952782c2f59aa276928a4dcbb66c8c62ce44f9d623ecd5a03bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d483e4b09a4ba73725625c346870e109bd335831f2ba0b72b09bfb44040f1016caed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a") assertEquals(address.description(), expected.description()) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoSigning.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoSigning.kt new file mode 100644 index 00000000000..e76aa67b785 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoSigning.kt @@ -0,0 +1,264 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +package com.trustwallet.core.app.blockchains.cardano + +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.toHexBytes +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.* +import wallet.core.jni.Cardano.getStakingAddress +import wallet.core.jni.CoinType.CARDANO +import wallet.core.jni.proto.Cardano +import wallet.core.jni.proto.Cardano.SigningInput +import wallet.core.jni.proto.Cardano.SigningOutput +import wallet.core.jni.proto.Common.SigningError + + +class TestCardanoSigning { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testSignTransfer1() { + val message = Cardano.Transfer.newBuilder() + .setToAddress("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5") + .setChangeAddress("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23") + .setAmount(7_000_000) + .build() + val input = Cardano.SigningInput.newBuilder() + .setTransferMessage(message) + .setTtl(53333333) + + val privKey = Numeric.hexStringToByteArray("089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e") + input.addPrivateKey(ByteString.copyFrom(privKey)) + + val outpoint1 = Cardano.OutPoint.newBuilder() + .setTxHash(ByteString.copyFrom(Numeric.hexStringToByteArray("f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767"))) + .setOutputIndex(1) + .build() + val utxo1 = Cardano.TxInput.newBuilder() + .setOutPoint(outpoint1) + .setAddress("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23") + .setAmount(1_500_000) + .build() + input.addUtxos(utxo1) + + val outpoint2 = Cardano.OutPoint.newBuilder() + .setTxHash(ByteString.copyFrom(Numeric.hexStringToByteArray("554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af0"))) + .setOutputIndex(0) + .build() + val utxo2 = Cardano.TxInput.newBuilder() + .setOutPoint(outpoint2) + .setAddress("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23") + .setAmount(6_500_000) + .build() + input.addUtxos(utxo2) + + val output = AnySigner.sign(input.build(), CARDANO, SigningOutput.parser()) + assertEquals(output.error, SigningError.OK) + + val encoded = output.encoded + assertEquals(Numeric.toHexString(encoded.toByteArray()), + "0x83a40082825820554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af000825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd51a006acfc082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a000ca96c021a000298d4031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058407cf591599852b5f5e007fdc241062405c47e519266c0d884b0767c1d4f5eacce00db035998e53ed10ca4ba5ce4aac8693798089717ce6cf4415f345cc764200ef6"); + + val txid = output.txId + assertEquals(Numeric.toHexString(txid.toByteArray()), "0x9b5b15e133cd73ccaa85307d2986aebc846505118a2eb4e6111e6b4b67d1f389"); + } + + @Test + fun testSignTransferToken1() { + val toToken = Cardano.TokenAmount.newBuilder() + .setPolicyId("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77") + .setAssetName("SUNDAE") + .setAmount(ByteString.copyFrom(Numeric.hexStringToByteArray("01312d00"))) // 20000000 + .build() + val toTokenBundle = Cardano.TokenBundle.newBuilder() + .addToken(toToken) + .build() + + // check min ADA amount, set it + val minAmount = wallet.core.jni.Cardano.minAdaAmount(toTokenBundle.toByteArray()) + assertEquals(minAmount, 1_444_443) + + val message = Cardano.Transfer.newBuilder() + .setToAddress("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5") + .setChangeAddress("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq") + .setAmount(minAmount) + .setUseMaxAmount(false) + .setTokenAmount(toTokenBundle) + .build() + val input = Cardano.SigningInput.newBuilder() + .setTransferMessage(message) + .setTtl(53333333) + + val privKey = Numeric.hexStringToByteArray("089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e") + input.addPrivateKey(ByteString.copyFrom(privKey)) + + val outpoint1 = Cardano.OutPoint.newBuilder() + .setTxHash(ByteString.copyFrom(Numeric.hexStringToByteArray("f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767"))) + .setOutputIndex(1) + .build() + val utxo1 = Cardano.TxInput.newBuilder() + .setOutPoint(outpoint1) + .setAddress("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23") + .setAmount(8_051_373) + val token3 = Cardano.TokenAmount.newBuilder() + .setPolicyId("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77") + .setAssetName("CUBY") + .setAmount(ByteString.copyFrom(Numeric.hexStringToByteArray("2dc6c0"))) // 3000000 + .build() + utxo1.addTokenAmount(token3) + input.addUtxos(utxo1.build()) + + val outpoint2 = Cardano.OutPoint.newBuilder() + .setTxHash(ByteString.copyFrom(Numeric.hexStringToByteArray("f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767"))) + .setOutputIndex(2) + .build() + val utxo2 = Cardano.TxInput.newBuilder() + .setOutPoint(outpoint2) + .setAddress("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23") + .setAmount(2_000_000) + val token1 = Cardano.TokenAmount.newBuilder() + .setPolicyId("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77") + .setAssetName("SUNDAE") + .setAmount(ByteString.copyFrom(Numeric.hexStringToByteArray("04d3e8d9"))) // 80996569 + .build() + utxo2.addTokenAmount(token1) + val token2 = Cardano.TokenAmount.newBuilder() + .setPolicyId("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77") + .setAssetName("CUBY") + .setAmount(ByteString.copyFrom(Numeric.hexStringToByteArray("1e8480"))) // 2000000 + .build() + utxo2.addTokenAmount(token2) + input.addUtxos(utxo2.build()) + + val output = AnySigner.sign(input.build(), CARDANO, SigningOutput.parser()) + assertEquals(output.error, SigningError.OK) + + val encoded = output.encoded + assertEquals(Numeric.toHexString(encoded.toByteArray()), + "0x83a40082825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76702018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd5821a00160a5ba1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a14653554e4441451a01312d00825839018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468821a0080aac9a1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a244435542591a004c4b404653554e4441451a03a2bbd9021a0002aa09031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840d90dcfbd190cbe59c42094e59eeb49b3de9d80a85b786cc311f932c5c9302d1c8c6c577b22aa70ff7955c139c700ea918f8cb425c3ba43a27980e1d238e4e908f6"); + + val txid = output.txId + assertEquals(Numeric.toHexString(txid.toByteArray()), "0x201c537693b005b64a0f0528e366ec67a84be0119ed4363b547f141f2a7770c2"); + } + + @Test + fun testSignStakingRegisterAndDelegate() { + val ownAddress = "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23" + val stakingAddress = getStakingAddress(ownAddress) + val poolIdNufi = Numeric.hexStringToByteArray("7d7ac07a2f2a25b7a4db868a40720621c4939cf6aefbb9a11464f1a6") + + val message = Cardano.Transfer.newBuilder() + .setToAddress(ownAddress) + .setChangeAddress(ownAddress) + .setAmount(4_000_000) // not relevant if we use MaxAmount + .setUseMaxAmount(true) + .build() + // Register staking key, 2 ADA desposit + val register = Cardano.RegisterStakingKey.newBuilder() + .setStakingAddress(stakingAddress) + .setDepositAmount(2_000_000) + // Delegate + val delegate = Cardano.Delegate.newBuilder() + .setStakingAddress(stakingAddress) + .setPoolId(ByteString.copyFrom(poolIdNufi)) + .setDepositAmount(0) + val input = Cardano.SigningInput.newBuilder() + .setTransferMessage(message) + .setRegisterStakingKey(register) + .setDelegate(delegate) + .setTtl(69885081) + + val privKey = Numeric.hexStringToByteArray("089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e") + input.addPrivateKey(ByteString.copyFrom(privKey)) + + val outpoint1 = Cardano.OutPoint.newBuilder() + .setTxHash(ByteString.copyFrom(Numeric.hexStringToByteArray("9b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e"))) + .setOutputIndex(0) + .build() + val utxo1 = Cardano.TxInput.newBuilder() + .setOutPoint(outpoint1) + .setAddress(ownAddress) + .setAmount(4_000_000) + .build() + input.addUtxos(utxo1) + + val outpoint2 = Cardano.OutPoint.newBuilder() + .setTxHash(ByteString.copyFrom(Numeric.hexStringToByteArray("9b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e"))) + .setOutputIndex(1) + .build() + val utxo2 = Cardano.TxInput.newBuilder() + .setOutPoint(outpoint2) + .setAddress(ownAddress) + .setAmount(26651312) + .build() + input.addUtxos(utxo2) + + val output = AnySigner.sign(input.build(), CARDANO, SigningOutput.parser()) + assertEquals(output.error, SigningError.OK) + + val encoded = output.encoded + assertEquals(Numeric.toHexString(encoded.toByteArray()), + "0x83a500828258209b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e008258209b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e01018182583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a01b27ef5021a0002b03b031a042a5c99048282008200581cdf22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b83028200581cdf22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b581c7d7ac07a2f2a25b7a4db868a40720621c4939cf6aefbb9a11464f1a6a100828258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840677c901704be027d9a1734e8aa06f0700009476fa252baaae0de280331746a320a61456d842d948ea5c0e204fc36f3bd04c88ca7ee3d657d5a38014243c37c07825820e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e0693258401fa21bdc62b85ca217bf08cbacdeba2fadaf33dc09ee3af9cc25b40f24822a1a42cfbc03585cc31a370ef75aaec4d25db6edcf329e40a4e725ec8718c94f220af6"); + + val txid = output.txId + assertEquals(Numeric.toHexString(txid.toByteArray()), "0x96a781fd6481b6a7fd3926da110265e8c44b53947b81daa84da5b148825d02aa"); + } + + @Test + fun testSignStakingWithdraw() { + val ownAddress = "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23" + val stakingAddress = getStakingAddress(ownAddress) + + val message = Cardano.Transfer.newBuilder() + .setToAddress(ownAddress) + .setChangeAddress(ownAddress) + .setAmount(6_000_000) // not relevant if we use MaxAmount + .setUseMaxAmount(true) + .build() + // Withdraw available amount + val withdraw = Cardano.Withdraw.newBuilder() + .setStakingAddress(stakingAddress) + .setWithdrawAmount(3468) + val input = Cardano.SigningInput.newBuilder() + .setTransferMessage(message) + .setWithdraw(withdraw) + .setTtl(71678326) + + val privKey = Numeric.hexStringToByteArray("089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e") + input.addPrivateKey(ByteString.copyFrom(privKey)) + + val outpoint1 = Cardano.OutPoint.newBuilder() + .setTxHash(ByteString.copyFrom(Numeric.hexStringToByteArray("7dfd2c579794314b1f84efc9db932a098e440ccefb874945591f1d4e85a9152a"))) + .setOutputIndex(0) + .build() + val utxo1 = Cardano.TxInput.newBuilder() + .setOutPoint(outpoint1) + .setAddress(ownAddress) + .setAmount(6_305_913) + .build() + input.addUtxos(utxo1) + + val output = AnySigner.sign(input.build(), CARDANO, SigningOutput.parser()) + assertEquals(output.error, SigningError.OK) + + val encoded = output.encoded + assertEquals(Numeric.toHexString(encoded.toByteArray()), + "0x83a500818258207dfd2c579794314b1f84efc9db932a098e440ccefb874945591f1d4e85a9152a00018182583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a005da6ff021a00029f06031a0445b97605a1581de1df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b190d8ca100828258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058401ebaca2876fd17122404912a2558a98109cdf0f990a938d2917fa2c3b8c4e55e18a2cbabfa82fff03fa0d7ab8b88ca01ed18e42af3bfc4cda7f423a3aa30c00b825820e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e069325840777f04fa8f083fe562aecf78898aaaaac36e2cc6ca962f6ffb01e84a421cae1860496db79b2c5fb2879524c3d5121060b9ea1e693336230c6e5338e14c4c3303f6"); + + val txid = output.txId + assertEquals(Numeric.toHexString(txid.toByteArray()), "0x6dcf3956232953fc25b8355fb1ded1e912b5802090fd21434d789087d6329683"); + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cosmos/TestCosmosTransactions.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cosmos/TestCosmosTransactions.kt index ff0ed3a94c9..7267138e1da 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cosmos/TestCosmosTransactions.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cosmos/TestCosmosTransactions.kt @@ -1,14 +1,16 @@ -package com.trustwalval.core.app.blockchains.cosmos +package com.trustwallet.core.app.blockchains.cosmos import android.util.Log import com.google.protobuf.ByteString import com.trustwallet.core.app.utils.toHexByteArray +import com.trustwallet.core.app.utils.toHex import org.junit.Assert.assertEquals import org.junit.Test import wallet.core.jni.* import wallet.core.jni.CoinType.COSMOS import wallet.core.jni.proto.Cosmos import wallet.core.jni.proto.Cosmos.SigningOutput +import wallet.core.jni.proto.Cosmos.SigningMode import wallet.core.java.AnySigner class TestCosmosTransactions { @@ -17,6 +19,104 @@ class TestCosmosTransactions { System.loadLibrary("TrustWalletCore") } + @Test + fun testAuthStakingTransaction() { + val key = + PrivateKey("c7764249cdf77f8f1d840fa8af431579e5e41cf1af937e1e23afa22f3f4f0ccc".toHexByteArray()) + + val stakeAuth = Cosmos.Message.StakeAuthorization.newBuilder().apply { + allowList = Cosmos.Message.StakeAuthorization.Validators.newBuilder().apply { + addAddress("cosmosvaloper1gjtvly9lel6zskvwtvlg5vhwpu9c9waw7sxzwx") + }.build() + authorizationType = Cosmos.Message.AuthorizationType.DELEGATE + }.build() + + val authStakingMsg = Cosmos.Message.AuthGrant.newBuilder().apply { + grantee = "cosmos1fs7lu28hx5m9akm7rp0c2422cn8r2f7gurujhf" + granter = "cosmos13k0q0l7lg2kr32kvt7ly236ppldy8v9dzwh3gd" + grantStake = stakeAuth + expiration = 1692309600 + }.build() + + val message = Cosmos.Message.newBuilder().apply { + authGrant = authStakingMsg + }.build() + + val feeAmount = Cosmos.Amount.newBuilder().apply { + amount = "2418" + denom = "uatom" + }.build() + + val cosmosFee = Cosmos.Fee.newBuilder().apply { + gas = 96681 + addAllAmounts(listOf(feeAmount)) + }.build() + + val signingInput = Cosmos.SigningInput.newBuilder().apply { + signingMode = SigningMode.Protobuf + accountNumber = 1290826 + chainId = "cosmoshub-4" + memo = "" + sequence = 5 + fee = cosmosFee + privateKey = ByteString.copyFrom(key.data()) + addAllMessages(listOf(message)) + }.build() + + val output = AnySigner.sign(signingInput, COSMOS, SigningOutput.parser()) + + assertEquals( + output.serialized, + "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczEzazBxMGw3bGcya3IzMmt2dDdseTIzNnBwbGR5OHY5ZHp3aDNnZBItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjoSNgo0Y29zbW9zdmFsb3BlcjFnanR2bHk5bGVsNnpza3Z3dHZsZzV2aHdwdTljOXdhdzdzeHp3eCABEgYI4LD6pgYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAUSEwoNCgV1YXRvbRIEMjQxOBCp8wUaQIFyfuijGKf87Hz61ZqxasfLI1PZnNge4RDq/tRyB/tZI6p80iGRqHecoV6+84EQkc9GTlNRQOSlApRCsivT9XI=\"}" + ) + assertEquals(output.error, "") + } + + @Test + fun testRemoveAuthStakingTransaction() { + val key = + PrivateKey("c7764249cdf77f8f1d840fa8af431579e5e41cf1af937e1e23afa22f3f4f0ccc".toHexByteArray()) + + val removeAuthStakingMsg = Cosmos.Message.AuthRevoke.newBuilder().apply { + grantee = "cosmos1fs7lu28hx5m9akm7rp0c2422cn8r2f7gurujhf" + granter = "cosmos13k0q0l7lg2kr32kvt7ly236ppldy8v9dzwh3gd" + msgTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate" + }.build() + + val message = Cosmos.Message.newBuilder().apply { + authRevoke = removeAuthStakingMsg + }.build() + + val feeAmount = Cosmos.Amount.newBuilder().apply { + amount = "2194" + denom = "uatom" + }.build() + + val cosmosFee = Cosmos.Fee.newBuilder().apply { + gas = 87735 + addAllAmounts(listOf(feeAmount)) + }.build() + + val signingInput = Cosmos.SigningInput.newBuilder().apply { + signingMode = SigningMode.Protobuf + accountNumber = 1290826 + chainId = "cosmoshub-4" + memo = "" + sequence = 4 + fee = cosmosFee + privateKey = ByteString.copyFrom(key.data()) + addAllMessages(listOf(message)) + }.build() + + val output = AnySigner.sign(signingInput, COSMOS, SigningOutput.parser()) + + assertEquals( + output.serialized, + "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CqoBCqcBCh8vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnUmV2b2tlEoMBCi1jb3Ntb3MxM2swcTBsN2xnMmtyMzJrdnQ3bHkyMzZwcGxkeTh2OWR6d2gzZ2QSLWNvc21vczFmczdsdTI4aHg1bTlha203cnAwYzI0MjJjbjhyMmY3Z3VydWpoZhojL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuTXNnRGVsZWdhdGUSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAQSEwoNCgV1YXRvbRIEMjE5NBC3rQUaQI7K+W7MMBoD6FbFZxRBqs9VTjErztjWTy57+fvrLaTCIZ+eBs7CuaKqfUZdSN8otjubSHVTQID3k9DpPAX0yDo=\"}" + ) + assertEquals(output.error, "") + } + @Test fun testSigningTransaction() { val key = @@ -25,7 +125,7 @@ class TestCosmosTransactions { val from = AnyAddress(publicKey, COSMOS).description() val txAmount = Cosmos.Amount.newBuilder().apply { - amount = 1 + amount = "1" denom = "muon" }.build() @@ -40,7 +140,7 @@ class TestCosmosTransactions { }.build() val feeAmount = Cosmos.Amount.newBuilder().apply { - amount = 200 + amount = "200" denom = "muon" }.build() @@ -50,6 +150,7 @@ class TestCosmosTransactions { }.build() val signingInput = Cosmos.SigningInput.newBuilder().apply { + signingMode = SigningMode.Protobuf accountNumber = 1037 chainId = "gaia-13003" memo = "" @@ -60,11 +161,12 @@ class TestCosmosTransactions { }.build() val output = AnySigner.sign(signingInput, COSMOS, SigningOutput.parser()) - val jsonPayload = output.json - - val expectedJsonPayload = """{"mode":"block","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"muon"}],"from_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","to_address":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"/D74mdIGyIB3/sQvIboLTfS9P9EV/fYGrgHZE2/vNj9X6eM6e57G3atljNB+PABnRw3pTk51uXmhCFop8O/ZJg=="}]}}""" - assertEquals(expectedJsonPayload, jsonPayload) + assertEquals( + output.serialized, + "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H\"}" + ) + assertEquals(output.error, "") } @Test @@ -93,8 +195,12 @@ class TestCosmosTransactions { }] } """ - val key = "c9b0a273831931aa4a5f8d1a570d5021dda91d3319bd3819becdaabfb7b44e3b".toHexByteArray() + val key = + "c9b0a273831931aa4a5f8d1a570d5021dda91d3319bd3819becdaabfb7b44e3b".toHexByteArray() val result = AnySigner.signJSON(json, key, COSMOS.value()) - assertEquals(result, """{"mode":"block","tx":{"fee":{"amount":[{"amount":"5000","denom":"uatom"}],"gas":"200000"},"memo":"Testing","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"995000","denom":"uatom"}],"from_address":"cosmos1ufwv9ymhqaal6xz47n0jhzm2wf4empfqvjy575","to_address":"cosmos135qla4294zxarqhhgxsx0sw56yssa3z0f78pm0"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A6EsukEXB53GhohQVeDpxtkeH8KQIayd/Co/ApYRYkTm"},"signature":"ULEpUqNzoAnYEx2x22F3ANAiPXquAU9+mqLWoAA/ZOUGTMsdb6vryzsW6AKX2Kqj1pGNdrTcQ58Z09JPyjpgEA=="}]}}""") + assertEquals( + result, + """{"mode":"block","tx":{"fee":{"amount":[{"amount":"5000","denom":"uatom"}],"gas":"200000"},"memo":"Testing","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"995000","denom":"uatom"}],"from_address":"cosmos1ufwv9ymhqaal6xz47n0jhzm2wf4empfqvjy575","to_address":"cosmos135qla4294zxarqhhgxsx0sw56yssa3z0f78pm0"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A6EsukEXB53GhohQVeDpxtkeH8KQIayd/Co/ApYRYkTm"},"signature":"ULEpUqNzoAnYEx2x22F3ANAiPXquAU9+mqLWoAA/ZOUGTMsdb6vryzsW6AKX2Kqj1pGNdrTcQ58Z09JPyjpgEA=="}]}}""" + ) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cryptoorg/TestCryptoorgSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cryptoorg/TestCryptoorgSigner.kt index 56707c5969c..98c568d9bef 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cryptoorg/TestCryptoorgSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cryptoorg/TestCryptoorgSigner.kt @@ -10,6 +10,7 @@ import com.google.protobuf.ByteString import com.trustwallet.core.app.utils.Numeric import com.trustwallet.core.app.utils.toHexByteArray import com.trustwallet.core.app.utils.toHexBytes +import com.trustwallet.core.app.utils.toHex import com.trustwallet.core.app.utils.toHexBytesInByteString import org.junit.Assert.assertEquals import org.junit.Test @@ -17,6 +18,7 @@ import wallet.core.java.AnySigner import wallet.core.jni.CoinType.CRYPTOORG import wallet.core.jni.proto.Cosmos import wallet.core.jni.proto.Cosmos.SigningOutput +import wallet.core.jni.proto.Cosmos.SigningMode import wallet.core.jni.* class TestCryptoorgSigner { @@ -32,7 +34,7 @@ class TestCryptoorgSigner { val from = AnyAddress(publicKey, CRYPTOORG).description() val txAmount = Cosmos.Amount.newBuilder().apply { - amount = 100000000 + amount = "50000000" denom = "basecro" }.build() @@ -47,7 +49,7 @@ class TestCryptoorgSigner { }.build() val feeAmount = Cosmos.Amount.newBuilder().apply { - amount = 5000 + amount = "5000" denom = "basecro" }.build() @@ -57,19 +59,20 @@ class TestCryptoorgSigner { }.build() val signingInput = Cosmos.SigningInput.newBuilder().apply { + signingMode = SigningMode.Protobuf accountNumber = 125798 chainId = "crypto-org-chain-mainnet-1" memo = "" - sequence = 0 + sequence = 2 fee = cosmosFee privateKey = ByteString.copyFrom(key.data()) addAllMessages(listOf(message)) }.build() val output = AnySigner.sign(signingInput, CRYPTOORG, SigningOutput.parser()) - val jsonPayload = output.json - val expectedJsonPayload = """{"mode":"block","tx":{"fee":{"amount":[{"amount":"5000","denom":"basecro"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"100000000","denom":"basecro"}],"from_address":"cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0","to_address":"cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A4gxsGFiPn6L5Z2IjHEISkXI0IkwfL9exV3GLB171Wvj"},"signature":"5+5rSFFg0FE9cTklQWQHNktBDJsz7UCnMSgF0t0+gYcrIhEWUyTtibXaHZQbKAAaciJ1BkHXYREjU55VswByVg=="}]}}""" - assertEquals(expectedJsonPayload, jsonPayload) + // https://crypto.org/explorer/tx/BCB213B0A121F0CF11BECCF52475F1C8328D6070F3CFDA9E14C42E6DB30E847E + assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CpABCo0BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm0KKmNybzFjdHd0Y3dwZ2tza3k5ODhkaHRoNmpzbHh2ZXVtZ3UwZDQ1emdmMBIqY3JvMXhwYWh5NmM3d2xkeGFjdjZsZDk5aDQzNW1odmZuc3VwMjR2Y3VzGhMKB2Jhc2Vjcm8SCDUwMDAwMDAwEmkKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQOIMbBhYj5+i+WdiIxxCEpFyNCJMHy/XsVdxiwde9Vr4xIECgIIARgCEhUKDwoHYmFzZWNybxIENTAwMBDAmgwaQAcxK9xk6r69gmz+1UWaCnYxNuXPXZdp59YcqKPJE5d6fp+IICTBOwd2rs8MiApcf8kNSrbZ6oECxcGQAdxF0SI=\"}") + assertEquals(output.error, "") } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondAddress.kt index 05c17837ddc..e8304e3deeb 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondAddress.kt @@ -18,9 +18,9 @@ class TestElrondAddress { System.loadLibrary("TrustWalletCore") } - private val aliceBech32 = "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz" - private var aliceSeedHex = "1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf" - private var alicePubKeyHex = "0xfd691bb5e85d102687d81079dffce842d4dc328276d2d4c60d8fd1c3433c3293" + private var aliceBech32 = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + private var alicePubKeyHex = "0x0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1" + private var aliceSeedHex = "0x413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9" @Test fun testAddressFromPrivateKey() { diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondSigner.kt index ac37e8f520e..a99a949bdb4 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondSigner.kt @@ -21,39 +21,128 @@ class TestElrondSigner { System.loadLibrary("TrustWalletCore") } - val aliceBech32 = "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz" - var aliceSeedHex = "1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf" - var alicePubKeyHex = "fd691bb5e85d102687d81079dffce842d4dc328276d2d4c60d8fd1c3433c3293" - - val bobBech32 = "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r" - var bobSeedHex = "e3a3a3d1ac40d42d8fd4c569a9749b65a1250dd3d10b6f4e438297662ea4850e" - var bobPubKeyHex = "c70cf50b238372fffaf7b7c5723b06b57859d424a2da621bcc1b2f317543aa36" - + private var aliceBech32 = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + private var aliceSeedHex = "0x413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9" + private var bobBech32 = "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx" + @Test - fun signTransaction() { - val transaction = Elrond.TransactionMessage.newBuilder() - .setNonce(0) - .setValue("0") + fun signGenericAction() { + val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) + + val accounts = Elrond.Accounts.newBuilder() + .setSenderNonce(7) .setSender(aliceBech32) .setReceiver(bobBech32) + .build() + + val genericAction = Elrond.GenericAction.newBuilder() + .setAccounts(accounts) + .setValue("0") + .setData("foo") + .setVersion(1) + .build() + + val signingInput = Elrond.SigningInput.newBuilder() + .setGenericAction(genericAction) .setGasPrice(1000000000) .setGasLimit(50000) - .setData("foo") .setChainId("1") - .setVersion(1) + .setPrivateKey(privateKey) + .build() + + val output = AnySigner.sign(signingInput, CoinType.ELROND, Elrond.SigningOutput.parser()) + val expectedSignature = "e8647dae8b16e034d518a1a860c6a6c38d16192d0f1362833e62424f424e5da660770dff45f4b951d9cc58bfb9d14559c977d443449bfc4b8783ff9c84065700" + + assertEquals(expectedSignature, output.signature) + assertEquals("""{"nonce":7,"value":"0","receiver":"$bobBech32","sender":"$aliceBech32","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"$expectedSignature"}""", output.encoded) + } + + @Test + fun signEGLDTransfer() { + val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) + + val accounts = Elrond.Accounts.newBuilder() + .setSenderNonce(7) + .setSender(aliceBech32) + .setReceiver(bobBech32) + .build() + + val transfer = Elrond.EGLDTransfer.newBuilder() + .setAccounts(accounts) + .setAmount("1000000000000000000") + .build() + + val signingInput = Elrond.SigningInput.newBuilder() + .setEgldTransfer(transfer) + .setChainId("1") + .setPrivateKey(privateKey) + .build() + + val output = AnySigner.sign(signingInput, CoinType.ELROND, Elrond.SigningOutput.parser()) + val expectedSignature = "7e1c4c63b88ea72dcf7855a54463b1a424eb357ac3feb4345221e512ce07c7a50afb6d7aec6f480b554e32cf2037082f3bc17263d1394af1f3ef240be53c930b" + + assertEquals(expectedSignature, output.signature) + assertEquals("""{"nonce":7,"value":"1000000000000000000","receiver":"$bobBech32","sender":"$aliceBech32","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":1,"signature":"$expectedSignature"}""", output.encoded) + } + + @Test + fun signESDTTransfer() { + val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) + + val accounts = Elrond.Accounts.newBuilder() + .setSenderNonce(7) + .setSender(aliceBech32) + .setReceiver(bobBech32) + .build() + + val transfer = Elrond.ESDTTransfer.newBuilder() + .setAccounts(accounts) + .setTokenIdentifier("MYTOKEN-1234") + .setAmount("10000000000000") + .build() + + val signingInput = Elrond.SigningInput.newBuilder() + .setEsdtTransfer(transfer) + .setChainId("1") + .setPrivateKey(privateKey) .build() - + + val output = AnySigner.sign(signingInput, CoinType.ELROND, Elrond.SigningOutput.parser()) + val expectedSignature = "9add6d9ac3f1a1fddb07b934e8a73cad3b8c232bdf29d723c1b38ad619905f03e864299d06eb3fe3bbb48a9f1d9b7f14e21dc5eaffe0c87f5718ad0c4198bb0c" + val expectedData = "RVNEVFRyYW5zZmVyQDRkNTk1NDRmNGI0NTRlMmQzMTMyMzMzNEAwOTE4NGU3MmEwMDA=" + + assertEquals(expectedSignature, output.signature) + assertEquals("""{"nonce":7,"value":"0","receiver":"$bobBech32","sender":"$aliceBech32","gasPrice":1000000000,"gasLimit":425000,"data":"$expectedData","chainID":"1","version":1,"signature":"$expectedSignature"}""", output.encoded) + } + + @Test + fun signESDTNFTTransfer() { val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) + val accounts = Elrond.Accounts.newBuilder() + .setSenderNonce(7) + .setSender(aliceBech32) + .setReceiver(bobBech32) + .build() + + val transfer = Elrond.ESDTNFTTransfer.newBuilder() + .setAccounts(accounts) + .setTokenCollection("LKMEX-aab910") + .setTokenNonce(4) + .setAmount("184300000000000000") + .build() + val signingInput = Elrond.SigningInput.newBuilder() + .setEsdtnftTransfer(transfer) + .setChainId("1") .setPrivateKey(privateKey) - .setTransaction(transaction) .build() val output = AnySigner.sign(signingInput, CoinType.ELROND, Elrond.SigningOutput.parser()) - val expectedSignature = "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00" + val expectedSignature = "cc935685d5b31525e059a16a832cba98dee751983a5a93de4198f6553a2c55f5f1e0b4300fe9077376fa754546da0b0f6697e66462101a209aafd0fc775ab60a" + val expectedData = "RVNEVE5GVFRyYW5zZmVyQDRjNGI0ZDQ1NTgyZDYxNjE2MjM5MzEzMEAwNEAwMjhlYzNkZmEwMWFjMDAwQDgwNDlkNjM5ZTVhNjk4MGQxY2QyMzkyYWJjY2U0MTAyOWNkYTc0YTE1NjM1MjNhMjAyZjA5NjQxY2MyNjE4Zjg=" assertEquals(expectedSignature, output.signature) - assertEquals("""{"nonce":0,"value":"0","receiver":"$bobBech32","sender":"$aliceBech32","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"$expectedSignature"}""", output.encoded) + assertEquals("""{"nonce":7,"value":"0","receiver":"$aliceBech32","sender":"$aliceBech32","gasPrice":1000000000,"gasLimit":937500,"data":"$expectedData","chainID":"1","version":1,"signature":"$expectedSignature"}""", output.encoded) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/everscale/TestEverscaleAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/everscale/TestEverscaleAddress.kt new file mode 100644 index 00000000000..b09cfaff057 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/everscale/TestEverscaleAddress.kt @@ -0,0 +1,28 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +package com.trustwallet.core.app.blockchains.everscale + +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 TestEverscaleAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("5b59e0372d19b6355c73fa8cc708fa3301ae2ec21bb6277e8b79d386ccb7846f".toHexByteArray()) + val pubkey = key.publicKeyEd25519 + val address = AnyAddress(pubkey, CoinType.EVERSCALE) + val expected = AnyAddress("0:269fee242eb410786abe1777a14785c8bbeb1e34100c7570e17698b36ad66fb0", CoinType.EVERSCALE) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/everscale/TestEverscaleSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/everscale/TestEverscaleSigner.kt new file mode 100644 index 00000000000..cb1d7266f92 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/everscale/TestEverscaleSigner.kt @@ -0,0 +1,48 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +package com.trustwallet.core.app.blockchains.everscale + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHexByteArray +import com.trustwallet.core.app.utils.toHexBytes +import com.trustwallet.core.app.utils.toHexBytesInByteString +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.CoinType.EVERSCALE +import wallet.core.jni.proto.Everscale +import wallet.core.jni.proto.Everscale.SigningOutput + +class TestEverscaleSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testSign() { + val transferMessage = Everscale.Transfer.newBuilder().apply { + bounce = false + behavior = Everscale.MessageBehavior.SimpleTransfer + amount = 100000000 + expiredAt = 1680770631 + to = "0:db18a67f4626f15ac0537a18445937f685f9b30184f0d7b28be4bdeb92d2fd90" + encodedContractData = "te6ccgEBAQEAKgAAUAAAAAFLqS2KOWKN+7Y5OSiKhKisiw6t/h2ovvR3WbapyAtrdctwupw=" + }.build() + val signingInput = Everscale.SigningInput.newBuilder().apply { + transfer = transferMessage + privateKey = ByteString.copyFrom("542bd4288352f1c6b270046f153d406aec054a0a06000ab9b36b5c6dd3050ad4".toHexByteArray()) + }.build() + + val output = AnySigner.sign(signingInput, EVERSCALE, SigningOutput.parser()) + + // Link to the external message: https://everscan.io/messages/73807b0a3ca2d8564c023dccd5b9da222a270f68338c6fc2c064dda376a2c59d + val expectedString = "te6ccgICAAIAAQAAAKoAAAHfiAG+Ilaz1wTyTEauoymMGl6o+NGqhszIlHS8BXAmXniYrAImASIQKH2jIwoA65IGC6aua4gAA4fFo/Nuxgb3sIRELhZnSXIS7IsE2E4D+8hk3EWGVZX+ICqlN/ka9DvXduhaXUlsUyF0MjgAAAAIHAABAGhCAG2MUz+jE3itYCm9DCIsm/tC/NmAwnhr2UXyXvXJaX7IIC+vCAAAAAAAAAAAAAAAAAAA" + assertEquals(output.encoded, expectedString) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/juno/TestJunoAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/juno/TestJunoAddress.kt new file mode 100644 index 00000000000..d7db293adf0 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/juno/TestJunoAddress.kt @@ -0,0 +1,35 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +package com.trustwallet.core.app.blockchains.juno + +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert +import org.junit.Test +import wallet.core.jni.* + +class TestJunoAddress { + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAnyAddressValidation() { + val addr = "juno1gckvjxau7k56f8wg8c8xj80khyp83y8x8eqc94" + val anyAddr = AnyAddress(addr, CoinType.COSMOS, "juno") + assert(AnyAddress.isValidBech32(anyAddr.description(), CoinType.COSMOS, "juno")) + assert(!AnyAddress.isValidBech32(anyAddr.description(), CoinType.BITCOIN, "juno")) + assert(!AnyAddress.isValid(anyAddr.description(), CoinType.BITCOIN)) + assert(!AnyAddress.isValid(anyAddr.description(), CoinType.COSMOS)) + } + + @Test + fun testAnyAddressFromPubkey() { + val pubKey = PublicKey("02753f5c275e1847ba4d2fd3df36ad00af2e165650b35fe3991e9c9c46f68b12bc".toHexByteArray(), PublicKeyType.SECP256K1) + val anyAddr = AnyAddress(pubKey, CoinType.COSMOS, "juno") + Assert.assertEquals(anyAddr.description(), "juno1cj2vfjec3c3luf9fx9vddnglhh9gawmncn4k5n"); + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kava/TestKavaTransactions.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kava/TestKavaTransactions.kt index 06b3c7b686a..b715150a080 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kava/TestKavaTransactions.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kava/TestKavaTransactions.kt @@ -1,4 +1,4 @@ -package com.trustwalval.core.app.blockchains.cosmos +package com.trustwallet.core.app.blockchains.cosmos import android.util.Log import com.google.protobuf.ByteString @@ -40,7 +40,7 @@ class TestKavaTransactions { }.build() val feeAmount = Cosmos.Amount.newBuilder().apply { - amount = 100 + amount = "100" denom = "ukava" }.build() diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kcc/TestKuCoinCommunityChainAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kcc/TestKuCoinCommunityChainAddress.kt new file mode 100644 index 00000000000..0d2fe4a7446 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kcc/TestKuCoinCommunityChainAddress.kt @@ -0,0 +1,31 @@ +// Copyright © 2017-2021 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.kcc + +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 TestKuCoinCommunityChainAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("33b85056aabab539bcb68540735ecf054e38bc58b29b751530e2b54ecb4ca564".toHexByteArray()) + val pubkey = key.getPublicKeySecp256k1(false) + val address = AnyAddress(pubkey, CoinType.KUCOINCOMMUNITYCHAIN) + val expected = AnyAddress("0xE5cA667d795685E9915E5F4b4254ca832eEB398B", CoinType.KUCOINCOMMUNITYCHAIN) + + assertEquals(pubkey.data().toHex(), "0x0413bde18e3329af54d51a24f424fe09a8d7d42c324c07e10e53a6e139cbee80e6288142dec2ed46f7b81dccbb28d6168cdc7b208928730cbeeb911f8db6a707bb") + assertEquals(address.description(), expected.description()) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/near/TestNEARSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/near/TestNEARSigner.kt index 3680868be56..8e5021abd4b 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/near/TestNEARSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/near/TestNEARSigner.kt @@ -26,9 +26,9 @@ class TestNEARSigner { signerId = "test.near" nonce = 1 receiverId = "whatever.near" - addActionsBuilder().apply { + addActions(NEAR.Action.newBuilder().apply { transfer = transferAction - } + }) blockHash = ByteString.copyFrom(Base58.decodeNoCheck("244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM")) privateKey = ByteString.copyFrom(Base58.decodeNoCheck("3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv").sliceArray(0..31)) }.build() diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nervos/TestNervosAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nervos/TestNervosAddress.kt new file mode 100644 index 00000000000..64f1c492ada --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nervos/TestNervosAddress.kt @@ -0,0 +1,29 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +package com.trustwallet.core.app.blockchains.nervos + +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestNervosAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb".toHexByteArray()) + val pubkey = key.getPublicKeySecp256k1(true) + val address = AnyAddress(pubkey, CoinType.NERVOS) + val expected = AnyAddress("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8furras980hksatlslfaktks7epf25", CoinType.NERVOS) + + assertEquals(address.description(), expected.description()) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nervos/TestNervosSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nervos/TestNervosSigner.kt new file mode 100644 index 00000000000..3d0a149e5ae --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nervos/TestNervosSigner.kt @@ -0,0 +1,69 @@ +// Copyright © 2017-2021 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.nervos + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHexByteArray +import com.trustwallet.core.app.utils.toHexBytesInByteString +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* +import wallet.core.jni.CoinType.NERVOS +import wallet.core.jni.proto.Nervos.* +import wallet.core.java.AnySigner + +class TestNervosSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testSigning() { + val key = PrivateKey("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb".toHexByteArray()) + + val lockScript = Script.newBuilder().apply { + codeHash = "9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8".toHexBytesInByteString() + hashType = "type" + args = "c4b50c5c8d074f063ec0a77ded0eaff0fa7b65da".toHexBytesInByteString() + }.build() + + val signingInput = SigningInput.newBuilder().apply { + addPrivateKey(ByteString.copyFrom(key.data())) + byteFee = 1 + nativeTransfer = NativeTransfer.newBuilder().apply { + toAddress = "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02wectaumxn0664yw2jd53lqk4mxg3" + changeAddress = "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqds6ed78yze6eyfyvd537z66ur22c9mmrgz82ama" + amount = 10000000000 + }.build() + addAllCell(listOf( + Cell.newBuilder().apply { + capacity = 100000000000 + outPoint = OutPoint.newBuilder().apply { + txHash = "71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3".toHexBytesInByteString() + index = 1 + }.build() + lock = lockScript + }.build(), + Cell.newBuilder().apply { + capacity = 20000000000 + outPoint = OutPoint.newBuilder().apply { + txHash = "71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3".toHexBytesInByteString() + index = 0 + }.build() + lock = lockScript + }.build() + )) + }.build() + + val output = AnySigner.sign(signingInput, NERVOS, SigningOutput.parser()) + + assertEquals(output.transactionJson, "{\"cell_deps\":[{\"dep_type\":\"dep_group\",\"out_point\":{\"index\":\"0x0\",\"tx_hash\":\"0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c\"}}],\"header_deps\":[],\"inputs\":[{\"previous_output\":{\"index\":\"0x0\",\"tx_hash\":\"0x71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3\"},\"since\":\"0x0\"}],\"outputs\":[{\"capacity\":\"0x2540be400\",\"lock\":{\"args\":\"0xab201f55b02f53b385f79b34dfad548e549b48fc\",\"code_hash\":\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_type\":\"type\"},\"type\":null},{\"capacity\":\"0x2540be230\",\"lock\":{\"args\":\"0xb0d65be39059d6489231b48f85ad706a560bbd8d\",\"code_hash\":\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_type\":\"type\"},\"type\":null}],\"outputs_data\":[\"0x\",\"0x\"],\"version\":\"0x0\",\"witnesses\":[\"0x55000000100000005500000055000000410000002a9ef2ad7829e5ea0c7a32735d29a0cb2ec20434f6fd5bf6e29cda56b28e08140156191cbbf80313d3c9cae4b74607acce7b28eb21d52ef058ed8491cdde70b700\"]}") + assertEquals(output.transactionId, "0xf2c32afde7e72011985583873bc16c0a3c01fc01fc161eb4b914fcf84c53cdf8") + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/oasis/TestOasisSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/oasis/TestOasisSigner.kt index b0839d0b5ba..f9962ebb13c 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/oasis/TestOasisSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/oasis/TestOasisSigner.kt @@ -8,16 +8,12 @@ package com.trustwallet.core.app.blockchains.oasis import com.google.protobuf.ByteString import com.trustwallet.core.app.utils.Numeric -import com.trustwallet.core.app.utils.toHexByteArray -import com.trustwallet.core.app.utils.toHexBytes -import com.trustwallet.core.app.utils.toHexBytesInByteString -import junit.framework.Assert.assertEquals import org.junit.Test +import org.junit.Assert.assertEquals import wallet.core.java.AnySigner import wallet.core.jni.CoinType.OASIS import wallet.core.jni.proto.Oasis import wallet.core.jni.proto.Oasis.SigningOutput -import java.math.BigInteger class TestOasisSigner { @@ -49,7 +45,7 @@ class TestOasisSigner { val output = AnySigner.sign(signingInput.build(), OASIS, SigningOutput.parser()) assertEquals( - "0xa273756e747275737465645f7261775f76616c7565585ea4656e6f6e636500666d6574686f64707374616b696e672e5472616e7366657263666565a2636761730066616d6f756e74410064626f6479a262746f5500c73cc001463434915ba3f39751beb7c0905b45eb66616d6f756e744400989680697369676e6174757265a26a7075626c69635f6b6579582093d8f8a455f50527976a8aa87ebde38d5606efa86cb985d3fb466aff37000e3b697369676e61747572655840e331ce731ed819106586152b13cd98ecf3248a880bdc71174ee3d83f6d5f3f8ee8fc34c19b22032f2f1e3e06d382720125d7a517fba9295c813228cc2b63170b", + "0xa2697369676e6174757265a2697369676e617475726558406e51c18c9b2015c9b49414b3307336597f51ff331873d214ce2db81c9651a34d99529ccaa294a39ccd01c6b0bc2c2239d87c624e5ba4840cf99ac8f9283e240c6a7075626c69635f6b6579582093d8f8a455f50527976a8aa87ebde38d5606efa86cb985d3fb466aff37000e3b73756e747275737465645f7261775f76616c7565585ea463666565a2636761730066616d6f756e74410064626f6479a262746f5500c73cc001463434915ba3f39751beb7c0905b45eb66616d6f756e744400989680656e6f6e636500666d6574686f64707374616b696e672e5472616e73666572", Numeric.toHexString(output.encoded.toByteArray()) ) } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisAddress.kt new file mode 100644 index 00000000000..1d881bbdef1 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisAddress.kt @@ -0,0 +1,31 @@ +// Copyright © 2017-2021 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.osmosis + +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 TestOsmosisAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af".toHexByteArray()) + val pubkey = key.getPublicKeySecp256k1(true) + val address = AnyAddress(pubkey, CoinType.OSMOSIS) + val expected = AnyAddress("osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5", CoinType.OSMOSIS) + + assertEquals(pubkey.data().toHex(), "0x02ecef5ce437a302c67f95468de4b31f36e911f467d7e6a52b41c1e13e1d563649") + assertEquals(address.description(), expected.description()) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisSigner.kt new file mode 100644 index 00000000000..97f075947ba --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisSigner.kt @@ -0,0 +1,78 @@ +// Copyright © 2017-2021 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.osmosis + +import android.util.Log +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHexByteArray +import com.trustwallet.core.app.utils.toHexBytes +import com.trustwallet.core.app.utils.toHexBytesInByteString +import com.trustwallet.core.app.utils.toHex +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* +import wallet.core.jni.CoinType.OSMOSIS +import wallet.core.jni.proto.Cosmos +import wallet.core.jni.proto.Cosmos.SigningOutput +import wallet.core.jni.proto.Cosmos.SigningMode +import wallet.core.java.AnySigner + +class TestOsmosisSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun OsmosisTransactionSigning() { + val key = PrivateKey("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af".toHexByteArray()) + val publicKey = key.getPublicKeySecp256k1(true) + val from = AnyAddress(publicKey, OSMOSIS).description() + + val txAmount = Cosmos.Amount.newBuilder().apply { + amount = "99800" + denom = "uosmo" + }.build() + + val sendCoinsMsg = Cosmos.Message.Send.newBuilder().apply { + fromAddress = from + toAddress = "osmo18s0hdnsllgcclweu9aymw4ngktr2k0rkvn7jmn" + addAllAmounts(listOf(txAmount)) + }.build() + + val message = Cosmos.Message.newBuilder().apply { + sendCoinsMessage = sendCoinsMsg + }.build() + + val feeAmount = Cosmos.Amount.newBuilder().apply { + amount = "200" + denom = "uosmo" + }.build() + + val osmosisFee = Cosmos.Fee.newBuilder().apply { + gas = 200000 + addAllAmounts(listOf(feeAmount)) + }.build() + + val signingInput = Cosmos.SigningInput.newBuilder().apply { + signingMode = SigningMode.Protobuf + accountNumber = 124703 + chainId = "osmosis-1" + memo = "" + sequence = 0 + fee = osmosisFee + privateKey = ByteString.copyFrom(key.data()) + addAllMessages(listOf(message)) + }.build() + + val output = AnySigner.sign(signingInput, OSMOSIS, SigningOutput.parser()) + + assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"Co0BCooBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmoKK29zbW8xbWt5Njljbjhla3R3eTA4NDV2ZWM5dXBzZHBoa3R4dDBlbjk3ZjUSK29zbW8xOHMwaGRuc2xsZ2NjbHdldTlheW13NG5na3RyMmswcmt2bjdqbW4aDgoFdW9zbW8SBTk5ODAwEmQKTgpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQLs71zkN6MCxn+VRo3ksx826RH0Z9fmpStBweE+HVY2SRIECgIIARISCgwKBXVvc21vEgMyMDAQwJoMGkAMY//Md5GRUR4lVZhk558hFS3kii9QZYoYKfg4+ac/xgNeyoiEweVDhcmEvlH1orVwjLUOnYs4ly2a/yIurYVj\"}") + assertEquals(output.error, "") + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polkadot/TestPolkadotSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polkadot/TestPolkadotSigner.kt index 74d9826c091..2e5a09f2885 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polkadot/TestPolkadotSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polkadot/TestPolkadotSigner.kt @@ -84,4 +84,35 @@ class TestPolkadotSigner { val expected = "0x6103840036092fac541e0e5feda19e537c679b487566d7101141c203ac8322c27e5f076a00a8b1f859d788f11a958e98b731358f89cf3fdd41a667ea992522e8d4f46915f4c03a1896f2ac54bdc5f16e2ce8a2a3bf233d02aad8192332afd2113ed6688e0d0010001a02080700007120f76076bcb0efdf94c7219e116899d0163ea61cb428183d71324eb33b2bce0700e40b540201070508002c2a55b5ffdca266bd0207df97565b03255f70783ca1a349be5ed9f44589c36000d44533a4d21fd9d6f5d57c8cd05c61a6f23f9131cec8ae386b6b437db399ec3d" assertEquals(encoded, expected) } + + @Test + fun PolkadotTransactionSignChillAndUnbond() { + val call = Polkadot.Staking.ChillAndUnbond.newBuilder().apply { + value = "0x1766444D00".toHexBytesInByteString() // 10.05 DOT + } + + val input = Polkadot.SigningInput.newBuilder().apply { + genesisHash = genesisHashStr + blockHash = "0x35ba668bb19453e8da6334cadcef2a27c8d4141bfc8b49e78e853c3d73e1ecd0".toHexBytesInByteString() + nonce = 6 + specVersion = 9200 + network = Polkadot.Network.POLKADOT + transactionVersion = 12 + privateKey = "298fcced2b497ed48367261d8340f647b3fca2d9415d57c2e3c5ef90482a2266".toHexBytesInByteString() + era = Polkadot.Era.newBuilder().apply { + blockNumber = 10541373 + period = 64 + }.build() + stakingCall = Polkadot.Staking.newBuilder().apply { + chillAndUnbond = call.build() + }.build() + } + + val output = AnySigner.sign(input.build(), POLKADOT, SigningOutput.parser()) + val encoded = Numeric.toHexString(output.encoded.toByteArray()) + + // https://polkadot.subscan.io/extrinsic/10541383-2 + val expected = "0xd10184008361bd08ddca5fda28b5e2aa84dc2621de566e23e089e555a42194c3eaf2da7900c891ba102db672e378945d74cf7f399226a76b43cab502436971599255451597fc2599902e4b62c7ce85ecc3f653c693fef3232be620984b5bb5bcecbbd7b209d50318001a02080706070207004d446617" + assertEquals(encoded, expected) + } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/smartbitcoincash/TestSmartBitcoinCashAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/smartbitcoincash/TestSmartBitcoinCashAddress.kt new file mode 100644 index 00000000000..852245d5a05 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/smartbitcoincash/TestSmartBitcoinCashAddress.kt @@ -0,0 +1,31 @@ +// Copyright © 2017-2021 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.smartbitcoincash + +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 TestSmartBitcoinCashAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("ab4accc9310d90a61fc354d8f353bca4a2b3c0590685d3eb82d0216af3badddc".toHexByteArray()) + val pubkey = key.getPublicKeySecp256k1(false) + val address = AnyAddress(pubkey, CoinType.SMARTBITCOINCASH) + val expected = AnyAddress("0xA3Dcd899C0f3832DFDFed9479a9d828c6A4EB2A7", CoinType.SMARTBITCOINCASH) + + assertEquals(pubkey.data().toHex(), "0x0448a9ffac8022f1c7eb5253746e24d11d9b6b2737c0aecd48335feabb95a179916b1f3a97bed6740a85a2d11c663d38566acfb08af48a47ce0c835c65c9b23d0d") + assertEquals(address.description(), expected.description()) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stellar/TestStellarAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stellar/TestStellarAddress.kt index ddd89799276..9d11510cf1f 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stellar/TestStellarAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stellar/TestStellarAddress.kt @@ -18,7 +18,7 @@ class TestAddress { val pubkey = key.publicKeyEd25519 val address = AnyAddress(pubkey, CoinType.STELLAR) - assertEquals(pubkey.data().toHex(), "0x09A966BCAACC103E38896BAAE3F8C2F06C21FD47DD4F864FF0D33F9819DF5CA2".toLowerCase()) + assertEquals(pubkey.data().toHex(), "0x09A966BCAACC103E38896BAAE3F8C2F06C21FD47DD4F864FF0D33F9819DF5CA2".lowercase()) assertEquals(address.description(), "GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI") } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stellar/TestStellarSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stellar/TestStellarSigner.kt index a0a35c869a5..a76ea84550a 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stellar/TestStellarSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stellar/TestStellarSigner.kt @@ -31,7 +31,6 @@ class TestStellarTransactionSigner { sequence = 2 passphrase = StellarPassphrase.STELLAR.toString() opPayment = operation.build() - memoVoid = memoVoidBuilder.build() privateKey = ByteString.copyFrom(PrivateKey("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722".toHexByteArray()).data()) } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraClassicTxs.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraClassicTxs.kt new file mode 100644 index 00000000000..2c67c665dd8 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraClassicTxs.kt @@ -0,0 +1,209 @@ +package com.trustwallet.core.app.blockchains.terra + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* +import wallet.core.jni.CoinType.TERRA +import wallet.core.jni.proto.Cosmos +import wallet.core.jni.proto.Cosmos.SigningOutput +import wallet.core.jni.proto.Cosmos.SigningMode +import wallet.core.java.AnySigner + +class TestTerraClassicTxs { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testSigningTransaction() { + val key = + PrivateKey("1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6".toHexByteArray()) + val publicKey = key.getPublicKeySecp256k1(true) + val from = AnyAddress(publicKey, TERRA).description() + + val txAmount = Cosmos.Amount.newBuilder().apply { + amount = "1000000" + denom = "uluna" + }.build() + + val sendCoinsMsg = Cosmos.Message.Send.newBuilder().apply { + fromAddress = from + toAddress = "terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms" + addAllAmounts(listOf(txAmount)) + typePrefix = "bank/MsgSend" + }.build() + + val message = Cosmos.Message.newBuilder().apply { + sendCoinsMessage = sendCoinsMsg + }.build() + + val feeAmount = Cosmos.Amount.newBuilder().apply { + amount = "3000" + denom = "uluna" + }.build() + + val cosmosFee = Cosmos.Fee.newBuilder().apply { + gas = 200000 + addAllAmounts(listOf(feeAmount)) + }.build() + + val signingInput = Cosmos.SigningInput.newBuilder().apply { + accountNumber = 158 + chainId = "soju-0013" + memo = "" + sequence = 0 + fee = cosmosFee + privateKey = ByteString.copyFrom(key.data()) + addAllMessages(listOf(message)) + }.build() + + val output = AnySigner.sign(signingInput, TERRA, SigningOutput.parser()) + val jsonPayload = output.json + + val expectedJsonPayload = """{"mode":"block","tx":{"fee":{"amount":[{"amount":"3000","denom":"uluna"}],"gas":"200000"},"memo":"","msg":[{"type":"bank/MsgSend","value":{"amount":[{"amount":"1000000","denom":"uluna"}],"from_address":"terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe","to_address":"terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk"},"signature":"KPdiVsKpY12JG/VKEJVa/FpMKclxlS0qNNG6VOAypj10R5vY5UX5IgRJET1zNYnH0wvcXxfNXV+s8jtwN2UXiQ=="}]}}""" + assertEquals(expectedJsonPayload, jsonPayload) + + } + + @Test + fun testSigningWasmTerraTransferTxProtobuf() { + val key = + PrivateKey("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616".toHexByteArray()) + val publicKey = key.getPublicKeySecp256k1(true) + val from = AnyAddress(publicKey, TERRA).description() + + val wasmTransferMessage = Cosmos.Message.WasmTerraExecuteContractTransfer.newBuilder().apply { + senderAddress = from + contractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76" // ANC + amount = ByteString.copyFrom("0x3D090".toHexByteArray()) // 250000 + recipientAddress = "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp" + }.build() + + val message = Cosmos.Message.newBuilder().apply { + wasmTerraExecuteContractTransferMessage = wasmTransferMessage + }.build() + + val feeAmount = Cosmos.Amount.newBuilder().apply { + amount = "3000" + denom = "uluna" + }.build() + + val cosmosFee = Cosmos.Fee.newBuilder().apply { + gas = 200000 + addAllAmounts(listOf(feeAmount)) + }.build() + + val signingInput = Cosmos.SigningInput.newBuilder().apply { + signingMode = SigningMode.Protobuf + accountNumber = 3407705 + chainId = "columbus-5" + memo = "" + sequence = 3 + fee = cosmosFee + privateKey = ByteString.copyFrom(key.data()) + addAllMessages(listOf(message)) + }.build() + + val output = AnySigner.sign(signingInput, TERRA, SigningOutput.parser()) + + assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CucBCuQBCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBK5AQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2Glt7InRyYW5zZmVyIjp7ImFtb3VudCI6IjI1MDAwMCIsInJlY2lwaWVudCI6InRlcnJhMWpsZ2FxeTludm4yaGY1dDJzcmE5eWN6OHM3N3duZjlsMGttZ2NwIn19EmcKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQNwZjrHsPmJKW/rXOWfukpQ1+lOHOJW3/IlFFnKLNmsABIECgIIARgDEhMKDQoFdWx1bmESBDMwMDAQwJoMGkAaprIEMLPH2HmFdwFGoaipb2GIyhXt6ombz+WMnG2mORBI6gFt0M+IymYgzZz6w1SW52R922yafDnn7yXfutRw\"}") + assertEquals(output.error, "") + } + + @Test + fun testSigningWasmTerraGenericProtobuf() { + val key = + PrivateKey("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616".toHexByteArray()) + val publicKey = key.getPublicKeySecp256k1(true) + val from = AnyAddress(publicKey, TERRA).description() + + val wasmGenericMessage = Cosmos.Message.WasmTerraExecuteContractGeneric.newBuilder().apply { + senderAddress = from + contractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76" // ANC + executeMsg = """{"transfer": { "amount": "250000", "recipient": "terra1d7048csap4wzcv5zm7z6tdqem2agyp9647vdyj" } }""" + }.build() + + val message = Cosmos.Message.newBuilder().apply { + wasmTerraExecuteContractGeneric = wasmGenericMessage + }.build() + + val feeAmount = Cosmos.Amount.newBuilder().apply { + amount = "3000" + denom = "uluna" + }.build() + + val cosmosFee = Cosmos.Fee.newBuilder().apply { + gas = 200000 + addAllAmounts(listOf(feeAmount)) + }.build() + + val signingInput = Cosmos.SigningInput.newBuilder().apply { + signingMode = SigningMode.Protobuf + accountNumber = 3407705 + chainId = "columbus-5" + memo = "" + sequence = 7 + fee = cosmosFee + privateKey = ByteString.copyFrom(key.data()) + addAllMessages(listOf(message)) + }.build() + + val output = AnySigner.sign(signingInput, TERRA, SigningOutput.parser()) + + assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"Cu4BCusBCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBLAAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2GmJ7InRyYW5zZmVyIjogeyAiYW1vdW50IjogIjI1MDAwMCIsICJyZWNpcGllbnQiOiAidGVycmExZDcwNDhjc2FwNHd6Y3Y1em03ejZ0ZHFlbTJhZ3lwOTY0N3ZkeWoiIH0gfRJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYBxITCg0KBXVsdW5hEgQzMDAwEMCaDBpAkPsS7xlSng2LMc9KiD1soN5NLaDcUh8I9okPmsdJN3le1B7yxRGNB4aQfhaRl/8Z0r5vitRT0AWuxDasd8wcFw==\"}") + assertEquals(output.error, "") + } + + @Test + fun testSigningWasmTerraGenericWithCoinsProtobuf() { + val key = + PrivateKey("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616".toHexByteArray()) + val publicKey = key.getPublicKeySecp256k1(true) + val from = AnyAddress(publicKey, TERRA).description() + + val coins = Cosmos.Amount.newBuilder().apply { + amount = "1000" + denom = "uusd" + }.build() + + val wasmGenericMessage = Cosmos.Message.WasmTerraExecuteContractGeneric.newBuilder().apply { + senderAddress = from + contractAddress = "terra1sepfj7s0aeg5967uxnfk4thzlerrsktkpelm5s" // ANC Market + executeMsg = """{ "deposit_stable": {} }""" + addCoins(coins) + }.build() + + val message = Cosmos.Message.newBuilder().apply { + wasmTerraExecuteContractGeneric = wasmGenericMessage + }.build() + + val feeAmount = Cosmos.Amount.newBuilder().apply { + amount = "7000" + denom = "uluna" + }.build() + + val cosmosFee = Cosmos.Fee.newBuilder().apply { + gas = 600000 + addAllAmounts(listOf(feeAmount)) + }.build() + + val signingInput = Cosmos.SigningInput.newBuilder().apply { + signingMode = SigningMode.Protobuf + accountNumber = 3407705 + chainId = "columbus-5" + memo = "" + sequence = 9 + fee = cosmosFee + privateKey = ByteString.copyFrom(key.data()) + addAllMessages(listOf(message)) + }.build() + + val output = AnySigner.sign(signingInput, TERRA, SigningOutput.parser()) + + assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CrIBCq8BCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBKEAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMXNlcGZqN3MwYWVnNTk2N3V4bmZrNHRoemxlcnJza3RrcGVsbTVzGhh7ICJkZXBvc2l0X3N0YWJsZSI6IHt9IH0qDAoEdXVzZBIEMTAwMBJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYCRITCg0KBXVsdW5hEgQ3MDAwEMDPJBpAGyi7f1ioY8XV6pjFq1s86Om4++CIUnd3rLHif2iopCcYvX0mLkTlQ6NUERg8nWTYgXcj6fOTO/ptgPuAtv0NWg==\"}") + assertEquals(output.error, "") + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraTransactions.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraTransactions.kt index 0f1dbf2dac6..6ee4574001a 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraTransactions.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraTransactions.kt @@ -1,15 +1,15 @@ -package com.trustwalval.core.app.blockchains.terra +package com.trustwallet.core.app.blockchains.terra -import android.util.Log import com.google.protobuf.ByteString import com.trustwallet.core.app.utils.toHexByteArray import org.junit.Assert.assertEquals import org.junit.Test -import wallet.core.java.AnySigner import wallet.core.jni.* -import wallet.core.jni.CoinType.TERRA +import wallet.core.jni.CoinType.TERRAV2 import wallet.core.jni.proto.Cosmos import wallet.core.jni.proto.Cosmos.SigningOutput +import wallet.core.jni.proto.Cosmos.SigningMode +import wallet.core.java.AnySigner class TestTerraTransactions { @@ -20,20 +20,19 @@ class TestTerraTransactions { @Test fun testSigningTransaction() { val key = - PrivateKey("1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6".toHexByteArray()) + PrivateKey("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005".toHexByteArray()) val publicKey = key.getPublicKeySecp256k1(true) - val from = AnyAddress(publicKey, TERRA).description() + val from = AnyAddress(publicKey, TERRAV2).description() val txAmount = Cosmos.Amount.newBuilder().apply { - amount = 1000000 + amount = "1000000" denom = "uluna" }.build() val sendCoinsMsg = Cosmos.Message.Send.newBuilder().apply { fromAddress = from - toAddress = "terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms" + toAddress = "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp" addAllAmounts(listOf(txAmount)) - typePrefix = "bank/MsgSend" }.build() val message = Cosmos.Message.newBuilder().apply { @@ -41,7 +40,7 @@ class TestTerraTransactions { }.build() val feeAmount = Cosmos.Amount.newBuilder().apply { - amount = 3000 + amount = "30000" denom = "uluna" }.build() @@ -51,20 +50,65 @@ class TestTerraTransactions { }.build() val signingInput = Cosmos.SigningInput.newBuilder().apply { - accountNumber = 158 - chainId = "soju-0013" + signingMode = SigningMode.Protobuf + accountNumber = 1037 + chainId = "phoenix-1" memo = "" - sequence = 0 + sequence = 1 fee = cosmosFee privateKey = ByteString.copyFrom(key.data()) addAllMessages(listOf(message)) }.build() - val output = AnySigner.sign(signingInput, TERRA, SigningOutput.parser()) + val output = AnySigner.sign(signingInput, TERRAV2, SigningOutput.parser()) val jsonPayload = output.json - val expectedJsonPayload = """{"mode":"block","tx":{"fee":{"amount":[{"amount":"3000","denom":"uluna"}],"gas":"200000"},"memo":"","msg":[{"type":"bank/MsgSend","value":{"amount":[{"amount":"1000000","denom":"uluna"}],"from_address":"terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe","to_address":"terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk"},"signature":"KPdiVsKpY12JG/VKEJVa/FpMKclxlS0qNNG6VOAypj10R5vY5UX5IgRJET1zNYnH0wvcXxfNXV+s8jtwN2UXiQ=="}]}}""" - assertEquals(expectedJsonPayload, jsonPayload) + assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CpEBCo4BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm4KLHRlcnJhMWhzazZqcnl5cWpmaHA1ZGhjNTV0YzlqdGNreWd4MGVwMzdoZGQyEix0ZXJyYTFqbGdhcXk5bnZuMmhmNXQyc3JhOXljejhzNzd3bmY5bDBrbWdjcBoQCgV1bHVuYRIHMTAwMDAwMBJoClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECVyhuw/N9M1V7u6oACyd0SskCOqmWfK51oYHR/5H6ncUSBAoCCAEYARIUCg4KBXVsdW5hEgUzMDAwMBDAmgwaQPh0C3rjzdixIUiyPx3FlWAxzbKILNAcSRVeQnaTl1vsI5DEfYa2oYlUBLqyilcMCcU/iaJLhex30No2ak0Zn1Q=\"}") + assertEquals(output.error, "") + } + + @Test + fun testSigningWasmTerraTransferTx() { + val key = + PrivateKey("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616".toHexByteArray()) + val publicKey = key.getPublicKeySecp256k1(true) + val from = AnyAddress(publicKey, TERRAV2).description() + + val wasmTransferMessage = Cosmos.Message.WasmExecuteContractTransfer.newBuilder().apply { + senderAddress = from + contractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76" + amount = ByteString.copyFrom("0x3D090".toHexByteArray()) // 250000 + recipientAddress = "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp" + }.build() + + val message = Cosmos.Message.newBuilder().apply { + wasmExecuteContractTransferMessage = wasmTransferMessage + }.build() + + val feeAmount = Cosmos.Amount.newBuilder().apply { + amount = "3000" + denom = "uluna" + }.build() + + val cosmosFee = Cosmos.Fee.newBuilder().apply { + gas = 200000 + addAllAmounts(listOf(feeAmount)) + }.build() + + val signingInput = Cosmos.SigningInput.newBuilder().apply { + signingMode = SigningMode.Protobuf + accountNumber = 3407705 + chainId = "phoenix-1" + memo = "" + sequence = 3 + fee = cosmosFee + privateKey = ByteString.copyFrom(key.data()) + addAllMessages(listOf(message)) + }.build() + + val output = AnySigner.sign(signingInput, TERRAV2, SigningOutput.parser()) + assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CuUBCuIBCiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QSuQEKLHRlcnJhMTh3dWtwODRkcTIyN3d1NG1naDBqbTZuOW5sbmo2cnM4MnBwOXdmEix0ZXJyYTE0ejU2bDBmcDJsc2Y4Nnp5M2h0eTJ6NDdlemtobnRodHI5eXE3NhpbeyJ0cmFuc2ZlciI6eyJhbW91bnQiOiIyNTAwMDAiLCJyZWNpcGllbnQiOiJ0ZXJyYTFqbGdhcXk5bnZuMmhmNXQyc3JhOXljejhzNzd3bmY5bDBrbWdjcCJ9fRJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYAxITCg0KBXVsdW5hEgQzMDAwEMCaDBpAiBGbQaj+jsXE6/FssD3fC77QOxpli9GqsPea+KoNyMIEgVj89Hii+oU1bAEQS4qV0SaE2V6RNy24uCcFTIRbcQ==\"}") + assertEquals(output.error, "") } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tezos/TestTezosSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tezos/TestTezosSigner.kt index f84b3edb766..f14a6a6c689 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tezos/TestTezosSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tezos/TestTezosSigner.kt @@ -1,10 +1,13 @@ -package com.trustwallet.core.app.blockchains.waves +package com.trustwallet.core.app.blockchains.tezos +import com.trustwallet.core.app.utils.Numeric import com.trustwallet.core.app.utils.toHexByteArray +import com.trustwallet.core.app.utils.toHexBytesInByteString import org.junit.Assert.* import org.junit.Test import wallet.core.jni.CoinType.TEZOS import wallet.core.java.AnySigner +import wallet.core.jni.proto.Tezos.* class TestTezosTransactionSigner { @@ -12,6 +15,115 @@ class TestTezosTransactionSigner { System.loadLibrary("TrustWalletCore") } + @Test + fun testSigningFA2() { + val key = + "363265a0b3f06661001cab8b4f3ca8fd97ae70608184979cf7300836f57ec2d6".toHexBytesInByteString() + + val transferInfos = Txs.newBuilder() + .setAmount("10") + .setTokenId("0") + .setTo("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP") + .build() + + val txObj = TxObject.newBuilder() + .setFrom("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP") + .addTxs(transferInfos) + .build() + + val fa12 = FA2Parameters.newBuilder() + .setEntrypoint("transfer") + .addTxsObject(txObj) + .build() + + val parameters = OperationParameters.newBuilder() + .setFa2Parameters(fa12) + .build() + + val transactionData = TransactionOperationData.newBuilder() + .setAmount(0) + .setDestination("KT1DYk1XDzHredJq1EyNkDindiWDqZyekXGj") + .setParameters(parameters) + .build() + + val transaction = Operation.newBuilder() + .setSource("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP") + .setFee(100000) + .setCounter(2993173) + .setGasLimit(100000) + .setStorageLimit(0) + .setKind(Operation.OperationKind.TRANSACTION) + .setTransactionOperationData(transactionData) + .build(); + + val operationList = OperationList.newBuilder() + .setBranch("BKvEAX9HXfJZWYfTQbR1C7B3ADoKY6a1aKVRF7qQqvc9hS8Rr3m") + .addOperations(transaction) + .build(); + + val signingInput = SigningInput.newBuilder() + .setPrivateKey(key) + .setOperationList(operationList) + .build() + + val result = AnySigner.sign(signingInput, TEZOS, SigningOutput.parser()) + + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.encoded.toByteArray())), + "1b1f9345dc9f77bd24b09034d1d2f9a28f02ac837f49db54b8d68341f53dc4b76c00fe2ce0cccc0214af521ad60c140c5589b4039247a08d0695d8b601a08d0600000136767f88850bae28bfb9f46b73c5e87ede4de12700ffff087472616e7366657200000066020000006107070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b5550020000003107070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555007070000000a552d24710d6c59383286700c6c2917b25a6c1fa8b587e593c289dd47704278796792f1e522c1623845ec991e292b0935445e6994850bd03f035a006c5ed93806" + ) + } + + @Test + fun testSigningFA12() { + val key = + "363265a0b3f06661001cab8b4f3ca8fd97ae70608184979cf7300836f57ec2d6".toHexBytesInByteString() + + val fa12 = FA12Parameters.newBuilder() + .setEntrypoint("transfer") + .setFrom("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP") + .setTo("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP") + .setValue("123") + .build() + + val parameters = OperationParameters.newBuilder() + .setFa12Parameters(fa12) + .build() + + val transactionData = TransactionOperationData.newBuilder() + .setAmount(0) + .setDestination("KT1EwXFWoG9bYebmF4pYw72aGjwEnBWefgW5") + .setParameters(parameters) + .build() + + val transaction = Operation.newBuilder() + .setSource("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP") + .setFee(100000) + .setCounter(2993172) + .setGasLimit(100000) + .setStorageLimit(0) + .setKind(Operation.OperationKind.TRANSACTION) + .setTransactionOperationData(transactionData) + .build(); + + val operationList = OperationList.newBuilder() + .setBranch("BL8euoCWqNCny9AR3AKjnpi38haYMxjei1ZqNHuXMn19JSQnoWp") + .addOperations(transaction) + .build(); + + val signingInput = SigningInput.newBuilder() + .setPrivateKey(key) + .setOperationList(operationList) + .build() + + val result = AnySigner.sign(signingInput, TEZOS, SigningOutput.parser()) + + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.encoded.toByteArray())), + "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016c00fe2ce0cccc0214af521ad60c140c5589b4039247a08d0694d8b601a08d0600000145bd8a65cc48159d8ea60a55df735b7c5ad45f0e00ffff087472616e736665720000005907070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555007070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555000bb012914d768155fba2df319a81136e8e3e573b9cadb1676834490c90212615d271da029b6b0531e290e9063bcdb40bea43627af048b18e036f02be2b6b22fc8b307" + ) + } + @Test fun testSigningJSON() { val json = """ @@ -43,9 +155,13 @@ class TestTezosTransactionSigner { } } """ - val key = "2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6f".toHexByteArray() + val key = + "2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6f".toHexByteArray() val result = AnySigner.signJSON(json, key, TEZOS.value()) assertTrue(AnySigner.supportsJSON(TEZOS.value())) - assertEquals(result, "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016b0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80992f001f44e810200429a986c8072a40a1f3a3e2ab5a5819bb1b2fb69993c5004837815b9dc55923e6c0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80993f001f44e810201000081faa75f741ef614b0e35fcc8c90dfa3b0b957210001b86398d5b9be737dca8e4106ea18d70e69b75e92f892fb283546a99152b8d7794b919c0fbf1c31de386069a60014491c0e7505adef5781cead1cfe6608030b") + assertEquals( + result, + "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016b0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80992f001f44e810200429a986c8072a40a1f3a3e2ab5a5819bb1b2fb69993c5004837815b9dc55923e6c0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80993f001f44e810201000081faa75f741ef614b0e35fcc8c90dfa3b0b957210001b86398d5b9be737dca8e4106ea18d70e69b75e92f892fb283546a99152b8d7794b919c0fbf1c31de386069a60014491c0e7505adef5781cead1cfe6608030b" + ) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORChainSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORChainSigner.kt index 41021609528..2314586d13e 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORChainSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORChainSigner.kt @@ -18,6 +18,7 @@ import wallet.core.java.AnySigner import wallet.core.jni.CoinType.THORCHAIN import wallet.core.jni.proto.Cosmos import wallet.core.jni.proto.Cosmos.SigningOutput +import wallet.core.jni.proto.Cosmos.SigningMode import wallet.core.jni.* class TestTHORChainSigner { @@ -31,47 +32,49 @@ class TestTHORChainSigner { val key = PrivateKey("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e".toHexByteArray()) val publicKey = key.getPublicKeySecp256k1(true) - val from = AnyAddress(publicKey, THORCHAIN).description() + val from = AnyAddress(publicKey, THORCHAIN).data() + val to = AnyAddress("thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn", THORCHAIN).data() val txAmount = Cosmos.Amount.newBuilder().apply { - amount = 2000000 + amount = "38000000" denom = "rune" }.build() - val sendCoinsMsg = Cosmos.Message.Send.newBuilder().apply { - fromAddress = from - toAddress = "thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn" + val sendCoinsMsg = Cosmos.Message.THORChainSend.newBuilder().apply { + fromAddress = ByteString.copyFrom(from) + toAddress = ByteString.copyFrom(to) addAllAmounts(listOf(txAmount)) }.build() val message = Cosmos.Message.newBuilder().apply { - sendCoinsMessage = sendCoinsMsg + thorchainSendMessage = sendCoinsMsg }.build() val feeAmount = Cosmos.Amount.newBuilder().apply { - amount = 2000000 + amount = "200" denom = "rune" }.build() val cosmosFee = Cosmos.Fee.newBuilder().apply { - gas = 200000 + gas = 2500000 addAllAmounts(listOf(feeAmount)) }.build() val signingInput = Cosmos.SigningInput.newBuilder().apply { + signingMode = SigningMode.Protobuf + chainId = "thorchain-mainnet-v1" accountNumber = 593 - chainId = "thorchain" + sequence = 21 memo = "" - sequence = 2 fee = cosmosFee privateKey = ByteString.copyFrom(key.data()) addAllMessages(listOf(message)) }.build() val output = AnySigner.sign(signingInput, THORCHAIN, SigningOutput.parser()) - val jsonPayload = output.json - val expectedJsonPayload = """{"mode":"block","tx":{"fee":{"amount":[{"amount":"2000000","denom":"rune"}],"gas":"200000"},"memo":"","msg":[{"type":"thorchain/MsgSend","value":{"amount":[{"amount":"2000000","denom":"rune"}],"from_address":"thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r","to_address":"thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A+2Zfjls9CkvX85aQrukFZnM1dluMTFUp8nqcEneMXx3"},"signature":"SsagpldYbikqjq0sw4IM8oo3l2FE4iPi1kPxJgQ6PhdT/RRmg4BEAq3weB+hTOp4SfFXI/r+wms7tKkZci6SbA=="}]}}""" - assertEquals(expectedJsonPayload, jsonPayload) + assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"ClIKUAoOL3R5cGVzLk1zZ1NlbmQSPgoUFSLnZ9tusZcIsAOAKb+9YHvJvQ4SFMqGRZ+wBVHH30JUDF54aRksgzrbGhAKBHJ1bmUSCDM4MDAwMDAwEmYKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQPtmX45bPQpL1/OWkK7pBWZzNXZbjExVKfJ6nBJ3jF8dxIECgIIARgVEhIKCwoEcnVuZRIDMjAwEKDLmAEaQKZtS3ATa26OOGvqdKm14ZbHeNfkPtIajXi5MkZ5XaX2SWOeX+YnCPZ9TxF9Jj5cVIo71m55xq4hVL3yDbRe89g=\"}") + assertEquals(output.error, "") + assertEquals(output.json, "") } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORSwapSigning.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORSwapSigning.kt new file mode 100644 index 00000000000..2d1d3caf77e --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORSwapSigning.kt @@ -0,0 +1,71 @@ +package com.trustwallet.core.app.blockchains.thorchainswap + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import wallet.core.jni.PrivateKey +import wallet.core.java.AnySigner +import wallet.core.jni.CoinType.ETHEREUM +import wallet.core.jni.proto.Ethereum.SigningOutput +import wallet.core.jni.proto.THORChainSwap +import wallet.core.jni.THORChainSwap.buildSwap +import com.trustwallet.core.app.utils.Numeric + +class TestTHORChainSwap { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testSwapEthBnb() { + // prepare swap input + val input = THORChainSwap.SwapInput.newBuilder() + input.apply { + fromChain = THORChainSwap.Chain.ETH + fromAddress = "0xb9f5771c27664bf2282d98e09d7f50cec7cb01a7" + toAsset = THORChainSwap.Asset.newBuilder().apply { + chain = THORChainSwap.Chain.BNB + symbol = "BNB" + tokenId = "" + }.build() + toAddress = "bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx" + vaultAddress = "0x1091c4De6a3cF09CdA00AbDAeD42c7c3B69C83EC" + routerAddress = "0x42A5Ed456650a09Dc10EBc6361A7480fDd61f27B" + fromAmount = "50000000000000000" + toAmountLimit = "600003" + } + + // serialize input + val inputSerialized = input.build().toByteArray() + assertEquals(Numeric.toHexString(inputSerialized), "0x0802122a3078623966353737316332373636346266323238326439386530396437663530636563376362303161371a0708031203424e42222a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372782a2a307831303931633444653661336346303943644130304162444165443432633763334236394338334543322a3078343241354564343536363530613039446331304542633633363141373438306644643631663237423a1135303030303030303030303030303030304206363030303033") + + // invoke swap + val outputData = buildSwap(inputSerialized) + assertEquals(outputData.count(), 311) + + // parse result in proto + val outputProto = THORChainSwap.SwapOutput.newBuilder().mergeFrom(outputData) + assertEquals(outputProto.fromChain, THORChainSwap.Chain.ETH) + assertEquals(outputProto.toChain, THORChainSwap.Chain.BNB) + assertEquals(outputProto.error.code, THORChainSwap.ErrorCode.OK) + assertTrue(outputProto.hasEthereum()) + val txInput = outputProto.ethereum + + // set few fields before signing + val txInputFull = txInput.toBuilder().apply { + chainId = ByteString.copyFrom("0x01".toHexByteArray()) + nonce = ByteString.copyFrom("0x03".toHexByteArray()) + gasPrice = ByteString.copyFrom("0x06FC23AC00".toHexByteArray()) + gasLimit = ByteString.copyFrom("0x013880".toHexByteArray()) + privateKey = ByteString.copyFrom(PrivateKey("0x4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904".toHexByteArray()).data()) + }.build() + + // sign and encode resulting input + val output = AnySigner.sign(txInputFull, ETHEREUM, SigningOutput.parser()) + + assertEquals(Numeric.toHexString(output.encoded.toByteArray()), "0xf90151038506fc23ac00830138809442a5ed456650a09dc10ebc6361a7480fdd61f27b87b1a2bc2ec50000b8e41fece7b40000000000000000000000001091c4de6a3cf09cda00abdaed42c7c3b69c83ec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b1a2bc2ec500000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000003e535741503a424e422e424e423a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a363030303033000025a06ae104be3201baca38315352f81fac70ca4dd47339981914e64e91149813e780a066a3f0b2c44ddf5a96a38481274f623f552a593d723237d6742185f4885c0064") + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/waves/TestWavesAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/waves/TestWavesAddress.kt index d0669b9feca..ef8b4217e6c 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/waves/TestWavesAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/waves/TestWavesAddress.kt @@ -18,7 +18,7 @@ class TestAddress { val pubkey = key.publicKeyCurve25519 val address = AnyAddress(pubkey, CoinType.WAVES) - assertEquals(pubkey.data().toHex(), "0x559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d".toLowerCase()) + assertEquals(pubkey.data().toHex(), "0x559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d".lowercase()) assertEquals(address.description(), "3P2uzAzX9XTu1t32GkWw68YFFLwtapWvDds") } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestBase32.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestBase32.kt new file mode 100644 index 00000000000..d564eff45d8 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestBase32.kt @@ -0,0 +1,35 @@ +package com.trustwallet.core.app.utils + +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.Base32 + +class TestBase32 { + init { + System.loadLibrary("TrustWalletCore"); + } + + @Test + fun testEncode() { + assertEquals(Base32.encode("HelloWorld".toByteArray()), "JBSWY3DPK5XXE3DE") + } + + @Test + fun testEncodeWithAlphabet() { + assertEquals(Base32.encodeWithAlphabet("7uoq6tp427uzv7fztkbsnn64iwotfrristwpryy".toByteArray(), "abcdefghijklmnopqrstuvwxyz234567"), "g52w64jworydimrxov5hmn3gpj2gwyttnzxdmndjo5xxiztsojuxg5dxobzhs6i") + } + + @Test + fun testDecode() { + var decoded = Base32.decode("JBSWY3DPK5XXE3DE") + + assertEquals(String(decoded, Charsets.UTF_8), "HelloWorld") + } + + @Test + fun testDecodeWithAlphabet() { + var decoded = Base32.decodeWithAlphabet("g52w64jworydimrxov5hmn3gpj2gwyttnzxdmndjo5xxiztsojuxg5dxobzhs6i", "abcdefghijklmnopqrstuvwxyz234567") + + assertEquals(String(decoded, Charsets.UTF_8), "7uoq6tp427uzv7fztkbsnn64iwotfrristwpryy") + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestBase64.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestBase64.kt new file mode 100644 index 00000000000..b69851da1b8 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestBase64.kt @@ -0,0 +1,34 @@ +package com.trustwallet.core.app.utils + +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.Base64 + +class TestBase64 { + init { + System.loadLibrary("TrustWalletCore"); + } + + @Test + fun testEncode() { + assertEquals(Base64.encode("HelloWorld".toByteArray()), "SGVsbG9Xb3JsZA==") + } + + @Test + fun testDecode() { + val decoded = Base64.decode("SGVsbG9Xb3JsZA==") + assertEquals(String(decoded, Charsets.UTF_8), "HelloWorld") + } + + @Test + fun testEncodeUrl() { + assertEquals(Base64.encodeUrl("+\\?ab".toByteArray()), "K1w_YWI=") + } + + @Test + fun testDecodeUrl() { + val decoded = Base64.decodeUrl("K1w_YWI=") + assertEquals(String(decoded, Charsets.UTF_8), "+\\?ab") + } +} + diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestData.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestData.kt index 439c74f99ab..4df94d6b558 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestData.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestData.kt @@ -15,25 +15,22 @@ class TestData { assertEquals(Numeric.toHexString(data), "0x01020304") } + @Test fun testUsingExtensions() { - { - val data = "01020304".toHexBytes() - assertEquals(data.toHex(), "0x01020304") - } - { // with prefix - val data = "0x01020304".toHexBytes() - assertEquals(data.toHex(), "0x01020304") - } + val data = "01020304".toHexBytes() + assertEquals(data.toHex(), "0x01020304") + + // with prefix + val data2 = "0x01020304".toHexBytes() + assertEquals(data2.toHex(), "0x01020304") } + @Test fun testOddLength() { - { - val data = "0x0".toHexBytes() - assertEquals(data.toHex(), "0x00") - } - { - val data = "0x28fa6ae00".toHexBytes() - assertEquals(data.toHex(), "0x28fa6ae00") - } + val data = "0x0".toHexBytes() + assertEquals(data.toHex(), "0x00") + + val data2 = "0x28fa6ae00".toHexBytes() + assertEquals(data2.toHex(), "0x028fa6ae00") } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestPrivateKey.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestPrivateKey.kt index c6d069ae45b..abdbc8050ab 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestPrivateKey.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestPrivateKey.kt @@ -69,10 +69,10 @@ class TestPrivateKey { @Test fun testGetSharedKey() { val privateKeyData = "9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0".toHexBytes() - val privateKey = PrivateKey(privateKeyData)!! + val privateKey = PrivateKey(privateKeyData) val publicKeyData = "02a18a98316b5f52596e75bfa5ca9fa9912edd0c989b86b73d41bb64c9c6adb992".toHexBytes() - val publicKey = PublicKey(publicKeyData, PublicKeyType.SECP256K1)!! + val publicKey = PublicKey(publicKeyData, PublicKeyType.SECP256K1) val derivedData = privateKey.getSharedKey(publicKey, Curve.SECP256K1) assertNotNull(derivedData) @@ -83,10 +83,10 @@ class TestPrivateKey { @Test fun testGetSharedKeyWycherproof() { val privateKeyData = "f4b7ff7cccc98813a69fae3df222bfe3f4e28f764bf91b4a10d8096ce446b254".toHexBytes() - val privateKey = PrivateKey(privateKeyData)!! + val privateKey = PrivateKey(privateKeyData) val publicKeyData = "02d8096af8a11e0b80037e1ee68246b5dcbb0aeb1cf1244fd767db80f3fa27da2b".toHexBytes() - val publicKey = PublicKey(publicKeyData, PublicKeyType.SECP256K1)!! + val publicKey = PublicKey(publicKeyData, PublicKeyType.SECP256K1) val derivedData = privateKey.getSharedKey(publicKey, Curve.SECP256K1) assertNotNull(derivedData) @@ -97,11 +97,11 @@ class TestPrivateKey { @Test fun testGetSharedKeyBidirectional() { val privateKeyData1 = "9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0".toHexBytes() - val privateKey1 = PrivateKey(privateKeyData1)!! + val privateKey1 = PrivateKey(privateKeyData1) val publicKey1 = privateKey1.getPublicKeySecp256k1(true) val privateKeyData2 = "ef2cf705af8714b35c0855030f358f2bee356ff3579cea2607b2025d80133c3a".toHexBytes() - val privateKey2 = PrivateKey(privateKeyData2)!! + val privateKey2 = PrivateKey(privateKeyData2) val publicKey2 = privateKey2.getPublicKeySecp256k1(true) val derivedData1 = privateKey1.getSharedKey(publicKey2, Curve.SECP256K1) @@ -116,10 +116,10 @@ class TestPrivateKey { @Test fun testGetSharedKeyError() { val privateKeyData = "9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0".toHexBytes() - val privateKey = PrivateKey(privateKeyData)!! + val privateKey = PrivateKey(privateKeyData) val publicKeyData = "02a18a98316b5f52596e75bfa5ca9fa9912edd0c989b86b73d41bb64c9c6adb992".toHexBytes() - val publicKey = PublicKey(publicKeyData, PublicKeyType.SECP256K1)!! + val publicKey = PublicKey(publicKeyData, PublicKeyType.SECP256K1) val derivedData = privateKey.getSharedKey(publicKey, Curve.ED25519) assertNull(derivedData) diff --git a/android/build.gradle b/android/build.gradle index 4ee76b45799..b1ca946f2d1 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.4.21' + ext.kotlin_version = '1.6.10' repositories { google() mavenCentral() @@ -10,7 +10,7 @@ buildscript { } } dependencies { - classpath 'com.android.tools.build:gradle:4.2.1' + classpath 'com.android.tools.build:gradle:4.2.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 493d6573668..4cf5f4720c8 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Mar 16 12:35:47 JST 2021 +#Wed Jan 19 18:01:57 JST 2022 distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.2-bin.zip distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip +zipStoreBase=GRADLE_USER_HOME diff --git a/android/trustwalletcore/build.gradle b/android/trustwalletcore/build.gradle index f1c8028a4d1..bf97bfd2412 100644 --- a/android/trustwalletcore/build.gradle +++ b/android/trustwalletcore/build.gradle @@ -3,11 +3,10 @@ apply plugin: 'maven-publish' group='com.github.trustwallet' android { - compileSdkVersion 28 - ndkVersion '21.2.6472646' + compileSdkVersion 32 + ndkVersion '23.1.7779620' defaultConfig { minSdkVersion 23 - targetSdkVersion 29 versionCode 1 versionName "1.0" externalNativeBuild { @@ -30,7 +29,7 @@ android { minifyEnabled false // limit platforms built for testing ndk { - abiFilters 'x86' + abiFilters 'x86', 'arm64-v8a' } } } @@ -41,16 +40,14 @@ android { externalNativeBuild { cmake { - version "3.10.2" + version "3.18.1" path "../../CMakeLists.txt" } } } dependencies { - implementation 'io.grpc:grpc-protobuf:1.34.0' + implementation 'com.google.protobuf:protobuf-javalite:3.21.2' } apply from: 'maven-push.gradle' - - diff --git a/bootstrap.sh b/bootstrap.sh index e5c948496d8..1dba588dd30 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -1,27 +1,12 @@ #!/usr/bin/env bash +# +# Initializes the workspace with dependencies, then performs full build # Fail if any commands fails set -e -echo "#### Initializing... ####" +echo "#### Initializing workspace with dependencies ... ####" tools/install-dependencies -echo "#### Generating files... ####" -tools/generate-files - -echo "#### Building... ####" -cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug -make -Cbuild -j12 tests TrezorCryptoTests - -if [ -x "$(command -v clang-tidy)" ]; then - echo "#### Linting... ####" - tools/lint -fi - -echo "#### Testing... ####" -export CK_TIMEOUT_MULTIPLIER=4 -build/trezor-crypto/crypto/tests/TrezorCryptoTests - -ROOT="`dirname \"$0\"`" -TESTS_ROOT="`(cd \"$ROOT/tests\" && pwd)`" -build/tests/tests "$TESTS_ROOT" +echo "#### Building and running tests ... ####" +tools/build-and-test diff --git a/cmake/CompilerWarnings.cmake b/cmake/CompilerWarnings.cmake new file mode 100644 index 00000000000..d7d54fd984e --- /dev/null +++ b/cmake/CompilerWarnings.cmake @@ -0,0 +1,95 @@ +macro(target_enable_asan target) + message("-- ASAN Enabled, Configuring...") + target_compile_options(${target} PUBLIC + $<$,$>:-fsanitize=address -fno-omit-frame-pointer> + $<$,$>:-fsanitize=address -fno-omit-frame-pointer>) + target_link_options(${target} PUBLIC + $<$,$>:-fsanitize=address -fno-omit-frame-pointer> + $<$,$>:-fsanitize=address -fno-omit-frame-pointer>) +endmacro() + +macro(target_enable_coverage target) + message(STATUS "Code coverage ON") + # This option is used to compile and link code instrumented for coverage analysis. + # The option is a synonym for -fprofile-arcs -ftest-coverage (when compiling) and -lgcov (when linking). + # See the documentation for those options for more details. + # https://gcc.gnu.org/onlinedocs/gcc-9.3.0/gcc/Instrumentation-Options.html + if (TW_IDE_CLION) + message(STATUS "Code coverage for Clion ON") + target_compile_options(${target} PUBLIC + $<$,$>:-fprofile-instr-generate -fcoverage-mapping> + $<$,$>:-fprofile-instr-generate -fcoverage-mapping> + $<$,$>:-fprofile-instr-generate -fcoverage-mapping>) + target_link_options(${target} PUBLIC + $<$,$>:-fprofile-instr-generate -fcoverage-mapping> + $<$,$>:-fprofile-instr-generate -fcoverage-mapping> + $<$,$>:-fprofile-instr-generate -fcoverage-mapping>) + else() + target_compile_options(${target} PUBLIC + $<$,$>:--coverage> + $<$,$>:--coverage>) + target_link_options(${target} PUBLIC + $<$,$>:--coverage> + $<$,$>:--coverage>) + endif () +endmacro() + +add_library(tw_error_settings INTERFACE) +add_library(tw::error_settings ALIAS tw_error_settings) + +add_library(tw_defaults_features INTERFACE) +add_library(tw::defaults_features ALIAS tw_defaults_features) + +add_library(tw_optimize_settings INTERFACE) +add_library(tw::optimize_settings ALIAS tw_optimize_settings) + +if(NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "MSVC")) + target_compile_options( + tw_error_settings + INTERFACE + -Wall + -Wextra # reasonable and standard + -Wfatal-errors # short error report + -Wshadow # warn the user if a variable declaration shadows one from a + -Wshorten-64-to-32 + -Wno-nullability-completeness + # parent context + -Wnon-virtual-dtor # warn the user if a class with virtual functions has a + # non-virtual destructor. This helps catch hard to track down memory errors + -Wcast-align # warn for potential performance problem casts + #-Wunused # warn on anything being unused + -Woverloaded-virtual # warn if you overload (not override) a virtual + # function + -Wnull-dereference # warn if a null dereference is detected + -Wdouble-promotion # warn if float is implicit promoted to double + -Wformat=2 # warn on security issues around functions that format output + ) +endif () + + + + +if (TW_WARNINGS_AS_ERRORS) + target_compile_options( + tw_error_settings + INTERFACE + -Werror + ) +endif () + +target_compile_features(tw_defaults_features INTERFACE cxx_std_20) + +target_compile_options(tw_optimize_settings INTERFACE + $<$,$>:-O0 -g> + $<$,$>:-O0 -g> + $<$,$>:-O2> + $<$,$>:-O2> + ) + +function(set_project_warnings project_name) + target_link_libraries(${project_name} INTERFACE tw::error_settings tw::defaults_features tw::optimize_settings) + + if (NOT TARGET ${project_name}) + message(AUTHOR_WARNING "${project_name} is not a target, thus no compiler warning were added.") + endif () +endfunction() diff --git a/cmake/FindHostPackage.cmake b/cmake/FindHostPackage.cmake new file mode 100644 index 00000000000..188a2ee7e9d --- /dev/null +++ b/cmake/FindHostPackage.cmake @@ -0,0 +1,9 @@ +macro(find_host_package) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER) + find_package(${ARGN}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endmacro(find_host_package) diff --git a/cmake/PVS-Studio.cmake b/cmake/PVS-Studio.cmake new file mode 100644 index 00000000000..d19bc3b105e --- /dev/null +++ b/cmake/PVS-Studio.cmake @@ -0,0 +1,615 @@ +# 2006-2008 (c) Viva64.com Team +# 2008-2018 (c) OOO "Program Verification Systems" +# +# Version 12 + +cmake_minimum_required(VERSION 3.0.0) +cmake_policy(SET CMP0054 NEW) + +if (PVS_STUDIO_AS_SCRIPT) + # This code runs at build time. + # It executes pvs-studio-analyzer and propagates its return value. + + set(in_cl_params FALSE) + set(additional_args) + + foreach (arg ${PVS_STUDIO_COMMAND}) + if (NOT in_cl_params) + if ("${arg}" STREQUAL "--cl-params") + set(in_cl_params TRUE) + endif () + else () + # A workaround for macOS frameworks (e.g. QtWidgets.framework) + # You can test this workaround on this project: https://github.com/easyaspi314/MidiEditor/tree/gba + if (APPLE AND "${arg}" MATCHES "^-I(.*)\\.framework$") + STRING(REGEX REPLACE "^-I(.*)\\.framework$" "\\1.framework" framework "${arg}") + if (IS_ABSOLUTE "${framework}") + get_filename_component(framework "${framework}" DIRECTORY) + list(APPEND additional_args "-iframework") + list(APPEND additional_args "${framework}") + endif () + endif () + endif () + endforeach () + + file(REMOVE "${PVS_STUDIO_LOG_FILE}") + execute_process(COMMAND ${PVS_STUDIO_COMMAND} ${additional_args} + RESULT_VARIABLE result + OUTPUT_VARIABLE output + ERROR_VARIABLE error) + + if (result AND NOT output MATCHES "^No compilation units were found\\.") + message(FATAL_ERROR "PVS-Studio exited with non-zero code.\nStdout:\n${output}\nStderr:\n${error}\n") + endif() + + return() +endif () + +if(__PVS_STUDIO_INCLUDED) + return() +endif() +set(__PVS_STUDIO_INCLUDED TRUE) + +set(PVS_STUDIO_SCRIPT "${CMAKE_CURRENT_LIST_FILE}") + +function (pvs_studio_log TEXT) + if (PVS_STUDIO_DEBUG) + message("PVS-Studio: ${TEXT}") + endif () +endfunction () + +function (pvs_studio_relative_path VAR ROOT FILEPATH) + if (WIN32) + STRING(REGEX REPLACE "\\\\" "/" ROOT ${ROOT}) + STRING(REGEX REPLACE "\\\\" "/" FILEPATH ${FILEPATH}) + endif() + set("${VAR}" "${FILEPATH}" PARENT_SCOPE) + if (IS_ABSOLUTE "${FILEPATH}") + file(RELATIVE_PATH RPATH "${ROOT}" "${FILEPATH}") + if (NOT IS_ABSOLUTE "${RPATH}") + set("${VAR}" "${RPATH}" PARENT_SCOPE) + endif() + endif() +endfunction () + +function (pvs_studio_join_path VAR DIR1 DIR2) + if ("${DIR2}" MATCHES "^(/|~|.:/).*$" OR "${DIR1}" STREQUAL "") + set("${VAR}" "${DIR2}" PARENT_SCOPE) + else () + set("${VAR}" "${DIR1}/${DIR2}" PARENT_SCOPE) + endif () +endfunction () + +macro (pvs_studio_append_flags_from_property CXX C DIR PREFIX) + if (NOT "${PROPERTY}" STREQUAL "NOTFOUND" AND NOT "${PROPERTY}" STREQUAL "PROPERTY-NOTFOUND") + foreach (PROP ${PROPERTY}) + pvs_studio_join_path(PROP "${DIR}" "${PROP}") + + if (APPLE AND "${PREFIX}" STREQUAL "-I" AND IS_ABSOLUTE "${PROP}" AND "${PROP}" MATCHES "\\.framework$") + get_filename_component(FRAMEWORK "${PROP}" DIRECTORY) + list(APPEND "${CXX}" "-iframework") + list(APPEND "${CXX}" "${FRAMEWORK}") + list(APPEND "${C}" "-iframework") + list(APPEND "${C}" "${FRAMEWORK}") + pvs_studio_log("framework: ${FRAMEWORK}") + elseif (NOT "${PROP}" STREQUAL "") + list(APPEND "${CXX}" "${PREFIX}${PROP}") + list(APPEND "${C}" "${PREFIX}${PROP}") + endif() + endforeach () + endif () +endmacro () + +macro (pvs_studio_append_standard_flag FLAGS STANDARD) + if ("${STANDARD}" MATCHES "^(99|11|14|17|20)$") + if ("${PVS_STUDIO_PREPROCESSOR}" MATCHES "gcc|clang") + list(APPEND "${FLAGS}" "-std=c++${STANDARD}") + endif () + endif () +endmacro () + +function (pvs_studio_set_directory_flags DIRECTORY CXX C) + set(CXX_FLAGS "${${CXX}}") + set(C_FLAGS "${${C}}") + + get_directory_property(PROPERTY DIRECTORY "${DIRECTORY}" INCLUDE_DIRECTORIES) + pvs_studio_append_flags_from_property(CXX_FLAGS C_FLAGS "${DIRECTORY}" "-I") + + get_directory_property(PROPERTY DIRECTORY "${DIRECTORY}" COMPILE_DEFINITIONS) + pvs_studio_append_flags_from_property(CXX_FLAGS C_FLAGS "" "-D") + + set("${CXX}" "${CXX_FLAGS}" PARENT_SCOPE) + set("${C}" "${C_FLAGS}" PARENT_SCOPE) +endfunction () + +function (pvs_studio_set_target_flags TARGET CXX C) + set(CXX_FLAGS "${${CXX}}") + set(C_FLAGS "${${C}}") + + if (NOT MSVC) + list(APPEND CXX_FLAGS "$<$:--sysroot=${CMAKE_SYSROOT}>") + list(APPEND C_FLAGS "$<$:--sysroot=${CMAKE_SYSROOT}>") + endif () + + set(prop_incdirs "$") + list(APPEND CXX_FLAGS "$<$:-I$-I>>") + list(APPEND C_FLAGS "$<$:-I$-I>>") + + set(prop_compdefs "$") + list(APPEND CXX_FLAGS "$<$:-D$-D>>") + list(APPEND C_FLAGS "$<$:-D$-D>>") + + set(prop_compopt "$") + list(APPEND CXX_FLAGS "$<$:$>>") + list(APPEND C_FLAGS "$<$:$>>") + + set("${CXX}" "${CXX_FLAGS}" PARENT_SCOPE) + set("${C}" "${C_FLAGS}" PARENT_SCOPE) +endfunction () + +function (pvs_studio_set_source_file_flags SOURCE) + set(LANGUAGE "") + + string(TOLOWER "${SOURCE}" SOURCE_LOWER) + if ("${LANGUAGE}" STREQUAL "" AND "${SOURCE_LOWER}" MATCHES "^.*\\.(c|cpp|cc|cx|cxx|cp|c\\+\\+)$") + if ("${SOURCE}" MATCHES "^.*\\.c$") + set(LANGUAGE C) + else () + set(LANGUAGE CXX) + endif () + endif () + + if ("${LANGUAGE}" STREQUAL "C") + set(CL_PARAMS ${PVS_STUDIO_C_FLAGS} ${PVS_STUDIO_TARGET_C_FLAGS} -DPVS_STUDIO) + elseif ("${LANGUAGE}" STREQUAL "CXX") + set(CL_PARAMS ${PVS_STUDIO_CXX_FLAGS} ${PVS_STUDIO_TARGET_CXX_FLAGS} -DPVS_STUDIO) + endif () + + set(PVS_STUDIO_LANGUAGE "${LANGUAGE}" PARENT_SCOPE) + set(PVS_STUDIO_CL_PARAMS "${CL_PARAMS}" PARENT_SCOPE) +endfunction () + +function (pvs_studio_analyze_file SOURCE SOURCE_DIR BINARY_DIR) + set(PLOGS ${PVS_STUDIO_PLOGS}) + pvs_studio_set_source_file_flags("${SOURCE}") + + get_filename_component(SOURCE "${SOURCE}" REALPATH) + + get_source_file_property(PROPERTY "${SOURCE}" HEADER_FILE_ONLY) + if (PROPERTY) + return() + endif () + + pvs_studio_relative_path(SOURCE_RELATIVE "${SOURCE_DIR}" "${SOURCE}") + pvs_studio_join_path(SOURCE "${SOURCE_DIR}" "${SOURCE}") + + set(LOG "${BINARY_DIR}/PVS-Studio/${SOURCE_RELATIVE}.plog") + get_filename_component(LOG "${LOG}" REALPATH) + get_filename_component(PARENT_DIR "${LOG}" DIRECTORY) + + if (EXISTS "${SOURCE}" AND NOT TARGET "${LOG}" AND NOT "${PVS_STUDIO_LANGUAGE}" STREQUAL "") + # A workaround to support implicit dependencies for ninja generators. + set(depPvsArg) + set(depCommandArg) + if (CMAKE_VERSION VERSION_GREATER 3.6 AND "${CMAKE_GENERATOR}" STREQUAL "Ninja") + pvs_studio_relative_path(relLog "${CMAKE_BINARY_DIR}" "${LOG}") + set(depPvsArg --dep-file "${LOG}.d" --dep-file-target "${relLog}") + set(depCommandArg DEPFILE "${LOG}.d") + endif () + + # https://public.kitware.com/Bug/print_bug_page.php?bug_id=14353 + # https://public.kitware.com/Bug/file/5436/expand_command.cmake + # + # It is a workaround to expand generator expressions. + set(cmdline "${PVS_STUDIO_BIN}" analyze + --output-file "${LOG}" + --source-file "${SOURCE}" + ${depPvsArg} + ${PVS_STUDIO_ARGS} + --cl-params "${PVS_STUDIO_CL_PARAMS}" "${SOURCE}") + + string(REPLACE ";" "$" cmdline "${cmdline}") + set(pvscmd "${CMAKE_COMMAND}" + -D "PVS_STUDIO_AS_SCRIPT=TRUE" + -D "PVS_STUDIO_COMMAND=${cmdline}" + -D "PVS_STUDIO_LOG_FILE=${LOG}" + -P "${PVS_STUDIO_SCRIPT}" + ) + + add_custom_command(OUTPUT "${LOG}" + COMMAND "${CMAKE_COMMAND}" -E make_directory "${PARENT_DIR}" + COMMAND "${CMAKE_COMMAND}" -E remove_directory "${LOG}" + COMMAND ${pvscmd} + WORKING_DIRECTORY "${BINARY_DIR}" + DEPENDS "${SOURCE}" "${PVS_STUDIO_SUPPRESS_BASE}" "${PVS_STUDIO_DEPENDS}" + IMPLICIT_DEPENDS "${PVS_STUDIO_LANGUAGE}" "${SOURCE}" + ${depCommandArg} + VERBATIM + COMMENT "Analyzing ${PVS_STUDIO_LANGUAGE} file ${SOURCE_RELATIVE}") + list(APPEND PLOGS "${LOG}") + endif () + set(PVS_STUDIO_PLOGS "${PLOGS}" PARENT_SCOPE) +endfunction () + +function (pvs_studio_analyze_target TARGET DIR) + set(PVS_STUDIO_PLOGS "${PVS_STUDIO_PLOGS}") + set(PVS_STUDIO_TARGET_CXX_FLAGS "") + set(PVS_STUDIO_TARGET_C_FLAGS "") + + get_target_property(PROPERTY "${TARGET}" SOURCES) + pvs_studio_relative_path(BINARY_DIR "${CMAKE_SOURCE_DIR}" "${DIR}") + if ("${BINARY_DIR}" MATCHES "^/.*$") + pvs_studio_join_path(BINARY_DIR "${CMAKE_BINARY_DIR}" "PVS-Studio/__${BINARY_DIR}") + else () + pvs_studio_join_path(BINARY_DIR "${CMAKE_BINARY_DIR}" "${BINARY_DIR}") + endif () + + file(MAKE_DIRECTORY "${BINARY_DIR}") + + pvs_studio_set_directory_flags("${DIR}" PVS_STUDIO_TARGET_CXX_FLAGS PVS_STUDIO_TARGET_C_FLAGS) + pvs_studio_set_target_flags("${TARGET}" PVS_STUDIO_TARGET_CXX_FLAGS PVS_STUDIO_TARGET_C_FLAGS) + + if (NOT "${PROPERTY}" STREQUAL "NOTFOUND" AND NOT "${PROPERTY}" STREQUAL "PROPERTY-NOTFOUND") + foreach (SOURCE ${PROPERTY}) + pvs_studio_join_path(SOURCE "${DIR}" "${SOURCE}") + pvs_studio_analyze_file("${SOURCE}" "${DIR}" "${BINARY_DIR}") + endforeach () + endif () + + set(PVS_STUDIO_PLOGS "${PVS_STUDIO_PLOGS}" PARENT_SCOPE) +endfunction () + +set(PVS_STUDIO_RECURSIVE_TARGETS) +set(PVS_STUDIO_RECURSIVE_TARGETS_NEW) + +macro(pvs_studio_get_recursive_targets TARGET) + get_target_property(libs "${TARGET}" LINK_LIBRARIES) + foreach (lib IN LISTS libs) + list(FIND PVS_STUDIO_RECURSIVE_TARGETS "${lib}" index) + if (TARGET "${lib}" AND "${index}" STREQUAL -1) + get_target_property(target_type "${lib}" TYPE) + if (NOT "${target_type}" STREQUAL "INTERFACE_LIBRARY") + list(APPEND PVS_STUDIO_RECURSIVE_TARGETS "${lib}") + list(APPEND PVS_STUDIO_RECURSIVE_TARGETS_NEW "${lib}") + pvs_studio_get_recursive_targets("${lib}") + endif () + endif () + endforeach () +endmacro() + +option(PVS_STUDIO_DISABLE OFF "Disable PVS-Studio targets") +option(PVS_STUDIO_DEBUG OFF "Add debug info") + +# pvs_studio_add_target +# Target options: +# ALL add PVS-Studio target to default build (default: off) +# TARGET target name of analysis target (default: pvs) +# ANALYZE targets... targets to analyze +# RECURSIVE analyze target's dependencies (requires CMake 3.5+) +# COMPILE_COMMANDS use compile_commands.json instead of targets (specified by the 'ANALYZE' option) to determine files for analysis +# (set CMAKE_EXPORT_COMPILE_COMMANDS, available only for Makefile and Ninja generators) +# +# Output options: +# OUTPUT prints report to stdout +# LOG path path to report (default: ${CMAKE_CURRENT_BINARY_DIR}/PVS-Studio.log) +# FORMAT format format of report +# MODE mode analyzers/levels filter (default: GA:1,2) +# HIDE_HELP do not print help message +# +# Analyzer options: +# PLATFORM name linux32/linux64 (default: linux64) +# PREPROCESSOR name preprocessor type: gcc/clang (default: auto detected) +# LICENSE path path to PVS-Studio.lic (default: ~/.config/PVS-Studio/PVS-Studio.lic) +# CONFIG path path to PVS-Studio.cfg +# CFG_TEXT text embedded PVS-Studio.cfg +# SUPPRESS_BASE path to suppress base file +# KEEP_COMBINED_PLOG do not delete combined plog file *.pvs.raw for further processing with plog-converter +# +# Misc options: +# DEPENDS targets.. additional target dependencies +# SOURCES path... list of source files to analyze +# BIN path path to pvs-studio-analyzer (Unix) or CompilerCommandsAnalyzer.exe (Windows) +# CONVERTER path path to plog-converter (Unix) or HtmlGenerator.exe (Windows) +# C_FLAGS flags... additional C_FLAGS +# CXX_FLAGS flags... additional CXX_FLAGS +# ARGS args... additional pvs-studio-analyzer/CompilerCommandsAnalyzer.exe flags +# CONVERTER_ARGS args... additional plog-converter/HtmlGenerator.exe flags +function (pvs_studio_add_target) + macro (default VAR VALUE) + if ("${${VAR}}" STREQUAL "") + set("${VAR}" "${VALUE}") + endif () + endmacro () + + set(PVS_STUDIO_SUPPORTED_PREPROCESSORS "gcc|clang|visualcpp") + if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") + set(DEFAULT_PREPROCESSOR "clang") + elseif (MSVC) + set(DEFAULT_PREPROCESSOR "visualcpp") + else () + set(DEFAULT_PREPROCESSOR "gcc") + endif () + + set(OPTIONAL OUTPUT ALL RECURSIVE HIDE_HELP KEEP_COMBINED_PLOG COMPILE_COMMANDS KEEP_INTERMEDIATE_FILES) + set(SINGLE LICENSE CONFIG TARGET LOG FORMAT BIN CONVERTER PLATFORM PREPROCESSOR CFG_TEXT SUPPRESS_BASE) + set(MULTI SOURCES C_FLAGS CXX_FLAGS ARGS DEPENDS ANALYZE MODE CONVERTER_ARGS) + cmake_parse_arguments(PVS_STUDIO "${OPTIONAL}" "${SINGLE}" "${MULTI}" ${ARGN}) + + + default(PVS_STUDIO_C_FLAGS "") + default(PVS_STUDIO_CXX_FLAGS "") + default(PVS_STUDIO_TARGET "pvs") + default(PVS_STUDIO_LOG "PVS-Studio.log") + + set(PATHS) + + if (WIN32) + # The registry value is only read when you do some cache operation on it. + # https://stackoverflow.com/questions/1762201/reading-registry-values-with-cmake + GET_FILENAME_COMPONENT(ROOT "[HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\ProgramVerificationSystems\\PVS-Studio;installDir]" ABSOLUTE CACHE) + + if(EXISTS "${ROOT}") + set(PATHS "${ROOT}") + else() + set(ROOT "PROGRAMFILES(X86)") + set(ROOT "$ENV{${ROOT}}/PVS-Studio") + string(REPLACE \\ / ROOT "${ROOT}") + + if (EXISTS "${ROOT}") + set(PATHS "${ROOT}") + else() + set(ROOT "PATH") + set(ROOT "$ENV{${ROOT}}") + set(PATHS "${ROOT}") + endif () + endif() + + + + default(PVS_STUDIO_BIN "CompilerCommandsAnalyzer.exe") + default(PVS_STUDIO_CONVERTER "HtmlGenerator.exe") + else () + default(PVS_STUDIO_BIN "pvs-studio-analyzer") + default(PVS_STUDIO_CONVERTER "plog-converter") + endif () + + find_program(PVS_STUDIO_BIN_PATH "${PVS_STUDIO_BIN}" ${PATHS}) + set(PVS_STUDIO_BIN "${PVS_STUDIO_BIN_PATH}") + + if (NOT EXISTS "${PVS_STUDIO_BIN}") + message(FATAL_ERROR "pvs-studio-analyzer is not found") + endif () + + find_program(PVS_STUDIO_CONVERTER_PATH "${PVS_STUDIO_CONVERTER}" ${PATHS}) + set(PVS_STUDIO_CONVERTER "${PVS_STUDIO_CONVERTER_PATH}") + + if (NOT EXISTS "${PVS_STUDIO_CONVERTER}") + message(FATAL_ERROR "plog-converter is not found") + endif () + + default(PVS_STUDIO_MODE "GA:1,2") + default(PVS_STUDIO_PREPROCESSOR "${DEFAULT_PREPROCESSOR}") + if (WIN32) + default(PVS_STUDIO_PLATFORM "x64") + else () + default(PVS_STUDIO_PLATFORM "linux64") + endif () + + string(REPLACE ";" "+" PVS_STUDIO_MODE "${PVS_STUDIO_MODE}") + + if ("${PVS_STUDIO_CONFIG}" STREQUAL "" AND NOT "${PVS_STUDIO_CFG_TEXT}" STREQUAL "") + set(PVS_STUDIO_CONFIG "${CMAKE_BINARY_DIR}/PVS-Studio.cfg") + + set(PVS_STUDIO_CONFIG_COMMAND "${CMAKE_COMMAND}" -E echo "${PVS_STUDIO_CFG_TEXT}" > "${PVS_STUDIO_CONFIG}") + + add_custom_command(OUTPUT "${PVS_STUDIO_CONFIG}" + COMMAND ${PVS_STUDIO_CONFIG_COMMAND} + WORKING_DIRECTORY "${BINARY_DIR}" + COMMENT "Generating PVS-Studio.cfg") + + list(APPEND PVS_STUDIO_DEPENDS "${PVS_STUDIO_CONFIG}") + endif () + if (NOT "${PVS_STUDIO_PREPROCESSOR}" MATCHES "^${PVS_STUDIO_SUPPORTED_PREPROCESSORS}$") + message(FATAL_ERROR "Preprocessor ${PVS_STUDIO_PREPROCESSOR} isn't supported. Available options: ${PVS_STUDIO_SUPPORTED_PREPROCESSORS}.") + endif () + + pvs_studio_append_standard_flag(PVS_STUDIO_CXX_FLAGS "${CMAKE_CXX_STANDARD}") + pvs_studio_set_directory_flags("${CMAKE_CURRENT_SOURCE_DIR}" PVS_STUDIO_CXX_FLAGS PVS_STUDIO_C_FLAGS) + + if (NOT "${PVS_STUDIO_LICENSE}" STREQUAL "") + list(APPEND PVS_STUDIO_ARGS --lic-file "${PVS_STUDIO_LICENSE}") + endif () + + if (NOT ${PVS_STUDIO_CONFIG} STREQUAL "") + list(APPEND PVS_STUDIO_ARGS --cfg "${PVS_STUDIO_CONFIG}") + endif () + + list(APPEND PVS_STUDIO_ARGS --platform "${PVS_STUDIO_PLATFORM}" + --preprocessor "${PVS_STUDIO_PREPROCESSOR}") + + if (NOT "${PVS_STUDIO_SUPPRESS_BASE}" STREQUAL "") + pvs_studio_join_path(PVS_STUDIO_SUPPRESS_BASE "${CMAKE_CURRENT_SOURCE_DIR}" "${PVS_STUDIO_SUPPRESS_BASE}") + list(APPEND PVS_STUDIO_ARGS --suppress-file "${PVS_STUDIO_SUPPRESS_BASE}") + endif () + + if (NOT "${CMAKE_CXX_COMPILER}" STREQUAL "") + list(APPEND PVS_STUDIO_ARGS --cxx "${CMAKE_CXX_COMPILER}") + endif () + + if (NOT "${CMAKE_C_COMPILER}" STREQUAL "") + list(APPEND PVS_STUDIO_ARGS --cc "${CMAKE_C_COMPILER}") + endif () + + if (PVS_STUDIO_KEEP_INTERMEDIATE_FILES) + list(APPEND PVS_STUDIO_ARGS --dump-files) + endif() + + string(REGEX REPLACE [123,:] "" ANALYZER_MODE ${PVS_STUDIO_MODE}) + if (NOT "$ANALYZER_MODE" STREQUAL "GA") + list (APPEND PVS_STUDIO_ARGS -a "${ANALYZER_MODE}") + endif () + + set(PVS_STUDIO_PLOGS "") + + set(PVS_STUDIO_RECURSIVE_TARGETS_NEW) + if (${PVS_STUDIO_RECURSIVE}) + foreach (TARGET IN LISTS PVS_STUDIO_ANALYZE) + list(APPEND PVS_STUDIO_RECURSIVE_TARGETS_NEW "${TARGET}") + pvs_studio_get_recursive_targets("${TARGET}") + endforeach () + endif () + + set(inc_path) + + foreach (TARGET ${PVS_STUDIO_ANALYZE}) + set(DIR "${CMAKE_CURRENT_SOURCE_DIR}") + string(FIND "${TARGET}" ":" DELIM) + if ("${DELIM}" GREATER "-1") + math(EXPR DELIMI "${DELIM}+1") + string(SUBSTRING "${TARGET}" "${DELIMI}" "-1" DIR) + string(SUBSTRING "${TARGET}" "0" "${DELIM}" TARGET) + pvs_studio_join_path(DIR "${CMAKE_CURRENT_SOURCE_DIR}" "${DIR}") + else () + get_target_property(TARGET_SOURCE_DIR "${TARGET}" SOURCE_DIR) + if (EXISTS "${TARGET_SOURCE_DIR}") + set(DIR "${TARGET_SOURCE_DIR}") + endif () + endif () + pvs_studio_analyze_target("${TARGET}" "${DIR}") + list(APPEND PVS_STUDIO_DEPENDS "${TARGET}") + + if ("${inc_path}" STREQUAL "") + set(inc_path "$") + else () + set(inc_path "${inc_path}$$") + endif () + endforeach () + + foreach (TARGET ${PVS_STUDIO_RECURSIVE_TARGETS_NEW}) + set(DIR "${CMAKE_CURRENT_SOURCE_DIR}") + get_target_property(TARGET_SOURCE_DIR "${TARGET}" SOURCE_DIR) + if (EXISTS "${TARGET_SOURCE_DIR}") + set(DIR "${TARGET_SOURCE_DIR}") + endif () + pvs_studio_analyze_target("${TARGET}" "${DIR}") + list(APPEND PVS_STUDIO_DEPENDS "${TARGET}") + endforeach () + + set(PVS_STUDIO_TARGET_CXX_FLAGS "") + set(PVS_STUDIO_TARGET_C_FLAGS "") + foreach (SOURCE ${PVS_STUDIO_SOURCES}) + pvs_studio_analyze_file("${SOURCE}" "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}") + endforeach () + + if (PVS_STUDIO_COMPILE_COMMANDS) + set(COMPILE_COMMANDS_LOG "${PVS_STUDIO_LOG}.pvs.analyzer.raw") + if (NOT CMAKE_EXPORT_COMPILE_COMMANDS) + message(FATAL_ERROR "You should set CMAKE_EXPORT_COMPILE_COMMANDS to TRUE") + endif () + add_custom_command( + OUTPUT "${COMPILE_COMMANDS_LOG}" + COMMAND "${PVS_STUDIO_BIN}" analyze -i + --output-file "${COMPILE_COMMANDS_LOG}.always" + ${PVS_STUDIO_ARGS} + COMMENT "Analyzing with PVS-Studio" + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + DEPENDS "${PVS_STUDIO_SUPPRESS_BASE}" "${PVS_STUDIO_DEPENDS}" + ) + list(APPEND PVS_STUDIO_PLOGS_LOGS "${COMPILE_COMMANDS_LOG}.always") + list(APPEND PVS_STUDIO_PLOGS_DEPENDENCIES "${COMPILE_COMMANDS_LOG}") + endif () + + pvs_studio_relative_path(LOG_RELATIVE "${CMAKE_BINARY_DIR}" "${PVS_STUDIO_LOG}") + if (PVS_STUDIO_PLOGS OR PVS_STUDIO_COMPILE_COMMANDS) + if (WIN32) + string(REPLACE / \\ PVS_STUDIO_PLOGS "${PVS_STUDIO_PLOGS}") + endif () + if (WIN32) + set(COMMANDS COMMAND type ${PVS_STUDIO_PLOGS} ${PVS_STUDIO_PLOGS_LOGS} > "${PVS_STUDIO_LOG}" 2>nul || cd .) + else () + set(COMMANDS COMMAND cat ${PVS_STUDIO_PLOGS} ${PVS_STUDIO_PLOGS_LOGS} > "${PVS_STUDIO_LOG}" 2>/dev/null || true) + endif () + set(COMMENT "Generating ${LOG_RELATIVE}") + if (NOT "${PVS_STUDIO_FORMAT}" STREQUAL "" OR PVS_STUDIO_OUTPUT) + if ("${PVS_STUDIO_FORMAT}" STREQUAL "") + set(PVS_STUDIO_FORMAT "errorfile") + endif () + set(converter_no_help "") + if (PVS_STUDIO_HIDE_HELP) + set(converter_no_help "--noHelpMessages") + endif() + list(APPEND COMMANDS + COMMAND "${CMAKE_COMMAND}" -E remove -f "${PVS_STUDIO_LOG}.pvs.raw" + COMMAND "${CMAKE_COMMAND}" -E rename "${PVS_STUDIO_LOG}" "${PVS_STUDIO_LOG}.pvs.raw" + COMMAND "${PVS_STUDIO_CONVERTER}" "${PVS_STUDIO_CONVERTER_ARGS}" ${converter_no_help} -t "${PVS_STUDIO_FORMAT}" "${PVS_STUDIO_LOG}.pvs.raw" -o "${PVS_STUDIO_LOG}" -a "${PVS_STUDIO_MODE}" + ) + if(NOT PVS_STUDIO_KEEP_COMBINED_PLOG) + list(APPEND COMMANDS COMMAND "${CMAKE_COMMAND}" -E remove -f "${PVS_STUDIO_LOG}.pvs.raw") + endif() + endif () + else () + set(COMMANDS COMMAND "${CMAKE_COMMAND}" -E touch "${PVS_STUDIO_LOG}") + set(COMMENT "Generating ${LOG_RELATIVE}: no sources found") + endif () + + if (WIN32) + string(REPLACE / \\ PVS_STUDIO_LOG "${PVS_STUDIO_LOG}") + endif () + + if (CMAKE_GENERATOR STREQUAL "Unix Makefiles") + get_filename_component(LOG_NAME ${LOG_RELATIVE} NAME) + set(LOG_TARGET "${PVS_STUDIO_TARGET}-${LOG_NAME}-log") + add_custom_target("${LOG_TARGET}" + BYPRODUCTS "${PVS_STUDIO_LOG}" + ${COMMANDS} + COMMENT "${COMMENT}" + DEPENDS ${PVS_STUDIO_PLOGS} ${PVS_STUDIO_PLOGS_DEPENDENCIES} + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") + else() + set(LOG_TARGET "${PVS_STUDIO_LOG}") + add_custom_command(OUTPUT "${LOG_TARGET}" + ${COMMANDS} + COMMENT "${COMMENT}" + DEPENDS ${PVS_STUDIO_PLOGS} ${PVS_STUDIO_PLOGS_DEPENDENCIES} + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") + endif() + + if (PVS_STUDIO_ALL) + set(ALL "ALL") + else () + set(ALL "") + endif () + + if (PVS_STUDIO_OUTPUT) + if (WIN32) + set(COMMANDS COMMAND type "${PVS_STUDIO_LOG}" 1>&2) + else () + set(COMMANDS COMMAND cat "${PVS_STUDIO_LOG}" 1>&2) + endif() + else () + set(COMMANDS "") + endif () + + set(props_file "${CMAKE_BINARY_DIR}/${PVS_STUDIO_TARGET}.user.props") + file(WRITE "${props_file}" [=[ + + + + + + + + true + + + +]=]) + + add_custom_target("${PVS_STUDIO_TARGET}" ${ALL} ${COMMANDS} + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + DEPENDS ${PVS_STUDIO_DEPENDS} "${LOG_TARGET}") + set_target_properties("${PVS_STUDIO_TARGET}" PROPERTIES VS_USER_PROPS "${props_file}") + + # A workaround to add implicit dependencies of source files from include directories + set_target_properties("${PVS_STUDIO_TARGET}" PROPERTIES INCLUDE_DIRECTORIES "${inc_path}") +endfunction () diff --git a/cmake/Protobuf.cmake b/cmake/Protobuf.cmake index 6d80eefde97..891d734ba24 100644 --- a/cmake/Protobuf.cmake +++ b/cmake/Protobuf.cmake @@ -1,5 +1,11 @@ -set(protobuf_SOURCE_DIR ${CMAKE_SOURCE_DIR}/build/local/src/protobuf/protobuf-3.14.0) -set(protobuf_source_dir ${CMAKE_SOURCE_DIR}/build/local/src/protobuf/protobuf-3.14.0) +# 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. + +set(protobuf_SOURCE_DIR ${CMAKE_SOURCE_DIR}/build/local/src/protobuf/protobuf-3.19.2) +set(protobuf_source_dir ${CMAKE_SOURCE_DIR}/build/local/src/protobuf/protobuf-3.19.2) # sort + uniq -u # https://github.com/protocolbuffers/protobuf/blob/master/cmake/libprotobuf.cmake @@ -24,11 +30,15 @@ set(protobuf_SOURCE_FILES ${protobuf_source_dir}/src/google/protobuf/extension_set_heavy.cc ${protobuf_source_dir}/src/google/protobuf/field_mask.pb.cc ${protobuf_source_dir}/src/google/protobuf/generated_enum_util.cc + ${protobuf_source_dir}/src/google/protobuf/generated_message_bases.cc ${protobuf_source_dir}/src/google/protobuf/generated_message_reflection.cc ${protobuf_source_dir}/src/google/protobuf/generated_message_table_driven.cc ${protobuf_source_dir}/src/google/protobuf/generated_message_table_driven_lite.cc + ${protobuf_source_dir}/src/google/protobuf/generated_message_tctable_full.cc + ${protobuf_source_dir}/src/google/protobuf/generated_message_tctable_lite.cc ${protobuf_source_dir}/src/google/protobuf/generated_message_util.cc ${protobuf_source_dir}/src/google/protobuf/implicit_weak_message.cc + ${protobuf_source_dir}/src/google/protobuf/inlined_string_field.cc ${protobuf_source_dir}/src/google/protobuf/io/coded_stream.cc ${protobuf_source_dir}/src/google/protobuf/io/gzip_stream.cc ${protobuf_source_dir}/src/google/protobuf/io/io_win32.cc @@ -45,6 +55,7 @@ set(protobuf_SOURCE_FILES ${protobuf_source_dir}/src/google/protobuf/parse_context.cc ${protobuf_source_dir}/src/google/protobuf/reflection_ops.cc ${protobuf_source_dir}/src/google/protobuf/repeated_field.cc + ${protobuf_source_dir}/src/google/protobuf/repeated_ptr_field.cc ${protobuf_source_dir}/src/google/protobuf/service.cc ${protobuf_source_dir}/src/google/protobuf/source_context.pb.cc ${protobuf_source_dir}/src/google/protobuf/struct.pb.cc @@ -78,7 +89,6 @@ set(protobuf_SOURCE_FILES ${protobuf_source_dir}/src/google/protobuf/util/internal/protostream_objectsource.cc ${protobuf_source_dir}/src/google/protobuf/util/internal/protostream_objectwriter.cc ${protobuf_source_dir}/src/google/protobuf/util/internal/type_info.cc - ${protobuf_source_dir}/src/google/protobuf/util/internal/type_info_test_helper.cc ${protobuf_source_dir}/src/google/protobuf/util/internal/utility.cc ${protobuf_source_dir}/src/google/protobuf/util/json_util.cc ${protobuf_source_dir}/src/google/protobuf/util/message_differencer.cc @@ -94,6 +104,7 @@ set(protobuf_HEADER_FILES ${protobuf_source_dir}/src/google/protobuf/any.pb.h ${protobuf_source_dir}/src/google/protobuf/api.pb.h ${protobuf_source_dir}/src/google/protobuf/arena.h + ${protobuf_source_dir}/src/google/protobuf/arena_impl.h ${protobuf_source_dir}/src/google/protobuf/arenastring.h ${protobuf_source_dir}/src/google/protobuf/compiler/importer.h ${protobuf_source_dir}/src/google/protobuf/compiler/parser.h @@ -103,39 +114,70 @@ set(protobuf_HEADER_FILES ${protobuf_source_dir}/src/google/protobuf/duration.pb.h ${protobuf_source_dir}/src/google/protobuf/dynamic_message.h ${protobuf_source_dir}/src/google/protobuf/empty.pb.h + ${protobuf_source_dir}/src/google/protobuf/explicitly_constructed.h ${protobuf_source_dir}/src/google/protobuf/extension_set.h + ${protobuf_source_dir}/src/google/protobuf/extension_set_inl.h + ${protobuf_source_dir}/src/google/protobuf/field_access_listener.h ${protobuf_source_dir}/src/google/protobuf/field_mask.pb.h + ${protobuf_source_dir}/src/google/protobuf/generated_enum_reflection.h + ${protobuf_source_dir}/src/google/protobuf/generated_enum_util.h + ${protobuf_source_dir}/src/google/protobuf/generated_message_bases.h ${protobuf_source_dir}/src/google/protobuf/generated_message_reflection.h + ${protobuf_source_dir}/src/google/protobuf/generated_message_table_driven.h + ${protobuf_source_dir}/src/google/protobuf/generated_message_table_driven_lite.h + ${protobuf_source_dir}/src/google/protobuf/generated_message_tctable_decl.h + ${protobuf_source_dir}/src/google/protobuf/generated_message_tctable_impl.h + ${protobuf_source_dir}/src/google/protobuf/generated_message_tctable_impl.inc ${protobuf_source_dir}/src/google/protobuf/generated_message_util.h + ${protobuf_source_dir}/src/google/protobuf/has_bits.h ${protobuf_source_dir}/src/google/protobuf/implicit_weak_message.h + ${protobuf_source_dir}/src/google/protobuf/inlined_string_field.h ${protobuf_source_dir}/src/google/protobuf/io/coded_stream.h ${protobuf_source_dir}/src/google/protobuf/io/gzip_stream.h + ${protobuf_source_dir}/src/google/protobuf/io/io_win32.h ${protobuf_source_dir}/src/google/protobuf/io/printer.h ${protobuf_source_dir}/src/google/protobuf/io/strtod.h ${protobuf_source_dir}/src/google/protobuf/io/tokenizer.h ${protobuf_source_dir}/src/google/protobuf/io/zero_copy_stream.h ${protobuf_source_dir}/src/google/protobuf/io/zero_copy_stream_impl.h ${protobuf_source_dir}/src/google/protobuf/io/zero_copy_stream_impl_lite.h + ${protobuf_source_dir}/src/google/protobuf/map.h + ${protobuf_source_dir}/src/google/protobuf/map_entry.h + ${protobuf_source_dir}/src/google/protobuf/map_entry_lite.h ${protobuf_source_dir}/src/google/protobuf/map_field.h + ${protobuf_source_dir}/src/google/protobuf/map_field_inl.h + ${protobuf_source_dir}/src/google/protobuf/map_field_lite.h + ${protobuf_source_dir}/src/google/protobuf/map_type_handler.h ${protobuf_source_dir}/src/google/protobuf/message.h ${protobuf_source_dir}/src/google/protobuf/message_lite.h + ${protobuf_source_dir}/src/google/protobuf/metadata.h + ${protobuf_source_dir}/src/google/protobuf/metadata_lite.h ${protobuf_source_dir}/src/google/protobuf/parse_context.h + ${protobuf_source_dir}/src/google/protobuf/port.h + ${protobuf_source_dir}/src/google/protobuf/reflection.h ${protobuf_source_dir}/src/google/protobuf/reflection_ops.h ${protobuf_source_dir}/src/google/protobuf/repeated_field.h + ${protobuf_source_dir}/src/google/protobuf/repeated_ptr_field.h ${protobuf_source_dir}/src/google/protobuf/service.h ${protobuf_source_dir}/src/google/protobuf/source_context.pb.h ${protobuf_source_dir}/src/google/protobuf/struct.pb.h ${protobuf_source_dir}/src/google/protobuf/stubs/bytestream.h + ${protobuf_source_dir}/src/google/protobuf/stubs/callback.h + ${protobuf_source_dir}/src/google/protobuf/stubs/casts.h ${protobuf_source_dir}/src/google/protobuf/stubs/common.h - ${protobuf_source_dir}/src/google/protobuf/stubs/int128.h + ${protobuf_source_dir}/src/google/protobuf/stubs/hash.h + ${protobuf_source_dir}/src/google/protobuf/stubs/logging.h + ${protobuf_source_dir}/src/google/protobuf/stubs/macros.h + ${protobuf_source_dir}/src/google/protobuf/stubs/map_util.h + ${protobuf_source_dir}/src/google/protobuf/stubs/mutex.h ${protobuf_source_dir}/src/google/protobuf/stubs/once.h + ${protobuf_source_dir}/src/google/protobuf/stubs/platform_macros.h + ${protobuf_source_dir}/src/google/protobuf/stubs/port.h ${protobuf_source_dir}/src/google/protobuf/stubs/status.h - ${protobuf_source_dir}/src/google/protobuf/stubs/statusor.h + ${protobuf_source_dir}/src/google/protobuf/stubs/stl_util.h ${protobuf_source_dir}/src/google/protobuf/stubs/stringpiece.h - ${protobuf_source_dir}/src/google/protobuf/stubs/stringprintf.h ${protobuf_source_dir}/src/google/protobuf/stubs/strutil.h - ${protobuf_source_dir}/src/google/protobuf/stubs/substitute.h - ${protobuf_source_dir}/src/google/protobuf/stubs/time.h + ${protobuf_source_dir}/src/google/protobuf/stubs/template_util.h ${protobuf_source_dir}/src/google/protobuf/text_format.h ${protobuf_source_dir}/src/google/protobuf/timestamp.pb.h ${protobuf_source_dir}/src/google/protobuf/type.pb.h @@ -143,23 +185,10 @@ set(protobuf_HEADER_FILES ${protobuf_source_dir}/src/google/protobuf/util/delimited_message_util.h ${protobuf_source_dir}/src/google/protobuf/util/field_comparator.h ${protobuf_source_dir}/src/google/protobuf/util/field_mask_util.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/datapiece.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/default_value_objectwriter.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/error_listener.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/field_mask_utility.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/json_escaping.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/json_objectwriter.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/json_stream_parser.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/object_writer.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/proto_writer.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/protostream_objectsource.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/protostream_objectwriter.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/type_info.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/type_info_test_helper.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/utility.h ${protobuf_source_dir}/src/google/protobuf/util/json_util.h ${protobuf_source_dir}/src/google/protobuf/util/message_differencer.h ${protobuf_source_dir}/src/google/protobuf/util/time_util.h + ${protobuf_source_dir}/src/google/protobuf/util/type_resolver.h ${protobuf_source_dir}/src/google/protobuf/util/type_resolver_util.h ${protobuf_source_dir}/src/google/protobuf/wire_format.h ${protobuf_source_dir}/src/google/protobuf/wire_format_lite.h @@ -173,7 +202,7 @@ add_library(protobuf ${protobuf_SOURCE_FILES} ${protobuf_HEADER_FILES}) set_target_properties( protobuf PROPERTIES - CXX_STANDARD 17 + CXX_STANDARD 20 CXX_STANDARD_REQUIRED ON IMPORTED_CONFIGURATIONS Release INCLUDE_DIRECTORIES ${protobuf_source_dir}/src @@ -181,12 +210,11 @@ set_target_properties( LINK_FLAGS -no-undefined ) -target_compile_options(protobuf PRIVATE -DHAVE_PTHREAD=1 -Wno-inconsistent-missing-override -Wno-shorten-64-to-32) +target_compile_options(protobuf PRIVATE -DHAVE_PTHREAD=1 -Wno-inconsistent-missing-override -Wno-shorten-64-to-32 -Wno-invalid-noreturn) install(TARGETS protobuf LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/protobuf - ) - -set(Protobuf_LIBRARIES protobuf) +) +set(Protobuf_LIBRARIES protobuf) \ No newline at end of file diff --git a/cmake/StandardSettings.cmake b/cmake/StandardSettings.cmake new file mode 100644 index 00000000000..752b2285672 --- /dev/null +++ b/cmake/StandardSettings.cmake @@ -0,0 +1,86 @@ +# +# Default settings +# +set(CMAKE_CXX_VISIBILITY_PRESET hidden) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS 1) +set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version" FORCE) + +# +# IDE Settings +# +option(TW_IDE_CLION "Enable if your IDE is CLion" OFF) +option(TW_IDE_VSCODE "Enable if your IDE is VSCode" OFF) + +# +# Build Settings +# +option(TW_UNITY_BUILD "Enable Unity build for TrustWalletCore and unit tests." OFF) + +# +# Static analyzers +# +# Currently supporting: Clang-Tidy, PVS-Studio. +option(TW_ENABLE_CLANG_TIDY "Enable static analysis with Clang-Tidy." OFF) +option(TW_ENABLE_PVS_STUDIO "Enable static analysis with PVS-Studio." OFF) + +# +# Runtime analyzers +# +# Currently supporting: Clang ASAN. +option(TW_CLANG_ASAN "Enable ASAN dynamic address sanitizer" OFF) + +# +# Specific platforms support +# +# Currently supporting: Wasm. +option(TW_COMPILE_WASM "Target Wasm" OFF) + +# +# Coverage +# +option(TW_CODE_COVERAGE "Enable coverage reporting" OFF) + +# +# Compiler warnings options +# +option(TW_WARNINGS_AS_ERRORS "Compiler Options as Error" OFF) + +# +# Compilation Speed options +# +option(TW_ENABLE_CCACHE "Enable the usage of Ccache, in order to speed up rebuild times." ON) + +if (TW_ENABLE_CCACHE) + find_program(CCACHE_FOUND ccache) + if (CCACHE_FOUND) + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) + message(STATUS "ccache activated") + endif () +endif () + +# +# Tests/Examples options +# +option(TW_UNIT_TESTS "Enable the unit tests of the project" ON) +option(TW_BUILD_EXAMPLES "Enable the examples builds of the project" ON) + +if (ANDROID OR IOS_PLATFORM OR TW_COMPILE_WASM) + set(TW_UNIT_TESTS OFF) + set(TW_BUILD_EXAMPLES OFF) +endif() + +if (TW_UNIT_TESTS) + message(STATUS "Native unit tests activated") +else() + message(STATUS "Native unit tests skipped") +endif() + +if (TW_BUILD_EXAMPLES) + message(STATUS "Native examples activated") +else() + message(STATUS "Native examples skipped") +endif() + + diff --git a/cmake/StaticAnalyzers.cmake b/cmake/StaticAnalyzers.cmake new file mode 100644 index 00000000000..bb06683916e --- /dev/null +++ b/cmake/StaticAnalyzers.cmake @@ -0,0 +1,37 @@ +if (TW_ENABLE_CLANG_TIDY) + macro(tw_add_clang_tidy_target target) + find_program(CLANGTIDY clang-tidy) + if (CLANGTIDY) + set_property( + TARGET ${target} + PROPERTY CXX_CLANG_TIDY clang-tidy;-extra-arg=-Wno-unknown-warning-option) + message("Clang-Tidy finished setting up.") + else () + message(SEND_ERROR "Clang-Tidy requested but executable not found.") + endif () + endmacro() +endif () + +if (TW_ENABLE_PVS_STUDIO) + macro(tw_add_pvs_studio_target target) + message(STATUS "PVS-Studio analyzer enabled - ${CMAKE_SOURCE_DIR}/tools/pvs-studio/config.cfg") + include(cmake/PVS-Studio.cmake) + if (TW_IDE_VSCODE) + pvs_studio_add_target(TARGET TrustWalletCore.analyze ALL + OUTPUT FORMAT sarif-vscode + ANALYZE ${target} + MODE GA:1,2 + LOG result.sarif + CONFIG ${CMAKE_SOURCE_DIR}/tools/pvs-studio/config.cfg + ) + else () + pvs_studio_add_target(TARGET TrustWalletCore.analyze ALL + OUTPUT FORMAT json + ANALYZE ${target} + MODE GA:1,2 + LOG result.json + CONFIG ${CMAKE_SOURCE_DIR}/tools/pvs-studio/config.cfg + ) + endif () + endmacro() +endif () diff --git a/codegen/.rubocop.yml b/codegen/.rubocop.yml index c4410459c53..7e5aa453c4c 100644 --- a/codegen/.rubocop.yml +++ b/codegen/.rubocop.yml @@ -1,2 +1,13 @@ -Metrics/LineLength: +AllCops: + NewCops: enable + Exclude: + - 'vendor/**/*' + - 'spec/fixtures/**/*' + - 'tmp/**/*' + - '.git/**/*' + - 'bin/*' + TargetRubyVersion: 2.6 + SuggestExtensions: false + +Layout/LineLength: Enabled: false diff --git a/codegen/bin/codegen b/codegen/bin/codegen index d8b7a6f0873..8fc86c7e86e 100755 --- a/codegen/bin/codegen +++ b/codegen/bin/codegen @@ -20,6 +20,8 @@ options.swift = true options.java = true options.jni_h = true options.jni_c = true +options.wasm_cpp = true +options.ts_declaration = true OptionParser.new do |opts| opts.banner = 'Usage: codegen [options]' @@ -42,6 +44,12 @@ OptionParser.new do |opts| opts.on('-c', '--jnic', "Generate JNI code. Default: #{options.jni_c}") do |v| options.jni_c = v end + opts.on('-w', '--wasm-cpp', "Generate cpp code for Wasm(Emscripten). Default: #{options.wasm_cpp}") do |v| + options.wasm_cpp = v + end + opts.on('-t', '--typescript-declaration', "Generate typescript declaration file Default: #{options.ts_declaration}") do |v| + options.ts_declaration = v + end opts.on_tail('-h', '--help', 'Show this message') do puts opts exit @@ -73,3 +81,10 @@ end if options.jni_c generator.render_jni_c end +if options.wasm_cpp + generator.render_wasm_h + generator.render_wasm_cpp +end +if options.ts_declaration + generator.render_ts_declaration +end diff --git a/codegen/bin/coins b/codegen/bin/coins index 53c26f999dd..a5ea5909e05 100755 --- a/codegen/bin/coins +++ b/codegen/bin/coins @@ -14,6 +14,28 @@ def self.format_name(n) formatted end +def self.coin_name(coin) + coin['displayName'] || coin['name'] +end + +def self.derivation_path(coin) + coin['derivation'][0]['path'] +end + +def self.camel_case(id) + id[0].upcase + id[1..].downcase +end + +def self.derivation_name(deriv) + return "" if deriv['name'].nil? + deriv['name'].downcase +end + +def self.derivation_enum_name(deriv, coin) + return "TWDerivationDefault" if deriv['name'].nil? + "TWDerivation" + format_name(coin['name']) + camel_case(deriv['name']) +end + def self.coin_img(coin) "" end @@ -29,11 +51,16 @@ end json_string = File.read('registry.json') coins = JSON.parse(json_string).sort_by { |x| x['coinId'] } +# used in some cases for numbering enum values +enum_count = 0 + erbs = [ + {'template' => 'TWDerivation.h.erb', 'folder' => 'include/TrustWalletCore', 'file' => 'TWDerivation.h'}, {'template' => 'CoinInfoData.cpp.erb', 'folder' => 'src/Generated', 'file' => 'CoinInfoData.cpp'}, {'template' => 'registry.md.erb', 'folder' => 'docs', 'file' => 'registry.md'}, {'template' => 'hrp.cpp.erb', 'folder' => 'src/Generated', 'file' => 'TWHRP.cpp'}, - {'template' => 'hrp.h.erb', 'folder' => 'include/TrustWalletCore', 'file' => 'TWHRP.h'} + {'template' => 'hrp.h.erb', 'folder' => 'include/TrustWalletCore', 'file' => 'TWHRP.h'}, + {'template' => 'TWEthereumChainID.h.erb', 'folder' => 'include/TrustWalletCore', 'file' => 'TWEthereumChainID.h'} ] FileUtils.mkdir_p File.join('src', 'Generated') diff --git a/codegen/bin/cointests b/codegen/bin/cointests index b30fc1e91af..a40f56b0250 100755 --- a/codegen/bin/cointests +++ b/codegen/bin/cointests @@ -2,25 +2,25 @@ # Sript for creating/updating CoinType unit tests, based on the registry.json file # It is intended as a one-time or occasional generation, not every time! (that way the tests would have zero added value) -# Usage: codegen/bin/cointests +# Usage: codegen/bin/cointests [--coin-id coinid] [--no-skip-existing] # Files are generated to: tests//TWCoinTypeTests.cpp require 'erb' require 'fileutils' require 'json' +require 'optparse' + +options = { :no_skip_existing => false } +OptionParser.new do |opt| + opt.banner = "Usage: codegen/bin/cointests [options]" + opt.on('--coin-id ') { |o| options[:coin_id] = o.downcase } + opt.on('--no-skip-existing') { options[:no_skip_existing] = true } +end.parse! CurrentDir = File.dirname(__FILE__) $LOAD_PATH.unshift(File.join(CurrentDir, '..', 'lib')) require 'coin_test_gen' -# Transforms a coin name to a C++ name -def self.format_name(n) - formatted = n - #formatted = formatted.sub(/^([a-z]+)/, &:upcase) - formatted = formatted.sub(/\s/, '') - formatted -end - # Transforms number to hex def self.to_hex(i) hex = i.to_i().to_s(16) @@ -68,6 +68,19 @@ erbs = [ coin_test_gen = CoinTestGen.new() templateFile = 'TWCoinTypeTests.cpp.erb' +foundCoinId = false coins.each do |coin| - coin_test_gen.generate_coin_test_file(coin, templateFile) + if options[:coin_id].nil? or coin['id'] == options[:coin_id] + coin_test_gen.generate_coin_test_file(coin, templateFile, options[:no_skip_existing]) + foundCoinId = true + end end + +if not options[:coin_id].nil? and not foundCoinId + puts "Not found specified coin-id " + options[:coin_id] + supportedIds = [] + coins.each do |coin| + supportedIds << coin['id'] + end + puts "Supported coin-ids: " + supportedIds.join(", ") +end diff --git a/codegen/bin/newcoin b/codegen/bin/newcoin index 974febcd8f3..8fdacb210fe 100755 --- a/codegen/bin/newcoin +++ b/codegen/bin/newcoin @@ -14,6 +14,8 @@ require 'entity_decl' require 'code_generator' require 'coin_test_gen' +$flag_comment = " // TODO remove if the blockchain already exists, or just remove this comment if not" + # Transforms a coin name to a C++ name def self.format_name(coin) formatted = coin['name'] @@ -54,18 +56,19 @@ end def insert_blockchain_type(coin) target_file = "include/TrustWalletCore/TWBlockchain.h" - line_number = File.readlines(target_file).count - target_line = " TWBlockchain#{coin['blockchain']} = #{line_number - 17},\n" + line_number = File.readlines(target_file).count + 2 # add offset because of removed blockchain enum type + target_line = " TWBlockchain#{coin['blockchain']} = #{line_number - 17}, " + $flag_comment + "\n" insert_target_line(target_file, target_line, "};\n") end def insert_coin_entry(coin) target_file = "src/Coin.cpp" - target_line = "#include \"#{format_name(coin)}/Entry.h\"\n" + entryName = coin['blockchain'] + target_line = "#include \"#{entryName}/Entry.h\"" + $flag_comment + "\n" insert_target_line(target_file, target_line, "// end_of_coin_includes_marker_do_not_modify\n") - target_line = "#{format_name(coin)}::Entry #{format_name(coin)}DP;\n" + target_line = "#{entryName}::Entry #{entryName}DP;" + $flag_comment + "\n" insert_target_line(target_file, target_line, "// end_of_coin_dipatcher_declarations_marker_do_not_modify\n") - target_line = " case TWCoinType#{format_name(coin)}: entry = &#{format_name(coin)}DP; break;\n" + target_line = " case TWBlockchain#{entryName}: entry = &#{entryName}DP; break;" + $flag_comment + "\n" insert_target_line(target_file, target_line, " // end_of_coin_dipatcher_switch_marker_do_not_modify\n") end @@ -101,7 +104,7 @@ puts "New coin template for coin '#{coin_id}' requested" json_string = File.read('registry.json') coins = JSON.parse(json_string).sort_by { |x| x['name'] } -entity = EntityDecl.new(name: "New" + coin_id, is_struct: false) +entity = EntityDecl.new(name: "New" + coin_id, is_struct: false, comment: '') file = "new"+ coin_id generator = CodeGenerator.new(entities: [entity], files: [file], output_folder: ".") @@ -131,14 +134,14 @@ generate_file("newcoin/Proto.erb", "src/proto", "#{name}.proto", coin) generate_file("newcoin/Signer.h.erb", "src/#{name}", "Signer.h", coin) generate_file("newcoin/Signer.cpp.erb", "src/#{name}", "Signer.cpp", coin) -generate_file("newcoin/AddressTests.cpp.erb", "tests/#{name}", "AddressTests.cpp", coin) -generate_file("newcoin/SignerTests.cpp.erb", "tests/#{name}", "SignerTests.cpp", coin) -generate_file("newcoin/TWAddressTests.cpp.erb", "tests/#{name}", "TWAnyAddressTests.cpp", coin) -generate_file("newcoin/TWSignerTests.cpp.erb", "tests/#{name}", "TWAnySignerTests.cpp", coin) +generate_file("newcoin/AddressTests.cpp.erb", "tests/chains/#{name}", "AddressTests.cpp", coin) +generate_file("newcoin/SignerTests.cpp.erb", "tests/chains/#{name}", "SignerTests.cpp", coin) +generate_file("newcoin/TWAddressTests.cpp.erb", "tests/chains/#{name}", "TWAnyAddressTests.cpp", coin) +generate_file("newcoin/TWSignerTests.cpp.erb", "tests/chains/#{name}", "TWAnySignerTests.cpp", coin) generate_file("newcoin/AddressTests.kt.erb", "android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/#{format_name_lowercase(coin)}", "Test#{name}Address.kt", coin) generate_file("newcoin/SignerTests.kt.erb", "android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/#{format_name_lowercase(coin)}", "Test#{name}Signer.kt", coin) generate_file("newcoin/Tests.swift.erb", "swift/Tests/Blockchains", "#{name}Tests.swift", coin) -coin_test_gen.generate_coin_test_file(coin, 'TWCoinTypeTests.cpp.erb') +coin_test_gen.generate_coin_test_file(coin, 'TWCoinTypeTests.cpp.erb', true) puts "please tools/generate-files to generate Swift/Java/Protobuf files" diff --git a/codegen/lib/code_generator.rb b/codegen/lib/code_generator.rb index 56b49b892c6..7896c2f8dca 100644 --- a/codegen/lib/code_generator.rb +++ b/codegen/lib/code_generator.rb @@ -5,6 +5,8 @@ require 'java_helper' require 'jni_helper' require 'swift_helper' +require 'wasm_cpp_helper' +require 'ts_helper' # Code generation class CodeGenerator @@ -20,7 +22,7 @@ def initialize(entities:, files:, output_folder:) # Renders an enum template def render_swift_enum_template(file:, header:, template:, output_subfolder:, extension:) # split Enum to Enum.swift and Enum+Extension.swift (easier to support cocoapods subspec) - output_enum_subfolder = "#{output_subfolder + '/Enums'}" + output_enum_subfolder = "#{output_subfolder}/Enums" FileUtils.mkdir_p File.join(output_folder, output_enum_subfolder) has_extension = entity.properties.length > 0 || entity.methods.length > 0 header = render(header) @@ -39,7 +41,7 @@ def render_swift_enum_template(file:, header:, template:, output_subfolder:, ext code = +'' code << header code << render('swift/enum_extension.erb') - path = File.expand_path(File.join(output_folder, output_subfolder, "#{file + '+Extension'}.#{extension}")) + path = File.expand_path(File.join(output_folder, output_subfolder, "#{file}+Extension.#{extension}")) File.write(path, code) end end @@ -60,7 +62,7 @@ def render_template(header:, template:, output_subfolder:, extension:) unless string.nil? || string.empty? code << "\n" unless header.nil? code << string - + path = File.expand_path(File.join(output_folder, output_subfolder, "#{file}.#{extension}")) File.write(path, code) end @@ -69,7 +71,7 @@ def render_template(header:, template:, output_subfolder:, extension:) end def render_swift - render_template(header: 'swift/header.erb', template: 'swift.erb', output_subfolder: 'swift/Sources/Generated', extension: 'swift') + render_template(header: 'copyright_header.erb', template: 'swift.erb', output_subfolder: 'swift/Sources/Generated', extension: 'swift') framework_header = render('swift/TrustWalletCore.h.erb') framework_header_path = File.expand_path(File.join(output_folder, 'swift/Sources/Generated', 'WalletCore.h')) @@ -81,11 +83,24 @@ def render_java end def render_jni_h - render_template(header: 'jni/header.erb', template: 'jni_h.erb', output_subfolder: 'jni/cpp/generated', extension: 'h') + render_template(header: 'copyright_header.erb', template: 'jni_h.erb', output_subfolder: 'jni/cpp/generated', extension: 'h') end def render_jni_c - render_template(header: 'jni/header.erb', template: 'jni_c.erb', output_subfolder: 'jni/cpp/generated', extension: 'c') + render_template(header: 'copyright_header.erb', template: 'jni_c.erb', output_subfolder: 'jni/cpp/generated', extension: 'c') + end + + def render_wasm_h + render_template(header: 'copyright_header.erb', template: 'wasm_h.erb', output_subfolder: 'wasm/src/generated', extension: 'h') + end + + def render_wasm_cpp + render_template(header: 'copyright_header.erb', template: 'wasm_cpp.erb', output_subfolder: 'wasm/src/generated', extension: 'cpp') + end + + def render_ts_declaration + render_template(header: nil, template: 'wasm_d_ts.erb', output_subfolder: 'wasm/lib/generated', extension: 'd.ts') + TsHelper.combine_declaration_files() end def render(file, locals = {}) @@ -102,7 +117,7 @@ def should_return_data(method) end def should_return_string(method) - # Note: method with no parameters can also return string + # NOTE: method with no parameters can also return string method.return_type.name == :string end diff --git a/codegen/lib/coin_test_gen.rb b/codegen/lib/coin_test_gen.rb index 2583e549a1b..0b831bfb277 100755 --- a/codegen/lib/coin_test_gen.rb +++ b/codegen/lib/coin_test_gen.rb @@ -14,8 +14,10 @@ def initialize() # Transforms a coin name to a C++ name def format_name(n) formatted = n - #formatted = formatted.sub(/^([a-z]+)/, &:upcase) - formatted = formatted.sub(/\s/, '') + + # Remove whitespaces + formatted.gsub!(/\s+/, '') + formatted end @@ -56,16 +58,24 @@ def explorer_sample_account(c) end end - def generate_coin_test_file(coin, templateFile) + def generate_coin_test_file(coin, templateFile, overwriteExisting = true) path = File.expand_path(templateFile, File.join(File.dirname(__FILE__), '..', 'lib', 'templates')) template = ERB.new(File.read(path), nil, '-') result = template.result(binding) - folder = 'tests/' + format_name(coin['name']) + folder = 'tests/chains/' + if coin.key?('testFolderName') + folder += format_name(coin['testFolderName']) + else + folder += format_name(coin['name']) + end + file = 'TWCoinTypeTests.cpp' FileUtils.mkdir_p folder path = File.join(folder, file) - File.write(path, result) - puts "Generated file " + path + if not File.exist?(path) or overwriteExisting + File.write(path, result) + puts "Generated file " + path + end end end diff --git a/codegen/lib/entity_decl.rb b/codegen/lib/entity_decl.rb index f22a0ea6587..aee285ef97b 100644 --- a/codegen/lib/entity_decl.rb +++ b/codegen/lib/entity_decl.rb @@ -2,16 +2,17 @@ # Class or struct declaration class EntityDecl - attr_reader :name + attr_reader :name, :comment attr_accessor :is_struct, :methods, :properties, :static_methods, :static_properties - def initialize(name:, is_struct:) + def initialize(name:, is_struct:, comment:) @name = name @is_struct = is_struct @methods = [] @properties = [] @static_methods = [] @static_properties = [] + @comment = comment end def struct? diff --git a/codegen/lib/enum_decl.rb b/codegen/lib/enum_decl.rb index 303909dec82..0fae5774f50 100644 --- a/codegen/lib/enum_decl.rb +++ b/codegen/lib/enum_decl.rb @@ -2,11 +2,11 @@ # Enum declaration. class EnumDecl - attr_reader :name + attr_reader :name, :comment attr_accessor :cases, :raw_type attr_accessor :methods, :properties, :static_methods, :static_properties - def initialize(name:, raw_type:) + def initialize(name:, raw_type:, comment:) @name = name @cases = [] @raw_type = raw_type @@ -14,6 +14,7 @@ def initialize(name:, raw_type:) @properties = [] @static_methods = [] @static_properties = [] + @comment = comment end def struct? diff --git a/codegen/lib/function_decl.rb b/codegen/lib/function_decl.rb index cd5135f1231..de0a3f7853c 100644 --- a/codegen/lib/function_decl.rb +++ b/codegen/lib/function_decl.rb @@ -4,8 +4,9 @@ class FunctionDecl attr_reader :name, :entity attr_accessor :is_method, :return_type, :parameters, :static, :discardable_result + attr_accessor :comment, :comment_with_indent - def initialize(name:, entity:, is_method:, return_type: :void, parameters: [], static: false, discardable_result: false) + def initialize(name:, entity:, is_method:, return_type: :void, parameters: [], static: false, discardable_result: false, comment: '') @name = name @entity = entity @is_method = is_method @@ -13,6 +14,8 @@ def initialize(name:, entity:, is_method:, return_type: :void, parameters: [], s @parameters = parameters @static = static @discardable_result = discardable_result + @comment = comment + @comment_with_indent = comment.to_s.gsub('///', ' ///') end end diff --git a/codegen/lib/parser.rb b/codegen/lib/parser.rb index fab69837507..e4e287614a0 100644 --- a/codegen/lib/parser.rb +++ b/codegen/lib/parser.rb @@ -9,7 +9,7 @@ # C header parser class Parser - attr_reader :path, :entity + attr_reader :path, :entity, :entity_comment def initialize(path:, string: nil) @path = path @@ -19,32 +19,49 @@ def initialize(path:, string: nil) # Parses a C header file for class/struct declarations def parse + clear_comment until @buffer.eos? - break if @buffer.skip_until(/\n/).nil? + @buffer.skip(/\s*/) + + if !@buffer.scan(/\/\//).nil? + @entity_comment = @entity_comment + '//' + @buffer.scan_until(/(\r\n|\r|\n)/) + next + end + + if !@buffer.scan(/TW_EXTERN_C_BEGIN/).nil? + # This is to ignore very first comments from the file + clear_comment + next + end + + @entity_comment = @entity_comment.strip # Look for TW_EXPORT statements - @buffer.skip(/\s*/) - next if @buffer.scan(/TW_EXPORT_[A-Z_]+/).nil? - - # Handle statements - case @buffer[0] - when 'TW_EXPORT_CLASS' - handle_class - when 'TW_EXPORT_STRUCT' - handle_struct - when 'TW_EXPORT_ENUM' - handle_enum - when 'TW_EXPORT_FUNC' - handle_func - when 'TW_EXPORT_METHOD' - handle_method - when 'TW_EXPORT_PROPERTY' - handle_property - when 'TW_EXPORT_STATIC_METHOD' - handle_static_method - when 'TW_EXPORT_STATIC_PROPERTY' - handle_static_property + if !@buffer.scan(/TW_EXPORT_[A-Z_]+/).nil? + # Handle statements + case @buffer[0] + when 'TW_EXPORT_CLASS' + handle_class + when 'TW_EXPORT_STRUCT' + handle_struct + when 'TW_EXPORT_ENUM' + handle_enum + when 'TW_EXPORT_FUNC' + handle_func + when 'TW_EXPORT_METHOD' + handle_method + when 'TW_EXPORT_PROPERTY' + handle_property + when 'TW_EXPORT_STATIC_METHOD' + handle_static_method + when 'TW_EXPORT_STATIC_PROPERTY' + handle_static_property + end + + clear_comment end + + break if @buffer.skip_until(/\n/).nil? end @entity @@ -80,7 +97,7 @@ def parse_func @buffer.skip(/\s*/) scan_or_fail(/\w+/, 'Invalid function name') - func = FunctionDecl.new(name: @buffer[0], entity: @entity, is_method: true, return_type: return_type) + func = FunctionDecl.new(name: @buffer[0], entity: @entity, is_method: true, return_type: return_type, comment: @entity_comment) @buffer.skip(/\s*/) scan_or_fail(/\(/, 'Invalid function declaration. Expected (') @@ -117,7 +134,7 @@ def handle_class @buffer.skip(/\s*/) report_error 'Invalid type name' if @buffer.scan(/struct TW(\w+)\s*;/).nil? report_error 'Found more than one class/struct in the same file' unless @entity.nil? - @entity = EntityDecl.new(name: @buffer[1], is_struct: false) + @entity = EntityDecl.new(name: @buffer[1], is_struct: false, comment: @entity_comment) puts "Found a class #{@entity.name}" end @@ -125,7 +142,7 @@ def handle_struct @buffer.skip(/\s*/) report_error 'Invalid type name at' if @buffer.scan(/struct TW(\w+)\s*\{?/).nil? report_error 'Found more than one class/struct in the same file' unless @entity.nil? - @entity = EntityDecl.new(name: @buffer[1], is_struct: true) + @entity = EntityDecl.new(name: @buffer[1], is_struct: true, comment: @entity_comment) puts "Found a struct #{@buffer[1]}" end @@ -136,7 +153,7 @@ def handle_enum @buffer.skip(/\s*/) report_error 'Invalid enum' if @buffer.scan(/enum TW(\w+)\s*\{/).nil? - @entity = EnumDecl.new(name: @buffer[1], raw_type: TypeDecl.fromPrimitive(type)) + @entity = EnumDecl.new(name: @buffer[1], raw_type: TypeDecl.fromPrimitive(type), comment: @entity_comment) incremental_value = 0 until @buffer.eos? @@ -277,4 +294,8 @@ def report_error(message) def current_line_number @buffer.string[0..@buffer.pos].count("\n") + 1 end + + def clear_comment + @entity_comment = '' + end end diff --git a/codegen/lib/templates/CoinInfoData.cpp.erb b/codegen/lib/templates/CoinInfoData.cpp.erb index 8dc394ace16..44dd8acd044 100644 --- a/codegen/lib/templates/CoinInfoData.cpp.erb +++ b/codegen/lib/templates/CoinInfoData.cpp.erb @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -21,16 +21,16 @@ static const CoinInfo defaultsForMissing = { TWBlockchainBitcoin, TWPurposeBIP44, TWCurveNone, - TWHDVersionNone, - TWHDVersionNone, - "", + {Derivation()}, TWPublicKeyTypeSECP256k1, 0, 0, 0, TWHRPUnknown, - Hash::sha256ripemd, - Hash::sha256d, + "", + Hash::HasherSha256ripemd, + Hash::HasherSha256d, + Hash::HasherSha256ripemd, "?", 2, "", @@ -46,20 +46,28 @@ const CoinInfo getCoinInfo(TWCoinType coin) { case TWCoinType<%= format_name(coin['name']) %>: return CoinInfo { "<%= coin['id'] %>", - <% if coin['displayName'].nil? -%>"<%= coin['name'] %>"<% else -%>"<%= coin['displayName'] %>"<% end -%>, + "<%= coin_name(coin) %>", TWBlockchain<%= format_name(coin['blockchain']) %>, - TWPurposeBIP<%= /^m\/(\d+)'?(\/\d+'?)+$/.match(coin['derivationPath'])[1] %>, + TWPurposeBIP<%= /^m\/(\d+)'?(\/\d+'?)+$/.match(derivation_path(coin))[1] %>, TWCurve<%= format_name(coin['curve']) %>, - TWHDVersion<% if coin['xpub'].nil? -%>None<% else -%><%= format_name(coin['xpub']) %><% end -%>, - TWHDVersion<% if coin['xprv'].nil? -%>None<% else -%><%= format_name(coin['xprv']) %><% end -%>, - "<%= coin['derivationPath'] %>", + { + <% coin['derivation'].each do |deriv| -%>{ + <%= derivation_enum_name(deriv, coin) %>, + "<%= deriv['path'] %>", + "<%= derivation_name(deriv) %>", + TWHDVersion<% if deriv['xpub'].nil? -%>None<% else -%><%= format_name(deriv['xpub']) %><% end -%>, + TWHDVersion<% if deriv['xprv'].nil? -%>None<% else -%><%= format_name(deriv['xprv']) %><% end -%>, + }, + <% end -%>}, TWPublicKeyType<%= format_name(coin['publicKeyType']) %>, <% if coin['staticPrefix'].nil? -%>0<% else -%><%= coin['staticPrefix'] %><% end -%>, <% if coin['p2pkhPrefix'].nil? -%>0<% else -%><%= coin['p2pkhPrefix'] %><% end -%>, <% if coin['p2shPrefix'].nil? -%>0<% else -%><%= coin['p2shPrefix'] %><% end -%>, TWHRP<% if coin['hrp'].nil? -%>Unknown<% else -%><%= format_name(coin['name']) %><% end -%>, - Hash::<% if coin['publicKeyHasher'].nil? -%>sha256ripemd<% else -%><%= coin['publicKeyHasher'] %><% end -%>, - Hash::<% if coin['base58Hasher'].nil? -%>sha256d<% else -%><%= coin['base58Hasher'] %><% end -%>, + "<%= coin['chainId'] %>", + Hash::Hasher<% if coin['publicKeyHasher'].nil? -%>Sha256ripemd<% else -%><%= camel_case(coin['publicKeyHasher']) %><% end -%>, + Hash::Hasher<% if coin['base58Hasher'].nil? -%>Sha256d<% else -%><%= camel_case(coin['base58Hasher']) %><% end -%>, + Hash::Hasher<% if coin['addressHasher'].nil? -%>Sha256ripemd<% else -%><%= camel_case(coin['addressHasher']) %><% end -%>, "<%= coin['symbol'] %>", <%= coin['decimals'] %>, "<%= explorer_tx_url(coin) %>", diff --git a/codegen/lib/templates/TWCoinTypeTests.cpp.erb b/codegen/lib/templates/TWCoinTypeTests.cpp.erb index 32a9534a95f..ab2a8fbf9c3 100644 --- a/codegen/lib/templates/TWCoinTypeTests.cpp.erb +++ b/codegen/lib/templates/TWCoinTypeTests.cpp.erb @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -8,27 +8,34 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include TEST(TW<%= format_name(coin['name']) %>CoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinType<%= format_name(coin['name']) %>)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("<%= explorer_sample_tx(coin) %>")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinType<%= format_name(coin['name']) %>, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("<%= explorer_sample_account(coin) %>")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinType<%= format_name(coin['name']) %>, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinType<%= format_name(coin['name']) %>)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinType<%= format_name(coin['name']) %>)); + const auto coin = TWCoinType<%= format_name(coin['name']) %>; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); +<% if !coin['chainId'].nil? -%> + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); +<% end -%> + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("<%= explorer_sample_tx(coin) %>")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("<%= explorer_sample_account(coin) %>")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinType<%= format_name(coin['name']) %>), <%= coin['decimals'] %>); - ASSERT_EQ(TWBlockchain<%= coin['blockchain'] %>, TWCoinTypeBlockchain(TWCoinType<%= format_name(coin['name']) %>)); - ASSERT_EQ(0x<%= to_hex(coin['p2shPrefix']) %>, TWCoinTypeP2shPrefix(TWCoinType<%= format_name(coin['name']) %>)); - ASSERT_EQ(0x<%= to_hex(coin['staticPrefix']) %>, TWCoinTypeStaticPrefix(TWCoinType<%= format_name(coin['name']) %>)); + assertStringsEqual(id, "<%= coin['id'] %>"); + assertStringsEqual(name, "<%= display_name(coin) %>"); assertStringsEqual(symbol, "<%= coin['symbol'] %>"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), <%= coin['decimals'] %>); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchain<%= coin['blockchain'] %>); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x<%= to_hex(coin['p2shPrefix']) %>); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x<%= to_hex(coin['staticPrefix']) %>); +<% if !coin['chainId'].nil? -%> + assertStringsEqual(chainId, "<%= format_name(coin['chainId']) %>"); +<% end -%> assertStringsEqual(txUrl, "<%= explorer_tx_url(coin) %><%= explorer_sample_tx(coin) %>"); assertStringsEqual(accUrl, "<%= explorer_account_url(coin) %><%= explorer_sample_account(coin) %>"); - assertStringsEqual(id, "<%= coin['id'] %>"); - assertStringsEqual(name, "<%= display_name(coin) %>"); } diff --git a/codegen/lib/templates/TWDerivation.h.erb b/codegen/lib/templates/TWDerivation.h.erb new file mode 100644 index 00000000000..06266d5cb82 --- /dev/null +++ b/codegen/lib/templates/TWDerivation.h.erb @@ -0,0 +1,33 @@ +// Copyright © 2017-2021 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 from \registry.json, changes made here WILL BE LOST. +// + +#pragma once + +#include "TWBase.h" + +TW_EXTERN_C_BEGIN + +/// Non-default coin address derivation names (default, unnamed derivations are not included). +TW_EXPORT_ENUM() +enum TWDerivation { + TWDerivationDefault = 0, // default, for any coin + TWDerivationCustom = 1, // custom, for any coin +<% enum_count += 1 -%> +<% coins.each do |coin| -%> +<% if coin['derivation'].count > 1 -%> +<% coin['derivation'].each_with_index do |deriv, index| -%> +<% if index > 0 or !deriv['name'].nil? -%> + <%= derivation_enum_name(deriv, coin) %> = <% enum_count += 1 -%><%= enum_count %>, +<% end -%> +<% end -%> +<% end -%> +<% end -%> +}; + +TW_EXTERN_C_END diff --git a/codegen/lib/templates/TWEthereumChainID.h.erb b/codegen/lib/templates/TWEthereumChainID.h.erb new file mode 100644 index 00000000000..99862693b3f --- /dev/null +++ b/codegen/lib/templates/TWEthereumChainID.h.erb @@ -0,0 +1,26 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// +// This is a GENERATED FILE from \registry.json, changes made here WILL BE LOST. +// + +#pragma once + +#include "TWBase.h" + +TW_EXTERN_C_BEGIN + +/// Chain identifiers for Ethereum-based blockchains, for convenience. Recommended to use the dynamic CoinType.ChainId() instead. +/// See also TWChainId. +TW_EXPORT_ENUM(uint32_t) +enum TWEthereumChainID { +<% chains = ['Ethereum', 'Ronin', 'Vechain'] -%> +<% coins.select{ |coin| chains.include?(coin['blockchain']) && coin['deprecated'] != true }.each do |coin| -%> + TWEthereumChainID<%= camel_case(coin['id']) %> = <%= coin['chainId'] %>, +<% end -%> +}; + +TW_EXTERN_C_END diff --git a/codegen/lib/templates/jni/header.erb b/codegen/lib/templates/copyright_header.erb similarity index 87% rename from codegen/lib/templates/jni/header.erb rename to codegen/lib/templates/copyright_header.erb index 0770a18cd04..be628ee1f6e 100644 --- a/codegen/lib/templates/jni/header.erb +++ b/codegen/lib/templates/copyright_header.erb @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 diff --git a/codegen/lib/templates/cpp/class.erb b/codegen/lib/templates/cpp/class.erb new file mode 100644 index 00000000000..ced639f9ebf --- /dev/null +++ b/codegen/lib/templates/cpp/class.erb @@ -0,0 +1,37 @@ +#include "<%= entity.name %>.h" + +namespace TW::Wasm { +<%# Constructors -%> +<% entity.static_methods.each do |method| -%> +<% next if method.name.start_with?('Create') || method.name.start_with?('Init') -%> +<%= render('cpp/static_method.erb', { method: method }) -%> +<% end -%> +<%# Properties -%> +<%= render('cpp/class_properties.erb') -%> + +<%# Methods -%> +<% entity.methods.each do |method| -%> +<% next if method.name == "Delete" -%> +<%= render('cpp/method.erb', { method: method }) %> +<% end -%> + +<%# EMSCRIPTEN_BINDINGS -%> +EMSCRIPTEN_BINDINGS(Wasm_TW<%= entity.name %>) { + class_<<%= WasmCppHelper.class_name(entity: entity) %>>("<%= entity.name %>") +<% entity.static_methods.each do |method| -%> +<% function_name = WasmCppHelper.function_name(entity: entity, function: method) -%> + .class_function("<%= function_name %>", &<%= WasmCppHelper.class_name(entity: entity) %>::<%= function_name %>, allow_raw_pointers()) +<% end -%> +<%- entity.properties.each do |property| -%> +<% function_name = WasmCppHelper.format_name(property.name) -%> + .function("<%= function_name %>", &<%= WasmCppHelper.class_name(entity: entity) %>::<%= function_name %>, allow_raw_pointers()) +<%- end -%> +<% entity.methods.each do |method| -%> +<% next if method.name == "Delete" -%> +<% function_name = WasmCppHelper.format_name(method.name) -%> + .function("<%= function_name %>", &<%= WasmCppHelper.class_name(entity: entity) %>::<%= function_name %>, allow_raw_pointers()) +<% end -%> + ; +} + +} // namespace TW::Wasm diff --git a/codegen/lib/templates/cpp/class_properties.erb b/codegen/lib/templates/cpp/class_properties.erb new file mode 100644 index 00000000000..0648de25352 --- /dev/null +++ b/codegen/lib/templates/cpp/class_properties.erb @@ -0,0 +1,22 @@ +<% entity.static_methods.each do |method| -%> +<% next unless method.name.start_with?('Create') -%> +auto <%= WasmCppHelper.class_name(entity: entity) %>::<%= WasmCppHelper.function_name(entity: entity, function: method) %>(<%= WasmCppHelper.parameters(method.parameters) %>) -> <%= WasmCppHelper.class_name(entity: entity) %>* { +<%= render('cpp/parameter_access.erb', { parameters: method.parameters }) -%> + TW<%= entity.name %> *instance = TW<%= entity.name %><%= method.name %>(<%= WasmCppHelper.arguments(method.parameters).join(', ') %>); +<% if method.return_type.is_nullable -%> + if (instance == nullptr) { + return nullptr; + } +<% end -%> + return new <%= WasmCppHelper.class_name(entity: entity) %>(instance); +} + +<% end -%> + +<%# Properties -%> +<%- entity.properties.each do |property| -%> +auto <%= WasmCppHelper.class_name(entity: entity) %>::<%= WasmCppHelper.format_name(property.name) %>() { +<%= render('cpp/method_forward.erb', { method: property }) -%> +} + +<%- end -%> diff --git a/codegen/lib/templates/cpp/enum.erb b/codegen/lib/templates/cpp/enum.erb new file mode 100644 index 00000000000..398420d93eb --- /dev/null +++ b/codegen/lib/templates/cpp/enum.erb @@ -0,0 +1,32 @@ +<% has_string = entity.cases.all? { |c| !c.string.nil? } -%> +<% if has_string -%> +#include + +<% end -%> +namespace TW::Wasm { + +<% if has_string -%> +auto describe<%= entity.name %>(TW<%= entity.name %> value) -> std::string { + switch (value) { +<% entity.cases.each do |c| -%> + case TW<%= entity.name %><%= c.name %>: return <%= c.string %>; +<% end -%> + default: return ""; + } +} + +<% end -%> +EMSCRIPTEN_BINDINGS(Wasm_TW<%= entity.name %>) { + enum_>("<%= entity.name %>") +<%# Cases -%> +<% entity.cases.each do |c| -%> + .value("<%= WasmCppHelper.format_name(c.name) %>", TW<%= entity.name %>::TW<%= entity.name %><%= c.name %>) +<% end -%> + ; +<%# Description -%> +<% if has_string -%> + function("describe<%= entity.name %>", describe<%= entity.name %>); +<% end -%> +} + +} // namespace TW::Wasm diff --git a/codegen/lib/templates/cpp/header.erb b/codegen/lib/templates/cpp/header.erb new file mode 100644 index 00000000000..1132e73092b --- /dev/null +++ b/codegen/lib/templates/cpp/header.erb @@ -0,0 +1,41 @@ +#include + +#include "../WasmString.h" +#include "../WasmData.h" + +namespace TW::Wasm { + +class <%= WasmCppHelper.class_name(entity: entity) %> { +<% if not entity.is_struct -%> + public: + TW<%= entity.name %> *instance; + public: + <%= WasmCppHelper.class_name(entity: entity) %>(TW<%= entity.name %> *instance) : instance(instance) {} +<% unless entity.methods.select{ |x| x.name == "Delete" }.empty? -%> + ~<%= WasmCppHelper.class_name(entity: entity) %>() { + TW<%= entity.name %>Delete(instance); + } +<% end -%> +<% end -%> + public: +<%# Constructors -%> +<% entity.static_methods.each do |method| -%> +<% next if method.name.start_with?('Create') || method.name.start_with?('Init') -%> + static auto <%= WasmCppHelper.function_name(entity: entity, function: method) %>(<%= WasmCppHelper.parameters(method.parameters) %>); +<% end -%> +<%# Properties -%> +<% entity.static_methods.each do |method| -%> +<% next unless method.name.start_with?('Create') -%> + static auto <%= WasmCppHelper.function_name(entity: entity, function: method) %>(<%= WasmCppHelper.parameters(method.parameters) %>) -> <%= WasmCppHelper.class_name(entity: entity) %>*; +<% end -%> +<%- entity.properties.each do |property| -%> + auto <%= WasmCppHelper.format_name(property.name) %>(); +<%- end -%> +<%# Methods -%> +<% entity.methods.each do |method| -%> +<% next if method.name == "Delete" -%> + auto <%= WasmCppHelper.format_name(method.name) %>(<%= WasmCppHelper.parameters(method.parameters.drop(1)) %>); +<% end -%> +}; // class <%= entity.name %> + +} // namespace TW::Wasm diff --git a/codegen/lib/templates/cpp/includes.erb b/codegen/lib/templates/cpp/includes.erb new file mode 100644 index 00000000000..6c3d2e996c4 --- /dev/null +++ b/codegen/lib/templates/cpp/includes.erb @@ -0,0 +1,21 @@ +<%# Include entity headers -%> +<% require 'set' -%> +<% includes = Set.new([entity.name]) -%> +<% (entity.static_methods + entity.methods).each do |method| -%> +<% includes << method.return_type.name if method.return_type.is_struct || method.return_type.is_class -%> +<% method.parameters.each do |param| -%> +<% includes << param.type.name if param.type.is_struct || param.type.is_class -%> +<% end -%> +<% end -%> +<% includes.each do |include| -%> +#include .h> +<% end -%> + +<% includes.each do |include| -%> +<% next if include == entity.name -%> +#include "./<%= include %>.h" +<% end -%> + +#include + +using namespace emscripten; diff --git a/codegen/lib/templates/cpp/method.erb b/codegen/lib/templates/cpp/method.erb new file mode 100644 index 00000000000..f4b0a5954f1 --- /dev/null +++ b/codegen/lib/templates/cpp/method.erb @@ -0,0 +1,6 @@ +<% method = locals[:method] -%> +<% arguments = locals[:arguments] || ['instance'] + WasmCppHelper.arguments(method.parameters.drop(1)) -%> +auto <%= WasmCppHelper.class_name(entity: entity) %>::<%= WasmCppHelper.format_name(method.name) %>(<%= WasmCppHelper.parameters(method.parameters.drop(1)) %>) { +<%= render('cpp/parameter_access.erb', { parameters: method.parameters }) -%> +<%= render('cpp/method_forward.erb', { method: method, arguments: arguments }) -%> +} diff --git a/codegen/lib/templates/cpp/method_call.erb b/codegen/lib/templates/cpp/method_call.erb new file mode 100644 index 00000000000..9b24e549e30 --- /dev/null +++ b/codegen/lib/templates/cpp/method_call.erb @@ -0,0 +1,5 @@ +<% + method = locals[:method] + arguments = locals[:arguments] || ['instance'] + WasmCppHelper.arguments(method.parameters.drop(1)) +-%> +TW<%= entity.name %><%= method.name %>(<%= arguments.join(', ') %>) \ No newline at end of file diff --git a/codegen/lib/templates/cpp/method_forward.erb b/codegen/lib/templates/cpp/method_forward.erb new file mode 100644 index 00000000000..46a856a5118 --- /dev/null +++ b/codegen/lib/templates/cpp/method_forward.erb @@ -0,0 +1,29 @@ +<% + method = locals[:method] + arguments = locals[:arguments] || ['instance'] + WasmCppHelper.arguments(method.parameters.drop(1)) + call = render('cpp/method_call.erb', { method: method, arguments: arguments }) + + # Method returns data + if should_return_data(method) + if method.return_type.is_nullable -%> + return TWDataToVal(<%= call %>); +<% else -%> + return TWDataToVal(<%= call %>); +<% end + # Method returns a string + elsif should_return_string(method) + if method.return_type.is_nullable -%> + return TWStringToStd(<%= call %>); +<% else -%> + return TWStringToStd(<%= call %>); +<% end + # Method returns a class or struct + elsif method.return_type.is_class || method.return_type.is_struct + if method.return_type.is_nullable -%> + return new Wasm<%= method.return_type.name %>(<%= call %>); +<% else -%> + return new Wasm<%= method.return_type.name %>(<%= call %>); +<% end + else -%> + return <%= call %>; +<%end -%> diff --git a/codegen/lib/templates/cpp/parameter_access.erb b/codegen/lib/templates/cpp/parameter_access.erb new file mode 100644 index 00000000000..da6e41d367a --- /dev/null +++ b/codegen/lib/templates/cpp/parameter_access.erb @@ -0,0 +1,8 @@ +<% parameters = locals[:parameters] -%> +<% parameters.each do |param| -%> +<% if param.type.name == :data -%> + auto <%= param.name %>Data = TW::data(<%= param.name %>); +<% elsif param.type.name == :string -%> +<% elsif param.type.is_struct || param.type.is_class -%> +<% end -%> +<% end -%> diff --git a/codegen/lib/templates/cpp/static_method.erb b/codegen/lib/templates/cpp/static_method.erb new file mode 100644 index 00000000000..86f9bc2734e --- /dev/null +++ b/codegen/lib/templates/cpp/static_method.erb @@ -0,0 +1,6 @@ +<% method = locals[:method] -%> +<% arguments = WasmCppHelper.arguments(method.parameters) -%> +auto <%= WasmCppHelper.class_name(entity: entity) %>::<%= WasmCppHelper.function_name(entity: entity, function: method) %>(<%= WasmCppHelper.parameters(method.parameters) %>) { +<%= render('cpp/parameter_access.erb', { parameters: method.parameters }) -%> +<%= render('cpp/method_forward.erb', { method: method, arguments: arguments }) -%> +} diff --git a/codegen/lib/templates/hrp.h.erb b/codegen/lib/templates/hrp.h.erb index b6706fdef39..0f4ed0432f1 100644 --- a/codegen/lib/templates/hrp.h.erb +++ b/codegen/lib/templates/hrp.h.erb @@ -4,7 +4,7 @@ // 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 from \coins.json, changes made here WILL BE LOST. +// This is a GENERATED FILE from \registry.json, changes made here WILL BE LOST. // #pragma once diff --git a/codegen/lib/templates/java/class.erb b/codegen/lib/templates/java/class.erb index 8f9d3367739..69cd027f403 100644 --- a/codegen/lib/templates/java/class.erb +++ b/codegen/lib/templates/java/class.erb @@ -3,6 +3,7 @@ import java.util.HashSet; <% less = entity.static_methods.detect{ |i| i.name == 'Less' } -%> <% equal = entity.static_methods.detect{ |i| i.name == 'Equal' } -%> +<%= entity.comment %> <% if !less.nil? && !equal.nil? -%> public class <%= entity.name %> implements Comparable<<%= entity.name %>> { <% else -%> @@ -24,10 +25,10 @@ public class <%= entity.name %> { <% end -%> return instance; } - <%# Constructor declarations -%> <% entity.static_methods.each do |method| -%> <% next unless method.name.start_with?('Create') -%> +<%= method.comment_with_indent %> static native long native<%= method.name %>(<%= JavaHelper.parameters(method.parameters) %>); <% end -%> <%# Destructor declarations -%> @@ -35,9 +36,9 @@ public class <%= entity.name %> { <% next unless method.name.start_with?('Delete') -%> static native void native<%= method.name %>(long handle); <% end -%> - <%# Static property declarations -%> <% entity.static_properties.each do |property| -%> +<%= property.comment_with_indent %> <% if should_return_data(property) -%> public static native byte[] <%= JavaHelper.format_name(property.name) %>(<%= JavaHelper.parameters(property.parameters) %>); <% elsif should_return_string(property) -%> @@ -49,6 +50,7 @@ public class <%= entity.name %> { <%# Static method declarations -%> <% entity.static_methods.each do |method| -%> <% next if method.name.start_with?('Create') -%> +<%= method.comment_with_indent %> <% if should_return_data(method) -%> public static native byte[] <%= JavaHelper.format_name(method.name) %>(<%= JavaHelper.parameters(method.parameters) %>); <% elsif should_return_string(method) -%> @@ -59,6 +61,7 @@ public class <%= entity.name %> { <% end -%> <%# Property declarations -%> <% entity.properties.each do |property| -%> +<%= property.comment_with_indent %> <% if should_return_data(property) -%> public native byte[] <%= JavaHelper.format_name(property.name) %>(<%= JavaHelper.parameters(property.parameters.drop(1)) %>); <% elsif should_return_string(property) -%> @@ -70,6 +73,7 @@ public class <%= entity.name %> { <%# Method declarations -%> <% entity.methods.each do |method| -%> <% next if method.name.start_with?('Delete') -%> +<%= method.comment_with_indent %> <% if should_return_data(method) -%> public native byte[] <%= JavaHelper.format_name(method.name) %>(<%= JavaHelper.parameters(method.parameters.drop(1)) %>); <% elsif should_return_string(method) -%> @@ -82,10 +86,10 @@ public class <%= entity.name %> { <% compareMethod = JNIHelper.compareMethod(entity) -%> public native <%= JavaHelper.type(compareMethod.return_type) %> <%= JavaHelper.format_name(compareMethod.name) %>(<%= JavaHelper.parameters(compareMethod.parameters.drop(1)) %>); <% end -%> - <%# Constructors -%> <%- entity.static_methods.each do |method| -%> <%- next unless method.name.start_with?('Create') -%> +<%= method.comment_with_indent %> public <%= entity.name %>(<%= JavaHelper.parameters(method.parameters) %>) { nativeHandle = native<%= method.name %>(<%= JavaHelper.arguments(method.parameters) %>); if (nativeHandle == 0) { @@ -97,7 +101,6 @@ public class <%= entity.name %> { <%- end -%> } - <% unless entity.methods.select{ |x| x.name == "Delete" }.empty? -%> class <%= entity.name %>PhantomReference extends java.lang.ref.PhantomReference<<%= entity.name %>> { private static java.util.Set<<%= entity.name %>PhantomReference> references = new HashSet<<%= entity.name %>PhantomReference>(); diff --git a/codegen/lib/templates/java/enum.erb b/codegen/lib/templates/java/enum.erb index 27947182cc8..354b7c145a3 100644 --- a/codegen/lib/templates/java/enum.erb +++ b/codegen/lib/templates/java/enum.erb @@ -1,4 +1,5 @@ <% has_string = entity.cases.all? { |c| !c.string.nil? } -%> +<%= entity.comment %> <% type = entity.raw_type ? JavaHelper.type(entity.raw_type) : 'int' -%> public enum <%= entity.name %> { <%# Cases -%> diff --git a/codegen/lib/templates/java/header.erb b/codegen/lib/templates/java/header.erb index e596ae5e9fe..b1b1aeabc86 100644 --- a/codegen/lib/templates/java/header.erb +++ b/codegen/lib/templates/java/header.erb @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 diff --git a/codegen/lib/templates/java/struct.erb b/codegen/lib/templates/java/struct.erb index 5499705670d..bb57cf25017 100644 --- a/codegen/lib/templates/java/struct.erb +++ b/codegen/lib/templates/java/struct.erb @@ -2,6 +2,7 @@ import java.security.InvalidParameterException; <% less = entity.static_methods.detect{ |i| i.name == 'Less' } -%> <% equal = entity.static_methods.detect{ |i| i.name == 'Equal' } -%> +<%= entity.comment %> <% if !less.nil? && !equal.nil? -%> public class <%= entity.name %> implements Comparable<<%= entity.name %>> { <% else -%> @@ -17,15 +18,15 @@ public class <%= entity.name %> { instance.bytes = bytes; return instance; } - <%# Constructor declarations -%> <%- entity.static_methods.each do |method| -%> <%- next unless method.name.start_with?('Init') -%> +<%= method.comment_with_indent %> static native byte[] <%= JavaHelper.format_name(method.name) %>(<%= JavaHelper.parameters(method.parameters.drop(1)) %>); <%- end -%> - <%# Static property declarations -%> <%- entity.static_properties.each do |property| -%> +<%= property.comment_with_indent %> <%- if should_return_data(property) -%> public static native byte[] <%= JavaHelper.format_name(property.name) %>(<%= JavaHelper.parameters(property.parameters) %>); <%- else -%> @@ -35,6 +36,7 @@ public class <%= entity.name %> { <%# Static method declarations -%> <%- entity.static_methods.each do |method| -%> <%- next if method.name.start_with?('Init') -%> +<%= method.comment_with_indent %> <%- if should_return_data(method) -%> public static native byte[] <%= JavaHelper.format_name(method.name) %>(<%= JavaHelper.parameters(method.parameters) %>); <%- else -%> @@ -43,6 +45,7 @@ public class <%= entity.name %> { <%- end -%> <%# Property declarations -%> <%- entity.properties.each do |property| -%> +<%= property.comment_with_indent %> <%- if should_return_data(property) -%> public native byte[] <%= JavaHelper.format_name(property.name) %>(<%= JavaHelper.parameters(property.parameters.drop(1)) %>); <%- else -%> @@ -52,6 +55,7 @@ public class <%= entity.name %> { <%# Method declarations -%> <%- entity.methods.each do |method| -%> <%- next if method.name.start_with?('Delete') -%> +<%= method.comment_with_indent %> <%- if should_return_data(method) -%> public native byte[] <%= JavaHelper.format_name(method.name) %>(<%= JavaHelper.parameters(method.parameters.drop(1)) %>); <%- else -%> @@ -62,16 +66,15 @@ public class <%= entity.name %> { <% compareMethod = JNIHelper.compareMethod(entity) -%> public native <%= JavaHelper.type(compareMethod.return_type) %> <%= JavaHelper.format_name(compareMethod.name) %>(<%= JavaHelper.parameters(compareMethod.parameters.drop(1)) %>); <% end -%> - <%# Constructors -%> <%- entity.static_methods.each do |method| -%> <%- next unless method.name.start_with?('Init') -%> +<%= method.comment_with_indent %> public <%= entity.name %>(<%= JavaHelper.parameters(method.parameters.drop(1)) %>) { bytes = <%= JavaHelper.format_name(method.name) %>(<%= JavaHelper.arguments(method.parameters.drop(1)) %>); if (bytes == null) { throw new InvalidParameterException(); } } - <%- end -%> } diff --git a/codegen/lib/templates/jni_c.erb b/codegen/lib/templates/jni_c.erb index cf807ae79ad..d7785723aac 100644 --- a/codegen/lib/templates/jni_c.erb +++ b/codegen/lib/templates/jni_c.erb @@ -3,7 +3,7 @@ #include <% require 'set' -%> -<% includes = SortedSet.new([entity.name]) -%> +<% includes = Set.new([entity.name]) -%> <% entity.static_methods.each do |method| -%> <% includes << method.return_type.name if method.return_type.is_struct || method.return_type.is_class -%> <% method.parameters.each do |param| -%> diff --git a/codegen/lib/templates/newcoin/Address.cpp.erb b/codegen/lib/templates/newcoin/Address.cpp.erb index 0f15d92edcc..e9eb3b1412f 100644 --- a/codegen/lib/templates/newcoin/Address.cpp.erb +++ b/codegen/lib/templates/newcoin/Address.cpp.erb @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -6,7 +6,7 @@ #include "Address.h" -using namespace TW::<%= format_name(coin) %>; +namespace TW::<%= format_name(coin) %> { bool Address::isValid(const std::string& string) { // TODO: Finalize implementation @@ -29,3 +29,5 @@ std::string Address::string() const { // TODO: Finalize implementation return "TODO"; } + +} // namespace TW::<%= format_name(coin) %> diff --git a/codegen/lib/templates/newcoin/Address.h.erb b/codegen/lib/templates/newcoin/Address.h.erb index a9e30cb303c..9c6f60df4f6 100644 --- a/codegen/lib/templates/newcoin/Address.h.erb +++ b/codegen/lib/templates/newcoin/Address.h.erb @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -36,8 +36,3 @@ inline bool operator==(const Address& lhs, const Address& rhs) { } } // namespace TW::<%= format_name(coin) %> - -/// Wrapper for C interface. -struct TW<%= format_name(coin) %>Address { - TW::<%= format_name(coin) %>::Address impl; -}; diff --git a/codegen/lib/templates/newcoin/AddressTests.cpp.erb b/codegen/lib/templates/newcoin/AddressTests.cpp.erb index 9953e01fbdc..ca9d99d3139 100644 --- a/codegen/lib/templates/newcoin/AddressTests.cpp.erb +++ b/codegen/lib/templates/newcoin/AddressTests.cpp.erb @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -11,8 +11,7 @@ #include #include -using namespace TW; -using namespace TW::<%= format_name(coin) %>; +namespace TW::<%= format_name(coin) %>::tests { TEST(<%= format_name(coin) %>Address, Valid) { ASSERT_TRUE(Address::isValid("__ADD_VALID_ADDRESS_HERE__")); @@ -46,3 +45,5 @@ TEST(<%= format_name(coin) %>Address, FromString) { auto address = Address("__ADD_VALID_ADDRESS_HERE__"); ASSERT_EQ(address.string(), "__ADD_SAME_VALID_ADDRESS_HERE__"); } + +} // namespace TW::<%= format_name(coin) %>::tests diff --git a/codegen/lib/templates/newcoin/AddressTests.kt.erb b/codegen/lib/templates/newcoin/AddressTests.kt.erb index f118db94a20..34781800a4c 100644 --- a/codegen/lib/templates/newcoin/AddressTests.kt.erb +++ b/codegen/lib/templates/newcoin/AddressTests.kt.erb @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 diff --git a/codegen/lib/templates/newcoin/Entry.cpp.erb b/codegen/lib/templates/newcoin/Entry.cpp.erb index 348e884d9f0..d0585ed64b9 100644 --- a/codegen/lib/templates/newcoin/Entry.cpp.erb +++ b/codegen/lib/templates/newcoin/Entry.cpp.erb @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -9,19 +9,20 @@ #include "Address.h" #include "Signer.h" -using namespace TW::<%= format_name(coin) %>; -using namespace std; +namespace TW::<%= format_name(coin) %> { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress(TWCoinType coin, const std::string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey).string(); } void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +} // namespace TW::<%= format_name(coin) %> diff --git a/codegen/lib/templates/newcoin/Entry.h.erb b/codegen/lib/templates/newcoin/Entry.h.erb index ea5cd318bf7..1b58f5f78cd 100644 --- a/codegen/lib/templates/newcoin/Entry.h.erb +++ b/codegen/lib/templates/newcoin/Entry.h.erb @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -12,9 +12,8 @@ namespace TW::<%= format_name(coin) %> { /// Entry point for implementation of <%= format_name(coin) %> coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinType<%= format_name(coin) %>}; } virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; diff --git a/codegen/lib/templates/newcoin/Proto.erb b/codegen/lib/templates/newcoin/Proto.erb index d7bdb047f8e..3065a609020 100644 --- a/codegen/lib/templates/newcoin/Proto.erb +++ b/codegen/lib/templates/newcoin/Proto.erb @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 diff --git a/codegen/lib/templates/newcoin/Signer.cpp.erb b/codegen/lib/templates/newcoin/Signer.cpp.erb index 381832fb176..7dec6579529 100644 --- a/codegen/lib/templates/newcoin/Signer.cpp.erb +++ b/codegen/lib/templates/newcoin/Signer.cpp.erb @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -8,9 +8,7 @@ #include "Address.h" #include "../PublicKey.h" -using namespace TW; -using namespace TW::<%= name %>; - +namespace TW::<%= name %> { Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { // TODO: Check and finalize implementation @@ -24,3 +22,5 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { protoOutput.set_encoded(encoded.data(), encoded.size()); return protoOutput; } + +} // namespace TW::<%= name %> diff --git a/codegen/lib/templates/newcoin/Signer.h.erb b/codegen/lib/templates/newcoin/Signer.h.erb index 7345aad9c84..1261f9c6149 100644 --- a/codegen/lib/templates/newcoin/Signer.h.erb +++ b/codegen/lib/templates/newcoin/Signer.h.erb @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -23,8 +23,3 @@ public: }; } // namespace TW::<%= name %> - -/// Wrapper for C interface. -struct TW<%= name %>Signer { - TW::<%= name %>::Signer impl; -}; diff --git a/codegen/lib/templates/newcoin/SignerTests.cpp.erb b/codegen/lib/templates/newcoin/SignerTests.cpp.erb index 27eb5ef11ee..9df8b26ee1e 100644 --- a/codegen/lib/templates/newcoin/SignerTests.cpp.erb +++ b/codegen/lib/templates/newcoin/SignerTests.cpp.erb @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -12,8 +12,7 @@ #include -using namespace TW; -using namespace TW::<%= format_name(coin) %>; +namespace TW::<%= format_name(coin) %>::tests { // TODO: Add tests @@ -32,3 +31,5 @@ TEST(<%= format_name(coin) %>Signer, Sign) { //ASSERT_EQ(hex(serialized), "__RESULT__"); //ASSERT_EQ(...) } + +} // namespace TW::<%= format_name(coin) %>::tests diff --git a/codegen/lib/templates/newcoin/SignerTests.kt.erb b/codegen/lib/templates/newcoin/SignerTests.kt.erb index ba0bbafcfd9..e0512c55b1d 100644 --- a/codegen/lib/templates/newcoin/SignerTests.kt.erb +++ b/codegen/lib/templates/newcoin/SignerTests.kt.erb @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 diff --git a/codegen/lib/templates/newcoin/TWAddressTests.cpp.erb b/codegen/lib/templates/newcoin/TWAddressTests.cpp.erb index 11331829f38..b290debae7a 100644 --- a/codegen/lib/templates/newcoin/TWAddressTests.cpp.erb +++ b/codegen/lib/templates/newcoin/TWAddressTests.cpp.erb @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -7,7 +7,7 @@ #include #include "HexCoding.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include using namespace TW; diff --git a/codegen/lib/templates/newcoin/TWSignerTests.cpp.erb b/codegen/lib/templates/newcoin/TWSignerTests.cpp.erb index a59a2fdc772..59b6aa225c6 100644 --- a/codegen/lib/templates/newcoin/TWSignerTests.cpp.erb +++ b/codegen/lib/templates/newcoin/TWSignerTests.cpp.erb @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -7,7 +7,7 @@ #include #include "HexCoding.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include using namespace TW; diff --git a/codegen/lib/templates/newcoin/Tests.swift.erb b/codegen/lib/templates/newcoin/Tests.swift.erb index 58851cb7024..583696882c5 100644 --- a/codegen/lib/templates/newcoin/Tests.swift.erb +++ b/codegen/lib/templates/newcoin/Tests.swift.erb @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 diff --git a/codegen/lib/templates/registry.md.erb b/codegen/lib/templates/registry.md.erb index a62b332c1d6..e642b3bc7cd 100644 --- a/codegen/lib/templates/registry.md.erb +++ b/codegen/lib/templates/registry.md.erb @@ -5,5 +5,5 @@ This list is generated from [./registry.json](../registry.json) | Index | Name | Symbol | Logo | URL | | ------- | ---------------- | ------ | --------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | <% coins.select{ |c| c['deprecated'].nil? }.each do |coin| -%> -| <%= coin['coinId'].to_s.ljust(7, " ") %> | <%= coin['name'].ljust(16, " ") %> | <%= coin['symbol'].ljust(6, " ") %> | <%= coin_img(coin['id']).ljust(123) %> | <%= "<#{coin['info']['url']}>".ljust(29, " ") %> | +| <%= coin['coinId'].to_s.ljust(7, " ") %> | <%= coin_name(coin).ljust(16, " ") %> | <%= coin['symbol'].ljust(6, " ") %> | <%= coin_img(coin['id']).ljust(123) %> | <%= "<#{coin['info']['url']}>".ljust(29, " ") %> | <% end -%> diff --git a/codegen/lib/templates/swift/class.erb b/codegen/lib/templates/swift/class.erb index 4d090e7bc70..3404e02778d 100644 --- a/codegen/lib/templates/swift/class.erb +++ b/codegen/lib/templates/swift/class.erb @@ -1,9 +1,11 @@ import Foundation <% protocols = SwiftHelper.protocol(entity) -%> +<%= entity.comment %> public final class <%= entity.name %><% unless protocols.empty? %>: <%= protocols.join(', ') %><% end %> { <%# Static properties -%> <% entity.static_properties.each do |property| -%> +<%= property.comment_with_indent %> public static var <%= SwiftHelper.format_name(property.name) %>: <%= SwiftHelper.type(property.return_type) %> { <%- if property.return_type.is_class || property.return_type.is_struct -%> return <%= SwiftHelper.type(property.return_type) %>(rawValue: TW<%= entity.name %><%= property.name %>()) diff --git a/codegen/lib/templates/swift/class_properties.erb b/codegen/lib/templates/swift/class_properties.erb index c36feb40cb7..25c9a7c4c9d 100644 --- a/codegen/lib/templates/swift/class_properties.erb +++ b/codegen/lib/templates/swift/class_properties.erb @@ -1,5 +1,6 @@ <%# Properties -%> <%- entity.properties.each do |property| -%> +<%= property.comment_with_indent %> public var <%= SwiftHelper.format_name(property.name) %>: <%= SwiftHelper.type(property.return_type) %> { <%= render('swift/method_forward.erb', { method: property }) -%> } diff --git a/codegen/lib/templates/swift/enum.erb b/codegen/lib/templates/swift/enum.erb index e54c283f148..6c1165edab5 100644 --- a/codegen/lib/templates/swift/enum.erb +++ b/codegen/lib/templates/swift/enum.erb @@ -1,4 +1,5 @@ <% has_string = entity.cases.all? { |c| !c.string.nil? } -%> +<%= entity.comment %> <% type = entity.raw_type ? SwiftHelper.type(entity.raw_type) : 'UInt32' -%> public enum <%= entity.name %>: <%= type %>, CaseIterable<% if has_string %>, CustomStringConvertible <% end %> { <%# Cases -%> diff --git a/codegen/lib/templates/swift/enum_extension.erb b/codegen/lib/templates/swift/enum_extension.erb index 133a6028953..c5f4f2d3f0c 100644 --- a/codegen/lib/templates/swift/enum_extension.erb +++ b/codegen/lib/templates/swift/enum_extension.erb @@ -1,7 +1,7 @@ extension <%= entity.name %> { <%# Properties -%> <%- entity.properties.each do |property| -%> - +<%= property.comment_with_indent %> public var <%= SwiftHelper.format_name(property.name) %>: <%= SwiftHelper.type(property.return_type) %> { <%= render('swift/method_forward.erb', { method: property, arguments: ["TW#{entity.name}(rawValue: rawValue)"] }) -%> } diff --git a/codegen/lib/templates/swift/header.erb b/codegen/lib/templates/swift/header.erb deleted file mode 100644 index 0770a18cd04..00000000000 --- a/codegen/lib/templates/swift/header.erb +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here WILL BE LOST. -// diff --git a/codegen/lib/templates/swift/method.erb b/codegen/lib/templates/swift/method.erb index d50b6a0629a..d9700123165 100644 --- a/codegen/lib/templates/swift/method.erb +++ b/codegen/lib/templates/swift/method.erb @@ -1,4 +1,5 @@ <% method = locals[:method] -%> +<%= method.comment_with_indent %> <% arguments = locals[:arguments] || ['rawValue'] + SwiftHelper.arguments(method.parameters.drop(1)) -%> <% if method.discardable_result -%> @discardableResult diff --git a/codegen/lib/templates/swift/parameter_access.erb b/codegen/lib/templates/swift/parameter_access.erb index 8521c18bb4a..ddf4335753f 100644 --- a/codegen/lib/templates/swift/parameter_access.erb +++ b/codegen/lib/templates/swift/parameter_access.erb @@ -10,12 +10,14 @@ let <%= param.name %>String: UnsafeRawPointer? if let s = <%= param.name %> { <%= param.name %>String = TWStringCreateWithNSString(s) - defer { - TWStringDelete(s) - } } else { <%= param.name %>String = nil } + defer { + if let s = <%= param.name %>String { + TWStringDelete(s) + } + } <% else -%> let <%= param.name %>String = TWStringCreateWithNSString(<%= param.name %>) defer { diff --git a/codegen/lib/templates/swift/static_method.erb b/codegen/lib/templates/swift/static_method.erb index 5a9638220cf..8ecd73229be 100644 --- a/codegen/lib/templates/swift/static_method.erb +++ b/codegen/lib/templates/swift/static_method.erb @@ -1,4 +1,5 @@ <% method = locals[:method] -%> +<%= method.comment_with_indent %> <% arguments = SwiftHelper.arguments(method.parameters) -%> <% if method.discardable_result -%> @discardableResult diff --git a/codegen/lib/templates/swift/struct.erb b/codegen/lib/templates/swift/struct.erb index e7c3b2ca7ea..518588bd0ac 100644 --- a/codegen/lib/templates/swift/struct.erb +++ b/codegen/lib/templates/swift/struct.erb @@ -1,9 +1,11 @@ import Foundation <% protocols = SwiftHelper.protocol(entity) -%> +<%= entity.comment %> public struct <%= entity.name %><% unless protocols.empty? %>: <%= protocols.join(', ') %><% end %> { <%# Static properties -%> <% entity.static_properties.each do |property| -%> +<%= property.comment_with_indent %> public static var <%= SwiftHelper.format_name(property.name) %>: <%= SwiftHelper.type(property.return_type) %> { <%- if property.return_type.is_class || property.return_type.is_struct -%> return <%= SwiftHelper.type(property.return_type) %>(rawValue: TW<%= entity.name %><%= property.name %>()) diff --git a/codegen/lib/templates/swift/struct_properties.erb b/codegen/lib/templates/swift/struct_properties.erb index ebd50de5629..10d896df1e4 100644 --- a/codegen/lib/templates/swift/struct_properties.erb +++ b/codegen/lib/templates/swift/struct_properties.erb @@ -1,6 +1,7 @@ <%# Properties -%> <%- entity.properties.each do |property| -%> +<%= property.comment_with_indent %> public var <%= SwiftHelper.format_name(property.name) %>: <%= SwiftHelper.type(property.return_type) %> { <%= render('swift/method_forward.erb', { method: property }) -%> } diff --git a/codegen/lib/templates/ts/class_d.erb b/codegen/lib/templates/ts/class_d.erb new file mode 100644 index 00000000000..87591ca7978 --- /dev/null +++ b/codegen/lib/templates/ts/class_d.erb @@ -0,0 +1,22 @@ +export class <%= entity.name %> { +<% entity.static_methods.each do |method| -%> +<% next if method.name.start_with?('Create') || method.name.start_with?('Init') -%> + static <%= WasmCppHelper.function_name(entity: entity, function: method) %>(<%= TsHelper.parameters(method.parameters) %>): <%= TsHelper.type(method.return_type) %>; +<% end -%> +<% entity.static_methods.each do |method| -%> +<% next unless method.name.start_with?('Create') -%> + static <%= WasmCppHelper.function_name(entity: entity, function: method) %>(<%= TsHelper.parameters(method.parameters) %>): <%= TsHelper.type(method.return_type) %>; +<% end -%> +<%- entity.properties.each do |property| -%> + <%= WasmCppHelper.format_name(property.name) %>(): <%= TsHelper.type(property.return_type) %>; +<%- end -%> +<% entity.methods.each do |method| -%> +<% next if method.name == "Delete" -%> + <%= WasmCppHelper.format_name(method.name) %>(<%= TsHelper.parameters(method.parameters.drop(1)) %>): <%= TsHelper.type(method.return_type) %>; +<% end -%> +<% if not entity.is_struct -%> +<% unless entity.methods.select{ |x| x.name == "Delete" }.empty? -%> + delete(): void; +<% end -%> +<% end -%> +} diff --git a/codegen/lib/templates/ts/enum_d.erb b/codegen/lib/templates/ts/enum_d.erb new file mode 100644 index 00000000000..18b5b2c973d --- /dev/null +++ b/codegen/lib/templates/ts/enum_d.erb @@ -0,0 +1,12 @@ +export class <%= entity.name %> { + value: number; +<% entity.cases.each do |c| -%> + static <%= WasmCppHelper.format_name(c.name) %>: <%= entity.name %>; +<% end -%> +} +<% has_string = entity.cases.all? { |c| !c.string.nil? } -%> +<% if has_string -%> + +declare function describe<%= entity.name %>(value: <%= entity.name %>): string; + +<% end -%> diff --git a/codegen/lib/templates/wasm_cpp.erb b/codegen/lib/templates/wasm_cpp.erb new file mode 100644 index 00000000000..2d3338e94fb --- /dev/null +++ b/codegen/lib/templates/wasm_cpp.erb @@ -0,0 +1,7 @@ +<% if entity.is_a?(EnumDecl) -%> +<%= render('cpp/includes.erb') -%> + +<%= render('cpp/enum.erb') -%> +<% else -%> +<%= render('cpp/class.erb') -%> +<% end -%> diff --git a/codegen/lib/templates/wasm_d_ts.erb b/codegen/lib/templates/wasm_d_ts.erb new file mode 100644 index 00000000000..7600b54197b --- /dev/null +++ b/codegen/lib/templates/wasm_d_ts.erb @@ -0,0 +1,5 @@ +<% if entity.is_a?(EnumDecl) -%> +<%= render('ts/enum_d.erb') -%> +<% else -%> +<%= render('ts/class_d.erb') -%> +<% end -%> diff --git a/codegen/lib/templates/wasm_h.erb b/codegen/lib/templates/wasm_h.erb new file mode 100644 index 00000000000..38215a86f2b --- /dev/null +++ b/codegen/lib/templates/wasm_h.erb @@ -0,0 +1,7 @@ +#pragma once + +<%= render('cpp/includes.erb') -%> + +<% if not entity.is_a?(EnumDecl) -%> +<%= render('cpp/header.erb') -%> +<% end -%> diff --git a/codegen/lib/ts_helper.rb b/codegen/lib/ts_helper.rb new file mode 100644 index 00000000000..a75027b8999 --- /dev/null +++ b/codegen/lib/ts_helper.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +module TsHelper + def self.parameters(params) + names = params.map do |param| + type = type(param.type) + if param.type.name == :data + type += ' | Buffer' + end + "#{param.name}: #{type}" + end + names.join(', ') + end + + def self.primitive_type(t) + case t.name + when :bool + 'boolean' + when :int, :uint8, :uint16, :uint32, :uint64, :size + 'number' + when :data + 'Uint8Array | Buffer' + when :string + 'string' + else + raise "Invalid type #{t.name}" + end + end + + def self.type(t) + case t.name + when :void + 'void' + when :bool + 'boolean' + when :int, :uint8, :int8, :uint16, :int16, :uint32, :int32, :uint64, :int64, :size + 'number' + when :data + 'Uint8Array' + when :string + 'string' + else + t.name + end + end + + def self.combine_declaration_files + wasm_src = File.expand_path(File.join(File.dirname(__FILE__), '../../wasm')) + header = File.expand_path('copyright_header.erb', File.join(File.dirname(__FILE__), 'templates')) + combined_path = "#{wasm_src}/src/wallet-core.d.ts" + + combined = File.open(combined_path, 'w') + # append header + combined.write(File.read(header)) + + # append .d.ts in src + Dir.glob("#{wasm_src}/src/*.d.ts").each do |file| + combined.write(File.read(file)) + end + + # append .d.ts in generated + Dir.glob("#{wasm_src}/lib/generated/*.d.ts").each do |file| + combined.write(File.read(file)) + end + combined.close + FileUtils.remove_dir("#{wasm_src}/lib/generated", true) + + # generate WalletCore interface + interface = "export interface WalletCore {\n" + + combined = File.open(combined_path, 'r') + all_lines = combined.read + combined.close + + export_regex = /^export (class|namespace) (.*)\b/ + declare_regex = /^declare function (.+?(?=\())/ + + all_lines.scan(export_regex).each do |match| + matched = match[1] + interface += " #{matched}: typeof #{matched};\n" + end + + all_lines.scan(declare_regex).each do |match| + matched = match[0] + interface += " #{matched}: typeof #{matched};\n" + end + interface += "}\n" + + File.open(combined_path, 'a') do |file| + file << interface + end + end +end diff --git a/codegen/lib/wasm_cpp_helper.rb b/codegen/lib/wasm_cpp_helper.rb new file mode 100644 index 00000000000..98ba4b07b46 --- /dev/null +++ b/codegen/lib/wasm_cpp_helper.rb @@ -0,0 +1,116 @@ +# frozen_string_literal: true + +module WasmCppHelper + # Transforms an interface name to a cpp method name + def self.format_name(name) + result = name + match = /^([A-Z]+)/.match(name) + result = name.sub(match[1], match[1].downcase) unless match.nil? + result + end + + # Transforms a method/property name to a cpp function name + + def self.class_name(entity:) + "Wasm" + entity.name + end + + def self.function_name(entity:, function:) + "#{format_name(function.name)}" + end + + def self.parameters(params) + names = params.map do |param| + "#{type(param.type)} #{param.name}" + end + names.join(', ') + end + + def self.arguments(params) + params.map do |param| + if param.type.name == :data + "&#{param.name}Data" + elsif param.type.name == :string + '&' + param.name + elsif param.type.is_struct || param.type.is_class + param.name + '->instance' + else + param.name + end + end + end + + def self.primitive_type(t) + case t.name + when :bool + 'bool' + when :int + 'int' + when :uint8 + 'uint8_t' + when :size + 'size_t' + when :uint16 + 'uint16_t' + when :uint32 + 'uint32_t' + when :uint64 + 'uint64_t' + when :string + 'std::string' + else + raise "Invalid type #{t.name}" + end + end + + def self.type(t) + case t.name + when :void + 'void' + when :bool + 'bool' + when :int + 'int' + when :uint8 + 'uint8_t' + when :uint16 + 'uint16_t' + when :uint32 + 'uint32_t' + when :uint64 + 'uint64_t' + when :int8 + 'int8_t' + when :int16 + 'int16_t' + when :int32 + 'int32_t' + when :int64 + 'int64_t' + when :size + 'size_t' + when :data + 'const std::string&' + when :string + 'const std::string&' + else + if t.is_enum + "TW#{t.name}" + elsif t.is_struct || t.is_class + "Wasm#{t.name}*" + else + t.name + end + end + end + + def self.compareMethod(entity) + FunctionDecl.new( + name: 'compareTo', + entity: entity, + is_method: true, + return_type: TypeDecl.new(name: :int), + parameters: [Parameter.new(name: 'thisObject', type: entity.type), Parameter.new(name: 'other', type: entity.type)], + static: false) + end + end diff --git a/codegen/test/test_jni_helper.rb b/codegen/test/test_jni_helper.rb index 766c8dfdcf7..595ff1e53e2 100644 --- a/codegen/test/test_jni_helper.rb +++ b/codegen/test/test_jni_helper.rb @@ -8,7 +8,7 @@ def test_format_name end def test_function_name - entity = EntityDecl.new(name: 'Test', is_struct: false) + entity = EntityDecl.new(name: 'Test', is_struct: false, comment: '') method = FunctionDecl.new(name: 'Function', entity: entity, is_method: true) name = JNIHelper.function_name(entity: entity, function: method) assert_equal(name, 'Java_wallet_core_jni_Test_function') diff --git a/codegen/test/test_parser.rb b/codegen/test/test_parser.rb index 6f51a726695..7ec0af335e8 100644 --- a/codegen/test/test_parser.rb +++ b/codegen/test/test_parser.rb @@ -44,22 +44,26 @@ def test_parse_invalid_method def test_parse_method_discardable_result parser = Parser.new(path: '', string: ' + // This is a sample file TW_EXTERN_C_BEGIN struct TWEthereumAbiFunction; + // Ethereuem ABI helpers TW_EXPORT_CLASS struct TWEthereumAbiEncoder; - /// Encode function to Eth ABI binary + // Encode function to Eth ABI binary TW_EXPORT_STATIC_METHOD TW_METHOD_DISCARDABLE_RESULT TWData*_Nonnull TWEthereumAbiEncoderEncode(struct TWEthereumAbiFunction *_Nonnull func_in); TW_EXTERN_C_END ') parser.parse + assert_equal(parser.entity.name, 'EthereumAbiEncoder') method = parser.entity.static_methods[0] assert_equal(method.return_type.name, :data) assert_equal(method.name, 'Encode') assert_equal(method.discardable_result, true) + assert_equal(method.comment, '// Encode function to Eth ABI binary') end def test_init diff --git a/coverage.stats b/coverage.stats index ee1376541f1..b99dacf6dff 100644 --- a/coverage.stats +++ b/coverage.stats @@ -1 +1 @@ -94.6 +95.0 \ No newline at end of file diff --git a/docs/registry-fields.md b/docs/registry-fields.md new file mode 100644 index 00000000000..f4662d510cd --- /dev/null +++ b/docs/registry-fields.md @@ -0,0 +1,196 @@ +# Documentation for registry.json + +The file `registry.json` contains meta info about supported blockchains. +It is the input for some generated source files and documentation ([registry.md](registry.md)), and values from it are used during runtime. + +## Fields + +**`id`** +Internal ID of the chain. Lowercase letters only. Should be never changed. +Ex.: `'bitcoin'`. + +**`name`** +More readable name, can include lower/uppercase, space. +Ex.: `'Bitcoin'`. + +**`displayName`** +Optional, if present, overrides **name** for places where it is visible to the user. +Ex.: `'BNB Beacon Chain'`. + +**`coinId`** +Internal numerical ID for the chain. In most cases it is the ID used in BIP-44 address derivation. +See quasi-standard repository here: https://github.com/satoshilabs/slips/blob/master/slip-0044.md +Ex.: `0` for Bitcoin, `60` for Ethereum. + +Some typical special cases: + +- Multiple chains/coins with the same ID: adding value `10000000` to distinguish. Possible multiple times. +Ex.: `10000118` for Osmosis, `118` for Cosmos; `20000714` for BNB Smart Chain. +- Ethereum-clone chains with no own BIP-44 ID: use the `10000000 + chainID` as coinID. + +See also: `slip44` and `chainId`. + +**`slip44`** +Optionally, SLIP-44 (BIP-44) coin ID can be specified here, in case it differs from `coinId`. Most of the case the two are the same, so this can be omitted. +Ex.: `60` for Optimism (coinID is `10000070`). + +**`symbol`** +Symbol of the native coin. Typically a short, upper-case-only string. +Ex.: `BTC`, `ETH`. + +**`decimals`** +Number of decimals in coin amounts. Amounts are typically expressed as (large) integer values, with all decimal values, like `100000` for `0.00100000` BTC (decimals=8). +Ex.: `8` for Bitcoin, `18` for Ethereum. + +**`blockchain`** +Some chains are very similar and share the implementation code. +This flag is used to direct logic to the right implementation. +Ex. `'Cosmos'` for Oasis, as Oasis is handled by Cosmos implementation. + +**`derivation`** +Defines properties for address derivation, most importantly derivation paths. + +Typically only one derivation is supported per chain, in this case the definition looks like: + +``` + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], +``` + +It contains the derivation path used to derive private key (and address) from the wallet mnemonic (HD wallet). +Derivation path is usually well known for a chain implementation, and different wallet implementations use the same path (so cross-import is possible). +Note that the second number, the BIP-44 ID, usually matches the coinId. + +Some blockchains may support additional alternative derivations. These have: + +- a name +- a alternative derivation path (optional) + +Derivation may differ in the derivation path, or by address generation method (based on the derivation name). +The first derivation is considered the default. + +Examples: +Bitcoin uses Segwit address by default, but also supports earlier P2PKH addresses: + +``` + "derivation": [ + { + "name": "segwit", + "path": "m/84'/0'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" + }, + { + "name": "legacy", + "path": "m/44'/0'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], +``` + +Solana supports two derivations, which differ in derivation path: + +``` + "derivation": [ + { + "path": "m/44'/501'/0'" + }, + { + "name": "solana", + "path": "m/44'/501'/0'/0'" + } + ], +``` + +**`xpub` and `xprv`** +Defines the XPub and XPriv format used, Bitcoin-style. Defined inside the derivation section (as they may differ per derivation). + +**`curve`** +Defines the elliptic curve used in private-public key generation and signing. +Ex.: `'secp256k1'` for Bitcoin and Ethereum, `'ed25519'` for Polkadot. + +**`publicKeyType`** +The type of public key used. +Ex.: `'secp256k1'` for Bitcoin, `'secp256k1Extended'` for Ethereum. + +**`staticPrefix`** +Optional byte prefix, used in some Bitcoin-like chains. +Ex.: `7` for Decred. + +**`p2pkhPrefix` and `p2shPrefix`** +Defines the prefix byte used in P2PKH and P2SH addresses, Bitcoin style. +Ex. `0` and `5` for Bitcoin. + +**`hrp`** +Human Readable Prefix used to prefix an address, used to indicate type of address, to minimalize risk of accidental address mix-up across chains. +Ex. `'bc'` for Bitcoin, `'cosmos'` for Cosmos. + +**`chainId`** +Chain identifier, used by forks, e.g. in case of Ethereum (a decimal number), or Cosomos (a string ID). +Chain identifier, in case of Ethereum it's a constant decimal number; +for Cosmos, it's a dynamic string network id (usually changes with network upgrades). + +Please note the chain id might not be always latest in registry. In transaction building current value has to be supplied each time. + +Ex.: `'1'` for Ethereum, `'61'` for Ethereum Classic, `'osmosis-1'`for Osmosis. + +**`publicKeyHasher`** +Hash method used in XPub derivation. +Default is `sha256ripemd`. +Ex.: `'sha256ripemd'` for Bitcoin, `'blake256ripemd'` for Decred. + +**`base58Hasher`** +Hash method used in extended private and public key derivation, for checksumming within Base58 addresses. +Default is `sha256d`. +Ex.: `'sha256d'` for Bitcoin, `'blake256d'` for Decred. + +**`addressHasher`** +Hash method used in the publicKey -> address generation. +Only some chain implementation use this setting, in most implementation this is fixed (and value here is only informative). +Default is `sha256ripemd`. +Ex.: missing ('sha256ripemd') for Bitcoin, `'keccak256'` for Ethereum, `'sha256ripemd'` for Cosmos, `'keccak256'` for Native Evmos, despite being a Cosmos fork. + +**`explorer`** +Explorer web service for this chain. Sub-fields are used so that full URLs can be built for any address or transactions. +Note that the sample values should include existing IDs, so that the resulting full URL is valid. + +Example: + +``` + "explorer": { + "url": "https://blockchair.com", + "txPath": "/bitcoin/transaction/", + "accountPath": "/bitcoin/address/", + "sampleTx": "0607f62530b68cfcc91c57a1702841dd399a899d0eecda8e31ecca3f52f01df2", + "sampleAccount": "17A16QmavnUfCW11DAApiJxp7ARnxN5pGX" + }, +``` + +This results in the full URL for the sample address: +https://blockchair.com/bitcoin/address/17A16QmavnUfCW11DAApiJxp7ARnxN5pGX +which is a working URL. + +Beware of the starting-ending slashes used. + +**`info`** +Section with project info: + +**`info/url`** +Main project website. + +**`info/source`** +Link to the default implementation of the node or RPC gateway that can be used by a wallet. + +**`info/rpc`** +Optional URL to an available public RPC service. + +**`info/documentation`** +Main porject documentation site/subsite. + +**`deprecated`** +If set to `true`, the project is considered deprecated: its info is kept here, but it will not be supported. +Ex. `'true'` for Kin. diff --git a/docs/registry.md b/docs/registry.md index e87f8d2dc3d..5d89f556a91 100644 --- a/docs/registry.md +++ b/docs/registry.md @@ -16,14 +16,14 @@ This list is generated from [./registry.json](../registry.json) | 60 | Ethereum | ETH | | | | 61 | Ethereum Classic | ETC | | | | 74 | ICON | ICX | | | -| 118 | Cosmos | ATOM | | | +| 118 | Cosmos Hub | ATOM | | | | 133 | Zcash | ZEC | | | -| 136 | Zcoin | FIRO | | | +| 136 | Firo | FIRO | | | | 144 | XRP | XRP | | | | 145 | Bitcoin Cash | BCH | | | | 148 | Stellar | XLM | | | | 156 | Bitcoin Gold | BTG | | | -| 165 | Nano | NANO | | | +| 165 | Nano | XNO | | | | 175 | Ravencoin | RVN | | | | 178 | POA Network | POA | | | | 194 | EOS | EOS | | | @@ -32,10 +32,12 @@ This list is generated from [./registry.json](../registry.json) | 242 | Nimiq | NIM | | | | 283 | Algorand | ALGO | | | | 304 | IoTeX | IOTX | | | +| 309 | Nervos | CKB | | | | 313 | Zilliqa | ZIL | | | -| 330 | Terra | LUNA | | | +| 330 | Terra Classic | LUNC | | | | 354 | Polkadot | DOT | | | -| 394 | CryptoOrg | CRO | | | +| 394 | Crypto.org | CRO | | | +| 396 | Everscale | EVER | | | | 397 | NEAR | NEAR | | | | 425 | Aion | AION | | | | 434 | Kusama | KSM | | | @@ -48,13 +50,16 @@ This list is generated from [./registry.json](../registry.json) | 500 | Theta | THETA | | | | 501 | Solana | SOL | | | | 508 | Elrond | eGLD | | | -| 714 | Binance | BNB | | | +| 637 | Aptos | APT | | | +| 714 | BNB Beacon Chain | BNB | | | | 818 | VeChain | VET | | | | 820 | Callisto | CLO | | | | 888 | NEO | NEO | | | | 889 | TomoChain | TOMO | | | +| 899 | eCash | XEC | | | | 931 | THORChain | RUNE | | | | 966 | Polygon | MATIC | | | +| 996 | OKX Chain | OKT | | | | 1001 | Thunder Token | TT | | | | 1023 | Harmony | ONE | | | | 1024 | Ontology | ONT | | | @@ -64,15 +69,31 @@ This list is generated from [./registry.json](../registry.json) | 2718 | Nebulas | NAS | | | | 6060 | GoChain | GO | | | | 8964 | NULS | NULS | | | -| 19167 | Zelcash | FLUX | | | +| 18000 | Meter | MTR | | | +| 19167 | Flux | FLUX | | | | 52752 | Celo | CELO | | | | 5718350 | Wanchain | WAN | | | | 5741564 | Waves | WAVES | | | -| 10000070 | Optimism | OETH | | | -| 10000100 | xDai | xDAI | | | +| 10000025 | Cronos Chain | CRO | | | +| 10000070 | Optimistic Ethereum | ETH | | | +| 10000100 | Gnosis Chain | xDAI | | | +| 10000118 | Osmosis | OSMO | | | +| 10000145 | Smart Bitcoin Cash | BCH | | | | 10000250 | Fantom | FTM | | | -| 10000553 | ECO Chain | HT | | | +| 10000280 | zkSync v2 | ETH | | | +| 10000288 | Boba | BOBAETH | | | +| 10000321 | KuCoin Community Chain | KCS | | | +| 10000330 | Terra | LUNA | | | +| 10000553 | Huobi ECO Chain | HT | | | +| 10001088 | Metis | METIS | | | +| 10001284 | Moonbeam | GLMR | | | +| 10001285 | Moonriver | MOVR | | | | 10002020 | Ronin | RON | | | +| 10002222 | KavaEvm | KAVA | | | +| 10008217 | Klaytn | KLAY | | | | 10009000 | Avalanche C-Chain | AVAX | | | -| 10042221 | Arbitrum | ARETH | | | -| 20000714 | Smart Chain | BNB | | | +| 10009001 | Evmos | EVMOS | | | +| 10042221 | Arbitrum | ETH | | | +| 20000714 | BNB Smart Chain | BNB | | | +| 20009001 | Native Evmos | EVMOS | | | +| 1323161554 | Aurora | ETH | | | diff --git a/include/TrustWalletCore/TWAES.h b/include/TrustWalletCore/TWAES.h index 510d2f32291..b44041f36f2 100644 --- a/include/TrustWalletCore/TWAES.h +++ b/include/TrustWalletCore/TWAES.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -12,40 +12,47 @@ TW_EXTERN_C_BEGIN +/// AES encryption/decryption methods. TW_EXPORT_STRUCT struct TWAES { uint8_t unused; // C doesn't allow zero-sized struct }; -/// Encrypts a block of data using AES in Cipher Block Chaining (CBC) mode. +/// Encrypts a block of Data using AES in Cipher Block Chaining (CBC) mode. /// -/// \param key encryption key, must be 16, 24, or 32 bytes long. -/// \param data data to encrypt. +/// \param key encryption key Data, must be 16, 24, or 32 bytes long. +/// \param data Data to encrypt. /// \param iv initialization vector. +/// \param mode padding mode. +/// \return encrypted Data. TW_EXPORT_STATIC_METHOD TWData *_Nullable TWAESEncryptCBC(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv, enum TWAESPaddingMode mode); /// Decrypts a block of data using AES in Cipher Block Chaining (CBC) mode. /// -/// \param key decryption key, must be 16, 24, or 32 bytes long. -/// \param data data to decrypt. -/// \param iv initialization vector. +/// \param key decryption key Data, must be 16, 24, or 32 bytes long. +/// \param data Data to decrypt. +/// \param iv initialization vector Data. +/// \param mode padding mode. +/// \return decrypted Data. TW_EXPORT_STATIC_METHOD TWData *_Nullable TWAESDecryptCBC(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv, enum TWAESPaddingMode mode); /// Encrypts a block of data using AES in Counter (CTR) mode. /// -/// \param key encryption key, must be 16, 24, or 32 bytes long. -/// \param data data to encrypt. -/// \param iv initialization vector. +/// \param key encryption key Data, must be 16, 24, or 32 bytes long. +/// \param data Data to encrypt. +/// \param iv initialization vector Data. +/// \return encrypted Data. TW_EXPORT_STATIC_METHOD TWData *_Nullable TWAESEncryptCTR(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv); /// Decrypts a block of data using AES in Counter (CTR) mode. /// -/// \param key decryption key, must be 16, 24, or 32 bytes long. -/// \param data data to decrypt. -/// \param iv initialization vector. +/// \param key decryption key Data, must be 16, 24, or 32 bytes long. +/// \param data Data to decrypt. +/// \param iv initialization vector Data. +/// \return decrypted Data. TW_EXPORT_STATIC_METHOD TWData *_Nullable TWAESDecryptCTR(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv); diff --git a/include/TrustWalletCore/TWAESPaddingMode.h b/include/TrustWalletCore/TWAESPaddingMode.h index 9e4713d0ed6..5cfe2103172 100644 --- a/include/TrustWalletCore/TWAESPaddingMode.h +++ b/include/TrustWalletCore/TWAESPaddingMode.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -10,6 +10,7 @@ TW_EXTERN_C_BEGIN +/// Padding mode used in AES encryption. TW_EXPORT_ENUM(uint32_t) enum TWAESPaddingMode { TWAESPaddingModeZero = 0, // padding value is zero diff --git a/include/TrustWalletCore/TWAccount.h b/include/TrustWalletCore/TWAccount.h index c1cb6ef04a8..c5d6f5e33ce 100644 --- a/include/TrustWalletCore/TWAccount.h +++ b/include/TrustWalletCore/TWAccount.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -7,31 +7,72 @@ #pragma once #include "TWBase.h" -#include "TWString.h" #include "TWCoinType.h" +#include "TWDerivation.h" +#include "TWString.h" TW_EXTERN_C_BEGIN -/// Account for a particular coin within a wallet. +/// Represents an Account in C++ with address, coin type and public key info, an item within a keystore. TW_EXPORT_CLASS struct TWAccount; +/// Creates a new Account with an address, a coin type, derivation enum, derivationPath, publicKey, +/// and extendedPublicKey. Must be deleted with TWAccountDelete after use. +/// +/// \param address The address of the Account. +/// \param coin The coin type of the Account. +/// \param derivation The derivation of the Account. +/// \param derivationPath The derivation path of the Account. +/// \param publicKey hex encoded public key. +/// \param extendedPublicKey Base58 encoded extended public key. +/// \return A new Account. TW_EXPORT_STATIC_METHOD -struct TWAccount *_Nonnull TWAccountCreate(TWString *_Nonnull address, enum TWCoinType coin, TWString *_Nonnull derivationPath, TWString *_Nonnull extendedPublicKey); - +struct TWAccount* _Nonnull TWAccountCreate(TWString* _Nonnull address, enum TWCoinType coin, + enum TWDerivation derivation, + TWString* _Nonnull derivationPath, + TWString* _Nonnull publicKey, + TWString* _Nonnull extendedPublicKey); +/// Deletes an account. +/// +/// \param account Account to delete. TW_EXPORT_METHOD void TWAccountDelete(struct TWAccount *_Nonnull account); +/// Returns the address of an account. +/// +/// \param account Account to get the address of. TW_EXPORT_PROPERTY TWString *_Nonnull TWAccountAddress(struct TWAccount *_Nonnull account); +/// Returns the derivation enum of an account. +/// +/// \param account Account to get the derivation enum of. +TW_EXPORT_PROPERTY +enum TWDerivation TWAccountDerivation(struct TWAccount *_Nonnull account); + +/// Returns derivationPath of an account. +/// +/// \param account Account to get the derivation path of. TW_EXPORT_PROPERTY TWString *_Nonnull TWAccountDerivationPath(struct TWAccount *_Nonnull account); +/// Returns hex encoded publicKey of an account. +/// +/// \param account Account to get the public key of. +TW_EXPORT_PROPERTY +TWString* _Nonnull TWAccountPublicKey(struct TWAccount* _Nonnull account); + +/// Returns Base58 encoded extendedPublicKey of an account. +/// +/// \param account Account to get the extended public key of. TW_EXPORT_PROPERTY -TWString *_Nonnull TWAccountExtendedPublicKey(struct TWAccount *_Nonnull account); +TWString* _Nonnull TWAccountExtendedPublicKey(struct TWAccount* _Nonnull account); +/// Return CoinType enum of an account. +/// +/// \param account Account to get the coin type of. TW_EXPORT_PROPERTY -enum TWCoinType TWAccountCoin(struct TWAccount *_Nonnull account); +enum TWCoinType TWAccountCoin(struct TWAccount* _Nonnull account); TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWAnyAddress.h b/include/TrustWalletCore/TWAnyAddress.h index 3771b80e762..449d518ba2c 100644 --- a/include/TrustWalletCore/TWAnyAddress.h +++ b/include/TrustWalletCore/TWAnyAddress.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -15,38 +15,91 @@ TW_EXTERN_C_BEGIN struct TWPublicKey; -/// Represents Any blockchain address. +/// Represents an address in C++ for almost any blockchain. TW_EXPORT_CLASS struct TWAnyAddress; /// Compares two addresses for equality. +/// +/// \param lhs The first address to compare. +/// \param rhs The second address to compare. +/// \return bool indicating the addresses are equal. TW_EXPORT_STATIC_METHOD bool TWAnyAddressEqual(struct TWAnyAddress* _Nonnull lhs, struct TWAnyAddress* _Nonnull rhs); /// Determines if the string is a valid Any address. +/// +/// \param string address to validate. +/// \param coin coin type of the address. +/// \return bool indicating if the address is valid. TW_EXPORT_STATIC_METHOD bool TWAnyAddressIsValid(TWString* _Nonnull string, enum TWCoinType coin); -/// Creates an address from a string representaion. +/// Determines if the string is a valid Any address with the given hrp. +/// +/// \param string address to validate. +/// \param coin coin type of the address. +/// \param hrp explicit given hrp of the given address. +/// \return bool indicating if the address is valid. +TW_EXPORT_STATIC_METHOD +bool TWAnyAddressIsValidBech32(TWString* _Nonnull string, enum TWCoinType coin, TWString* _Nonnull hrp); + +/// Creates an address from a string representation and a coin type. Must be deleted with TWAnyAddressDelete after use. +/// +/// \param string address to create. +/// \param coin coin type of the address. +/// \return TWAnyAddress pointer or nullptr if address and coin are invalid. TW_EXPORT_STATIC_METHOD struct TWAnyAddress* _Nullable TWAnyAddressCreateWithString(TWString* _Nonnull string, enum TWCoinType coin); +/// Creates an bech32 address from a string representation, a coin type and the given hrp. Must be deleted with TWAnyAddressDelete after use. +/// +/// \param string address to create. +/// \param coin coin type of the address. +/// \param hrp hrp of the address. +/// \return TWAnyAddress pointer or nullptr if address and coin are invalid. +TW_EXPORT_STATIC_METHOD +struct TWAnyAddress* _Nullable TWAnyAddressCreateBech32(TWString* _Nonnull string, enum TWCoinType coin, TWString* _Nonnull hrp); + + /// Creates an address from a public key. +/// +/// \param publicKey derivates the address from the public key. +/// \param coin coin type of the address. +/// \return TWAnyAddress pointer or nullptr if public key is invalid. TW_EXPORT_STATIC_METHOD struct TWAnyAddress* _Nonnull TWAnyAddressCreateWithPublicKey(struct TWPublicKey* _Nonnull publicKey, enum TWCoinType coin); +/// Creates an bech32 address from a public key and a given hrp. +/// +/// \param publicKey derivates the address from the public key. +/// \param coin coin type of the address. +/// \param hrp hrp of the address. +/// \return TWAnyAddress pointer or nullptr if public key is invalid. +TW_EXPORT_STATIC_METHOD +struct TWAnyAddress* _Nonnull TWAnyAddressCreateBech32WithPublicKey(struct TWPublicKey* _Nonnull publicKey, enum TWCoinType coin, TWString* _Nonnull hrp); + +/// Deletes an address. +/// +/// \param address address to delete. TW_EXPORT_METHOD void TWAnyAddressDelete(struct TWAnyAddress* _Nonnull address); /// Returns the address string representation. +/// +/// \param address address to get the string representation of. TW_EXPORT_PROPERTY TWString* _Nonnull TWAnyAddressDescription(struct TWAnyAddress* _Nonnull address); /// Returns coin type of address. +/// +/// \param address address to get the coin type of. TW_EXPORT_PROPERTY enum TWCoinType TWAnyAddressCoin(struct TWAnyAddress* _Nonnull address); /// Returns underlaying data (public key or key hash) +/// +/// \param address address to get the data of. TW_EXPORT_PROPERTY TWData* _Nonnull TWAnyAddressData(struct TWAnyAddress* _Nonnull address); diff --git a/include/TrustWalletCore/TWAnySigner.h b/include/TrustWalletCore/TWAnySigner.h index 7e4a460112f..90276f3dea8 100644 --- a/include/TrustWalletCore/TWAnySigner.h +++ b/include/TrustWalletCore/TWAnySigner.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -12,21 +12,38 @@ TW_EXTERN_C_BEGIN -/// Helper class to sign any transactions. +/// Represents a signer to sign transactions for any blockchain. struct TWAnySigner; -/// Signs a transaction. +/// Signs a transaction specified by the signing input and coin type. +/// +/// \param input The serialized data of a signing input (e.g. TW.Bitcoin.Proto.SigningInput). +/// \param coin The given coin type to sign the transaction for. +/// \return The serialized data of a `SigningOutput` proto object. (e.g. TW.Bitcoin.Proto.SigningOutput). TW_EXTERN extern TWData *_Nonnull TWAnySignerSign(TWData *_Nonnull input, enum TWCoinType coin); -/// Signs a json transaction with private key. +/// Signs a transaction specified by the JSON representation of signing input, coin type and a private key, returning the JSON representation of the signing output. +/// +/// \param json JSON representation of a signing input +/// \param key The private key to sign with. +/// \param coin The given coin type to sign the transaction for. +/// \return The JSON representation of a `SigningOutput` proto object. TW_EXTERN extern TWString *_Nonnull TWAnySignerSignJSON(TWString *_Nonnull json, TWData *_Nonnull key, enum TWCoinType coin); +/// Check if AnySigner supports signing JSON representation of signing input. +/// +/// \param coin The given coin type to sign the transaction for. +/// \return true if AnySigner supports signing JSON representation of signing input for a given coin. TW_EXTERN extern bool TWAnySignerSupportsJSON(enum TWCoinType coin); -/// Plan a transaction (for UTXO chains). +/// Plans a transaction (for UTXO chains only). +/// +/// \param input The serialized data of a signing input +/// \param coin The given coin type to plan the transaction for. +/// \return The serialized data of a `TransactionPlan` proto object. TW_EXTERN extern TWData *_Nonnull TWAnySignerPlan(TWData *_Nonnull input, enum TWCoinType coin); diff --git a/include/TrustWalletCore/TWBase.h b/include/TrustWalletCore/TWBase.h index 7ce85f50e6d..db2f782dd9a 100644 --- a/include/TrustWalletCore/TWBase.h +++ b/include/TrustWalletCore/TWBase.h @@ -28,6 +28,13 @@ #define TW_EXTERN extern #endif +// Marker for default visibility +#ifdef _MSC_VER + #define TW_VISIBILITY_DEFAULT +#else + #define TW_VISIBILITY_DEFAULT __attribute__((visibility("default"))) +#endif + // Marker for exported classes #define TW_EXPORT_CLASS @@ -89,3 +96,4 @@ #include #include #include + diff --git a/include/TrustWalletCore/TWBase32.h b/include/TrustWalletCore/TWBase32.h new file mode 100644 index 00000000000..e65c67e5890 --- /dev/null +++ b/include/TrustWalletCore/TWBase32.h @@ -0,0 +1,54 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "TWBase.h" +#include "TWData.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +/// Base32 encode / decode functions +TW_EXPORT_STRUCT +struct TWBase32; + +/// Decode a Base32 input with the given alphabet +/// +/// \param string Encoded base32 input to be decoded +/// \param alphabet Decode with the given alphabet, if nullptr ALPHABET_RFC4648 is used by default +/// \return The decoded data, can be null. +/// \note ALPHABET_RFC4648 doesn't support padding in the default alphabet +TW_EXPORT_STATIC_METHOD +TWData* _Nullable TWBase32DecodeWithAlphabet(TWString* _Nonnull string, TWString* _Nullable alphabet); + +/// Decode a Base32 input with the default alphabet (ALPHABET_RFC4648) +/// +/// \param string Encoded input to be decoded +/// \return The decoded data +/// \note Call TWBase32DecodeWithAlphabet with nullptr. +TW_EXPORT_STATIC_METHOD +TWData* _Nullable TWBase32Decode(TWString* _Nonnull string); + +/// Encode an input to Base32 with the given alphabet +/// +/// \param data Data to be encoded (raw bytes) +/// \param alphabet Encode with the given alphabet, if nullptr ALPHABET_RFC4648 is used by default +/// \return The encoded data +/// \note ALPHABET_RFC4648 doesn't support padding in the default alphabet +TW_EXPORT_STATIC_METHOD +TWString *_Nonnull TWBase32EncodeWithAlphabet(TWData *_Nonnull data, TWString* _Nullable alphabet); + +/// Encode an input to Base32 with the default alphabet (ALPHABET_RFC4648) +/// +/// \param data Data to be encoded (raw bytes) +/// \return The encoded data +/// \note Call TWBase32EncodeWithAlphabet with nullptr. +TW_EXPORT_STATIC_METHOD +TWString *_Nonnull TWBase32Encode(TWData *_Nonnull data); + +TW_EXTERN_C_END + diff --git a/include/TrustWalletCore/TWBase58.h b/include/TrustWalletCore/TWBase58.h index a37e14e06d3..6e5dbd30a48 100644 --- a/include/TrustWalletCore/TWBase58.h +++ b/include/TrustWalletCore/TWBase58.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -12,22 +12,35 @@ TW_EXTERN_C_BEGIN +/// Base58 encode / decode functions TW_EXPORT_STRUCT struct TWBase58; /// Encodes data as a Base58 string, including the checksum. +/// +/// \param data The data to encode. +/// \return the encoded Base58 string with checksum. TW_EXPORT_STATIC_METHOD TWString *_Nonnull TWBase58Encode(TWData *_Nonnull data); /// Encodes data as a Base58 string, not including the checksum. +/// +/// \param data The data to encode. +/// \return then encoded Base58 string without checksum. TW_EXPORT_STATIC_METHOD TWString *_Nonnull TWBase58EncodeNoCheck(TWData *_Nonnull data); -/// Decodes a Base58 string checking the checksum. +/// Decodes a Base58 string, checking the checksum. Returns null if the string is not a valid Base58 string. +/// +/// \param string The Base58 string to decode. +/// \return the decoded data, empty if the string is not a valid Base58 string with checksum. TW_EXPORT_STATIC_METHOD TWData *_Nullable TWBase58Decode(TWString *_Nonnull string); -/// Decodes a Base58 string with no checksum. +/// Decodes a Base58 string, w/o checking the checksum. Returns null if the string is not a valid Base58 string. +/// +/// \param string The Base58 string to decode. +/// \return the decoded data, empty if the string is not a valid Base58 string without checksum. TW_EXPORT_STATIC_METHOD TWData *_Nullable TWBase58DecodeNoCheck(TWString *_Nonnull string); diff --git a/include/TrustWalletCore/TWBase64.h b/include/TrustWalletCore/TWBase64.h new file mode 100644 index 00000000000..fa1c29173d1 --- /dev/null +++ b/include/TrustWalletCore/TWBase64.h @@ -0,0 +1,47 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "TWBase.h" +#include "TWData.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +/// Base64 encode / decode functions +TW_EXPORT_STRUCT +struct TWBase64; + +/// Decode a Base64 input with the default alphabet (RFC4648 with '+', '/') +/// +/// \param string Encoded input to be decoded +/// \return The decoded data, empty if decoding failed. +TW_EXPORT_STATIC_METHOD +TWData* _Nullable TWBase64Decode(TWString* _Nonnull string); + +/// Decode a Base64 input with the alphabet safe for URL-s and filenames (RFC4648 with '-', '_') +/// +/// \param string Encoded base64 input to be decoded +/// \return The decoded data, empty if decoding failed. +TW_EXPORT_STATIC_METHOD +TWData* _Nullable TWBase64DecodeUrl(TWString* _Nonnull string); + +/// Encode an input to Base64 with the default alphabet (RFC4648 with '+', '/') +/// +/// \param data Data to be encoded (raw bytes) +/// \return The encoded data +TW_EXPORT_STATIC_METHOD +TWString *_Nonnull TWBase64Encode(TWData *_Nonnull data); + +/// Encode an input to Base64 with the alphabet safe for URL-s and filenames (RFC4648 with '-', '_') +/// +/// \param data Data to be encoded (raw bytes) +/// \return The encoded data +TW_EXPORT_STATIC_METHOD +TWString *_Nonnull TWBase64EncodeUrl(TWData *_Nonnull data); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWBitcoinAddress.h b/include/TrustWalletCore/TWBitcoinAddress.h index b42bdba717f..77ae6bf1d5d 100644 --- a/include/TrustWalletCore/TWBitcoinAddress.h +++ b/include/TrustWalletCore/TWBitcoinAddress.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -14,46 +14,75 @@ TW_EXTERN_C_BEGIN struct TWPublicKey; -/// Represents a legacy Bitcoin address. +/// Represents a legacy Bitcoin address in C++. TW_EXPORT_CLASS struct TWBitcoinAddress; /// Compares two addresses for equality. +/// +/// \param lhs The first address to compare. +/// \param rhs The second address to compare. +/// \return bool indicating the addresses are equal. TW_EXPORT_STATIC_METHOD bool TWBitcoinAddressEqual(struct TWBitcoinAddress *_Nonnull lhs, struct TWBitcoinAddress *_Nonnull rhs); /// Determines if the data is a valid Bitcoin address. +/// +/// \param data data to validate. +/// \return bool indicating if the address data is valid. TW_EXPORT_STATIC_METHOD bool TWBitcoinAddressIsValid(TWData *_Nonnull data); /// Determines if the string is a valid Bitcoin address. +/// +/// \param string string to validate. +/// \return bool indicating if the address string is valid. TW_EXPORT_STATIC_METHOD bool TWBitcoinAddressIsValidString(TWString *_Nonnull string); -/// Initializes an address from a base58 sring representaion. +/// Initializes an address from a Base58 sring. Must be deleted with TWBitcoinAddressDelete after use. +/// +/// \param string Base58 string to initialize the address from. +/// \return TWBitcoinAddress pointer or nullptr if string is invalid. TW_EXPORT_STATIC_METHOD struct TWBitcoinAddress *_Nullable TWBitcoinAddressCreateWithString(TWString *_Nonnull string); /// Initializes an address from raw data. +/// +/// \param data Raw data to initialize the address from. Must be deleted with TWBitcoinAddressDelete after use. +/// \return TWBitcoinAddress pointer or nullptr if data is invalid. TW_EXPORT_STATIC_METHOD struct TWBitcoinAddress *_Nullable TWBitcoinAddressCreateWithData(TWData *_Nonnull data); /// Initializes an address from a public key and a prefix byte. +/// +/// \param publicKey Public key to initialize the address from. +/// \param prefix Prefix byte (p2pkh, p2sh, etc). +/// \return TWBitcoinAddress pointer or nullptr if public key is invalid. TW_EXPORT_STATIC_METHOD struct TWBitcoinAddress *_Nullable TWBitcoinAddressCreateWithPublicKey(struct TWPublicKey *_Nonnull publicKey, uint8_t prefix); +/// Deletes a legacy Bitcoin address. +/// +/// \param address Address to delete. TW_EXPORT_METHOD void TWBitcoinAddressDelete(struct TWBitcoinAddress *_Nonnull address); -/// Returns the address base58 string representation. +/// Returns the address in Base58 string representation. +/// +/// \param address Address to get the string representation of. TW_EXPORT_PROPERTY TWString *_Nonnull TWBitcoinAddressDescription(struct TWBitcoinAddress *_Nonnull address); /// Returns the address prefix. +/// +/// \param address Address to get the prefix of. TW_EXPORT_PROPERTY uint8_t TWBitcoinAddressPrefix(struct TWBitcoinAddress *_Nonnull address); -/// Returns the keyhash data. +/// Returns the key hash data. +/// +/// \param address Address to get the keyhash data of. TW_EXPORT_PROPERTY TWData *_Nonnull TWBitcoinAddressKeyhash(struct TWBitcoinAddress *_Nonnull address); diff --git a/include/TrustWalletCore/TWBitcoinMessageSigner.h b/include/TrustWalletCore/TWBitcoinMessageSigner.h new file mode 100644 index 00000000000..d0fcd2e17bd --- /dev/null +++ b/include/TrustWalletCore/TWBitcoinMessageSigner.h @@ -0,0 +1,43 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "TWBase.h" +#include "TWData.h" +#include "TWString.h" +#include "TWPrivateKey.h" + +TW_EXTERN_C_BEGIN + +/// Bitcoin message signing and verification. +/// +/// Bitcoin Core and some other wallets support a message signing & verification format, to create a proof (a signature) +/// that someone has access to the private keys of a specific address. +/// This feature currently works on old legacy addresses only. +TW_EXPORT_CLASS +struct TWBitcoinMessageSigner; + +/// Sign a message. +/// +/// \param privateKey: the private key used for signing +/// \param address: the address that matches the privateKey, must be a legacy address (P2PKH) +/// \param message: A custom message which is input to the signing. +/// \note Address is derived assuming compressed public key format. +/// \returns the signature, Base64-encoded. On invalid input empty string is returned. Returned object needs to be deleteed after use. +TW_EXPORT_STATIC_METHOD +TWString* _Nonnull TWBitcoinMessageSignerSignMessage(const struct TWPrivateKey* _Nonnull privateKey, TWString* _Nonnull address, TWString* _Nonnull message); + +/// Verify signature for a message. +/// +/// \param address: address to use, only legacy is supported +/// \param message: the message signed (without prefix) +/// \param signature: in Base64-encoded form. +/// \returns false on any invalid input (does not throw). +TW_EXPORT_STATIC_METHOD +bool TWBitcoinMessageSignerVerifyMessage(TWString* _Nonnull address, TWString* _Nonnull message, TWString* _Nonnull signature); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWBitcoinScript.h b/include/TrustWalletCore/TWBitcoinScript.h index 701d6ad4243..c04611ab166 100644 --- a/include/TrustWalletCore/TWBitcoinScript.h +++ b/include/TrustWalletCore/TWBitcoinScript.h @@ -7,119 +7,205 @@ #pragma once #include "TWBase.h" +#include "TWBitcoinSigHashType.h" +#include "TWCoinType.h" #include "TWData.h" #include "TWPublicKey.h" -#include "TWCoinType.h" -#include "TWBitcoinSigHashType.h" TW_EXTERN_C_BEGIN +/// Bitcoin script manipulating functions TW_EXPORT_CLASS struct TWBitcoinScript; /// Creates an empty script. +/// +/// \return A pointer to the script TW_EXPORT_STATIC_METHOD -struct TWBitcoinScript *_Nonnull TWBitcoinScriptCreate(); +struct TWBitcoinScript* _Nonnull TWBitcoinScriptCreate(); /// Creates a script from a raw data representation. +/// +/// \param data The data buffer +/// \note Must be deleted with \TWBitcoinScriptDelete +/// \return A pointer to the script TW_EXPORT_STATIC_METHOD -struct TWBitcoinScript *_Nonnull TWBitcoinScriptCreateWithData(TWData *_Nonnull data); -struct TWBitcoinScript *_Nonnull TWBitcoinScriptCreateWithBytes(uint8_t *_Nonnull bytes, size_t size); +struct TWBitcoinScript* _Nonnull TWBitcoinScriptCreateWithData(TWData* _Nonnull data); -/// Creates a script by copying an existring script. +/// Creates a script from a raw bytes and size. +/// +/// \param bytes The buffer +/// \param size The size of the buffer +/// \note Must be deleted with \TWBitcoinScriptDelete +/// \return A pointer to the script +struct TWBitcoinScript* _Nonnull TWBitcoinScriptCreateWithBytes(uint8_t* _Nonnull bytes, size_t size); + +/// Creates a script by copying an existing script. +/// +/// \param script Non-null pointer to a script +/// \note Must be deleted with \TWBitcoinScriptDelete +/// \return A pointer to the script TW_EXPORT_STATIC_METHOD -struct TWBitcoinScript *_Nonnull TWBitcoinScriptCreateCopy(const struct TWBitcoinScript *_Nonnull script); +struct TWBitcoinScript* _Nonnull TWBitcoinScriptCreateCopy(const struct TWBitcoinScript* _Nonnull script); +/// Delete/Deallocate a given script. +/// +/// \param script Non-null pointer to a script TW_EXPORT_METHOD -void TWBitcoinScriptDelete(struct TWBitcoinScript *_Nonnull script); +void TWBitcoinScriptDelete(struct TWBitcoinScript* _Nonnull script); +/// Get size of a script +/// +/// \param script Non-null pointer to a script +/// \return size of the script TW_EXPORT_PROPERTY -size_t TWBitcoinScriptSize(const struct TWBitcoinScript *_Nonnull script); +size_t TWBitcoinScriptSize(const struct TWBitcoinScript* _Nonnull script); +/// Get data of a script +/// +/// \param script Non-null pointer to a script +/// \return data of the given script TW_EXPORT_PROPERTY -TWData *_Nonnull TWBitcoinScriptData(const struct TWBitcoinScript *_Nonnull script); +TWData* _Nonnull TWBitcoinScriptData(const struct TWBitcoinScript* _Nonnull script); +/// Return script hash of a script +/// +/// \param script Non-null pointer to a script +/// \return script hash of the given script TW_EXPORT_PROPERTY -TWData *_Nonnull TWBitcoinScriptScriptHash(const struct TWBitcoinScript *_Nonnull script); +TWData* _Nonnull TWBitcoinScriptScriptHash(const struct TWBitcoinScript* _Nonnull script); /// Determines whether this is a pay-to-script-hash (P2SH) script. +/// +/// \param script Non-null pointer to a script +/// \return true if this is a pay-to-script-hash (P2SH) script, false otherwise TW_EXPORT_PROPERTY -bool TWBitcoinScriptIsPayToScriptHash(const struct TWBitcoinScript *_Nonnull script); +bool TWBitcoinScriptIsPayToScriptHash(const struct TWBitcoinScript* _Nonnull script); /// Determines whether this is a pay-to-witness-script-hash (P2WSH) script. +/// +/// \param script Non-null pointer to a script +/// \return true if this is a pay-to-witness-script-hash (P2WSH) script, false otherwise TW_EXPORT_PROPERTY -bool TWBitcoinScriptIsPayToWitnessScriptHash(const struct TWBitcoinScript *_Nonnull script); +bool TWBitcoinScriptIsPayToWitnessScriptHash(const struct TWBitcoinScript* _Nonnull script); /// Determines whether this is a pay-to-witness-public-key-hash (P2WPKH) script. +/// +/// \param script Non-null pointer to a script +/// \return true if this is a pay-to-witness-public-key-hash (P2WPKH) script, false otherwise TW_EXPORT_PROPERTY -bool TWBitcoinScriptIsPayToWitnessPublicKeyHash(const struct TWBitcoinScript *_Nonnull script); +bool TWBitcoinScriptIsPayToWitnessPublicKeyHash(const struct TWBitcoinScript* _Nonnull script); -/// Determines whether this is a witness programm script. +/// Determines whether this is a witness program script. +/// +/// \param script Non-null pointer to a script +/// \return true if this is a witness program script, false otherwise TW_EXPORT_PROPERTY -bool TWBitcoinScriptIsWitnessProgram(const struct TWBitcoinScript *_Nonnull script); +bool TWBitcoinScriptIsWitnessProgram(const struct TWBitcoinScript* _Nonnull script); +/// Determines whether 2 scripts have the same content +/// +/// \param lhs Non-null pointer to the first script +/// \param rhs Non-null pointer to the second script +/// \return true if both script have the same content TW_EXPORT_STATIC_METHOD -bool TWBitcoinScriptEqual(const struct TWBitcoinScript *_Nonnull lhs, const struct TWBitcoinScript *_Nonnull rhs); +bool TWBitcoinScriptEqual(const struct TWBitcoinScript* _Nonnull lhs, const struct TWBitcoinScript* _Nonnull rhs); /// Matches the script to a pay-to-public-key (P2PK) script. /// -/// - Returns: the public key. +/// \param script Non-null pointer to a script +/// \return The public key. TW_EXPORT_METHOD -TWData *_Nullable TWBitcoinScriptMatchPayToPubkey(const struct TWBitcoinScript *_Nonnull script); +TWData* _Nullable TWBitcoinScriptMatchPayToPubkey(const struct TWBitcoinScript* _Nonnull script); /// Matches the script to a pay-to-public-key-hash (P2PKH). /// -/// - Returns: the key hash. +/// \param script Non-null pointer to a script +/// \return the key hash. TW_EXPORT_METHOD -TWData *_Nullable TWBitcoinScriptMatchPayToPubkeyHash(const struct TWBitcoinScript *_Nonnull script); +TWData* _Nullable TWBitcoinScriptMatchPayToPubkeyHash(const struct TWBitcoinScript* _Nonnull script); /// Matches the script to a pay-to-script-hash (P2SH). /// -/// - Returns: the script hash. +/// \param script Non-null pointer to a script +/// \return the script hash. TW_EXPORT_METHOD -TWData *_Nullable TWBitcoinScriptMatchPayToScriptHash(const struct TWBitcoinScript *_Nonnull script); +TWData* _Nullable TWBitcoinScriptMatchPayToScriptHash(const struct TWBitcoinScript* _Nonnull script); /// Matches the script to a pay-to-witness-public-key-hash (P2WPKH). /// -/// - Returns: the key hash. +/// \param script Non-null pointer to a script +/// \return the key hash. TW_EXPORT_METHOD -TWData *_Nullable TWBitcoinScriptMatchPayToWitnessPublicKeyHash(const struct TWBitcoinScript *_Nonnull script); +TWData* _Nullable TWBitcoinScriptMatchPayToWitnessPublicKeyHash(const struct TWBitcoinScript* _Nonnull script); /// Matches the script to a pay-to-witness-script-hash (P2WSH). /// -/// - Returns: the script hash, a SHA256 of the witness script. +/// \param script Non-null pointer to a script +/// \return the script hash, a SHA256 of the witness script.. TW_EXPORT_METHOD -TWData *_Nullable TWBitcoinScriptMatchPayToWitnessScriptHash(const struct TWBitcoinScript *_Nonnull script); +TWData* _Nullable TWBitcoinScriptMatchPayToWitnessScriptHash(const struct TWBitcoinScript* _Nonnull script); /// Encodes the script. +/// +/// \param script Non-null pointer to a script +/// \return The encoded script TW_EXPORT_METHOD -TWData *_Nonnull TWBitcoinScriptEncode(const struct TWBitcoinScript *_Nonnull script); +TWData* _Nonnull TWBitcoinScriptEncode(const struct TWBitcoinScript* _Nonnull script); /// Builds a standard 'pay to public key' script. +/// +/// \param pubkey Non-null pointer to a pubkey +/// \note Must be deleted with \TWBitcoinScriptDelete +/// \return A pointer to the built script TW_EXPORT_STATIC_METHOD -struct TWBitcoinScript *_Nonnull TWBitcoinScriptBuildPayToPublicKey(TWData *_Nonnull pubkey); +struct TWBitcoinScript* _Nonnull TWBitcoinScriptBuildPayToPublicKey(TWData* _Nonnull pubkey); /// Builds a standard 'pay to public key hash' script. +/// +/// \param hash Non-null pointer to a PublicKey hash +/// \note Must be deleted with \TWBitcoinScriptDelete +/// \return A pointer to the built script TW_EXPORT_STATIC_METHOD -struct TWBitcoinScript *_Nonnull TWBitcoinScriptBuildPayToPublicKeyHash(TWData *_Nonnull hash); +struct TWBitcoinScript* _Nonnull TWBitcoinScriptBuildPayToPublicKeyHash(TWData* _Nonnull hash); /// Builds a standard 'pay to script hash' script. +/// +/// \param scriptHash Non-null pointer to a script hash +/// \note Must be deleted with \TWBitcoinScriptDelete +/// \return A pointer to the built script TW_EXPORT_STATIC_METHOD -struct TWBitcoinScript *_Nonnull TWBitcoinScriptBuildPayToScriptHash(TWData *_Nonnull scriptHash); +struct TWBitcoinScript* _Nonnull TWBitcoinScriptBuildPayToScriptHash(TWData* _Nonnull scriptHash); -/// Builds a pay-to-witness-public-key-hash (P2WPKH) script. +/// Builds a pay-to-witness-public-key-hash (P2WPKH) script.. +/// +/// \param hash Non-null pointer to a witness public key hash +/// \note Must be deleted with \TWBitcoinScriptDelete +/// \return A pointer to the built script TW_EXPORT_STATIC_METHOD -struct TWBitcoinScript *_Nonnull TWBitcoinScriptBuildPayToWitnessPubkeyHash(TWData *_Nonnull hash); +struct TWBitcoinScript* _Nonnull TWBitcoinScriptBuildPayToWitnessPubkeyHash(TWData* _Nonnull hash); /// Builds a pay-to-witness-script-hash (P2WSH) script. +/// +/// \param scriptHash Non-null pointer to a script hash +/// \note Must be deleted with \TWBitcoinScriptDelete +/// \return A pointer to the built script TW_EXPORT_STATIC_METHOD -struct TWBitcoinScript *_Nonnull TWBitcoinScriptBuildPayToWitnessScriptHash(TWData *_Nonnull scriptHash); +struct TWBitcoinScript* _Nonnull TWBitcoinScriptBuildPayToWitnessScriptHash(TWData* _Nonnull scriptHash); -/// Builds a appropriate lock script for the given address. +/// Builds a appropriate lock script for the given address.. +/// +/// \param address Non-null pointer to an address +/// \param coin coin type +/// \note Must be deleted with \TWBitcoinScriptDelete +/// \return A pointer to the built script TW_EXPORT_STATIC_METHOD -struct TWBitcoinScript *_Nonnull TWBitcoinScriptLockScriptForAddress(TWString *_Nonnull address, enum TWCoinType coin); +struct TWBitcoinScript* _Nonnull TWBitcoinScriptLockScriptForAddress(TWString* _Nonnull address, enum TWCoinType coin); -// Return the default HashType for the given coin, such as TWBitcoinSigHashTypeAll. +/// Return the default HashType for the given coin, such as TWBitcoinSigHashTypeAll. +/// +/// \param coinType coin type +/// \return default HashType for the given coin TW_EXPORT_STATIC_METHOD uint32_t TWBitcoinScriptHashTypeForCoin(enum TWCoinType coinType); diff --git a/include/TrustWalletCore/TWBitcoinSigHashType.h b/include/TrustWalletCore/TWBitcoinSigHashType.h index 33fba1992d2..b2682d67074 100644 --- a/include/TrustWalletCore/TWBitcoinSigHashType.h +++ b/include/TrustWalletCore/TWBitcoinSigHashType.h @@ -10,6 +10,7 @@ TW_EXTERN_C_BEGIN +/// Bitcoin SIGHASH type. TW_EXPORT_ENUM(uint32_t) enum TWBitcoinSigHashType { TWBitcoinSigHashTypeAll = 0x01, @@ -20,9 +21,17 @@ enum TWBitcoinSigHashType { TWBitcoinSigHashTypeAnyoneCanPay = 0x80 }; +/// Determines if the given sig hash is single +/// +/// \param type sig hash type +/// \return true if the sigh hash type is single, false otherwise TW_EXPORT_METHOD bool TWBitcoinSigHashTypeIsSingle(enum TWBitcoinSigHashType type); +/// Determines if the given sig hash is none +/// +/// \param type sig hash type +/// \return true if the sigh hash type is none, false otherwise TW_EXPORT_METHOD bool TWBitcoinSigHashTypeIsNone(enum TWBitcoinSigHashType type); diff --git a/include/TrustWalletCore/TWBlockchain.h b/include/TrustWalletCore/TWBlockchain.h index c0e8db65bfd..80fe59660ce 100644 --- a/include/TrustWalletCore/TWBlockchain.h +++ b/include/TrustWalletCore/TWBlockchain.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -10,6 +10,7 @@ TW_EXTERN_C_BEGIN +/// Blockchain enum type TW_EXPORT_ENUM(uint32_t) enum TWBlockchain { TWBlockchainBitcoin = 0, @@ -45,6 +46,15 @@ enum TWBlockchain { TWBlockchainFilecoin = 32, TWBlockchainElrondNetwork = 33, TWBlockchainOasisNetwork = 34, + TWBlockchainDecred = 35, // Bitcoin + TWBlockchainZcash = 36, // Bitcoin + TWBlockchainGroestlcoin = 37, // Bitcoin + TWBlockchainThorchain = 38, // Cosmos + TWBlockchainRonin = 39, // Ethereum + TWBlockchainKusama = 40, // Polkadot + TWBlockchainNervos = 41, + TWBlockchainEverscale = 42, + TWBlockchainAptos = 43, // Aptos }; TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWCardano.h b/include/TrustWalletCore/TWCardano.h new file mode 100644 index 00000000000..0def3db33b6 --- /dev/null +++ b/include/TrustWalletCore/TWCardano.h @@ -0,0 +1,34 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "TWBase.h" +#include "TWData.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +/// Cardano helper functions +TW_EXPORT_STRUCT +struct TWCardano; + +/// Calculates the minimum ADA amount needed for a UTXO. +/// +/// \see reference https://docs.cardano.org/native-tokens/minimum-ada-value-requirement +/// \param tokenBundle serialized data of TW.Cardano.Proto.TokenBundle. +/// \return the minimum ADA amount. +TW_EXPORT_STATIC_METHOD +uint64_t TWCardanoMinAdaAmount(TWData *_Nonnull tokenBundle) TW_VISIBILITY_DEFAULT; + +/// Return the staking address associated to (contained in) this address. Must be a Base address. +/// Empty string is returned on error. Result must be freed. +/// \param baseAddress A valid base address, as string. +/// \return the associated staking (reward) address, as string, or empty string on error. +TW_EXPORT_STATIC_METHOD +TWString *_Nonnull TWCardanoGetStakingAddress(TWString *_Nonnull baseAddress) TW_VISIBILITY_DEFAULT; + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWCoinType.h b/include/TrustWalletCore/TWCoinType.h index 49e7c8e7272..52e5f2d50f5 100644 --- a/include/TrustWalletCore/TWCoinType.h +++ b/include/TrustWalletCore/TWCoinType.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -14,12 +14,13 @@ #include "TWPrivateKey.h" #include "TWPurpose.h" #include "TWString.h" +#include "TWDerivation.h" TW_EXTERN_C_BEGIN /// Coin type for Level 2 of BIP44. /// -/// - SeeAlso: https://github.com/satoshilabs/slips/blob/master/slip-0044.md +/// \see https://github.com/satoshilabs/slips/blob/master/slip-0044.md TW_EXPORT_ENUM(uint32_t) enum TWCoinType { TWCoinTypeAeternity = 457, @@ -68,12 +69,13 @@ enum TWCoinType { TWCoinTypeViacoin = 14, TWCoinTypeWanchain = 5718350, TWCoinTypeZcash = 133, - TWCoinTypeZcoin = 136, + TWCoinTypeFiro = 136, TWCoinTypeZilliqa = 313, TWCoinTypeZelcash = 19167, TWCoinTypeRavencoin = 175, TWCoinTypeWaves = 5741564, - TWCoinTypeTerra = 330, + TWCoinTypeTerra = 330, // see also TerraV2 + TWCoinTypeTerraV2 = 10000330, // see also Terra TWCoinTypeHarmony = 1023, TWCoinTypeAlgorand = 283, TWCoinTypeKusama = 434, @@ -88,6 +90,7 @@ enum TWCoinType { TWCoinTypeTHORChain = 931, TWCoinTypeBluzelle = 483, TWCoinTypeOptimism = 10000070, + TWCoinTypeZksync = 10000280, TWCoinTypeArbitrum = 10042221, TWCoinTypeECOChain = 10000553, TWCoinTypeAvalancheCChain = 10009000, @@ -96,64 +99,151 @@ enum TWCoinType { TWCoinTypeCryptoOrg = 394, TWCoinTypeCelo = 52752, TWCoinTypeRonin = 10002020, + TWCoinTypeOsmosis = 10000118, + TWCoinTypeECash = 899, + TWCoinTypeCronosChain = 10000025, + TWCoinTypeSmartBitcoinCash = 10000145, + TWCoinTypeKuCoinCommunityChain = 10000321, + TWCoinTypeBoba = 10000288, + TWCoinTypeMetis = 1001088, + TWCoinTypeAurora = 1323161554, + TWCoinTypeEvmos = 10009001, + TWCoinTypeNativeEvmos = 20009001, + TWCoinTypeMoonriver = 10001285, + TWCoinTypeMoonbeam = 10001284, + TWCoinTypeKavaEvm = 10002222, + TWCoinTypeKlaytn = 10008217, + TWCoinTypeMeter = 18000, + TWCoinTypeOKXChain = 996, + TWCoinTypeNervos = 309, + TWCoinTypeEverscale = 396, + TWCoinTypeAptos = 637, }; /// Returns the blockchain for a coin type. +/// +/// \param coin A coin type +/// \return blockchain associated to the given coin type TW_EXPORT_PROPERTY enum TWBlockchain TWCoinTypeBlockchain(enum TWCoinType coin); /// Returns the purpose for a coin type. +/// +/// \param coin A coin type +/// \return purpose associated to the given coin type TW_EXPORT_PROPERTY enum TWPurpose TWCoinTypePurpose(enum TWCoinType coin); /// Returns the curve that should be used for a coin type. +/// +/// \param coin A coin type +/// \return curve that should be used for the given coin type TW_EXPORT_PROPERTY enum TWCurve TWCoinTypeCurve(enum TWCoinType coin); /// Returns the xpub HD version that should be used for a coin type. +/// +/// \param coin A coin type +/// \return xpub HD version that should be used for the given coin type TW_EXPORT_PROPERTY enum TWHDVersion TWCoinTypeXpubVersion(enum TWCoinType coin); /// Returns the xprv HD version that should be used for a coin type. +/// +/// \param coin A coin type +/// \return the xprv HD version that should be used for the given coin type. TW_EXPORT_PROPERTY enum TWHDVersion TWCoinTypeXprvVersion(enum TWCoinType coin); /// Validates an address string. +/// +/// \param coin A coin type +/// \param address A public address +/// \return true if the address is a valid public address of the given coin, false otherwise. TW_EXPORT_METHOD bool TWCoinTypeValidate(enum TWCoinType coin, TWString* _Nonnull address); /// Returns the default derivation path for a particular coin. +/// +/// \param coin A coin type +/// \return the default derivation path for the given coin type. TW_EXPORT_METHOD TWString* _Nonnull TWCoinTypeDerivationPath(enum TWCoinType coin); +/// Returns the derivation path for a particular coin with the explicit given derivation. +/// +/// \param coin A coin type +/// \param derivation A derivation type +/// \return the derivation path for the given coin with the explicit given derivation +TW_EXPORT_METHOD +TWString* _Nonnull TWCoinTypeDerivationPathWithDerivation(enum TWCoinType coin, enum TWDerivation derivation); + /// Derives the address for a particular coin from the private key. +/// +/// \param coin A coin type +/// \param privateKey A valid private key +/// \return Derived address for the given coin from the private key. TW_EXPORT_METHOD TWString* _Nonnull TWCoinTypeDeriveAddress(enum TWCoinType coin, struct TWPrivateKey* _Nonnull privateKey); /// Derives the address for a particular coin from the public key. +/// +/// \param coin A coin type +/// \param publicKey A valid public key +/// \return Derived address for the given coin from the public key. TW_EXPORT_METHOD TWString* _Nonnull TWCoinTypeDeriveAddressFromPublicKey(enum TWCoinType coin, struct TWPublicKey* _Nonnull publicKey); /// HRP for this coin type +/// +/// \param coin A coin type +/// \return HRP of the given coin type. TW_EXPORT_PROPERTY enum TWHRP TWCoinTypeHRP(enum TWCoinType coin); /// P2PKH prefix for this coin type +/// +/// \param coin A coin type +/// \return P2PKH prefix for the given coin type TW_EXPORT_PROPERTY uint8_t TWCoinTypeP2pkhPrefix(enum TWCoinType coin); /// P2SH prefix for this coin type +/// +/// \param coin A coin type +/// \return P2SH prefix for the given coin type TW_EXPORT_PROPERTY uint8_t TWCoinTypeP2shPrefix(enum TWCoinType coin); /// Static prefix for this coin type +/// +/// \param coin A coin type +/// \return Static prefix for the given coin type TW_EXPORT_PROPERTY uint8_t TWCoinTypeStaticPrefix(enum TWCoinType coin); +/// ChainID for this coin type. +/// +/// \param coin A coin type +/// \return ChainID for the given coin type. +/// \note Caller must free returned object. +TW_EXPORT_PROPERTY +TWString* _Nonnull TWCoinTypeChainId(enum TWCoinType coin); + /// SLIP-0044 id for this coin type +/// +/// \param coin A coin type +/// \return SLIP-0044 id for the given coin type TW_EXPORT_PROPERTY uint32_t TWCoinTypeSlip44Id(enum TWCoinType coin); +/// public key type for this coin type +/// +/// \param coin A coin type +/// \return public key type for the given coin type +TW_EXPORT_PROPERTY +enum TWPublicKeyType TWCoinTypePublicKeyType(enum TWCoinType coin); + TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWCoinTypeConfiguration.h b/include/TrustWalletCore/TWCoinTypeConfiguration.h index 47458179ac5..1a04fab57d8 100644 --- a/include/TrustWalletCore/TWCoinTypeConfiguration.h +++ b/include/TrustWalletCore/TWCoinTypeConfiguration.h @@ -12,32 +12,54 @@ TW_EXTERN_C_BEGIN +/// CoinTypeConfiguration functions TW_EXPORT_STRUCT struct TWCoinTypeConfiguration { uint8_t unused; // C doesn't allow zero-sized struct }; /// Returns stock symbol of coin +/// +/// \param type A coin type +/// \return A non-null TWString stock symbol of coin +/// \note Caller must free returned object TW_EXPORT_STATIC_METHOD TWString *_Nonnull TWCoinTypeConfigurationGetSymbol(enum TWCoinType type); /// Returns max count decimal places for minimal coin unit +/// +/// \param type A coin type +/// \return Returns max count decimal places for minimal coin unit TW_EXPORT_STATIC_METHOD int TWCoinTypeConfigurationGetDecimals(enum TWCoinType type); /// Returns transaction url in blockchain explorer +/// +/// \param type A coin type +/// \param transactionID A transaction identifier +/// \return Returns a non-null TWString transaction url in blockchain explorer TW_EXPORT_STATIC_METHOD TWString *_Nonnull TWCoinTypeConfigurationGetTransactionURL(enum TWCoinType type, TWString *_Nonnull transactionID); /// Returns account url in blockchain explorer +/// +/// \param type A coin type +/// \param accountID an Account identifier +/// \return Returns a non-null TWString account url in blockchain explorer TW_EXPORT_STATIC_METHOD TWString *_Nonnull TWCoinTypeConfigurationGetAccountURL(enum TWCoinType type, TWString *_Nonnull accountID); /// Returns full name of coin in lower case +/// +/// \param type A coin type +/// \return Returns a non-null TWString, full name of coin in lower case TW_EXPORT_STATIC_METHOD TWString *_Nonnull TWCoinTypeConfigurationGetID(enum TWCoinType type); /// Returns full name of coin +/// +/// \param type A coin type +/// \return Returns a non-null TWString, full name of coin TW_EXPORT_STATIC_METHOD TWString *_Nonnull TWCoinTypeConfigurationGetName(enum TWCoinType type); diff --git a/include/TrustWalletCore/TWCurve.h b/include/TrustWalletCore/TWCurve.h index 6ce6e645539..79d2891aa40 100644 --- a/include/TrustWalletCore/TWCurve.h +++ b/include/TrustWalletCore/TWCurve.h @@ -18,7 +18,7 @@ enum TWCurve { TWCurveED25519Blake2bNano /* "ed25519-blake2b-nano" */, TWCurveCurve25519 /* "curve25519" */, TWCurveNIST256p1 /* "nist256p1" */, - TWCurveED25519Extended /* "ed25519-cardano-seed" */, + TWCurveED25519ExtendedCardano /* "ed25519-cardano-seed" */, TWCurveNone }; diff --git a/include/TrustWalletCore/TWData.h b/include/TrustWalletCore/TWData.h index 6ebe32aff78..2eebf174c39 100644 --- a/include/TrustWalletCore/TWData.h +++ b/include/TrustWalletCore/TWData.h @@ -19,54 +19,111 @@ typedef const void TWString; typedef const void TWData; /// Creates a block of data from a byte array. -TWData *_Nonnull TWDataCreateWithBytes(const uint8_t *_Nonnull bytes, size_t size); +/// +/// \param bytes Non-null raw bytes buffer +/// \param size size of the buffer +/// \return Non-null filled block of data. +TWData *_Nonnull TWDataCreateWithBytes(const uint8_t *_Nonnull bytes, size_t size) TW_VISIBILITY_DEFAULT; /// Creates an uninitialized block of data with the provided size. -TWData *_Nonnull TWDataCreateWithSize(size_t size); +/// +/// \param size size for the block of data +/// \return Non-null uninitialized block of data with the provided size +TWData *_Nonnull TWDataCreateWithSize(size_t size) TW_VISIBILITY_DEFAULT; /// Creates a block of data by copying another block of data. -TWData *_Nonnull TWDataCreateWithData(TWData *_Nonnull data); +/// +/// \param data buffer that need to be copied +/// \return Non-null filled block of data. +TWData *_Nonnull TWDataCreateWithData(TWData *_Nonnull data) TW_VISIBILITY_DEFAULT; /// Creates a block of data from a hexadecimal string. Odd length is invalid (intended grouping to bytes is not obvious). -TWData *_Nullable TWDataCreateWithHexString(const TWString *_Nonnull hex); +/// +/// \param hex input hex string +/// \return Non-null filled block of data +TWData *_Nullable TWDataCreateWithHexString(const TWString *_Nonnull hex) TW_VISIBILITY_DEFAULT; /// Returns the size in bytes. -size_t TWDataSize(TWData *_Nonnull data); +/// +/// \param data A non-null valid block of data +/// \return the size of the given block of data +size_t TWDataSize(TWData *_Nonnull data) TW_VISIBILITY_DEFAULT; /// Returns the raw pointer to the contents of data. -uint8_t *_Nonnull TWDataBytes(TWData *_Nonnull data); +/// +/// \param data A non-null valid block of data +/// \return the raw pointer to the contents of data +uint8_t *_Nonnull TWDataBytes(TWData *_Nonnull data) TW_VISIBILITY_DEFAULT; /// Returns the byte at the provided index. -uint8_t TWDataGet(TWData *_Nonnull data, size_t index); +/// +/// \param data A non-null valid block of data +/// \param index index of the byte that we want to fetch - index need to be < TWDataSize(data) +/// \return the byte at the provided index +uint8_t TWDataGet(TWData *_Nonnull data, size_t index) TW_VISIBILITY_DEFAULT; /// Sets the byte at the provided index. -void TWDataSet(TWData *_Nonnull data, size_t index, uint8_t byte); +/// +/// \param data A non-null valid block of data +/// \param index index of the byte that we want to set - index need to be < TWDataSize(data) +/// \param byte Given byte to be written in data +void TWDataSet(TWData *_Nonnull data, size_t index, uint8_t byte) TW_VISIBILITY_DEFAULT; /// Copies a range of bytes into the provided buffer. -void TWDataCopyBytes(TWData *_Nonnull data, size_t start, size_t size, uint8_t *_Nonnull output); +/// +/// \param data A non-null valid block of data +/// \param start starting index of the range - index need to be < TWDataSize(data) +/// \param size size of the range we want to copy - size need to be < TWDataSize(data) - start +/// \param output The output buffer where we want to copy the data. +void TWDataCopyBytes(TWData *_Nonnull data, size_t start, size_t size, uint8_t *_Nonnull output) TW_VISIBILITY_DEFAULT; /// Replaces a range of bytes with the contents of the provided buffer. -void TWDataReplaceBytes(TWData *_Nonnull data, size_t start, size_t size, const uint8_t *_Nonnull bytes); +/// +/// \param data A non-null valid block of data +/// \param start starting index of the range - index need to be < TWDataSize(data) +/// \param size size of the range we want to replace - size need to be < TWDataSize(data) - start +/// \param bytes The buffer that will replace the range of data +void TWDataReplaceBytes(TWData *_Nonnull data, size_t start, size_t size, const uint8_t *_Nonnull bytes) TW_VISIBILITY_DEFAULT; /// Appends data from a byte array. -void TWDataAppendBytes(TWData *_Nonnull data, const uint8_t *_Nonnull bytes, size_t size); +/// +/// \param data A non-null valid block of data +/// \param bytes Non-null byte array +/// \param size The size of the byte array +void TWDataAppendBytes(TWData *_Nonnull data, const uint8_t *_Nonnull bytes, size_t size) TW_VISIBILITY_DEFAULT; /// Appends a single byte. -void TWDataAppendByte(TWData *_Nonnull data, uint8_t byte); +/// +/// \param data A non-null valid block of data +/// \param byte A single byte +void TWDataAppendByte(TWData *_Nonnull data, uint8_t byte) TW_VISIBILITY_DEFAULT; /// Appends a block of data. -void TWDataAppendData(TWData *_Nonnull data, TWData *_Nonnull append); +/// +/// \param data A non-null valid block of data +/// \param append A non-null valid block of data +void TWDataAppendData(TWData *_Nonnull data, TWData *_Nonnull append) TW_VISIBILITY_DEFAULT; -/// Revereses the bytes. -void TWDataReverse(TWData *_Nonnull data); +/// Reverse the bytes. +/// +/// \param data A non-null valid block of data +void TWDataReverse(TWData *_Nonnull data) TW_VISIBILITY_DEFAULT; /// Sets all bytes to the given value. -void TWDataReset(TWData *_Nonnull data); +/// +/// \param data A non-null valid block of data +void TWDataReset(TWData *_Nonnull data) TW_VISIBILITY_DEFAULT; /// Deletes a block of data created with a `TWDataCreate*` method. -void TWDataDelete(TWData *_Nonnull data); +/// +/// \param data A non-null valid block of data +void TWDataDelete(TWData *_Nonnull data) TW_VISIBILITY_DEFAULT; /// Determines whether two data blocks are equal. -bool TWDataEqual(TWData *_Nonnull lhs, TWData *_Nonnull rhs); +/// +/// \param lhs left non null block of data to be compared +/// \param rhs right non null block of data to be compared +/// \return true if both block of data are equal, false otherwise +bool TWDataEqual(TWData *_Nonnull lhs, TWData *_Nonnull rhs) TW_VISIBILITY_DEFAULT; TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWDataVector.h b/include/TrustWalletCore/TWDataVector.h new file mode 100644 index 00000000000..6e2e88912d0 --- /dev/null +++ b/include/TrustWalletCore/TWDataVector.h @@ -0,0 +1,62 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "TWBase.h" +#include "TWData.h" + +TW_EXTERN_C_BEGIN + +/// A vector of TWData byte arrays +TW_EXPORT_CLASS +struct TWDataVector; + +/// Creates a Vector of Data. +/// +/// \note Must be deleted with \TWDataVectorDelete +/// \return a non-null Vector of Data. +TW_EXPORT_STATIC_METHOD +struct TWDataVector* _Nonnull TWDataVectorCreate(); + +/// Creates a Vector of Data with the given element +/// +/// \param data A non-null valid block of data +/// \return A Vector of data with a single given element +TW_EXPORT_STATIC_METHOD +struct TWDataVector* _Nonnull TWDataVectorCreateWithData(TWData* _Nonnull data); + +/// Delete/Deallocate a Vector of Data +/// +/// \param dataVector A non-null Vector of data +TW_EXPORT_METHOD +void TWDataVectorDelete(struct TWDataVector* _Nonnull dataVector); + +/// Add an element to a Vector of Data. Element is cloned +/// +/// \param dataVector A non-null Vector of data +/// \param data A non-null valid block of data +/// \note data input parameter must be deleted on its own +TW_EXPORT_METHOD +void TWDataVectorAdd(struct TWDataVector* _Nonnull dataVector, TWData* _Nonnull data); + +/// Retrieve the number of elements +/// +/// \param dataVector A non-null Vector of data +/// \return the size of the given vector. +TW_EXPORT_PROPERTY +size_t TWDataVectorSize(const struct TWDataVector* _Nonnull dataVector); + +/// Retrieve the n-th element. +/// +/// \param dataVector A non-null Vector of data +/// \param index index element of the vector to be retrieved, need to be < TWDataVectorSize +/// \note Returned element must be freed with \TWDataDelete +/// \return A non-null block of data +TW_EXPORT_METHOD +TWData* _Nullable TWDataVectorGet(const struct TWDataVector* _Nonnull dataVector, size_t index); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWDerivationPath.h b/include/TrustWalletCore/TWDerivationPath.h new file mode 100644 index 00000000000..910288f600f --- /dev/null +++ b/include/TrustWalletCore/TWDerivationPath.h @@ -0,0 +1,103 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "TWBase.h" +#include "TWCoinType.h" +#include "TWDerivationPath.h" +#include "TWPurpose.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +/// Represents a BIP44 DerivationPath in C++. +TW_EXPORT_CLASS +struct TWDerivationPath; + +/// Creates a new DerivationPath with a purpose, coin, account, change and address. +/// Must be deleted with TWDerivationPathDelete after use. +/// +/// \param purpose The purpose of the Path. +/// \param coin The coin type of the Path. +/// \param account The derivation of the Path. +/// \param change The derivation path of the Path. +/// \param address hex encoded public key. +/// \return A new DerivationPath. +TW_EXPORT_STATIC_METHOD +struct TWDerivationPath* _Nonnull TWDerivationPathCreate(enum TWPurpose purpose, uint32_t coin, uint32_t account, uint32_t change, uint32_t address); + +/// Creates a new DerivationPath with a string +/// +/// \param string The string of the Path. +/// \return A new DerivationPath or null if string is invalid. +TW_EXPORT_STATIC_METHOD +struct TWDerivationPath* _Nullable TWDerivationPathCreateWithString(TWString* _Nonnull string); + +/// Deletes a DerivationPath. +/// +/// \param path DerivationPath to delete. +TW_EXPORT_METHOD +void TWDerivationPathDelete(struct TWDerivationPath* _Nonnull path); + +/// Returns the index component of a DerivationPath. +/// +/// \param path DerivationPath to get the index of. +/// \param index The index component of the DerivationPath. +/// \return DerivationPathIndex or null if index is invalid. +TW_EXPORT_METHOD +struct TWDerivationPathIndex* _Nullable TWDerivationPathIndexAt(struct TWDerivationPath* _Nonnull path, uint32_t index); + +/// Returns the indices count of a DerivationPath. +/// +/// \param path DerivationPath to get the indices count of. +/// \return The indices count of the DerivationPath. +TW_EXPORT_METHOD +uint32_t TWDerivationPathIndicesCount(struct TWDerivationPath* _Nonnull path); + +/// Returns the purpose enum of a DerivationPath. +/// +/// \param path DerivationPath to get the purpose of. +/// \return DerivationPathPurpose. +TW_EXPORT_PROPERTY +enum TWPurpose TWDerivationPathPurpose(struct TWDerivationPath* _Nonnull path); + +/// Returns the coin value of a derivation path. +/// +/// \param path DerivationPath to get the coin of. +/// \return The coin part of the DerivationPath. +TW_EXPORT_PROPERTY +uint32_t TWDerivationPathCoin(struct TWDerivationPath* _Nonnull path); + +/// Returns the account value of a derivation path. +/// +/// \param path DerivationPath to get the account of. +/// \return the account part of a derivation path. +TW_EXPORT_PROPERTY +uint32_t TWDerivationPathAccount(struct TWDerivationPath* _Nonnull path); + +/// Returns the change value of a derivation path. +/// +/// \param path DerivationPath to get the change of. +/// \return The change part of a derivation path. +TW_EXPORT_PROPERTY +uint32_t TWDerivationPathChange(struct TWDerivationPath* _Nonnull path); + +/// Returns the address value of a derivation path. +/// +/// \param path DerivationPath to get the address of. +/// \return The address part of the derivation path. +TW_EXPORT_PROPERTY +uint32_t TWDerivationPathAddress(struct TWDerivationPath* _Nonnull path); + +/// Returns the string description of a derivation path. +/// +/// \param path DerivationPath to get the address of. +/// \return The string description of the derivation path. +TW_EXPORT_PROPERTY +TWString* _Nonnull TWDerivationPathDescription(struct TWDerivationPath* _Nonnull path); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWDerivationPathIndex.h b/include/TrustWalletCore/TWDerivationPathIndex.h new file mode 100644 index 00000000000..72bd2b8344e --- /dev/null +++ b/include/TrustWalletCore/TWDerivationPathIndex.h @@ -0,0 +1,53 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "TWBase.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +/// Represents a derivation path index in C++ with value and hardened flag. +TW_EXPORT_CLASS +struct TWDerivationPathIndex; + +/// Creates a new Index with a value and hardened flag. +/// Must be deleted with TWDerivationPathIndexDelete after use. +/// +/// \param value Index value +/// \param hardened Indicates if the Index is hardened. +/// \return A new Index. +TW_EXPORT_STATIC_METHOD +struct TWDerivationPathIndex* _Nonnull TWDerivationPathIndexCreate(uint32_t value, bool hardened); + +/// Deletes an Index. +/// +/// \param index Index to delete. +TW_EXPORT_METHOD +void TWDerivationPathIndexDelete(struct TWDerivationPathIndex* _Nonnull index); + +/// Returns numeric value of an Index. +/// +/// \param index Index to get the numeric value of. +TW_EXPORT_PROPERTY +uint32_t TWDerivationPathIndexValue(struct TWDerivationPathIndex* _Nonnull index); + +/// Returns hardened flag of an Index. +/// +/// \param index Index to get hardened flag. +/// \return true if hardened, false otherwise. +TW_EXPORT_PROPERTY +bool TWDerivationPathIndexHardened(struct TWDerivationPathIndex* _Nonnull index); + +/// Returns the string description of a derivation path index. +/// +/// \param path Index to get the address of. +/// \return The string description of the derivation path index. +TW_EXPORT_PROPERTY +TWString* _Nonnull TWDerivationPathIndexDescription(struct TWDerivationPathIndex* _Nonnull index); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWEthereumAbi.h b/include/TrustWalletCore/TWEthereumAbi.h index 0430ea07a3c..5baf7161942 100644 --- a/include/TrustWalletCore/TWEthereumAbi.h +++ b/include/TrustWalletCore/TWEthereumAbi.h @@ -10,24 +10,34 @@ #include "TWString.h" #include "TWData.h" -// Wrapper class for Ethereum ABI encoding & decoding. - TW_EXTERN_C_BEGIN +/// Wrapper class for Ethereum ABI encoding & decoding. struct TWEthereumAbiFunction; TW_EXPORT_STRUCT struct TWEthereumAbi; /// Encode function to Eth ABI binary +/// +/// \param fn Non-null Eth abi function +/// \return Non-null encoded block of data TW_EXPORT_STATIC_METHOD TWData* _Nonnull TWEthereumAbiEncode(struct TWEthereumAbiFunction* _Nonnull fn); /// Decode function output from Eth ABI binary, fill output parameters +/// +/// \param[in] fn Non-null Eth abi function +/// \param[out] encoded Non-null block of data +/// \return true if encoded have been filled correctly, false otherwise TW_EXPORT_STATIC_METHOD bool TWEthereumAbiDecodeOutput(struct TWEthereumAbiFunction* _Nonnull fn, TWData* _Nonnull encoded); /// Decode function call data to human readable json format, according to input abi json +/// +/// \param data Non-null block of data +/// \param abi Non-null string +/// \return Non-null json string function call data TW_EXPORT_STATIC_METHOD TWString* _Nullable TWEthereumAbiDecodeCall(TWData* _Nonnull data, TWString* _Nonnull abi); @@ -66,6 +76,9 @@ TWString* _Nullable TWEthereumAbiDecodeCall(TWData* _Nonnull data, TWString* _No /// })"); /// On error, empty Data is returned. /// Returned data must be deleted (hint: use WRAPD() macro). +/// +/// \param messageJson Non-null json abi input +/// \return Non-null block of data, encoded abi input TW_EXPORT_STATIC_METHOD TWData* _Nonnull TWEthereumAbiEncodeTyped(TWString* _Nonnull messageJson); diff --git a/include/TrustWalletCore/TWEthereumAbiFunction.h b/include/TrustWalletCore/TWEthereumAbiFunction.h index 81daa7b3e7e..4034ade5180 100644 --- a/include/TrustWalletCore/TWEthereumAbiFunction.h +++ b/include/TrustWalletCore/TWEthereumAbiFunction.h @@ -7,182 +7,450 @@ #pragma once #include "TWBase.h" -#include "TWString.h" #include "TWData.h" +#include "TWString.h" TW_EXTERN_C_BEGIN +/// Represents Ethereum ABI function TW_EXPORT_CLASS struct TWEthereumAbiFunction; /// Creates a function object, with the given name and empty parameter list. It must be deleted at the end. +/// +/// \param name function name +/// \return Non-null Ethereum abi function TW_EXPORT_STATIC_METHOD -struct TWEthereumAbiFunction *_Nonnull TWEthereumAbiFunctionCreateWithString(TWString *_Nonnull name); +struct TWEthereumAbiFunction* _Nonnull TWEthereumAbiFunctionCreateWithString(TWString* _Nonnull name); /// Deletes a function object created with a 'TWEthereumAbiFunctionCreateWithString' method. +/// +/// \param fn Non-null Ethereum abi function TW_EXPORT_METHOD -void TWEthereumAbiFunctionDelete(struct TWEthereumAbiFunction *_Nonnull fn); +void TWEthereumAbiFunctionDelete(struct TWEthereumAbiFunction* _Nonnull fn); /// Return the function type signature, of the form "baz(int32,uint256)" +/// +/// \param fn A Non-null eth abi function +/// \return function type signature as a Non-null string. TW_EXPORT_METHOD -TWString *_Nonnull TWEthereumAbiFunctionGetType(struct TWEthereumAbiFunction *_Nonnull fn); +TWString* _Nonnull TWEthereumAbiFunctionGetType(struct TWEthereumAbiFunction* _Nonnull fn); + +/// Methods for adding parameters of the given type (input or output). +/// For output parameters (isOutput=true) a value has to be specified, although usually not need; -/// Methods for adding parameters of the given type (input or output). -/// For output parameters (isOutput=true) a value has to be specified, although usually not needd. -/// Returns the index of the parameter (0-based). +/// Add a uint8 type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamUInt8(struct TWEthereumAbiFunction *_Nonnull fn, uint8_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamUInt8(struct TWEthereumAbiFunction* _Nonnull fn, uint8_t val, bool isOutput); +/// Add a uint16 type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamUInt16(struct TWEthereumAbiFunction *_Nonnull fn, uint16_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamUInt16(struct TWEthereumAbiFunction* _Nonnull fn, uint16_t val, bool isOutput); +/// Add a uint32 type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamUInt32(struct TWEthereumAbiFunction *_Nonnull fn, uint32_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamUInt32(struct TWEthereumAbiFunction* _Nonnull fn, uint32_t val, bool isOutput); +/// Add a uint64 type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamUInt64(struct TWEthereumAbiFunction *_Nonnull fn, uint64_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamUInt64(struct TWEthereumAbiFunction* _Nonnull fn, uint64_t val, bool isOutput); +/// Add a uint256 type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamUInt256(struct TWEthereumAbiFunction *_Nonnull fn, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamUInt256(struct TWEthereumAbiFunction* _Nonnull fn, TWData* _Nonnull val, bool isOutput); +/// Add a uint(bits) type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamUIntN(struct TWEthereumAbiFunction *_Nonnull fn, int bits, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamUIntN(struct TWEthereumAbiFunction* _Nonnull fn, int bits, TWData* _Nonnull val, bool isOutput); +/// Add a int8 type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamInt8(struct TWEthereumAbiFunction *_Nonnull fn, int8_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamInt8(struct TWEthereumAbiFunction* _Nonnull fn, int8_t val, bool isOutput); +/// Add a int16 type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamInt16(struct TWEthereumAbiFunction *_Nonnull fn, int16_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamInt16(struct TWEthereumAbiFunction* _Nonnull fn, int16_t val, bool isOutput); +/// Add a int32 type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamInt32(struct TWEthereumAbiFunction *_Nonnull fn, int32_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamInt32(struct TWEthereumAbiFunction* _Nonnull fn, int32_t val, bool isOutput); +/// Add a int64 type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamInt64(struct TWEthereumAbiFunction *_Nonnull fn, int64_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamInt64(struct TWEthereumAbiFunction* _Nonnull fn, int64_t val, bool isOutput); +/// Add a int256 type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified (stored in a block of data) +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamInt256(struct TWEthereumAbiFunction *_Nonnull fn, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamInt256(struct TWEthereumAbiFunction* _Nonnull fn, TWData* _Nonnull val, bool isOutput); +/// Add a int(bits) type parameter +/// +/// \param fn A Non-null eth abi function +/// \param bits Number of bits of the integer parameter +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamIntN(struct TWEthereumAbiFunction *_Nonnull fn, int bits, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamIntN(struct TWEthereumAbiFunction* _Nonnull fn, int bits, TWData* _Nonnull val, bool isOutput); + +/// Add a bool type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamBool(struct TWEthereumAbiFunction *_Nonnull fn, bool val, bool isOutput); +int TWEthereumAbiFunctionAddParamBool(struct TWEthereumAbiFunction* _Nonnull fn, bool val, bool isOutput); +/// Add a string type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamString(struct TWEthereumAbiFunction *_Nonnull fn, TWString *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamString(struct TWEthereumAbiFunction* _Nonnull fn, TWString* _Nonnull val, bool isOutput); +/// Add an address type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamAddress(struct TWEthereumAbiFunction *_Nonnull fn, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamAddress(struct TWEthereumAbiFunction* _Nonnull fn, TWData* _Nonnull val, bool isOutput); +/// Add a bytes type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamBytes(struct TWEthereumAbiFunction *_Nonnull fn, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamBytes(struct TWEthereumAbiFunction* _Nonnull fn, TWData* _Nonnull val, bool isOutput); +/// Add a bytes[N] type parameter +/// +/// \param fn A Non-null eth abi function +/// \param size fixed size of the bytes array parameter (val). +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamBytesFix(struct TWEthereumAbiFunction *_Nonnull fn, size_t size, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamBytesFix(struct TWEthereumAbiFunction* _Nonnull fn, size_t size, TWData* _Nonnull val, bool isOutput); +/// Add a type[] type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamArray(struct TWEthereumAbiFunction *_Nonnull fn, bool isOutput); +int TWEthereumAbiFunctionAddParamArray(struct TWEthereumAbiFunction* _Nonnull fn, bool isOutput); /// Methods for accessing the value of an output or input parameter, of different types. + +/// Get a uint8 type parameter at the given index +/// +/// \param fn A Non-null eth abi function +/// \param idx index for the parameter (0-based). +/// \param isOutput determines if the parameter is an input or output +/// \return the value of the parameter. TW_EXPORT_METHOD -uint8_t TWEthereumAbiFunctionGetParamUInt8(struct TWEthereumAbiFunction *_Nonnull fn, int idx, bool isOutput); +uint8_t TWEthereumAbiFunctionGetParamUInt8(struct TWEthereumAbiFunction* _Nonnull fn, int idx, bool isOutput); + +/// Get a uint64 type parameter at the given index +/// +/// \param fn A Non-null eth abi function +/// \param idx index for the parameter (0-based). +/// \param isOutput determines if the parameter is an input or output +/// \return the value of the parameter. TW_EXPORT_METHOD -uint64_t TWEthereumAbiFunctionGetParamUInt64(struct TWEthereumAbiFunction *_Nonnull fn, int idx, bool isOutput); +uint64_t TWEthereumAbiFunctionGetParamUInt64(struct TWEthereumAbiFunction* _Nonnull fn, int idx, bool isOutput); + +/// Get a uint256 type parameter at the given index +/// +/// \param fn A Non-null eth abi function +/// \param idx index for the parameter (0-based). +/// \param isOutput determines if the parameter is an input or output +/// \return the value of the parameter stored in a block of data. TW_EXPORT_METHOD -TWData *_Nonnull TWEthereumAbiFunctionGetParamUInt256(struct TWEthereumAbiFunction *_Nonnull fn, int idx, bool isOutput); +TWData* _Nonnull TWEthereumAbiFunctionGetParamUInt256(struct TWEthereumAbiFunction* _Nonnull fn, int idx, bool isOutput); + +/// Get a bool type parameter at the given index +/// +/// \param fn A Non-null eth abi function +/// \param idx index for the parameter (0-based). +/// \param isOutput determines if the parameter is an input or output +/// \return the value of the parameter. TW_EXPORT_METHOD -bool TWEthereumAbiFunctionGetParamBool(struct TWEthereumAbiFunction *_Nonnull fn, int idx, bool isOutput); +bool TWEthereumAbiFunctionGetParamBool(struct TWEthereumAbiFunction* _Nonnull fn, int idx, bool isOutput); + +/// Get a string type parameter at the given index +/// +/// \param fn A Non-null eth abi function +/// \param idx index for the parameter (0-based). +/// \param isOutput determines if the parameter is an input or output +/// \return the value of the parameter. TW_EXPORT_METHOD -TWString *_Nonnull TWEthereumAbiFunctionGetParamString(struct TWEthereumAbiFunction *_Nonnull fn, int idx, bool isOutput); +TWString* _Nonnull TWEthereumAbiFunctionGetParamString(struct TWEthereumAbiFunction* _Nonnull fn, int idx, bool isOutput); + +/// Get an address type parameter at the given index +/// +/// \param fn A Non-null eth abi function +/// \param idx index for the parameter (0-based). +/// \param isOutput determines if the parameter is an input or output +/// \return the value of the parameter. TW_EXPORT_METHOD -TWData *_Nonnull TWEthereumAbiFunctionGetParamAddress(struct TWEthereumAbiFunction *_Nonnull fn, int idx, bool isOutput); +TWData* _Nonnull TWEthereumAbiFunctionGetParamAddress(struct TWEthereumAbiFunction* _Nonnull fn, int idx, bool isOutput); /// Methods for adding a parameter of the given type to a top-level input parameter array. Returns the index of the parameter (0-based). /// Note that nested ParamArrays are not possible through this API, could be done by using index paths like "1/0" + +/// Adding a uint8 type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamUInt8(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, uint8_t val); +int TWEthereumAbiFunctionAddInArrayParamUInt8(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, uint8_t val); +/// Adding a uint16 type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamUInt16(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, uint16_t val); +int TWEthereumAbiFunctionAddInArrayParamUInt16(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, uint16_t val); +/// Adding a uint32 type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamUInt32(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, uint32_t val); +int TWEthereumAbiFunctionAddInArrayParamUInt32(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, uint32_t val); +/// Adding a uint64 type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamUInt64(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, uint64_t val); +int TWEthereumAbiFunctionAddInArrayParamUInt64(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, uint64_t val); +/// Adding a uint256 type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter stored in a block of data +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamUInt256(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, TWData *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamUInt256(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, TWData* _Nonnull val); +/// Adding a uint[N] type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param bits Number of bits of the integer parameter +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter stored in a block of data +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamUIntN(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, int bits, TWData *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamUIntN(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, int bits, TWData* _Nonnull val); +/// Adding a int8 type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamInt8(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, int8_t val); +int TWEthereumAbiFunctionAddInArrayParamInt8(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, int8_t val); +/// Adding a int16 type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamInt16(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, int16_t val); +int TWEthereumAbiFunctionAddInArrayParamInt16(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, int16_t val); +/// Adding a int32 type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamInt32(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, int32_t val); +int TWEthereumAbiFunctionAddInArrayParamInt32(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, int32_t val); +/// Adding a int64 type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamInt64(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, int64_t val); +int TWEthereumAbiFunctionAddInArrayParamInt64(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, int64_t val); +/// Adding a int256 type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter stored in a block of data +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamInt256(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, TWData *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamInt256(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, TWData* _Nonnull val); +/// Adding a int[N] type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param bits Number of bits of the integer parameter +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter stored in a block of data +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamIntN(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, int bits, TWData *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamIntN(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, int bits, TWData* _Nonnull val); +/// Adding a bool type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamBool(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, bool val); +int TWEthereumAbiFunctionAddInArrayParamBool(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, bool val); +/// Adding a string type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamString(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, TWString *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamString(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, TWString* _Nonnull val); +/// Adding an address type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamAddress(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, TWData *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamAddress(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, TWData* _Nonnull val); +/// Adding a bytes type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamBytes(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, TWData *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamBytes(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, TWData* _Nonnull val); +/// Adding a int64 type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param size fixed size of the bytes array parameter (val). +/// \param val the value of the parameter +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamBytesFix(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, size_t size, TWData *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamBytesFix(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, size_t size, TWData* _Nonnull val); TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWEthereumAbiValue.h b/include/TrustWalletCore/TWEthereumAbiValue.h index 58a2f7e74d5..9bbf6ce34eb 100644 --- a/include/TrustWalletCore/TWEthereumAbiValue.h +++ b/include/TrustWalletCore/TWEthereumAbiValue.h @@ -12,55 +12,93 @@ TW_EXTERN_C_BEGIN +/// Represents Ethereum ABI value TW_EXPORT_STRUCT struct TWEthereumAbiValue; -/// Returned data must be deleted (hint: use WRAPD() macro). -/// Encode a type according to Ethereum ABI, into 32 bytes. Values are padded by 0 on the left, unless specified otherwise. - +/// Encode a bool according to Ethereum ABI, into 32 bytes. Values are padded by 0 on the left, unless specified otherwise +/// +/// \param value a boolean value +/// \return Encoded value stored in a block of data TW_EXPORT_STATIC_METHOD TWData* _Nonnull TWEthereumAbiValueEncodeBool(bool value); +/// Encode a int32 according to Ethereum ABI, into 32 bytes. Values are padded by 0 on the left, unless specified otherwise +/// +/// \param value a int32 value +/// \return Encoded value stored in a block of data TW_EXPORT_STATIC_METHOD TWData* _Nonnull TWEthereumAbiValueEncodeInt32(int32_t value); +/// Encode a uint32 according to Ethereum ABI, into 32 bytes. Values are padded by 0 on the left, unless specified otherwise +/// +/// \param value a uint32 value +/// \return Encoded value stored in a block of data TW_EXPORT_STATIC_METHOD TWData* _Nonnull TWEthereumAbiValueEncodeUInt32(uint32_t value); -/// Encode an int256. Input value is represented as a 32-byte value +/// Encode a int256 according to Ethereum ABI, into 32 bytes. Values are padded by 0 on the left, unless specified otherwise +/// +/// \param value a int256 value stored in a block of data +/// \return Encoded value stored in a block of data TW_EXPORT_STATIC_METHOD TWData* _Nonnull TWEthereumAbiValueEncodeInt256(TWData* _Nonnull value); -/// Encode an uint256. Input value is represented as a 32-byte binary value +/// Encode an int256 according to Ethereum ABI, into 32 bytes. Values are padded by 0 on the left, unless specified otherwise +/// +/// \param value a int256 value stored in a block of data +/// \return Encoded value stored in a block of data TW_EXPORT_STATIC_METHOD TWData* _Nonnull TWEthereumAbiValueEncodeUInt256(TWData* _Nonnull value); -/// Encode the 20 bytes of an address +/// Encode an address according to Ethereum ABI, 20 bytes of the address. +/// +/// \param value an address value stored in a block of data +/// \return Encoded value stored in a block of data TW_EXPORT_STATIC_METHOD TWData* _Nonnull TWEthereumAbiValueEncodeAddress(TWData* _Nonnull value); -/// Encode a string by encoding its hash +/// Encode a string according to Ethereum ABI by encoding its hash. +/// +/// \param value a string value +/// \return Encoded value stored in a block of data TW_EXPORT_STATIC_METHOD TWData* _Nonnull TWEthereumAbiValueEncodeString(TWString* _Nonnull value); /// Encode a number of bytes, up to 32 bytes, padded on the right. Longer arrays are truncated. +/// +/// \param value bunch of bytes +/// \return Encoded value stored in a block of data TW_EXPORT_STATIC_METHOD TWData* _Nonnull TWEthereumAbiValueEncodeBytes(TWData* _Nonnull value); /// Encode a dynamic number of bytes by encoding its hash +/// +/// \param value bunch of bytes +/// \return Encoded value stored in a block of data TW_EXPORT_STATIC_METHOD TWData* _Nonnull TWEthereumAbiValueEncodeBytesDyn(TWData* _Nonnull value); - /// Decodes input data (bytes longer than 32 will be truncated) as uint256 +/// +/// \param input Data to be decoded +/// \return Non-null decoded string value TW_EXPORT_STATIC_METHOD TWString* _Nonnull TWEthereumAbiValueDecodeUInt256(TWData* _Nonnull input); /// Decode an arbitrary type, return value as string +/// +/// \param input Data to be decoded +/// \param type the underlying type that need to be decoded +/// \return Non-null decoded string value TW_EXPORT_STATIC_METHOD TWString* _Nonnull TWEthereumAbiValueDecodeValue(TWData* _Nonnull input, TWString* _Nonnull type); /// Decode an array of given simple types. Return a '\n'-separated string of elements +/// +/// \param input Data to be decoded +/// \param type the underlying type that need to be decoded +/// \return Non-null decoded string value TW_EXPORT_STATIC_METHOD TWString* _Nonnull TWEthereumAbiValueDecodeArray(TWData* _Nonnull input, TWString* _Nonnull type); diff --git a/include/TrustWalletCore/TWEthereumChainID.h b/include/TrustWalletCore/TWEthereumChainID.h deleted file mode 100644 index cbf04a1acfa..00000000000 --- a/include/TrustWalletCore/TWEthereumChainID.h +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "TWBase.h" - -TW_EXTERN_C_BEGIN - -/// Chain identifier for Ethereum-based blockchains. -TW_EXPORT_ENUM(uint32_t) -enum TWEthereumChainID { - TWEthereumChainIDEthereum = 1, - TWEthereumChainIDGo = 60, - TWEthereumChainIDPOA = 99, - TWEthereumChainIDCallisto = 820, - TWEthereumChainIDEthereumClassic = 61, - TWEthereumChainIDVeChain = 74, - TWEthereumChainIDThunderToken = 108, - TWEthereumChainIDTomoChain = 88, - TWEthereumChainIDBinanceSmartChain = 56, - TWEthereumChainIDPolygon = 137, - TWEthereumChainIDWanchain = 888, - TWEthereumChainIDOptimism = 10, - TWEthereumChainIDArbitrum = 42161, - TWEthereumChainIDHeco = 128, - TWEthereumChainIDAvalanche = 43114, - TWEthereumChainIDXDai = 100, - TWEthereumChainIDFantom = 250, - TWEthereumChainIDCelo = 42220, - TWEthereumChainIDRonin = 2020, -}; - -TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWFIOAccount.h b/include/TrustWalletCore/TWFIOAccount.h index 8012cbf0faf..876aabbda7d 100644 --- a/include/TrustWalletCore/TWFIOAccount.h +++ b/include/TrustWalletCore/TWFIOAccount.h @@ -15,13 +15,24 @@ TW_EXTERN_C_BEGIN TW_EXPORT_CLASS struct TWFIOAccount; +/// Create a FIO Account +/// +/// \param string Account name +/// \note Must be deleted with \TWFIOAccountDelete +/// \return Pointer to a nullable FIO Account TW_EXPORT_STATIC_METHOD struct TWFIOAccount *_Nullable TWFIOAccountCreateWithString(TWString *_Nonnull string); +/// Delete a FIO Account +/// +/// \param account Pointer to a non-null FIO Account TW_EXPORT_METHOD void TWFIOAccountDelete(struct TWFIOAccount *_Nonnull account); /// Returns the short account string representation. +/// +/// \param account Pointer to a non-null FIO Account +/// \return Account non-null string representation TW_EXPORT_PROPERTY TWString *_Nonnull TWFIOAccountDescription(struct TWFIOAccount *_Nonnull account); diff --git a/include/TrustWalletCore/TWGroestlcoinAddress.h b/include/TrustWalletCore/TWGroestlcoinAddress.h index 37edc0e8eb5..b35ce21ee64 100644 --- a/include/TrustWalletCore/TWGroestlcoinAddress.h +++ b/include/TrustWalletCore/TWGroestlcoinAddress.h @@ -19,25 +19,47 @@ TW_EXPORT_CLASS struct TWGroestlcoinAddress; /// Compares two addresses for equality. +/// +/// \param lhs left Non-null GroestlCoin address to be compared +/// \param rhs right Non-null GroestlCoin address to be compared +/// \return true if both address are equal, false otherwise TW_EXPORT_STATIC_METHOD bool TWGroestlcoinAddressEqual(struct TWGroestlcoinAddress *_Nonnull lhs, struct TWGroestlcoinAddress *_Nonnull rhs); /// Determines if the string is a valid Groestlcoin address. +/// +/// \param string Non-null string. +/// \return true if it's a valid address, false otherwise TW_EXPORT_STATIC_METHOD bool TWGroestlcoinAddressIsValidString(TWString *_Nonnull string); -/// Create an address from a base58 sring representaion. +/// Create an address from a base58 string representation. +/// +/// \param string Non-null string +/// \note Must be deleted with \TWGroestlcoinAddressDelete +/// \return Non-null GroestlcoinAddress TW_EXPORT_STATIC_METHOD struct TWGroestlcoinAddress *_Nullable TWGroestlcoinAddressCreateWithString(TWString *_Nonnull string); /// Create an address from a public key and a prefix byte. +/// +/// \param publicKey Non-null public key +/// \param prefix public key prefix +/// \note Must be deleted with \TWGroestlcoinAddressDelete +/// \return Non-null GroestlcoinAddress TW_EXPORT_STATIC_METHOD struct TWGroestlcoinAddress *_Nonnull TWGroestlcoinAddressCreateWithPublicKey(struct TWPublicKey *_Nonnull publicKey, uint8_t prefix); +/// Delete a Groestlcoin address +/// +/// \param address Non-null GroestlcoinAddress TW_EXPORT_METHOD void TWGroestlcoinAddressDelete(struct TWGroestlcoinAddress *_Nonnull address); /// Returns the address base58 string representation. +/// +/// \param address Non-null GroestlcoinAddress +/// \return Address description as a non-null string TW_EXPORT_PROPERTY TWString *_Nonnull TWGroestlcoinAddressDescription(struct TWGroestlcoinAddress *_Nonnull address); diff --git a/include/TrustWalletCore/TWHDVersion.h b/include/TrustWalletCore/TWHDVersion.h index 9e93aed3aa7..dec82a60ebc 100644 --- a/include/TrustWalletCore/TWHDVersion.h +++ b/include/TrustWalletCore/TWHDVersion.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -10,9 +10,9 @@ TW_EXTERN_C_BEGIN -/// Registered HD version bytes +/// Registered HD version bytes /// -/// - SeeAlso: https://github.com/satoshilabs/slips/blob/master/slip-0132.md +/// \see https://github.com/satoshilabs/slips/blob/master/slip-0132.md TW_EXPORT_ENUM(uint32_t) enum TWHDVersion { TWHDVersionNone = 0, @@ -40,9 +40,17 @@ enum TWHDVersion { TWHDVersionDGPV = 0x02fac398, }; +/// Determine if the HD Version is public +/// +/// \param version HD version +/// \return true if the version is public, false otherwise TW_EXPORT_PROPERTY bool TWHDVersionIsPublic(enum TWHDVersion version); +/// Determine if the HD Version is private +/// +/// \param version HD version +/// \return true if the version is private, false otherwise TW_EXPORT_PROPERTY bool TWHDVersionIsPrivate(enum TWHDVersion version); diff --git a/include/TrustWalletCore/TWHDWallet.h b/include/TrustWalletCore/TWHDWallet.h index 5f8a2d849af..55ab3e13b4a 100644 --- a/include/TrustWalletCore/TWHDWallet.h +++ b/include/TrustWalletCore/TWHDWallet.h @@ -10,6 +10,7 @@ #include "TWCoinType.h" #include "TWCurve.h" #include "TWData.h" +#include "TWDerivation.h" #include "TWHDVersion.h" #include "TWPrivateKey.h" #include "TWPublicKey.h" @@ -18,79 +19,218 @@ TW_EXTERN_C_BEGIN +/// Hierarchical Deterministic (HD) Wallet TW_EXPORT_CLASS struct TWHDWallet; -/// TWHDWalletIsValid has been deprecated; use TWMnemonicIsValid(). - /// Creates a new HDWallet with a new random mnemonic with the provided strength in bits. -/// Null is returned on invalid strength. Returned object needs to be deleted. +/// +/// \param strength strength in bits +/// \param passphrase non-null passphrase +/// \note Null is returned on invalid strength +/// \note Returned object needs to be deleted with \TWHDWalletDelete +/// \return Nullable TWHDWallet TW_EXPORT_STATIC_METHOD -struct TWHDWallet *_Nullable TWHDWalletCreate(int strength, TWString *_Nonnull passphrase); +struct TWHDWallet* _Nullable TWHDWalletCreate(int strength, TWString* _Nonnull passphrase); /// Creates an HDWallet from a valid BIP39 English mnemonic and a passphrase. -/// Null is returned on invalid mnemonic. Returned object needs to be deleted. +/// +/// \param mnemonic non-null Valid BIP39 mnemonic +/// \param passphrase non-null passphrase +/// \note Null is returned on invalid mnemonic +/// \note Returned object needs to be deleted with \TWHDWalletDelete +/// \return Nullable TWHDWallet TW_EXPORT_STATIC_METHOD -struct TWHDWallet *_Nullable TWHDWalletCreateWithMnemonic(TWString *_Nonnull mnemonic, TWString *_Nonnull passphrase); +struct TWHDWallet* _Nullable TWHDWalletCreateWithMnemonic(TWString* _Nonnull mnemonic, TWString* _Nonnull passphrase); /// Creates an HDWallet from a BIP39 mnemonic, a passphrase and validation flag. -/// Null is returned on invalid mnemonic. Returned object needs to be deleted. +/// +/// \param mnemonic non-null Valid BIP39 mnemonic +/// \param passphrase non-null passphrase +/// \param check validation flag +/// \note Null is returned on invalid mnemonic +/// \note Returned object needs to be deleted with \TWHDWalletDelete +/// \return Nullable TWHDWallet TW_EXPORT_STATIC_METHOD -struct TWHDWallet *_Nullable TWHDWalletCreateWithMnemonicCheck(TWString *_Nonnull mnemonic, TWString *_Nonnull passphrase, bool check); +struct TWHDWallet* _Nullable TWHDWalletCreateWithMnemonicCheck(TWString* _Nonnull mnemonic, TWString* _Nonnull passphrase, bool check); /// Creates an HDWallet from entropy (corresponding to a mnemonic). -/// Null is returned on invalid input. Returned object needs to be deleted. +/// +/// \param entropy Non-null entropy data (corresponding to a mnemonic) +/// \param passphrase non-null passphrase +/// \note Null is returned on invalid input +/// \note Returned object needs to be deleted with \TWHDWalletDelete +/// \return Nullable TWHDWallet TW_EXPORT_STATIC_METHOD -struct TWHDWallet *_Nullable TWHDWalletCreateWithEntropy(TWData *_Nonnull entropy, TWString *_Nonnull passphrase); +struct TWHDWallet* _Nullable TWHDWalletCreateWithEntropy(TWData* _Nonnull entropy, TWString* _Nonnull passphrase); /// Deletes a wallet. +/// +/// \param wallet non-null TWHDWallet TW_EXPORT_METHOD -void TWHDWalletDelete(struct TWHDWallet *_Nonnull wallet); +void TWHDWalletDelete(struct TWHDWallet* _Nonnull wallet); /// Wallet seed. +/// +/// \param wallet non-null TWHDWallet +/// \return The wallet seed as a Non-null block of data. TW_EXPORT_PROPERTY -TWData *_Nonnull TWHDWalletSeed(struct TWHDWallet *_Nonnull wallet); +TWData* _Nonnull TWHDWalletSeed(struct TWHDWallet* _Nonnull wallet); -// Wallet Mnemonic +/// Wallet Mnemonic +/// +/// \param wallet non-null TWHDWallet +/// \return The wallet mnemonic as a non-null TWString TW_EXPORT_PROPERTY -TWString *_Nonnull TWHDWalletMnemonic(struct TWHDWallet *_Nonnull wallet); +TWString* _Nonnull TWHDWalletMnemonic(struct TWHDWallet* _Nonnull wallet); -// Wallet entropy +/// Wallet entropy +/// +/// \param wallet non-null TWHDWallet +/// \return The wallet entropy as a non-null block of data. TW_EXPORT_PROPERTY -TWData *_Nonnull TWHDWalletEntropy(struct TWHDWallet *_Nonnull wallet); +TWData* _Nonnull TWHDWalletEntropy(struct TWHDWallet* _Nonnull wallet); -/// Returns master key. Returned object needs to be deleted. +/// Returns master key. +/// +/// \param wallet non-null TWHDWallet +/// \param curve a curve +/// \note Returned object needs to be deleted with \TWPrivateKeyDelete +/// \return Non-null corresponding private key TW_EXPORT_METHOD -struct TWPrivateKey *_Nonnull TWHDWalletGetMasterKey(struct TWHDWallet *_Nonnull wallet, enum TWCurve curve); +struct TWPrivateKey* _Nonnull TWHDWalletGetMasterKey(struct TWHDWallet* _Nonnull wallet, enum TWCurve curve); -/// Generates the default private key for the specified coin. Returned object needs to be deleted. +/// Generates the default private key for the specified coin. +/// +/// \param wallet non-null TWHDWallet +/// \param coin a coin type +/// \note Returned object needs to be deleted with \TWPrivateKeyDelete +/// \return return the default private key for the specified coin TW_EXPORT_METHOD -struct TWPrivateKey *_Nonnull TWHDWalletGetKeyForCoin(struct TWHDWallet *_Nonnull wallet, enum TWCoinType coin); +struct TWPrivateKey* _Nonnull TWHDWalletGetKeyForCoin(struct TWHDWallet* _Nonnull wallet, enum TWCoinType coin); /// Generates the default address for the specified coin (without exposing intermediary private key). +/// +/// \param wallet non-null TWHDWallet +/// \param coin a coin type +/// \return return the default address for the specified coin as a non-null TWString +TW_EXPORT_METHOD +TWString* _Nonnull TWHDWalletGetAddressForCoin(struct TWHDWallet* _Nonnull wallet, enum TWCoinType coin); + +/// Generates the private key for the specified derivation path. +/// +/// \param wallet non-null TWHDWallet +/// \param coin a coin type +/// \param derivationPath a non-null derivation path +/// \note Returned object needs to be deleted with \TWPrivateKeyDelete +/// \return The private key for the specified derivation path/coin +TW_EXPORT_METHOD +struct TWPrivateKey* _Nonnull TWHDWalletGetKey(struct TWHDWallet* _Nonnull wallet, enum TWCoinType coin, TWString* _Nonnull derivationPath); + +/// Generates the private key for the specified derivation path and curve. +/// +/// \param wallet non-null TWHDWallet +/// \param curve a curve +/// \param derivationPath a non-null derivation path +/// \note Returned object needs to be deleted with \TWPrivateKeyDelete +/// \return The private key for the specified derivation path/curve +TW_EXPORT_METHOD +struct TWPrivateKey* _Nonnull TWHDWalletGetKeyByCurve(struct TWHDWallet* _Nonnull wallet, enum TWCurve curve, TWString* _Nonnull derivationPath); + +/// Shortcut method to generate private key with the specified account/change/address (bip44 standard). +/// +/// \see https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki +/// +/// \param wallet non-null TWHDWallet +/// \param coin a coin type +/// \param account valid bip44 account +/// \param change valid bip44 change +/// \param address valid bip44 address +/// \note Returned object needs to be deleted with \TWPrivateKeyDelete +/// \return The private key for the specified bip44 parameters +TW_EXPORT_METHOD +struct TWPrivateKey* _Nonnull TWHDWalletGetDerivedKey(struct TWHDWallet* _Nonnull wallet, enum TWCoinType coin, uint32_t account, uint32_t change, uint32_t address); + +/// Returns the extended private key (for default 0 account). +/// +/// \param wallet non-null TWHDWallet +/// \param purpose a purpose +/// \param coin a coin type +/// \param version hd version +/// \note Returned object needs to be deleted with \TWStringDelete +/// \return Extended private key as a non-null TWString +TW_EXPORT_METHOD +TWString* _Nonnull TWHDWalletGetExtendedPrivateKey(struct TWHDWallet* _Nonnull wallet, enum TWPurpose purpose, enum TWCoinType coin, enum TWHDVersion version); + +/// Returns the extended public key (for default 0 account). +/// +/// \param wallet non-null TWHDWallet +/// \param purpose a purpose +/// \param coin a coin type +/// \param version hd version +/// \note Returned object needs to be deleted with \TWStringDelete +/// \return Extended public key as a non-null TWString TW_EXPORT_METHOD -TWString *_Nonnull TWHDWalletGetAddressForCoin(struct TWHDWallet *_Nonnull wallet, enum TWCoinType coin); +TWString* _Nonnull TWHDWalletGetExtendedPublicKey(struct TWHDWallet* _Nonnull wallet, enum TWPurpose purpose, enum TWCoinType coin, enum TWHDVersion version); -/// Generates the private key for the specified derivation path. Returned object needs to be deleted. +/// Returns the extended private key, for custom account. +/// +/// \param wallet non-null TWHDWallet +/// \param purpose a purpose +/// \param coin a coin type +/// \param derivation a derivation +/// \param version an hd version +/// \param account valid bip44 account +/// \note Returned object needs to be deleted with \TWStringDelete +/// \return Extended private key as a non-null TWString TW_EXPORT_METHOD -struct TWPrivateKey *_Nonnull TWHDWalletGetKey(struct TWHDWallet *_Nonnull wallet, enum TWCoinType coin, TWString *_Nonnull derivationPath); +TWString* _Nonnull TWHDWalletGetExtendedPrivateKeyAccount(struct TWHDWallet* _Nonnull wallet, enum TWPurpose purpose, enum TWCoinType coin, enum TWDerivation derivation, enum TWHDVersion version, uint32_t account); -/// Shortcut method to generate private key with the specified account/change/address (bip44 standard). Returned object needs to be deleted. +/// Returns the extended public key, for custom account. /// -/// @see https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki +/// \param wallet non-null TWHDWallet +/// \param purpose a purpose +/// \param coin a coin type +/// \param derivation a derivation +/// \param version an hd version +/// \param account valid bip44 account +/// \note Returned object needs to be deleted with \TWStringDelete +/// \return Extended public key as a non-null TWString TW_EXPORT_METHOD -struct TWPrivateKey *_Nonnull TWHDWalletGetDerivedKey(struct TWHDWallet *_Nonnull wallet, enum TWCoinType coin, uint32_t account, uint32_t change, uint32_t address); +TWString* _Nonnull TWHDWalletGetExtendedPublicKeyAccount(struct TWHDWallet* _Nonnull wallet, enum TWPurpose purpose, enum TWCoinType coin, enum TWDerivation derivation, enum TWHDVersion version, uint32_t account); -/// Returns the extended private key. +/// Returns the extended private key (for default 0 account with derivation). +/// +/// \param wallet non-null TWHDWallet +/// \param purpose a purpose +/// \param coin a coin type +/// \param derivation a derivation +/// \param version an hd version +/// \note Returned object needs to be deleted with \TWStringDelete +/// \return Extended private key as a non-null TWString TW_EXPORT_METHOD -TWString *_Nonnull TWHDWalletGetExtendedPrivateKey(struct TWHDWallet *_Nonnull wallet, enum TWPurpose purpose, enum TWCoinType coin, enum TWHDVersion version); +TWString* _Nonnull TWHDWalletGetExtendedPrivateKeyDerivation(struct TWHDWallet* _Nonnull wallet, enum TWPurpose purpose, enum TWCoinType coin, enum TWDerivation derivation, enum TWHDVersion version); -/// Returns the exteded public key. Returned object needs to be deleted. +/// Returns the extended public key (for default 0 account with derivation). +/// +/// \param wallet non-null TWHDWallet +/// \param purpose a purpose +/// \param coin a coin type +/// \param derivation a derivation +/// \param version an hd version +/// \note Returned object needs to be deleted with \TWStringDelete +/// \return Extended public key as a non-null TWString TW_EXPORT_METHOD -TWString *_Nonnull TWHDWalletGetExtendedPublicKey(struct TWHDWallet *_Nonnull wallet, enum TWPurpose purpose, enum TWCoinType coin, enum TWHDVersion version); +TWString* _Nonnull TWHDWalletGetExtendedPublicKeyDerivation(struct TWHDWallet* _Nonnull wallet, enum TWPurpose purpose, enum TWCoinType coin, enum TWDerivation derivation, enum TWHDVersion version); -/// Computes the public key from an exteded public key representation. Returned object needs to be deleted. +/// Computes the public key from an extended public key representation. +/// +/// \param extended extended public key +/// \param coin a coin type +/// \param derivationPath a derivation path +/// \note Returned object needs to be deleted with \TWPublicKeyDelete +/// \return Nullable TWPublic key TW_EXPORT_STATIC_METHOD -struct TWPublicKey *_Nullable TWHDWalletGetPublicKeyFromExtended(TWString *_Nonnull extended, enum TWCoinType coin, TWString *_Nonnull derivationPath); +struct TWPublicKey* _Nullable TWHDWalletGetPublicKeyFromExtended(TWString* _Nonnull extended, enum TWCoinType coin, TWString* _Nonnull derivationPath); TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWHash.h b/include/TrustWalletCore/TWHash.h index 280dc5f9b7f..ccdb7d0fb9e 100644 --- a/include/TrustWalletCore/TWHash.h +++ b/include/TrustWalletCore/TWHash.h @@ -11,6 +11,7 @@ TW_EXTERN_C_BEGIN +/// Hash functions TW_EXPORT_STRUCT struct TWHash { uint8_t unused; // C doesn't allow zero-sized struct @@ -22,63 +23,128 @@ static const size_t TWHashSHA512Length = 64; static const size_t TWHashRipemdLength = 20; /// Computes the SHA1 of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed SHA1 block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashSHA1(TWData *_Nonnull data); +/// Computes the SHA256 of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed SHA256 block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashSHA256(TWData *_Nonnull data); +/// Computes the SHA512 of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed SHA512 block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashSHA512(TWData *_Nonnull data); +/// Computes the SHA512_256 of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed SHA512_256 block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashSHA512_256(TWData *_Nonnull data); +/// Computes the Keccak256 of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed Keccak256 block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashKeccak256(TWData *_Nonnull data); +/// Computes the Keccak512 of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed Keccak512 block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashKeccak512(TWData *_Nonnull data); +/// Computes the SHA3_256 of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed SHA3_256 block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashSHA3_256(TWData *_Nonnull data); +/// Computes the SHA3_512 of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed SHA3_512 block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashSHA3_512(TWData *_Nonnull data); +/// Computes the RIPEMD of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed RIPEMD block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashRIPEMD(TWData *_Nonnull data); +/// Computes the Blake256 of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed Blake256 block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashBlake256(TWData *_Nonnull data); +/// Computes the Blake2b of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed Blake2b block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashBlake2b(TWData *_Nonnull data, size_t size); +/// Computes the Groestl512 of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed Groestl512 block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashGroestl512(TWData *_Nonnull data); -TW_EXPORT_STATIC_METHOD -TWData *_Nonnull TWHashXXHash64(TWData *_Nonnull data, uint64_t seed); - -TW_EXPORT_STATIC_METHOD -TWData *_Nonnull TWHashTwoXXHash64Concat(TWData *_Nonnull data); - +/// Computes the SHA256D of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed SHA256D block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashSHA256SHA256(TWData *_Nonnull data); +/// Computes the SHA256RIPEMD of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed SHA256RIPEMD block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashSHA256RIPEMD(TWData *_Nonnull data); +/// Computes the SHA3_256RIPEMD of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed SHA3_256RIPEMD block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashSHA3_256RIPEMD(TWData *_Nonnull data); +/// Computes the Blake256D of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed Blake256D block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashBlake256Blake256(TWData *_Nonnull data); +/// Computes the Blake256RIPEMD of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed Blake256RIPEMD block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashBlake256RIPEMD(TWData *_Nonnull data); +/// Computes the Groestl512D of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed Groestl512D block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashGroestl512Groestl512(TWData *_Nonnull data); diff --git a/include/TrustWalletCore/TWMnemonic.h b/include/TrustWalletCore/TWMnemonic.h index b8b60d46158..f74b6ad164f 100644 --- a/include/TrustWalletCore/TWMnemonic.h +++ b/include/TrustWalletCore/TWMnemonic.h @@ -11,18 +11,28 @@ TW_EXTERN_C_BEGIN +/// Mnemonic validate / lookup functions TW_EXPORT_STRUCT struct TWMnemonic; /// Determines whether a BIP39 English mnemonic phrase is valid. +/// +/// \param mnemonic Non-null BIP39 english mnemonic +/// \return true if the mnemonic is valid, false otherwise TW_EXPORT_STATIC_METHOD bool TWMnemonicIsValid(TWString *_Nonnull mnemonic); -/// Determines whether word is a valid BIP39 English menemonic word. +/// Determines whether word is a valid BIP39 English mnemonic word. +/// +/// \param word Non-null BIP39 English mnemonic word +/// \return true if the word is a valid BIP39 English mnemonic word, false otherwise TW_EXPORT_STATIC_METHOD bool TWMnemonicIsValidWord(TWString *_Nonnull word); /// Return BIP39 English words that match the given prefix. A single string is returned, with space-separated list of words. +/// +/// \param prefix Non-null string prefix +/// \return Single non-null string, space-separated list of words containing BIP39 words that match the given prefix. TW_EXPORT_STATIC_METHOD TWString* _Nonnull TWMnemonicSuggest(TWString *_Nonnull prefix); diff --git a/include/TrustWalletCore/TWNEARAccount.h b/include/TrustWalletCore/TWNEARAccount.h index 66b4c8ee3ec..73e7662fe34 100644 --- a/include/TrustWalletCore/TWNEARAccount.h +++ b/include/TrustWalletCore/TWNEARAccount.h @@ -15,13 +15,24 @@ TW_EXTERN_C_BEGIN TW_EXPORT_CLASS struct TWNEARAccount; +/// Create a NEAR Account +/// +/// \param string Account name +/// \note Account should be deleted by calling \TWNEARAccountDelete +/// \return Pointer to a nullable NEAR Account. TW_EXPORT_STATIC_METHOD struct TWNEARAccount *_Nullable TWNEARAccountCreateWithString(TWString *_Nonnull string); +/// Delete the given Near Account +/// +/// \param account Pointer to a non-null NEAR Account TW_EXPORT_METHOD void TWNEARAccountDelete(struct TWNEARAccount *_Nonnull account); /// Returns the user friendly string representation. +/// +/// \param account Pointer to a non-null NEAR Account +/// \return Non-null string account description TW_EXPORT_PROPERTY TWString *_Nonnull TWNEARAccountDescription(struct TWNEARAccount *_Nonnull account); diff --git a/include/TrustWalletCore/TWNervosAddress.h b/include/TrustWalletCore/TWNervosAddress.h new file mode 100644 index 00000000000..b12e663aec1 --- /dev/null +++ b/include/TrustWalletCore/TWNervosAddress.h @@ -0,0 +1,71 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "TWBase.h" +#include "TWData.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +/// Represents a Nervos address. +TW_EXPORT_CLASS +struct TWNervosAddress; + +/// Compares two addresses for equality. +/// +/// \param lhs The first address to compare. +/// \param rhs The second address to compare. +/// \return bool indicating the addresses are equal. +TW_EXPORT_STATIC_METHOD +bool TWNervosAddressEqual(struct TWNervosAddress *_Nonnull lhs, struct TWNervosAddress *_Nonnull rhs); + +/// Determines if the string is a valid Nervos address. +/// +/// \param string string to validate. +/// \return bool indicating if the address is valid. +TW_EXPORT_STATIC_METHOD +bool TWNervosAddressIsValidString(TWString *_Nonnull string); + +/// Initializes an address from a sring representaion. +/// +/// \param string Bech32 string to initialize the address from. +/// \return TWNervosAddress pointer or nullptr if string is invalid. +TW_EXPORT_STATIC_METHOD +struct TWNervosAddress *_Nullable TWNervosAddressCreateWithString(TWString *_Nonnull string); + +/// Deletes a Nervos address. +/// +/// \param address Address to delete. +TW_EXPORT_METHOD +void TWNervosAddressDelete(struct TWNervosAddress *_Nonnull address); + +/// Returns the address string representation. +/// +/// \param address Address to get the string representation of. +TW_EXPORT_PROPERTY +TWString *_Nonnull TWNervosAddressDescription(struct TWNervosAddress *_Nonnull address); + +/// Returns the Code hash +/// +/// \param address Address to get the keyhash data of. +TW_EXPORT_PROPERTY +TWData *_Nonnull TWNervosAddressCodeHash(struct TWNervosAddress *_Nonnull address); + +/// Returns the address hash type +/// +/// \param address Address to get the hash type of. +TW_EXPORT_PROPERTY +TWString *_Nonnull TWNervosAddressHashType(struct TWNervosAddress *_Nonnull address); + +/// Returns the address args data. +/// +/// \param address Address to get the args data of. +TW_EXPORT_PROPERTY +TWData *_Nonnull TWNervosAddressArgs(struct TWNervosAddress *_Nonnull address); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWPBKDF2.h b/include/TrustWalletCore/TWPBKDF2.h new file mode 100644 index 00000000000..a7edcc65380 --- /dev/null +++ b/include/TrustWalletCore/TWPBKDF2.h @@ -0,0 +1,38 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "TWBase.h" +#include "TWData.h" + +TW_EXTERN_C_BEGIN + +/// Password-Based Key Derivation Function 2 +TW_EXPORT_STRUCT +struct TWPBKDF2; + +/// Derives a key from a password and a salt using PBKDF2 + Sha256. +/// +/// \param password is the master password from which a derived key is generated +/// \param salt is a sequence of bits, known as a cryptographic salt +/// \param iterations is the number of iterations desired +/// \param dkLen is the desired bit-length of the derived key +/// \return the derived key data. +TW_EXPORT_STATIC_METHOD +TWData *_Nullable TWPBKDF2HmacSha256(TWData *_Nonnull password, TWData *_Nonnull salt, uint32_t iterations, uint32_t dkLen); + +/// Derives a key from a password and a salt using PBKDF2 + Sha512. +/// +/// \param password is the master password from which a derived key is generated +/// \param salt is a sequence of bits, known as a cryptographic salt +/// \param iterations is the number of iterations desired +/// \param dkLen is the desired bit-length of the derived key +/// \return the derived key data. +TW_EXPORT_STATIC_METHOD +TWData *_Nullable TWPBKDF2HmacSha512(TWData *_Nonnull password, TWData *_Nonnull salt, uint32_t iterations, uint32_t dkLen); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWPrivateKey.h b/include/TrustWalletCore/TWPrivateKey.h index 8400050e654..ee4cf3fc7e2 100644 --- a/include/TrustWalletCore/TWPrivateKey.h +++ b/include/TrustWalletCore/TWPrivateKey.h @@ -13,68 +13,132 @@ TW_EXTERN_C_BEGIN +/// Represents a private key. TW_EXPORT_CLASS struct TWPrivateKey; static const size_t TWPrivateKeySize = 32; +/// Create a random private key +/// +/// \note Should be deleted with \TWPrivateKeyDelete +/// \return Non-null Private key TW_EXPORT_STATIC_METHOD -struct TWPrivateKey *_Nonnull TWPrivateKeyCreate(void); +struct TWPrivateKey* _Nonnull TWPrivateKeyCreate(void); +/// Create a private key with the given block of data +/// +/// \param data a block of data +/// \note Should be deleted with \TWPrivateKeyDelete +/// \return Nullable pointer to Private Key TW_EXPORT_STATIC_METHOD -struct TWPrivateKey *_Nullable TWPrivateKeyCreateWithData(TWData *_Nonnull data); +struct TWPrivateKey* _Nullable TWPrivateKeyCreateWithData(TWData* _Nonnull data); +/// Deep copy a given private key +/// +/// \param key Non-null private key to be copied +/// \note Should be deleted with \TWPrivateKeyDelete +/// \return Deep copy, Nullable pointer to Private key TW_EXPORT_STATIC_METHOD -struct TWPrivateKey *_Nullable TWPrivateKeyCreateCopy(struct TWPrivateKey *_Nonnull key); +struct TWPrivateKey* _Nullable TWPrivateKeyCreateCopy(struct TWPrivateKey* _Nonnull key); +/// Delete the given private key +/// +/// \param pk Non-null pointer to private key TW_EXPORT_METHOD -void TWPrivateKeyDelete(struct TWPrivateKey *_Nonnull pk); +void TWPrivateKeyDelete(struct TWPrivateKey* _Nonnull pk); +/// Determines if the given private key is valid or not. +/// +/// \param data block of data (private key bytes) +/// \param curve Eliptic curve of the private key +/// \return true if the private key is valid, false otherwise TW_EXPORT_STATIC_METHOD -bool TWPrivateKeyIsValid(TWData *_Nonnull data, enum TWCurve curve); +bool TWPrivateKeyIsValid(TWData* _Nonnull data, enum TWCurve curve); +/// Convert the given private key to raw-bytes block of data +/// +/// \param pk Non-null pointer to the private key +/// \return Non-null block of data (raw bytes) of the given private key TW_EXPORT_PROPERTY -TWData *_Nonnull TWPrivateKeyData(struct TWPrivateKey *_Nonnull pk); +TWData* _Nonnull TWPrivateKeyData(struct TWPrivateKey* _Nonnull pk); -/// Returns the public key associated with this private key. +/// Returns the Secp256k1 public key associated with the given private key +/// +/// \param pk Non-null pointer to the private key +/// \param compressed if the given private key is compressed or not +/// \return Non-null pointer to the corresponding public key TW_EXPORT_METHOD -struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeySecp256k1(struct TWPrivateKey *_Nonnull pk, bool compressed); +struct TWPublicKey* _Nonnull TWPrivateKeyGetPublicKeySecp256k1(struct TWPrivateKey* _Nonnull pk, bool compressed); -/// Returns the public key associated with this private key. +/// Returns the Nist256p1 public key associated with the given private key +/// +/// \param pk Non-null pointer to the private key +/// \return Non-null pointer to the corresponding public key TW_EXPORT_METHOD -struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeyNist256p1(struct TWPrivateKey *_Nonnull pk); +struct TWPublicKey* _Nonnull TWPrivateKeyGetPublicKeyNist256p1(struct TWPrivateKey* _Nonnull pk); -/// Returns the public key associated with this private key. +/// Returns the Ed25519 public key associated with the given private key +/// +/// \param pk Non-null pointer to the private key +/// \return Non-null pointer to the corresponding public key TW_EXPORT_METHOD -struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeyEd25519(struct TWPrivateKey *_Nonnull pk); +struct TWPublicKey* _Nonnull TWPrivateKeyGetPublicKeyEd25519(struct TWPrivateKey* _Nonnull pk); -/// Returns the public key associated with this private key. +/// Returns the Ed25519Blake2b public key associated with the given private key +/// +/// \param pk Non-null pointer to the private key +/// \return Non-null pointer to the corresponding public key TW_EXPORT_METHOD -struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeyEd25519Blake2b(struct TWPrivateKey *_Nonnull pk); +struct TWPublicKey* _Nonnull TWPrivateKeyGetPublicKeyEd25519Blake2b(struct TWPrivateKey* _Nonnull pk); -/// Returns the public key associated with this private key. +/// Returns the Ed25519Cardano public key associated with the given private key +/// +/// \param pk Non-null pointer to the private key +/// \return Non-null pointer to the corresponding public key TW_EXPORT_METHOD -struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeyEd25519Extended(struct TWPrivateKey *_Nonnull pk); +struct TWPublicKey* _Nonnull TWPrivateKeyGetPublicKeyEd25519Cardano(struct TWPrivateKey* _Nonnull pk); -/// Returns the public key associated with this private key. +/// Returns the Curve25519 public key associated with the given private key +/// +/// \param pk Non-null pointer to the private key +/// \return Non-null pointer to the corresponding public key TW_EXPORT_METHOD -struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeyCurve25519(struct TWPrivateKey *_Nonnull pk); +struct TWPublicKey* _Nonnull TWPrivateKeyGetPublicKeyCurve25519(struct TWPrivateKey* _Nonnull pk); /// Computes an EC Diffie-Hellman secret in constant time /// Supported curves: secp256k1 +/// +/// \param pk Non-null pointer to a Private key +/// \param publicKey Non-null pointer to the corresponding public key +/// \param curve Eliptic curve +/// \return The corresponding shared key as a non-null block of data TW_EXPORT_METHOD -TWData *_Nullable TWPrivateKeyGetSharedKey(const struct TWPrivateKey *_Nonnull pk, const struct TWPublicKey *_Nonnull publicKey, enum TWCurve curve); +TWData* _Nullable TWPrivateKeyGetSharedKey(const struct TWPrivateKey* _Nonnull pk, const struct TWPublicKey* _Nonnull publicKey, enum TWCurve curve); /// Signs a digest using ECDSA and given curve. +/// +/// \param pk Non-null pointer to a Private key +/// \param digest Non-null digest block of data +/// \param curve Eliptic curve +/// \return Signature as a Non-null block of data TW_EXPORT_METHOD -TWData *_Nullable TWPrivateKeySign(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull digest, enum TWCurve curve); +TWData* _Nullable TWPrivateKeySign(struct TWPrivateKey* _Nonnull pk, TWData* _Nonnull digest, enum TWCurve curve); -/// Signs a digest using ECDSA and given curve. The result is encoded with DER. +/// Signs a digest using ECDSA. The result is encoded with DER. +/// +/// \param pk Non-null pointer to a Private key +/// \param digest Non-null digest block of data +/// \return Signature as a Non-null block of data TW_EXPORT_METHOD -TWData *_Nullable TWPrivateKeySignAsDER(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull digest, enum TWCurve curve); +TWData* _Nullable TWPrivateKeySignAsDER(struct TWPrivateKey* _Nonnull pk, TWData* _Nonnull digest); -/// Signs a digest using ECDSA and given curve, returns schnoor signature. +/// Signs a digest using ECDSA and Zilliqa schnorr signature scheme. +/// +/// \param pk Non-null pointer to a Private key +/// \param message Non-null message +/// \return Signature as a Non-null block of data TW_EXPORT_METHOD -TWData *_Nullable TWPrivateKeySignSchnorr(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull message, enum TWCurve curve); +TWData* _Nullable TWPrivateKeySignZilliqaSchnorr(struct TWPrivateKey* _Nonnull pk, TWData* _Nonnull message); TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWPrivateKeyType.h b/include/TrustWalletCore/TWPrivateKeyType.h new file mode 100644 index 00000000000..868b5d2dfea --- /dev/null +++ b/include/TrustWalletCore/TWPrivateKeyType.h @@ -0,0 +1,20 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "TWBase.h" + +TW_EXTERN_C_BEGIN + +/// Private key types, the vast majority of chains use the default, 32-byte key. +TW_EXPORT_ENUM(uint32_t) +enum TWPrivateKeyType { + TWPrivateKeyTypeDefault = 0, // 32 bytes long + TWPrivateKeyTypeCardano = 1, // 2 extended keys plus chainCode, 96 bytes long, used by Cardano +}; + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWPublicKey.h b/include/TrustWalletCore/TWPublicKey.h index 220672d2a1e..d9164855382 100644 --- a/include/TrustWalletCore/TWPublicKey.h +++ b/include/TrustWalletCore/TWPublicKey.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -16,42 +16,108 @@ TW_EXTERN_C_BEGIN static const size_t TWPublicKeyCompressedSize = 33; static const size_t TWPublicKeyUncompressedSize = 65; +/// Represents a public key. TW_EXPORT_CLASS struct TWPublicKey; +/// Create a public key from a block of data +/// +/// \param data Non-null block of data representing the public key +/// \param type type of the public key +/// \note Should be deleted with \TWPublicKeyDelete +/// \return Nullable pointer to the public key TW_EXPORT_STATIC_METHOD struct TWPublicKey *_Nullable TWPublicKeyCreateWithData(TWData *_Nonnull data, enum TWPublicKeyType type); +/// Delete the given public key +/// +/// \param pk Non-null pointer to a public key TW_EXPORT_METHOD void TWPublicKeyDelete(struct TWPublicKey *_Nonnull pk); +/// Determines if the given public key is valid or not +/// +/// \param data Non-null block of data representing the public key +/// \param type type of the public key +/// \return true if the block of data is a valid public key, false otherwise TW_EXPORT_STATIC_METHOD bool TWPublicKeyIsValid(TWData *_Nonnull data, enum TWPublicKeyType type); +/// Determines if the given public key is compressed or not +/// +/// \param pk Non-null pointer to a public key +/// \return true if the public key is compressed, false otherwise TW_EXPORT_PROPERTY bool TWPublicKeyIsCompressed(struct TWPublicKey *_Nonnull pk); +/// Give the compressed public key of the given non-compressed public key +/// +/// \param from Non-null pointer to a non-compressed public key +/// \return Non-null pointer to the corresponding compressed public-key TW_EXPORT_PROPERTY struct TWPublicKey *_Nonnull TWPublicKeyCompressed(struct TWPublicKey *_Nonnull from); +/// Give the non-compressed public key of a corresponding compressed public key +/// +/// \param from Non-null pointer to the corresponding compressed public key +/// \return Non-null pointer to the corresponding non-compressed public key TW_EXPORT_PROPERTY struct TWPublicKey *_Nonnull TWPublicKeyUncompressed(struct TWPublicKey *_Nonnull from); +/// Gives the raw data of a given public-key +/// +/// \param pk Non-null pointer to a public key +/// \return Non-null pointer to the raw block of data of the given public key TW_EXPORT_PROPERTY TWData *_Nonnull TWPublicKeyData(struct TWPublicKey *_Nonnull pk); +/// Verify the validity of a signature and a message using the given public key +/// +/// \param pk Non-null pointer to a public key +/// \param signature Non-null pointer to a block of data corresponding to the signature +/// \param message Non-null pointer to a block of data corresponding to the message +/// \return true if the signature and the message belongs to the given public key, false otherwise TW_EXPORT_METHOD bool TWPublicKeyVerify(struct TWPublicKey *_Nonnull pk, TWData *_Nonnull signature, TWData *_Nonnull message); +/// Verify the validity as DER of a signature and a message using the given public key +/// +/// \param pk Non-null pointer to a public key +/// \param signature Non-null pointer to a block of data corresponding to the signature +/// \param message Non-null pointer to a block of data corresponding to the message +/// \return true if the signature and the message belongs to the given public key, false otherwise TW_EXPORT_METHOD -bool TWPublicKeyVerifySchnorr(struct TWPublicKey *_Nonnull pk, TWData *_Nonnull signature, TWData *_Nonnull message); +bool TWPublicKeyVerifyAsDER(struct TWPublicKey *_Nonnull pk, TWData *_Nonnull signature, TWData *_Nonnull message); + +/// Verify a Zilliqa schnorr signature with a signature and message. +/// +/// \param pk Non-null pointer to a public key +/// \param signature Non-null pointer to a block of data corresponding to the signature +/// \param message Non-null pointer to a block of data corresponding to the message +/// \return true if the signature and the message belongs to the given public key, false otherwise +TW_EXPORT_METHOD +bool TWPublicKeyVerifyZilliqaSchnorr(struct TWPublicKey *_Nonnull pk, TWData *_Nonnull signature, TWData *_Nonnull message); +/// Give the public key type (eliptic) of a given public key +/// +/// \param publicKey Non-null pointer to a public key +/// \return The public key type of the given public key (eliptic) TW_EXPORT_PROPERTY enum TWPublicKeyType TWPublicKeyKeyType(struct TWPublicKey *_Nonnull publicKey); +/// Get the public key description from a given public key +/// +/// \param publicKey Non-null pointer to a public key +/// \return Non-null pointer to a string representing the description of the public key TW_EXPORT_PROPERTY TWString *_Nonnull TWPublicKeyDescription(struct TWPublicKey *_Nonnull publicKey); +/// Try to get a public key from a given signature and a message +/// +/// \param signature Non-null pointer to a block of data corresponding to the signature +/// \param message Non-null pointer to a block of data corresponding to the message +/// \return Null pointer if the public key can't be recover from the given signature and message, +/// pointer to the public key otherwise TW_EXPORT_STATIC_METHOD struct TWPublicKey *_Nullable TWPublicKeyRecover(TWData *_Nonnull signature, TWData *_Nonnull message); diff --git a/include/TrustWalletCore/TWPublicKeyType.h b/include/TrustWalletCore/TWPublicKeyType.h index e16727754df..e9d0e53b347 100644 --- a/include/TrustWalletCore/TWPublicKeyType.h +++ b/include/TrustWalletCore/TWPublicKeyType.h @@ -20,7 +20,7 @@ enum TWPublicKeyType { TWPublicKeyTypeED25519 = 4, TWPublicKeyTypeED25519Blake2b = 5, TWPublicKeyTypeCURVE25519 = 6, - TWPublicKeyTypeED25519Extended = 7, + TWPublicKeyTypeED25519Cardano = 7, }; TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWPurpose.h b/include/TrustWalletCore/TWPurpose.h index ad4d9206cc7..509b813bbec 100644 --- a/include/TrustWalletCore/TWPurpose.h +++ b/include/TrustWalletCore/TWPurpose.h @@ -12,9 +12,9 @@ TW_EXTERN_C_BEGIN /// HD wallet purpose /// -/// See https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki -/// See https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki -/// See https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki +/// \see https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki +/// \see https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki +/// \see https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki TW_EXPORT_ENUM(uint32_t) enum TWPurpose { TWPurposeBIP44 = 44, diff --git a/include/TrustWalletCore/TWRippleXAddress.h b/include/TrustWalletCore/TWRippleXAddress.h index 8b41daf1a82..401b885e554 100644 --- a/include/TrustWalletCore/TWRippleXAddress.h +++ b/include/TrustWalletCore/TWRippleXAddress.h @@ -20,29 +20,54 @@ TW_EXPORT_CLASS struct TWRippleXAddress; /// Compares two addresses for equality. +/// +/// \param lhs left non-null pointer to a Ripple Address +/// \param rhs right non-null pointer to a Ripple Address +/// \return true if both address are equal, false otherwise TW_EXPORT_STATIC_METHOD bool TWRippleXAddressEqual(struct TWRippleXAddress *_Nonnull lhs, struct TWRippleXAddress *_Nonnull rhs); /// Determines if the string is a valid Ripple address. +/// +/// \param string Non-null pointer to a string that represent the Ripple Address to be checked +/// \return true if the given address is a valid Ripple address, false otherwise TW_EXPORT_STATIC_METHOD bool TWRippleXAddressIsValidString(TWString *_Nonnull string); -/// Creates an address from a string representaion. +/// Creates an address from a string representation. +/// +/// \param string Non-null pointer to a string that should be a valid ripple address +/// \note Should be deleted with \TWRippleXAddressDelete +/// \return Null pointer if the given string is an invalid ripple address, pointer to a Ripple address otherwise TW_EXPORT_STATIC_METHOD struct TWRippleXAddress *_Nullable TWRippleXAddressCreateWithString(TWString *_Nonnull string); /// Creates an address from a public key and destination tag. +/// +/// \param publicKey Non-null pointer to a public key +/// \param tag valid ripple destination tag (1-10) +/// \note Should be deleted with \TWRippleXAddressDelete +/// \return Non-null pointer to a Ripple Address TW_EXPORT_STATIC_METHOD struct TWRippleXAddress *_Nonnull TWRippleXAddressCreateWithPublicKey(struct TWPublicKey *_Nonnull publicKey, uint32_t tag); +/// Delete the given ripple address +/// +/// \param address Non-null pointer to a Ripple Address TW_EXPORT_METHOD void TWRippleXAddressDelete(struct TWRippleXAddress *_Nonnull address); /// Returns the address string representation. +/// +/// \param address Non-null pointer to a Ripple Address +/// \return Non-null pointer to the ripple address string representation TW_EXPORT_PROPERTY TWString *_Nonnull TWRippleXAddressDescription(struct TWRippleXAddress *_Nonnull address); /// Returns the destination tag. +/// +/// \param address Non-null pointer to a Ripple Address +/// \return The destination tag of the given Ripple Address (1-10) TW_EXPORT_PROPERTY uint32_t TWRippleXAddressTag(struct TWRippleXAddress *_Nonnull address); diff --git a/include/TrustWalletCore/TWSS58AddressType.h b/include/TrustWalletCore/TWSS58AddressType.h index 13ca9769e39..b6bf7e50927 100644 --- a/include/TrustWalletCore/TWSS58AddressType.h +++ b/include/TrustWalletCore/TWSS58AddressType.h @@ -1,5 +1,5 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -11,9 +11,9 @@ TW_EXTERN_C_BEGIN -/// Substrate based chains Address Type +/// Substrate based chains Address Type /// -/// - See Also: https://github.com/paritytech/substrate/wiki/External-Address-Format-(SS58)#address-type +/// \see https://github.com/paritytech/substrate/wiki/External-Address-Format-(SS58)#address-type TW_EXPORT_ENUM(uint8_t) enum TWSS58AddressType { TWSS58AddressTypePolkadot = 0, diff --git a/include/TrustWalletCore/TWSegwitAddress.h b/include/TrustWalletCore/TWSegwitAddress.h index 57fbd3fed5a..3e106375527 100644 --- a/include/TrustWalletCore/TWSegwitAddress.h +++ b/include/TrustWalletCore/TWSegwitAddress.h @@ -20,33 +20,69 @@ TW_EXPORT_CLASS struct TWSegwitAddress; /// Compares two addresses for equality. +/// +/// \param lhs left non-null pointer to a Bech32 Address +/// \param rhs right non-null pointer to a Bech32 Address +/// \return true if both address are equal, false otherwise TW_EXPORT_STATIC_METHOD bool TWSegwitAddressEqual(struct TWSegwitAddress *_Nonnull lhs, struct TWSegwitAddress *_Nonnull rhs); /// Determines if the string is a valid Bech32 address. +/// +/// \param string Non-null pointer to a Bech32 address as a string +/// \return true if the string is a valid Bech32 address, false otherwise. TW_EXPORT_STATIC_METHOD bool TWSegwitAddressIsValidString(TWString *_Nonnull string); -/// Creates an address from a string representaion. +/// Creates an address from a string representation. +/// +/// \param string Non-null pointer to a Bech32 address as a string +/// \note should be deleted with \TWSegwitAddressDelete +/// \return Pointer to a Bech32 address if the string is a valid Bech32 address, null pointer otherwise TW_EXPORT_STATIC_METHOD struct TWSegwitAddress *_Nullable TWSegwitAddressCreateWithString(TWString *_Nonnull string); -/// Creates an address from a public key. +/// Creates a segwit-version-0 address from a public key and HRP prefix. +/// Taproot (v>=1) is not supported by this method. +/// +/// \param hrp HRP of the utxo coin targeted +/// \param publicKey Non-null pointer to the public key of the targeted coin +/// \note should be deleted with \TWSegwitAddressDelete +/// \return Non-null pointer to the corresponding Segwit address TW_EXPORT_STATIC_METHOD struct TWSegwitAddress *_Nonnull TWSegwitAddressCreateWithPublicKey(enum TWHRP hrp, struct TWPublicKey *_Nonnull publicKey); +/// Delete the given Segwit address +/// +/// \param address Non-null pointer to a Segwit address TW_EXPORT_METHOD void TWSegwitAddressDelete(struct TWSegwitAddress *_Nonnull address); /// Returns the address string representation. +/// +/// \param address Non-null pointer to a Segwit Address +/// \return Non-null pointer to the segwit address string representation TW_EXPORT_PROPERTY TWString *_Nonnull TWSegwitAddressDescription(struct TWSegwitAddress *_Nonnull address); /// Returns the human-readable part. +/// +/// \param address Non-null pointer to a Segwit Address +/// \return the HRP part of the given address TW_EXPORT_PROPERTY enum TWHRP TWSegwitAddressHRP(struct TWSegwitAddress *_Nonnull address); +/// Returns the human-readable part. +/// +/// \param address Non-null pointer to a Segwit Address +/// \return returns the witness version of the given segwit address +TW_EXPORT_PROPERTY +int TWSegwitAddressWitnessVersion(struct TWSegwitAddress *_Nonnull address); + /// Returns the witness program +/// +/// \param address Non-null pointer to a Segwit Address +/// \return returns the witness data of the given segwit address as a non-null pointer block of data TW_EXPORT_PROPERTY TWData *_Nonnull TWSegwitAddressWitnessProgram(struct TWSegwitAddress *_Nonnull address); diff --git a/include/TrustWalletCore/TWSolanaAddress.h b/include/TrustWalletCore/TWSolanaAddress.h index 76f7fe863e7..b8a372155e6 100644 --- a/include/TrustWalletCore/TWSolanaAddress.h +++ b/include/TrustWalletCore/TWSolanaAddress.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -11,21 +11,36 @@ TW_EXTERN_C_BEGIN +/// Solana address helper functions TW_EXPORT_CLASS struct TWSolanaAddress; -/// Creates an address from a string representaion. +/// Creates an address from a string representation. +/// +/// \param string Non-null pointer to a solana address string +/// \note Should be deleted with \TWSolanaAddressDelete +/// \return Non-null pointer to a Solana address data structure TW_EXPORT_STATIC_METHOD struct TWSolanaAddress* _Nullable TWSolanaAddressCreateWithString(TWString* _Nonnull string); +/// Delete the given Solana address +/// +/// \param address Non-null pointer to a Solana Address TW_EXPORT_METHOD void TWSolanaAddressDelete(struct TWSolanaAddress* _Nonnull address); /// Derive default token address for token +/// +/// \param address Non-null pointer to a Solana Address +/// \param tokenMintAddress Non-null pointer to a token mint address as a string +/// \return Null pointer if the Default token address for a token is not found, valid pointer otherwise TW_EXPORT_METHOD TWString* _Nullable TWSolanaAddressDefaultTokenAddress(struct TWSolanaAddress* _Nonnull address, TWString* _Nonnull tokenMintAddress); /// Returns the address string representation. +/// +/// \param address Non-null pointer to a Solana Address +/// \return Non-null pointer to the Solana address string representation TW_EXPORT_PROPERTY TWString *_Nonnull TWSolanaAddressDescription(struct TWSolanaAddress *_Nonnull address); diff --git a/include/TrustWalletCore/TWStellarMemoType.h b/include/TrustWalletCore/TWStellarMemoType.h index 45f9c629206..ef3f42702e4 100644 --- a/include/TrustWalletCore/TWStellarMemoType.h +++ b/include/TrustWalletCore/TWStellarMemoType.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -10,6 +10,7 @@ TW_EXTERN_C_BEGIN +/// Stellar memo type. TW_EXPORT_ENUM(uint32_t) enum TWStellarMemoType { TWStellarMemoTypeNone = 0, diff --git a/include/TrustWalletCore/TWStellarPassphrase.h b/include/TrustWalletCore/TWStellarPassphrase.h index 630adf3ef83..4854a4d3977 100644 --- a/include/TrustWalletCore/TWStellarPassphrase.h +++ b/include/TrustWalletCore/TWStellarPassphrase.h @@ -10,6 +10,7 @@ TW_EXTERN_C_BEGIN +/// Stellar network passphrase string. TW_EXPORT_ENUM() enum TWStellarPassphrase { TWStellarPassphraseStellar /* "Public Global Stellar Network ; September 2015" */, diff --git a/include/TrustWalletCore/TWStellarVersionByte.h b/include/TrustWalletCore/TWStellarVersionByte.h index b694c80b6bb..4439d1a555a 100644 --- a/include/TrustWalletCore/TWStellarVersionByte.h +++ b/include/TrustWalletCore/TWStellarVersionByte.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -10,11 +10,12 @@ TW_EXTERN_C_BEGIN +/// Stellar address version byte. TW_EXPORT_ENUM(uint16_t) enum TWStellarVersionByte { - TWStellarVersionByteAccountID = 0x30, // G - TWStellarVersionByteSeed = 0xc0, // S - TWStellarVersionBytePreAuthTX = 0xc8, // T + TWStellarVersionByteAccountID = 0x30, // G + TWStellarVersionByteSeed = 0xc0, // S + TWStellarVersionBytePreAuthTX = 0xc8, // T TWStellarVersionByteSHA256Hash = 0x118, // X }; diff --git a/include/TrustWalletCore/TWStoredKey.h b/include/TrustWalletCore/TWStoredKey.h index c5b6484d3cb..02bd0ae4dad 100644 --- a/include/TrustWalletCore/TWStoredKey.h +++ b/include/TrustWalletCore/TWStoredKey.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,8 +9,10 @@ #include "TWBase.h" #include "TWCoinType.h" #include "TWData.h" +#include "TWDerivation.h" #include "TWHDWallet.h" #include "TWPrivateKey.h" +#include "TWStoredKeyEncryptionLevel.h" #include "TWString.h" TW_EXTERN_C_BEGIN @@ -19,90 +21,240 @@ TW_EXTERN_C_BEGIN TW_EXPORT_CLASS struct TWStoredKey; -/// Loads a key from a file. Returned object needs to be deleted. +/// Loads a key from a file. +/// +/// \param path filepath to the key as a non-null string +/// \note Returned object needs to be deleted with \TWStoredKeyDelete +/// \return Nullptr if the key can't be load, the stored key otherwise TW_EXPORT_STATIC_METHOD struct TWStoredKey* _Nullable TWStoredKeyLoad(TWString* _Nonnull path); -/// Imports a private key. Returned object needs to be deleted. +/// Imports a private key. +/// +/// \param privateKey Non-null Block of data private key +/// \param name The name of the stored key to import as a non-null string +/// \param password Non-null block of data, password of the stored key +/// \param coin the coin type +/// \note Returned object needs to be deleted with \TWStoredKeyDelete +/// \return Nullptr if the key can't be imported, the stored key otherwise TW_EXPORT_STATIC_METHOD struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKey(TWData* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin); -/// Imports an HD wallet. Returned object needs to be deleted. +/// Imports an HD wallet. +/// +/// \param mnemonic Non-null bip39 mnemonic +/// \param name The name of the stored key to import as a non-null string +/// \param password Non-null block of data, password of the stored key +/// \param coin the coin type +/// \note Returned object needs to be deleted with \TWStoredKeyDelete +/// \return Nullptr if the key can't be imported, the stored key otherwise TW_EXPORT_STATIC_METHOD struct TWStoredKey* _Nullable TWStoredKeyImportHDWallet(TWString* _Nonnull mnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin); -/// Imports a key from JSON. Returned object needs to be deleted. +/// Imports a key from JSON. +/// +/// \param json Json stored key import format as a non-null block of data +/// \note Returned object needs to be deleted with \TWStoredKeyDelete +/// \return Nullptr if the key can't be imported, the stored key otherwise TW_EXPORT_STATIC_METHOD struct TWStoredKey* _Nullable TWStoredKeyImportJSON(TWData* _Nonnull json); -/// Creates a new key. Returned object needs to be deleted. +/// Creates a new key, with given encryption strength level. Returned object needs to be deleted. +/// +/// \param name The name of the key to be stored +/// \param password Non-null block of data, password of the stored key +/// \param encryptionLevel The level of encryption, see \TWStoredKeyEncryptionLevel +/// \note Returned object needs to be deleted with \TWStoredKeyDelete +/// \return The stored key as a non-null pointer TW_EXPORT_STATIC_METHOD -struct TWStoredKey* _Nonnull TWStoredKeyCreate(TWString* _Nonnull name, TWData* _Nonnull password); +struct TWStoredKey* _Nonnull TWStoredKeyCreateLevel(TWString* _Nonnull name, TWData* _Nonnull password, enum TWStoredKeyEncryptionLevel encryptionLevel); +/// Creates a new key. +/// +/// \deprecated use TWStoredKeyCreateLevel. +/// \param name The name of the key to be stored +/// \param password Non-null block of data, password of the stored key +/// \note Returned object needs to be deleted with \TWStoredKeyDelete +/// \return The stored key as a non-null pointer +TW_EXPORT_STATIC_METHOD struct TWStoredKey* _Nonnull TWStoredKeyCreate(TWString* _Nonnull name, TWData* _Nonnull password); + +/// Delete a stored key +/// +/// \param key The key to be deleted TW_EXPORT_METHOD void TWStoredKeyDelete(struct TWStoredKey* _Nonnull key); -/// Stored key uniqie identifier. Returned object needs to be deleted. +/// Stored key unique identifier. +/// +/// \param key Non-null pointer to a stored key +/// \note Returned object needs to be deleted with \TWStringDelete +/// \return The stored key unique identifier if it's found, null pointer otherwise. TW_EXPORT_PROPERTY TWString* _Nullable TWStoredKeyIdentifier(struct TWStoredKey* _Nonnull key); -/// Stored key namer. Returned object needs to be deleted. +/// Stored key namer. +/// +/// \param key Non-null pointer to a stored key +/// \note Returned object needs to be deleted with \TWStringDelete +/// \return The stored key name as a non-null string pointer. TW_EXPORT_PROPERTY TWString* _Nonnull TWStoredKeyName(struct TWStoredKey* _Nonnull key); /// Whether this key is a mnemonic phrase for a HD wallet. +/// +/// \param key Non-null pointer to a stored key +/// \return true if the given stored key is a mnemonic, false otherwise TW_EXPORT_PROPERTY bool TWStoredKeyIsMnemonic(struct TWStoredKey* _Nonnull key); /// The number of accounts. +/// +/// \param key Non-null pointer to a stored key +/// \return the number of accounts associated to the given stored key TW_EXPORT_PROPERTY size_t TWStoredKeyAccountCount(struct TWStoredKey* _Nonnull key); -/// Returns the account at a given index. Returned object needs to be deleted. +/// Returns the account at a given index. +/// +/// \param key Non-null pointer to a stored key +/// \param index the account index to be retrieved +/// \note Returned object needs to be deleted with \TWAccountDelete +/// \return Null pointer if the associated account is not found, pointer to the account otherwise. TW_EXPORT_METHOD struct TWAccount* _Nullable TWStoredKeyAccount(struct TWStoredKey* _Nonnull key, size_t index); -/// Returns the account for a specific coin, creating it if necessary. Returned object needs to be deleted. +/// Returns the account for a specific coin, creating it if necessary. +/// +/// \param key Non-null pointer to a stored key +/// \param coin The coin type +/// \param wallet The associated HD wallet, can be null. +/// \note Returned object needs to be deleted with \TWAccountDelete +/// \return Null pointer if the associated account is not found/not created, pointer to the account otherwise. TW_EXPORT_METHOD struct TWAccount* _Nullable TWStoredKeyAccountForCoin(struct TWStoredKey* _Nonnull key, enum TWCoinType coin, struct TWHDWallet* _Nullable wallet); +/// Returns the account for a specific coin + derivation, creating it if necessary. +/// +/// \param key Non-null pointer to a stored key +/// \param coin The coin type +/// \param derivation The derivation for the given coin +/// \param wallet the associated HD wallet, can be null. +/// \note Returned object needs to be deleted with \TWAccountDelete +/// \return Null pointer if the associated account is not found/not created, pointer to the account otherwise. +TW_EXPORT_METHOD +struct TWAccount* _Nullable TWStoredKeyAccountForCoinDerivation(struct TWStoredKey* _Nonnull key, enum TWCoinType coin, enum TWDerivation derivation, struct TWHDWallet* _Nullable wallet); + +/// Adds a new account, using given derivation (usually TWDerivationDefault) +/// and derivation path (usually matches path from derivation, but custom possible). +/// +/// \param key Non-null pointer to a stored key +/// \param address Non-null pointer to the address of the coin for this account +/// \param coin coin type +/// \param derivation derivation of the given coin type +/// \param derivationPath HD bip44 derivation path of the given coin +/// \param publicKey Non-null public key of the given coin/address +/// \param extendedPublicKey Non-null extended public key of the given coin/address +TW_EXPORT_METHOD +void TWStoredKeyAddAccountDerivation(struct TWStoredKey* _Nonnull key, TWString* _Nonnull address, enum TWCoinType coin, enum TWDerivation derivation, TWString* _Nonnull derivationPath, TWString* _Nonnull publicKey, TWString* _Nonnull extendedPublicKey); + +/// Adds a new account, using given derivation path. +/// +/// \deprecated Use TWStoredKeyAddAccountDerivation (with TWDerivationDefault) instead. +/// \param key Non-null pointer to a stored key +/// \param address Non-null pointer to the address of the coin for this account +/// \param coin coin type +/// \param derivationPath HD bip44 derivation path of the given coin +/// \param publicKey Non-null public key of the given coin/address +/// \param extendedPublicKey Non-null extended public key of the given coin/address +TW_EXPORT_METHOD +void TWStoredKeyAddAccount(struct TWStoredKey* _Nonnull key, TWString* _Nonnull address, enum TWCoinType coin, TWString* _Nonnull derivationPath, TWString* _Nonnull publicKey, TWString* _Nonnull extendedPublicKey); + /// Remove the account for a specific coin +/// +/// \param key Non-null pointer to a stored key +/// \param coin Account coin type to be removed TW_EXPORT_METHOD void TWStoredKeyRemoveAccountForCoin(struct TWStoredKey* _Nonnull key, enum TWCoinType coin); -/// Adds a new account. +/// Remove the account for a specific coin with the given derivation. +/// +/// \param key Non-null pointer to a stored key +/// \param coin Account coin type to be removed +/// \param derivation The derivation of the given coin type +TW_EXPORT_METHOD +void TWStoredKeyRemoveAccountForCoinDerivation(struct TWStoredKey* _Nonnull key, enum TWCoinType coin, enum TWDerivation derivation); + +/// Remove the account for a specific coin with the given derivation path. +/// +/// \param key Non-null pointer to a stored key +/// \param coin Account coin type to be removed +/// \param derivationPath The derivation path (bip44) of the given coin type TW_EXPORT_METHOD -void TWStoredKeyAddAccount(struct TWStoredKey* _Nonnull key, TWString* _Nonnull address, enum TWCoinType coin, TWString* _Nonnull derivationPath, TWString* _Nonnull extetndedPublicKey); +void TWStoredKeyRemoveAccountForCoinDerivationPath(struct TWStoredKey* _Nonnull key, enum TWCoinType coin, TWString* _Nonnull derivationPath); /// Saves the key to a file. +/// +/// \param key Non-null pointer to a stored key +/// \param path Non-null string filepath where the key will be saved +/// \return true if the key was successfully stored in the given filepath file, false otherwise TW_EXPORT_METHOD bool TWStoredKeyStore(struct TWStoredKey* _Nonnull key, TWString* _Nonnull path); /// Decrypts the private key. +/// +/// \param key Non-null pointer to a stored key +/// \param password Non-null block of data, password of the stored key +/// \return Decrypted private key as a block of data if success, null pointer otherwise TW_EXPORT_METHOD TWData* _Nullable TWStoredKeyDecryptPrivateKey(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password); /// Decrypts the mnemonic phrase. +/// +/// \param key Non-null pointer to a stored key +/// \param password Non-null block of data, password of the stored key +/// \return Bip39 decrypted mnemonic if success, null pointer otherwise TW_EXPORT_METHOD TWString* _Nullable TWStoredKeyDecryptMnemonic(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password); /// Returns the private key for a specific coin. Returned object needs to be deleted. +/// +/// \param key Non-null pointer to a stored key +/// \param coin Account coin type to be queried +/// \note Returned object needs to be deleted with \TWPrivateKeyDelete +/// \return Null pointer on failure, pointer to the private key otherwise TW_EXPORT_METHOD struct TWPrivateKey* _Nullable TWStoredKeyPrivateKey(struct TWStoredKey* _Nonnull key, enum TWCoinType coin, TWData* _Nonnull password); -/// Dercrypts and returns the HD Wallet for mnemonic phrase keys. Returned object needs to be deleted. +/// Decrypts and returns the HD Wallet for mnemonic phrase keys. Returned object needs to be deleted. +/// +/// \param key Non-null pointer to a stored key +/// \param password Non-null block of data, password of the stored key +/// \note Returned object needs to be deleted with \TWHDWalletDelete +/// \return Null pointer on failure, pointer to the HDWallet otherwise TW_EXPORT_METHOD struct TWHDWallet* _Nullable TWStoredKeyWallet(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password); /// Exports the key as JSON +/// +/// \param key Non-null pointer to a stored key +/// \return Null pointer on failure, pointer to a block of data containing the json otherwise TW_EXPORT_METHOD TWData* _Nullable TWStoredKeyExportJSON(struct TWStoredKey* _Nonnull key); /// Fills in empty and invalid addresses. -/// /// This method needs the encryption password to re-derive addresses from private keys. -/// @returns `false` if the password is incorrect. +/// +/// \param key Non-null pointer to a stored key +/// \param password Non-null block of data, password of the stored key +/// \return `false` if the password is incorrect, true otherwise. TW_EXPORT_METHOD bool TWStoredKeyFixAddresses(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password); +/// Retrieve stored key encoding parameters, as JSON string. +/// +/// \param key Non-null pointer to a stored key +/// \return Null pointer on failure, encoding parameter as a json string otherwise. +TW_EXPORT_PROPERTY +TWString* _Nullable TWStoredKeyEncryptionParameters(struct TWStoredKey* _Nonnull key); + TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWStoredKeyEncryptionLevel.h b/include/TrustWalletCore/TWStoredKeyEncryptionLevel.h new file mode 100644 index 00000000000..6b0b79476ea --- /dev/null +++ b/include/TrustWalletCore/TWStoredKeyEncryptionLevel.h @@ -0,0 +1,26 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "TWBase.h" + +TW_EXTERN_C_BEGIN + +/// Preset encryption parameter with different security strength, for key store +TW_EXPORT_ENUM(uint32_t) +enum TWStoredKeyEncryptionLevel { + /// Default, which is one of the below values, determined by the implementation. + TWStoredKeyEncryptionLevelDefault = 0, + /// Minimal sufficient level of encryption strength (scrypt 4096) + TWStoredKeyEncryptionLevelMinimal = 1, + /// Weak encryption strength (scrypt 16k) + TWStoredKeyEncryptionLevelWeak = 2, + /// Standard level of encryption strength (scrypt 262k) + TWStoredKeyEncryptionLevelStandard = 3, +}; + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWString.h b/include/TrustWalletCore/TWString.h index 64c2eb5eafc..4ad92566126 100644 --- a/include/TrustWalletCore/TWString.h +++ b/include/TrustWalletCore/TWString.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -14,41 +14,60 @@ typedef const void TWData; /// Defines a resizable string. /// -/// The implementantion of these methods should be language-specific to minimize translation overhead. For instance it -/// should be a `jstring` for Java and an `NSString` for Swift. -/// Create allocates memory, the delete call should be called at the end to release memory. +/// The implementantion of these methods should be language-specific to minimize translation +/// overhead. For instance it should be a `jstring` for Java and an `NSString` for Swift. Create +/// allocates memory, the delete call should be called at the end to release memory. typedef const void TWString; -/// Creates a string from a null-terminated UTF8 byte array. It must be deleted at the end. +/// Creates a TWString from a null-terminated UTF8 byte array. It must be deleted at the end. +/// +/// \param bytes a null-terminated UTF8 byte array. TW_EXTERN -TWString *_Nonnull TWStringCreateWithUTF8Bytes(const char *_Nonnull bytes); +TWString* _Nonnull TWStringCreateWithUTF8Bytes(const char* _Nonnull bytes) TW_VISIBILITY_DEFAULT; -/// Creates a string from a raw byte array and size. +/// Creates a string from a raw byte array and size. It must be deleted at the end. +/// +/// \param bytes a raw byte array. +/// \param size the size of the byte array. TW_EXTERN -TWString *_Nonnull TWStringCreateWithRawBytes(const uint8_t *_Nonnull bytes, size_t size); +TWString* _Nonnull TWStringCreateWithRawBytes(const uint8_t* _Nonnull bytes, size_t size) TW_VISIBILITY_DEFAULT; /// Creates a hexadecimal string from a block of data. It must be deleted at the end. +/// +/// \param data a block of data. TW_EXTERN -TWString *_Nonnull TWStringCreateWithHexData(TWData *_Nonnull data); +TWString* _Nonnull TWStringCreateWithHexData(TWData* _Nonnull data) TW_VISIBILITY_DEFAULT; /// Returns the string size in bytes. +/// +/// \param string a TWString pointer. TW_EXTERN -size_t TWStringSize(TWString *_Nonnull string); +size_t TWStringSize(TWString* _Nonnull string) TW_VISIBILITY_DEFAULT; /// Returns the byte at the provided index. +/// +/// \param string a TWString pointer. +/// \param index the index of the byte. TW_EXTERN -char TWStringGet(TWString *_Nonnull string, size_t index); +char TWStringGet(TWString* _Nonnull string, size_t index) TW_VISIBILITY_DEFAULT; /// Returns the raw pointer to the string's UTF8 bytes (null-terminated). +/// +/// \param string a TWString pointer. TW_EXTERN -const char *_Nonnull TWStringUTF8Bytes(TWString *_Nonnull string); +const char* _Nonnull TWStringUTF8Bytes(TWString* _Nonnull string) TW_VISIBILITY_DEFAULT; -/// Deletes a string created with a `TWStringCreate*` method. After delete it must not be used (can segfault)! +/// Deletes a string created with a `TWStringCreate*` method and frees the memory. +/// +/// \param string a TWString pointer. TW_EXTERN -void TWStringDelete(TWString *_Nonnull string); +void TWStringDelete(TWString* _Nonnull string) TW_VISIBILITY_DEFAULT; /// Determines whether two string blocks are equal. +/// +/// \param lhs a TWString pointer. +/// \param rhs another TWString pointer. TW_EXTERN -bool TWStringEqual(TWString *_Nonnull lhs, TWString *_Nonnull rhs); +bool TWStringEqual(TWString* _Nonnull lhs, TWString* _Nonnull rhs) TW_VISIBILITY_DEFAULT; TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWEthereumFee.h b/include/TrustWalletCore/TWTHORChainSwap.h similarity index 60% rename from include/TrustWalletCore/TWEthereumFee.h rename to include/TrustWalletCore/TWTHORChainSwap.h index f57f0e68c98..27708cec96f 100644 --- a/include/TrustWalletCore/TWEthereumFee.h +++ b/include/TrustWalletCore/TWTHORChainSwap.h @@ -3,22 +3,23 @@ // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. - #pragma once #include "TWBase.h" -#include "TWString.h" #include "TWData.h" - -// Wrapper class for Ethereum EIP 1559 Fee suggestion +#include "TWString.h" TW_EXTERN_C_BEGIN +/// THORChain swap functions TW_EXPORT_STRUCT -struct TWEthereumFee; +struct TWTHORChainSwap; -/// Suggest baseFee and maxPriorityFee based on eth_feeHistory RPC call response +/// Builds a THORChainSwap transaction input. +/// +/// \param input The serialized data of SwapInput. +/// \return The serialized data of SwapOutput. TW_EXPORT_STATIC_METHOD -TWString* _Nullable TWEthereumFeeSuggest(TWString* _Nonnull feeHistory); +TWData *_Nonnull TWTHORChainSwapBuildSwap(TWData *_Nonnull input); TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWTransactionCompiler.h b/include/TrustWalletCore/TWTransactionCompiler.h new file mode 100644 index 00000000000..93e68c65cce --- /dev/null +++ b/include/TrustWalletCore/TWTransactionCompiler.h @@ -0,0 +1,63 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "TWBase.h" +#include "TWCoinType.h" +#include "TWData.h" +#include "TWDataVector.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +/// Non-core transaction utility methods, like building a transaction using an external signature. +TW_EXPORT_STRUCT +struct TWTransactionCompiler; + +/// Builds a coin-specific SigningInput (proto object) from a simple transaction. +/// +/// \param coin coin type. +/// \param from sender of the transaction. +/// \param to receiver of the transaction. +/// \param amount transaction amount in string +/// \param asset optional asset name, like "BNB" +/// \param memo optional memo +/// \param chainId optional chainId to override default +/// \return serialized data of the SigningInput proto object. +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWTransactionCompilerBuildInput(enum TWCoinType coinType, TWString* _Nonnull from, + TWString* _Nonnull to, TWString* _Nonnull amount, + TWString* _Nonnull asset, TWString* _Nonnull memo, + TWString* _Nonnull chainId); + +/// Obtains pre-signing hashes of a transaction. +/// +/// We provide a default `PreSigningOutput` in TransactionCompiler.proto. +/// For some special coins, such as bitcoin, we will create a custom `PreSigningOutput` object in its proto file. +/// \param coin coin type. +/// \param txInputData The serialized data of a signing input +/// \return serialized data of a proto object `PreSigningOutput` includes hash. +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWTransactionCompilerPreImageHashes(enum TWCoinType coinType, + TWData* _Nonnull txInputData); + +/// Compiles a complete transation with one or more external signatures. +/// +/// Puts together from transaction input and provided public keys and signatures. The signatures must match the hashes +/// returned by TWTransactionCompilerPreImageHashes, in the same order. The publicKeyHash attached +/// to the hashes enable identifying the private key needed for signing the hash. +/// \param coin coin type. +/// \param txInputData The serialized data of a signing input. +/// \param signatures signatures to compile, using TWDataVector. +/// \param publicKeys public keys for signers to match private keys, using TWDataVector. +/// \return serialized data of a proto object `SigningOutput`. +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWTransactionCompilerCompileWithSignatures( + enum TWCoinType coinType, TWData* _Nonnull txInputData, + const struct TWDataVector* _Nonnull signatures, const struct TWDataVector* _Nonnull publicKeys); + +TW_EXTERN_C_END diff --git a/jni/java/wallet/core/java/AnySigner.java b/jni/java/wallet/core/java/AnySigner.java index 47f9b4fce9a..49c7770d3d1 100644 --- a/jni/java/wallet/core/java/AnySigner.java +++ b/jni/java/wallet/core/java/AnySigner.java @@ -6,13 +6,13 @@ package wallet.core.java; -import com.google.protobuf.Message; +import com.google.protobuf.MessageLite; import com.google.protobuf.Parser; import wallet.core.jni.CoinType; public class AnySigner { - public static T sign(Message input, CoinType coin, Parser parser) throws Exception { + public static T sign(MessageLite input, CoinType coin, Parser parser) throws Exception { byte[] data = input.toByteArray(); byte[] outputData = nativeSign(data, coin.value()); T output = parser.parseFrom(outputData); @@ -25,7 +25,7 @@ public static T sign(Message input, CoinType coin, Parser public static native boolean supportsJSON(int coin); - public static T plan(Message input, CoinType coin, Parser parser) throws Exception { + public static T plan(MessageLite input, CoinType coin, Parser parser) throws Exception { byte[] data = input.toByteArray(); byte[] outputData = nativePlan(data, coin.value()); T output = parser.parseFrom(outputData); diff --git a/protobuf-plugin/CMakeLists.txt b/protobuf-plugin/CMakeLists.txt index e636f4a2fb4..2d8c3a722fb 100644 --- a/protobuf-plugin/CMakeLists.txt +++ b/protobuf-plugin/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.2 FATAL_ERROR) project(TrustWalletCoreProtobufPlugin) set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14" CACHE STRING "Minimum OS X deployment version" FORCE) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) if ("$ENV{PREFIX}" STREQUAL "") diff --git a/protobuf-plugin/swift_typealias.cc b/protobuf-plugin/swift_typealias.cc index a5877616af2..57a169dea03 100644 --- a/protobuf-plugin/swift_typealias.cc +++ b/protobuf-plugin/swift_typealias.cc @@ -4,6 +4,8 @@ #include #include #include +#include +#include using namespace google::protobuf; @@ -26,31 +28,48 @@ class Generator : public compiler::CodeGenerator { "// file LICENSE at the root of the source code distribution tree.\n" "\n" ); + + std::vector names; + std::vector> aliases; + for (int i = 0; i < file->message_type_count(); i += 1) { - auto message = file->message_type(i); - auto parts = Generator::getParts(message->full_name()); + const auto* message = file->message_type(i); + names.emplace_back(message->full_name()); + } + + for (int i = 0; i < file->enum_type_count(); i += 1) { + const auto* enum_type = file->enum_type(i); + names.emplace_back(enum_type->full_name()); + } + + for (auto& name : names) { + auto parts = Generator::getParts(name); if (parts.size() < 3 || parts[0] != "TW") { - std::cerr << "Invalid proto name '" << message->full_name() << "'" << std::endl; + std::cerr << "Invalid proto name '" << name << "'" << std::endl; continue; } - std::string def = "public typealias "; + + std::string alias = ""; for (auto i = 0; i < parts.size(); i += 1) { if (i == 0 || i == 2) { continue; } - def += parts[i]; + alias += parts[i]; } - def += " = "; - + std::string type = ""; for (auto& part : parts) { - def += part + "_"; + type += part + "_"; } - def = def.substr(0, def.size() - 1); - def += ";\n"; - printer.Print(def.c_str()); + type = type.substr(0, type.size() - 1); + + aliases.emplace_back(std::make_tuple(alias, type)); } + for (auto& alias : aliases) { + std::string line = "public typealias " + std::get<0>(alias) + " = " + std::get<1>(alias) + "\n"; + printer.Print(line.c_str()); + } return true; } diff --git a/registry.json b/registry.json index 40f406e3051..d77105beebe 100644 --- a/registry.json +++ b/registry.json @@ -6,7 +6,26 @@ "symbol": "BTC", "decimals": 8, "blockchain": "Bitcoin", - "derivationPath": "m/84'/0'/0'/0/0", + "derivation": [ + { + "name": "segwit", + "path": "m/84'/0'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" + }, + { + "name": "legacy", + "path": "m/44'/0'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + }, + { + "name": "testnet", + "path": "m/84'/1'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "p2pkhPrefix": 0, @@ -14,8 +33,6 @@ "hrp": "bc", "publicKeyHasher": "sha256ripemd", "base58Hasher": "sha256d", - "xpub": "zpub", - "xprv": "zprv", "explorer": { "url": "https://blockchair.com", "txPath": "/bitcoin/transaction/", @@ -37,7 +54,19 @@ "symbol": "LTC", "decimals": 8, "blockchain": "Bitcoin", - "derivationPath": "m/84'/2'/0'/0/0", + "derivation": [ + { + "path": "m/84'/2'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" + }, + { + "name": "legacy", + "path": "m/44'/2'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "p2pkhPrefix": 48, @@ -45,8 +74,6 @@ "hrp": "ltc", "publicKeyHasher": "sha256ripemd", "base58Hasher": "sha256d", - "xpub": "zpub", - "xprv": "zprv", "explorer": { "url": "https://blockchair.com", "txPath": "/litecoin/transaction/", @@ -66,15 +93,19 @@ "symbol": "DOGE", "decimals": 8, "blockchain": "Bitcoin", - "derivationPath": "m/44'/3'/0'/0/0", + "derivation": [ + { + "path": "m/44'/3'/0'/0/0", + "xpub": "dgub", + "xprv": "dgpv" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "p2pkhPrefix": 30, "p2shPrefix": 22, "publicKeyHasher": "sha256ripemd", "base58Hasher": "sha256d", - "xpub": "dgub", - "xprv": "dgpv", "explorer": { "url": "https://blockchair.com", "txPath": "/dogecoin/transaction/", @@ -94,15 +125,19 @@ "symbol": "DASH", "decimals": 8, "blockchain": "Bitcoin", - "derivationPath": "m/44'/5'/0'/0/0", + "derivation": [ + { + "path": "m/44'/5'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "p2pkhPrefix": 76, "p2shPrefix": 16, "publicKeyHasher": "sha256ripemd", "base58Hasher": "sha256d", - "xpub": "xpub", - "xprv": "xprv", "explorer": { "url": "https://blockchair.com", "txPath": "/dash/transaction/", @@ -122,7 +157,13 @@ "symbol": "VIA", "decimals": 8, "blockchain": "Bitcoin", - "derivationPath": "m/84'/14'/0'/0/0", + "derivation": [ + { + "path": "m/84'/14'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "p2pkhPrefix": 71, @@ -130,8 +171,6 @@ "hrp": "via", "publicKeyHasher": "sha256ripemd", "base58Hasher": "sha256d", - "xpub": "zpub", - "xprv": "zprv", "explorer": { "url": "https://explorer.viacoin.org", "txPath": "/tx/", @@ -150,8 +189,14 @@ "coinId": 17, "symbol": "GRS", "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/84'/17'/0'/0/0", + "blockchain": "Groestlcoin", + "derivation": [ + { + "path": "m/84'/17'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "p2pkhPrefix": 36, @@ -159,8 +204,6 @@ "hrp": "grs", "publicKeyHasher": "sha256ripemd", "base58Hasher": "groestl512d", - "xpub": "zpub", - "xprv": "zprv", "explorer": { "url": "https://blockchair.com", "txPath": "/groestlcoin/transaction/", @@ -180,7 +223,13 @@ "symbol": "DGB", "decimals": 8, "blockchain": "Bitcoin", - "derivationPath": "m/84'/20'/0'/0/0", + "derivation": [ + { + "path": "m/84'/20'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "p2pkhPrefix": 30, @@ -188,8 +237,6 @@ "hrp": "dgb", "publicKeyHasher": "sha256ripemd", "base58Hasher": "sha256d", - "xpub": "zpub", - "xprv": "zprv", "explorer": { "url": "https://digiexplorer.info", "txPath": "/tx/", @@ -209,7 +256,13 @@ "symbol": "MONA", "decimals": 8, "blockchain": "Bitcoin", - "derivationPath": "m/44'/22'/0'/0/0", + "derivation": [ + { + "path": "m/44'/22'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "p2pkhPrefix": 50, @@ -217,8 +270,6 @@ "hrp": "mona", "publicKeyHasher": "sha256ripemd", "base58Hasher": "sha256d", - "xpub": "xpub", - "xprv": "xprv", "explorer": { "url": "https://blockbook.electrum-mona.org", "txPath": "/tx/", @@ -237,8 +288,14 @@ "coinId": 42, "symbol": "DCR", "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/44'/42'/0'/0/0", + "blockchain": "Decred", + "derivation": [ + { + "path": "m/44'/42'/0'/0/0", + "xpub": "dpub", + "xprv": "dprv" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "staticPrefix": 7, @@ -246,8 +303,6 @@ "p2shPrefix": 26, "publicKeyHasher": "blake256ripemd", "base58Hasher": "blake256d", - "xpub": "dpub", - "xprv": "dprv", "explorer": { "url": "https://dcrdata.decred.org", "txPath": "/tx/", @@ -267,9 +322,15 @@ "symbol": "ETH", "decimals": 18, "blockchain": "Ethereum", - "derivationPath": "m/44'/60'/0'/0/0", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", + "chainId": "1", + "addressHasher": "keccak256", "explorer": { "url": "https://etherscan.io", "txPath": "/tx/", @@ -291,9 +352,15 @@ "symbol": "ETC", "decimals": 18, "blockchain": "Ethereum", - "derivationPath": "m/44'/61'/0'/0/0", + "derivation": [ + { + "path": "m/44'/61'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", + "chainId": "61", + "addressHasher": "keccak256", "explorer": { "url": "https://blockscout.com/etc/mainnet", "txPath": "/tx/", @@ -313,7 +380,11 @@ "symbol": "ICX", "decimals": 18, "blockchain": "Icon", - "derivationPath": "m/44'/74'/0'/0/0", + "derivation": [ + { + "path": "m/44'/74'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", "explorer": { @@ -328,19 +399,56 @@ "documentation": "https://www.icondev.io/docs/icon-json-rpc-v3" } }, + { + "id": "aptos", + "name": "Aptos", + "displayName": "Aptos", + "coinId": 637, + "symbol": "APT", + "decimals": 8, + "chainId": "1", + "blockchain": "Aptos", + "derivation": [ + { + "path": "m/44'/637'/0'/0'/0'" + } + ], + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://explorer.aptoslabs.com", + "txPath": "/txn/", + "accountPath": "/account/", + "sampleTx": "91424546", + "sampleAccount": "0x6af7d07b8a541913dfa87a9f99628faa255c70241ef9ebd9b82a7e715ee13108" + }, + "info": { + "url": "https://aptoslabs.com/", + "source": "https://github.com/aptos-labs/aptos-core", + "rpc": "https://fullnode.devnet.aptoslabs.com", + "documentation": "https://fullnode.devnet.aptoslabs.com/v1/spec#/" + } + }, { "id": "cosmos", "name": "Cosmos", + "displayName": "Cosmos Hub", "coinId": 118, "symbol": "ATOM", "decimals": 6, + "chainId": "cosmoshub-4", "blockchain": "Cosmos", - "derivationPath": "m/44'/118'/0'/0/0", + "derivation": [ + { + "path": "m/44'/118'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "hrp": "cosmos", + "addressHasher": "sha256ripemd", "explorer": { - "url": "https://www.mintscan.io", + "url": "https://mintscan.io/cosmos", "txPath": "/txs/", "accountPath": "/account/" }, @@ -357,8 +465,14 @@ "coinId": 133, "symbol": "ZEC", "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/44'/133'/0'/0/0", + "blockchain": "Zcash", + "derivation": [ + { + "path": "m/44'/133'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "staticPrefix": 28, @@ -366,8 +480,6 @@ "p2shPrefix": 189, "publicKeyHasher": "sha256ripemd", "base58Hasher": "sha256d", - "xpub": "xpub", - "xprv": "xprv", "explorer": { "url": "https://blockchair.com/zcash", "txPath": "/transaction/", @@ -381,22 +493,25 @@ } }, { - "id": "zcoin", - "name": "Zcoin", - "displayName": "Firo", + "id": "firo", + "name": "Firo", "coinId": 136, "symbol": "FIRO", "decimals": 8, "blockchain": "Bitcoin", - "derivationPath": "m/44'/136'/0'/0/0", + "derivation": [ + { + "path": "m/44'/136'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "p2pkhPrefix": 82, "p2shPrefix": 7, "publicKeyHasher": "sha256ripemd", "base58Hasher": "sha256d", - "xpub": "xpub", - "xprv": "xprv", "explorer": { "url": "https://explorer.firo.org", "txPath": "/tx/", @@ -416,7 +531,11 @@ "symbol": "XRP", "decimals": 6, "blockchain": "Ripple", - "derivationPath": "m/44'/144'/0'/0/0", + "derivation": [ + { + "path": "m/44'/144'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "explorer": { @@ -440,7 +559,13 @@ "symbol": "BCH", "decimals": 8, "blockchain": "Bitcoin", - "derivationPath": "m/44'/145'/0'/0/0", + "derivation": [ + { + "path": "m/44'/145'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "p2pkhPrefix": 0, @@ -448,8 +573,6 @@ "hrp": "bitcoincash", "publicKeyHasher": "sha256ripemd", "base58Hasher": "sha256d", - "xpub": "xpub", - "xprv": "xprv", "explorer": { "url": "https://blockchair.com", "txPath": "/bitcoin-cash/transaction/", @@ -469,7 +592,11 @@ "symbol": "XLM", "decimals": 7, "blockchain": "Stellar", - "derivationPath": "m/44'/148'/0'", + "derivation": [ + { + "path": "m/44'/148'/0'" + } + ], "curve": "ed25519", "publicKeyType": "ed25519", "explorer": { @@ -491,7 +618,13 @@ "symbol": "BTG", "decimals": 8, "blockchain": "Bitcoin", - "derivationPath": "m/84'/156'/0'/0/0", + "derivation": [ + { + "path": "m/84'/156'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "p2pkhPrefix": 38, @@ -499,8 +632,6 @@ "hrp": "btg", "publicKeyHasher": "sha256ripemd", "base58Hasher": "sha256d", - "xpub": "zpub", - "xprv": "zprv", "explorer": { "url": "https://explorer.bitcoingold.org/insight", "txPath": "/tx/", @@ -517,10 +648,14 @@ "id": "nano", "name": "Nano", "coinId": 165, - "symbol": "NANO", + "symbol": "XNO", "decimals": 30, "blockchain": "Nano", - "derivationPath": "m/44'/165'/0'", + "derivation": [ + { + "path": "m/44'/165'/0'" + } + ], "curve": "ed25519Blake2bNano", "publicKeyType": "ed25519Blake2b", "url": "https://nano.org", @@ -545,15 +680,19 @@ "symbol": "RVN", "decimals": 8, "blockchain": "Bitcoin", - "derivationPath": "m/44'/175'/0'/0/0", + "derivation": [ + { + "path": "m/44'/175'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "p2pkhPrefix": 60, "p2shPrefix": 122, "publicKeyHasher": "sha256ripemd", "base58Hasher": "sha256d", - "xpub": "xpub", - "xprv": "xprv", "explorer": { "url": "https://ravencoin.network", "txPath": "/tx/", @@ -573,9 +712,15 @@ "symbol": "POA", "decimals": 18, "blockchain": "Ethereum", - "derivationPath": "m/44'/178'/0'/0/0", + "derivation": [ + { + "path": "m/44'/178'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", + "chainId": "99", + "addressHasher": "keccak256", "explorer": { "url": "https://blockscout.com", "txPath": "/poa/core/tx/", @@ -595,7 +740,11 @@ "symbol": "EOS", "decimals": 4, "blockchain": "EOS", - "derivationPath": "m/44'/194'/0'/0/0", + "derivation": [ + { + "path": "m/44'/194'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "explorer": { @@ -617,7 +766,11 @@ "symbol": "TRX", "decimals": 6, "blockchain": "Tron", - "derivationPath": "m/44'/195'/0'/0/0", + "derivation": [ + { + "path": "m/44'/195'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", "explorer": { @@ -639,7 +792,11 @@ "symbol": "FIO", "decimals": 9, "blockchain": "FIO", - "derivationPath": "m/44'/235'/0'/0/0", + "derivation": [ + { + "path": "m/44'/235'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "url": "https://fioprotocol.io/", @@ -662,7 +819,11 @@ "symbol": "NIM", "decimals": 5, "blockchain": "Nimiq", - "derivationPath": "m/44'/242'/0'/0'", + "derivation": [ + { + "path": "m/44'/242'/0'/0'" + } + ], "curve": "ed25519", "publicKeyType": "ed25519", "explorer": { @@ -684,7 +845,11 @@ "symbol": "ALGO", "decimals": 6, "blockchain": "Algorand", - "derivationPath": "m/44'/283'/0'/0'/0'", + "derivation": [ + { + "path": "m/44'/283'/0'/0'/0'" + } + ], "curve": "ed25519", "publicKeyType": "ed25519", "explorer": { @@ -708,7 +873,11 @@ "symbol": "IOTX", "decimals": 18, "blockchain": "IoTeX", - "derivationPath": "m/44'/304'/0'/0/0", + "derivation": [ + { + "path": "m/44'/304'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", "hrp": "io", @@ -724,6 +893,33 @@ "documentation": "https://docs.iotex.io/#api" } }, + { + "id": "nervos", + "name": "Nervos", + "coinId": 309, + "symbol": "CKB", + "decimals": 8, + "blockchain": "Nervos", + "derivation": [ + { + "path": "m/44'/309'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "ckb", + "explorer": { + "url": "https://explorer.nervos.org", + "txPath": "/transaction/", + "accountPath": "/address/" + }, + "info": { + "url": "https://nervos.org", + "source": "https://github.com/nervosnetwork/ckb", + "rpc": "https://mainnet.ckb.dev/rpc", + "documentation": "https://github.com/nervosnetwork/rfcs" + } + }, { "id": "zilliqa", "name": "Zilliqa", @@ -731,7 +927,11 @@ "symbol": "ZIL", "decimals": 12, "blockchain": "Zilliqa", - "derivationPath": "m/44'/313'/0'/0/0", + "derivation": [ + { + "path": "m/44'/313'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "hrp": "zil", @@ -750,23 +950,60 @@ { "id": "terra", "name": "Terra", + "displayName": "Terra Classic", "coinId": 330, + "symbol": "LUNC", + "decimals": 6, + "blockchain": "Cosmos", + "chainId": "columbus-5", + "derivation": [ + { + "path": "m/44'/330'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "terra", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://finder.terra.money/classic", + "txPath": "/tx/", + "accountPath": "/address/" + }, + "info": { + "url": "https://terra.money", + "source": "https://github.com/terra-project/core", + "rpc": "https://columbus-fcd.terra.dev", + "documentation": "https://docs.terra.money" + } + }, + { + "id": "terrav2", + "name": "TerraV2", + "displayName": "Terra", + "coinId": 10000330, "symbol": "LUNA", "decimals": 6, "blockchain": "Cosmos", - "derivationPath": "m/44'/330'/0'/0/0", + "derivation": [ + { + "path": "m/44'/330'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "hrp": "terra", + "chainId": "phoenix-1", + "addressHasher": "sha256ripemd", "explorer": { - "url": "https://finder.terra.money/tx", + "url": "https://finder.terra.money/mainnet", "txPath": "/tx/", "accountPath": "/address/" }, "info": { "url": "https://terra.money", "source": "https://github.com/terra-project/core", - "rpc": "https://rpc.terra.dev", + "rpc": "https://phoenix-lcd.terra.dev", "documentation": "https://docs.terra.money" } }, @@ -777,9 +1014,14 @@ "symbol": "DOT", "decimals": 10, "blockchain": "Polkadot", - "derivationPath": "m/44'/354'/0'/0'/0'", + "derivation": [ + { + "path": "m/44'/354'/0'/0'/0'" + } + ], "curve": "ed25519", "publicKeyType": "ed25519", + "addressHasher": "keccak256", "explorer": { "url": "https://polkadot.subscan.io", "txPath": "/extrinsic/", @@ -792,6 +1034,34 @@ "documentation": "https://polkadot.js.org/api/substrate/rpc.html" } }, + { + "id": "everscale", + "name": "Everscale", + "coinId": 396, + "symbol": "EVER", + "decimals": 9, + "blockchain": "Everscale", + "derivation": [ + { + "path": "m/44'/396'/0'/0/0" + } + ], + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://everscan.io", + "txPath": "/transactions/", + "accountPath": "/accounts/", + "sampleTx": "781238b2b0d15cd4cd2e2a0a142753750cd5e1b2c8b506fcede75a90e02f1268", + "sampleAccount": "0:d2bf59964a05dee84a0dd1ddc0ad83ba44d49719cf843d689dc8b726d0fb59d8" + }, + "info": { + "url": "https://everscale.network/", + "source": "https://github.com/tonlabs/evernode-ds", + "rpc": "https://evercloud.dev", + "documentation": "https://docs.everos.dev/evernode-platform/products/evercloud/get-started" + } + }, { "id": "near", "name": "NEAR", @@ -799,7 +1069,11 @@ "symbol": "NEAR", "decimals": 24, "blockchain": "NEAR", - "derivationPath": "m/44'/397'/0'", + "derivation": [ + { + "path": "m/44'/397'/0'" + } + ], "curve": "ed25519", "publicKeyType": "ed25519", "explorer": { @@ -821,7 +1095,11 @@ "symbol": "AION", "decimals": 18, "blockchain": "Aion", - "derivationPath": "m/44'/425'/0'/0'/0'", + "derivation": [ + { + "path": "m/44'/425'/0'/0'/0'" + } + ], "curve": "ed25519", "publicKeyType": "ed25519", "explorer": { @@ -842,10 +1120,15 @@ "coinId": 434, "symbol": "KSM", "decimals": 12, - "blockchain": "Polkadot", - "derivationPath": "m/44'/434'/0'/0'/0'", + "blockchain": "Kusama", + "derivation": [ + { + "path": "m/44'/434'/0'/0'/0'" + } + ], "curve": "ed25519", "publicKeyType": "ed25519", + "addressHasher": "keccak256", "explorer": { "url": "https://kusama.subscan.io", "txPath": "/extrinsic/", @@ -867,7 +1150,11 @@ "symbol": "AE", "decimals": 18, "blockchain": "Aeternity", - "derivationPath": "m/44'/457'/0'/0'/0'", + "derivation": [ + { + "path": "m/44'/457'/0'/0'/0'" + } + ], "curve": "ed25519", "publicKeyType": "ed25519", "explorer": { @@ -889,12 +1176,18 @@ "symbol": "KAVA", "decimals": 6, "blockchain": "Cosmos", - "derivationPath": "m/44'/459'/0'/0/0", + "chainId": "kava_2222-10", + "derivation": [ + { + "path": "m/44'/459'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "hrp": "kava", + "addressHasher": "sha256ripemd", "explorer": { - "url": "https://kava.mintscan.io", + "url": "https://mintscan.io/kava", "txPath": "/txs/", "accountPath": "/account/", "sampleTx": "2988DF83FCBFAA38179D583A96734CBD071541D6768221BB23111BC8136D5E6A", @@ -914,7 +1207,11 @@ "symbol": "FIL", "decimals": 18, "blockchain": "Filecoin", - "derivationPath": "m/44'/461'/0'/0/0", + "derivation": [ + { + "path": "m/44'/461'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", "explorer": { @@ -938,10 +1235,15 @@ "symbol": "BLZ", "decimals": 6, "blockchain": "Cosmos", - "derivationPath": "m/44'/483'/0'/0/0", + "derivation": [ + { + "path": "m/44'/483'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "hrp": "bluzelle", + "addressHasher": "sha256ripemd", "explorer": { "url": "https://bigdipper.net.bluzelle.com", "txPath": "/transactions/", @@ -963,10 +1265,16 @@ "coinId": 494, "decimals": 6, "blockchain": "Cosmos", - "derivationPath": "m/44'/494'/0'/0/0", + "chainId": "laozi-mainnet", + "derivation": [ + { + "path": "m/44'/494'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "hrp": "band", + "addressHasher": "sha256ripemd", "explorer": { "url": "https://scan-wenchang-testnet2.bandchain.org/", "txPath": "/tx/", @@ -988,7 +1296,11 @@ "symbol": "THETA", "decimals": 18, "blockchain": "Theta", - "derivationPath": "m/44'/500'/0'/0/0", + "derivation": [ + { + "path": "m/44'/500'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", "explorer": { @@ -1010,7 +1322,15 @@ "symbol": "SOL", "decimals": 9, "blockchain": "Solana", - "derivationPath": "m/44'/501'/0'", + "derivation": [ + { + "path": "m/44'/501'/0'" + }, + { + "name": "solana", + "path": "m/44'/501'/0'/0'" + } + ], "curve": "ed25519", "publicKeyType": "ed25519", "explorer": { @@ -1034,7 +1354,11 @@ "symbol": "eGLD", "decimals": 18, "blockchain": "ElrondNetwork", - "derivationPath": "m/44'/508'/0'/0'/0'", + "derivation": [ + { + "path": "m/44'/508'/0'/0'/0'" + } + ], "curve": "ed25519", "publicKeyType": "ed25519", "hrp": "erd", @@ -1053,15 +1377,20 @@ { "id": "binance", "name": "Binance", - "displayName": "BNB", + "displayName": "BNB Beacon Chain", "coinId": 714, "symbol": "BNB", "decimals": 8, "blockchain": "Binance", - "derivationPath": "m/44'/714'/0'/0/0", + "derivation": [ + { + "path": "m/44'/714'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "hrp": "bnb", + "chainId": "Binance-Chain-Tigris", "explorer": { "url": "https://explorer.binance.org", "txPath": "/tx/", @@ -1083,9 +1412,14 @@ "symbol": "VET", "decimals": 18, "blockchain": "Vechain", - "derivationPath": "m/44'/818'/0'/0/0", + "derivation": [ + { + "path": "m/44'/818'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", + "chainId": "74", "explorer": { "url": "https://explore.vechain.org", "txPath": "/transactions/", @@ -1105,11 +1439,17 @@ "symbol": "CLO", "decimals": 18, "blockchain": "Ethereum", - "derivationPath": "m/44'/820'/0'/0/0", + "derivation": [ + { + "path": "m/44'/820'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", + "chainId": "820", + "addressHasher": "keccak256", "explorer": { - "url": "https://explorer2.callisto.network", + "url": "https://explorer.callisto.network", "txPath": "/tx/", "accountPath": "/addr/" }, @@ -1127,7 +1467,11 @@ "symbol": "NEO", "decimals": 8, "blockchain": "NEO", - "derivationPath": "m/44'/888'/0'/0/0", + "derivation": [ + { + "path": "m/44'/888'/0'/0/0" + } + ], "curve": "nist256p1", "publicKeyType": "nist256p1", "explorer": { @@ -1151,9 +1495,15 @@ "symbol": "TOMO", "decimals": 18, "blockchain": "Ethereum", - "derivationPath": "m/44'/889'/0'/0/0", + "derivation": [ + { + "path": "m/44'/889'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", + "chainId": "88", + "addressHasher": "keccak256", "explorer": { "url": "https://tomoscan.io", "txPath": "/tx/", @@ -1173,9 +1523,15 @@ "symbol": "TT", "decimals": 18, "blockchain": "Ethereum", - "derivationPath": "m/44'/1001'/0'/0/0", + "derivation": [ + { + "path": "m/44'/1001'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", + "chainId": "108", + "addressHasher": "keccak256", "explorer": { "url": "https://scan.thundercore.com", "txPath": "/transactions/", @@ -1195,7 +1551,11 @@ "symbol": "ONE", "decimals": 18, "blockchain": "Harmony", - "derivationPath": "m/44'/1023'/0'/0/0", + "derivation": [ + { + "path": "m/44'/1023'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", "hrp": "one", @@ -1218,7 +1578,11 @@ "symbol": "ROSE", "decimals": 9, "blockchain": "OasisNetwork", - "derivationPath": "m/44'/474'/0'", + "derivation": [ + { + "path": "m/44'/474'/0'" + } + ], "curve": "ed25519", "publicKeyType": "ed25519", "hrp": "oasis", @@ -1243,7 +1607,11 @@ "symbol": "ONT", "decimals": 0, "blockchain": "Ontology", - "derivationPath": "m/44'/1024'/0'/0/0", + "derivation": [ + { + "path": "m/44'/1024'/0'/0/0" + } + ], "curve": "nist256p1", "publicKeyType": "nist256p1", "explorer": { @@ -1265,7 +1633,11 @@ "symbol": "XTZ", "decimals": 6, "blockchain": "Tezos", - "derivationPath": "m/44'/1729'/0'/0'", + "derivation": [ + { + "path": "m/44'/1729'/0'/0'" + } + ], "curve": "ed25519", "publicKeyType": "ed25519", "explorer": { @@ -1287,13 +1659,17 @@ "symbol": "ADA", "decimals": 6, "blockchain": "Cardano", - "derivationPath": "m/1852'/1815'/0'/0/0", - "curve": "ed25519Extended", - "publicKeyType": "ed25519Extended", + "derivation": [ + { + "path": "m/1852'/1815'/0'/0/0" + } + ], + "curve": "ed25519ExtendedCardano", + "publicKeyType": "ed25519Cardano", "hrp": "addr", "explorer": { - "url": "https://shelleyexplorer.cardano.org", - "txPath": "/tx/", + "url": "https://cardanoscan.io", + "txPath": "/transaction/", "accountPath": "/address/", "sampleTx": "b7a6c5cadab0f64bdc89c77ee4a351463aba5c33f2cef6bbd6542a74a90a3af3", "sampleAccount": "addr1s3xuxwfetyfe7q9u3rfn6je9stlvcgmj8rezd87qjjegdtxm3y3f2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59jz29g8j" @@ -1312,7 +1688,11 @@ "symbol": "KIN", "decimals": 5, "blockchain": "Stellar", - "derivationPath": "m/44'/2017'/0'", + "derivation": [ + { + "path": "m/44'/2017'/0'" + } + ], "curve": "ed25519", "publicKeyType": "ed25519", "explorer": { @@ -1335,7 +1715,13 @@ "symbol": "QTUM", "decimals": 8, "blockchain": "Bitcoin", - "derivationPath": "m/44'/2301'/0'/0/0", + "derivation": [ + { + "path": "m/44'/2301'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "p2pkhPrefix": 58, @@ -1343,8 +1729,6 @@ "hrp": "qc", "publicKeyHasher": "sha256ripemd", "base58Hasher": "sha256d", - "xpub": "xpub", - "xprv": "xprv", "explorer": { "url": "https://qtum.info", "txPath": "/tx/", @@ -1364,7 +1748,11 @@ "symbol": "NAS", "decimals": 18, "blockchain": "Nebulas", - "derivationPath": "m/44'/2718'/0'/0/0", + "derivation": [ + { + "path": "m/44'/2718'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", "explorer": { @@ -1386,9 +1774,15 @@ "symbol": "GO", "decimals": 18, "blockchain": "Ethereum", - "derivationPath": "m/44'/6060'/0'/0/0", + "derivation": [ + { + "path": "m/44'/6060'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", + "chainId": "60", + "addressHasher": "keccak256", "explorer": { "url": "https://explorer.gochain.io", "txPath": "/tx/", @@ -1408,7 +1802,11 @@ "symbol": "NULS", "decimals": 8, "blockchain": "NULS", - "derivationPath": "m/44'/8964'/0'/0/0", + "derivation": [ + { + "path": "m/44'/8964'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "explorer": { @@ -1430,8 +1828,14 @@ "coinId": 19167, "symbol": "FLUX", "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/44'/19167'/0'/0/0", + "blockchain": "Zcash", + "derivation": [ + { + "path": "m/44'/19167'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "staticPrefix": 28, @@ -1439,8 +1843,6 @@ "p2shPrefix": 189, "publicKeyHasher": "sha256ripemd", "base58Hasher": "sha256d", - "xpub": "xpub", - "xprv": "xprv", "explorer": { "url": "https://explorer.runonflux.io", "txPath": "/tx/", @@ -1460,9 +1862,15 @@ "symbol": "WAN", "decimals": 18, "blockchain": "Ethereum", - "derivationPath": "m/44'/5718350'/0'/0/0", + "derivation": [ + { + "path": "m/44'/5718350'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", + "chainId": "888", + "addressHasher": "keccak256", "explorer": { "url": "https://www.wanscan.org", "txPath": "/tx/", @@ -1484,7 +1892,11 @@ "symbol": "WAVES", "decimals": 8, "blockchain": "Waves", - "derivationPath": "m/44'/5741564'/0'/0'/0'", + "derivation": [ + { + "path": "m/44'/5741564'/0'/0'/0'" + } + ], "curve": "ed25519", "publicKeyType": "curve25519", "explorer": { @@ -1507,9 +1919,15 @@ "symbol": "BNB", "decimals": 18, "blockchain": "Ethereum", - "derivationPath": "m/44'/714'/0'/0/0", + "derivation": [ + { + "path": "m/44'/714'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", + "chainId": "56", + "addressHasher": "keccak256", "explorer": { "url": "https://bscscan.com", "txPath": "/tx/", @@ -1523,19 +1941,27 @@ "rpc": "https://data-seed-prebsc-1-s1.binance.org:8545", "documentation": "https://eth.wiki/json-rpc/API" }, - "deprecated": true + "deprecated": true, + "testFolderName" : "Binance" }, { "id": "smartchain", "name": "Smart Chain", + "displayName": "BNB Smart Chain", "coinId": 20000714, "slip44": 714, "symbol": "BNB", "decimals": 18, "blockchain": "Ethereum", - "derivationPath": "m/44'/60'/0'/0/0", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", + "chainId": "56", + "addressHasher": "keccak256", "explorer": { "url": "https://bscscan.com", "txPath": "/tx/", @@ -1548,7 +1974,8 @@ "source": "https://github.com/binance-chain/bsc", "rpc": "https://bsc-dataseed1.binance.org", "documentation": "https://eth.wiki/json-rpc/API" - } + }, + "testFolderName" : "Binance" }, { "id": "polygon", @@ -1557,9 +1984,15 @@ "symbol": "MATIC", "decimals": 18, "blockchain": "Ethereum", - "derivationPath": "m/44'/60'/0'/0/0", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", + "chainId": "137", + "addressHasher": "keccak256", "explorer": { "url": "https://polygonscan.com", "txPath": "/tx/", @@ -1570,7 +2003,7 @@ "info": { "url": "https://polygon.technology", "source": "https://github.com/maticnetwork/contracts", - "rpc": "https://rpc-mainnet.matic.network", + "rpc": "https://polygon-rpc.com", "documentation": "https://eth.wiki/json-rpc/API" } }, @@ -1580,11 +2013,16 @@ "coinId": 931, "symbol": "RUNE", "decimals": 8, - "blockchain": "Cosmos", - "derivationPath": "m/44'/931'/0'/0/0", + "blockchain": "Thorchain", + "derivation": [ + { + "path": "m/44'/931'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "hrp": "thor", + "chainId": "thorchain-mainnet-v1", "explorer": { "url": "https://viewblock.io/thorchain", "txPath": "/tx/", @@ -1605,12 +2043,18 @@ "displayName": "Optimistic Ethereum", "coinId": 10000070, "slip44": 60, - "symbol": "OETH", + "symbol": "ETH", "decimals": 18, "blockchain": "Ethereum", - "derivationPath": "m/44'/60'/0'/0/0", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", + "chainId": "10", + "addressHasher": "keccak256", "explorer": { "url": "https://optimistic.etherscan.io", "txPath": "/tx/", @@ -1623,17 +2067,53 @@ "documentation": "https://eth.wiki/json-rpc/API" } }, + { + "id": "zksync", + "name": "Zksync", + "displayName": "zkSync v2", + "coinId": 10000280, + "slip44": 60, + "symbol": "ETH", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "280", + "addressHasher": "keccak256", + "explorer": { + "url": "https://zksync2-testnet.zkscan.io", + "txPath": "/tx/", + "accountPath": "/address/" + }, + "info": { + "url": "https://portal.zksync.io/", + "source": "https://github.com/matter-labs/zksync", + "rpc": "https://zksync2-testnet.zksync.dev", + "documentation": "https://v2-docs.zksync.io" + } + }, { "id": "arbitrum", "name": "Arbitrum", "coinId": 10042221, "slip44": 60, - "symbol": "ARETH", + "symbol": "ETH", "decimals": 18, "blockchain": "Ethereum", - "derivationPath": "m/44'/60'/0'/0/0", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", + "chainId": "42161", + "addressHasher": "keccak256", "explorer": { "url": "https://arbiscan.io", "txPath": "/tx/", @@ -1655,9 +2135,15 @@ "symbol": "HT", "decimals": 18, "blockchain": "Ethereum", - "derivationPath": "m/44'/60'/0'/0/0", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", + "chainId": "128", + "addressHasher": "keccak256", "explorer": { "url": "https://hecoinfo.com", "txPath": "/tx/", @@ -1668,7 +2154,8 @@ "source": "https://github.com/HuobiGroup/huobi-eco-chain", "rpc": "https://http-mainnet-node.huobichain.com", "documentation": "https://eth.wiki/json-rpc/API" - } + }, + "testFolderName" : "ECO" }, { "id": "avalanchec", @@ -1677,11 +2164,17 @@ "symbol": "AVAX", "decimals": 18, "blockchain": "Ethereum", - "derivationPath": "m/44'/60'/0'/0/0", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", + "chainId": "43114", + "addressHasher": "keccak256", "explorer": { - "url": "https://cchain.explorer.avax.network", + "url": "https://snowtrace.io", "txPath": "/tx/", "accountPath": "/address/", "sampleTx": "0x9243890b844219accefd8798271052f5a056453ec18984a56e81c92921330d54", @@ -1692,18 +2185,26 @@ "client": "https://github.com/ava-labs/avalanchego", "clientPublic": "https://api.avax.network/ext/bc/C/rpc", "clientDocs": "https://docs.avax.network/" - } + }, + "testFolderName" : "Avalanche" }, { "id": "xdai", "name": "xDai", + "displayName": "Gnosis Chain", "coinId": 10000100, "symbol": "xDAI", "decimals": 18, "blockchain": "Ethereum", - "derivationPath": "m/44'/60'/0'/0/0", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", + "chainId": "100", + "addressHasher": "keccak256", "explorer": { "url": "https://blockscout.com/xdai/mainnet", "txPath": "/tx/", @@ -1714,7 +2215,7 @@ "info": { "url": "https://www.xdaichain.com", "client": "https://github.com/openethereum/openethereum", - "clientPublic": "https://rpc.xdaichain.com", + "clientPublic": "https://rpc.gnosischain.com", "clientDocs": "https://eth.wiki/json-rpc/API" } }, @@ -1725,9 +2226,15 @@ "symbol": "FTM", "decimals": 18, "blockchain": "Ethereum", - "derivationPath": "m/44'/60'/0'/0/0", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", + "chainId": "250", + "addressHasher": "keccak256", "explorer": { "url": "https://ftmscan.com", "txPath": "/tx/", @@ -1750,10 +2257,16 @@ "symbol": "CRO", "decimals": 8, "blockchain": "Cosmos", - "derivationPath": "m/44'/394'/0'/0/0", + "derivation": [ + { + "path": "m/44'/394'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1", "hrp": "cro", + "chainId": "crypto-org-chain-mainnet-1", + "addressHasher": "sha256ripemd", "explorer": { "url": "https://crypto.org/explorer", "txPath": "/tx/", @@ -1775,9 +2288,15 @@ "symbol": "CELO", "decimals": 18, "blockchain": "Ethereum", - "derivationPath": "m/44'/52752'/0'/0/0", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", + "chainId": "42220", + "addressHasher": "keccak256", "explorer": { "url": "https://explorer.celo.org", "txPath": "/tx/", @@ -1799,10 +2318,16 @@ "slip44": 60, "symbol": "RON", "decimals": 18, - "blockchain": "Ethereum", - "derivationPath": "m/44'/60'/0'/0/0", + "blockchain": "Ronin", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", + "chainId": "2020", + "addressHasher": "keccak256", "explorer": { "url": "https://explorer.roninchain.com", "txPath": "/tx/", @@ -1816,5 +2341,483 @@ "clientPublic": "https://api.roninchain.com/rpc", "clientDocs": "https://eth.wiki/json-rpc/API" } + }, + { + "id": "osmosis", + "name": "Osmosis", + "displayName": "Osmosis", + "coinId": 10000118, + "symbol": "OSMO", + "decimals": 6, + "blockchain": "Cosmos", + "derivation": [ + { + "path": "m/44'/118'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "osmo", + "chainId": "osmosis-1", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://mintscan.io/osmosis", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "5A6E50A6F2927E4B8C87BB094D5FBF15F1287429A09111806FC44B3CD86CACA8", + "sampleAccount": "osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5" + }, + "info": { + "url": "https://osmosis.zone/", + "client": "https://github.com/osmosis-labs/osmosis", + "clientPublic": "https://rpc-osmosis.keplr.app/", + "clientDocs": "" + } + }, + { + "id": "ecash", + "name": "eCash", + "coinId": 899, + "symbol": "XEC", + "decimals": 2, + "blockchain": "Bitcoin", + "derivation": [ + { + "path": "m/44'/899'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 0, + "p2shPrefix": 5, + "hrp": "ecash", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://explorer.bitcoinabc.org", + "txPath": "/tx/", + "accountPath": "/address/" + }, + "info": { + "url": "https://e.cash", + "source": "https://github.com/trezor/blockbook", + "rpc": "https://blockbook.fabien.cash:9197", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "cronos", + "name": "Cronos Chain", + "coinId": 10000025, + "symbol": "CRO", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "25", + "addressHasher": "keccak256", + "explorer": { + "url": "https://cronoscan.com", + "txPath": "/tx/", + "accountPath": "/address/" + }, + "info": { + "url": "https://cronos.org", + "client": "https://github.com/crypto-org-chain/cronos", + "clientPublic": "https://evm-cronos.crypto.org", + "clientDocs": "https://eth.wiki/json-rpc/API" + }, + "testFolderName" : "Cronos" + }, + { + "id": "kavaevm", + "name": "KavaEvm", + "coinId": 10002222, + "symbol": "KAVA", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "2222", + "addressHasher": "keccak256", + "explorer": { + "url": "https://explorer.kava.io", + "txPath": "/tx/", + "accountPath": "/address/" + }, + "info": { + "url": "https://www.kava.io/", + "client": "https://github.com/Kava-Labs/kava", + "documentation": "https://docs.kava.io/docs/ethereum/overview/", + "rpc": "https://evm.kava.io" + } + }, + { + "id": "smartbch", + "name": "Smart Bitcoin Cash", + "coinId": 10000145, + "symbol": "BCH", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "10000", + "addressHasher": "keccak256", + "explorer": { + "url": "https://www.smartscan.cash", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x6413466b455b17d03c7a8ce2d7f99fec34bcd338628bdd2d0580a21e3197a4d9", + "sampleAccount": "0xFeEc227410E1DF9f3b4e6e2E284DC83051ae468F" + }, + "info": { + "url": "https://smartbch.org/", + "source": "https://github.com/smartbch/smartbch", + "rpc": "https://smartbch.fountainhead.cash/mainnet", + "documentation": "https://github.com/smartbch/docs/blob/main/developers-guide/jsonrpc.md" + }, + "testFolderName" : "Bitcoin" + }, + { + "id": "kcc", + "name": "KuCoin Community Chain", + "coinId": 10000321, + "symbol": "KCS", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "321", + "addressHasher": "keccak256", + "explorer": { + "url": "https://explorer.kcc.io/en", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x2f0d79cd289a02f3181b68b9583a64c3809fe7387810b274275985c29d02c80d", + "sampleAccount": "0x4446fc4eb47f2f6586f9faab68b3498f86c07521" + }, + "info": { + "url": "https://www.kcc.io/", + "source": "https://github.com/kcc-community/kcc", + "rpc": "https://rpc-mainnet.kcc.network", + "documentation": "https://docs.kcc.io/#/en-us/" + }, + "testFolderName" : "KuCoinCommunityChain" + }, + { + "id": "boba", + "name": "Boba", + "coinId": 10000288, + "symbol": "BOBAETH", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "288", + "addressHasher": "keccak256", + "explorer": { + "url": "https://blockexplorer.boba.network", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x31533707c3feb3b10f7deeea387ff8893f229253e65ca6b14d2400bf95b5d103", + "sampleAccount": "0x4F96F50eDB37a19216d87693E5dB241e31bD3735" + }, + "info": { + "url": "https://boba.network/", + "source": "https://github.com/bobanetwork/boba", + "rpc": "https://mainnet.boba.network", + "documentation": "https://docs.boba.network/" + } + }, + { + "id": "metis", + "name": "Metis", + "coinId": 10001088, + "symbol": "METIS", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "1088", + "addressHasher": "keccak256", + "explorer": { + "url": "https://andromeda-explorer.metis.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x422f2ebbede32d4434ad0cf0ae55d44a84e14d3d5725a760133255b42676d8ce", + "sampleAccount": "0xBe9E8Ec25866B21bA34e97b9393BCabBcB4A5C86" + }, + "info": { + "url": "https://www.metis.io/", + "source": "https://github.com/MetisProtocol/mvm", + "rpc": "https://andromeda.metis.io/?owner=1088", + "documentation": "https://docs.metis.io/" + } + }, + { + "id": "aurora", + "name": "Aurora", + "coinId": 1323161554, + "symbol": "ETH", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "1313161554", + "addressHasher": "keccak256", + "explorer": { + "url": "https://aurorascan.dev", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x99deebdb70f8027037abb3d3d0f3c7523daee857d85e9056d2671593ff2f2f28", + "sampleAccount": "0x8707cdE20dd43E3dB1F74c28fcd509ef38B0bA51" + }, + "info": { + "url": "https://aurora.dev/", + "source": "https://github.com/aurora-is-near/aurora-engine", + "rpc": "https://mainnet.aurora.dev/", + "documentation": "https://doc.aurora.dev/" + } + }, + { + "id": "evmos", + "name": "Evmos", + "coinId": 10009001, + "symbol": "EVMOS", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "9001", + "addressHasher": "keccak256", + "explorer": { + "url": "https://evm.evmos.org", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x24af42cf4977a96d62e3a82c3cd9b519c3e7c53dd83398b88f0cb435d867b422", + "sampleAccount": "0x30627903124Aa1e71384bc52e1cb96E4AB3252b6" + }, + "info": { + "url": "https://evmos.org/", + "source": "https://github.com/tharsis/evmos", + "rpc": "https://eth.bd.evmos.org:8545", + "documentation": "https://docs.evmos.org/" + } + }, + { + "id": "nativeevmos", + "name": "NativeEvmos", + "displayName": "Native Evmos", + "coinId": 20009001, + "symbol": "EVMOS", + "decimals": 18, + "blockchain": "Cosmos", + "chainId": "evmos_9001-2", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "hrp": "evmos", + "addressHasher": "keccak256", + "explorer": { + "url": "https://mintscan.io/evmos", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "A16C211C83AD1E684DE46F694FAAC17D8465C864BD7385A81EC062CDE0638811", + "sampleAccount": "evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34" + }, + "info": { + "url": "https://evmos.org/", + "client": "https://github.com/tharsis/evmos", + "clientPublic": "https://rest.bd.evmos.org:1317", + "clientDocs": "" + } + }, + { + "id": "moonriver", + "name": "Moonriver", + "coinId": 10001285, + "symbol": "MOVR", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "1285", + "explorer": { + "url": "https://moonriver.moonscan.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x2e2daa3943ba65d9bbb910a4f6765aa6a466a0ef8935090547ca9d30e201e032", + "sampleAccount": "0x899831D937937d011305E73EE782cce0455DF15a" + }, + "info": { + "url": "https://moonbeam.network/networks/moonriver", + "rpc": "https://moonriver.public.blastapi.io" + } + }, + { + "id": "moonbeam", + "name": "Moonbeam", + "coinId": 10001284, + "symbol": "GLMR", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "1284", + "explorer": { + "url": "https://moonscan.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0xb22a146c933e6e51affbfa5f712a266b5f5e92ae453cd2f252bcc3c36ff035a6", + "sampleAccount": "0x201bb4f276C765dF7225e5A4153E17edD23a67eC" + }, + "info": { + "url": "https://moonbeam.network", + "rpc": "https://rpc.api.moonbeam.network", + "documentation": "https://docs.moonbeam.network" + } + }, + { + "id": "klaytn", + "name": "Klaytn", + "coinId": 10008217, + "symbol": "KLAY", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "8217", + "explorer": { + "url": "https://scope.klaytn.com", + "txPath": "/tx/", + "accountPath": "/account/", + "sampleTx": "0x93ea92687845fe7bb6cacd69c76a16a2a3c2bbb85a8a93ff0e032d0098d583d7", + "sampleAccount": "0x2ad9656bf5b82caf10847b431012e28e301e83ba" + }, + "info": { + "url": "https://klaytn.foundation", + "rpc": "https://public-node-api.klaytnapi.com/v1/cypress", + "documentation": "https://docs.klaytn.foundation" + } + }, + { + "id": "meter", + "name": "Meter", + "coinId": 18000, + "chainId": "82", + "symbol": "MTR", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://scan.meter.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x8ea268d5dbb40217c763b800a75fc063cf28b56f40f2bc69dc043f5c4bbdc144", + "sampleAccount": "0xe5a273954d24eddf9ae9ea4cef2347d584cfa3dd" + }, + "info": { + "url": "https://meter.io/", + "source": "https://github.com/meterio/meter-pov", + "rpc": "https://rpc.meter.io", + "documentation": "https://docs.meter.io/" + } + }, + { + "id": "okc", + "name": "OKX Chain", + "coinId": 996, + "chainId": "66", + "symbol": "OKT", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "addressHasher": "keccak256", + "explorer": { + "url": "https://www.oklink.com/en/okc", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x46C3A947E8248570FBD28E4FE456CC8F80DFD90716533878FB67857B95FA3D37", + "sampleAccount": "0x074faafd0b20fad2efa115b8ed7e75993e580b85" + }, + "info": { + "url": "https://www.okx.com/okc", + "source": "https://github.com/okex/exchain", + "rpc": "https://exchainrpc.okex.org", + "documentation": "https://okc-docs.readthedocs.io/en/latest" + } } ] diff --git a/samples/android/app/build.gradle b/samples/android/app/build.gradle index abe39291f50..71479d4bac2 100644 --- a/samples/android/app/build.gradle +++ b/samples/android/app/build.gradle @@ -5,11 +5,11 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' android { - compileSdkVersion 29 + compileSdkVersion 32 defaultConfig { applicationId "com.trust.walletcore.example" minSdkVersion 23 - targetSdkVersion 29 + targetSdkVersion 32 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -23,7 +23,7 @@ android { } project.ext { - walletcore_version = "2.6.16" + walletcore_version = "2.9.8" } dependencies { diff --git a/samples/android/app/src/main/AndroidManifest.xml b/samples/android/app/src/main/AndroidManifest.xml index 8cb737a9b5d..a198932f7df 100644 --- a/samples/android/app/src/main/AndroidManifest.xml +++ b/samples/android/app/src/main/AndroidManifest.xml @@ -11,7 +11,7 @@ android:supportsRtl="true" android:theme="@style/AppTheme" tools:ignore="GoogleAppIndexingWarning"> - + diff --git a/samples/android/build.gradle b/samples/android/build.gradle index ce4fc31295f..5aa93a5e31a 100644 --- a/samples/android/build.gradle +++ b/samples/android/build.gradle @@ -1,5 +1,15 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. +Properties properties = new Properties() +File localProps = new File(rootDir.absolutePath, "local.properties") +if (localProps.exists()) { + properties.load(localProps.newDataInputStream()) + println "Authenticating user: " + properties.getProperty("gpr.user") +} else { + println "local.properties not found, please create it next to build.gradle and set gpr.user and gpr.key (Create a GitHub package read only + non expiration token at https://github.com/settings/tokens)\n" + + "Or set GITHUB_USER and GITHUB_TOKEN environment variables" +} + buildscript { ext.kotlin_version = '1.3.50' repositories { @@ -7,7 +17,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.2.1' + classpath 'com.android.tools.build:gradle:7.2.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -19,8 +29,8 @@ allprojects { maven { url = uri("https://maven.pkg.github.com/trustwallet/wallet-core") credentials { - username = project.findProperty("gpr.user") as String?: System.getenv("GITHUB_USER") - password = project.findProperty("gpr.key") as String?: System.getenv("GITHUB_TOKEN") + username = properties.getProperty("gpr.user") as String?: System.getenv("GITHUB_USER") + password = properties.getProperty("gpr.key") as String?: System.getenv("GITHUB_TOKEN") } } } diff --git a/samples/android/gradle/wrapper/gradle-wrapper.properties b/samples/android/gradle/wrapper/gradle-wrapper.properties index 98e52a713b4..87163a3434d 100644 --- a/samples/android/gradle/wrapper/gradle-wrapper.properties +++ b/samples/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip diff --git a/samples/cpp/CMakeLists.txt b/samples/cpp/CMakeLists.txt index 58715bcf9ad..5ecf5e6ba0d 100644 --- a/samples/cpp/CMakeLists.txt +++ b/samples/cpp/CMakeLists.txt @@ -1,32 +1,42 @@ +# Copyright © 2017-2022 Trust Wallet. +# +# This file is part of Trust. The full Trust copyright notice, including +# terms governing use, modification, and redistribution, is contained in the +# file LICENSE at the root of the source code distribution tree. + # Expected input configuration: WALLET_CORE: directory for TrustWalletCore build dir # e.g. cmake . -DWALLET_CORE=../wallet-core -cmake_minimum_required (VERSION 3.4) +cmake_minimum_required (VERSION 3.8 FATAL_ERROR) project (wallet-core-demo-cpp) +if (NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")) + message(FATAL_ERROR "You should use clang compiler") +endif() + set (SETUP_MESSAGE "Please provide TrustWalletCore build directory with -DWALLET_CORE. Example: cmake . -DWALLET_CORE=../wallet-core") if (NOT WALLET_CORE) message (FATAL_ERROR "${SETUP_MESSAGE}") endif () +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set (CMAKE_C_STANDARD 11) +set (CMAKE_C_STANDARD_REQUIRED ON) + # Include dirs: # ${WALLET_CORE}/include -- public TrustWalletCore includes # ${WALLET_CORE}/src -- internal TrustWalletCore files, for signer protobuf messages # ${WALLET_CORE}/build/local/include) -- for protobuf includes include_directories (${CMAKE_SOURCE_DIR} ${WALLET_CORE}/include ${WALLET_CORE}/src ${WALLET_CORE}/build/local/include) -link_directories (${WALLET_CORE}/build/install/lib ${WALLET_CORE}/build ${WALLET_CORE}/build/trezor-crypto ${WALLET_CORE}/build/local/lib) +link_directories (${WALLET_CORE}/build ${WALLET_CORE}/build/trezor-crypto ${WALLET_CORE}/build/local/lib) -find_library(WALLET_CORE_LIB_RELEASE TrustWalletCore PATH ${WALLET_CORE}/build ${WALLET_CORE}/build/install/lib) -find_library(WALLET_CORE_LIB_DEBUG TrustWalletCored PATH ${WALLET_CORE}/build ${WALLET_CORE}/build/install/lib) -if (NOT WALLET_CORE_LIB_RELEASE) +find_library(WALLET_CORE_LIB_FILE TrustWalletCore PATH ${WALLET_CORE}/build) +if (NOT WALLET_CORE_LIB_FILE) message (FATAL_ERROR "TrustWalletCore library not found. ${SETUP_MESSAGE}") else () - if (WALLET_CORE_LIB_DEBUG) - set (WALLET_CORE_LIBRARIES optimized ${WALLET_CORE_LIB_RELEASE} debug ${WALLET_CORE_LIB_DEBUG}) - else () - set (WALLET_CORE_LIBRARIES ${WALLET_CORE_LIB_RELEASE}) - endif () - message ("TrustWalletCore library found here: ${WALLET_CORE_LIBRARIES}") + message ("TrustWalletCore library found here: ${WALLET_CORE_LIB_FILE}") endif () # Create all libraries and executables in the root binary dir @@ -45,16 +55,6 @@ else () add_compile_options (-Werror=switch) endif () -if (NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "MSVC")) - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") -endif () - -set (CMAKE_C_STANDARD 11) -set (CMAKE_C_STANDARD_REQUIRED ON) - -set (CMAKE_CXX_STANDARD 14) -set (CMAKE_CXX_STANDARD_REQUIRED ON) - if (WIN32) add_definitions(/bigobj) endif () @@ -71,7 +71,4 @@ SET (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${PLATFORM_LINK_FLAGS}") add_executable (sample sample.cpp) # link with our library, and default platform libraries -target_link_libraries (sample ${WALLET_CORE_LIBRARIES} ${Protobuf_LIBRARIES} ${PLATFORM_LIBS}) -if (NOT WIN32) - target_link_libraries (TrezorCrypto) -endif () +target_link_libraries (sample TrustWalletCore TrezorCrypto protobuf pthread ${PLATFORM_LIBS}) diff --git a/samples/cpp/sample.cpp b/samples/cpp/sample.cpp index a50e807a031..bb69ec05b1c 100644 --- a/samples/cpp/sample.cpp +++ b/samples/cpp/sample.cpp @@ -17,106 +17,111 @@ using namespace std; int main() { - { - cout << endl; - cout << " Wallet Core Demo, C++" << endl; - cout << endl; - cout << " *** DISCLAIMER ***" << endl; - cout << " THIS IS A SAMPLE APPLICATION WITH DEMONSTRATION PURPOSES ONLY." << endl; - cout << " DO NOT USE WITH REAL SECRETS, REAL ADDRESSESS, OR REAL TRANSACTIONS. USE IT AT YOUR OWN RISK." << endl; - cout << " *** DISCLAIMER ***" << endl; - cout << endl; - } + try { + { + cout << endl; + cout << " Wallet Core Demo, C++" << endl; + cout << endl; + cout << " *** DISCLAIMER ***" << endl; + cout << " THIS IS A SAMPLE APPLICATION WITH DEMONSTRATION PURPOSES ONLY." << endl; + cout << " DO NOT USE WITH REAL SECRETS, REAL ADDRESSESS, OR REAL TRANSACTIONS. USE IT AT YOUR OWN RISK." << endl; + cout << " *** DISCLAIMER ***" << endl; + cout << endl; + } - TWHDWallet* walletImp = nullptr; - { - // Create a new multi-coin HD wallet, with new recovery phrase (mnemonic) - cout << "Creating a new HD wallet ... "; - TWHDWallet* walletNew = TWHDWalletCreate(128, TWStringCreateWithUTF8Bytes("")); - cout << "done." << endl; - cout << "Secret mnemonic for new wallet: '"; - cout << TWStringUTF8Bytes(TWHDWalletMnemonic(walletNew)) << "'." << endl; - TWHDWalletDelete(walletNew); + TWHDWallet* walletImp = nullptr; + { + // Create a new multi-coin HD wallet, with new recovery phrase (mnemonic) + cout << "Creating a new HD wallet ... "; + TWHDWallet* walletNew = TWHDWalletCreate(128, TWStringCreateWithUTF8Bytes("")); + cout << "done." << endl; + cout << "Secret mnemonic for new wallet: '"; + cout << TWStringUTF8Bytes(TWHDWalletMnemonic(walletNew)) << "'." << endl; + TWHDWalletDelete(walletNew); - // Alternative: Import wallet with existing recovery phrase (mnemonic) - cout << "Importing an HD wallet from earlier ... "; - auto secretMnemonic = TWStringCreateWithUTF8Bytes("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"); - walletImp = TWHDWalletCreateWithMnemonic(secretMnemonic, TWStringCreateWithUTF8Bytes("")); - TWStringDelete(secretMnemonic); - cout << "done." << endl; - cout << "Secret mnemonic for imported wallet: '"; - cout << TWStringUTF8Bytes(TWHDWalletMnemonic(walletImp)) << "'." << endl; - cout << endl; - } + // Alternative: Import wallet with existing recovery phrase (mnemonic) + cout << "Importing an HD wallet from earlier ... "; + auto secretMnemonic = TWStringCreateWithUTF8Bytes("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"); + walletImp = TWHDWalletCreateWithMnemonic(secretMnemonic, TWStringCreateWithUTF8Bytes("")); + TWStringDelete(secretMnemonic); + cout << "done." << endl; + cout << "Secret mnemonic for imported wallet: '"; + cout << TWStringUTF8Bytes(TWHDWalletMnemonic(walletImp)) << "'." << endl; + cout << endl; + } - { - // coin type: we use Ethereum - const TWCoinType coinType = TWCoinType::TWCoinTypeEthereum; // TWCoinTypeBitcoin, TWCoinTypeEthereum - cout << "Working with coin: " << - TWStringUTF8Bytes(TWCoinTypeConfigurationGetName(coinType)) << " " << - TWStringUTF8Bytes(TWCoinTypeConfigurationGetSymbol(coinType)) << endl; + { + // coin type: we use Ethereum + const TWCoinType coinType = TWCoinType::TWCoinTypeEthereum; // TWCoinTypeBitcoin, TWCoinTypeEthereum + cout << "Working with coin: " << + TWStringUTF8Bytes(TWCoinTypeConfigurationGetName(coinType)) << " " << + TWStringUTF8Bytes(TWCoinTypeConfigurationGetSymbol(coinType)) << endl; - // Derive default address. - cout << "Obtaining default address ... "; - string address = TWStringUTF8Bytes(TWHDWalletGetAddressForCoin(walletImp, coinType)); - cout << " done." << endl; - cout << "Default address: '" << address << "'" << endl; + // Derive default address. + cout << "Obtaining default address ... "; + string address = TWStringUTF8Bytes(TWHDWalletGetAddressForCoin(walletImp, coinType)); + cout << " done." << endl; + cout << "Default address: '" << address << "'" << endl; - // Alternative: Derive address using default derivation path. - // Done in 2 steps: derive private key, then address from private key. - // Note that private key is passed around between the two calls by the wallet -- be always cautious when handling secrets, avoid the risk of leaking secrets. - cout << "Default derivation path: " << TWStringUTF8Bytes(TWCoinTypeDerivationPath(coinType)) << endl; - TWPrivateKey* secretPrivateKeyDefault = TWHDWalletGetKeyForCoin(walletImp, coinType); - string addressDefault = TWStringUTF8Bytes(TWCoinTypeDeriveAddress(coinType, secretPrivateKeyDefault)); - cout << "Address from default key: '" << addressDefault << "'" << endl; + // Alternative: Derive address using default derivation path. + // Done in 2 steps: derive private key, then address from private key. + // Note that private key is passed around between the two calls by the wallet -- be always cautious when handling secrets, avoid the risk of leaking secrets. + cout << "Default derivation path: " << TWStringUTF8Bytes(TWCoinTypeDerivationPath(coinType)) << endl; + TWPrivateKey* secretPrivateKeyDefault = TWHDWalletGetKeyForCoin(walletImp, coinType); + string addressDefault = TWStringUTF8Bytes(TWCoinTypeDeriveAddress(coinType, secretPrivateKeyDefault)); + cout << "Address from default key: '" << addressDefault << "'" << endl; - // Alternative: Derive address using custom derivation path. Done in 2 steps: derive private key, then address. - auto customDerivationPath = TWStringCreateWithUTF8Bytes("m/44'/60'/1'/0/0"); - TWPrivateKey* secretPrivateKeyCustom = TWHDWalletGetKey(walletImp, coinType, customDerivationPath); - TWStringDelete(customDerivationPath); - string addressCustom = TWStringUTF8Bytes(TWCoinTypeDeriveAddress(coinType, secretPrivateKeyCustom)); - cout << "Custom-derived address: '" << addressCustom << "'" << endl; - cout << endl; + // Alternative: Derive address using custom derivation path. Done in 2 steps: derive private key, then address. + auto customDerivationPath = TWStringCreateWithUTF8Bytes("m/44'/60'/1'/0/0"); + TWPrivateKey* secretPrivateKeyCustom = TWHDWalletGetKey(walletImp, coinType, customDerivationPath); + TWStringDelete(customDerivationPath); + string addressCustom = TWStringUTF8Bytes(TWCoinTypeDeriveAddress(coinType, secretPrivateKeyCustom)); + cout << "Custom-derived address: '" << addressCustom << "'" << endl; + cout << endl; - cout << "RECEIVE funds: Perform send from somewehere else to this address: " << address << " ." << endl; - cout << endl; + cout << "RECEIVE funds: Perform send from somewehere else to this address: " << address << " ." << endl; + cout << endl; - // Steps for sending: - // 1. put together a send message (contains sender and receiver address, amount, gas price, etc.) - // 2. sign this message - // 3. broadcast this message to the P2P network -- not done in this sample - // Note that Signer input and output are represented as protobuf binary messages, for which support is missing in C++. - // Therefore some direct serialization/parsing is done in helper methods. - cout << "SEND funds:" << endl; - const string dummyReceiverAddress = "0xC37054b3b48C3317082E7ba872d7753D13da4986"; - auto secretPrivKey = TWPrivateKeyData(secretPrivateKeyDefault); + // Steps for sending: + // 1. put together a send message (contains sender and receiver address, amount, gas price, etc.) + // 2. sign this message + // 3. broadcast this message to the P2P network -- not done in this sample + // Note that Signer input and output are represented as protobuf binary messages, for which support is missing in C++. + // Therefore some direct serialization/parsing is done in helper methods. + cout << "SEND funds:" << endl; + const string dummyReceiverAddress = "0xC37054b3b48C3317082E7ba872d7753D13da4986"; + auto secretPrivKey = TWPrivateKeyData(secretPrivateKeyDefault); - cout << "preparing transaction (using AnySigner) ... "; - string chainIdB64 = "AQ=="; // base64(parse_hex("01")) - string gasPriceB64 = "1pOkAA=="; // base64(parse_hex("d693a4")) decimal 3600000000 - string gasLimitB64 = "Ugg="; // base64(parse_hex("5208")) decimal 21000 - string amountB64 = "A0i8paFgAA=="; // base64(parse_hex("0348bca5a160")) 924400000000000 - string transaction = "{" - "\"chainId\":\"" + chainIdB64 + - "\",\"gasPrice\":\"" + gasPriceB64 + - "\",\"gasLimit\":\"" + gasLimitB64 + - "\",\"toAddress\":\"" + dummyReceiverAddress + - "\",\"transaction\":{\"transfer\":{\"amount\":\"" + amountB64 + - "\"}}}"; - cout << "transaction: " << transaction << endl; + cout << "preparing transaction (using AnySigner) ... "; + string chainIdB64 = "AQ=="; // base64(parse_hex("01")) + string gasPriceB64 = "1pOkAA=="; // base64(parse_hex("d693a4")) decimal 3600000000 + string gasLimitB64 = "Ugg="; // base64(parse_hex("5208")) decimal 21000 + string amountB64 = "A0i8paFgAA=="; // base64(parse_hex("0348bca5a160")) 924400000000000 + string transaction = "{" + "\"chainId\":\"" + chainIdB64 + + "\",\"gasPrice\":\"" + gasPriceB64 + + "\",\"gasLimit\":\"" + gasLimitB64 + + "\",\"toAddress\":\"" + dummyReceiverAddress + + "\",\"transaction\":{\"transfer\":{\"amount\":\"" + amountB64 + + "\"}}}"; + cout << "transaction: " << transaction << endl; - cout << "signing transaction ... "; + cout << "signing transaction ... "; - auto json = TWStringCreateWithUTF8Bytes(transaction.c_str()); - auto result = TWAnySignerSignJSON(json, secretPrivKey, TWCoinTypeEthereum); - auto signedTransaction = string(TWStringUTF8Bytes(result)); - cout << "done" << endl; - cout << "Signed transaction data (to be broadcast to network): (len " << signedTransaction.length() << ") '" << signedTransaction << "'" << endl; - // see e.g. https://github.com/flightwallet/decode-eth-tx for checking binary output content - cout << endl; - TWStringDelete(json); - TWStringDelete(result); + auto json = TWStringCreateWithUTF8Bytes(transaction.c_str()); + auto result = TWAnySignerSignJSON(json, secretPrivKey, TWCoinTypeEthereum); + auto signedTransaction = string(TWStringUTF8Bytes(result)); + cout << "done" << endl; + cout << "Signed transaction data (to be broadcast to network): (len " << signedTransaction.length() << ") '" << signedTransaction << "'" << endl; + // see e.g. https://github.com/flightwallet/decode-eth-tx for checking binary output content + cout << endl; + TWStringDelete(json); + TWStringDelete(result); + } + cout << "Bye!" << endl; + TWHDWalletDelete(walletImp); + } catch (const exception& ex) { + cout << "EXCEPTION: " << ex.what() << endl; + throw ex; } - cout << "Bye!" << endl; - TWHDWalletDelete(walletImp); } diff --git a/samples/go/README.md b/samples/go/README.md index d28a5dd0ad6..96716cabb90 100644 --- a/samples/go/README.md +++ b/samples/go/README.md @@ -1,18 +1,18 @@ # Sample Go Integration for [Wallet-Core](https://github.com/trustwallet/wallet-core) -## Overview +## 🔖 Overview This folder contains a small **Go** sample integration with [Wallet Core](https://github.com/trustwallet/wallet-core) library (part of [Trust Wallet](https://trustwallet.com)), using [cgo](https://golang.org/cmd/cgo/). -## DISCLAIMER +## âš ï¸ DISCLAIMER > This is a sample application with demonstration purpose only, > do not use it with real addresses, real transactions, or real funds. > Use it at your own risk. -## Documentation +## 📜 Documentation See the official [Trust Wallet developer documentation here](https://developer.trustwallet.com). @@ -20,31 +20,57 @@ See especially Wallet Core [Integration Guide](https://developer.trustwallet.com/wallet-core/integration-guide), and [Build Instructions](https://developer.trustwallet.com/wallet-core/building). -## Prerequisites +## 🛠 Prerequisites -* Docker +`macOS` or `Docker` -## Building and Running +## âš™ï¸ Building and Running +###  macOS +#### Prerequisites on macOS +* CMake `brew install cmake` +* Boost `brew install boost` +* Xcode +* Xcode command line tools: `xcode-select --install` +* Other tools: `brew install git ninja autoconf automake libtool xcodegen clang-format` +* GoLang: [download](https://go.dev/dl/) +* Protobuf: `brew install protobuf protoc-gen-go` +#### Full Build + +1. Clone the wallet-core repo and go inside: +```shell +git clone https://github.com/trustwallet/wallet-core.git + +cd wallet-core +``` +2. The full build can be triggered with one top-level script: +```shell +./bootstrap.sh +``` + +### 🳠Docker 1. Run `docker run -it trustwallet/wallet-core` The librabry is already built in this image (Build instructions [here](building.md)) Note: may not be the most recent version. 2. Install go: `apt-get update && apt-get install golang` -(or download from here [go1.14.2](https://dl.google.com/go/go1.14.2.linux-amd64.tar.gz), configure `GOROOT` and append `GOROOT/bin` to `PATH`). -3. Go to the **samples/go** folder within wallet core repo: +(or download from here [go1.16.12](https://go.dev/dl/go1.16.12.linux-amd64.tar.gz), configure `GOROOT` and append `GOROOT/bin` to `PATH`). + +### ðŸƒðŸ½â€â™‚ï¸ **Run** (macOS & Docker) +1. Go to the **samples/go** folder within wallet core repo: ```shell -git clone https://github.com/trustwallet/wallet-core.git cd wallet-core/samples/go ``` -4. Compile it by `go build -o main`. Relavant source file is `main.go`. +2. Compile it by `go build -o main`. Relavant source file is `main.go`. -5. Run `./main` and you will see the output below: +3. Run `./main` and you will see the output below: ```shell ==> calling wallet core from go ==> mnemonic is valid: true -==> bitcoin... +==> Bitcoin... ``` -6. You might want to copy and run `main` outside of the docker container, make sure you have `libc++1` and `libc++abi1` installed in your host Ubuntu. +4. *(optional)* You might want to copy and run `main` outside of the docker container, make sure you have `libc++1` and `libc++abi1` installed in your host Ubuntu. + +5. *(optional)* If you want to make transaction on other networks you need to compile `src/proto` proto files and to do that, just run the `./compile.sh` . you can also modify it based on your project. \ No newline at end of file diff --git a/samples/go/compile.sh b/samples/go/compile.sh new file mode 100644 index 00000000000..a0484037828 --- /dev/null +++ b/samples/go/compile.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# Run this file ./compile.sh to generate all go protobuf file from src/proto + +# Clean +rm -rf protos +mkdir protos + +PROTO_PATH=../../src/proto +for FILE in "$PROTO_PATH"/*.proto; do + # Reading proto files + FILE_NAME="${FILE#"$PROTO_PATH"/}" + PKG=$(echo "${FILE_NAME%.proto}" | tr '[:upper:]' '[:lower:]') + # Generate Go protobuf files + # + # manual --go_opt=M... declarations is because of + # dependencies between some proto files + mkdir protos/"$PKG" + protoc -I=$PROTO_PATH --go_out=protos/"$PKG" \ + --go_opt=paths=source_relative \ + --go_opt=M"$FILE_NAME"=tw/protos/"$PKG" \ + --go_opt=MCommon.proto=tw/protos/common \ + --go_opt=MBitcoin.proto=tw/protos/bitcoin \ + --go_opt=MEthereum.proto=tw/protos/ethereum \ + --go_opt=MBinance.proto=tw/protos/binance \ + "$PROTO_PATH"/"$FILE_NAME" +done diff --git a/samples/go/core/bitcoin.go b/samples/go/core/bitcoin.go new file mode 100644 index 00000000000..5613b452ffa --- /dev/null +++ b/samples/go/core/bitcoin.go @@ -0,0 +1,54 @@ +package core + +// #cgo CFLAGS: -I../../../include +// #cgo LDFLAGS: -L../../../build -L../../../build/trezor-crypto -lTrustWalletCore -lprotobuf -lTrezorCrypto -lstdc++ -lm +// #include +// #include +// #include +import "C" + +import "tw/types" + +const ( + BitcoinSigHashTypeAll = C.TWBitcoinSigHashTypeAll + BitcoinSigHashTypeNone = C.TWBitcoinSigHashTypeNone + BitcoinSigHashTypeSingle = C.TWBitcoinSigHashTypeSingle + BitcoinSigHashTypeFork = C.TWBitcoinSigHashTypeFork + BitcoinSigHashTypeForkBTG = C.TWBitcoinSigHashTypeForkBTG + BitcoinSigHashTypeAnyoneCanPay = C.TWBitcoinSigHashTypeAnyoneCanPay +) + +func BitcoinScriptLockScriptForAddress(addr string, ct CoinType) []byte { + address := types.TWStringCreateWithGoString(addr) + defer C.TWStringDelete(address) + + script := C.TWBitcoinScriptLockScriptForAddress(address, C.enum_TWCoinType(ct)) + scriptData := C.TWBitcoinScriptData(script) + defer C.TWBitcoinScriptDelete(script) + defer C.TWDataDelete(scriptData) + + return types.TWDataGoBytes(scriptData) +} + +func BitcoinScriptBuildPayToPublicKeyHash(hash []byte) []byte { + hashData := types.TWDataCreateWithGoBytes(hash) + defer C.TWDataDelete(hashData) + + script := C.TWBitcoinScriptBuildPayToPublicKeyHash(hashData) + scriptData := C.TWBitcoinScriptData(script) + defer C.TWBitcoinScriptDelete(script) + defer C.TWDataDelete(scriptData) + + return types.TWDataGoBytes(scriptData) +} + +func BitcoinScriptMatchPayToWitnessPublicKeyHash(script []byte) []byte { + scriptData := types.TWDataCreateWithGoBytes(script) + defer C.TWDataDelete(scriptData) + scriptObj := C.TWBitcoinScriptCreateWithData(scriptData) + defer C.TWBitcoinScriptDelete(scriptObj) + + hash := C.TWBitcoinScriptMatchPayToWitnessPublicKeyHash(scriptObj) + defer C.TWDataDelete(hash) + return types.TWDataGoBytes(hash) +} diff --git a/samples/go/core/coin.go b/samples/go/core/coin.go new file mode 100644 index 00000000000..122e9a286fb --- /dev/null +++ b/samples/go/core/coin.go @@ -0,0 +1,28 @@ +package core + +// #cgo CFLAGS: -I../../../include +// #cgo LDFLAGS: -L../../../build -L../../../build/trezor-crypto -lTrustWalletCore -lprotobuf -lTrezorCrypto -lstdc++ -lm +// #include +// #include +import "C" + +import "tw/types" + +type CoinType uint32 + +const ( + CoinTypeBitcoin CoinType = C.TWCoinTypeBitcoin + CoinTypeBinance CoinType = C.TWCoinTypeBinance + CoinTypeEthereum CoinType = C.TWCoinTypeEthereum + CoinTypeTron CoinType = C.TWCoinTypeTron +) + +func (c CoinType) GetName() string { + name := C.TWCoinTypeConfigurationGetName(C.enum_TWCoinType(c)) + defer C.TWStringDelete(name) + return types.TWStringGoString(name) +} + +func (c CoinType) Decimals() int { + return int(C.TWCoinTypeConfigurationGetDecimals(C.enum_TWCoinType(c))) +} diff --git a/samples/go/core/datavector.go b/samples/go/core/datavector.go new file mode 100644 index 00000000000..014030e8817 --- /dev/null +++ b/samples/go/core/datavector.go @@ -0,0 +1,31 @@ +package core + +// #cgo CFLAGS: -I../../../include +// #cgo LDFLAGS: -L../../../build -L../../../build/trezor-crypto -lTrustWalletCore -lprotobuf -lTrezorCrypto -lstdc++ -lm +// #include +import "C" +import "tw/types" + +type TWDataVector *C.struct_TWDataVector + +// Go [][]byte -> C.TWDataVector +func TWDataVectorCreateWithGoBytes(d [][]byte) TWDataVector { + vec := C.TWDataVectorCreate() + for i := 0; i < len(d); i++ { + elem := types.TWDataCreateWithGoBytes(d[i]) + C.TWDataVectorAdd(vec, elem) + } + return vec +} + +// C.TWDataVector -> Go [][]byte +func TWDataVectorGoBytes(d *C.struct_TWDataVector) [][]byte { + var vec [][]byte + cSize := int(C.TWDataVectorSize(d)) + for i := 0; i < cSize; i++ { + elemC := C.TWDataVectorGet(d, C.ulong(i)) + elemG := types.TWDataGoBytes(elemC) + vec = append(vec, elemG) + } + return vec +} diff --git a/samples/go/core/mnemonic.go b/samples/go/core/mnemonic.go new file mode 100644 index 00000000000..12394cc86b9 --- /dev/null +++ b/samples/go/core/mnemonic.go @@ -0,0 +1,14 @@ +package core + +// #cgo CFLAGS: -I../../../include +// #cgo LDFLAGS: -L../../../build -L../../../build/trezor-crypto -lTrustWalletCore -lprotobuf -lTrezorCrypto -lstdc++ -lm +// #include +import "C" + +import "tw/types" + +func IsMnemonicValid(mn string) bool { + str := types.TWStringCreateWithGoString(mn) + defer C.TWStringDelete(str) + return bool(C.TWMnemonicIsValid(str)) +} diff --git a/samples/go/core/publicKey.go b/samples/go/core/publicKey.go new file mode 100644 index 00000000000..d116f8e7270 --- /dev/null +++ b/samples/go/core/publicKey.go @@ -0,0 +1,41 @@ +package core + +// #cgo CFLAGS: -I../../../include +// #cgo LDFLAGS: -L../../../build -L../../../build/trezor-crypto -lTrustWalletCore -lprotobuf -lTrezorCrypto -lstdc++ -lm +// #include +import "C" + +import "tw/types" + +type PublicKeyType uint32 + +const ( + PublicKeyTypeSECP256k1 PublicKeyType = C.TWPublicKeyTypeSECP256k1 + PublicKeyTypeSECP256k1Extended PublicKeyType = C.TWPublicKeyTypeSECP256k1Extended +) + +func PublicKeyVerify(key []byte, keyType PublicKeyType, signature []byte, message []byte) bool { + keyData := types.TWDataCreateWithGoBytes(key) + defer C.TWDataDelete(keyData) + publicKey := C.TWPublicKeyCreateWithData(keyData, C.enum_TWPublicKeyType(keyType)) + defer C.TWPublicKeyDelete(publicKey) + sig := types.TWDataCreateWithGoBytes(signature) + defer C.TWDataDelete(sig) + msg := types.TWDataCreateWithGoBytes(message) + defer C.TWDataDelete(msg) + + return bool(C.TWPublicKeyVerify(publicKey, sig, msg)) +} + +func PublicKeyVerifyAsDER(key []byte, keyType PublicKeyType, signature []byte, message []byte) bool { + keyData := types.TWDataCreateWithGoBytes(key) + defer C.TWDataDelete(keyData) + publicKey := C.TWPublicKeyCreateWithData(keyData, C.enum_TWPublicKeyType(keyType)) + defer C.TWPublicKeyDelete(publicKey) + sig := types.TWDataCreateWithGoBytes(signature) + defer C.TWDataDelete(sig) + msg := types.TWDataCreateWithGoBytes(message) + defer C.TWDataDelete(msg) + + return bool(C.TWPublicKeyVerifyAsDER(publicKey, sig, msg)) +} diff --git a/samples/go/core/transaction.go b/samples/go/core/transaction.go new file mode 100644 index 00000000000..3b3333d5a11 --- /dev/null +++ b/samples/go/core/transaction.go @@ -0,0 +1,28 @@ +package core + +// #cgo CFLAGS: -I../../../include +// #cgo LDFLAGS: -L../../../build -L../../../build/trezor-crypto -lTrustWalletCore -lprotobuf -lTrezorCrypto -lstdc++ -lm +// #include +// #include +import "C" + +import ( + "tw/types" + + "google.golang.org/protobuf/proto" +) + +func CreateSignedTx(inputData proto.Message, ct CoinType, outputData proto.Message) error { + ibytes, _ := proto.Marshal(inputData) + idata := types.TWDataCreateWithGoBytes(ibytes) + defer C.TWDataDelete(idata) + + odata := C.TWAnySignerSign(idata, C.enum_TWCoinType(ct)) + defer C.TWDataDelete(odata) + + err := proto.Unmarshal(types.TWDataGoBytes(odata), outputData) + if err != nil { + return err + } + return nil +} diff --git a/samples/go/core/transactionHelper.go b/samples/go/core/transactionHelper.go new file mode 100644 index 00000000000..c8414724a32 --- /dev/null +++ b/samples/go/core/transactionHelper.go @@ -0,0 +1,49 @@ +package core + +// #cgo CFLAGS: -I../../../include +// #cgo LDFLAGS: -L../../../build -L../../../build/trezor-crypto -lTrustWalletCore -lprotobuf -lTrezorCrypto -lstdc++ -lm +// #include +import "C" +import "tw/types" + +func BuildInput(c CoinType, from, to string, amount string, asset string, memo string, chainId string) []byte { + fromStr := types.TWStringCreateWithGoString(from) + defer C.TWStringDelete(fromStr) + toStr := types.TWStringCreateWithGoString(to) + defer C.TWStringDelete(toStr) + amountStr := types.TWStringCreateWithGoString(amount) + defer C.TWStringDelete(amountStr) + assetStr := types.TWStringCreateWithGoString(asset) + defer C.TWStringDelete(assetStr) + memoStr := types.TWStringCreateWithGoString(memo) + defer C.TWStringDelete(memoStr) + chainIdStr := types.TWStringCreateWithGoString(chainId) + defer C.TWStringDelete(chainIdStr) + + result := C.TWTransactionCompilerBuildInput(C.enum_TWCoinType(c), fromStr, toStr, amountStr, assetStr, memoStr, chainIdStr) + defer C.TWDataDelete(result) + return types.TWDataGoBytes(result) +} + +func PreImageHashes(c CoinType, txInputData []byte) []byte { + input := types.TWDataCreateWithGoBytes(txInputData) + defer C.TWDataDelete(input) + + result := C.TWTransactionCompilerPreImageHashes(C.enum_TWCoinType(c), input) + defer C.TWDataDelete(result) + return types.TWDataGoBytes(result) +} + +func CompileWithSignatures(c CoinType, txInputData []byte, signatures [][]byte, publicKeyHashes [][]byte) []byte { + input := types.TWDataCreateWithGoBytes(txInputData) + defer C.TWDataDelete(input) + + sigs := TWDataVectorCreateWithGoBytes(signatures) + defer C.TWDataVectorDelete(sigs) + pubkeyhashes := TWDataVectorCreateWithGoBytes(publicKeyHashes) + defer C.TWDataVectorDelete(pubkeyhashes) + + result := C.TWTransactionCompilerCompileWithSignatures(C.enum_TWCoinType(c), input, sigs, pubkeyhashes) + defer C.TWDataDelete(result) + return types.TWDataGoBytes(result) +} diff --git a/samples/go/core/wallet.go b/samples/go/core/wallet.go new file mode 100644 index 00000000000..09af05cdd42 --- /dev/null +++ b/samples/go/core/wallet.go @@ -0,0 +1,54 @@ +package core + +// #cgo CFLAGS: -I../../../include +// #cgo LDFLAGS: -L../../../build -L../../../build/trezor-crypto -lTrustWalletCore -lprotobuf -lTrezorCrypto -lstdc++ -lm +// #include +// #include +// #include +import "C" + +import ( + "errors" + "tw/types" +) + +type Wallet struct { + Address string + PriKey string + PubKey string + CoinType +} + +func CreateWalletWithMnemonic(mn string, ct CoinType) (*Wallet, error) { + if !IsMnemonicValid(mn) { + return nil, errors.New("mnemonic is not valid") + } + + str := types.TWStringCreateWithGoString(mn) + empty := types.TWStringCreateWithGoString("") + defer C.TWStringDelete(str) + defer C.TWStringDelete(empty) + + tw := C.TWHDWalletCreateWithMnemonic(str, empty) + defer C.TWHDWalletDelete(tw) + + priKey := C.TWHDWalletGetKeyForCoin(tw, C.enum_TWCoinType(ct)) + defer C.TWPrivateKeyDelete(priKey) + priKeyData := C.TWPrivateKeyData(priKey) + defer C.TWDataDelete(priKeyData) + + pubKey := C.TWPrivateKeyGetPublicKeySecp256k1(priKey, true) + defer C.TWPublicKeyDelete(pubKey) + pubKeyData := C.TWPublicKeyData(pubKey) + defer C.TWDataDelete(pubKeyData) + + address := C.TWHDWalletGetAddressForCoin(tw, C.enum_TWCoinType(ct)) + defer C.TWStringDelete(address) + + return &Wallet{ + Address: types.TWStringGoString(address), + PriKey: types.TWDataHexString(priKeyData), + PubKey: types.TWDataHexString(pubKeyData), + CoinType: ct, + }, nil +} diff --git a/samples/go/dev-console/.gitignore b/samples/go/dev-console/.gitignore new file mode 100644 index 00000000000..6320cd248dd --- /dev/null +++ b/samples/go/dev-console/.gitignore @@ -0,0 +1 @@ +data \ No newline at end of file diff --git a/samples/go/dev-console/README.md b/samples/go/dev-console/README.md new file mode 100644 index 00000000000..9c1b0ed6495 --- /dev/null +++ b/samples/go/dev-console/README.md @@ -0,0 +1,12 @@ +## Quick start + +Compile wallet core with `${PROJECT_ROOT}/bootstrap.sh` +Use `./prepare.sh` +Compile the cli with `cd cmd && go build -o ../tw_dev_console && cd -` +Start `./tw_dev_console` + +If there is any compilation error on MacOS you can try the following command: + +`go get -u golang.org/x/sys` + +source: https://stackoverflow.com/questions/71507321/go-1-18-build-error-on-mac-unix-syscall-darwin-1-13-go253-golinkname-mus \ No newline at end of file diff --git a/samples/go/dev-console/cli/address.go b/samples/go/dev-console/cli/address.go new file mode 100644 index 00000000000..d2d11a8d6f7 --- /dev/null +++ b/samples/go/dev-console/cli/address.go @@ -0,0 +1,23 @@ +package cli + +import ( + "dev-console/native" + "dev-console/wallet" + "github.com/kyokomi/emoji/v2" +) + +func DumpAllAddress() { + if wallet.GlobalWallet == nil || !wallet.GlobalWallet.Ks.IsLoaded() { + emoji.Printf(":warning:No wallet loaded, use load_wallet or create_wallet first :warning:\n") + return + } + nbWallets := wallet.GlobalWallet.Ks.AccountCount() + var accounts []*native.Account + for idx := int32(0); idx < nbWallets; idx++ { + accounts = append(accounts, wallet.GlobalWallet.Ks.Account(idx)) + } + native.ToTableAccounts(accounts) + for _, account := range accounts { + account.Delete() + } +} diff --git a/samples/go/dev-console/cli/completer.go b/samples/go/dev-console/cli/completer.go new file mode 100644 index 00000000000..15021fec4d5 --- /dev/null +++ b/samples/go/dev-console/cli/completer.go @@ -0,0 +1,37 @@ +package cli + +import ( + "github.com/c-bata/go-prompt" + "strings" +) + +var gCommands = []prompt.Suggest{ + {Text: "exit", Description: "Quit the CLI"}, + {Text: "create_wallet", Description: "Create a new wallet"}, + {Text: "load_wallet", Description: "Load an existing wallet"}, + {Text: "delete_wallet", Description: "Delete an existing wallet"}, + {Text: "address_all", Description: "Show the addresses of all accounts from the current wallet"}, + {Text: "help", Description: "Show the global help"}, +} + +type Completer struct { +} + +func NewCompleter() (*Completer, error) { + return &Completer{}, nil +} + +func (c *Completer) argumentsCompleter(args []string) []prompt.Suggest { + if len(args) <= 1 { + return prompt.FilterContains(gCommands, args[0], true) + } + return []prompt.Suggest{} +} + +func (c *Completer) Complete(d prompt.Document) []prompt.Suggest { + if d.TextBeforeCursor() == "" { + return []prompt.Suggest{} + } + args := strings.Split(d.TextBeforeCursor(), " ") + return c.argumentsCompleter(args) +} diff --git a/samples/go/dev-console/cli/create_wallet.go b/samples/go/dev-console/cli/create_wallet.go new file mode 100644 index 00000000000..11eb80914f4 --- /dev/null +++ b/samples/go/dev-console/cli/create_wallet.go @@ -0,0 +1,118 @@ +package cli + +import ( + "dev-console/native" + "dev-console/wallet" + "fmt" + "github.com/kyokomi/emoji/v2" + "github.com/manifoldco/promptui" + "log" + "path/filepath" +) + +func ConfigureMnemonic() *native.Wallet { + prompt := promptui.Select{ + Label: "Passphrase Configuration", + Items: []string{"Generate a Seed", "Restore a Seed"}, + } + _, result, _ := prompt.Run() + + if result == "Generate a Seed" { + return generateSeed() + } else if result == "Restore a Seed" { + return restoreSeed() + } + return nil +} + +func restoreSeed() *native.Wallet { + promptRestoreSeed := promptui.Prompt{ + Label: "Please enter your seed", + } + resultSeed, err := promptRestoreSeed.Run() + if err != nil { + fmt.Printf("Prompt failed %v\n", err) + return restoreSeed() + } + + if len(resultSeed) == 0 { + fmt.Println("You're custom seed cannot be empty, please try again") + return restoreSeed() + } + + if !native.IsMnemonicValid(resultSeed) { + fmt.Println("You're seed is not a valid bip39 seed, please retry") + return restoreSeed() + } + + wallet, err := native.NewWalletWithMnemonic(resultSeed) + if err != nil { + log.Fatalf("Couldn't create the wallet: %v", err) + } + return wallet +} + +func generateSeed() *native.Wallet { + wallet := native.NewWalletWithRandomMnemonic() + return wallet +} + +func ConfigureWalletName() string { + promptWalletName := promptui.Prompt{ + Label: "Please enter your wallet name", + } + walletName, err := promptWalletName.Run() + if err != nil { + fmt.Printf("Prompt failed %v\n", err) + walletName = ConfigureWalletName() + } + + if len(walletName) == 0 { + fmt.Println("You're custom seed cannot be empty, please try again") + walletName = ConfigureWalletName() + } + + return walletName +} + +func ConfigurePassword() string { + promptPassword := promptui.Prompt{ + Label: "Please, choose a wallet password", + } + walletPassword, err := promptPassword.Run() + if err != nil { + fmt.Printf("Prompt failed %v\n", err) + walletPassword = ConfigureWalletName() + } + + if len(walletPassword) == 0 { + fmt.Println("You're password cannot be empty, please try again") + walletPassword = ConfigureWalletName() + } + + // Do We really need to check if the password is strong? It's a dev console. + return walletPassword +} + +func CreateWallet() { + walletName := ConfigureWalletName() + freshWallet := ConfigureMnemonic() + password := ConfigurePassword() + defer freshWallet.Delete() + storedKey := native.NewStoredKeyFromHDWallet(freshWallet.Mnemonic(), walletName, password, native.CoinTypeBitcoin) + storedKey.AccountForCoin(password, native.CoinTypeBinance) + storedKey.AccountForCoin(password, native.CoinTypeEthereum) + if storedKey != nil { + res := storedKey.Store(filepath.Join(wallet.GetWalletDataDirectory(), walletName+".json")) + if res { + _, _ = emoji.Println("Wallet successfully created :white_check_mark:") + // the global wallet can be loaded on creation or with load_wallet `wallet_name` - afterwards we can query accounts and address + // open to change the package name + // I guess keep the password the time the app is open is OK, no need to query it again + wallet.GlobalWallet = &wallet.Wallet{Ks: storedKey, Password: password, WalletName: walletName} + wallet.GlobalWallet.Dump() + } + } else { + fmt.Println("Couldn't create the wallet") + } +} diff --git a/samples/go/dev-console/cli/delete_wallet.go b/samples/go/dev-console/cli/delete_wallet.go new file mode 100644 index 00000000000..cd61a2b26b6 --- /dev/null +++ b/samples/go/dev-console/cli/delete_wallet.go @@ -0,0 +1,41 @@ +package cli + +import ( + "dev-console/native" + "dev-console/wallet" + "fmt" + "github.com/kyokomi/emoji/v2" + "log" + "os" + "path/filepath" +) + +func DeleteWallet() { + wallets := listWallets() + if len(wallets) == 0 { + fmt.Println("No wallets found, use create_wallet instead.") + return + } + walletName := chooseWallet(wallets) + walletPath := filepath.Join(wallet.GetWalletDataDirectory(), walletName+".json") + + walletPassword := queryWalletPassword() + storedKey := native.Load(walletPath) + if storedKey.IsLoaded() { + + if hdWallet := storedKey.Wallet(walletPassword); hdWallet.IsValid() { + e := os.Remove(walletPath) + if e != nil { + log.Fatal(e) + } + wallet.GlobalWallet = nil + emoji.Printf("Wallet %s successfully deleted :white_check_mark:\n", walletName) + defer hdWallet.Delete() + } else { + fmt.Println("Password from the wallet is incorrect") + DeleteWallet() + } + } else { + fmt.Println("Can't load the wallet.") + } +} diff --git a/samples/go/dev-console/cli/executor.go b/samples/go/dev-console/cli/executor.go new file mode 100644 index 00000000000..7c157234783 --- /dev/null +++ b/samples/go/dev-console/cli/executor.go @@ -0,0 +1,28 @@ +package cli + +import ( + "fmt" + "os" + "strings" +) + +func Executor(fullCommand string) { + fullCommand = strings.TrimSpace(fullCommand) + command := strings.Split(fullCommand, " ") + switch command[0] { + case "create_wallet": + CreateWallet() + case "load_wallet": + LoadWallet() + case "delete_wallet": + DeleteWallet() + case "address_all": + DumpAllAddress() + case "help": + ShowGlobalHelp() + case "exit": + fmt.Println("Quitting the CLI") + os.Exit(0) + } + return +} diff --git a/samples/go/dev-console/cli/help.go b/samples/go/dev-console/cli/help.go new file mode 100644 index 00000000000..c47277c9029 --- /dev/null +++ b/samples/go/dev-console/cli/help.go @@ -0,0 +1,36 @@ +package cli + +import ( + "github.com/olekukonko/tablewriter" + "os" +) + +const ( + createWalletHelp = `The create_wallet command create a new wallet with a given name and password. +You can choose to restore or generate a seed.` + createWalletUsage = `create_wallet` + loadWalletHelp = `The load_wallet command load the current wallet, the password will be prompted.` + loadWalletUsage = `load_wallet` + deleteWalletHelp = `The delete_wallet command delete the chosen wallet, password will be prompted.` + deleteWalletUsage = `delete_wallet` + addressAllHelp = `The address_all command dump a table of all address accounts of the current loaded wallet. +Wallet need to be loaded with load_wallet before usage.` + addressAllUsage = `address_all` +) + +func ShowGlobalHelp() { + data := [][]string{ + {"create_wallet", "", createWalletHelp, createWalletUsage}, + {"load_wallet", "", loadWalletHelp, loadWalletUsage}, + {"delete_wallet", "", deleteWalletHelp, deleteWalletUsage}, + {"address_all", "", addressAllHelp, addressAllUsage}, + } + + table := tablewriter.NewWriter(os.Stdout) + table.SetAutoWrapText(false) + table.SetHeader([]string{"Command", "Args", "Description", "Usage"}) + table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false}) + table.SetCenterSeparator("|") + table.AppendBulk(data) // Add Bulk Data + table.Render() +} diff --git a/samples/go/dev-console/cli/load_wallet.go b/samples/go/dev-console/cli/load_wallet.go new file mode 100644 index 00000000000..c9f2968ce9c --- /dev/null +++ b/samples/go/dev-console/cli/load_wallet.go @@ -0,0 +1,77 @@ +package cli + +import ( + "dev-console/native" + "dev-console/wallet" + "fmt" + "github.com/manifoldco/promptui" + "log" + "os" + "path/filepath" + "strings" +) + +func listWallets() []string { + files, err := os.ReadDir(wallet.GetWalletDataDirectory()) + if err != nil { + log.Fatal(err) + } + + var wallets []string + for _, file := range files { + wallets = append(wallets, strings.TrimSuffix(file.Name(), filepath.Ext(file.Name()))) + } + return wallets +} + +func chooseWallet(wallets []string) string { + prompt := promptui.Select{ + Label: "Choose the wallet to load", + Items: wallets, + } + _, result, _ := prompt.Run() + return result +} + +func queryWalletPassword() string { + promptPassword := promptui.Prompt{ + Label: "Please, enter your wallet password", + } + walletPassword, err := promptPassword.Run() + if err != nil { + fmt.Printf("Prompt failed %v\n", err) + walletPassword = queryWalletPassword() + } + + if len(walletPassword) == 0 { + fmt.Println("You're password cannot be empty, please try again") + walletPassword = queryWalletPassword() + } + + return walletPassword +} + +func LoadWallet() { + wallets := listWallets() + if len(wallets) == 0 { + fmt.Println("No wallets found, use create_wallet instead.") + return + } + walletName := chooseWallet(wallets) + walletPath := filepath.Join(wallet.GetWalletDataDirectory(), walletName+".json") + walletPassword := queryWalletPassword() + storedKey := native.Load(walletPath) + if storedKey.IsLoaded() { + wallet.GlobalWallet = &wallet.Wallet{Ks: storedKey, Password: walletPassword, WalletName: walletName} + if hdWallet := storedKey.Wallet(walletPassword); hdWallet.IsValid() { + fmt.Printf("Wallet %s successfully loaded\n", walletName) + defer hdWallet.Delete() + } else { + fmt.Println("Password from the wallet is incorrect") + wallet.GlobalWallet = nil + LoadWallet() + } + } else { + fmt.Println("Can't load the wallet.") + } +} diff --git a/samples/go/dev-console/cmd/cli.go b/samples/go/dev-console/cmd/cli.go new file mode 100644 index 00000000000..07ee64ec2a0 --- /dev/null +++ b/samples/go/dev-console/cmd/cli.go @@ -0,0 +1,15 @@ +package main + +import ( + "dev-console/cli" + "github.com/c-bata/go-prompt" +) + +func main() { + completer, _ := cli.NewCompleter() + p := prompt.New( + cli.Executor, + completer.Complete, + ) + p.Run() +} diff --git a/samples/go/dev-console/go.mod b/samples/go/dev-console/go.mod new file mode 100644 index 00000000000..f134213cc0d --- /dev/null +++ b/samples/go/dev-console/go.mod @@ -0,0 +1,20 @@ +module dev-console + +go 1.19 + +require ( + github.com/c-bata/go-prompt v0.2.6 + github.com/kyokomi/emoji/v2 v2.2.10 + github.com/manifoldco/promptui v0.9.0 + github.com/olekukonko/tablewriter v0.0.5 +) + +require ( + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect + github.com/mattn/go-colorable v0.1.7 // indirect + github.com/mattn/go-isatty v0.0.12 // indirect + github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/mattn/go-tty v0.0.3 // indirect + github.com/pkg/term v1.2.0-beta.2 // indirect + golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect +) diff --git a/samples/go/dev-console/go.sum b/samples/go/dev-console/go.sum new file mode 100644 index 00000000000..18a71a2e408 --- /dev/null +++ b/samples/go/dev-console/go.sum @@ -0,0 +1,39 @@ +github.com/c-bata/go-prompt v0.2.6 h1:POP+nrHE+DfLYx370bedwNhsqmpCUynWPxuHi0C5vZI= +github.com/c-bata/go-prompt v0.2.6/go.mod h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdfpU9wwHfY= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/kyokomi/emoji/v2 v2.2.10 h1:1z5eMVcxFifsmEoNpdeq4UahbcicgQ4FEHuzrCVwmiI= +github.com/kyokomi/emoji/v2 v2.2.10/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE= +github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= +github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI= +github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/pkg/term v1.2.0-beta.2 h1:L3y/h2jkuBVFdWiJvNfYfKmzcCnILw7mJWm2JQuMppw= +github.com/pkg/term v1.2.0-beta.2/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/samples/go/dev-console/native/cgo.go b/samples/go/dev-console/native/cgo.go new file mode 100644 index 00000000000..3e0569e9162 --- /dev/null +++ b/samples/go/dev-console/native/cgo.go @@ -0,0 +1,14 @@ +package native + +// #cgo CFLAGS: -I packaged/include +// #cgo LDFLAGS: -lTrustWalletCore -lstdc++ -lm -lprotobuf -lTrezorCrypto +// +// +// #cgo LDFLAGS: -Wl,-rpath,${SRCDIR}/packaged/lib -L${SRCDIR}/packaged/lib +// +import "C" + +import ( + _ "dev-console/native/packaged/include" + _ "dev-console/native/packaged/lib" +) diff --git a/samples/go/dev-console/native/packaged/.gitignore b/samples/go/dev-console/native/packaged/.gitignore new file mode 100644 index 00000000000..a48ad1e8d53 --- /dev/null +++ b/samples/go/dev-console/native/packaged/.gitignore @@ -0,0 +1,2 @@ +*.h +*.a \ No newline at end of file diff --git a/samples/go/dev-console/native/packaged/.gitkeep b/samples/go/dev-console/native/packaged/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/samples/go/dev-console/native/packaged/include/.gitkeep b/samples/go/dev-console/native/packaged/include/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/samples/go/dev-console/native/packaged/include/dummy.go b/samples/go/dev-console/native/packaged/include/dummy.go new file mode 100644 index 00000000000..3f807a2211b --- /dev/null +++ b/samples/go/dev-console/native/packaged/include/dummy.go @@ -0,0 +1,2 @@ +// See https://github.com/golang/go/issues/26366. +package include diff --git a/samples/go/dev-console/native/packaged/lib/.gitkeep b/samples/go/dev-console/native/packaged/lib/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/samples/go/dev-console/native/packaged/lib/dummy.go b/samples/go/dev-console/native/packaged/lib/dummy.go new file mode 100644 index 00000000000..662a7ee75a7 --- /dev/null +++ b/samples/go/dev-console/native/packaged/lib/dummy.go @@ -0,0 +1,2 @@ +// See https://github.com/golang/go/issues/26366. +package lib diff --git a/samples/go/dev-console/native/twaccount.go b/samples/go/dev-console/native/twaccount.go new file mode 100644 index 00000000000..33e5b22f2fe --- /dev/null +++ b/samples/go/dev-console/native/twaccount.go @@ -0,0 +1,44 @@ +package native + +// #include +import "C" +import ( + "github.com/olekukonko/tablewriter" + "os" +) + +type Account struct { + account *C.struct_TWAccount +} + +func (self *Account) Delete() { + C.TWAccountDelete(self.account) +} + +func (self *Account) Address() string { + address := TWString{s: C.TWAccountAddress(self.account)} + defer address.Delete() + return address.String() +} + +func (self *Account) CoinType() CoinType { + return CoinType(C.TWAccountCoin(self.account)) +} + +func ToTableAccounts(accounts []*Account) { + var data [][]string + + for _, account := range accounts { + cur := []string{account.CoinType().GetName(), account.Address()} + data = append(data, cur) + } + + table := tablewriter.NewWriter(os.Stdout) + table.SetAutoWrapText(false) + table.SetHeader([]string{"Coin", "Address"}) + table.SetFooter([]string{"", ""}) + table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false}) + table.SetCenterSeparator("|") + table.AppendBulk(data) // Add Bulk Data + table.Render() +} diff --git a/samples/go/dev-console/native/twcoin.go b/samples/go/dev-console/native/twcoin.go new file mode 100644 index 00000000000..42eb2010640 --- /dev/null +++ b/samples/go/dev-console/native/twcoin.go @@ -0,0 +1,24 @@ +package native + +// #include +// #include +import "C" + +type CoinType uint32 + +const ( + CoinTypeBitcoin CoinType = C.TWCoinTypeBitcoin + CoinTypeBinance CoinType = C.TWCoinTypeBinance + CoinTypeEthereum CoinType = C.TWCoinTypeEthereum + CoinTypeTron CoinType = C.TWCoinTypeTron +) + +func (c CoinType) GetName() string { + name := C.TWCoinTypeConfigurationGetName(C.enum_TWCoinType(c)) + defer C.TWStringDelete(name) + return TWString{s: name}.String() +} + +func (c CoinType) Decimals() int { + return int(C.TWCoinTypeConfigurationGetDecimals(C.enum_TWCoinType(c))) +} diff --git a/samples/go/dev-console/native/twdata.go b/samples/go/dev-console/native/twdata.go new file mode 100644 index 00000000000..c558bddfa05 --- /dev/null +++ b/samples/go/dev-console/native/twdata.go @@ -0,0 +1,29 @@ +package native + +// #include +import "C" + +import ( + "encoding/hex" + "unsafe" +) + +// C.TWData -> Go byte[] +func TWDataGoBytes(d unsafe.Pointer) []byte { + cBytes := C.TWDataBytes(d) + cSize := C.TWDataSize(d) + return C.GoBytes(unsafe.Pointer(cBytes), C.int(cSize)) +} + +// Go byte[] -> C.TWData +func TWDataCreateWithGoBytes(d []byte) unsafe.Pointer { + cBytes := C.CBytes(d) + defer C.free(unsafe.Pointer(cBytes)) + data := C.TWDataCreateWithBytes((*C.uchar)(cBytes), C.ulong(len(d))) + return data +} + +// C.TWData -> Go hex string +func TWDataHexString(d unsafe.Pointer) string { + return hex.EncodeToString(TWDataGoBytes(d)) +} diff --git a/samples/go/dev-console/native/twmnemonic.go b/samples/go/dev-console/native/twmnemonic.go new file mode 100644 index 00000000000..f159a1a486a --- /dev/null +++ b/samples/go/dev-console/native/twmnemonic.go @@ -0,0 +1,10 @@ +package native + +// #include +import "C" + +func IsMnemonicValid(mn string) bool { + str := NewTWString(mn) + defer str.Delete() + return bool(C.TWMnemonicIsValid(str.s)) +} diff --git a/samples/go/dev-console/native/twstoredkey.go b/samples/go/dev-console/native/twstoredkey.go new file mode 100644 index 00000000000..c84976ed47e --- /dev/null +++ b/samples/go/dev-console/native/twstoredkey.go @@ -0,0 +1,54 @@ +package native + +// #include +import "C" + +type StoredKey struct { + storedKey *C.struct_TWStoredKey +} + +func NewStoredKeyFromHDWallet(mnemonic string, walletName string, password string, coinType CoinType) *StoredKey { + mnemonicRaw := NewTWString(mnemonic) + defer mnemonicRaw.Delete() + walletNameRaw := NewTWString(walletName) + defer walletNameRaw.Delete() + passwordRaw := TWDataCreateWithGoBytes([]byte(password)) + sk := C.TWStoredKeyImportHDWallet(mnemonicRaw.s, walletNameRaw.s, passwordRaw, uint32(coinType)) + if sk != nil { + return &StoredKey{storedKey: sk} + } + return nil +} + +func (self *StoredKey) IsLoaded() bool { + return self.storedKey != nil +} + +func Load(path string) *StoredKey { + pathRaw := NewTWString(path) + return &StoredKey{storedKey: C.TWStoredKeyLoad(pathRaw.s)} +} + +func (self *StoredKey) AccountForCoin(password string, coinType CoinType) *Account { + wallet := self.Wallet(password) + defer wallet.Delete() + return &Account{account: C.TWStoredKeyAccountForCoin(self.storedKey, uint32(coinType), wallet.wallet)} +} + +func (self *StoredKey) Store(path string) bool { + pathRaw := NewTWString(path) + defer pathRaw.Delete() + return bool(C.TWStoredKeyStore(self.storedKey, pathRaw.s)) +} + +func (self *StoredKey) Wallet(password string) *Wallet { + return &Wallet{wallet: C.TWStoredKeyWallet(self.storedKey, TWDataCreateWithGoBytes([]byte(password)))} +} + +func (self *StoredKey) AccountCount() int32 { + return int32(C.TWStoredKeyAccountCount(self.storedKey)) +} + +func (self *StoredKey) Account(index int32) *Account { + return &Account{account: C.TWStoredKeyAccount(self.storedKey, C.size_t(index))} +} diff --git a/samples/go/dev-console/native/twstring.go b/samples/go/dev-console/native/twstring.go new file mode 100644 index 00000000000..b8df5b2349b --- /dev/null +++ b/samples/go/dev-console/native/twstring.go @@ -0,0 +1,31 @@ +package native + +// #include +import "C" + +import ( + "unsafe" +) + +type TWString struct { + s unsafe.Pointer +} + +func NewTWString(s string) TWString { + cStr := C.CString(s) + defer C.free(unsafe.Pointer(cStr)) + str := C.TWStringCreateWithUTF8Bytes(cStr) + return TWString{s: str} +} + +func (self TWString) Delete() { + C.TWStringDelete(self.s) +} + +func (self TWString) String() string { + return C.GoString(C.TWStringUTF8Bytes(self.s)) +} + +func (self TWString) Size() int64 { + return int64(C.size_t(C.TWStringSize(self.s))) +} diff --git a/samples/go/dev-console/native/twwallet.go b/samples/go/dev-console/native/twwallet.go new file mode 100644 index 00000000000..c2453580b96 --- /dev/null +++ b/samples/go/dev-console/native/twwallet.go @@ -0,0 +1,44 @@ +package native + +// #include +import "C" +import "errors" + +type Wallet struct { + wallet *C.struct_TWHDWallet +} + +func NewWalletWithRandomMnemonic() *Wallet { + return &Wallet{wallet: C.TWHDWalletCreate(256, NewTWString("").s)} +} + +func NewWalletWithMnemonic(mn string) (*Wallet, error) { + if !IsMnemonicValid(mn) { + return nil, errors.New("mnemonic is not valid") + } + str := NewTWString(mn) + empty := NewTWString("") + defer str.Delete() + defer empty.Delete() + + tw := C.TWHDWalletCreateWithMnemonic(str.s, empty.s) + return &Wallet{wallet: tw}, nil +} + +func (self *Wallet) IsValid() bool { + return self.wallet != nil +} + +func (self *Wallet) Delete() { + C.TWHDWalletDelete(self.wallet) +} + +func (self *Wallet) Seed() string { + return TWDataHexString(C.TWHDWalletSeed(self.wallet)) +} + +func (self *Wallet) Mnemonic() string { + str := TWString{s: C.TWHDWalletMnemonic(self.wallet)} + defer str.Delete() + return str.String() +} diff --git a/samples/go/dev-console/prepare.sh b/samples/go/dev-console/prepare.sh new file mode 100644 index 00000000000..76ec7e9bb85 --- /dev/null +++ b/samples/go/dev-console/prepare.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +mkdir -p build && cd build +cmake -DCMAKE_BUILD_TYPE=Release -DTW_UNIT_TESTS=OFF -DTW_BUILD_EXAMPLES=OFF -DTW_UNITY_BUILD=ON -GNinja "$PWD"/../../../../ +ninja +cp libTrustWalletCore.a ../native/packaged/lib/ +cp libprotobuf.a ../native/packaged/lib +cp trezor-crypto/libTrezorCrypto.a ../native/packaged/lib +cd - +cp -R ../../../include native/packaged/ \ No newline at end of file diff --git a/samples/go/dev-console/wallet/wallet.go b/samples/go/dev-console/wallet/wallet.go new file mode 100644 index 00000000000..4f29e713666 --- /dev/null +++ b/samples/go/dev-console/wallet/wallet.go @@ -0,0 +1,41 @@ +package wallet + +import ( + "dev-console/native" + "errors" + "github.com/kyokomi/emoji/v2" + "log" + "os" + "path/filepath" +) + +type Wallet struct { + Ks *native.StoredKey + Password string + WalletName string +} + +var GlobalWallet *Wallet = nil + +func (self *Wallet) Dump() { + emoji.Printf("Wallet Name: %s :white_check_mark:\n", self.WalletName) + // Should we really dump password? + emoji.Printf("Wallet Password: %s :white_check_mark:\n", self.Password) + wallet := self.Ks.Wallet(self.Password) + defer wallet.Delete() + emoji.Printf("Wallet Mnemonic: %s :white_check_mark:\n", wallet.Mnemonic()) + emoji.Printf("Wallet Accounts count: %d :white_check_mark:\n", self.Ks.AccountCount()) + // Should we dump other infos? +} + +func GetWalletDataDirectory() string { + pwd, _ := os.Getwd() + dir := filepath.Join(pwd, "data") + if _, err := os.Stat(dir); errors.Is(err, os.ErrNotExist) { + err := os.Mkdir(dir, os.ModePerm) + if err != nil { + log.Println(err) + } + } + return dir +} diff --git a/samples/go/go.mod b/samples/go/go.mod index 9a564387542..2835844a409 100644 --- a/samples/go/go.mod +++ b/samples/go/go.mod @@ -1,5 +1,5 @@ module tw -go 1.14 +go 1.16 -require github.com/golang/protobuf v1.4.2 +require google.golang.org/protobuf v1.27.1 diff --git a/samples/go/go.sum b/samples/go/go.sum index 0eeb4918550..03b1917b5a4 100644 --- a/samples/go/go.sum +++ b/samples/go/go.sum @@ -1,20 +1,8 @@ -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= diff --git a/samples/go/main.go b/samples/go/main.go index 27933dff91e..8aae9f98dac 100644 --- a/samples/go/main.go +++ b/samples/go/main.go @@ -1,55 +1,94 @@ package main -// #cgo CFLAGS: -I../../include -// #cgo LDFLAGS: -L../../build -L../../build/trezor-crypto -lTrustWalletCore -lprotobuf -lTrezorCrypto -lc++ -lm -// #include -// #include -// #include -// #include -// #include -import "C" - import ( "encoding/hex" "fmt" + "math" + "math/big" + "tw/core" "tw/protos/bitcoin" - "tw/types" - - "github.com/golang/protobuf/proto" + "tw/protos/common" + "tw/protos/ethereum" + "tw/sample" ) func main() { fmt.Println("==> calling wallet core from go") - str := types.TWStringCreateWithGoString("confirm bleak useless tail chalk destroy horn step bulb genuine attract split") - emtpy := types.TWStringCreateWithGoString("") - defer C.TWStringDelete(str) - defer C.TWStringDelete(emtpy) - fmt.Println("==> mnemonic is valid: ", C.TWMnemonicIsValid(str)) + mn := "confirm bleak useless tail chalk destroy horn step bulb genuine attract split" - wallet := C.TWHDWalletCreateWithMnemonic(str, emtpy) - defer C.TWHDWalletDelete(wallet) + fmt.Println("==> mnemonic is valid: ", core.IsMnemonicValid(mn)) - key := C.TWHDWalletGetKeyForCoin(wallet, C.TWCoinTypeBitcoin) - keyData := C.TWPrivateKeyData(key) - defer C.TWDataDelete(keyData) + // bitcoin wallet + bw, err := core.CreateWalletWithMnemonic(mn, core.CoinTypeBitcoin) + if err != nil { + panic(err) + } + printWallet(bw) - fmt.Println("<== bitcoin private key: ", types.TWDataHexString(keyData)) + // ethereum wallet + ew, err := core.CreateWalletWithMnemonic(mn, core.CoinTypeEthereum) + if err != nil { + panic(err) + } + printWallet(ew) - pubKey, _ := hex.DecodeString("0288be7586c41a0498c1f931a0aaf08c15811ee2651a5fe0fa213167dcaba59ae8") - pubKeyData := types.TWDataCreateWithGoBytes(pubKey) - defer C.TWDataDelete(pubKeyData) - fmt.Println("==> bitcoin public key is valid: ", C.TWPublicKeyIsValid(pubKeyData, C.TWPublicKeyTypeSECP256k1)) + // tron wallet + tw, err := core.CreateWalletWithMnemonic(mn, core.CoinTypeTron) + if err != nil { + panic(err) + } + printWallet(tw) - address := C.TWHDWalletGetAddressForCoin(wallet, C.TWCoinTypeBitcoin) - defer C.TWStringDelete(address) - fmt.Println("<== bitcoin address: ", types.TWStringGoString(address)) + // Ethereum transaction + ethTxn := createEthTransaction(ew) + fmt.Println("Ethereum signed tx:") + fmt.Println("\t", ethTxn) - script := C.TWBitcoinScriptLockScriptForAddress(address, C.TWCoinTypeBitcoin) - scriptData := C.TWBitcoinScriptData(script) - defer C.TWBitcoinScriptDelete(script) - defer C.TWDataDelete(scriptData) - fmt.Println("<== bitcoin address lock script: ", types.TWDataHexString(scriptData)) + // Bitcion transaction + btcTxn := createBtcTransaction(bw) + fmt.Println("\nBitcoin signed tx:") + fmt.Println("\t", btcTxn) + + sample.ExternalSigningDemo() +} + +func createEthTransaction(ew *core.Wallet) string { + priKeyByte, _ := hex.DecodeString(ew.PriKey) + + input := ethereum.SigningInput{ + ChainId: big.NewInt(4).Bytes(), // mainnet: 1, rinkeby: 4 https://chainlist.org/ + Nonce: big.NewInt(0).Bytes(), // get nonce from network + TxMode: ethereum.TransactionMode_Legacy, + GasPrice: big.NewInt(100000000000).Bytes(), // 100 gwei + GasLimit: big.NewInt(21000).Bytes(), + ToAddress: "0xE9B511C0753649E5F3E78Ed8AdBEE92d0d2Db384", + PrivateKey: priKeyByte, + Transaction: ðereum.Transaction{ + TransactionOneof: ðereum.Transaction_Transfer_{ + Transfer: ðereum.Transaction_Transfer{ + // amount should be in wei unit, eth * (10^decimals) = wei + Amount: big.NewInt(int64( + 0.01 * math.Pow10(ew.CoinType.Decimals()), + )).Bytes(), + Data: []byte{}, + }, + }, + }, + } + + var output ethereum.SigningOutput + err := core.CreateSignedTx(&input, ew.CoinType, &output) + if err != nil { + panic(err) + } + return hex.EncodeToString(output.GetEncoded()) +} + +func createBtcTransaction(bw *core.Wallet) string { + lockScript := core.BitcoinScriptLockScriptForAddress(bw.Address, bw.CoinType) + fmt.Println("\nBitcoin address lock script:") + fmt.Println("\t", hex.EncodeToString(lockScript)) utxoHash, _ := hex.DecodeString("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f") @@ -60,28 +99,37 @@ func main() { Sequence: 4294967295, }, Amount: 625000000, - Script: types.TWDataGoBytes(scriptData), + Script: lockScript, } + priKeyByte, _ := hex.DecodeString(bw.PriKey) + input := bitcoin.SigningInput{ - HashType: 1, // TWBitcoinSigHashTypeAll + HashType: uint32(core.BitcoinSigHashTypeAll), Amount: 1000000, ByteFee: 1, ToAddress: "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx", ChangeAddress: "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU", - PrivateKey: [][]byte{types.TWDataGoBytes(keyData)}, + PrivateKey: [][]byte{priKeyByte}, Utxo: []*bitcoin.UnspentTransaction{&utxo}, - CoinType: 0, // TWCoinTypeBitcoin + CoinType: uint32(core.CoinTypeBitcoin), } - inputBytes, _ := proto.Marshal(&input) - inputData := types.TWDataCreateWithGoBytes(inputBytes) - defer C.TWDataDelete(inputData) - - outputData := C.TWAnySignerSign(inputData, C.TWCoinTypeBitcoin) - defer C.TWDataDelete(outputData) - var output bitcoin.SigningOutput - _ = proto.Unmarshal(types.TWDataGoBytes(outputData), &output) - fmt.Println("<== bitcoin signed tx: ", hex.EncodeToString(output.Encoded)) + err := core.CreateSignedTx(&input, bw.CoinType, &output) + if err != nil { + panic(err) + } + if output.GetError() != common.SigningError_OK { + panic(output.GetError().String()) + } + return hex.EncodeToString(output.GetEncoded()) +} + +func printWallet(w *core.Wallet) { + fmt.Printf("%s wallet: \n", w.CoinType.GetName()) + fmt.Printf("\t address: %s \n", w.Address) + fmt.Printf("\t pri key: %s \n", w.PriKey) + fmt.Printf("\t pub key: %s \n", w.PubKey) + fmt.Println("") } diff --git a/samples/go/protos/binance/Binance.pb.go b/samples/go/protos/binance/Binance.pb.go new file mode 100644 index 00000000000..9205f012247 --- /dev/null +++ b/samples/go/protos/binance/Binance.pb.go @@ -0,0 +1,2965 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.0 +// protoc v3.19.2 +// source: Binance.proto + +package binance + +import ( + common "tw/protos/common" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Transaction struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // int64 SIZE-OF-ENCODED // varint encoded length of the structure after encoding + // 0xF0625DEE // prefix + Msgs [][]byte `protobuf:"bytes,1,rep,name=msgs,proto3" json:"msgs,omitempty"` // array of size 1, containing the transaction message, which are one of the transaction type below + Signatures [][]byte `protobuf:"bytes,2,rep,name=signatures,proto3" json:"signatures,omitempty"` // array of size 1, containing the standard signature structure of the transaction sender + Memo string `protobuf:"bytes,3,opt,name=memo,proto3" json:"memo,omitempty"` // a short sentence of remark for the transaction, only for `Transfer` transactions. + Source int64 `protobuf:"varint,4,opt,name=source,proto3" json:"source,omitempty"` // an identifier for tools triggerring this transaction, set to zero if unwilling to disclose. + Data []byte `protobuf:"bytes,5,opt,name=data,proto3" json:"data,omitempty"` // reserved for future use +} + +func (x *Transaction) Reset() { + *x = Transaction{} + if protoimpl.UnsafeEnabled { + mi := &file_Binance_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Transaction) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Transaction) ProtoMessage() {} + +func (x *Transaction) ProtoReflect() protoreflect.Message { + mi := &file_Binance_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Transaction.ProtoReflect.Descriptor instead. +func (*Transaction) Descriptor() ([]byte, []int) { + return file_Binance_proto_rawDescGZIP(), []int{0} +} + +func (x *Transaction) GetMsgs() [][]byte { + if x != nil { + return x.Msgs + } + return nil +} + +func (x *Transaction) GetSignatures() [][]byte { + if x != nil { + return x.Signatures + } + return nil +} + +func (x *Transaction) GetMemo() string { + if x != nil { + return x.Memo + } + return "" +} + +func (x *Transaction) GetSource() int64 { + if x != nil { + return x.Source + } + return 0 +} + +func (x *Transaction) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +type Signature struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PubKey []byte `protobuf:"bytes,1,opt,name=pub_key,json=pubKey,proto3" json:"pub_key,omitempty"` // public key bytes of the signer address + Signature []byte `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` // signature bytes, please check chain access section for signature generation + AccountNumber int64 `protobuf:"varint,3,opt,name=account_number,json=accountNumber,proto3" json:"account_number,omitempty"` // another identifier of signer, which can be read from chain by account REST API or RPC + Sequence int64 `protobuf:"varint,4,opt,name=sequence,proto3" json:"sequence,omitempty"` // sequence number for the next transaction +} + +func (x *Signature) Reset() { + *x = Signature{} + if protoimpl.UnsafeEnabled { + mi := &file_Binance_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Signature) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Signature) ProtoMessage() {} + +func (x *Signature) ProtoReflect() protoreflect.Message { + mi := &file_Binance_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Signature.ProtoReflect.Descriptor instead. +func (*Signature) Descriptor() ([]byte, []int) { + return file_Binance_proto_rawDescGZIP(), []int{1} +} + +func (x *Signature) GetPubKey() []byte { + if x != nil { + return x.PubKey + } + return nil +} + +func (x *Signature) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +func (x *Signature) GetAccountNumber() int64 { + if x != nil { + return x.AccountNumber + } + return 0 +} + +func (x *Signature) GetSequence() int64 { + if x != nil { + return x.Sequence + } + return 0 +} + +type TradeOrder struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // 0xCE6DC043 // prefix + Sender []byte `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty"` // originating address + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` // order id, optional + Symbol string `protobuf:"bytes,3,opt,name=symbol,proto3" json:"symbol,omitempty"` // symbol for trading pair in full name of the tokens + Ordertype int64 `protobuf:"varint,4,opt,name=ordertype,proto3" json:"ordertype,omitempty"` // only accept 2 for now, meaning limit order + Side int64 `protobuf:"varint,5,opt,name=side,proto3" json:"side,omitempty"` // 1 for buy and 2 fory sell + Price int64 `protobuf:"varint,6,opt,name=price,proto3" json:"price,omitempty"` // price of the order, which is the real price multiplied by 1e8 (10^8) and rounded to integer + Quantity int64 `protobuf:"varint,7,opt,name=quantity,proto3" json:"quantity,omitempty"` // quantity of the order, which is the real price multiplied by 1e8 (10^8) and rounded to integer + Timeinforce int64 `protobuf:"varint,8,opt,name=timeinforce,proto3" json:"timeinforce,omitempty"` // 1 for Good Till Expire(GTE) order and 3 for Immediate Or Cancel (IOC) +} + +func (x *TradeOrder) Reset() { + *x = TradeOrder{} + if protoimpl.UnsafeEnabled { + mi := &file_Binance_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TradeOrder) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TradeOrder) ProtoMessage() {} + +func (x *TradeOrder) ProtoReflect() protoreflect.Message { + mi := &file_Binance_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TradeOrder.ProtoReflect.Descriptor instead. +func (*TradeOrder) Descriptor() ([]byte, []int) { + return file_Binance_proto_rawDescGZIP(), []int{2} +} + +func (x *TradeOrder) GetSender() []byte { + if x != nil { + return x.Sender + } + return nil +} + +func (x *TradeOrder) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *TradeOrder) GetSymbol() string { + if x != nil { + return x.Symbol + } + return "" +} + +func (x *TradeOrder) GetOrdertype() int64 { + if x != nil { + return x.Ordertype + } + return 0 +} + +func (x *TradeOrder) GetSide() int64 { + if x != nil { + return x.Side + } + return 0 +} + +func (x *TradeOrder) GetPrice() int64 { + if x != nil { + return x.Price + } + return 0 +} + +func (x *TradeOrder) GetQuantity() int64 { + if x != nil { + return x.Quantity + } + return 0 +} + +func (x *TradeOrder) GetTimeinforce() int64 { + if x != nil { + return x.Timeinforce + } + return 0 +} + +type CancelTradeOrder struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // 0x166E681B // prefix + Sender []byte `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty"` // originating address + Symbol string `protobuf:"bytes,2,opt,name=symbol,proto3" json:"symbol,omitempty"` // symbol for trading pair in full name of the tokens + Refid string `protobuf:"bytes,3,opt,name=refid,proto3" json:"refid,omitempty"` // order id to cancel +} + +func (x *CancelTradeOrder) Reset() { + *x = CancelTradeOrder{} + if protoimpl.UnsafeEnabled { + mi := &file_Binance_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CancelTradeOrder) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CancelTradeOrder) ProtoMessage() {} + +func (x *CancelTradeOrder) ProtoReflect() protoreflect.Message { + mi := &file_Binance_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CancelTradeOrder.ProtoReflect.Descriptor instead. +func (*CancelTradeOrder) Descriptor() ([]byte, []int) { + return file_Binance_proto_rawDescGZIP(), []int{3} +} + +func (x *CancelTradeOrder) GetSender() []byte { + if x != nil { + return x.Sender + } + return nil +} + +func (x *CancelTradeOrder) GetSymbol() string { + if x != nil { + return x.Symbol + } + return "" +} + +func (x *CancelTradeOrder) GetRefid() string { + if x != nil { + return x.Refid + } + return "" +} + +type SendOrder struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Inputs []*SendOrder_Input `protobuf:"bytes,1,rep,name=inputs,proto3" json:"inputs,omitempty"` + Outputs []*SendOrder_Output `protobuf:"bytes,2,rep,name=outputs,proto3" json:"outputs,omitempty"` +} + +func (x *SendOrder) Reset() { + *x = SendOrder{} + if protoimpl.UnsafeEnabled { + mi := &file_Binance_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SendOrder) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendOrder) ProtoMessage() {} + +func (x *SendOrder) ProtoReflect() protoreflect.Message { + mi := &file_Binance_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendOrder.ProtoReflect.Descriptor instead. +func (*SendOrder) Descriptor() ([]byte, []int) { + return file_Binance_proto_rawDescGZIP(), []int{4} +} + +func (x *SendOrder) GetInputs() []*SendOrder_Input { + if x != nil { + return x.Inputs + } + return nil +} + +func (x *SendOrder) GetOutputs() []*SendOrder_Output { + if x != nil { + return x.Outputs + } + return nil +} + +type TokenIssueOrder struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // 0x17EFAB80 // prefix + From []byte `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` // owner address + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` // token name + Symbol string `protobuf:"bytes,3,opt,name=symbol,proto3" json:"symbol,omitempty"` // token symbol, in full name with "-" suffix + TotalSupply int64 `protobuf:"varint,4,opt,name=total_supply,json=totalSupply,proto3" json:"total_supply,omitempty"` // total supply + Mintable bool `protobuf:"varint,5,opt,name=mintable,proto3" json:"mintable,omitempty"` // mintable +} + +func (x *TokenIssueOrder) Reset() { + *x = TokenIssueOrder{} + if protoimpl.UnsafeEnabled { + mi := &file_Binance_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TokenIssueOrder) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TokenIssueOrder) ProtoMessage() {} + +func (x *TokenIssueOrder) ProtoReflect() protoreflect.Message { + mi := &file_Binance_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TokenIssueOrder.ProtoReflect.Descriptor instead. +func (*TokenIssueOrder) Descriptor() ([]byte, []int) { + return file_Binance_proto_rawDescGZIP(), []int{5} +} + +func (x *TokenIssueOrder) GetFrom() []byte { + if x != nil { + return x.From + } + return nil +} + +func (x *TokenIssueOrder) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *TokenIssueOrder) GetSymbol() string { + if x != nil { + return x.Symbol + } + return "" +} + +func (x *TokenIssueOrder) GetTotalSupply() int64 { + if x != nil { + return x.TotalSupply + } + return 0 +} + +func (x *TokenIssueOrder) GetMintable() bool { + if x != nil { + return x.Mintable + } + return false +} + +type TokenMintOrder struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // 0x467E0829 // prefix + From []byte `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` // owner address + Symbol string `protobuf:"bytes,2,opt,name=symbol,proto3" json:"symbol,omitempty"` // token symbol, in full name with "-" suffix + Amount int64 `protobuf:"varint,3,opt,name=amount,proto3" json:"amount,omitempty"` // amount to mint +} + +func (x *TokenMintOrder) Reset() { + *x = TokenMintOrder{} + if protoimpl.UnsafeEnabled { + mi := &file_Binance_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TokenMintOrder) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TokenMintOrder) ProtoMessage() {} + +func (x *TokenMintOrder) ProtoReflect() protoreflect.Message { + mi := &file_Binance_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TokenMintOrder.ProtoReflect.Descriptor instead. +func (*TokenMintOrder) Descriptor() ([]byte, []int) { + return file_Binance_proto_rawDescGZIP(), []int{6} +} + +func (x *TokenMintOrder) GetFrom() []byte { + if x != nil { + return x.From + } + return nil +} + +func (x *TokenMintOrder) GetSymbol() string { + if x != nil { + return x.Symbol + } + return "" +} + +func (x *TokenMintOrder) GetAmount() int64 { + if x != nil { + return x.Amount + } + return 0 +} + +type TokenBurnOrder struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // 0x7ED2D2A0 // prefix + From []byte `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` // owner address + Symbol string `protobuf:"bytes,2,opt,name=symbol,proto3" json:"symbol,omitempty"` // token symbol, in full name with "-" suffix + Amount int64 `protobuf:"varint,3,opt,name=amount,proto3" json:"amount,omitempty"` // amount to burn +} + +func (x *TokenBurnOrder) Reset() { + *x = TokenBurnOrder{} + if protoimpl.UnsafeEnabled { + mi := &file_Binance_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TokenBurnOrder) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TokenBurnOrder) ProtoMessage() {} + +func (x *TokenBurnOrder) ProtoReflect() protoreflect.Message { + mi := &file_Binance_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TokenBurnOrder.ProtoReflect.Descriptor instead. +func (*TokenBurnOrder) Descriptor() ([]byte, []int) { + return file_Binance_proto_rawDescGZIP(), []int{7} +} + +func (x *TokenBurnOrder) GetFrom() []byte { + if x != nil { + return x.From + } + return nil +} + +func (x *TokenBurnOrder) GetSymbol() string { + if x != nil { + return x.Symbol + } + return "" +} + +func (x *TokenBurnOrder) GetAmount() int64 { + if x != nil { + return x.Amount + } + return 0 +} + +type TokenFreezeOrder struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // 0xE774B32D // prefix + From []byte `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` // owner address + Symbol string `protobuf:"bytes,2,opt,name=symbol,proto3" json:"symbol,omitempty"` // token symbol, in full name with "-" suffix + Amount int64 `protobuf:"varint,3,opt,name=amount,proto3" json:"amount,omitempty"` // amount of token to freeze +} + +func (x *TokenFreezeOrder) Reset() { + *x = TokenFreezeOrder{} + if protoimpl.UnsafeEnabled { + mi := &file_Binance_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TokenFreezeOrder) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TokenFreezeOrder) ProtoMessage() {} + +func (x *TokenFreezeOrder) ProtoReflect() protoreflect.Message { + mi := &file_Binance_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TokenFreezeOrder.ProtoReflect.Descriptor instead. +func (*TokenFreezeOrder) Descriptor() ([]byte, []int) { + return file_Binance_proto_rawDescGZIP(), []int{8} +} + +func (x *TokenFreezeOrder) GetFrom() []byte { + if x != nil { + return x.From + } + return nil +} + +func (x *TokenFreezeOrder) GetSymbol() string { + if x != nil { + return x.Symbol + } + return "" +} + +func (x *TokenFreezeOrder) GetAmount() int64 { + if x != nil { + return x.Amount + } + return 0 +} + +type TokenUnfreezeOrder struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // 0x6515FF0D // prefix + From []byte `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` // owner address + Symbol string `protobuf:"bytes,2,opt,name=symbol,proto3" json:"symbol,omitempty"` // token symbol, in full name with "-" suffix + Amount int64 `protobuf:"varint,3,opt,name=amount,proto3" json:"amount,omitempty"` // amount of token to unfreeze +} + +func (x *TokenUnfreezeOrder) Reset() { + *x = TokenUnfreezeOrder{} + if protoimpl.UnsafeEnabled { + mi := &file_Binance_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TokenUnfreezeOrder) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TokenUnfreezeOrder) ProtoMessage() {} + +func (x *TokenUnfreezeOrder) ProtoReflect() protoreflect.Message { + mi := &file_Binance_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TokenUnfreezeOrder.ProtoReflect.Descriptor instead. +func (*TokenUnfreezeOrder) Descriptor() ([]byte, []int) { + return file_Binance_proto_rawDescGZIP(), []int{9} +} + +func (x *TokenUnfreezeOrder) GetFrom() []byte { + if x != nil { + return x.From + } + return nil +} + +func (x *TokenUnfreezeOrder) GetSymbol() string { + if x != nil { + return x.Symbol + } + return "" +} + +func (x *TokenUnfreezeOrder) GetAmount() int64 { + if x != nil { + return x.Amount + } + return 0 +} + +type HTLTOrder struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // 0xB33F9A24 // prefix + From []byte `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` // signer address + To []byte `protobuf:"bytes,2,opt,name=to,proto3" json:"to,omitempty"` // recipient address + RecipientOtherChain string `protobuf:"bytes,3,opt,name=recipient_other_chain,json=recipientOtherChain,proto3" json:"recipient_other_chain,omitempty"` + SenderOtherChain string `protobuf:"bytes,4,opt,name=sender_other_chain,json=senderOtherChain,proto3" json:"sender_other_chain,omitempty"` + RandomNumberHash []byte `protobuf:"bytes,5,opt,name=random_number_hash,json=randomNumberHash,proto3" json:"random_number_hash,omitempty"` //hash of a random number and timestamp, based on SHA256 + Timestamp int64 `protobuf:"varint,6,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Amount []*SendOrder_Token `protobuf:"bytes,7,rep,name=amount,proto3" json:"amount,omitempty"` + ExpectedIncome string `protobuf:"bytes,8,opt,name=expected_income,json=expectedIncome,proto3" json:"expected_income,omitempty"` // expected gained token on the other chain + HeightSpan int64 `protobuf:"varint,9,opt,name=height_span,json=heightSpan,proto3" json:"height_span,omitempty"` + CrossChain bool `protobuf:"varint,10,opt,name=cross_chain,json=crossChain,proto3" json:"cross_chain,omitempty"` +} + +func (x *HTLTOrder) Reset() { + *x = HTLTOrder{} + if protoimpl.UnsafeEnabled { + mi := &file_Binance_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HTLTOrder) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HTLTOrder) ProtoMessage() {} + +func (x *HTLTOrder) ProtoReflect() protoreflect.Message { + mi := &file_Binance_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HTLTOrder.ProtoReflect.Descriptor instead. +func (*HTLTOrder) Descriptor() ([]byte, []int) { + return file_Binance_proto_rawDescGZIP(), []int{10} +} + +func (x *HTLTOrder) GetFrom() []byte { + if x != nil { + return x.From + } + return nil +} + +func (x *HTLTOrder) GetTo() []byte { + if x != nil { + return x.To + } + return nil +} + +func (x *HTLTOrder) GetRecipientOtherChain() string { + if x != nil { + return x.RecipientOtherChain + } + return "" +} + +func (x *HTLTOrder) GetSenderOtherChain() string { + if x != nil { + return x.SenderOtherChain + } + return "" +} + +func (x *HTLTOrder) GetRandomNumberHash() []byte { + if x != nil { + return x.RandomNumberHash + } + return nil +} + +func (x *HTLTOrder) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *HTLTOrder) GetAmount() []*SendOrder_Token { + if x != nil { + return x.Amount + } + return nil +} + +func (x *HTLTOrder) GetExpectedIncome() string { + if x != nil { + return x.ExpectedIncome + } + return "" +} + +func (x *HTLTOrder) GetHeightSpan() int64 { + if x != nil { + return x.HeightSpan + } + return 0 +} + +func (x *HTLTOrder) GetCrossChain() bool { + if x != nil { + return x.CrossChain + } + return false +} + +type DepositHTLTOrder struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // 0xB33F9A24 // prefix + From []byte `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` // signer address + Amount []*SendOrder_Token `protobuf:"bytes,2,rep,name=amount,proto3" json:"amount,omitempty"` + SwapId []byte `protobuf:"bytes,3,opt,name=swap_id,json=swapId,proto3" json:"swap_id,omitempty"` +} + +func (x *DepositHTLTOrder) Reset() { + *x = DepositHTLTOrder{} + if protoimpl.UnsafeEnabled { + mi := &file_Binance_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DepositHTLTOrder) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DepositHTLTOrder) ProtoMessage() {} + +func (x *DepositHTLTOrder) ProtoReflect() protoreflect.Message { + mi := &file_Binance_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DepositHTLTOrder.ProtoReflect.Descriptor instead. +func (*DepositHTLTOrder) Descriptor() ([]byte, []int) { + return file_Binance_proto_rawDescGZIP(), []int{11} +} + +func (x *DepositHTLTOrder) GetFrom() []byte { + if x != nil { + return x.From + } + return nil +} + +func (x *DepositHTLTOrder) GetAmount() []*SendOrder_Token { + if x != nil { + return x.Amount + } + return nil +} + +func (x *DepositHTLTOrder) GetSwapId() []byte { + if x != nil { + return x.SwapId + } + return nil +} + +type ClaimHTLOrder struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // 0xC1665300 // prefix + From []byte `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` // signer address + SwapId []byte `protobuf:"bytes,2,opt,name=swap_id,json=swapId,proto3" json:"swap_id,omitempty"` + RandomNumber []byte `protobuf:"bytes,3,opt,name=random_number,json=randomNumber,proto3" json:"random_number,omitempty"` +} + +func (x *ClaimHTLOrder) Reset() { + *x = ClaimHTLOrder{} + if protoimpl.UnsafeEnabled { + mi := &file_Binance_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ClaimHTLOrder) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClaimHTLOrder) ProtoMessage() {} + +func (x *ClaimHTLOrder) ProtoReflect() protoreflect.Message { + mi := &file_Binance_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClaimHTLOrder.ProtoReflect.Descriptor instead. +func (*ClaimHTLOrder) Descriptor() ([]byte, []int) { + return file_Binance_proto_rawDescGZIP(), []int{12} +} + +func (x *ClaimHTLOrder) GetFrom() []byte { + if x != nil { + return x.From + } + return nil +} + +func (x *ClaimHTLOrder) GetSwapId() []byte { + if x != nil { + return x.SwapId + } + return nil +} + +func (x *ClaimHTLOrder) GetRandomNumber() []byte { + if x != nil { + return x.RandomNumber + } + return nil +} + +type RefundHTLTOrder struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // 0x3454A27C // prefix + From []byte `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` // signer address + SwapId []byte `protobuf:"bytes,2,opt,name=swap_id,json=swapId,proto3" json:"swap_id,omitempty"` +} + +func (x *RefundHTLTOrder) Reset() { + *x = RefundHTLTOrder{} + if protoimpl.UnsafeEnabled { + mi := &file_Binance_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RefundHTLTOrder) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RefundHTLTOrder) ProtoMessage() {} + +func (x *RefundHTLTOrder) ProtoReflect() protoreflect.Message { + mi := &file_Binance_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RefundHTLTOrder.ProtoReflect.Descriptor instead. +func (*RefundHTLTOrder) Descriptor() ([]byte, []int) { + return file_Binance_proto_rawDescGZIP(), []int{13} +} + +func (x *RefundHTLTOrder) GetFrom() []byte { + if x != nil { + return x.From + } + return nil +} + +func (x *RefundHTLTOrder) GetSwapId() []byte { + if x != nil { + return x.SwapId + } + return nil +} + +type TransferOut struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + From []byte `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` + To []byte `protobuf:"bytes,2,opt,name=to,proto3" json:"to,omitempty"` + Amount *SendOrder_Token `protobuf:"bytes,3,opt,name=amount,proto3" json:"amount,omitempty"` + ExpireTime int64 `protobuf:"varint,4,opt,name=expire_time,json=expireTime,proto3" json:"expire_time,omitempty"` +} + +func (x *TransferOut) Reset() { + *x = TransferOut{} + if protoimpl.UnsafeEnabled { + mi := &file_Binance_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TransferOut) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransferOut) ProtoMessage() {} + +func (x *TransferOut) ProtoReflect() protoreflect.Message { + mi := &file_Binance_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TransferOut.ProtoReflect.Descriptor instead. +func (*TransferOut) Descriptor() ([]byte, []int) { + return file_Binance_proto_rawDescGZIP(), []int{14} +} + +func (x *TransferOut) GetFrom() []byte { + if x != nil { + return x.From + } + return nil +} + +func (x *TransferOut) GetTo() []byte { + if x != nil { + return x.To + } + return nil +} + +func (x *TransferOut) GetAmount() *SendOrder_Token { + if x != nil { + return x.Amount + } + return nil +} + +func (x *TransferOut) GetExpireTime() int64 { + if x != nil { + return x.ExpireTime + } + return 0 +} + +type SideChainDelegate struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DelegatorAddr []byte `protobuf:"bytes,1,opt,name=delegator_addr,json=delegatorAddr,proto3" json:"delegator_addr,omitempty"` + ValidatorAddr []byte `protobuf:"bytes,2,opt,name=validator_addr,json=validatorAddr,proto3" json:"validator_addr,omitempty"` + Delegation *SendOrder_Token `protobuf:"bytes,3,opt,name=delegation,proto3" json:"delegation,omitempty"` + ChainId string `protobuf:"bytes,4,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` +} + +func (x *SideChainDelegate) Reset() { + *x = SideChainDelegate{} + if protoimpl.UnsafeEnabled { + mi := &file_Binance_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SideChainDelegate) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SideChainDelegate) ProtoMessage() {} + +func (x *SideChainDelegate) ProtoReflect() protoreflect.Message { + mi := &file_Binance_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SideChainDelegate.ProtoReflect.Descriptor instead. +func (*SideChainDelegate) Descriptor() ([]byte, []int) { + return file_Binance_proto_rawDescGZIP(), []int{15} +} + +func (x *SideChainDelegate) GetDelegatorAddr() []byte { + if x != nil { + return x.DelegatorAddr + } + return nil +} + +func (x *SideChainDelegate) GetValidatorAddr() []byte { + if x != nil { + return x.ValidatorAddr + } + return nil +} + +func (x *SideChainDelegate) GetDelegation() *SendOrder_Token { + if x != nil { + return x.Delegation + } + return nil +} + +func (x *SideChainDelegate) GetChainId() string { + if x != nil { + return x.ChainId + } + return "" +} + +type SideChainRedelegate struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DelegatorAddr []byte `protobuf:"bytes,1,opt,name=delegator_addr,json=delegatorAddr,proto3" json:"delegator_addr,omitempty"` + ValidatorSrcAddr []byte `protobuf:"bytes,2,opt,name=validator_src_addr,json=validatorSrcAddr,proto3" json:"validator_src_addr,omitempty"` + ValidatorDstAddr []byte `protobuf:"bytes,3,opt,name=validator_dst_addr,json=validatorDstAddr,proto3" json:"validator_dst_addr,omitempty"` + Amount *SendOrder_Token `protobuf:"bytes,4,opt,name=amount,proto3" json:"amount,omitempty"` + ChainId string `protobuf:"bytes,5,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` +} + +func (x *SideChainRedelegate) Reset() { + *x = SideChainRedelegate{} + if protoimpl.UnsafeEnabled { + mi := &file_Binance_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SideChainRedelegate) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SideChainRedelegate) ProtoMessage() {} + +func (x *SideChainRedelegate) ProtoReflect() protoreflect.Message { + mi := &file_Binance_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SideChainRedelegate.ProtoReflect.Descriptor instead. +func (*SideChainRedelegate) Descriptor() ([]byte, []int) { + return file_Binance_proto_rawDescGZIP(), []int{16} +} + +func (x *SideChainRedelegate) GetDelegatorAddr() []byte { + if x != nil { + return x.DelegatorAddr + } + return nil +} + +func (x *SideChainRedelegate) GetValidatorSrcAddr() []byte { + if x != nil { + return x.ValidatorSrcAddr + } + return nil +} + +func (x *SideChainRedelegate) GetValidatorDstAddr() []byte { + if x != nil { + return x.ValidatorDstAddr + } + return nil +} + +func (x *SideChainRedelegate) GetAmount() *SendOrder_Token { + if x != nil { + return x.Amount + } + return nil +} + +func (x *SideChainRedelegate) GetChainId() string { + if x != nil { + return x.ChainId + } + return "" +} + +type SideChainUndelegate struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DelegatorAddr []byte `protobuf:"bytes,1,opt,name=delegator_addr,json=delegatorAddr,proto3" json:"delegator_addr,omitempty"` + ValidatorAddr []byte `protobuf:"bytes,2,opt,name=validator_addr,json=validatorAddr,proto3" json:"validator_addr,omitempty"` + Amount *SendOrder_Token `protobuf:"bytes,3,opt,name=amount,proto3" json:"amount,omitempty"` + ChainId string `protobuf:"bytes,4,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` +} + +func (x *SideChainUndelegate) Reset() { + *x = SideChainUndelegate{} + if protoimpl.UnsafeEnabled { + mi := &file_Binance_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SideChainUndelegate) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SideChainUndelegate) ProtoMessage() {} + +func (x *SideChainUndelegate) ProtoReflect() protoreflect.Message { + mi := &file_Binance_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SideChainUndelegate.ProtoReflect.Descriptor instead. +func (*SideChainUndelegate) Descriptor() ([]byte, []int) { + return file_Binance_proto_rawDescGZIP(), []int{17} +} + +func (x *SideChainUndelegate) GetDelegatorAddr() []byte { + if x != nil { + return x.DelegatorAddr + } + return nil +} + +func (x *SideChainUndelegate) GetValidatorAddr() []byte { + if x != nil { + return x.ValidatorAddr + } + return nil +} + +func (x *SideChainUndelegate) GetAmount() *SendOrder_Token { + if x != nil { + return x.Amount + } + return nil +} + +func (x *SideChainUndelegate) GetChainId() string { + if x != nil { + return x.ChainId + } + return "" +} + +type TimeLockOrder struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + FromAddress []byte `protobuf:"bytes,1,opt,name=from_address,json=fromAddress,proto3" json:"from_address,omitempty"` // owner address + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + // Array of symbol/amount pairs. see SDK https://github.com/binance-chain/javascript-sdk/blob/master/docs/api-docs/classes/tokenmanagement.md#timelock + Amount []*SendOrder_Token `protobuf:"bytes,3,rep,name=amount,proto3" json:"amount,omitempty"` + LockTime int64 `protobuf:"varint,4,opt,name=lock_time,json=lockTime,proto3" json:"lock_time,omitempty"` +} + +func (x *TimeLockOrder) Reset() { + *x = TimeLockOrder{} + if protoimpl.UnsafeEnabled { + mi := &file_Binance_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TimeLockOrder) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TimeLockOrder) ProtoMessage() {} + +func (x *TimeLockOrder) ProtoReflect() protoreflect.Message { + mi := &file_Binance_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TimeLockOrder.ProtoReflect.Descriptor instead. +func (*TimeLockOrder) Descriptor() ([]byte, []int) { + return file_Binance_proto_rawDescGZIP(), []int{18} +} + +func (x *TimeLockOrder) GetFromAddress() []byte { + if x != nil { + return x.FromAddress + } + return nil +} + +func (x *TimeLockOrder) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *TimeLockOrder) GetAmount() []*SendOrder_Token { + if x != nil { + return x.Amount + } + return nil +} + +func (x *TimeLockOrder) GetLockTime() int64 { + if x != nil { + return x.LockTime + } + return 0 +} + +type TimeRelockOrder struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + FromAddress []byte `protobuf:"bytes,1,opt,name=from_address,json=fromAddress,proto3" json:"from_address,omitempty"` // owner address + Id int64 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"` // order ID + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` + // Array of symbol/amount pairs. + Amount []*SendOrder_Token `protobuf:"bytes,4,rep,name=amount,proto3" json:"amount,omitempty"` + LockTime int64 `protobuf:"varint,5,opt,name=lock_time,json=lockTime,proto3" json:"lock_time,omitempty"` +} + +func (x *TimeRelockOrder) Reset() { + *x = TimeRelockOrder{} + if protoimpl.UnsafeEnabled { + mi := &file_Binance_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TimeRelockOrder) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TimeRelockOrder) ProtoMessage() {} + +func (x *TimeRelockOrder) ProtoReflect() protoreflect.Message { + mi := &file_Binance_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TimeRelockOrder.ProtoReflect.Descriptor instead. +func (*TimeRelockOrder) Descriptor() ([]byte, []int) { + return file_Binance_proto_rawDescGZIP(), []int{19} +} + +func (x *TimeRelockOrder) GetFromAddress() []byte { + if x != nil { + return x.FromAddress + } + return nil +} + +func (x *TimeRelockOrder) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *TimeRelockOrder) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *TimeRelockOrder) GetAmount() []*SendOrder_Token { + if x != nil { + return x.Amount + } + return nil +} + +func (x *TimeRelockOrder) GetLockTime() int64 { + if x != nil { + return x.LockTime + } + return 0 +} + +type TimeUnlockOrder struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + FromAddress []byte `protobuf:"bytes,1,opt,name=from_address,json=fromAddress,proto3" json:"from_address,omitempty"` // owner address + Id int64 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"` // order ID +} + +func (x *TimeUnlockOrder) Reset() { + *x = TimeUnlockOrder{} + if protoimpl.UnsafeEnabled { + mi := &file_Binance_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TimeUnlockOrder) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TimeUnlockOrder) ProtoMessage() {} + +func (x *TimeUnlockOrder) ProtoReflect() protoreflect.Message { + mi := &file_Binance_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TimeUnlockOrder.ProtoReflect.Descriptor instead. +func (*TimeUnlockOrder) Descriptor() ([]byte, []int) { + return file_Binance_proto_rawDescGZIP(), []int{20} +} + +func (x *TimeUnlockOrder) GetFromAddress() []byte { + if x != nil { + return x.FromAddress + } + return nil +} + +func (x *TimeUnlockOrder) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +// Input data necessary to create a signed order. +type SigningInput struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ChainId string `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` + AccountNumber int64 `protobuf:"varint,2,opt,name=account_number,json=accountNumber,proto3" json:"account_number,omitempty"` + Sequence int64 `protobuf:"varint,3,opt,name=sequence,proto3" json:"sequence,omitempty"` + Source int64 `protobuf:"varint,4,opt,name=source,proto3" json:"source,omitempty"` + Memo string `protobuf:"bytes,5,opt,name=memo,proto3" json:"memo,omitempty"` + PrivateKey []byte `protobuf:"bytes,6,opt,name=private_key,json=privateKey,proto3" json:"private_key,omitempty"` + // Types that are assignable to OrderOneof: + // *SigningInput_TradeOrder + // *SigningInput_CancelTradeOrder + // *SigningInput_SendOrder + // *SigningInput_FreezeOrder + // *SigningInput_UnfreezeOrder + // *SigningInput_HtltOrder + // *SigningInput_DepositHTLTOrder + // *SigningInput_ClaimHTLTOrder + // *SigningInput_RefundHTLTOrder + // *SigningInput_IssueOrder + // *SigningInput_MintOrder + // *SigningInput_BurnOrder + // *SigningInput_TransferOutOrder + // *SigningInput_SideDelegateOrder + // *SigningInput_SideRedelegateOrder + // *SigningInput_SideUndelegateOrder + // *SigningInput_TimeLockOrder + // *SigningInput_TimeRelockOrder + // *SigningInput_TimeUnlockOrder + OrderOneof isSigningInput_OrderOneof `protobuf_oneof:"order_oneof"` +} + +func (x *SigningInput) Reset() { + *x = SigningInput{} + if protoimpl.UnsafeEnabled { + mi := &file_Binance_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SigningInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SigningInput) ProtoMessage() {} + +func (x *SigningInput) ProtoReflect() protoreflect.Message { + mi := &file_Binance_proto_msgTypes[21] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SigningInput.ProtoReflect.Descriptor instead. +func (*SigningInput) Descriptor() ([]byte, []int) { + return file_Binance_proto_rawDescGZIP(), []int{21} +} + +func (x *SigningInput) GetChainId() string { + if x != nil { + return x.ChainId + } + return "" +} + +func (x *SigningInput) GetAccountNumber() int64 { + if x != nil { + return x.AccountNumber + } + return 0 +} + +func (x *SigningInput) GetSequence() int64 { + if x != nil { + return x.Sequence + } + return 0 +} + +func (x *SigningInput) GetSource() int64 { + if x != nil { + return x.Source + } + return 0 +} + +func (x *SigningInput) GetMemo() string { + if x != nil { + return x.Memo + } + return "" +} + +func (x *SigningInput) GetPrivateKey() []byte { + if x != nil { + return x.PrivateKey + } + return nil +} + +func (m *SigningInput) GetOrderOneof() isSigningInput_OrderOneof { + if m != nil { + return m.OrderOneof + } + return nil +} + +func (x *SigningInput) GetTradeOrder() *TradeOrder { + if x, ok := x.GetOrderOneof().(*SigningInput_TradeOrder); ok { + return x.TradeOrder + } + return nil +} + +func (x *SigningInput) GetCancelTradeOrder() *CancelTradeOrder { + if x, ok := x.GetOrderOneof().(*SigningInput_CancelTradeOrder); ok { + return x.CancelTradeOrder + } + return nil +} + +func (x *SigningInput) GetSendOrder() *SendOrder { + if x, ok := x.GetOrderOneof().(*SigningInput_SendOrder); ok { + return x.SendOrder + } + return nil +} + +func (x *SigningInput) GetFreezeOrder() *TokenFreezeOrder { + if x, ok := x.GetOrderOneof().(*SigningInput_FreezeOrder); ok { + return x.FreezeOrder + } + return nil +} + +func (x *SigningInput) GetUnfreezeOrder() *TokenUnfreezeOrder { + if x, ok := x.GetOrderOneof().(*SigningInput_UnfreezeOrder); ok { + return x.UnfreezeOrder + } + return nil +} + +func (x *SigningInput) GetHtltOrder() *HTLTOrder { + if x, ok := x.GetOrderOneof().(*SigningInput_HtltOrder); ok { + return x.HtltOrder + } + return nil +} + +func (x *SigningInput) GetDepositHTLTOrder() *DepositHTLTOrder { + if x, ok := x.GetOrderOneof().(*SigningInput_DepositHTLTOrder); ok { + return x.DepositHTLTOrder + } + return nil +} + +func (x *SigningInput) GetClaimHTLTOrder() *ClaimHTLOrder { + if x, ok := x.GetOrderOneof().(*SigningInput_ClaimHTLTOrder); ok { + return x.ClaimHTLTOrder + } + return nil +} + +func (x *SigningInput) GetRefundHTLTOrder() *RefundHTLTOrder { + if x, ok := x.GetOrderOneof().(*SigningInput_RefundHTLTOrder); ok { + return x.RefundHTLTOrder + } + return nil +} + +func (x *SigningInput) GetIssueOrder() *TokenIssueOrder { + if x, ok := x.GetOrderOneof().(*SigningInput_IssueOrder); ok { + return x.IssueOrder + } + return nil +} + +func (x *SigningInput) GetMintOrder() *TokenMintOrder { + if x, ok := x.GetOrderOneof().(*SigningInput_MintOrder); ok { + return x.MintOrder + } + return nil +} + +func (x *SigningInput) GetBurnOrder() *TokenBurnOrder { + if x, ok := x.GetOrderOneof().(*SigningInput_BurnOrder); ok { + return x.BurnOrder + } + return nil +} + +func (x *SigningInput) GetTransferOutOrder() *TransferOut { + if x, ok := x.GetOrderOneof().(*SigningInput_TransferOutOrder); ok { + return x.TransferOutOrder + } + return nil +} + +func (x *SigningInput) GetSideDelegateOrder() *SideChainDelegate { + if x, ok := x.GetOrderOneof().(*SigningInput_SideDelegateOrder); ok { + return x.SideDelegateOrder + } + return nil +} + +func (x *SigningInput) GetSideRedelegateOrder() *SideChainRedelegate { + if x, ok := x.GetOrderOneof().(*SigningInput_SideRedelegateOrder); ok { + return x.SideRedelegateOrder + } + return nil +} + +func (x *SigningInput) GetSideUndelegateOrder() *SideChainUndelegate { + if x, ok := x.GetOrderOneof().(*SigningInput_SideUndelegateOrder); ok { + return x.SideUndelegateOrder + } + return nil +} + +func (x *SigningInput) GetTimeLockOrder() *TimeLockOrder { + if x, ok := x.GetOrderOneof().(*SigningInput_TimeLockOrder); ok { + return x.TimeLockOrder + } + return nil +} + +func (x *SigningInput) GetTimeRelockOrder() *TimeRelockOrder { + if x, ok := x.GetOrderOneof().(*SigningInput_TimeRelockOrder); ok { + return x.TimeRelockOrder + } + return nil +} + +func (x *SigningInput) GetTimeUnlockOrder() *TimeUnlockOrder { + if x, ok := x.GetOrderOneof().(*SigningInput_TimeUnlockOrder); ok { + return x.TimeUnlockOrder + } + return nil +} + +type isSigningInput_OrderOneof interface { + isSigningInput_OrderOneof() +} + +type SigningInput_TradeOrder struct { + TradeOrder *TradeOrder `protobuf:"bytes,8,opt,name=trade_order,json=tradeOrder,proto3,oneof"` +} + +type SigningInput_CancelTradeOrder struct { + CancelTradeOrder *CancelTradeOrder `protobuf:"bytes,9,opt,name=cancel_trade_order,json=cancelTradeOrder,proto3,oneof"` +} + +type SigningInput_SendOrder struct { + SendOrder *SendOrder `protobuf:"bytes,10,opt,name=send_order,json=sendOrder,proto3,oneof"` +} + +type SigningInput_FreezeOrder struct { + FreezeOrder *TokenFreezeOrder `protobuf:"bytes,11,opt,name=freeze_order,json=freezeOrder,proto3,oneof"` +} + +type SigningInput_UnfreezeOrder struct { + UnfreezeOrder *TokenUnfreezeOrder `protobuf:"bytes,12,opt,name=unfreeze_order,json=unfreezeOrder,proto3,oneof"` +} + +type SigningInput_HtltOrder struct { + HtltOrder *HTLTOrder `protobuf:"bytes,13,opt,name=htlt_order,json=htltOrder,proto3,oneof"` +} + +type SigningInput_DepositHTLTOrder struct { + DepositHTLTOrder *DepositHTLTOrder `protobuf:"bytes,14,opt,name=depositHTLT_order,json=depositHTLTOrder,proto3,oneof"` +} + +type SigningInput_ClaimHTLTOrder struct { + ClaimHTLTOrder *ClaimHTLOrder `protobuf:"bytes,15,opt,name=claimHTLT_order,json=claimHTLTOrder,proto3,oneof"` +} + +type SigningInput_RefundHTLTOrder struct { + RefundHTLTOrder *RefundHTLTOrder `protobuf:"bytes,16,opt,name=refundHTLT_order,json=refundHTLTOrder,proto3,oneof"` +} + +type SigningInput_IssueOrder struct { + IssueOrder *TokenIssueOrder `protobuf:"bytes,17,opt,name=issue_order,json=issueOrder,proto3,oneof"` +} + +type SigningInput_MintOrder struct { + MintOrder *TokenMintOrder `protobuf:"bytes,18,opt,name=mint_order,json=mintOrder,proto3,oneof"` +} + +type SigningInput_BurnOrder struct { + BurnOrder *TokenBurnOrder `protobuf:"bytes,19,opt,name=burn_order,json=burnOrder,proto3,oneof"` +} + +type SigningInput_TransferOutOrder struct { + TransferOutOrder *TransferOut `protobuf:"bytes,20,opt,name=transfer_out_order,json=transferOutOrder,proto3,oneof"` +} + +type SigningInput_SideDelegateOrder struct { + SideDelegateOrder *SideChainDelegate `protobuf:"bytes,21,opt,name=side_delegate_order,json=sideDelegateOrder,proto3,oneof"` +} + +type SigningInput_SideRedelegateOrder struct { + SideRedelegateOrder *SideChainRedelegate `protobuf:"bytes,22,opt,name=side_redelegate_order,json=sideRedelegateOrder,proto3,oneof"` +} + +type SigningInput_SideUndelegateOrder struct { + SideUndelegateOrder *SideChainUndelegate `protobuf:"bytes,23,opt,name=side_undelegate_order,json=sideUndelegateOrder,proto3,oneof"` +} + +type SigningInput_TimeLockOrder struct { + TimeLockOrder *TimeLockOrder `protobuf:"bytes,24,opt,name=time_lock_order,json=timeLockOrder,proto3,oneof"` +} + +type SigningInput_TimeRelockOrder struct { + TimeRelockOrder *TimeRelockOrder `protobuf:"bytes,25,opt,name=time_relock_order,json=timeRelockOrder,proto3,oneof"` +} + +type SigningInput_TimeUnlockOrder struct { + TimeUnlockOrder *TimeUnlockOrder `protobuf:"bytes,26,opt,name=time_unlock_order,json=timeUnlockOrder,proto3,oneof"` +} + +func (*SigningInput_TradeOrder) isSigningInput_OrderOneof() {} + +func (*SigningInput_CancelTradeOrder) isSigningInput_OrderOneof() {} + +func (*SigningInput_SendOrder) isSigningInput_OrderOneof() {} + +func (*SigningInput_FreezeOrder) isSigningInput_OrderOneof() {} + +func (*SigningInput_UnfreezeOrder) isSigningInput_OrderOneof() {} + +func (*SigningInput_HtltOrder) isSigningInput_OrderOneof() {} + +func (*SigningInput_DepositHTLTOrder) isSigningInput_OrderOneof() {} + +func (*SigningInput_ClaimHTLTOrder) isSigningInput_OrderOneof() {} + +func (*SigningInput_RefundHTLTOrder) isSigningInput_OrderOneof() {} + +func (*SigningInput_IssueOrder) isSigningInput_OrderOneof() {} + +func (*SigningInput_MintOrder) isSigningInput_OrderOneof() {} + +func (*SigningInput_BurnOrder) isSigningInput_OrderOneof() {} + +func (*SigningInput_TransferOutOrder) isSigningInput_OrderOneof() {} + +func (*SigningInput_SideDelegateOrder) isSigningInput_OrderOneof() {} + +func (*SigningInput_SideRedelegateOrder) isSigningInput_OrderOneof() {} + +func (*SigningInput_SideUndelegateOrder) isSigningInput_OrderOneof() {} + +func (*SigningInput_TimeLockOrder) isSigningInput_OrderOneof() {} + +func (*SigningInput_TimeRelockOrder) isSigningInput_OrderOneof() {} + +func (*SigningInput_TimeUnlockOrder) isSigningInput_OrderOneof() {} + +// Transaction signing output. +type SigningOutput struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Signed and encoded transaction bytes. + Encoded []byte `protobuf:"bytes,1,opt,name=encoded,proto3" json:"encoded,omitempty"` + /// error code, 0 is ok, other codes will be treated as errors + Error common.SigningError `protobuf:"varint,2,opt,name=error,proto3,enum=TW.Common.Proto.SigningError" json:"error,omitempty"` + /// error description + ErrorMessage string `protobuf:"bytes,3,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` +} + +func (x *SigningOutput) Reset() { + *x = SigningOutput{} + if protoimpl.UnsafeEnabled { + mi := &file_Binance_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SigningOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SigningOutput) ProtoMessage() {} + +func (x *SigningOutput) ProtoReflect() protoreflect.Message { + mi := &file_Binance_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SigningOutput.ProtoReflect.Descriptor instead. +func (*SigningOutput) Descriptor() ([]byte, []int) { + return file_Binance_proto_rawDescGZIP(), []int{22} +} + +func (x *SigningOutput) GetEncoded() []byte { + if x != nil { + return x.Encoded + } + return nil +} + +func (x *SigningOutput) GetError() common.SigningError { + if x != nil { + return x.Error + } + return common.SigningError(0) +} + +func (x *SigningOutput) GetErrorMessage() string { + if x != nil { + return x.ErrorMessage + } + return "" +} + +type Signature_PubKey struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *Signature_PubKey) Reset() { + *x = Signature_PubKey{} + if protoimpl.UnsafeEnabled { + mi := &file_Binance_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Signature_PubKey) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Signature_PubKey) ProtoMessage() {} + +func (x *Signature_PubKey) ProtoReflect() protoreflect.Message { + mi := &file_Binance_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Signature_PubKey.ProtoReflect.Descriptor instead. +func (*Signature_PubKey) Descriptor() ([]byte, []int) { + return file_Binance_proto_rawDescGZIP(), []int{1, 0} +} + +// 0x2A2C87FA +// A symbol-amount pair. Could be moved out of SendOrder; kept here for backward compatibility. +type SendOrder_Token struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Denom string `protobuf:"bytes,1,opt,name=denom,proto3" json:"denom,omitempty"` + Amount int64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"` +} + +func (x *SendOrder_Token) Reset() { + *x = SendOrder_Token{} + if protoimpl.UnsafeEnabled { + mi := &file_Binance_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SendOrder_Token) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendOrder_Token) ProtoMessage() {} + +func (x *SendOrder_Token) ProtoReflect() protoreflect.Message { + mi := &file_Binance_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendOrder_Token.ProtoReflect.Descriptor instead. +func (*SendOrder_Token) Descriptor() ([]byte, []int) { + return file_Binance_proto_rawDescGZIP(), []int{4, 0} +} + +func (x *SendOrder_Token) GetDenom() string { + if x != nil { + return x.Denom + } + return "" +} + +func (x *SendOrder_Token) GetAmount() int64 { + if x != nil { + return x.Amount + } + return 0 +} + +type SendOrder_Input struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address []byte `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Coins []*SendOrder_Token `protobuf:"bytes,2,rep,name=coins,proto3" json:"coins,omitempty"` +} + +func (x *SendOrder_Input) Reset() { + *x = SendOrder_Input{} + if protoimpl.UnsafeEnabled { + mi := &file_Binance_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SendOrder_Input) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendOrder_Input) ProtoMessage() {} + +func (x *SendOrder_Input) ProtoReflect() protoreflect.Message { + mi := &file_Binance_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendOrder_Input.ProtoReflect.Descriptor instead. +func (*SendOrder_Input) Descriptor() ([]byte, []int) { + return file_Binance_proto_rawDescGZIP(), []int{4, 1} +} + +func (x *SendOrder_Input) GetAddress() []byte { + if x != nil { + return x.Address + } + return nil +} + +func (x *SendOrder_Input) GetCoins() []*SendOrder_Token { + if x != nil { + return x.Coins + } + return nil +} + +type SendOrder_Output struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address []byte `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Coins []*SendOrder_Token `protobuf:"bytes,2,rep,name=coins,proto3" json:"coins,omitempty"` +} + +func (x *SendOrder_Output) Reset() { + *x = SendOrder_Output{} + if protoimpl.UnsafeEnabled { + mi := &file_Binance_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SendOrder_Output) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendOrder_Output) ProtoMessage() {} + +func (x *SendOrder_Output) ProtoReflect() protoreflect.Message { + mi := &file_Binance_proto_msgTypes[26] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendOrder_Output.ProtoReflect.Descriptor instead. +func (*SendOrder_Output) Descriptor() ([]byte, []int) { + return file_Binance_proto_rawDescGZIP(), []int{4, 2} +} + +func (x *SendOrder_Output) GetAddress() []byte { + if x != nil { + return x.Address + } + return nil +} + +func (x *SendOrder_Output) GetCoins() []*SendOrder_Token { + if x != nil { + return x.Coins + } + return nil +} + +var File_Binance_proto protoreflect.FileDescriptor + +var file_Binance_proto_rawDesc = []byte{ + 0x0a, 0x0d, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x10, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x1a, 0x0c, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, + 0x81, 0x01, 0x0a, 0x0b, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x12, 0x0a, 0x04, 0x6d, 0x73, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x04, 0x6d, + 0x73, 0x67, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, + 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, + 0x61, 0x74, 0x61, 0x22, 0x8f, 0x01, 0x0a, 0x09, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, + 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0d, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, + 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x08, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x1a, 0x08, 0x0a, 0x06, 0x50, + 0x75, 0x62, 0x4b, 0x65, 0x79, 0x22, 0xd2, 0x01, 0x0a, 0x0a, 0x54, 0x72, 0x61, 0x64, 0x65, 0x4f, + 0x72, 0x64, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, + 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x79, + 0x6d, 0x62, 0x6f, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x74, 0x79, + 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x04, 0x73, 0x69, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, + 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, + 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x74, 0x69, 0x6d, 0x65, + 0x69, 0x6e, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, + 0x69, 0x6d, 0x65, 0x69, 0x6e, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x22, 0x58, 0x0a, 0x10, 0x43, 0x61, + 0x6e, 0x63, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x64, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, + 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x12, 0x14, + 0x0a, 0x05, 0x72, 0x65, 0x66, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x72, + 0x65, 0x66, 0x69, 0x64, 0x22, 0xf4, 0x02, 0x0a, 0x09, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, + 0x65, 0x72, 0x12, 0x39, 0x0a, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x2e, + 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x3c, 0x0a, + 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, + 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x2e, 0x4f, 0x75, 0x74, 0x70, + 0x75, 0x74, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x1a, 0x35, 0x0a, 0x05, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x1a, 0x5a, 0x0a, 0x05, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x37, 0x0a, 0x05, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, + 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, + 0x72, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x05, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x1a, 0x5b, + 0x0a, 0x06, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x12, 0x37, 0x0a, 0x05, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x21, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x2e, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x05, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x22, 0x90, 0x01, 0x0a, 0x0f, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, + 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x66, + 0x72, 0x6f, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x79, 0x6d, 0x62, 0x6f, + 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x12, + 0x21, 0x0a, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x53, 0x75, 0x70, 0x70, + 0x6c, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x69, 0x6e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x54, + 0x0a, 0x0e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x4d, 0x69, 0x6e, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, + 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, + 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x12, 0x16, 0x0a, 0x06, + 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x54, 0x0a, 0x0e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x42, 0x75, 0x72, + 0x6e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x79, + 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x79, 0x6d, 0x62, + 0x6f, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x56, 0x0a, 0x10, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x46, 0x72, 0x65, 0x65, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x12, + 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x66, 0x72, + 0x6f, 0x6d, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x22, 0x58, 0x0a, 0x12, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x55, 0x6e, 0x66, 0x72, 0x65, + 0x65, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x16, 0x0a, 0x06, + 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x79, + 0x6d, 0x62, 0x6f, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x83, 0x03, 0x0a, + 0x09, 0x48, 0x54, 0x4c, 0x54, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, + 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x0e, + 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x74, 0x6f, 0x12, 0x32, + 0x0a, 0x15, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x6f, 0x74, 0x68, 0x65, + 0x72, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x72, + 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x43, 0x68, 0x61, + 0x69, 0x6e, 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x6f, 0x74, 0x68, + 0x65, 0x72, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, + 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x43, 0x68, 0x61, 0x69, 0x6e, + 0x12, 0x2c, 0x0a, 0x12, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, + 0x72, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x72, 0x61, + 0x6e, 0x64, 0x6f, 0x6d, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1c, + 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x39, 0x0a, 0x06, + 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x54, + 0x57, 0x2e, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, + 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x65, 0x78, 0x70, 0x65, 0x63, + 0x74, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0e, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x49, 0x6e, 0x63, 0x6f, 0x6d, 0x65, + 0x12, 0x1f, 0x0a, 0x0b, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x73, 0x70, 0x61, 0x6e, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x53, 0x70, 0x61, + 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x43, 0x68, 0x61, + 0x69, 0x6e, 0x22, 0x7a, 0x0a, 0x10, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x48, 0x54, 0x4c, + 0x54, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x39, 0x0a, 0x06, 0x61, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x54, 0x57, 0x2e, + 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x65, + 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x06, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x69, 0x64, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x77, 0x61, 0x70, 0x49, 0x64, 0x22, 0x61, + 0x0a, 0x0d, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x48, 0x54, 0x4c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, + 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x66, + 0x72, 0x6f, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x77, 0x61, 0x70, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, + 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x4e, 0x75, 0x6d, 0x62, 0x65, + 0x72, 0x22, 0x3e, 0x0a, 0x0f, 0x52, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x48, 0x54, 0x4c, 0x54, 0x4f, + 0x72, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x77, 0x61, 0x70, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x77, 0x61, 0x70, 0x49, + 0x64, 0x22, 0x8d, 0x01, 0x0a, 0x0b, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x4f, 0x75, + 0x74, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x02, 0x74, 0x6f, 0x12, 0x39, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x6e, 0x61, 0x6e, + 0x63, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, + 0x65, 0x72, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x54, 0x69, 0x6d, + 0x65, 0x22, 0xbf, 0x01, 0x0a, 0x11, 0x53, 0x69, 0x64, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x44, + 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x65, 0x6c, 0x65, 0x67, + 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x0d, 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x64, 0x72, 0x12, 0x25, + 0x0a, 0x0e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, + 0x72, 0x41, 0x64, 0x64, 0x72, 0x12, 0x41, 0x0a, 0x0a, 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x54, 0x57, 0x2e, 0x42, + 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x65, 0x6e, + 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x0a, 0x64, 0x65, + 0x6c, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x49, 0x64, 0x22, 0xee, 0x01, 0x0a, 0x13, 0x53, 0x69, 0x64, 0x65, 0x43, 0x68, 0x61, 0x69, + 0x6e, 0x52, 0x65, 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x64, + 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, + 0x64, 0x72, 0x12, 0x2c, 0x0a, 0x12, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x5f, + 0x73, 0x72, 0x63, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, + 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x53, 0x72, 0x63, 0x41, 0x64, 0x64, 0x72, + 0x12, 0x2c, 0x0a, 0x12, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x64, 0x73, + 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x76, 0x61, + 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x44, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x39, + 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, + 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x2e, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, + 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, 0x61, + 0x69, 0x6e, 0x49, 0x64, 0x22, 0xb9, 0x01, 0x0a, 0x13, 0x53, 0x69, 0x64, 0x65, 0x43, 0x68, 0x61, + 0x69, 0x6e, 0x55, 0x6e, 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x12, 0x25, 0x0a, 0x0e, + 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x6f, 0x72, 0x41, + 0x64, 0x64, 0x72, 0x12, 0x25, 0x0a, 0x0e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, + 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x76, 0x61, 0x6c, + 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x64, 0x72, 0x12, 0x39, 0x0a, 0x06, 0x61, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x54, 0x57, 0x2e, + 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x65, + 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x06, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, + 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, + 0x22, 0xac, 0x01, 0x0a, 0x0d, 0x54, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x4f, 0x72, 0x64, + 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x66, 0x72, 0x6f, 0x6d, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x6e, + 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, + 0x72, 0x64, 0x65, 0x72, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x54, 0x69, 0x6d, 0x65, 0x22, + 0xbe, 0x01, 0x0a, 0x0f, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x65, 0x6c, 0x6f, 0x63, 0x6b, 0x4f, 0x72, + 0x64, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x66, 0x72, 0x6f, 0x6d, 0x41, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, + 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x65, 0x6e, 0x64, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x06, 0x61, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x54, 0x69, 0x6d, 0x65, + 0x22, 0x44, 0x0a, 0x0f, 0x54, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x4f, 0x72, + 0x64, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x66, 0x72, 0x6f, 0x6d, 0x41, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x22, 0xf9, 0x0c, 0x0a, 0x0c, 0x53, 0x69, 0x67, 0x6e, 0x69, + 0x6e, 0x67, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6e, 0x75, + 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x61, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x71, + 0x75, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x73, 0x65, 0x71, + 0x75, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, + 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x65, 0x6d, + 0x6f, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, + 0x65, 0x79, 0x12, 0x3f, 0x0a, 0x0b, 0x74, 0x72, 0x61, 0x64, 0x65, 0x5f, 0x6f, 0x72, 0x64, 0x65, + 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x6e, + 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x72, 0x61, 0x64, 0x65, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x48, 0x00, 0x52, 0x0a, 0x74, 0x72, 0x61, 0x64, 0x65, 0x4f, 0x72, + 0x64, 0x65, 0x72, 0x12, 0x52, 0x0a, 0x12, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x5f, 0x74, 0x72, + 0x61, 0x64, 0x65, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x22, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x64, 0x65, 0x4f, 0x72, + 0x64, 0x65, 0x72, 0x48, 0x00, 0x52, 0x10, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x54, 0x72, 0x61, + 0x64, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x3c, 0x0a, 0x0a, 0x73, 0x65, 0x6e, 0x64, 0x5f, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x54, 0x57, + 0x2e, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, + 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x48, 0x00, 0x52, 0x09, 0x73, 0x65, 0x6e, 0x64, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x47, 0x0a, 0x0c, 0x66, 0x72, 0x65, 0x65, 0x7a, 0x65, 0x5f, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x54, 0x57, + 0x2e, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x46, 0x72, 0x65, 0x65, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x48, + 0x00, 0x52, 0x0b, 0x66, 0x72, 0x65, 0x65, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x4d, + 0x0a, 0x0e, 0x75, 0x6e, 0x66, 0x72, 0x65, 0x65, 0x7a, 0x65, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x6e, 0x61, + 0x6e, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x55, + 0x6e, 0x66, 0x72, 0x65, 0x65, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x48, 0x00, 0x52, 0x0d, + 0x75, 0x6e, 0x66, 0x72, 0x65, 0x65, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x3c, 0x0a, + 0x0a, 0x68, 0x74, 0x6c, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1b, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x54, 0x4c, 0x54, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x48, 0x00, + 0x52, 0x09, 0x68, 0x74, 0x6c, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x51, 0x0a, 0x11, 0x64, + 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x48, 0x54, 0x4c, 0x54, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x6e, 0x61, + 0x6e, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, + 0x74, 0x48, 0x54, 0x4c, 0x54, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x48, 0x00, 0x52, 0x10, 0x64, 0x65, + 0x70, 0x6f, 0x73, 0x69, 0x74, 0x48, 0x54, 0x4c, 0x54, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x4a, + 0x0a, 0x0f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x48, 0x54, 0x4c, 0x54, 0x5f, 0x6f, 0x72, 0x64, 0x65, + 0x72, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x6e, + 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, + 0x48, 0x54, 0x4c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x48, 0x00, 0x52, 0x0e, 0x63, 0x6c, 0x61, 0x69, + 0x6d, 0x48, 0x54, 0x4c, 0x54, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x4e, 0x0a, 0x10, 0x72, 0x65, + 0x66, 0x75, 0x6e, 0x64, 0x48, 0x54, 0x4c, 0x54, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x10, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, + 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x48, 0x54, + 0x4c, 0x54, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x48, 0x00, 0x52, 0x0f, 0x72, 0x65, 0x66, 0x75, 0x6e, + 0x64, 0x48, 0x54, 0x4c, 0x54, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x44, 0x0a, 0x0b, 0x69, 0x73, + 0x73, 0x75, 0x65, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x21, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x4f, 0x72, 0x64, + 0x65, 0x72, 0x48, 0x00, 0x52, 0x0a, 0x69, 0x73, 0x73, 0x75, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, + 0x12, 0x41, 0x0a, 0x0a, 0x6d, 0x69, 0x6e, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x12, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, + 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x4d, 0x69, 0x6e, + 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x48, 0x00, 0x52, 0x09, 0x6d, 0x69, 0x6e, 0x74, 0x4f, 0x72, + 0x64, 0x65, 0x72, 0x12, 0x41, 0x0a, 0x0a, 0x62, 0x75, 0x72, 0x6e, 0x5f, 0x6f, 0x72, 0x64, 0x65, + 0x72, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x6e, + 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x42, 0x75, 0x72, 0x6e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x48, 0x00, 0x52, 0x09, 0x62, 0x75, 0x72, + 0x6e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x4d, 0x0a, 0x12, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, + 0x65, 0x72, 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x14, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x4f, 0x75, + 0x74, 0x48, 0x00, 0x52, 0x10, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x4f, 0x75, 0x74, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x55, 0x0a, 0x13, 0x73, 0x69, 0x64, 0x65, 0x5f, 0x64, 0x65, + 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x15, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x69, 0x64, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x44, + 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x11, 0x73, 0x69, 0x64, 0x65, 0x44, + 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x5b, 0x0a, 0x15, + 0x73, 0x69, 0x64, 0x65, 0x5f, 0x72, 0x65, 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x5f, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x54, 0x57, + 0x2e, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, + 0x69, 0x64, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, + 0x74, 0x65, 0x48, 0x00, 0x52, 0x13, 0x73, 0x69, 0x64, 0x65, 0x52, 0x65, 0x64, 0x65, 0x6c, 0x65, + 0x67, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x5b, 0x0a, 0x15, 0x73, 0x69, 0x64, + 0x65, 0x5f, 0x75, 0x6e, 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x5f, 0x6f, 0x72, 0x64, + 0x65, 0x72, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, + 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x69, 0x64, 0x65, + 0x43, 0x68, 0x61, 0x69, 0x6e, 0x55, 0x6e, 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x48, + 0x00, 0x52, 0x13, 0x73, 0x69, 0x64, 0x65, 0x55, 0x6e, 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, + 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x49, 0x0a, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, + 0x6f, 0x63, 0x6b, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x18, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1f, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x4f, 0x72, 0x64, 0x65, 0x72, + 0x48, 0x00, 0x52, 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x4f, 0x72, 0x64, 0x65, + 0x72, 0x12, 0x4f, 0x0a, 0x11, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x72, 0x65, 0x6c, 0x6f, 0x63, 0x6b, + 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x54, + 0x57, 0x2e, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x54, 0x69, 0x6d, 0x65, 0x52, 0x65, 0x6c, 0x6f, 0x63, 0x6b, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x48, + 0x00, 0x52, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x52, 0x65, 0x6c, 0x6f, 0x63, 0x6b, 0x4f, 0x72, 0x64, + 0x65, 0x72, 0x12, 0x4f, 0x0a, 0x11, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x75, 0x6e, 0x6c, 0x6f, 0x63, + 0x6b, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, + 0x54, 0x57, 0x2e, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x4f, 0x72, 0x64, 0x65, 0x72, + 0x48, 0x00, 0x52, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x4f, 0x72, + 0x64, 0x65, 0x72, 0x42, 0x0d, 0x0a, 0x0b, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x6f, 0x6e, 0x65, + 0x6f, 0x66, 0x22, 0x83, 0x01, 0x0a, 0x0d, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x4f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x12, 0x33, + 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, + 0x54, 0x57, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x56, 0x0a, 0x15, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x6a, 0x6e, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x69, + 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2d, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x2d, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, 0x62, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_Binance_proto_rawDescOnce sync.Once + file_Binance_proto_rawDescData = file_Binance_proto_rawDesc +) + +func file_Binance_proto_rawDescGZIP() []byte { + file_Binance_proto_rawDescOnce.Do(func() { + file_Binance_proto_rawDescData = protoimpl.X.CompressGZIP(file_Binance_proto_rawDescData) + }) + return file_Binance_proto_rawDescData +} + +var file_Binance_proto_msgTypes = make([]protoimpl.MessageInfo, 27) +var file_Binance_proto_goTypes = []interface{}{ + (*Transaction)(nil), // 0: TW.Binance.Proto.Transaction + (*Signature)(nil), // 1: TW.Binance.Proto.Signature + (*TradeOrder)(nil), // 2: TW.Binance.Proto.TradeOrder + (*CancelTradeOrder)(nil), // 3: TW.Binance.Proto.CancelTradeOrder + (*SendOrder)(nil), // 4: TW.Binance.Proto.SendOrder + (*TokenIssueOrder)(nil), // 5: TW.Binance.Proto.TokenIssueOrder + (*TokenMintOrder)(nil), // 6: TW.Binance.Proto.TokenMintOrder + (*TokenBurnOrder)(nil), // 7: TW.Binance.Proto.TokenBurnOrder + (*TokenFreezeOrder)(nil), // 8: TW.Binance.Proto.TokenFreezeOrder + (*TokenUnfreezeOrder)(nil), // 9: TW.Binance.Proto.TokenUnfreezeOrder + (*HTLTOrder)(nil), // 10: TW.Binance.Proto.HTLTOrder + (*DepositHTLTOrder)(nil), // 11: TW.Binance.Proto.DepositHTLTOrder + (*ClaimHTLOrder)(nil), // 12: TW.Binance.Proto.ClaimHTLOrder + (*RefundHTLTOrder)(nil), // 13: TW.Binance.Proto.RefundHTLTOrder + (*TransferOut)(nil), // 14: TW.Binance.Proto.TransferOut + (*SideChainDelegate)(nil), // 15: TW.Binance.Proto.SideChainDelegate + (*SideChainRedelegate)(nil), // 16: TW.Binance.Proto.SideChainRedelegate + (*SideChainUndelegate)(nil), // 17: TW.Binance.Proto.SideChainUndelegate + (*TimeLockOrder)(nil), // 18: TW.Binance.Proto.TimeLockOrder + (*TimeRelockOrder)(nil), // 19: TW.Binance.Proto.TimeRelockOrder + (*TimeUnlockOrder)(nil), // 20: TW.Binance.Proto.TimeUnlockOrder + (*SigningInput)(nil), // 21: TW.Binance.Proto.SigningInput + (*SigningOutput)(nil), // 22: TW.Binance.Proto.SigningOutput + (*Signature_PubKey)(nil), // 23: TW.Binance.Proto.Signature.PubKey + (*SendOrder_Token)(nil), // 24: TW.Binance.Proto.SendOrder.Token + (*SendOrder_Input)(nil), // 25: TW.Binance.Proto.SendOrder.Input + (*SendOrder_Output)(nil), // 26: TW.Binance.Proto.SendOrder.Output + (common.SigningError)(0), // 27: TW.Common.Proto.SigningError +} +var file_Binance_proto_depIdxs = []int32{ + 25, // 0: TW.Binance.Proto.SendOrder.inputs:type_name -> TW.Binance.Proto.SendOrder.Input + 26, // 1: TW.Binance.Proto.SendOrder.outputs:type_name -> TW.Binance.Proto.SendOrder.Output + 24, // 2: TW.Binance.Proto.HTLTOrder.amount:type_name -> TW.Binance.Proto.SendOrder.Token + 24, // 3: TW.Binance.Proto.DepositHTLTOrder.amount:type_name -> TW.Binance.Proto.SendOrder.Token + 24, // 4: TW.Binance.Proto.TransferOut.amount:type_name -> TW.Binance.Proto.SendOrder.Token + 24, // 5: TW.Binance.Proto.SideChainDelegate.delegation:type_name -> TW.Binance.Proto.SendOrder.Token + 24, // 6: TW.Binance.Proto.SideChainRedelegate.amount:type_name -> TW.Binance.Proto.SendOrder.Token + 24, // 7: TW.Binance.Proto.SideChainUndelegate.amount:type_name -> TW.Binance.Proto.SendOrder.Token + 24, // 8: TW.Binance.Proto.TimeLockOrder.amount:type_name -> TW.Binance.Proto.SendOrder.Token + 24, // 9: TW.Binance.Proto.TimeRelockOrder.amount:type_name -> TW.Binance.Proto.SendOrder.Token + 2, // 10: TW.Binance.Proto.SigningInput.trade_order:type_name -> TW.Binance.Proto.TradeOrder + 3, // 11: TW.Binance.Proto.SigningInput.cancel_trade_order:type_name -> TW.Binance.Proto.CancelTradeOrder + 4, // 12: TW.Binance.Proto.SigningInput.send_order:type_name -> TW.Binance.Proto.SendOrder + 8, // 13: TW.Binance.Proto.SigningInput.freeze_order:type_name -> TW.Binance.Proto.TokenFreezeOrder + 9, // 14: TW.Binance.Proto.SigningInput.unfreeze_order:type_name -> TW.Binance.Proto.TokenUnfreezeOrder + 10, // 15: TW.Binance.Proto.SigningInput.htlt_order:type_name -> TW.Binance.Proto.HTLTOrder + 11, // 16: TW.Binance.Proto.SigningInput.depositHTLT_order:type_name -> TW.Binance.Proto.DepositHTLTOrder + 12, // 17: TW.Binance.Proto.SigningInput.claimHTLT_order:type_name -> TW.Binance.Proto.ClaimHTLOrder + 13, // 18: TW.Binance.Proto.SigningInput.refundHTLT_order:type_name -> TW.Binance.Proto.RefundHTLTOrder + 5, // 19: TW.Binance.Proto.SigningInput.issue_order:type_name -> TW.Binance.Proto.TokenIssueOrder + 6, // 20: TW.Binance.Proto.SigningInput.mint_order:type_name -> TW.Binance.Proto.TokenMintOrder + 7, // 21: TW.Binance.Proto.SigningInput.burn_order:type_name -> TW.Binance.Proto.TokenBurnOrder + 14, // 22: TW.Binance.Proto.SigningInput.transfer_out_order:type_name -> TW.Binance.Proto.TransferOut + 15, // 23: TW.Binance.Proto.SigningInput.side_delegate_order:type_name -> TW.Binance.Proto.SideChainDelegate + 16, // 24: TW.Binance.Proto.SigningInput.side_redelegate_order:type_name -> TW.Binance.Proto.SideChainRedelegate + 17, // 25: TW.Binance.Proto.SigningInput.side_undelegate_order:type_name -> TW.Binance.Proto.SideChainUndelegate + 18, // 26: TW.Binance.Proto.SigningInput.time_lock_order:type_name -> TW.Binance.Proto.TimeLockOrder + 19, // 27: TW.Binance.Proto.SigningInput.time_relock_order:type_name -> TW.Binance.Proto.TimeRelockOrder + 20, // 28: TW.Binance.Proto.SigningInput.time_unlock_order:type_name -> TW.Binance.Proto.TimeUnlockOrder + 27, // 29: TW.Binance.Proto.SigningOutput.error:type_name -> TW.Common.Proto.SigningError + 24, // 30: TW.Binance.Proto.SendOrder.Input.coins:type_name -> TW.Binance.Proto.SendOrder.Token + 24, // 31: TW.Binance.Proto.SendOrder.Output.coins:type_name -> TW.Binance.Proto.SendOrder.Token + 32, // [32:32] is the sub-list for method output_type + 32, // [32:32] is the sub-list for method input_type + 32, // [32:32] is the sub-list for extension type_name + 32, // [32:32] is the sub-list for extension extendee + 0, // [0:32] is the sub-list for field type_name +} + +func init() { file_Binance_proto_init() } +func file_Binance_proto_init() { + if File_Binance_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_Binance_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Transaction); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Binance_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Signature); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Binance_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TradeOrder); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Binance_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CancelTradeOrder); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Binance_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SendOrder); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Binance_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TokenIssueOrder); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Binance_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TokenMintOrder); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Binance_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TokenBurnOrder); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Binance_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TokenFreezeOrder); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Binance_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TokenUnfreezeOrder); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Binance_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HTLTOrder); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Binance_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DepositHTLTOrder); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Binance_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ClaimHTLOrder); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Binance_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RefundHTLTOrder); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Binance_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TransferOut); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Binance_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SideChainDelegate); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Binance_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SideChainRedelegate); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Binance_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SideChainUndelegate); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Binance_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TimeLockOrder); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Binance_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TimeRelockOrder); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Binance_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TimeUnlockOrder); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Binance_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SigningInput); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Binance_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SigningOutput); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Binance_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Signature_PubKey); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Binance_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SendOrder_Token); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Binance_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SendOrder_Input); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Binance_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SendOrder_Output); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_Binance_proto_msgTypes[21].OneofWrappers = []interface{}{ + (*SigningInput_TradeOrder)(nil), + (*SigningInput_CancelTradeOrder)(nil), + (*SigningInput_SendOrder)(nil), + (*SigningInput_FreezeOrder)(nil), + (*SigningInput_UnfreezeOrder)(nil), + (*SigningInput_HtltOrder)(nil), + (*SigningInput_DepositHTLTOrder)(nil), + (*SigningInput_ClaimHTLTOrder)(nil), + (*SigningInput_RefundHTLTOrder)(nil), + (*SigningInput_IssueOrder)(nil), + (*SigningInput_MintOrder)(nil), + (*SigningInput_BurnOrder)(nil), + (*SigningInput_TransferOutOrder)(nil), + (*SigningInput_SideDelegateOrder)(nil), + (*SigningInput_SideRedelegateOrder)(nil), + (*SigningInput_SideUndelegateOrder)(nil), + (*SigningInput_TimeLockOrder)(nil), + (*SigningInput_TimeRelockOrder)(nil), + (*SigningInput_TimeUnlockOrder)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_Binance_proto_rawDesc, + NumEnums: 0, + NumMessages: 27, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_Binance_proto_goTypes, + DependencyIndexes: file_Binance_proto_depIdxs, + MessageInfos: file_Binance_proto_msgTypes, + }.Build() + File_Binance_proto = out.File + file_Binance_proto_rawDesc = nil + file_Binance_proto_goTypes = nil + file_Binance_proto_depIdxs = nil +} diff --git a/samples/go/protos/bitcoin/Bitcoin.pb.go b/samples/go/protos/bitcoin/Bitcoin.pb.go index ed93c62556a..88b884e6912 100644 --- a/samples/go/protos/bitcoin/Bitcoin.pb.go +++ b/samples/go/protos/bitcoin/Bitcoin.pb.go @@ -1,27 +1,31 @@ // Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.0 +// protoc v3.19.2 // source: Bitcoin.proto package bitcoin import ( - fmt "fmt" - math "math" - - proto "github.com/golang/protobuf/proto" + common "tw/protos/common" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" ) -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) type Transaction struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Transaction data format version. Version int32 `protobuf:"zigzag32,1,opt,name=version,proto3" json:"version,omitempty"` // The block number or timestamp at which this transaction is unlocked. @@ -29,293 +33,397 @@ type Transaction struct { // A list of 1 or more transaction inputs or sources for coins. Inputs []*TransactionInput `protobuf:"bytes,3,rep,name=inputs,proto3" json:"inputs,omitempty"` // A list of 1 or more transaction outputs or destinations for coins - Outputs []*TransactionOutput `protobuf:"bytes,4,rep,name=outputs,proto3" json:"outputs,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Outputs []*TransactionOutput `protobuf:"bytes,4,rep,name=outputs,proto3" json:"outputs,omitempty"` } -func (m *Transaction) Reset() { *m = Transaction{} } -func (m *Transaction) String() string { return proto.CompactTextString(m) } -func (*Transaction) ProtoMessage() {} -func (*Transaction) Descriptor() ([]byte, []int) { - return fileDescriptor_9fbb050c4cb9ab40, []int{0} +func (x *Transaction) Reset() { + *x = Transaction{} + if protoimpl.UnsafeEnabled { + mi := &file_Bitcoin_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *Transaction) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Transaction.Unmarshal(m, b) +func (x *Transaction) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *Transaction) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Transaction.Marshal(b, m, deterministic) -} -func (m *Transaction) XXX_Merge(src proto.Message) { - xxx_messageInfo_Transaction.Merge(m, src) -} -func (m *Transaction) XXX_Size() int { - return xxx_messageInfo_Transaction.Size(m) -} -func (m *Transaction) XXX_DiscardUnknown() { - xxx_messageInfo_Transaction.DiscardUnknown(m) + +func (*Transaction) ProtoMessage() {} + +func (x *Transaction) ProtoReflect() protoreflect.Message { + mi := &file_Bitcoin_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_Transaction proto.InternalMessageInfo +// Deprecated: Use Transaction.ProtoReflect.Descriptor instead. +func (*Transaction) Descriptor() ([]byte, []int) { + return file_Bitcoin_proto_rawDescGZIP(), []int{0} +} -func (m *Transaction) GetVersion() int32 { - if m != nil { - return m.Version +func (x *Transaction) GetVersion() int32 { + if x != nil { + return x.Version } return 0 } -func (m *Transaction) GetLockTime() uint32 { - if m != nil { - return m.LockTime +func (x *Transaction) GetLockTime() uint32 { + if x != nil { + return x.LockTime } return 0 } -func (m *Transaction) GetInputs() []*TransactionInput { - if m != nil { - return m.Inputs +func (x *Transaction) GetInputs() []*TransactionInput { + if x != nil { + return x.Inputs } return nil } -func (m *Transaction) GetOutputs() []*TransactionOutput { - if m != nil { - return m.Outputs +func (x *Transaction) GetOutputs() []*TransactionOutput { + if x != nil { + return x.Outputs } return nil } // Bitcoin transaction input. type TransactionInput struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Reference to the previous transaction's output. PreviousOutput *OutPoint `protobuf:"bytes,1,opt,name=previousOutput,proto3" json:"previousOutput,omitempty"` // Transaction version as defined by the sender. Sequence uint32 `protobuf:"varint,2,opt,name=sequence,proto3" json:"sequence,omitempty"` // Computational script for confirming transaction authorization. - Script []byte `protobuf:"bytes,3,opt,name=script,proto3" json:"script,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Script []byte `protobuf:"bytes,3,opt,name=script,proto3" json:"script,omitempty"` } -func (m *TransactionInput) Reset() { *m = TransactionInput{} } -func (m *TransactionInput) String() string { return proto.CompactTextString(m) } -func (*TransactionInput) ProtoMessage() {} -func (*TransactionInput) Descriptor() ([]byte, []int) { - return fileDescriptor_9fbb050c4cb9ab40, []int{1} +func (x *TransactionInput) Reset() { + *x = TransactionInput{} + if protoimpl.UnsafeEnabled { + mi := &file_Bitcoin_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *TransactionInput) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_TransactionInput.Unmarshal(m, b) -} -func (m *TransactionInput) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_TransactionInput.Marshal(b, m, deterministic) +func (x *TransactionInput) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *TransactionInput) XXX_Merge(src proto.Message) { - xxx_messageInfo_TransactionInput.Merge(m, src) -} -func (m *TransactionInput) XXX_Size() int { - return xxx_messageInfo_TransactionInput.Size(m) -} -func (m *TransactionInput) XXX_DiscardUnknown() { - xxx_messageInfo_TransactionInput.DiscardUnknown(m) + +func (*TransactionInput) ProtoMessage() {} + +func (x *TransactionInput) ProtoReflect() protoreflect.Message { + mi := &file_Bitcoin_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_TransactionInput proto.InternalMessageInfo +// Deprecated: Use TransactionInput.ProtoReflect.Descriptor instead. +func (*TransactionInput) Descriptor() ([]byte, []int) { + return file_Bitcoin_proto_rawDescGZIP(), []int{1} +} -func (m *TransactionInput) GetPreviousOutput() *OutPoint { - if m != nil { - return m.PreviousOutput +func (x *TransactionInput) GetPreviousOutput() *OutPoint { + if x != nil { + return x.PreviousOutput } return nil } -func (m *TransactionInput) GetSequence() uint32 { - if m != nil { - return m.Sequence +func (x *TransactionInput) GetSequence() uint32 { + if x != nil { + return x.Sequence } return 0 } -func (m *TransactionInput) GetScript() []byte { - if m != nil { - return m.Script +func (x *TransactionInput) GetScript() []byte { + if x != nil { + return x.Script } return nil } // Bitcoin transaction out-point reference. type OutPoint struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The hash of the referenced transaction. Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` // The index of the specific output in the transaction. Index uint32 `protobuf:"varint,2,opt,name=index,proto3" json:"index,omitempty"` // Transaction version as defined by the sender. - Sequence uint32 `protobuf:"varint,3,opt,name=sequence,proto3" json:"sequence,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Sequence uint32 `protobuf:"varint,3,opt,name=sequence,proto3" json:"sequence,omitempty"` } -func (m *OutPoint) Reset() { *m = OutPoint{} } -func (m *OutPoint) String() string { return proto.CompactTextString(m) } -func (*OutPoint) ProtoMessage() {} -func (*OutPoint) Descriptor() ([]byte, []int) { - return fileDescriptor_9fbb050c4cb9ab40, []int{2} +func (x *OutPoint) Reset() { + *x = OutPoint{} + if protoimpl.UnsafeEnabled { + mi := &file_Bitcoin_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *OutPoint) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_OutPoint.Unmarshal(m, b) -} -func (m *OutPoint) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_OutPoint.Marshal(b, m, deterministic) +func (x *OutPoint) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *OutPoint) XXX_Merge(src proto.Message) { - xxx_messageInfo_OutPoint.Merge(m, src) -} -func (m *OutPoint) XXX_Size() int { - return xxx_messageInfo_OutPoint.Size(m) -} -func (m *OutPoint) XXX_DiscardUnknown() { - xxx_messageInfo_OutPoint.DiscardUnknown(m) + +func (*OutPoint) ProtoMessage() {} + +func (x *OutPoint) ProtoReflect() protoreflect.Message { + mi := &file_Bitcoin_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_OutPoint proto.InternalMessageInfo +// Deprecated: Use OutPoint.ProtoReflect.Descriptor instead. +func (*OutPoint) Descriptor() ([]byte, []int) { + return file_Bitcoin_proto_rawDescGZIP(), []int{2} +} -func (m *OutPoint) GetHash() []byte { - if m != nil { - return m.Hash +func (x *OutPoint) GetHash() []byte { + if x != nil { + return x.Hash } return nil } -func (m *OutPoint) GetIndex() uint32 { - if m != nil { - return m.Index +func (x *OutPoint) GetIndex() uint32 { + if x != nil { + return x.Index } return 0 } -func (m *OutPoint) GetSequence() uint32 { - if m != nil { - return m.Sequence +func (x *OutPoint) GetSequence() uint32 { + if x != nil { + return x.Sequence } return 0 } // Bitcoin transaction output. type TransactionOutput struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Transaction amount. Value int64 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"` // Usually contains the public key as a Bitcoin script setting up conditions to claim this output. - Script []byte `protobuf:"bytes,2,opt,name=script,proto3" json:"script,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Script []byte `protobuf:"bytes,2,opt,name=script,proto3" json:"script,omitempty"` } -func (m *TransactionOutput) Reset() { *m = TransactionOutput{} } -func (m *TransactionOutput) String() string { return proto.CompactTextString(m) } -func (*TransactionOutput) ProtoMessage() {} -func (*TransactionOutput) Descriptor() ([]byte, []int) { - return fileDescriptor_9fbb050c4cb9ab40, []int{3} +func (x *TransactionOutput) Reset() { + *x = TransactionOutput{} + if protoimpl.UnsafeEnabled { + mi := &file_Bitcoin_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *TransactionOutput) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_TransactionOutput.Unmarshal(m, b) -} -func (m *TransactionOutput) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_TransactionOutput.Marshal(b, m, deterministic) +func (x *TransactionOutput) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *TransactionOutput) XXX_Merge(src proto.Message) { - xxx_messageInfo_TransactionOutput.Merge(m, src) -} -func (m *TransactionOutput) XXX_Size() int { - return xxx_messageInfo_TransactionOutput.Size(m) -} -func (m *TransactionOutput) XXX_DiscardUnknown() { - xxx_messageInfo_TransactionOutput.DiscardUnknown(m) + +func (*TransactionOutput) ProtoMessage() {} + +func (x *TransactionOutput) ProtoReflect() protoreflect.Message { + mi := &file_Bitcoin_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_TransactionOutput proto.InternalMessageInfo +// Deprecated: Use TransactionOutput.ProtoReflect.Descriptor instead. +func (*TransactionOutput) Descriptor() ([]byte, []int) { + return file_Bitcoin_proto_rawDescGZIP(), []int{3} +} -func (m *TransactionOutput) GetValue() int64 { - if m != nil { - return m.Value +func (x *TransactionOutput) GetValue() int64 { + if x != nil { + return x.Value } return 0 } -func (m *TransactionOutput) GetScript() []byte { - if m != nil { - return m.Script +func (x *TransactionOutput) GetScript() []byte { + if x != nil { + return x.Script } return nil } +// An unspent transaction output, that can serve as input to a transaction type UnspentTransaction struct { - OutPoint *OutPoint `protobuf:"bytes,1,opt,name=out_point,json=outPoint,proto3" json:"out_point,omitempty"` - Script []byte `protobuf:"bytes,2,opt,name=script,proto3" json:"script,omitempty"` - Amount int64 `protobuf:"varint,3,opt,name=amount,proto3" json:"amount,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The unspent output + OutPoint *OutPoint `protobuf:"bytes,1,opt,name=out_point,json=outPoint,proto3" json:"out_point,omitempty"` + // Script for claiming this UTXO + Script []byte `protobuf:"bytes,2,opt,name=script,proto3" json:"script,omitempty"` + // Amount of the UTXO + Amount int64 `protobuf:"varint,3,opt,name=amount,proto3" json:"amount,omitempty"` +} + +func (x *UnspentTransaction) Reset() { + *x = UnspentTransaction{} + if protoimpl.UnsafeEnabled { + mi := &file_Bitcoin_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UnspentTransaction) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UnspentTransaction) ProtoMessage() {} + +func (x *UnspentTransaction) ProtoReflect() protoreflect.Message { + mi := &file_Bitcoin_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -func (m *UnspentTransaction) Reset() { *m = UnspentTransaction{} } -func (m *UnspentTransaction) String() string { return proto.CompactTextString(m) } -func (*UnspentTransaction) ProtoMessage() {} +// Deprecated: Use UnspentTransaction.ProtoReflect.Descriptor instead. func (*UnspentTransaction) Descriptor() ([]byte, []int) { - return fileDescriptor_9fbb050c4cb9ab40, []int{4} + return file_Bitcoin_proto_rawDescGZIP(), []int{4} } -func (m *UnspentTransaction) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_UnspentTransaction.Unmarshal(m, b) +func (x *UnspentTransaction) GetOutPoint() *OutPoint { + if x != nil { + return x.OutPoint + } + return nil } -func (m *UnspentTransaction) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_UnspentTransaction.Marshal(b, m, deterministic) + +func (x *UnspentTransaction) GetScript() []byte { + if x != nil { + return x.Script + } + return nil } -func (m *UnspentTransaction) XXX_Merge(src proto.Message) { - xxx_messageInfo_UnspentTransaction.Merge(m, src) + +func (x *UnspentTransaction) GetAmount() int64 { + if x != nil { + return x.Amount + } + return 0 } -func (m *UnspentTransaction) XXX_Size() int { - return xxx_messageInfo_UnspentTransaction.Size(m) + +// Pair of destination address and amount, used for extra outputs +type OutputAddress struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Destination address + ToAddress string `protobuf:"bytes,1,opt,name=to_address,json=toAddress,proto3" json:"to_address,omitempty"` + // Amount to be paid to this output + Amount int64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"` } -func (m *UnspentTransaction) XXX_DiscardUnknown() { - xxx_messageInfo_UnspentTransaction.DiscardUnknown(m) + +func (x *OutputAddress) Reset() { + *x = OutputAddress{} + if protoimpl.UnsafeEnabled { + mi := &file_Bitcoin_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -var xxx_messageInfo_UnspentTransaction proto.InternalMessageInfo +func (x *OutputAddress) String() string { + return protoimpl.X.MessageStringOf(x) +} -func (m *UnspentTransaction) GetOutPoint() *OutPoint { - if m != nil { - return m.OutPoint +func (*OutputAddress) ProtoMessage() {} + +func (x *OutputAddress) ProtoReflect() protoreflect.Message { + mi := &file_Bitcoin_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return nil + return mi.MessageOf(x) +} + +// Deprecated: Use OutputAddress.ProtoReflect.Descriptor instead. +func (*OutputAddress) Descriptor() ([]byte, []int) { + return file_Bitcoin_proto_rawDescGZIP(), []int{5} } -func (m *UnspentTransaction) GetScript() []byte { - if m != nil { - return m.Script +func (x *OutputAddress) GetToAddress() string { + if x != nil { + return x.ToAddress } - return nil + return "" } -func (m *UnspentTransaction) GetAmount() int64 { - if m != nil { - return m.Amount +func (x *OutputAddress) GetAmount() int64 { + if x != nil { + return x.Amount } return 0 } // Input data necessary to create a signed transaction. type SigningInput struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Hash type to use when signing. HashType uint32 `protobuf:"varint,1,opt,name=hash_type,json=hashType,proto3" json:"hash_type,omitempty"` - // Amount to send. + // Amount to send. Transaction created will have this amount in its output, + // except when use_max_amount is set, in that case this amount is not relevant, maximum possible amount will be used (max avail less fee). + // If amount is equal or more than the available amount, also max amount will be used. Amount int64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"` // Transaction fee per byte. ByteFee int64 `protobuf:"varint,3,opt,name=byte_fee,json=byteFee,proto3" json:"byte_fee,omitempty"` @@ -324,126 +432,182 @@ type SigningInput struct { // Change address. ChangeAddress string `protobuf:"bytes,5,opt,name=change_address,json=changeAddress,proto3" json:"change_address,omitempty"` // Available private keys. - PrivateKey [][]byte `protobuf:"bytes,10,rep,name=private_key,json=privateKey,proto3" json:"private_key,omitempty"` + PrivateKey [][]byte `protobuf:"bytes,6,rep,name=private_key,json=privateKey,proto3" json:"private_key,omitempty"` // Available redeem scripts indexed by script hash. - Scripts map[string][]byte `protobuf:"bytes,11,rep,name=scripts,proto3" json:"scripts,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Scripts map[string][]byte `protobuf:"bytes,7,rep,name=scripts,proto3" json:"scripts,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // Available unspent transaction outputs. - Utxo []*UnspentTransaction `protobuf:"bytes,12,rep,name=utxo,proto3" json:"utxo,omitempty"` + Utxo []*UnspentTransaction `protobuf:"bytes,8,rep,name=utxo,proto3" json:"utxo,omitempty"` // If sending max amount. - UseMaxAmount bool `protobuf:"varint,13,opt,name=use_max_amount,json=useMaxAmount,proto3" json:"use_max_amount,omitempty"` - // Coin type (forks). - CoinType uint32 `protobuf:"varint,14,opt,name=coin_type,json=coinType,proto3" json:"coin_type,omitempty"` + UseMaxAmount bool `protobuf:"varint,9,opt,name=use_max_amount,json=useMaxAmount,proto3" json:"use_max_amount,omitempty"` + CoinType uint32 `protobuf:"varint,10,opt,name=coin_type,json=coinType,proto3" json:"coin_type,omitempty"` // Optional transaction plan - Plan *TransactionPlan `protobuf:"bytes,15,opt,name=plan,proto3" json:"plan,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Plan *TransactionPlan `protobuf:"bytes,11,opt,name=plan,proto3" json:"plan,omitempty"` + // Optional lockTime, default value 0 means no time locking. + // If all inputs have final (`0xffffffff`) sequence numbers then `lockTime` is irrelevant. + // Otherwise, the transaction may not be added to a block until after `lockTime`. + // value < 500000000 : Block number at which this transaction is unlocked + // value >= 500000000 : UNIX timestamp at which this transaction is unlocked + LockTime uint32 `protobuf:"varint,12,opt,name=lock_time,json=lockTime,proto3" json:"lock_time,omitempty"` + // Optional zero-amount, OP_RETURN output + OutputOpReturn []byte `protobuf:"bytes,13,opt,name=output_op_return,json=outputOpReturn,proto3" json:"output_op_return,omitempty"` + // Optional additional destination addresses, additional to first to_address output + ExtraOutputs []*OutputAddress `protobuf:"bytes,14,rep,name=extra_outputs,json=extraOutputs,proto3" json:"extra_outputs,omitempty"` + // If use max utxo. + UseMaxUtxo bool `protobuf:"varint,15,opt,name=use_max_utxo,json=useMaxUtxo,proto3" json:"use_max_utxo,omitempty"` + // If disable dust filter. + DisableDustFilter bool `protobuf:"varint,16,opt,name=disable_dust_filter,json=disableDustFilter,proto3" json:"disable_dust_filter,omitempty"` +} + +func (x *SigningInput) Reset() { + *x = SigningInput{} + if protoimpl.UnsafeEnabled { + mi := &file_Bitcoin_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *SigningInput) Reset() { *m = SigningInput{} } -func (m *SigningInput) String() string { return proto.CompactTextString(m) } -func (*SigningInput) ProtoMessage() {} -func (*SigningInput) Descriptor() ([]byte, []int) { - return fileDescriptor_9fbb050c4cb9ab40, []int{5} +func (x *SigningInput) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *SigningInput) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_SigningInput.Unmarshal(m, b) -} -func (m *SigningInput) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_SigningInput.Marshal(b, m, deterministic) -} -func (m *SigningInput) XXX_Merge(src proto.Message) { - xxx_messageInfo_SigningInput.Merge(m, src) -} -func (m *SigningInput) XXX_Size() int { - return xxx_messageInfo_SigningInput.Size(m) -} -func (m *SigningInput) XXX_DiscardUnknown() { - xxx_messageInfo_SigningInput.DiscardUnknown(m) +func (*SigningInput) ProtoMessage() {} + +func (x *SigningInput) ProtoReflect() protoreflect.Message { + mi := &file_Bitcoin_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_SigningInput proto.InternalMessageInfo +// Deprecated: Use SigningInput.ProtoReflect.Descriptor instead. +func (*SigningInput) Descriptor() ([]byte, []int) { + return file_Bitcoin_proto_rawDescGZIP(), []int{6} +} -func (m *SigningInput) GetHashType() uint32 { - if m != nil { - return m.HashType +func (x *SigningInput) GetHashType() uint32 { + if x != nil { + return x.HashType } return 0 } -func (m *SigningInput) GetAmount() int64 { - if m != nil { - return m.Amount +func (x *SigningInput) GetAmount() int64 { + if x != nil { + return x.Amount } return 0 } -func (m *SigningInput) GetByteFee() int64 { - if m != nil { - return m.ByteFee +func (x *SigningInput) GetByteFee() int64 { + if x != nil { + return x.ByteFee } return 0 } -func (m *SigningInput) GetToAddress() string { - if m != nil { - return m.ToAddress +func (x *SigningInput) GetToAddress() string { + if x != nil { + return x.ToAddress } return "" } -func (m *SigningInput) GetChangeAddress() string { - if m != nil { - return m.ChangeAddress +func (x *SigningInput) GetChangeAddress() string { + if x != nil { + return x.ChangeAddress } return "" } -func (m *SigningInput) GetPrivateKey() [][]byte { - if m != nil { - return m.PrivateKey +func (x *SigningInput) GetPrivateKey() [][]byte { + if x != nil { + return x.PrivateKey } return nil } -func (m *SigningInput) GetScripts() map[string][]byte { - if m != nil { - return m.Scripts +func (x *SigningInput) GetScripts() map[string][]byte { + if x != nil { + return x.Scripts } return nil } -func (m *SigningInput) GetUtxo() []*UnspentTransaction { - if m != nil { - return m.Utxo +func (x *SigningInput) GetUtxo() []*UnspentTransaction { + if x != nil { + return x.Utxo } return nil } -func (m *SigningInput) GetUseMaxAmount() bool { - if m != nil { - return m.UseMaxAmount +func (x *SigningInput) GetUseMaxAmount() bool { + if x != nil { + return x.UseMaxAmount } return false } -func (m *SigningInput) GetCoinType() uint32 { - if m != nil { - return m.CoinType +func (x *SigningInput) GetCoinType() uint32 { + if x != nil { + return x.CoinType + } + return 0 +} + +func (x *SigningInput) GetPlan() *TransactionPlan { + if x != nil { + return x.Plan + } + return nil +} + +func (x *SigningInput) GetLockTime() uint32 { + if x != nil { + return x.LockTime } return 0 } -func (m *SigningInput) GetPlan() *TransactionPlan { - if m != nil { - return m.Plan +func (x *SigningInput) GetOutputOpReturn() []byte { + if x != nil { + return x.OutputOpReturn + } + return nil +} + +func (x *SigningInput) GetExtraOutputs() []*OutputAddress { + if x != nil { + return x.ExtraOutputs } return nil } +func (x *SigningInput) GetUseMaxUtxo() bool { + if x != nil { + return x.UseMaxUtxo + } + return false +} + +func (x *SigningInput) GetDisableDustFilter() bool { + if x != nil { + return x.DisableDustFilter + } + return false +} + // Describes a preliminary transaction plan. type TransactionPlan struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Amount to be received at the other end. Amount int64 `protobuf:"varint,1,opt,name=amount,proto3" json:"amount,omitempty"` // Maximum available amount. @@ -455,206 +619,669 @@ type TransactionPlan struct { // Selected unspent transaction outputs. Utxos []*UnspentTransaction `protobuf:"bytes,5,rep,name=utxos,proto3" json:"utxos,omitempty"` // Zcash branch id - BranchId []byte `protobuf:"bytes,6,opt,name=branch_id,json=branchId,proto3" json:"branch_id,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + BranchId []byte `protobuf:"bytes,6,opt,name=branch_id,json=branchId,proto3" json:"branch_id,omitempty"` + // Optional error + Error common.SigningError `protobuf:"varint,7,opt,name=error,proto3,enum=TW.Common.Proto.SigningError" json:"error,omitempty"` + // Optional zero-amount, OP_RETURN output + OutputOpReturn []byte `protobuf:"bytes,8,opt,name=output_op_return,json=outputOpReturn,proto3" json:"output_op_return,omitempty"` +} + +func (x *TransactionPlan) Reset() { + *x = TransactionPlan{} + if protoimpl.UnsafeEnabled { + mi := &file_Bitcoin_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *TransactionPlan) Reset() { *m = TransactionPlan{} } -func (m *TransactionPlan) String() string { return proto.CompactTextString(m) } -func (*TransactionPlan) ProtoMessage() {} -func (*TransactionPlan) Descriptor() ([]byte, []int) { - return fileDescriptor_9fbb050c4cb9ab40, []int{6} +func (x *TransactionPlan) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *TransactionPlan) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_TransactionPlan.Unmarshal(m, b) -} -func (m *TransactionPlan) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_TransactionPlan.Marshal(b, m, deterministic) -} -func (m *TransactionPlan) XXX_Merge(src proto.Message) { - xxx_messageInfo_TransactionPlan.Merge(m, src) -} -func (m *TransactionPlan) XXX_Size() int { - return xxx_messageInfo_TransactionPlan.Size(m) -} -func (m *TransactionPlan) XXX_DiscardUnknown() { - xxx_messageInfo_TransactionPlan.DiscardUnknown(m) +func (*TransactionPlan) ProtoMessage() {} + +func (x *TransactionPlan) ProtoReflect() protoreflect.Message { + mi := &file_Bitcoin_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_TransactionPlan proto.InternalMessageInfo +// Deprecated: Use TransactionPlan.ProtoReflect.Descriptor instead. +func (*TransactionPlan) Descriptor() ([]byte, []int) { + return file_Bitcoin_proto_rawDescGZIP(), []int{7} +} -func (m *TransactionPlan) GetAmount() int64 { - if m != nil { - return m.Amount +func (x *TransactionPlan) GetAmount() int64 { + if x != nil { + return x.Amount } return 0 } -func (m *TransactionPlan) GetAvailableAmount() int64 { - if m != nil { - return m.AvailableAmount +func (x *TransactionPlan) GetAvailableAmount() int64 { + if x != nil { + return x.AvailableAmount } return 0 } -func (m *TransactionPlan) GetFee() int64 { - if m != nil { - return m.Fee +func (x *TransactionPlan) GetFee() int64 { + if x != nil { + return x.Fee } return 0 } -func (m *TransactionPlan) GetChange() int64 { - if m != nil { - return m.Change +func (x *TransactionPlan) GetChange() int64 { + if x != nil { + return x.Change } return 0 } -func (m *TransactionPlan) GetUtxos() []*UnspentTransaction { - if m != nil { - return m.Utxos +func (x *TransactionPlan) GetUtxos() []*UnspentTransaction { + if x != nil { + return x.Utxos } return nil } -func (m *TransactionPlan) GetBranchId() []byte { - if m != nil { - return m.BranchId +func (x *TransactionPlan) GetBranchId() []byte { + if x != nil { + return x.BranchId + } + return nil +} + +func (x *TransactionPlan) GetError() common.SigningError { + if x != nil { + return x.Error + } + return common.SigningError(0) +} + +func (x *TransactionPlan) GetOutputOpReturn() []byte { + if x != nil { + return x.OutputOpReturn } return nil } // Transaction signing output. type SigningOutput struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Resulting transaction. Note that the amount may be different than the requested amount to account for fees and available funds. Transaction *Transaction `protobuf:"bytes,1,opt,name=transaction,proto3" json:"transaction,omitempty"` // Signed and encoded transaction bytes. Encoded []byte `protobuf:"bytes,2,opt,name=encoded,proto3" json:"encoded,omitempty"` // Transaction id TransactionId string `protobuf:"bytes,3,opt,name=transaction_id,json=transactionId,proto3" json:"transaction_id,omitempty"` - // Optional error message - Error string `protobuf:"bytes,4,opt,name=error,proto3" json:"error,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + // Optional error + Error common.SigningError `protobuf:"varint,4,opt,name=error,proto3,enum=TW.Common.Proto.SigningError" json:"error,omitempty"` + // error description + ErrorMessage string `protobuf:"bytes,5,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` +} + +func (x *SigningOutput) Reset() { + *x = SigningOutput{} + if protoimpl.UnsafeEnabled { + mi := &file_Bitcoin_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SigningOutput) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *SigningOutput) Reset() { *m = SigningOutput{} } -func (m *SigningOutput) String() string { return proto.CompactTextString(m) } -func (*SigningOutput) ProtoMessage() {} +func (*SigningOutput) ProtoMessage() {} + +func (x *SigningOutput) ProtoReflect() protoreflect.Message { + mi := &file_Bitcoin_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SigningOutput.ProtoReflect.Descriptor instead. func (*SigningOutput) Descriptor() ([]byte, []int) { - return fileDescriptor_9fbb050c4cb9ab40, []int{7} + return file_Bitcoin_proto_rawDescGZIP(), []int{8} +} + +func (x *SigningOutput) GetTransaction() *Transaction { + if x != nil { + return x.Transaction + } + return nil } -func (m *SigningOutput) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_SigningOutput.Unmarshal(m, b) +func (x *SigningOutput) GetEncoded() []byte { + if x != nil { + return x.Encoded + } + return nil } -func (m *SigningOutput) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_SigningOutput.Marshal(b, m, deterministic) + +func (x *SigningOutput) GetTransactionId() string { + if x != nil { + return x.TransactionId + } + return "" } -func (m *SigningOutput) XXX_Merge(src proto.Message) { - xxx_messageInfo_SigningOutput.Merge(m, src) + +func (x *SigningOutput) GetError() common.SigningError { + if x != nil { + return x.Error + } + return common.SigningError(0) } -func (m *SigningOutput) XXX_Size() int { - return xxx_messageInfo_SigningOutput.Size(m) + +func (x *SigningOutput) GetErrorMessage() string { + if x != nil { + return x.ErrorMessage + } + return "" } -func (m *SigningOutput) XXX_DiscardUnknown() { - xxx_messageInfo_SigningOutput.DiscardUnknown(m) + +type HashPublicKey struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + /// Pre-image data hash that will be used for signing + DataHash []byte `protobuf:"bytes,1,opt,name=data_hash,json=dataHash,proto3" json:"data_hash,omitempty"` + /// public key hash used for signing + PublicKeyHash []byte `protobuf:"bytes,2,opt,name=public_key_hash,json=publicKeyHash,proto3" json:"public_key_hash,omitempty"` } -var xxx_messageInfo_SigningOutput proto.InternalMessageInfo +func (x *HashPublicKey) Reset() { + *x = HashPublicKey{} + if protoimpl.UnsafeEnabled { + mi := &file_Bitcoin_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HashPublicKey) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HashPublicKey) ProtoMessage() {} -func (m *SigningOutput) GetTransaction() *Transaction { - if m != nil { - return m.Transaction +func (x *HashPublicKey) ProtoReflect() protoreflect.Message { + mi := &file_Bitcoin_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HashPublicKey.ProtoReflect.Descriptor instead. +func (*HashPublicKey) Descriptor() ([]byte, []int) { + return file_Bitcoin_proto_rawDescGZIP(), []int{9} +} + +func (x *HashPublicKey) GetDataHash() []byte { + if x != nil { + return x.DataHash } return nil } -func (m *SigningOutput) GetEncoded() []byte { - if m != nil { - return m.Encoded +func (x *HashPublicKey) GetPublicKeyHash() []byte { + if x != nil { + return x.PublicKeyHash } return nil } -func (m *SigningOutput) GetTransactionId() string { - if m != nil { - return m.TransactionId +/// Transaction pre-signing output +type PreSigningOutput struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + /// hash, public key list + HashPublicKeys []*HashPublicKey `protobuf:"bytes,1,rep,name=hash_public_keys,json=hashPublicKeys,proto3" json:"hash_public_keys,omitempty"` + /// error code, 0 is ok, other codes will be treated as errors + Error common.SigningError `protobuf:"varint,2,opt,name=error,proto3,enum=TW.Common.Proto.SigningError" json:"error,omitempty"` + /// error description + ErrorMessage string `protobuf:"bytes,3,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` +} + +func (x *PreSigningOutput) Reset() { + *x = PreSigningOutput{} + if protoimpl.UnsafeEnabled { + mi := &file_Bitcoin_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } - return "" } -func (m *SigningOutput) GetError() string { - if m != nil { - return m.Error +func (x *PreSigningOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PreSigningOutput) ProtoMessage() {} + +func (x *PreSigningOutput) ProtoReflect() protoreflect.Message { + mi := &file_Bitcoin_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PreSigningOutput.ProtoReflect.Descriptor instead. +func (*PreSigningOutput) Descriptor() ([]byte, []int) { + return file_Bitcoin_proto_rawDescGZIP(), []int{10} +} + +func (x *PreSigningOutput) GetHashPublicKeys() []*HashPublicKey { + if x != nil { + return x.HashPublicKeys + } + return nil +} + +func (x *PreSigningOutput) GetError() common.SigningError { + if x != nil { + return x.Error + } + return common.SigningError(0) +} + +func (x *PreSigningOutput) GetErrorMessage() string { + if x != nil { + return x.ErrorMessage } return "" } -func init() { - proto.RegisterType((*Transaction)(nil), "TW.Bitcoin.Proto.Transaction") - proto.RegisterType((*TransactionInput)(nil), "TW.Bitcoin.Proto.TransactionInput") - proto.RegisterType((*OutPoint)(nil), "TW.Bitcoin.Proto.OutPoint") - proto.RegisterType((*TransactionOutput)(nil), "TW.Bitcoin.Proto.TransactionOutput") - proto.RegisterType((*UnspentTransaction)(nil), "TW.Bitcoin.Proto.UnspentTransaction") - proto.RegisterType((*SigningInput)(nil), "TW.Bitcoin.Proto.SigningInput") - proto.RegisterMapType((map[string][]byte)(nil), "TW.Bitcoin.Proto.SigningInput.ScriptsEntry") - proto.RegisterType((*TransactionPlan)(nil), "TW.Bitcoin.Proto.TransactionPlan") - proto.RegisterType((*SigningOutput)(nil), "TW.Bitcoin.Proto.SigningOutput") -} - -func init() { proto.RegisterFile("Bitcoin.proto", fileDescriptor_9fbb050c4cb9ab40) } - -var fileDescriptor_9fbb050c4cb9ab40 = []byte{ - // 715 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x55, 0xdb, 0x6a, 0xdb, 0x4c, - 0x10, 0x46, 0x96, 0xe3, 0xc3, 0xf8, 0x10, 0x67, 0xf9, 0x0f, 0xfa, 0x13, 0xc2, 0xef, 0xaa, 0x29, - 0xb8, 0x14, 0x7c, 0x91, 0x52, 0x1a, 0x0c, 0xa5, 0x24, 0x90, 0x42, 0x28, 0x25, 0x66, 0xe3, 0xd2, - 0x4b, 0xb1, 0x96, 0xb6, 0xf1, 0x36, 0xca, 0xae, 0x2a, 0xad, 0x5c, 0xfb, 0xa2, 0x2f, 0xd0, 0x17, - 0xe9, 0x5b, 0xf4, 0x35, 0xfa, 0x32, 0xbd, 0x28, 0x7b, 0x90, 0x23, 0xdb, 0x34, 0xe4, 0xca, 0xfb, - 0x0d, 0x33, 0xdf, 0xec, 0xf7, 0xcd, 0xac, 0x05, 0x9d, 0x33, 0x26, 0x43, 0xc1, 0xf8, 0x30, 0x49, - 0x85, 0x14, 0xa8, 0x37, 0xf9, 0x30, 0x2c, 0x22, 0x63, 0x15, 0xf1, 0x7f, 0x38, 0xd0, 0x9a, 0xa4, - 0x84, 0x67, 0x24, 0x94, 0x4c, 0x70, 0xe4, 0x41, 0x7d, 0x4e, 0xd3, 0x8c, 0x09, 0xee, 0x39, 0x7d, - 0x67, 0xb0, 0x87, 0x0b, 0x88, 0xf6, 0xa1, 0x11, 0x8b, 0xf0, 0x66, 0xc2, 0x6e, 0xa9, 0x57, 0xe9, - 0x3b, 0x83, 0x0e, 0x5e, 0x61, 0x34, 0x82, 0x1a, 0xe3, 0x49, 0x2e, 0x33, 0xcf, 0xed, 0xbb, 0x83, - 0xd6, 0xb1, 0x3f, 0xdc, 0x6c, 0x34, 0x2c, 0x35, 0xb9, 0x50, 0xa9, 0xd8, 0x56, 0xa0, 0x57, 0x50, - 0x17, 0xb9, 0xd4, 0xc5, 0x55, 0x5d, 0xfc, 0xf8, 0xde, 0xe2, 0x4b, 0x9d, 0x8b, 0x8b, 0x1a, 0xff, - 0x9b, 0x03, 0xbd, 0x4d, 0x6e, 0x74, 0x06, 0xdd, 0x24, 0xa5, 0x73, 0x26, 0xf2, 0xcc, 0xe4, 0x6b, - 0x31, 0xad, 0xe3, 0xfd, 0x6d, 0xea, 0xcb, 0x5c, 0x8e, 0x05, 0xe3, 0x12, 0x6f, 0x54, 0x28, 0xbd, - 0x19, 0xfd, 0x9c, 0x53, 0x1e, 0xae, 0xf4, 0x16, 0x18, 0xfd, 0x03, 0xb5, 0x2c, 0x4c, 0x59, 0x22, - 0x3d, 0xb7, 0xef, 0x0c, 0xda, 0xd8, 0x22, 0x7f, 0x0c, 0x8d, 0x82, 0x0f, 0x21, 0xa8, 0xce, 0x48, - 0x36, 0xd3, 0x9d, 0xdb, 0x58, 0x9f, 0xd1, 0x5f, 0xb0, 0xc3, 0x78, 0x44, 0x17, 0x96, 0xd0, 0x80, - 0xb5, 0x4e, 0xee, 0x7a, 0x27, 0xff, 0x14, 0xf6, 0xb6, 0xc4, 0x2b, 0x9a, 0x39, 0x89, 0x73, 0xaa, - 0xb9, 0x5d, 0x6c, 0x40, 0xe9, 0x52, 0x95, 0xb5, 0x4b, 0x7d, 0x05, 0xf4, 0x9e, 0x67, 0x09, 0xe5, - 0xb2, 0x3c, 0xe8, 0x97, 0xd0, 0x14, 0xb9, 0x0c, 0x12, 0x75, 0xd7, 0x07, 0xb8, 0xd3, 0x10, 0x85, - 0xae, 0x3f, 0xb4, 0x51, 0x71, 0x72, 0x2b, 0x72, 0x6e, 0x3c, 0x71, 0xb1, 0x45, 0xfe, 0x2f, 0x17, - 0xda, 0x57, 0xec, 0x9a, 0x33, 0x7e, 0x6d, 0x86, 0x73, 0x00, 0x4d, 0x65, 0x46, 0x20, 0x97, 0x89, - 0x51, 0xd0, 0xc1, 0x0d, 0x15, 0x98, 0x2c, 0x13, 0x5a, 0x62, 0xa9, 0x94, 0x59, 0xd0, 0x7f, 0xd0, - 0x98, 0x2e, 0x25, 0x0d, 0x3e, 0x52, 0x6a, 0xf9, 0xeb, 0x0a, 0xbf, 0xa1, 0x14, 0x1d, 0x02, 0x48, - 0x11, 0x90, 0x28, 0x4a, 0x69, 0xa6, 0x76, 0xc8, 0x19, 0x34, 0x71, 0x53, 0x8a, 0x53, 0x13, 0x40, - 0x4f, 0xa0, 0x1b, 0xce, 0x08, 0xbf, 0xa6, 0xab, 0x94, 0x1d, 0x9d, 0xd2, 0x31, 0xd1, 0x22, 0xed, - 0x7f, 0x68, 0x25, 0x29, 0x9b, 0x13, 0x49, 0x83, 0x1b, 0xba, 0xf4, 0xa0, 0xef, 0x0e, 0xda, 0x18, - 0x6c, 0xe8, 0x2d, 0x5d, 0xa2, 0x73, 0xa8, 0x1b, 0xa5, 0x99, 0xd7, 0xd2, 0x7b, 0xfa, 0x6c, 0xdb, - 0xae, 0xb2, 0xce, 0xe1, 0x95, 0xc9, 0x3e, 0xe7, 0x32, 0x5d, 0xe2, 0xa2, 0x16, 0x9d, 0x40, 0x35, - 0x97, 0x0b, 0xe1, 0xb5, 0x35, 0xc7, 0xd1, 0x36, 0xc7, 0xf6, 0xac, 0xb0, 0xae, 0x40, 0x47, 0xd0, - 0xcd, 0x33, 0x1a, 0xdc, 0x92, 0x45, 0x60, 0x2d, 0xea, 0xf4, 0x9d, 0x41, 0x03, 0xb7, 0xf3, 0x8c, - 0xbe, 0x23, 0x8b, 0x53, 0x63, 0xd4, 0x01, 0x34, 0x15, 0x99, 0x71, 0xb7, 0x6b, 0xdc, 0x55, 0x01, - 0xed, 0xee, 0x0b, 0xa8, 0x26, 0x31, 0xe1, 0xde, 0xae, 0x9e, 0xf7, 0xa3, 0x7b, 0x1f, 0xda, 0x38, - 0x26, 0x1c, 0xeb, 0xf4, 0xfd, 0x11, 0xb4, 0xcb, 0x62, 0x50, 0x0f, 0x5c, 0xe5, 0x91, 0xa3, 0x7d, - 0x54, 0xc7, 0xbb, 0x8d, 0x34, 0x3b, 0x61, 0xc0, 0xa8, 0x72, 0xe2, 0xf8, 0x3f, 0x1d, 0xd8, 0xdd, - 0x60, 0x2d, 0x0d, 0xd9, 0x59, 0x1b, 0xf2, 0x53, 0xe8, 0x91, 0x39, 0x61, 0x31, 0x99, 0xc6, 0x34, - 0x58, 0x5b, 0x83, 0xdd, 0x55, 0xdc, 0xca, 0xec, 0x81, 0x7b, 0xb7, 0x0a, 0xea, 0xa8, 0x48, 0xcd, - 0x44, 0xf5, 0x0a, 0xb8, 0xd8, 0x22, 0x34, 0x82, 0x1d, 0x65, 0x9f, 0x1a, 0xfb, 0xc3, 0x1d, 0x37, - 0x25, 0xca, 0xcc, 0x69, 0x4a, 0x78, 0x38, 0x0b, 0x58, 0xe4, 0xd5, 0xb4, 0xb4, 0x86, 0x09, 0x5c, - 0x44, 0xfe, 0x77, 0x07, 0x3a, 0x76, 0xe0, 0xf6, 0x5d, 0xbe, 0x86, 0x96, 0xbc, 0x23, 0xb1, 0xaf, - 0xea, 0xf0, 0x5e, 0x97, 0x71, 0xb9, 0x42, 0xfd, 0xfb, 0x52, 0x1e, 0x8a, 0x88, 0x46, 0xd6, 0xc8, - 0x02, 0xaa, 0x2d, 0x2e, 0x25, 0xaa, 0xeb, 0xb8, 0x66, 0x8b, 0x4b, 0xd1, 0x8b, 0x48, 0xcd, 0x81, - 0xa6, 0xa9, 0x48, 0xed, 0x33, 0x30, 0xe0, 0xec, 0x5f, 0xf8, 0xfb, 0x0b, 0x89, 0x63, 0x2a, 0x87, - 0xa1, 0x48, 0xe9, 0xf0, 0x13, 0x67, 0xe6, 0x7b, 0x30, 0xad, 0xe9, 0x9f, 0xe7, 0xbf, 0x03, 0x00, - 0x00, 0xff, 0xff, 0x53, 0xd6, 0xbb, 0x06, 0x27, 0x06, 0x00, 0x00, +var File_Bitcoin_proto protoreflect.FileDescriptor + +var file_Bitcoin_proto_rawDesc = []byte{ + 0x0a, 0x0d, 0x42, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x10, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x1a, 0x0c, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, + 0xbe, 0x01, 0x0a, 0x0b, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x11, + 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x6f, 0x63, + 0x6b, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x6c, 0x6f, 0x63, + 0x6b, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x3a, 0x0a, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x74, 0x63, 0x6f, + 0x69, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, + 0x73, 0x12, 0x3d, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x2e, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, + 0x22, 0x8a, 0x01, 0x0a, 0x10, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x42, 0x0a, 0x0e, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, + 0x73, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x54, 0x57, 0x2e, 0x42, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0e, 0x70, 0x72, 0x65, 0x76, 0x69, + 0x6f, 0x75, 0x73, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x71, + 0x75, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x73, 0x65, 0x71, + 0x75, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0x50, 0x0a, + 0x08, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, + 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x14, 0x0a, + 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x69, 0x6e, + 0x64, 0x65, 0x78, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x22, + 0x41, 0x0a, 0x11, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x22, 0x7d, 0x0a, 0x12, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x09, 0x6f, 0x75, 0x74, 0x5f, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x54, 0x57, + 0x2e, 0x42, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4f, + 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, + 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x22, 0x46, 0x0a, 0x0d, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xdb, 0x05, 0x0a, 0x0c, 0x53, 0x69, + 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x68, 0x61, + 0x73, 0x68, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x68, + 0x61, 0x73, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x19, 0x0a, 0x08, 0x62, 0x79, 0x74, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x07, 0x62, 0x79, 0x74, 0x65, 0x46, 0x65, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, + 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x74, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, + 0x06, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, + 0x79, 0x12, 0x45, 0x0a, 0x07, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x18, 0x07, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x2e, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x70, + 0x75, 0x74, 0x2e, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x07, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x12, 0x38, 0x0a, 0x04, 0x75, 0x74, 0x78, 0x6f, + 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x74, 0x63, + 0x6f, 0x69, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, + 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x04, 0x75, 0x74, + 0x78, 0x6f, 0x12, 0x24, 0x0a, 0x0e, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x61, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x75, 0x73, 0x65, 0x4d, + 0x61, 0x78, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x69, 0x6e, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x63, 0x6f, 0x69, + 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, + 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x1b, 0x0a, 0x09, + 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x75, 0x74, + 0x70, 0x75, 0x74, 0x5f, 0x6f, 0x70, 0x5f, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x18, 0x0d, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4f, 0x70, 0x52, 0x65, 0x74, + 0x75, 0x72, 0x6e, 0x12, 0x44, 0x0a, 0x0d, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x6f, 0x75, 0x74, + 0x70, 0x75, 0x74, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x54, 0x57, 0x2e, + 0x42, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x0c, 0x65, 0x78, 0x74, + 0x72, 0x61, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x20, 0x0a, 0x0c, 0x75, 0x73, 0x65, + 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x75, 0x74, 0x78, 0x6f, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0a, 0x75, 0x73, 0x65, 0x4d, 0x61, 0x78, 0x55, 0x74, 0x78, 0x6f, 0x12, 0x2e, 0x0a, 0x13, 0x64, + 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x64, 0x75, 0x73, 0x74, 0x5f, 0x66, 0x69, 0x6c, 0x74, + 0x65, 0x72, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, + 0x65, 0x44, 0x75, 0x73, 0x74, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x1a, 0x3a, 0x0a, 0x0c, 0x53, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb6, 0x02, 0x0a, 0x0f, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6c, 0x61, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, + 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x61, + 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x10, + 0x0a, 0x03, 0x66, 0x65, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x66, 0x65, 0x65, + 0x12, 0x16, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x3a, 0x0a, 0x05, 0x75, 0x74, 0x78, 0x6f, + 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x74, + 0x63, 0x6f, 0x69, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55, 0x6e, 0x73, 0x70, 0x65, + 0x6e, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x75, + 0x74, 0x78, 0x6f, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x5f, 0x69, + 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x49, + 0x64, 0x12, 0x33, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x1d, 0x2e, 0x54, 0x57, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, + 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x5f, 0x6f, 0x70, 0x5f, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4f, 0x70, 0x52, 0x65, 0x74, 0x75, 0x72, 0x6e, + 0x22, 0xeb, 0x01, 0x0a, 0x0d, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x70, + 0x75, 0x74, 0x12, 0x3f, 0x0a, 0x0b, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x74, + 0x63, 0x6f, 0x69, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x12, 0x25, 0x0a, + 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x54, 0x57, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x45, 0x72, 0x72, + 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x54, + 0x0a, 0x0d, 0x48, 0x61, 0x73, 0x68, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, + 0x1b, 0x0a, 0x09, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x48, 0x61, 0x73, 0x68, 0x12, 0x26, 0x0a, 0x0f, + 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, + 0x48, 0x61, 0x73, 0x68, 0x22, 0xb7, 0x01, 0x0a, 0x10, 0x50, 0x72, 0x65, 0x53, 0x69, 0x67, 0x6e, + 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x49, 0x0a, 0x10, 0x68, 0x61, 0x73, + 0x68, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x54, 0x57, 0x2e, 0x42, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, + 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x50, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x4b, 0x65, 0x79, 0x52, 0x0e, 0x68, 0x61, 0x73, 0x68, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x4b, 0x65, 0x79, 0x73, 0x12, 0x33, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x54, 0x57, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x45, 0x72, 0x72, + 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x58, + 0x0a, 0x15, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x6a, 0x6e, + 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x5a, 0x3f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2d, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2d, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, 0x75, + 0x74, 0x78, 0x6f, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_Bitcoin_proto_rawDescOnce sync.Once + file_Bitcoin_proto_rawDescData = file_Bitcoin_proto_rawDesc +) + +func file_Bitcoin_proto_rawDescGZIP() []byte { + file_Bitcoin_proto_rawDescOnce.Do(func() { + file_Bitcoin_proto_rawDescData = protoimpl.X.CompressGZIP(file_Bitcoin_proto_rawDescData) + }) + return file_Bitcoin_proto_rawDescData +} + +var file_Bitcoin_proto_msgTypes = make([]protoimpl.MessageInfo, 12) +var file_Bitcoin_proto_goTypes = []interface{}{ + (*Transaction)(nil), // 0: TW.Bitcoin.Proto.Transaction + (*TransactionInput)(nil), // 1: TW.Bitcoin.Proto.TransactionInput + (*OutPoint)(nil), // 2: TW.Bitcoin.Proto.OutPoint + (*TransactionOutput)(nil), // 3: TW.Bitcoin.Proto.TransactionOutput + (*UnspentTransaction)(nil), // 4: TW.Bitcoin.Proto.UnspentTransaction + (*OutputAddress)(nil), // 5: TW.Bitcoin.Proto.OutputAddress + (*SigningInput)(nil), // 6: TW.Bitcoin.Proto.SigningInput + (*TransactionPlan)(nil), // 7: TW.Bitcoin.Proto.TransactionPlan + (*SigningOutput)(nil), // 8: TW.Bitcoin.Proto.SigningOutput + (*HashPublicKey)(nil), // 9: TW.Bitcoin.Proto.HashPublicKey + (*PreSigningOutput)(nil), // 10: TW.Bitcoin.Proto.PreSigningOutput + nil, // 11: TW.Bitcoin.Proto.SigningInput.ScriptsEntry + (common.SigningError)(0), // 12: TW.Common.Proto.SigningError +} +var file_Bitcoin_proto_depIdxs = []int32{ + 1, // 0: TW.Bitcoin.Proto.Transaction.inputs:type_name -> TW.Bitcoin.Proto.TransactionInput + 3, // 1: TW.Bitcoin.Proto.Transaction.outputs:type_name -> TW.Bitcoin.Proto.TransactionOutput + 2, // 2: TW.Bitcoin.Proto.TransactionInput.previousOutput:type_name -> TW.Bitcoin.Proto.OutPoint + 2, // 3: TW.Bitcoin.Proto.UnspentTransaction.out_point:type_name -> TW.Bitcoin.Proto.OutPoint + 11, // 4: TW.Bitcoin.Proto.SigningInput.scripts:type_name -> TW.Bitcoin.Proto.SigningInput.ScriptsEntry + 4, // 5: TW.Bitcoin.Proto.SigningInput.utxo:type_name -> TW.Bitcoin.Proto.UnspentTransaction + 7, // 6: TW.Bitcoin.Proto.SigningInput.plan:type_name -> TW.Bitcoin.Proto.TransactionPlan + 5, // 7: TW.Bitcoin.Proto.SigningInput.extra_outputs:type_name -> TW.Bitcoin.Proto.OutputAddress + 4, // 8: TW.Bitcoin.Proto.TransactionPlan.utxos:type_name -> TW.Bitcoin.Proto.UnspentTransaction + 12, // 9: TW.Bitcoin.Proto.TransactionPlan.error:type_name -> TW.Common.Proto.SigningError + 0, // 10: TW.Bitcoin.Proto.SigningOutput.transaction:type_name -> TW.Bitcoin.Proto.Transaction + 12, // 11: TW.Bitcoin.Proto.SigningOutput.error:type_name -> TW.Common.Proto.SigningError + 9, // 12: TW.Bitcoin.Proto.PreSigningOutput.hash_public_keys:type_name -> TW.Bitcoin.Proto.HashPublicKey + 12, // 13: TW.Bitcoin.Proto.PreSigningOutput.error:type_name -> TW.Common.Proto.SigningError + 14, // [14:14] is the sub-list for method output_type + 14, // [14:14] is the sub-list for method input_type + 14, // [14:14] is the sub-list for extension type_name + 14, // [14:14] is the sub-list for extension extendee + 0, // [0:14] is the sub-list for field type_name +} + +func init() { file_Bitcoin_proto_init() } +func file_Bitcoin_proto_init() { + if File_Bitcoin_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_Bitcoin_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Transaction); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Bitcoin_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TransactionInput); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Bitcoin_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OutPoint); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Bitcoin_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TransactionOutput); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Bitcoin_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UnspentTransaction); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Bitcoin_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OutputAddress); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Bitcoin_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SigningInput); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Bitcoin_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TransactionPlan); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Bitcoin_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SigningOutput); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Bitcoin_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HashPublicKey); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Bitcoin_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PreSigningOutput); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_Bitcoin_proto_rawDesc, + NumEnums: 0, + NumMessages: 12, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_Bitcoin_proto_goTypes, + DependencyIndexes: file_Bitcoin_proto_depIdxs, + MessageInfos: file_Bitcoin_proto_msgTypes, + }.Build() + File_Bitcoin_proto = out.File + file_Bitcoin_proto_rawDesc = nil + file_Bitcoin_proto_goTypes = nil + file_Bitcoin_proto_depIdxs = nil } diff --git a/samples/go/protos/common/Common.pb.go b/samples/go/protos/common/Common.pb.go new file mode 100644 index 00000000000..bc50f992214 --- /dev/null +++ b/samples/go/protos/common/Common.pb.go @@ -0,0 +1,236 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.0 +// protoc v3.19.2 +// source: Common.proto + +package common + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type SigningError int32 + +const ( + SigningError_OK SigningError = 0 // OK + // chain-generic, generic + SigningError_Error_general SigningError = 1 + SigningError_Error_internal SigningError = 2 + // chain-generic, input + SigningError_Error_low_balance SigningError = 3 + SigningError_Error_zero_amount_requested SigningError = 4 // Requested amount is zero + SigningError_Error_missing_private_key SigningError = 5 + SigningError_Error_invalid_private_key SigningError = 15 + SigningError_Error_invalid_address SigningError = 16 + SigningError_Error_invalid_utxo SigningError = 17 + SigningError_Error_invalid_utxo_amount SigningError = 18 + // chain-generic, fee + SigningError_Error_wrong_fee SigningError = 6 + // chain-generic, signing + SigningError_Error_signing SigningError = 7 + SigningError_Error_tx_too_big SigningError = 8 // [NEO] Transaction too big, fee in GAS needed or try send by parts + // UTXO-chain specific, inputs + SigningError_Error_missing_input_utxos SigningError = 9 // No UTXOs provided [BTC] + SigningError_Error_not_enough_utxos SigningError = 10 // Not enough non-dust input UTXOs to cover requested amount (dust UTXOs are filtered out) [BTC] + // UTXO-chain specific, script + SigningError_Error_script_redeem SigningError = 11 // [BTC] Missing redeem script + SigningError_Error_script_output SigningError = 12 // [BTC] Invalid output script + SigningError_Error_script_witness_program SigningError = 13 // [BTC] Unrecognized witness program + SigningError_Error_invalid_memo SigningError = 14 // e.g. [XRP] Invalid tag + SigningError_Error_input_parse SigningError = 19 // e.g. Invalid input data + SigningError_Error_no_support_n2n SigningError = 20 // e.g. Not support n2n transaction + SigningError_Error_signatures_count SigningError = 21 // Incorrect count of signatures passed to compile + SigningError_Error_invalid_params SigningError = 22 // Incorrect parameters +) + +// Enum value maps for SigningError. +var ( + SigningError_name = map[int32]string{ + 0: "OK", + 1: "Error_general", + 2: "Error_internal", + 3: "Error_low_balance", + 4: "Error_zero_amount_requested", + 5: "Error_missing_private_key", + 15: "Error_invalid_private_key", + 16: "Error_invalid_address", + 17: "Error_invalid_utxo", + 18: "Error_invalid_utxo_amount", + 6: "Error_wrong_fee", + 7: "Error_signing", + 8: "Error_tx_too_big", + 9: "Error_missing_input_utxos", + 10: "Error_not_enough_utxos", + 11: "Error_script_redeem", + 12: "Error_script_output", + 13: "Error_script_witness_program", + 14: "Error_invalid_memo", + 19: "Error_input_parse", + 20: "Error_no_support_n2n", + 21: "Error_signatures_count", + 22: "Error_invalid_params", + } + SigningError_value = map[string]int32{ + "OK": 0, + "Error_general": 1, + "Error_internal": 2, + "Error_low_balance": 3, + "Error_zero_amount_requested": 4, + "Error_missing_private_key": 5, + "Error_invalid_private_key": 15, + "Error_invalid_address": 16, + "Error_invalid_utxo": 17, + "Error_invalid_utxo_amount": 18, + "Error_wrong_fee": 6, + "Error_signing": 7, + "Error_tx_too_big": 8, + "Error_missing_input_utxos": 9, + "Error_not_enough_utxos": 10, + "Error_script_redeem": 11, + "Error_script_output": 12, + "Error_script_witness_program": 13, + "Error_invalid_memo": 14, + "Error_input_parse": 19, + "Error_no_support_n2n": 20, + "Error_signatures_count": 21, + "Error_invalid_params": 22, + } +) + +func (x SigningError) Enum() *SigningError { + p := new(SigningError) + *p = x + return p +} + +func (x SigningError) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SigningError) Descriptor() protoreflect.EnumDescriptor { + return file_Common_proto_enumTypes[0].Descriptor() +} + +func (SigningError) Type() protoreflect.EnumType { + return &file_Common_proto_enumTypes[0] +} + +func (x SigningError) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SigningError.Descriptor instead. +func (SigningError) EnumDescriptor() ([]byte, []int) { + return file_Common_proto_rawDescGZIP(), []int{0} +} + +var File_Common_proto protoreflect.FileDescriptor + +var file_Common_proto_rawDesc = []byte{ + 0x0a, 0x0c, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, + 0x54, 0x57, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2a, + 0xd1, 0x04, 0x0a, 0x0c, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x12, 0x06, 0x0a, 0x02, 0x4f, 0x4b, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x45, 0x72, 0x72, 0x6f, + 0x72, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x45, + 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x10, 0x02, 0x12, + 0x15, 0x0a, 0x11, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6c, 0x6f, 0x77, 0x5f, 0x62, 0x61, 0x6c, + 0x61, 0x6e, 0x63, 0x65, 0x10, 0x03, 0x12, 0x1f, 0x0a, 0x1b, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x5f, + 0x7a, 0x65, 0x72, 0x6f, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x65, 0x64, 0x10, 0x04, 0x12, 0x1d, 0x0a, 0x19, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x5f, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, + 0x5f, 0x6b, 0x65, 0x79, 0x10, 0x05, 0x12, 0x1d, 0x0a, 0x19, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x5f, + 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, + 0x6b, 0x65, 0x79, 0x10, 0x0f, 0x12, 0x19, 0x0a, 0x15, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x69, + 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, 0x10, + 0x12, 0x16, 0x0a, 0x12, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, + 0x64, 0x5f, 0x75, 0x74, 0x78, 0x6f, 0x10, 0x11, 0x12, 0x1d, 0x0a, 0x19, 0x45, 0x72, 0x72, 0x6f, + 0x72, 0x5f, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x75, 0x74, 0x78, 0x6f, 0x5f, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x10, 0x12, 0x12, 0x13, 0x0a, 0x0f, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x5f, 0x77, 0x72, 0x6f, 0x6e, 0x67, 0x5f, 0x66, 0x65, 0x65, 0x10, 0x06, 0x12, 0x11, 0x0a, 0x0d, + 0x45, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x10, 0x07, 0x12, + 0x14, 0x0a, 0x10, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x74, 0x78, 0x5f, 0x74, 0x6f, 0x6f, 0x5f, + 0x62, 0x69, 0x67, 0x10, 0x08, 0x12, 0x1d, 0x0a, 0x19, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x75, 0x74, 0x78, + 0x6f, 0x73, 0x10, 0x09, 0x12, 0x1a, 0x0a, 0x16, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6e, 0x6f, + 0x74, 0x5f, 0x65, 0x6e, 0x6f, 0x75, 0x67, 0x68, 0x5f, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x10, 0x0a, + 0x12, 0x17, 0x0a, 0x13, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x5f, 0x72, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x10, 0x0b, 0x12, 0x17, 0x0a, 0x13, 0x45, 0x72, 0x72, + 0x6f, 0x72, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x10, 0x0c, 0x12, 0x20, 0x0a, 0x1c, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x5f, 0x77, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x67, 0x72, + 0x61, 0x6d, 0x10, 0x0d, 0x12, 0x16, 0x0a, 0x12, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x69, 0x6e, + 0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x10, 0x0e, 0x12, 0x15, 0x0a, 0x11, + 0x45, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x70, 0x61, 0x72, 0x73, + 0x65, 0x10, 0x13, 0x12, 0x18, 0x0a, 0x14, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6e, 0x6f, 0x5f, + 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6e, 0x32, 0x6e, 0x10, 0x14, 0x12, 0x1a, 0x0a, + 0x16, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x10, 0x15, 0x12, 0x18, 0x0a, 0x14, 0x45, 0x72, 0x72, + 0x6f, 0x72, 0x5f, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, + 0x73, 0x10, 0x16, 0x42, 0x55, 0x0a, 0x15, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x6a, 0x6e, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x5a, 0x3c, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, + 0x2d, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2d, 0x69, 0x6e, 0x74, + 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x63, 0x68, + 0x61, 0x69, 0x6e, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var ( + file_Common_proto_rawDescOnce sync.Once + file_Common_proto_rawDescData = file_Common_proto_rawDesc +) + +func file_Common_proto_rawDescGZIP() []byte { + file_Common_proto_rawDescOnce.Do(func() { + file_Common_proto_rawDescData = protoimpl.X.CompressGZIP(file_Common_proto_rawDescData) + }) + return file_Common_proto_rawDescData +} + +var file_Common_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_Common_proto_goTypes = []interface{}{ + (SigningError)(0), // 0: TW.Common.Proto.SigningError +} +var file_Common_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_Common_proto_init() } +func file_Common_proto_init() { + if File_Common_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_Common_proto_rawDesc, + NumEnums: 1, + NumMessages: 0, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_Common_proto_goTypes, + DependencyIndexes: file_Common_proto_depIdxs, + EnumInfos: file_Common_proto_enumTypes, + }.Build() + File_Common_proto = out.File + file_Common_proto_rawDesc = nil + file_Common_proto_goTypes = nil + file_Common_proto_depIdxs = nil +} diff --git a/samples/go/protos/common/TransactionCompiler.pb.go b/samples/go/protos/common/TransactionCompiler.pb.go new file mode 100644 index 00000000000..8baf5d4f998 --- /dev/null +++ b/samples/go/protos/common/TransactionCompiler.pb.go @@ -0,0 +1,188 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.0 +// protoc v3.19.2 +// source: TransactionCompiler.proto + +package common + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +/// Transaction pre-signing output +type PreSigningOutput struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + /// Pre-image data hash that will be used for signing + DataHash []byte `protobuf:"bytes,1,opt,name=data_hash,json=dataHash,proto3" json:"data_hash,omitempty"` + /// Pre-image data + Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + /// error code, 0 is ok, other codes will be treated as errors + Error SigningError `protobuf:"varint,3,opt,name=error,proto3,enum=TW.Common.Proto.SigningError" json:"error,omitempty"` + /// error code description + ErrorMessage string `protobuf:"bytes,4,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` +} + +func (x *PreSigningOutput) Reset() { + *x = PreSigningOutput{} + if protoimpl.UnsafeEnabled { + mi := &file_TransactionCompiler_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PreSigningOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PreSigningOutput) ProtoMessage() {} + +func (x *PreSigningOutput) ProtoReflect() protoreflect.Message { + mi := &file_TransactionCompiler_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PreSigningOutput.ProtoReflect.Descriptor instead. +func (*PreSigningOutput) Descriptor() ([]byte, []int) { + return file_TransactionCompiler_proto_rawDescGZIP(), []int{0} +} + +func (x *PreSigningOutput) GetDataHash() []byte { + if x != nil { + return x.DataHash + } + return nil +} + +func (x *PreSigningOutput) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +func (x *PreSigningOutput) GetError() SigningError { + if x != nil { + return x.Error + } + return SigningError_OK +} + +func (x *PreSigningOutput) GetErrorMessage() string { + if x != nil { + return x.ErrorMessage + } + return "" +} + +var File_TransactionCompiler_proto protoreflect.FileDescriptor + +var file_TransactionCompiler_proto_rawDesc = []byte{ + 0x0a, 0x19, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, + 0x70, 0x69, 0x6c, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x13, 0x54, 0x57, 0x2e, + 0x54, 0x78, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, + 0x1a, 0x0c, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9d, + 0x01, 0x0a, 0x10, 0x50, 0x72, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, + 0x70, 0x75, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x68, 0x61, 0x73, 0x68, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x48, 0x61, 0x73, 0x68, + 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, + 0x64, 0x61, 0x74, 0x61, 0x12, 0x33, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x54, 0x57, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x45, 0x72, 0x72, + 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x55, + 0x0a, 0x15, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x6a, 0x6e, + 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x5a, 0x3c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2d, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2d, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, 0x63, + 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_TransactionCompiler_proto_rawDescOnce sync.Once + file_TransactionCompiler_proto_rawDescData = file_TransactionCompiler_proto_rawDesc +) + +func file_TransactionCompiler_proto_rawDescGZIP() []byte { + file_TransactionCompiler_proto_rawDescOnce.Do(func() { + file_TransactionCompiler_proto_rawDescData = protoimpl.X.CompressGZIP(file_TransactionCompiler_proto_rawDescData) + }) + return file_TransactionCompiler_proto_rawDescData +} + +var file_TransactionCompiler_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_TransactionCompiler_proto_goTypes = []interface{}{ + (*PreSigningOutput)(nil), // 0: TW.TxCompiler.Proto.PreSigningOutput + (SigningError)(0), // 1: TW.Common.Proto.SigningError +} +var file_TransactionCompiler_proto_depIdxs = []int32{ + 1, // 0: TW.TxCompiler.Proto.PreSigningOutput.error:type_name -> TW.Common.Proto.SigningError + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_TransactionCompiler_proto_init() } +func file_TransactionCompiler_proto_init() { + if File_TransactionCompiler_proto != nil { + return + } + file_Common_proto_init() + if !protoimpl.UnsafeEnabled { + file_TransactionCompiler_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PreSigningOutput); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_TransactionCompiler_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_TransactionCompiler_proto_goTypes, + DependencyIndexes: file_TransactionCompiler_proto_depIdxs, + MessageInfos: file_TransactionCompiler_proto_msgTypes, + }.Build() + File_TransactionCompiler_proto = out.File + file_TransactionCompiler_proto_rawDesc = nil + file_TransactionCompiler_proto_goTypes = nil + file_TransactionCompiler_proto_depIdxs = nil +} diff --git a/samples/go/protos/ethereum/Ethereum.pb.go b/samples/go/protos/ethereum/Ethereum.pb.go new file mode 100644 index 00000000000..14707736bce --- /dev/null +++ b/samples/go/protos/ethereum/Ethereum.pb.go @@ -0,0 +1,1122 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.0 +// protoc v3.19.2 +// source: Ethereum.proto + +package ethereum + +import ( + common "tw/protos/common" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type TransactionMode int32 + +const ( + TransactionMode_Legacy TransactionMode = 0 // Legacy transaction, pre-EIP2718/EIP1559; for fee gasPrice/gasLimit is used + TransactionMode_Enveloped TransactionMode = 1 // Enveloped transaction EIP2718 (with type 0x2), fee is according to EIP1559 (base fee, inclusion fee, ...) +) + +// Enum value maps for TransactionMode. +var ( + TransactionMode_name = map[int32]string{ + 0: "Legacy", + 1: "Enveloped", + } + TransactionMode_value = map[string]int32{ + "Legacy": 0, + "Enveloped": 1, + } +) + +func (x TransactionMode) Enum() *TransactionMode { + p := new(TransactionMode) + *p = x + return p +} + +func (x TransactionMode) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (TransactionMode) Descriptor() protoreflect.EnumDescriptor { + return file_Ethereum_proto_enumTypes[0].Descriptor() +} + +func (TransactionMode) Type() protoreflect.EnumType { + return &file_Ethereum_proto_enumTypes[0] +} + +func (x TransactionMode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use TransactionMode.Descriptor instead. +func (TransactionMode) EnumDescriptor() ([]byte, []int) { + return file_Ethereum_proto_rawDescGZIP(), []int{0} +} + +// Transaction (transfer, smart contract call, ...) +type Transaction struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to TransactionOneof: + // *Transaction_Transfer_ + // *Transaction_Erc20Transfer + // *Transaction_Erc20Approve + // *Transaction_Erc721Transfer + // *Transaction_Erc1155Transfer + // *Transaction_ContractGeneric_ + TransactionOneof isTransaction_TransactionOneof `protobuf_oneof:"transaction_oneof"` +} + +func (x *Transaction) Reset() { + *x = Transaction{} + if protoimpl.UnsafeEnabled { + mi := &file_Ethereum_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Transaction) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Transaction) ProtoMessage() {} + +func (x *Transaction) ProtoReflect() protoreflect.Message { + mi := &file_Ethereum_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Transaction.ProtoReflect.Descriptor instead. +func (*Transaction) Descriptor() ([]byte, []int) { + return file_Ethereum_proto_rawDescGZIP(), []int{0} +} + +func (m *Transaction) GetTransactionOneof() isTransaction_TransactionOneof { + if m != nil { + return m.TransactionOneof + } + return nil +} + +func (x *Transaction) GetTransfer() *Transaction_Transfer { + if x, ok := x.GetTransactionOneof().(*Transaction_Transfer_); ok { + return x.Transfer + } + return nil +} + +func (x *Transaction) GetErc20Transfer() *Transaction_ERC20Transfer { + if x, ok := x.GetTransactionOneof().(*Transaction_Erc20Transfer); ok { + return x.Erc20Transfer + } + return nil +} + +func (x *Transaction) GetErc20Approve() *Transaction_ERC20Approve { + if x, ok := x.GetTransactionOneof().(*Transaction_Erc20Approve); ok { + return x.Erc20Approve + } + return nil +} + +func (x *Transaction) GetErc721Transfer() *Transaction_ERC721Transfer { + if x, ok := x.GetTransactionOneof().(*Transaction_Erc721Transfer); ok { + return x.Erc721Transfer + } + return nil +} + +func (x *Transaction) GetErc1155Transfer() *Transaction_ERC1155Transfer { + if x, ok := x.GetTransactionOneof().(*Transaction_Erc1155Transfer); ok { + return x.Erc1155Transfer + } + return nil +} + +func (x *Transaction) GetContractGeneric() *Transaction_ContractGeneric { + if x, ok := x.GetTransactionOneof().(*Transaction_ContractGeneric_); ok { + return x.ContractGeneric + } + return nil +} + +type isTransaction_TransactionOneof interface { + isTransaction_TransactionOneof() +} + +type Transaction_Transfer_ struct { + Transfer *Transaction_Transfer `protobuf:"bytes,1,opt,name=transfer,proto3,oneof"` +} + +type Transaction_Erc20Transfer struct { + Erc20Transfer *Transaction_ERC20Transfer `protobuf:"bytes,2,opt,name=erc20_transfer,json=erc20Transfer,proto3,oneof"` +} + +type Transaction_Erc20Approve struct { + Erc20Approve *Transaction_ERC20Approve `protobuf:"bytes,3,opt,name=erc20_approve,json=erc20Approve,proto3,oneof"` +} + +type Transaction_Erc721Transfer struct { + Erc721Transfer *Transaction_ERC721Transfer `protobuf:"bytes,4,opt,name=erc721_transfer,json=erc721Transfer,proto3,oneof"` +} + +type Transaction_Erc1155Transfer struct { + Erc1155Transfer *Transaction_ERC1155Transfer `protobuf:"bytes,5,opt,name=erc1155_transfer,json=erc1155Transfer,proto3,oneof"` +} + +type Transaction_ContractGeneric_ struct { + ContractGeneric *Transaction_ContractGeneric `protobuf:"bytes,6,opt,name=contract_generic,json=contractGeneric,proto3,oneof"` +} + +func (*Transaction_Transfer_) isTransaction_TransactionOneof() {} + +func (*Transaction_Erc20Transfer) isTransaction_TransactionOneof() {} + +func (*Transaction_Erc20Approve) isTransaction_TransactionOneof() {} + +func (*Transaction_Erc721Transfer) isTransaction_TransactionOneof() {} + +func (*Transaction_Erc1155Transfer) isTransaction_TransactionOneof() {} + +func (*Transaction_ContractGeneric_) isTransaction_TransactionOneof() {} + +// Input data necessary to create a signed transaction. +// Legacy and EIP2718/EIP1559 transactions supported, see TransactionMode. +type SigningInput struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Chain identifier (256-bit number) + ChainId []byte `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` + // Nonce (256-bit number) + Nonce []byte `protobuf:"bytes,2,opt,name=nonce,proto3" json:"nonce,omitempty"` + // Transaction version selector: Legacy or enveloped, has impact on fee structure. + // Default is Legacy (value 0) + TxMode TransactionMode `protobuf:"varint,3,opt,name=tx_mode,json=txMode,proto3,enum=TW.Ethereum.Proto.TransactionMode" json:"tx_mode,omitempty"` + // Gas price (256-bit number) + // Relevant for legacy transactions only (disregarded for enveloped/EIP1559) + GasPrice []byte `protobuf:"bytes,4,opt,name=gas_price,json=gasPrice,proto3" json:"gas_price,omitempty"` + // Gas limit (256-bit number) + GasLimit []byte `protobuf:"bytes,5,opt,name=gas_limit,json=gasLimit,proto3" json:"gas_limit,omitempty"` + // Maxinmum optional inclusion fee (aka tip) (256-bit number) + // Relevant for enveloped/EIP1559 transactions only, tx_mode=Enveloped, (disregarded for legacy) + MaxInclusionFeePerGas []byte `protobuf:"bytes,6,opt,name=max_inclusion_fee_per_gas,json=maxInclusionFeePerGas,proto3" json:"max_inclusion_fee_per_gas,omitempty"` + // Maxinmum fee (256-bit number) + // Relevant for enveloped/EIP1559 transactions only, tx_mode=Enveloped, (disregarded for legacy) + MaxFeePerGas []byte `protobuf:"bytes,7,opt,name=max_fee_per_gas,json=maxFeePerGas,proto3" json:"max_fee_per_gas,omitempty"` + // Recipient's address. + ToAddress string `protobuf:"bytes,8,opt,name=to_address,json=toAddress,proto3" json:"to_address,omitempty"` + // Private key. + PrivateKey []byte `protobuf:"bytes,9,opt,name=private_key,json=privateKey,proto3" json:"private_key,omitempty"` + Transaction *Transaction `protobuf:"bytes,10,opt,name=transaction,proto3" json:"transaction,omitempty"` +} + +func (x *SigningInput) Reset() { + *x = SigningInput{} + if protoimpl.UnsafeEnabled { + mi := &file_Ethereum_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SigningInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SigningInput) ProtoMessage() {} + +func (x *SigningInput) ProtoReflect() protoreflect.Message { + mi := &file_Ethereum_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SigningInput.ProtoReflect.Descriptor instead. +func (*SigningInput) Descriptor() ([]byte, []int) { + return file_Ethereum_proto_rawDescGZIP(), []int{1} +} + +func (x *SigningInput) GetChainId() []byte { + if x != nil { + return x.ChainId + } + return nil +} + +func (x *SigningInput) GetNonce() []byte { + if x != nil { + return x.Nonce + } + return nil +} + +func (x *SigningInput) GetTxMode() TransactionMode { + if x != nil { + return x.TxMode + } + return TransactionMode_Legacy +} + +func (x *SigningInput) GetGasPrice() []byte { + if x != nil { + return x.GasPrice + } + return nil +} + +func (x *SigningInput) GetGasLimit() []byte { + if x != nil { + return x.GasLimit + } + return nil +} + +func (x *SigningInput) GetMaxInclusionFeePerGas() []byte { + if x != nil { + return x.MaxInclusionFeePerGas + } + return nil +} + +func (x *SigningInput) GetMaxFeePerGas() []byte { + if x != nil { + return x.MaxFeePerGas + } + return nil +} + +func (x *SigningInput) GetToAddress() string { + if x != nil { + return x.ToAddress + } + return "" +} + +func (x *SigningInput) GetPrivateKey() []byte { + if x != nil { + return x.PrivateKey + } + return nil +} + +func (x *SigningInput) GetTransaction() *Transaction { + if x != nil { + return x.Transaction + } + return nil +} + +// Transaction signing output. +type SigningOutput struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Signed and encoded transaction bytes. + Encoded []byte `protobuf:"bytes,1,opt,name=encoded,proto3" json:"encoded,omitempty"` + V []byte `protobuf:"bytes,2,opt,name=v,proto3" json:"v,omitempty"` + R []byte `protobuf:"bytes,3,opt,name=r,proto3" json:"r,omitempty"` + S []byte `protobuf:"bytes,4,opt,name=s,proto3" json:"s,omitempty"` + // The payload part, supplied in the input or assembled from input parameters + Data []byte `protobuf:"bytes,5,opt,name=data,proto3" json:"data,omitempty"` + /// error code, 0 is ok, other codes will be treated as errors + Error common.SigningError `protobuf:"varint,6,opt,name=error,proto3,enum=TW.Common.Proto.SigningError" json:"error,omitempty"` + /// error code description + ErrorMessage string `protobuf:"bytes,7,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` +} + +func (x *SigningOutput) Reset() { + *x = SigningOutput{} + if protoimpl.UnsafeEnabled { + mi := &file_Ethereum_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SigningOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SigningOutput) ProtoMessage() {} + +func (x *SigningOutput) ProtoReflect() protoreflect.Message { + mi := &file_Ethereum_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SigningOutput.ProtoReflect.Descriptor instead. +func (*SigningOutput) Descriptor() ([]byte, []int) { + return file_Ethereum_proto_rawDescGZIP(), []int{2} +} + +func (x *SigningOutput) GetEncoded() []byte { + if x != nil { + return x.Encoded + } + return nil +} + +func (x *SigningOutput) GetV() []byte { + if x != nil { + return x.V + } + return nil +} + +func (x *SigningOutput) GetR() []byte { + if x != nil { + return x.R + } + return nil +} + +func (x *SigningOutput) GetS() []byte { + if x != nil { + return x.S + } + return nil +} + +func (x *SigningOutput) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +func (x *SigningOutput) GetError() common.SigningError { + if x != nil { + return x.Error + } + return common.SigningError(0) +} + +func (x *SigningOutput) GetErrorMessage() string { + if x != nil { + return x.ErrorMessage + } + return "" +} + +// Native coin transfer transaction +type Transaction_Transfer struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Amount to send in wei (256-bit number) + Amount []byte `protobuf:"bytes,1,opt,name=amount,proto3" json:"amount,omitempty"` + // Optional payload data + Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *Transaction_Transfer) Reset() { + *x = Transaction_Transfer{} + if protoimpl.UnsafeEnabled { + mi := &file_Ethereum_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Transaction_Transfer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Transaction_Transfer) ProtoMessage() {} + +func (x *Transaction_Transfer) ProtoReflect() protoreflect.Message { + mi := &file_Ethereum_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Transaction_Transfer.ProtoReflect.Descriptor instead. +func (*Transaction_Transfer) Descriptor() ([]byte, []int) { + return file_Ethereum_proto_rawDescGZIP(), []int{0, 0} +} + +func (x *Transaction_Transfer) GetAmount() []byte { + if x != nil { + return x.Amount + } + return nil +} + +func (x *Transaction_Transfer) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +// ERC20 token transfer transaction +type Transaction_ERC20Transfer struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + To string `protobuf:"bytes,1,opt,name=to,proto3" json:"to,omitempty"` + // Amount to send (256-bit number) + Amount []byte `protobuf:"bytes,2,opt,name=amount,proto3" json:"amount,omitempty"` +} + +func (x *Transaction_ERC20Transfer) Reset() { + *x = Transaction_ERC20Transfer{} + if protoimpl.UnsafeEnabled { + mi := &file_Ethereum_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Transaction_ERC20Transfer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Transaction_ERC20Transfer) ProtoMessage() {} + +func (x *Transaction_ERC20Transfer) ProtoReflect() protoreflect.Message { + mi := &file_Ethereum_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Transaction_ERC20Transfer.ProtoReflect.Descriptor instead. +func (*Transaction_ERC20Transfer) Descriptor() ([]byte, []int) { + return file_Ethereum_proto_rawDescGZIP(), []int{0, 1} +} + +func (x *Transaction_ERC20Transfer) GetTo() string { + if x != nil { + return x.To + } + return "" +} + +func (x *Transaction_ERC20Transfer) GetAmount() []byte { + if x != nil { + return x.Amount + } + return nil +} + +// ERC20 approve transaction +type Transaction_ERC20Approve struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Spender string `protobuf:"bytes,1,opt,name=spender,proto3" json:"spender,omitempty"` + // Amount to send (256-bit number) + Amount []byte `protobuf:"bytes,2,opt,name=amount,proto3" json:"amount,omitempty"` +} + +func (x *Transaction_ERC20Approve) Reset() { + *x = Transaction_ERC20Approve{} + if protoimpl.UnsafeEnabled { + mi := &file_Ethereum_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Transaction_ERC20Approve) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Transaction_ERC20Approve) ProtoMessage() {} + +func (x *Transaction_ERC20Approve) ProtoReflect() protoreflect.Message { + mi := &file_Ethereum_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Transaction_ERC20Approve.ProtoReflect.Descriptor instead. +func (*Transaction_ERC20Approve) Descriptor() ([]byte, []int) { + return file_Ethereum_proto_rawDescGZIP(), []int{0, 2} +} + +func (x *Transaction_ERC20Approve) GetSpender() string { + if x != nil { + return x.Spender + } + return "" +} + +func (x *Transaction_ERC20Approve) GetAmount() []byte { + if x != nil { + return x.Amount + } + return nil +} + +// ERC721 NFT transfer transaction +type Transaction_ERC721Transfer struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + From string `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` + To string `protobuf:"bytes,2,opt,name=to,proto3" json:"to,omitempty"` + // ID of the token (256-bit number) + TokenId []byte `protobuf:"bytes,3,opt,name=token_id,json=tokenId,proto3" json:"token_id,omitempty"` +} + +func (x *Transaction_ERC721Transfer) Reset() { + *x = Transaction_ERC721Transfer{} + if protoimpl.UnsafeEnabled { + mi := &file_Ethereum_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Transaction_ERC721Transfer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Transaction_ERC721Transfer) ProtoMessage() {} + +func (x *Transaction_ERC721Transfer) ProtoReflect() protoreflect.Message { + mi := &file_Ethereum_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Transaction_ERC721Transfer.ProtoReflect.Descriptor instead. +func (*Transaction_ERC721Transfer) Descriptor() ([]byte, []int) { + return file_Ethereum_proto_rawDescGZIP(), []int{0, 3} +} + +func (x *Transaction_ERC721Transfer) GetFrom() string { + if x != nil { + return x.From + } + return "" +} + +func (x *Transaction_ERC721Transfer) GetTo() string { + if x != nil { + return x.To + } + return "" +} + +func (x *Transaction_ERC721Transfer) GetTokenId() []byte { + if x != nil { + return x.TokenId + } + return nil +} + +// ERC1155 NFT transfer transaction +type Transaction_ERC1155Transfer struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + From string `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` + To string `protobuf:"bytes,2,opt,name=to,proto3" json:"to,omitempty"` + // ID of the token (256-bit number) + TokenId []byte `protobuf:"bytes,3,opt,name=token_id,json=tokenId,proto3" json:"token_id,omitempty"` + // The amount of tokens being transferred + Value []byte `protobuf:"bytes,4,opt,name=value,proto3" json:"value,omitempty"` + Data []byte `protobuf:"bytes,5,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *Transaction_ERC1155Transfer) Reset() { + *x = Transaction_ERC1155Transfer{} + if protoimpl.UnsafeEnabled { + mi := &file_Ethereum_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Transaction_ERC1155Transfer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Transaction_ERC1155Transfer) ProtoMessage() {} + +func (x *Transaction_ERC1155Transfer) ProtoReflect() protoreflect.Message { + mi := &file_Ethereum_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Transaction_ERC1155Transfer.ProtoReflect.Descriptor instead. +func (*Transaction_ERC1155Transfer) Descriptor() ([]byte, []int) { + return file_Ethereum_proto_rawDescGZIP(), []int{0, 4} +} + +func (x *Transaction_ERC1155Transfer) GetFrom() string { + if x != nil { + return x.From + } + return "" +} + +func (x *Transaction_ERC1155Transfer) GetTo() string { + if x != nil { + return x.To + } + return "" +} + +func (x *Transaction_ERC1155Transfer) GetTokenId() []byte { + if x != nil { + return x.TokenId + } + return nil +} + +func (x *Transaction_ERC1155Transfer) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +func (x *Transaction_ERC1155Transfer) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +// Generic smart contract transaction +type Transaction_ContractGeneric struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Amount to send in wei (256-bit number) + Amount []byte `protobuf:"bytes,1,opt,name=amount,proto3" json:"amount,omitempty"` + // Contract call payload data + Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *Transaction_ContractGeneric) Reset() { + *x = Transaction_ContractGeneric{} + if protoimpl.UnsafeEnabled { + mi := &file_Ethereum_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Transaction_ContractGeneric) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Transaction_ContractGeneric) ProtoMessage() {} + +func (x *Transaction_ContractGeneric) ProtoReflect() protoreflect.Message { + mi := &file_Ethereum_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Transaction_ContractGeneric.ProtoReflect.Descriptor instead. +func (*Transaction_ContractGeneric) Descriptor() ([]byte, []int) { + return file_Ethereum_proto_rawDescGZIP(), []int{0, 5} +} + +func (x *Transaction_ContractGeneric) GetAmount() []byte { + if x != nil { + return x.Amount + } + return nil +} + +func (x *Transaction_ContractGeneric) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +var File_Ethereum_proto protoreflect.FileDescriptor + +var file_Ethereum_proto_rawDesc = []byte{ + 0x0a, 0x0e, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x12, 0x11, 0x54, 0x57, 0x2e, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x1a, 0x0c, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x22, 0xe7, 0x07, 0x0a, 0x0b, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x45, 0x0a, 0x08, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x54, 0x57, 0x2e, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, + 0x6d, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x48, 0x00, 0x52, 0x08, + 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x12, 0x55, 0x0a, 0x0e, 0x65, 0x72, 0x63, 0x32, + 0x30, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x2c, 0x2e, 0x54, 0x57, 0x2e, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x2e, 0x45, 0x52, 0x43, 0x32, 0x30, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x48, 0x00, + 0x52, 0x0d, 0x65, 0x72, 0x63, 0x32, 0x30, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x12, + 0x52, 0x0a, 0x0d, 0x65, 0x72, 0x63, 0x32, 0x30, 0x5f, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x54, 0x57, 0x2e, 0x45, 0x74, 0x68, 0x65, + 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x45, 0x52, 0x43, 0x32, 0x30, 0x41, 0x70, 0x70, 0x72, + 0x6f, 0x76, 0x65, 0x48, 0x00, 0x52, 0x0c, 0x65, 0x72, 0x63, 0x32, 0x30, 0x41, 0x70, 0x70, 0x72, + 0x6f, 0x76, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x65, 0x72, 0x63, 0x37, 0x32, 0x31, 0x5f, 0x74, 0x72, + 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x54, + 0x57, 0x2e, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x45, 0x52, 0x43, + 0x37, 0x32, 0x31, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x48, 0x00, 0x52, 0x0e, 0x65, + 0x72, 0x63, 0x37, 0x32, 0x31, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x12, 0x5b, 0x0a, + 0x10, 0x65, 0x72, 0x63, 0x31, 0x31, 0x35, 0x35, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, + 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x54, 0x57, 0x2e, 0x45, 0x74, 0x68, + 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x45, 0x52, 0x43, 0x31, 0x31, 0x35, 0x35, 0x54, + 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x48, 0x00, 0x52, 0x0f, 0x65, 0x72, 0x63, 0x31, 0x31, + 0x35, 0x35, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x12, 0x5b, 0x0a, 0x10, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x54, 0x57, 0x2e, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, + 0x75, 0x6d, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x47, 0x65, 0x6e, + 0x65, 0x72, 0x69, 0x63, 0x48, 0x00, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, + 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x1a, 0x36, 0x0a, 0x08, 0x54, 0x72, 0x61, 0x6e, 0x73, + 0x66, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x64, + 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x1a, + 0x37, 0x0a, 0x0d, 0x45, 0x52, 0x43, 0x32, 0x30, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, + 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x74, 0x6f, + 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x1a, 0x40, 0x0a, 0x0c, 0x45, 0x52, 0x43, 0x32, + 0x30, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x70, 0x65, 0x6e, + 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x70, 0x65, 0x6e, 0x64, + 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x1a, 0x4f, 0x0a, 0x0e, 0x45, 0x52, + 0x43, 0x37, 0x32, 0x31, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, + 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, + 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x74, 0x6f, + 0x12, 0x19, 0x0a, 0x08, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x07, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x49, 0x64, 0x1a, 0x7a, 0x0a, 0x0f, 0x45, + 0x52, 0x43, 0x31, 0x31, 0x35, 0x35, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x12, 0x12, + 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x72, + 0x6f, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, + 0x74, 0x6f, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x3d, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x74, 0x72, + 0x61, 0x63, 0x74, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x42, 0x13, 0x0a, 0x11, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x22, 0x99, 0x03, 0x0a, 0x0c, + 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x19, 0x0a, 0x08, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x3b, 0x0a, + 0x07, 0x74, 0x78, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, + 0x2e, 0x54, 0x57, 0x2e, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x6f, + 0x64, 0x65, 0x52, 0x06, 0x74, 0x78, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x61, + 0x73, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x67, + 0x61, 0x73, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x61, 0x73, 0x5f, 0x6c, + 0x69, 0x6d, 0x69, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x67, 0x61, 0x73, 0x4c, + 0x69, 0x6d, 0x69, 0x74, 0x12, 0x38, 0x0a, 0x19, 0x6d, 0x61, 0x78, 0x5f, 0x69, 0x6e, 0x63, 0x6c, + 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x67, 0x61, + 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x15, 0x6d, 0x61, 0x78, 0x49, 0x6e, 0x63, 0x6c, + 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x46, 0x65, 0x65, 0x50, 0x65, 0x72, 0x47, 0x61, 0x73, 0x12, 0x25, + 0x0a, 0x0f, 0x6d, 0x61, 0x78, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x67, 0x61, + 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x6d, 0x61, 0x78, 0x46, 0x65, 0x65, 0x50, + 0x65, 0x72, 0x47, 0x61, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x5f, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, + 0x6b, 0x65, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x76, 0x61, + 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x40, 0x0a, 0x0b, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x54, 0x57, 0x2e, + 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, + 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x74, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xc1, 0x01, 0x0a, 0x0d, 0x53, 0x69, 0x67, 0x6e, + 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x63, + 0x6f, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x65, 0x6e, 0x63, 0x6f, + 0x64, 0x65, 0x64, 0x12, 0x0c, 0x0a, 0x01, 0x76, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x01, + 0x76, 0x12, 0x0c, 0x0a, 0x01, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x01, 0x72, 0x12, + 0x0c, 0x0a, 0x01, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x01, 0x73, 0x12, 0x12, 0x0a, + 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, + 0x61, 0x12, 0x33, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x1d, 0x2e, 0x54, 0x57, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, + 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, + 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2a, 0x2c, 0x0a, 0x0f, 0x54, + 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x0a, + 0x0a, 0x06, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x45, 0x6e, + 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x64, 0x10, 0x01, 0x42, 0x57, 0x0a, 0x15, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x6a, 0x6e, 0x69, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, + 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2d, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, 0x63, 0x68, 0x61, + 0x69, 0x6e, 0x2d, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, + 0x75, 0x6d, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_Ethereum_proto_rawDescOnce sync.Once + file_Ethereum_proto_rawDescData = file_Ethereum_proto_rawDesc +) + +func file_Ethereum_proto_rawDescGZIP() []byte { + file_Ethereum_proto_rawDescOnce.Do(func() { + file_Ethereum_proto_rawDescData = protoimpl.X.CompressGZIP(file_Ethereum_proto_rawDescData) + }) + return file_Ethereum_proto_rawDescData +} + +var file_Ethereum_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_Ethereum_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_Ethereum_proto_goTypes = []interface{}{ + (TransactionMode)(0), // 0: TW.Ethereum.Proto.TransactionMode + (*Transaction)(nil), // 1: TW.Ethereum.Proto.Transaction + (*SigningInput)(nil), // 2: TW.Ethereum.Proto.SigningInput + (*SigningOutput)(nil), // 3: TW.Ethereum.Proto.SigningOutput + (*Transaction_Transfer)(nil), // 4: TW.Ethereum.Proto.Transaction.Transfer + (*Transaction_ERC20Transfer)(nil), // 5: TW.Ethereum.Proto.Transaction.ERC20Transfer + (*Transaction_ERC20Approve)(nil), // 6: TW.Ethereum.Proto.Transaction.ERC20Approve + (*Transaction_ERC721Transfer)(nil), // 7: TW.Ethereum.Proto.Transaction.ERC721Transfer + (*Transaction_ERC1155Transfer)(nil), // 8: TW.Ethereum.Proto.Transaction.ERC1155Transfer + (*Transaction_ContractGeneric)(nil), // 9: TW.Ethereum.Proto.Transaction.ContractGeneric + (common.SigningError)(0), // 10: TW.Common.Proto.SigningError +} +var file_Ethereum_proto_depIdxs = []int32{ + 4, // 0: TW.Ethereum.Proto.Transaction.transfer:type_name -> TW.Ethereum.Proto.Transaction.Transfer + 5, // 1: TW.Ethereum.Proto.Transaction.erc20_transfer:type_name -> TW.Ethereum.Proto.Transaction.ERC20Transfer + 6, // 2: TW.Ethereum.Proto.Transaction.erc20_approve:type_name -> TW.Ethereum.Proto.Transaction.ERC20Approve + 7, // 3: TW.Ethereum.Proto.Transaction.erc721_transfer:type_name -> TW.Ethereum.Proto.Transaction.ERC721Transfer + 8, // 4: TW.Ethereum.Proto.Transaction.erc1155_transfer:type_name -> TW.Ethereum.Proto.Transaction.ERC1155Transfer + 9, // 5: TW.Ethereum.Proto.Transaction.contract_generic:type_name -> TW.Ethereum.Proto.Transaction.ContractGeneric + 0, // 6: TW.Ethereum.Proto.SigningInput.tx_mode:type_name -> TW.Ethereum.Proto.TransactionMode + 1, // 7: TW.Ethereum.Proto.SigningInput.transaction:type_name -> TW.Ethereum.Proto.Transaction + 10, // 8: TW.Ethereum.Proto.SigningOutput.error:type_name -> TW.Common.Proto.SigningError + 9, // [9:9] is the sub-list for method output_type + 9, // [9:9] is the sub-list for method input_type + 9, // [9:9] is the sub-list for extension type_name + 9, // [9:9] is the sub-list for extension extendee + 0, // [0:9] is the sub-list for field type_name +} + +func init() { file_Ethereum_proto_init() } +func file_Ethereum_proto_init() { + if File_Ethereum_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_Ethereum_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Transaction); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Ethereum_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SigningInput); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Ethereum_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SigningOutput); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Ethereum_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Transaction_Transfer); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Ethereum_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Transaction_ERC20Transfer); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Ethereum_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Transaction_ERC20Approve); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Ethereum_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Transaction_ERC721Transfer); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Ethereum_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Transaction_ERC1155Transfer); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_Ethereum_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Transaction_ContractGeneric); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_Ethereum_proto_msgTypes[0].OneofWrappers = []interface{}{ + (*Transaction_Transfer_)(nil), + (*Transaction_Erc20Transfer)(nil), + (*Transaction_Erc20Approve)(nil), + (*Transaction_Erc721Transfer)(nil), + (*Transaction_Erc1155Transfer)(nil), + (*Transaction_ContractGeneric_)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_Ethereum_proto_rawDesc, + NumEnums: 1, + NumMessages: 9, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_Ethereum_proto_goTypes, + DependencyIndexes: file_Ethereum_proto_depIdxs, + EnumInfos: file_Ethereum_proto_enumTypes, + MessageInfos: file_Ethereum_proto_msgTypes, + }.Build() + File_Ethereum_proto = out.File + file_Ethereum_proto_rawDesc = nil + file_Ethereum_proto_goTypes = nil + file_Ethereum_proto_depIdxs = nil +} diff --git a/samples/go/sample/external_signing.go b/samples/go/sample/external_signing.go new file mode 100644 index 00000000000..636f56dbc0d --- /dev/null +++ b/samples/go/sample/external_signing.go @@ -0,0 +1,240 @@ +package sample + +import ( + "encoding/hex" + "fmt" + "math/big" + "tw/core" + "tw/protos/binance" + "tw/protos/bitcoin" + "tw/protos/common" + "tw/protos/ethereum" + + "google.golang.org/protobuf/proto" +) + +func ExternalSigningDemo() { + fmt.Println("") + SignExternalBinanceDemo() + SignExternalEthereumDemo() + SignExternalBitcoinDemo() +} + +func SignExternalBinanceDemo() { + fmt.Println("==> Signing with External Signature - Binance Demo") + + coin := core.CoinTypeBinance + + fmt.Println("\n==> Step 1: Prepare transaction input (protobuf)") + txInputData := core.BuildInput( + coin, + "bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", // from + "bnb1hlly02l6ahjsgxw9wlcswnlwdhg4xhx38yxpd5", // to + "1", // amount + "BNB", // asset + "", // memo + "", // chainId + ) + fmt.Println("txInputData len: ", len(txInputData)) + + fmt.Println("\n==> Step 2: Obtain preimage hash") + hashes := core.PreImageHashes(coin, txInputData) + fmt.Println("hash(es): ", len(hashes), hex.EncodeToString(hashes)) + + var preSigningOutput common.PreSigningOutput + proto.Unmarshal(hashes, &preSigningOutput) + + fmt.Println("\n==> Step 3: Compile transaction info") + // Simulate signature, normally obtained from signature server + signature, _ := hex.DecodeString("1b1181faec30b60a2ddaa2804c253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5366bb8b041781a6771248550d04094c3d7a504f9e8310679") + publicKey, _ := hex.DecodeString("026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e502") + txOutput := core.CompileWithSignatures(coin, txInputData, [][]byte{signature}, [][]byte{publicKey}) + + var output binance.SigningOutput + proto.Unmarshal(txOutput, &output) + fmt.Println("final txOutput proto: ", len(txOutput)) + fmt.Println("output.encoded: ", len(output.Encoded), hex.EncodeToString(output.Encoded)) + + fmt.Println("\n==> Double check signature validity (result should be true)") + verifyRes := core.PublicKeyVerify(publicKey, core.PublicKeyTypeSECP256k1, signature, preSigningOutput.DataHash) + fmt.Println(verifyRes) + + fmt.Println("") +} + +func SignExternalEthereumDemo() { + fmt.Println("==> Signing with External Signature - Ethereum Demo") + + coin := core.CoinTypeEthereum + + fmt.Println("\n==> Step 1: Prepare transaction input (protobuf)") + txInputData := core.BuildInput( + coin, + "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F", // from + "0x3535353535353535353535353535353535353535", // to + "1000000000000000000", // amount + "ETH", // asset + "", // memo + "", // chainId + ) + fmt.Println("txInputData len: ", len(txInputData)) + + // Set a few other values + var input ethereum.SigningInput + proto.Unmarshal(txInputData, &input) + input.Nonce = big.NewInt(11).Bytes() + input.GasPrice = big.NewInt(20000000000).Bytes() + input.GasLimit = big.NewInt(21000).Bytes() + input.TxMode = ethereum.TransactionMode_Legacy + txInputData2, _ := proto.Marshal(&input) + fmt.Println("txInputData len: ", len(txInputData2)) + + fmt.Println("\n==> Step 2: Obtain preimage hash") + hashes := core.PreImageHashes(coin, txInputData2) + fmt.Println("hash(es): ", len(hashes), hex.EncodeToString(hashes)) + + var preSigningOutput common.PreSigningOutput + proto.Unmarshal(hashes, &preSigningOutput) + + fmt.Println("\n==> Step 3: Compile transaction info") + // Simulate signature, normally obtained from signature server + signature, _ := hex.DecodeString("360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07b53bd9d1376e15a191d844db458893b928f3efbfee90c9febf51ab84c9796677900") + publicKey, _ := hex.DecodeString("044bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382ce28cab79ad7119ee1ad3ebcdb98a16805211530ecc6cfefa1b88e6dff99232a") + txOutput := core.CompileWithSignatures(coin, txInputData2, [][]byte{signature}, [][]byte{publicKey}) + + fmt.Println("final txOutput proto: ", len(txOutput)) + var output ethereum.SigningOutput + _ = proto.Unmarshal(txOutput, &output) + fmt.Println("output.encoded: ", len(output.Encoded), hex.EncodeToString(output.Encoded)) + + fmt.Println("\n==> Double check signature validity (result should be true)") + verifyRes := core.PublicKeyVerify(publicKey, core.PublicKeyTypeSECP256k1Extended, signature, preSigningOutput.DataHash) + fmt.Println(verifyRes) + + fmt.Println("") +} + +func SignExternalBitcoinDemo() { + fmt.Println("==> Signing with External Signature - Bitcoin Demo") + + fmt.Println("\n==> Step 1: Prepare transaction input (protobuf)") + + revUtxoHash0, _ := hex.DecodeString("07c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa8") + revUtxoHash1, _ := hex.DecodeString("d6892a5aa54e3b8fe430efd23f49a8950733aaa9d7c915d9989179f48dd1905e") + revUtxoHash2, _ := hex.DecodeString("6021efcf7555f90627364339fc921139dd40a06ccb2cb2a2a4f8f4ea7a2dc74d") + inPubKey0, _ := hex.DecodeString("024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382") + inPubKey1, _ := hex.DecodeString("0217142f69535e4dad0dc7060df645c55a174cc1bfa5b9eb2e59aad2ae96072dfc") + inPubKeyHash0, _ := hex.DecodeString("bd92088bb7e82d611a9b94fbb74a0908152b784f") + inPubKeyHash1, _ := hex.DecodeString("6641abedacf9483b793afe1718689cc9420bbb1c") + ownAddress := "bc1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z00ppggv" + + // Input UTXO infos + type UtxoInfo struct { + revUtxoHash []byte + publicKey []byte + address string + amount int + index int + } + + utxoInfos := [...]UtxoInfo{ + // first + {revUtxoHash0, inPubKey0, ownAddress, 600000, 0}, + // second UTXO, with same pubkey + {revUtxoHash1, inPubKey0, ownAddress, 500000, 1}, + // third UTXO, with different pubkey + {revUtxoHash2, inPubKey1, "bc1qveq6hmdvl9yrk7f6lct3s6yue9pqhwcuxedggg", 400000, 0}, + } + + // Signature infos, indexed by pubkeyhash+hash + type SignatureInfo struct { + signature []byte + publicKey []byte + } + inSig0, _ := hex.DecodeString("304402201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b34902200a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f2a40") + inSig1, _ := hex.DecodeString("3044022041294880caa09bb1b653775310fcdd1458da6b8e7d7fae34e37966414fe115820220646397c9d2513edc5974ecc336e9b287de0cdf071c366f3b3dc3ff309213e4e4") + inSig2, _ := hex.DecodeString("30440220764e3d5b3971c4b3e70b23fb700a7462a6fe519d9830e863a1f8388c402ad0b102207e777f7972c636961f92375a2774af3b7a2a04190251bbcb31d19c70927952dc") + signatureInfos := map[string]SignatureInfo{ + hex.EncodeToString(inPubKeyHash0) + "+" + "a296bead4172007be69b21971a790e076388666c162a9505698415f1b003ebd7": {inSig0, inPubKey0}, + hex.EncodeToString(inPubKeyHash1) + "+" + "505f527f00e15fcc5a2d2416c9970beb57dfdfaca99e572a01f143b24dd8fab6": {inSig1, inPubKey1}, + hex.EncodeToString(inPubKeyHash0) + "+" + "60ed6e9371e5ddc72fd88e46a12cb2f68516ebd307c0fd31b1b55cf767272101": {inSig2, inPubKey0}, + } + + coin := core.CoinTypeBitcoin + + // Setup input for Plan + input := bitcoin.SigningInput{ + HashType: uint32(core.BitcoinSigHashTypeAll), + Amount: 1200000, + ByteFee: 1, + ToAddress: "bc1q2dsdlq3343vk29runkgv4yc292hmq53jedfjmp", + ChangeAddress: ownAddress, + Utxo: []*bitcoin.UnspentTransaction{}, + CoinType: uint32(coin), + Scripts: map[string][]byte{}, + } + + // process UTXOs + for i := 0; i < len(utxoInfos); i++ { + address := utxoInfos[i].address + fmt.Println("utxo", i, ": ", address, utxoInfos[i].amount) + + lockScript := core.BitcoinScriptLockScriptForAddress(address, coin) + fmt.Println(" lockScript: ", hex.EncodeToString(lockScript)) + keyHash := core.BitcoinScriptMatchPayToWitnessPublicKeyHash(lockScript) + fmt.Println(" keyHash: ", hex.EncodeToString(keyHash)) + redeemScript := core.BitcoinScriptBuildPayToPublicKeyHash(keyHash) + fmt.Println(" redeemScript: ", hex.EncodeToString(redeemScript)) + input.Scripts[hex.EncodeToString(keyHash)] = redeemScript + + utxo := bitcoin.UnspentTransaction{ + OutPoint: &bitcoin.OutPoint{ + Hash: utxoInfos[i].revUtxoHash, + Index: uint32(utxoInfos[i].index), + Sequence: 4294967295, + }, + Amount: int64(utxoInfos[i].amount), + Script: lockScript, + } + input.Utxo = append(input.Utxo, &utxo) + } + + txInputData, _ := proto.Marshal(&input) + fmt.Println("txInputData len: ", len(txInputData)) + + fmt.Println("\n==> Step 2: Obtain preimage hashes") + hashes := core.PreImageHashes(coin, txInputData) + + var preSigningOutput bitcoin.PreSigningOutput + proto.Unmarshal(hashes, &preSigningOutput) + fmt.Println("hashes+pubkeyhashes: ", len(preSigningOutput.HashPublicKeys)) + for _, h := range preSigningOutput.HashPublicKeys { + fmt.Println(" ", hex.EncodeToString(h.DataHash), hex.EncodeToString(h.PublicKeyHash)) + } + + fmt.Println("\n==> Step 3: Compile transaction info") + // Simulate signature, normally obtained from signature server + signatureVec := [][]byte{} + pubkeyVec := [][]byte{} + for _, h := range preSigningOutput.HashPublicKeys { + preImageHash := h.DataHash + pubkeyHash := h.PublicKeyHash + key := hex.EncodeToString(pubkeyHash) + "+" + hex.EncodeToString(preImageHash) + sigInfo := signatureInfos[key] + + signatureVec = append(signatureVec, sigInfo.signature) + pubkeyVec = append(pubkeyVec, sigInfo.publicKey) + + // Verify signature (pubkey & hash & signature) + verifyRes := core.PublicKeyVerifyAsDER(sigInfo.publicKey, core.PublicKeyTypeSECP256k1, sigInfo.signature, preImageHash) + fmt.Println("signature verification:", verifyRes) + } + txOutput := core.CompileWithSignatures(coin, txInputData, signatureVec, pubkeyVec) + + fmt.Println("final txOutput proto: ", len(txOutput)) + var output bitcoin.SigningOutput + _ = proto.Unmarshal(txOutput, &output) + fmt.Println("output.encoded: ", len(output.Encoded), hex.EncodeToString(output.Encoded)) + + fmt.Println("") +} diff --git a/samples/go/types/twdata.go b/samples/go/types/twdata.go index f1195643570..dcb7bdc95f8 100644 --- a/samples/go/types/twdata.go +++ b/samples/go/types/twdata.go @@ -1,13 +1,13 @@ package types // #cgo CFLAGS: -I../../../include -// #cgo LDFLAGS: -L../../../build -L../../../build/trezor-crypto -lTrustWalletCore -lprotobuf -lTrezorCrypto -lc++ -lm +// #cgo LDFLAGS: -L../../../build -L../../../build/trezor-crypto -lTrustWalletCore -lprotobuf -lTrezorCrypto -lstdc++ -lm // #include import "C" import ( - "unsafe" "encoding/hex" + "unsafe" ) // C.TWData -> Go byte[] diff --git a/samples/go/types/twstring.go b/samples/go/types/twstring.go index de9fbb5eb2e..3c5b2668c64 100644 --- a/samples/go/types/twstring.go +++ b/samples/go/types/twstring.go @@ -1,7 +1,7 @@ package types // #cgo CFLAGS: -I../../../include -// #cgo LDFLAGS: -L../../../build -L../../../build/trezor-crypto -lTrustWalletCore -lprotobuf -lTrezorCrypto -lc++ -lm +// #cgo LDFLAGS: -L../../../build -L../../../build/trezor-crypto -lTrustWalletCore -lprotobuf -lTrezorCrypto -lstdc++ -lm // #include import "C" diff --git a/samples/node/index.ts b/samples/node/index.ts new file mode 100644 index 00000000000..450a4bdaaa3 --- /dev/null +++ b/samples/node/index.ts @@ -0,0 +1,39 @@ +#!/usr/bin/env ts-node + +const { initWasm, TW, KeyStore } = require("@trustwallet/wallet-core"); + +async function main() { + const start = new Date().getTime(); + console.log(`Initializing Wasm...`); + const core = await initWasm(); + const { CoinType, HexCoding, HDWallet, AnyAddress } = core; + console.log(`Done in ${new Date().getTime() - start} ms`); + + const wallet = HDWallet.create(256, ""); + const mnemonic = wallet.mnemonic(); + const key = wallet.getKeyForCoin(CoinType.ethereum); + const pubKey = key.getPublicKeySecp256k1(false); + const address = AnyAddress.createWithPublicKey(pubKey, CoinType.ethereum); + const storage = new KeyStore.FileSystemStorage("/tmp"); + const keystore = new KeyStore.Default(core, storage); + + const storedWallet = await keystore.import(mnemonic, "Coolw", "password", [ + CoinType.ethereum, + ]); + + console.log(`Create wallet: ${mnemonic}`); + console.log(`Get Ethereum public key: ${HexCoding.encode(pubKey.data())}`); + console.log(`Get Ethereum address: ${address.description()}`); + console.log(`CoinType.ethereum.value = ${CoinType.ethereum.value}`); + console.log("Ethereum protobuf models: \n", TW.Ethereum); + console.log("Keystore JSON: \n", JSON.stringify(storedWallet, null, 2)); + + await keystore.delete(storedWallet.id, "password"); + + wallet.delete(); + key.delete(); + pubKey.delete(); + address.delete(); +} + +main(); diff --git a/samples/node/package-lock.json b/samples/node/package-lock.json new file mode 100644 index 00000000000..e0a6b4d8d20 --- /dev/null +++ b/samples/node/package-lock.json @@ -0,0 +1,546 @@ +{ + "name": "node-sample", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "node-sample", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@trustwallet/wallet-core": "3.0.4" + }, + "devDependencies": { + "@types/node": "^10.9.1", + "ts-node": "^10.9.1", + "typescript": "^4.8.3" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "node_modules/@trustwallet/wallet-core": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@trustwallet/wallet-core/-/wallet-core-3.0.4.tgz", + "integrity": "sha512-FrIVEwRmUYFuwU9IoXg0J8fngeW7nlJZZAsrnOWba2g/yGWZl4FQ4z87MEQ5ROOGW9jJzsdWG4PRZffvYzhqsg==", + "dependencies": { + "protobufjs": ">=6.11.3" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, + "node_modules/@types/node": { + "version": "10.17.60", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", + "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/protobufjs/node_modules/@types/node": { + "version": "18.7.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz", + "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==" + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", + "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + } + }, + "dependencies": { + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "@trustwallet/wallet-core": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@trustwallet/wallet-core/-/wallet-core-3.0.4.tgz", + "integrity": "sha512-FrIVEwRmUYFuwU9IoXg0J8fngeW7nlJZZAsrnOWba2g/yGWZl4FQ4z87MEQ5ROOGW9jJzsdWG4PRZffvYzhqsg==", + "requires": { + "protobufjs": ">=6.11.3" + } + }, + "@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, + "@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, + "@types/node": { + "version": "10.17.60", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", + "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", + "dev": true + }, + "acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "dependencies": { + "@types/node": { + "version": "18.7.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz", + "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==" + } + } + }, + "ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + } + }, + "typescript": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", + "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==", + "dev": true + }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + } + } +} diff --git a/samples/node/package.json b/samples/node/package.json new file mode 100644 index 00000000000..c194836a001 --- /dev/null +++ b/samples/node/package.json @@ -0,0 +1,19 @@ +{ + "name": "node-sample", + "version": "1.0.0", + "description": "", + "main": "index.ts", + "scripts": { + "start": "ts-node index.ts" + }, + "author": "", + "license": "MIT", + "dependencies": { + "@trustwallet/wallet-core": "3.0.4" + }, + "devDependencies": { + "@types/node": "^10.9.1", + "ts-node": "^10.9.1", + "typescript": "^4.8.3" + } +} diff --git a/samples/node/tsconfig.json b/samples/node/tsconfig.json new file mode 100644 index 00000000000..d1395c05628 --- /dev/null +++ b/samples/node/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "es2017", + "lib": [ + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/samples/osx/cocoapods/Podfile b/samples/osx/cocoapods/Podfile index b3a50e2550b..d071a6b7c05 100644 --- a/samples/osx/cocoapods/Podfile +++ b/samples/osx/cocoapods/Podfile @@ -3,5 +3,6 @@ platform :osx, '10.14' target 'WalletCoreExample' do use_frameworks! pod 'TrustWalletCore' + pod 'BigInt' end diff --git a/samples/osx/cocoapods/Podfile.lock b/samples/osx/cocoapods/Podfile.lock index 796e8f34d88..bfe2d08725c 100644 --- a/samples/osx/cocoapods/Podfile.lock +++ b/samples/osx/cocoapods/Podfile.lock @@ -1,24 +1,28 @@ PODS: - - SwiftProtobuf (1.16.0) - - TrustWalletCore (2.6.7): - - TrustWalletCore/Core (= 2.6.7) - - TrustWalletCore/Core (2.6.7): + - BigInt (5.2.0) + - SwiftProtobuf (1.19.0) + - TrustWalletCore (2.9.5): + - TrustWalletCore/Core (= 2.9.5) + - TrustWalletCore/Core (2.9.5): - TrustWalletCore/Types - - TrustWalletCore/Types (2.6.7): + - TrustWalletCore/Types (2.9.5): - SwiftProtobuf DEPENDENCIES: + - BigInt - TrustWalletCore SPEC REPOS: trunk: + - BigInt - SwiftProtobuf - TrustWalletCore SPEC CHECKSUMS: - SwiftProtobuf: 4e16842b83c6fda06b10fac50d73b3f1fce8ab7b - TrustWalletCore: 1409475008901d761effd92fe6c3d6be6c83ae3a + BigInt: f668a80089607f521586bbe29513d708491ef2f7 + SwiftProtobuf: 6ef3f0e422ef90d6605ca20b21a94f6c1324d6b3 + TrustWalletCore: 52ec9b10c17c8d7443e848ff0e6adac4d39be9f0 -PODFILE CHECKSUM: 68848e868fc9571d319a35d139d7901079a7fe36 +PODFILE CHECKSUM: 5fb79210604aff833a8320c937ea3849c2d9ac06 -COCOAPODS: 1.10.1 +COCOAPODS: 1.11.3 diff --git a/samples/osx/cocoapods/WalletCoreExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/samples/osx/cocoapods/WalletCoreExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000000..18d981003d6 --- /dev/null +++ b/samples/osx/cocoapods/WalletCoreExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/samples/osx/cocoapods/WalletCoreExample/ViewController.swift b/samples/osx/cocoapods/WalletCoreExample/ViewController.swift index 22c7eb87acc..bb130d940b6 100644 --- a/samples/osx/cocoapods/WalletCoreExample/ViewController.swift +++ b/samples/osx/cocoapods/WalletCoreExample/ViewController.swift @@ -6,6 +6,7 @@ import Cocoa import WalletCore +import BigInt class ViewController: NSViewController { @@ -27,13 +28,13 @@ class ViewController: NSViewController { let dummyReceiverAddress = "0xC37054b3b48C3317082E7ba872d7753D13da4986" let signerInput = EthereumSigningInput.with { $0.chainID = Data(hexString: "01")! - $0.gasPrice = Data(hexString: "d693a400")! // decimal 3600000000 - $0.gasLimit = Data(hexString: "5208")! // decimal 21000 + $0.gasPrice = BigInt(3600000000).magnitude.serialize() + $0.gasLimit = BigInt(21000).magnitude.serialize() $0.toAddress = dummyReceiverAddress $0.transaction = EthereumTransaction.with { $0.transfer = EthereumTransaction.Transfer.with { - $0.amount = Data(hexString: "0348bca5a16000")! + $0.amount = BigInt(0.0009244*1000000000000000000).magnitude.serialize() } } $0.privateKey = secretPrivateKeyEth.data diff --git a/samples/typescript/devconsole.ts/.gitignore b/samples/typescript/devconsole.ts/.gitignore new file mode 100644 index 00000000000..83dbaee65a8 --- /dev/null +++ b/samples/typescript/devconsole.ts/.gitignore @@ -0,0 +1,3 @@ +node_modules +lib + diff --git a/samples/typescript/devconsole.ts/README.md b/samples/typescript/devconsole.ts/README.md new file mode 100644 index 00000000000..40ed8e5045d --- /dev/null +++ b/samples/typescript/devconsole.ts/README.md @@ -0,0 +1,34 @@ +# Devconsole.ts + +`Devconsole.ts` is a command-line utility to quickly work with `wallet-core` library. +It is based on `node` `typescript` console, and uses the WASM version of `wallet-core`. + +Type `help()` after starting it. + +Some wallet-core namespaces are exposed, and methods can be called directly. + +Note that auto-completion works in the console. + +## Build: + +``` +cd samples/typescript/devconsole.ts +npm install +npm run build +npm run start +``` + +## Sample usage: + +``` +> help() +This is an interactive typescript shell, to work with wallet-core (wasm) +You can use: +... +> w1 = HDWallet.create(256, '') +W {} +> w1.mnemonic() +'round profit immense ... sniff' +> w1.getAddressForCoin(CoinType.cosmos) +'cosmos1kgd25tvkz3jtuee4cl796trc2s0hjsfgqmhmku' +``` diff --git a/samples/typescript/devconsole.ts/package-lock.json b/samples/typescript/devconsole.ts/package-lock.json new file mode 100644 index 00000000000..50046ba9ab4 --- /dev/null +++ b/samples/typescript/devconsole.ts/package-lock.json @@ -0,0 +1,1491 @@ +{ + "name": "cli-wasm", + "version": "0.0.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "cli-wasm", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "@trustwallet/wallet-core": "3.0.4", + "chalk": "^4.1.2", + "clear": "^0.1.0", + "figlet": "^1.5.2", + "inquirer": "^8.2.4" + }, + "bin": { + "cli": "index.ts" + }, + "devDependencies": { + "@types/node": "^18.7.15", + "ts-node": "^10.9.1", + "typescript": "^4.8.2" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "node_modules/@trustwallet/wallet-core": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@trustwallet/wallet-core/-/wallet-core-3.0.4.tgz", + "integrity": "sha512-FrIVEwRmUYFuwU9IoXg0J8fngeW7nlJZZAsrnOWba2g/yGWZl4FQ4z87MEQ5ROOGW9jJzsdWG4PRZffvYzhqsg==", + "dependencies": { + "protobufjs": ">=6.11.3" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.7.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.15.tgz", + "integrity": "sha512-XnjpaI8Bgc3eBag2Aw4t2Uj/49lLBSStHWfqKvIuXD7FIrZyMLWp8KuAFHAqxMZYTF9l08N1ctUn9YNybZJVmQ==" + }, + "node_modules/acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + }, + "node_modules/clear": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/clear/-/clear-0.1.0.tgz", + "integrity": "sha512-qMjRnoL+JDPJHeLePZJuao6+8orzHMGP04A8CdwCNsKhRbOnKRjefxONR7bwILT3MHecxKBjHkKL/tkZ8r4Uzw==", + "engines": { + "node": "*" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", + "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==", + "dependencies": { + "clone": "^1.0.2" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/figlet": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.5.2.tgz", + "integrity": "sha512-WOn21V8AhyE1QqVfPIVxe3tupJacq1xGkPTB4iagT6o+P2cAgEOOwIxMftr4+ZCTI6d551ij9j61DFr0nsP2uQ==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + }, + "node_modules/inquirer": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz", + "integrity": "sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==", + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/long": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.0.tgz", + "integrity": "sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w==" + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/protobufjs": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.0.tgz", + "integrity": "sha512-rCuxKlh0UQKSMjrpIcTLbR5TtGQ52cgs1a5nUoPBAKOccdPblN67BJtjrbtudUJK6HmBvUdsmymyYOzO7lxZEA==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/rxjs": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.6.tgz", + "integrity": "sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.2.tgz", + "integrity": "sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + } + }, + "dependencies": { + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "@trustwallet/wallet-core": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@trustwallet/wallet-core/-/wallet-core-3.0.4.tgz", + "integrity": "sha512-FrIVEwRmUYFuwU9IoXg0J8fngeW7nlJZZAsrnOWba2g/yGWZl4FQ4z87MEQ5ROOGW9jJzsdWG4PRZffvYzhqsg==", + "requires": { + "protobufjs": ">=6.11.3" + } + }, + "@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, + "@types/node": { + "version": "18.7.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.15.tgz", + "integrity": "sha512-XnjpaI8Bgc3eBag2Aw4t2Uj/49lLBSStHWfqKvIuXD7FIrZyMLWp8KuAFHAqxMZYTF9l08N1ctUn9YNybZJVmQ==" + }, + "acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + } + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + }, + "clear": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/clear/-/clear-0.1.0.tgz", + "integrity": "sha512-qMjRnoL+JDPJHeLePZJuao6+8orzHMGP04A8CdwCNsKhRbOnKRjefxONR7bwILT3MHecxKBjHkKL/tkZ8r4Uzw==" + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-spinners": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", + "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==" + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==" + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==" + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==", + "requires": { + "clone": "^1.0.2" + } + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "figlet": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.5.2.tgz", + "integrity": "sha512-WOn21V8AhyE1QqVfPIVxe3tupJacq1xGkPTB4iagT6o+P2cAgEOOwIxMftr4+ZCTI6d551ij9j61DFr0nsP2uQ==" + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + }, + "inquirer": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz", + "integrity": "sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==", + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^7.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==" + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==" + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, + "long": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.0.tgz", + "integrity": "sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w==" + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "requires": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==" + }, + "protobufjs": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.0.tgz", + "integrity": "sha512-rCuxKlh0UQKSMjrpIcTLbR5TtGQ52cgs1a5nUoPBAKOccdPblN67BJtjrbtudUJK6HmBvUdsmymyYOzO7lxZEA==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" + }, + "rxjs": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.6.tgz", + "integrity": "sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==", + "requires": { + "tslib": "^2.1.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + } + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==" + }, + "typescript": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.2.tgz", + "integrity": "sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "requires": { + "defaults": "^1.0.3" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + } + } +} diff --git a/samples/typescript/devconsole.ts/package.json b/samples/typescript/devconsole.ts/package.json new file mode 100644 index 00000000000..7ca581061d5 --- /dev/null +++ b/samples/typescript/devconsole.ts/package.json @@ -0,0 +1,28 @@ +{ + "name": "cli-wasm", + "version": "0.0.1", + "description": "CLI utility for Wallet Core, through WASM", + "main": "index.ts", + "bin": { + "cli": "index.ts" + }, + "scripts": { + "start": "ts-node src/index.ts", + "build": "tsc -p .", + "test": "cat test/data/privpubkey.testinput.txt | ts-node src/index.ts" + }, + "author": "Trust Wallet", + "license": "MIT", + "dependencies": { + "@trustwallet/wallet-core": "3.0.4", + "chalk": "^4.1.2", + "clear": "^0.1.0", + "figlet": "^1.5.2", + "inquirer": "^8.2.4" + }, + "devDependencies": { + "@types/node": "^18.7.15", + "ts-node": "^10.9.1", + "typescript": "^4.8.2" + } +} diff --git a/samples/typescript/devconsole.ts/samplescripts/privkeysign.sampleinput.txt b/samples/typescript/devconsole.ts/samplescripts/privkeysign.sampleinput.txt new file mode 100644 index 00000000000..05a6b7a776f --- /dev/null +++ b/samples/typescript/devconsole.ts/samplescripts/privkeysign.sampleinput.txt @@ -0,0 +1,12 @@ +// Create a private key, sign a message, derive public key from private key, verify signature + +privKeyData = '0xafeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5' +messageDigest = '0001020304050607080910111213141519171819202122232425262728293031' + +privkey = PrivateKey.createWithData(HexCoding.decode(privKeyData)); HexCoding.encode(privkey.data()) + +sig1 = privkey.sign(HexCoding.decode(messageDigest), Curve.secp256k1); HexCoding.encode(sig1) + +pubkey = privkey.getPublicKeySecp256k1(true); HexCoding.encode(pubkey.data()) + +verifyRes = pubkey.verify(sig1, HexCoding.decode(messageDigest)) diff --git a/samples/typescript/devconsole.ts/samplescripts/protoeth.sampleinput.txt b/samples/typescript/devconsole.ts/samplescripts/protoeth.sampleinput.txt new file mode 100644 index 00000000000..422d16881d4 --- /dev/null +++ b/samples/typescript/devconsole.ts/samplescripts/protoeth.sampleinput.txt @@ -0,0 +1,26 @@ +// test signing eip1559 erc20 transfer tx + +input = TW.Ethereum.Proto.SigningInput.create({ + toAddress: "0x6b175474e89094c44da98b954eedeac495271d0f", + chainId: Buffer.from("01", "hex"), + nonce: Buffer.from("00", "hex"), + txMode: TW.Ethereum.Proto.TransactionMode.Enveloped, + maxInclusionFeePerGas: Buffer.from("0077359400", "hex"), + maxFeePerGas: Buffer.from("00b2d05e00", "hex"), + gasLimit: Buffer.from("0130B9", "hex"), + transaction: TW.Ethereum.Proto.Transaction.create({ + erc20Transfer: TW.Ethereum.Proto.Transaction.ERC20Transfer.create({ + to: "0x5322b34c88ed0691971bf52a7047448f0f4efc84", + amount: Buffer.from("1bc16d674ec80000", "hex"), + }), + }), + privateKey: HexCoding.decode( + "0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151" + ), + }); + +encoded = TW.Ethereum.Proto.SigningInput.encode(input).finish(); HexCoding.encode(encoded) + +outputData = AnySigner.sign(encoded, CoinType.ethereum); HexCoding.encode(outputData) + +output = TW.Ethereum.Proto.SigningOutput.decode(outputData); HexCoding.encode(output.encoded) diff --git a/samples/typescript/devconsole.ts/samplescripts/solanaaddress.sampleinput.txt b/samples/typescript/devconsole.ts/samplescripts/solanaaddress.sampleinput.txt new file mode 100644 index 00000000000..c2796a598b8 --- /dev/null +++ b/samples/typescript/devconsole.ts/samplescripts/solanaaddress.sampleinput.txt @@ -0,0 +1,13 @@ +// Derive a Solana address directly and through privKey-pubKey + +coin = CoinType.solana + +wallet = HDWallet.createWithMnemonic('ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal', '') + +address = wallet.getAddressForCoin(coin) + +pk = wallet.getKeyForCoin(coin); HexCoding.encode(pk.data()) +pubkey = pk.getPublicKeyEd25519(); HexCoding.encode(pubkey.data()) +a1 = AnyAddress.createWithPublicKey(pubkey, coin); a1.description() + +address === a1.description() diff --git a/samples/typescript/devconsole.ts/samplescripts/wallets.sampleinput.txt b/samples/typescript/devconsole.ts/samplescripts/wallets.sampleinput.txt new file mode 100644 index 00000000000..415993f3d4e --- /dev/null +++ b/samples/typescript/devconsole.ts/samplescripts/wallets.sampleinput.txt @@ -0,0 +1,51 @@ +// Delete all wallets to start with empty state (commented out not to accidentally remove data) +//await walletsDeleteAll('deleteall') + +// create a wallet +await walletCreate(256, 'First') +await walletsList() +await walletDump() + +// add an extra coin to it +await walletAddCoin(WC.CoinType.solana) +await walletDump() + +// Create a second wallet by mnemonic import +await walletImport('ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal', 'Imported') +await walletsList() +await walletDump() + +// Load first ... +await walletsList() +await walletLoad(0) +await walletDump() + +// ... then second +await walletsList() +await walletLoad(1) +await walletDump() + +// retrieve private key and verify that it derives the same address +coin = WC.CoinType.bitcoin +privkey = wallets.wallet.wallet.privateKey(coin, 'devconsole.ts') +pubkey = privkey.getPublicKeySecp256k1(true); HexCoding.encode(pubkey.data()) +addrDerived = AnyAddress.createWithPublicKey(pubkey, coin).description() +addrStored = wallets.wallet.wallet.account(0).address() +addrDerived === addrStored + +// create another wallet +await walletCreate(256, 'Another') +await walletsList() +await walletDump() + +// Delete it +await walletDelete('delete') +await walletsList() +await walletDump() + +await walletLoad(0) +await walletDump() + +// Delete all wallets (commented out not to accidentally remove data) +//await walletDeleteAll('deleteall') +//await walletsList() diff --git a/samples/typescript/devconsole.ts/src/index.ts b/samples/typescript/devconsole.ts/src/index.ts new file mode 100644 index 00000000000..f08c79bd6c8 --- /dev/null +++ b/samples/typescript/devconsole.ts/src/index.ts @@ -0,0 +1,366 @@ +#!/usr/bin/env ts-node + +const chalk = require('chalk'); +const clear = require('clear'); +const figlet = require('figlet'); +const repl = require('node:repl'); +const { initWasm, TW, KeyStore } = require("@trustwallet/wallet-core"); +const fs = require("fs"); + +function enumerateNamespaces(topLevelNamespace: any) { + const exceptions: string[] = ['AsciiToString', 'ExitStatus', 'ENV', 'ERRNO_CODES', 'ERRNO_MESSAGES', 'DNS', 'Protocols', 'Sockets', 'UNWIND_CACHE', 'PATH', 'PATH_FS', 'SYSCALLS', 'JSEvents', 'JSEvents_requestFullscreen', 'JSEvents_resizeCanvasForFullscreen', 'ExceptionInfo', 'Browser', 'FS', 'MEMFS', 'TTY', 'PIPEFS', 'SOCKFS', 'RegisteredClass', 'Emval']; + var ns : string[] = []; + for (var member in topLevelNamespace) { + if (typeof topLevelNamespace[member] == 'function' || typeof topLevelNamespace[member] == 'object') { + ns.push(member); + } + } + return ns + .filter(n => { var firstLetter = n[0]; return (firstLetter >= 'A' && firstLetter <= 'Z'); }) + .filter(n => !n.includes('Error')) + .filter(n => !n.startsWith('HEAP')) + .filter(n => !n.startsWith('UTF')) + .filter(n => !n.startsWith('FS_')) + .filter(n => !n.startsWith('ClassHandle')) + .filter(n => !n.startsWith('RegisteredPointer')) + .filter(n => !exceptions.includes(n)); +} + +async function main() { + // Wrapper for a wallet stored in keystore + class StoredKeyWallet { + wallet: any; // StoredKey + constructor(w: any) { + this.wallet = w; + } + status(): string { + return `Wallet '${this.wallet.name()}', with ${this.wallet.accountCount()} addresses`; + } + dump(): void { + console.error(`Wallet '${this.wallet.name()}', ${this.wallet.isMnemonic() ? 'type mnemonic' : ''}:`); + + if (this.wallet.accountCount() == 0) { + console.log('No addresses'); + } else { + console.log('Addresses:'); + const n = this.wallet.accountCount(); + for (var i = 0; i < n; ++i) { + const a = this.wallet.account(i); + console.log(` ${WC.CoinTypeConfiguration.getSymbol(a.coin())}: ${a.address()}`); + } + } + } + } + + // Handles several wallets, with keystore + class TestWalletManager { + // single opened StoredKeyWallet instance + wallet: StoredKeyWallet | null = null; + StoreFolderPath = "/tmp/devconsole.ts"; + StoreFixedPassword = "devconsole.ts"; + HDWalletPassord = ''; + DefaultCoins: any[] = []; + keystore: any = null; + // wallets available in storage + storeWallets: any[] = []; + + init(): void { + this.DefaultCoins = [WC.CoinType.bitcoin, WC.CoinType.ethereum, WC.CoinType.binance]; + this.keystore = null; + // check and try to create store folder + if (!fs.existsSync(this.StoreFolderPath)) { + // try to create, ignore error + fs.mkdirSync(this.StoreFolderPath); + } + // check again + if (!fs.existsSync(this.StoreFolderPath)) { + console.log(`ERROR: Could not open/create storage folder! Wallet storage will not work. '${this.StoreFolderPath}'`); + return; + } + const storage = new KeyStore.FileSystemStorage(this.StoreFolderPath); + this.keystore = new KeyStore.Default(WC, storage); + } + async refreshStoreWallets(): Promise { + if (!this.keystore) { return; } + process.stdout.write(`Refreshing wallets list ... `); + try { + this.storeWallets = await this.keystore.loadAll(); + console.log(`found ${this.storeWallets.length} wallets (dir: ${this.StoreFolderPath})`); + } catch (err) { + console.log(`Exception: ${err}`); + } + } + async list(): Promise { + if (!this.keystore) { + console.log(`Keystore not available!`); + return; + } + await this.refreshStoreWallets(); + if (!this.storeWallets || this.storeWallets.length == 0) { + console.log("No wallets found in storage."); + } else { + let idx = 0; + console.log(`Found ${this.storeWallets.length} wallets in storage, use walletLoad(id) to load:`); + this.storeWallets.forEach(w => console.log(`${idx++}: ${w.name} \t${w.activeAccounts.length} addrs \t${w.id}`)); + } + } + async load(index: number) { + this.wallet = null; + if (!this.keystore) { return; } + try { + await this.refreshStoreWallets(); + if (index >= this.storeWallets.length) { + console.log(`Index out of range, max ${this.storeWallets.length}`); + await walletsList(); + return; + } + const id = this.storeWallets[index].id; + process.stdout.write(`Loading wallet ${index} ${id} ... `); + const wallet = await this.keystore.load(id); + console.log("done"); + this.wallet = new StoredKeyWallet(this.keystore.mapStoredKey(wallet)); + this.status(); + } catch (err) { + console.log(`Exception: ${err}`); + } + } + async loadAWallet() { + if (this.storeWallets.length > 0) { + await this.load(0); + } + } + autoFillName(name: string): string { + return (name === '') ? `Wallet${(this.storeWallets.length + 1).toString()}` : name; + } + async create(strength: number, name: string): Promise { + this.wallet = null; + if (!this.keystore) { return null; } + try { + if (name === '') { name = this.autoFillName(name); } + const hdWallet = WC.HDWallet.create(strength, this.HDWalletPassord); + const storedKey = WC.StoredKey.importHDWallet(hdWallet.mnemonic(), name, Buffer.from(this.StoreFixedPassword), this.DefaultCoins[0]); + this.DefaultCoins.forEach((coin) => { + storedKey.accountForCoin(coin, hdWallet); + }); + let wallet = this.keystore.mapWallet(storedKey); + await this.keystore.importWallet(wallet); + this.wallet = new StoredKeyWallet(storedKey); + console.log(`Wallet ${name} created, mnemonic: ${hdWallet.mnemonic()}`); + this.status(); + return this.wallet; + } catch (err) { + console.log(`Exception: ${err}`); + return null; + } + } + async import(mnemonic: string, name: string): Promise { + this.wallet = null; + if (!this.keystore) { return null; } + try { + if (!WC.Mnemonic.isValid(mnemonic)) { + console.error(`Mnemonic is not valid ${mnemonic}`); + return null; + } + if (name === '') { name = this.autoFillName(name); } + // There are two ways to do this here: + // 1. Create HDWallet with mnemonic, create StoroedKey with it (add coins), then map to a KeyStore wallet and import it into the storage, or + // 2. Import in KeyStore directly (using mnemonic), and obtain StoredKey from it + let wallet = await this.keystore.import(mnemonic, name, this.StoreFixedPassword, this.DefaultCoins); + this.wallet = new StoredKeyWallet(this.keystore.mapStoredKey(wallet)); + console.log(`Wallet ${name} imported, mnemonic: ${mnemonic}`); + this.status(); + return this.wallet; + } catch (err) { + console.log(`Exception: ${err}`); + return null; + } + } + async addCoin(coin: string): Promise { + if (this.wallet == null) { + console.error('No wallet open, see walletCreate() / walletLoad() / walletsList()'); + return null; + } + if (!this.keystore) { return null; } + const wallet = await this.keystore.addAccounts(this.wallet?.wallet.identifier(), this.StoreFixedPassword, [coin]); + this.wallet = new StoredKeyWallet(this.keystore.mapStoredKey(wallet)); + return this.wallet; + } + // Delete loaded wallet + async delete(param: string): Promise { + if (!this.wallet) { + console.log(`No wallet loaded`); + return; + } + if (!this.keystore) { return; } + try { + if (param !== 'delete') { + console.log(`Are you sure? Invoke with 'delete' parameter!`); + return; + } + const name = this.wallet.wallet.name(); + const id = this.wallet.wallet.identifier(); + await this.keystore.delete(id, this.StoreFixedPassword); + this.wallet = null; + console.log(`Wallet '${name}' ${id} deleted.`); + await this.refreshStoreWallets(); + } catch (err) { + console.log(`Exception: ${err}`); + } + } + async deleteAll(param: string): Promise { + await this.refreshStoreWallets(); + if (this.storeWallets.length == 0) { + console.log(`No wallets found`); + return; + } + if (!this.keystore) { return; } + try { + if (param !== 'deleteall') { + console.log(`Are you sure? Invoke with 'deleteall' parameter!`); + return; + } + while (this.storeWallets.length > 0) { + const id = this.storeWallets[0].id; + await this.keystore.delete(id, this.StoreFixedPassword); + await this.refreshStoreWallets(); + } + this.wallet = null; + console.log(`All wallets deleted.`) + } catch (err) { + console.log(`Exception: ${err}`); + } + } + status(): void { + if (this.wallet === null) { + console.error(`No wallet open, ${this.storeWallets.length} available; see walletCreate() / walletLoad() / walletsList()`); + } else { + console.error(`Wallet open: ${this.wallet.status()}`); + } + } + dump(): void { + this.status(); + if (this.wallet) { + this.wallet.dump(); + } + } + }; + + // single global TestWalletManager instance + let wallets: TestWalletManager = new TestWalletManager(); + + async function walletsList() { await wallets.list(); } + async function walletLoad(index: number) { await wallets.load(index); } + async function walletsDeleteAll(param: string) { await wallets.deleteAll(param); } + async function walletCreate(strength: number = 256, name: string = ''): Promise { return await wallets.create(strength, name); } + async function walletImport(mnemonic: string, name: string = ''): Promise { return await wallets.import(mnemonic, name); } + async function walletAddCoin(coin: string): Promise { return await wallets.addCoin(coin); } + function walletDump(): void { wallets.dump(); } + async function walletDelete(param: string) { await wallets.delete(param); } + + function help(): void { + console.log('This is an interactive typescript shell, to work with wallet-core (wasm)'); + console.log('You can use:'); + console.log(); + console.log("- Any typescript commands EXAMPLES"); + console.log(" 'wallet-core'.length"); + console.log(" x = 5; ++x"); + console.log(); + console.log('- Any method from Wallet-Core, through various namespaces, see full list below. Some examples:'); + console.log(" CoinType CoinType.bitcoin.value"); + console.log(" HDWallet wallet = HDWallet.create(256, ''); wallet.mnemonic()"); + console.log(" PrivateKey pk = PrivateKey.createWithData(HexCoding.decode('afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5'))"); + console.log(" PublicKey pubkey = PublicKey.createWithData(HexCoding.decode('0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1'), PublicKeyType.secp256k1)"); + console.log(" AnyAddress AnyAddress.createWithString('EQCTVra3xPXenA1wNFMba2taTsc9XMdfCWC7FJpGXjk7', CoinType.solana).description()"); + console.log(" CoinTypeConfiguration CoinTypeConfiguration.getSymbol(CoinType.bitcoin)"); + console.log(" Mnemonmic Mnemonic.isValidWord('acid')"); + console.log(" HexCoding HexCoding.encode(HexCoding.decode('01ab02'))"); + console.log(); + console.log("- Convenience methods:"); + console.log(" walletCreate([strength], [name]) walletCreate(128)"); + console.log(" walletImport(mnemonic, [name]) walletImport('ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal', 'Test1')"); + console.log(" walletDump() walletDump()"); + console.log(" walletAddCoin(symbol) walletAddCoin(CoinType.solana)"); + console.log(" walletsList() walletsList()"); + console.log(" walletLoad() walletLoad(0)"); + console.log(" walletDelete() walletDelete('delete')"); + console.log(" walletsDeleteAll() walletsDeleteAll('deleteall')"); + console.log(); + console.log("See examples in samplescripts folder, e.g.: cat samplescripts/protoeth.sampleinput.txt | npm run start"); + console.log(); + console.log("A longer example:"); + console.log(" coin = CoinType.solana"); + console.log(" wallet = HDWallet.createWithMnemonic('ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal', '')"); + console.log(" wallet.getAddressForCoin(coin)"); + console.log(" pk = wallet.getKeyForCoin(coin)"); + console.log(" HexCoding.encode(pk.data())"); + console.log(" pubkey = pk.getPublicKeyEd25519()"); + console.log(" HexCoding.encode(pubkey.data())"); + console.log(" a1 = AnyAddress.createWithPublicKey(pubkey, coin)"); + console.log(" a1.description()"); + console.log(); + console.log("Full list of exposed Wallet-core namespaces:"); + console.log(enumerateNamespaces(WC).sort().join(', ')); + console.log(enumerateNamespaces(TW).sort().map(n => 'TW.' + n).join(', ')); + console.log(); + } + + function exit(code: number): void { + process.exit(code); + } + + clear(); + console.log( + chalk.blue( + figlet.textSync('wallet-core', { horizontalLayout: 'full' }) + ) + ); + console.log(chalk.blue(' devconsole.ts, wasm lib')); + console.log(); + + process.stdout.write("Initializing WalletCore library ..."); + const WC = await initWasm(); + console.log(` done.`); + console.log(); + console.log(`Type 'help()' for help`); + console.log(`Press Ctrl-C twice to exit`); + console.log(chalk.red(`This is a test tool, DO NOT USE WITH REAL FUNDS!`)); + console.log(); + + wallets.init(); + await wallets.refreshStoreWallets(); + await wallets.loadAWallet(); + + const local = repl.start('> '); + + // Expose WC namespaces, as top-level + var nss = enumerateNamespaces(WC); + nss.forEach(ns => local.context[ns] = WC[ns]); + // also all under WC + local.context.WC = WC; + // Expose TW namespaces (under TW) + local.context.TW = TW; + // Expose KeyStore namespace (under KeyStore) + local.context.KeyStore = KeyStore; + + // Expose more stuff; utilities + local.context.help = help; + local.context.exit = exit; + local.context.walletsList = walletsList; + local.context.walletLoad = walletLoad; + local.context.walletsDeleteAll = walletsDeleteAll; + local.context.walletCreate = walletCreate; + local.context.walletImport = walletImport; + local.context.walletAddCoin = walletAddCoin; + local.context.walletDump = walletDump; + local.context.walletDelete = walletDelete; + local.context.wallets = wallets; + local.context.wallet = wallets.wallet; + local.context.coin = WC.CoinType.bitcoin; + + local.on('exit', () => { + console.log('Bye!'); + process.exit(); + }); +} + +main(); diff --git a/samples/typescript/devconsole.ts/test/data/privpubkey.testinput.txt b/samples/typescript/devconsole.ts/test/data/privpubkey.testinput.txt new file mode 100644 index 00000000000..8431062b58b --- /dev/null +++ b/samples/typescript/devconsole.ts/test/data/privpubkey.testinput.txt @@ -0,0 +1,26 @@ +// Create a private key, sign a message, derive public key from private key, verify signature +privKeyData = '0xafeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5' +pubKeyData = '0x0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1' +messageDigest = '0001020304050607080910111213141519171819202122232425262728293031' +signature = '0x673b54a91d87cfb9389e54cc55b1a9343a6eb9f2ea1f449cb19a248a86bb904c1efb109f8cf655c4f510211053c1696e52c75843c5c803fa1b78fe0c263f468201' + +testAssert = function(expression, text) { + if (!expression) { console.log('ERROR: ', text); exit(1); } + return 'ok: ' + text; +} + +privkey = PrivateKey.createWithData(HexCoding.decode(privKeyData)) +HexCoding.encode(privkey.data()) +testAssert(HexCoding.encode(privkey.data()) == privKeyData, 'private key match') + +HexCoding.encode(sig1 = privkey.sign(HexCoding.decode(messageDigest), Curve.secp256k1)) +testAssert(HexCoding.encode(sig1) == signature, 'signature match') + +pubkey = privkey.getPublicKeySecp256k1(true) +HexCoding.encode(pubkey.data()) +testAssert(HexCoding.encode(pubkey.data()) == pubKeyData, 'pubkey match') + +verifyRes = pubkey.verify(sig1, HexCoding.decode(messageDigest)) +testAssert(verifyRes, 'signature verification') + +exit(0) diff --git a/samples/typescript/devconsole.ts/tsconfig.json b/samples/typescript/devconsole.ts/tsconfig.json new file mode 100644 index 00000000000..70198268800 --- /dev/null +++ b/samples/typescript/devconsole.ts/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "lib": [ + "es6", + "es2015", + "dom" + ], + "declaration": true, + "outDir": "lib", + "rootDir": "src", + "strict": true, + "types": [ + "node" + ], + "esModuleInterop": true, + "resolveJsonModule": true + } +} \ No newline at end of file diff --git a/samples/wasm/.gitignore b/samples/wasm/.gitignore new file mode 100644 index 00000000000..8fa7eb9d5df --- /dev/null +++ b/samples/wasm/.gitignore @@ -0,0 +1,2 @@ +*.js +*.wasm diff --git a/samples/wasm/README.md b/samples/wasm/README.md new file mode 100644 index 00000000000..010195fdb38 --- /dev/null +++ b/samples/wasm/README.md @@ -0,0 +1,42 @@ +# Wasm Sample + +## DISCLAIMER + +> This is a sample html for demonstration purpose only, +> do not use it with real addresses, real transactions, or real funds. +> Use it at your own risk. + +## Prerequisites + +1. Everything you need to build the wallet core: https://developer.trustwallet.com/wallet-core/developing-the-library/building +2. Install [emsdk](https://emscripten.org/docs/getting_started/downloads.html) +- - python3 +- - cmake +- - run `tools/install-dependencies` if you just cloned this repo +- - run `tools/install-wasm-dependencies` +3. node.js + +## Building and Running + +- run `tools/generate-files` +- run `tools/wasm-build` +- cd `wasm` +- - run `npm install` +- - run `npm run codegen:js-browser` +- - run `npm run copy:wasm-sample` +- cd `samples/wasm` +- run `python3 -m http.server 8000` +- open web browser and navigate to `http://127.0.0.1:8000` + +## Notes + +This sample also demonstrates how to use protobuf.js models in html directly, `core_proto.js` is built by command defined in `wasm/package.json` + +```shell +npm run codegen:js-browser +``` + +For modern javaScript sample, please check out: + +- React: https://github.com/hewigovens/wallet-core-react +- Flutter: https://github.com/iampawan/Wallet-Core-Web/tree/dev diff --git a/samples/wasm/index.html b/samples/wasm/index.html new file mode 100644 index 00000000000..13a6298ef1c --- /dev/null +++ b/samples/wasm/index.html @@ -0,0 +1,1319 @@ + + + + + + Emscripten-Generated Code + + + + + image/svg+xml + + +
+
Downloading...
+ + + Resize canvas + Lock/hide mouse pointer     + + + + +
+ +
+ + +
+ +
+ + + + + + + + + diff --git a/src/Aeternity/Address.cpp b/src/Aeternity/Address.cpp index 101ba527854..b3d0ecc64fb 100644 --- a/src/Aeternity/Address.cpp +++ b/src/Aeternity/Address.cpp @@ -7,10 +7,8 @@ #include "Address.h" #include "Identifiers.h" #include -#include -#include -using namespace TW::Aeternity; +namespace TW::Aeternity { /// Determines whether a string makes a valid address. bool Address::isValid(const std::string& string) { @@ -25,7 +23,7 @@ bool Address::isValid(const std::string& string) { } /// Initializes an address from a public key. -Address::Address(const PublicKey &publicKey) { +Address::Address(const PublicKey& publicKey) { if (publicKey.type != TWPublicKeyTypeED25519) { throw std::invalid_argument("Invalid public key type"); } @@ -56,3 +54,5 @@ bool Address::checkPayload(const std::string& payload) { unsigned long base58 = Base58::bitcoin.decodeCheck(payload).size(); return base58 == size; } + +} // namespace TW::Aeternity diff --git a/src/Aeternity/Address.h b/src/Aeternity/Address.h index 07e9b732939..3733a44e93d 100644 --- a/src/Aeternity/Address.h +++ b/src/Aeternity/Address.h @@ -4,6 +4,8 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +#pragma once + #include #include diff --git a/src/Aeternity/Entry.cpp b/src/Aeternity/Entry.cpp index 97d72906e63..cab15ca9d26 100644 --- a/src/Aeternity/Entry.cpp +++ b/src/Aeternity/Entry.cpp @@ -9,19 +9,21 @@ #include "Address.h" #include "Signer.h" -using namespace TW::Aeternity; using namespace std; +namespace TW::Aeternity { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +} // namespace TW::Aeternity diff --git a/src/Aeternity/Entry.h b/src/Aeternity/Entry.h index 3418220ea7d..b344b0ebb1f 100644 --- a/src/Aeternity/Entry.h +++ b/src/Aeternity/Entry.h @@ -12,12 +12,11 @@ namespace TW::Aeternity { /// Entry point for implementation of Aeternity coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeAeternity}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::Aeternity diff --git a/src/Aeternity/Signer.cpp b/src/Aeternity/Signer.cpp index 7c9adf1fafa..ff27db8d81a 100644 --- a/src/Aeternity/Signer.cpp +++ b/src/Aeternity/Signer.cpp @@ -5,7 +5,6 @@ // file LICENSE at the root of the source code distribution tree. #include "Signer.h" -#include "Address.h" #include "Base58.h" #include "Base64.h" #include "HexCoding.h" @@ -14,9 +13,10 @@ #include using namespace TW; -using namespace TW::Aeternity; -Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { +namespace TW::Aeternity { + +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); std::string sender_id = input.from_address(); std::string recipient_id = input.to_address(); @@ -29,7 +29,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { /// implementation copied from /// https://github.com/aeternity/aepp-sdk-go/blob/07aa8a77e5/aeternity/helpers.go#L367 -Proto::SigningOutput Signer::sign(const TW::PrivateKey &privateKey, Transaction &transaction) { +Proto::SigningOutput Signer::sign(const TW::PrivateKey& privateKey, Transaction& transaction) { auto txRlp = transaction.encode(); /// append networkId and txRaw @@ -86,4 +86,6 @@ std::string Signer::encodeBase64WithChecksum(const std::string& prefix, const TW append(data, checksumPart); return prefix + TW::Base64::encode(data); -} \ No newline at end of file +} + +} // namespace TW::Aeternity diff --git a/src/Aeternity/Signer.h b/src/Aeternity/Signer.h index dd289e5487d..d356375055a 100644 --- a/src/Aeternity/Signer.h +++ b/src/Aeternity/Signer.h @@ -4,6 +4,8 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +#pragma once + #include "Transaction.h" #include "../proto/Aeternity.pb.h" #include @@ -11,14 +13,14 @@ namespace TW::Aeternity { class Signer { - public: +public: /// Signs a Proto::SigningInput transaction static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; /// Signs the given transaction. - static Proto::SigningOutput sign(const PrivateKey &privateKey, Transaction &transaction); + static Proto::SigningOutput sign(const PrivateKey& privateKey, Transaction& transaction); - private: +private: static const uint8_t checkSumSize = 4; static Data buildRlpTxRaw(Data& txRaw, Data& sigRaw); diff --git a/src/Aeternity/Transaction.cpp b/src/Aeternity/Transaction.cpp index c06d74d7efe..7ed0515b97b 100644 --- a/src/Aeternity/Transaction.cpp +++ b/src/Aeternity/Transaction.cpp @@ -10,8 +10,7 @@ #include #include -using namespace TW; -using namespace TW::Aeternity; +namespace TW::Aeternity { /// RLP returns a byte serialized representation Data Transaction::encode() { @@ -46,3 +45,5 @@ TW::Data Transaction::encodeSafeZero(uint256_t value) { } return Ethereum::RLP::encode(value); } + +} // namespace TW::Aeternity diff --git a/src/Aeternity/Transaction.h b/src/Aeternity/Transaction.h index 6bace4629b8..a58db36f651 100644 --- a/src/Aeternity/Transaction.h +++ b/src/Aeternity/Transaction.h @@ -53,7 +53,7 @@ class Transaction { //// see https://github.com/aeternity/protocol/blob/epoch-v0.22.0/serializations.md#the-id-type static Data buildTag(const std::string& address); - /// Awternity network does not accept zero int values as rlp param, + /// Aeternity network does not accept zero int values as rlp param, /// instead empty byte array should be encoded /// see https://forum.aeternity.com/t/invalid-tx-error-on-mainnet-goggle-says-it-looks-good/4118/5?u=defuera static Data encodeSafeZero(uint256_t value); diff --git a/src/Aion/Address.cpp b/src/Aion/Address.cpp index 58872b004d1..411a1b97d9e 100644 --- a/src/Aion/Address.cpp +++ b/src/Aion/Address.cpp @@ -5,10 +5,9 @@ // file LICENSE at the root of the source code distribution tree. #include "Address.h" -#include "../Hash.h" #include "../HexCoding.h" -using namespace TW::Aion; +namespace TW::Aion { bool Address::isValid(const std::string& string) { const auto data = parse_hex(string); @@ -40,3 +39,5 @@ Address::Address(const PublicKey& publicKey) { std::string Address::string() const { return "0x" + hex(bytes); } + +} // namespace TW::Aion diff --git a/src/Aion/Entry.cpp b/src/Aion/Entry.cpp index c30ee168d7a..f32ef1e3491 100644 --- a/src/Aion/Entry.cpp +++ b/src/Aion/Entry.cpp @@ -9,19 +9,28 @@ #include "Address.h" #include "Signer.h" -using namespace TW::Aion; +using namespace TW; using namespace std; +namespace TW::Aion { + // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + const auto addr = Address(address); + return {addr.bytes.begin(), addr.bytes.end()}; +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +} // namespace TW::Aion diff --git a/src/Aion/Entry.h b/src/Aion/Entry.h index a369e5b39ea..1ff2ed22dc1 100644 --- a/src/Aion/Entry.h +++ b/src/Aion/Entry.h @@ -12,12 +12,12 @@ namespace TW::Aion { /// Entry point for implementation of Aion coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeAion}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::Aion diff --git a/src/Aion/RLP.h b/src/Aion/RLP.h index b713b79c47a..55330d37a5a 100644 --- a/src/Aion/RLP.h +++ b/src/Aion/RLP.h @@ -17,7 +17,7 @@ namespace TW::Aion { -/// Aion's RLP encoging for long numbers +/// Aion's RLP encoding for long numbers /// https://github.com/aionnetwork/aion/issues/680 struct RLP { static Data encodeLong(boost::multiprecision::uint128_t l) noexcept { diff --git a/src/Aion/Signer.cpp b/src/Aion/Signer.cpp index a4a3a4adf14..01c90385d91 100644 --- a/src/Aion/Signer.cpp +++ b/src/Aion/Signer.cpp @@ -5,13 +5,12 @@ // file LICENSE at the root of the source code distribution tree. #include "Signer.h" - -#include "../Hash.h" #include "../uint256.h" #include using namespace TW; -using namespace TW::Aion; + +namespace TW::Aion { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { using boost::multiprecision::uint128_t; @@ -46,3 +45,5 @@ void Signer::sign(const PrivateKey& privateKey, Transaction& transaction) noexce transaction.signature = result; } + +} // namespace TW::Aion diff --git a/src/Aion/Signer.h b/src/Aion/Signer.h index 68a95d79628..76d5e97d36b 100644 --- a/src/Aion/Signer.h +++ b/src/Aion/Signer.h @@ -7,7 +7,7 @@ #pragma once #include "Transaction.h" -#include "../Data.h" +#include "Data.h" #include "../Hash.h" #include "../PrivateKey.h" #include "../proto/Aion.pb.h" diff --git a/src/Aion/Transaction.cpp b/src/Aion/Transaction.cpp index 017dbe96896..c108f0d4b37 100644 --- a/src/Aion/Transaction.cpp +++ b/src/Aion/Transaction.cpp @@ -4,14 +4,14 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "RLP.h" #include "Transaction.h" -#include "../Ethereum/RLP.h" +#include "RLP.h" using namespace TW; -using namespace TW::Aion; using boost::multiprecision::uint128_t; +namespace TW::Aion { + Data Transaction::encode() const noexcept { auto encoded = Data(); append(encoded, Ethereum::RLP::encode(nonce)); @@ -27,3 +27,5 @@ Data Transaction::encode() const noexcept { } return Ethereum::RLP::encodeList(encoded); } + +} // namespace TW::Aion diff --git a/src/Aion/Transaction.h b/src/Aion/Transaction.h index dc975dcaac9..b5e54bc4d85 100644 --- a/src/Aion/Transaction.h +++ b/src/Aion/Transaction.h @@ -7,7 +7,7 @@ #pragma once #include "Address.h" -#include "../Data.h" +#include "Data.h" #include diff --git a/src/Algorand/Address.cpp b/src/Algorand/Address.cpp index 3e2b8d3b1fa..b0e4ede7229 100644 --- a/src/Algorand/Address.cpp +++ b/src/Algorand/Address.cpp @@ -1,17 +1,15 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. #include "Address.h" -#include "../HexCoding.h" -#include "../Hash.h" #include "../Base32.h" #include -using namespace TW::Algorand; +namespace TW::Algorand { bool Address::isValid(const std::string& string) { if (string.size() != encodedSize) { @@ -63,3 +61,5 @@ std::string Address::string() const { std::string encoded = Base32::encode(data); return encoded; } + +} // namespace TW::Algorand diff --git a/src/Algorand/Address.h b/src/Algorand/Address.h index 93f82788676..dc4af6666fe 100644 --- a/src/Algorand/Address.h +++ b/src/Algorand/Address.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include diff --git a/src/Algorand/AssetTransfer.cpp b/src/Algorand/AssetTransfer.cpp index eecb4cf8335..3f4c1a4eccd 100644 --- a/src/Algorand/AssetTransfer.cpp +++ b/src/Algorand/AssetTransfer.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -7,8 +7,7 @@ #include "AssetTransfer.h" #include "BinaryCoding.h" -using namespace TW; -using namespace TW::Algorand; +namespace TW::Algorand { Data AssetTransfer::serialize() const { Data data; @@ -59,3 +58,5 @@ Data AssetTransfer::serialize() const { return data; } + +} // namespace TW::Algorand diff --git a/src/Algorand/AssetTransfer.h b/src/Algorand/AssetTransfer.h index cdc5bab9c2b..63b4b51fc86 100644 --- a/src/Algorand/AssetTransfer.h +++ b/src/Algorand/AssetTransfer.h @@ -4,9 +4,11 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +#pragma once + #include "Address.h" #include "BaseTransaction.h" -#include "../Data.h" +#include "Data.h" #include "../proto/Algorand.pb.h" namespace TW::Algorand { diff --git a/src/Algorand/BaseTransaction.h b/src/Algorand/BaseTransaction.h index 1db984c5a3d..232c5ed2b00 100644 --- a/src/Algorand/BaseTransaction.h +++ b/src/Algorand/BaseTransaction.h @@ -12,7 +12,8 @@ namespace TW::Algorand { class BaseTransaction { - public: +public: + virtual ~BaseTransaction() noexcept = default; virtual Data serialize() const = 0; virtual Data serialize(const Data& signature) const { /* Algorand transaction and signature are encoded with msgpack: diff --git a/src/Algorand/BinaryCoding.h b/src/Algorand/BinaryCoding.h index a05ba376981..ee4220d46b3 100644 --- a/src/Algorand/BinaryCoding.h +++ b/src/Algorand/BinaryCoding.h @@ -11,10 +11,8 @@ namespace TW::Algorand { -#ifndef _MSC_VER #pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare" #pragma GCC diagnostic ignored "-Wunused-function" -#endif static inline void encodeString(std::string string, Data& data) { // encode string header diff --git a/src/Algorand/Entry.cpp b/src/Algorand/Entry.cpp index 29f9809955f..2f090b1ca5b 100644 --- a/src/Algorand/Entry.cpp +++ b/src/Algorand/Entry.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,23 +9,24 @@ #include "Address.h" #include "Signer.h" -using namespace TW::Algorand; -using namespace std; +namespace TW::Algorand { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { +std::string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { return Signer::signJSON(json, key); } + +} // namespace TW::Algorand diff --git a/src/Algorand/Entry.h b/src/Algorand/Entry.h index df752e2e42c..a2e2548839b 100644 --- a/src/Algorand/Entry.h +++ b/src/Algorand/Entry.h @@ -12,14 +12,13 @@ namespace TW::Algorand { /// Entry point for implementation of Algorand coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeAlgorand}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual bool supportsJSONSigning() const { return true; } - virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool supportsJSONSigning() const { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; }; } // namespace TW::Algorand diff --git a/src/Algorand/OptInAssetTransaction.cpp b/src/Algorand/OptInAssetTransaction.cpp index f83df502f78..e62488d48e1 100644 --- a/src/Algorand/OptInAssetTransaction.cpp +++ b/src/Algorand/OptInAssetTransaction.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -7,8 +7,7 @@ #include "OptInAssetTransaction.h" #include "BinaryCoding.h" -using namespace TW; -using namespace TW::Algorand; +namespace TW::Algorand { Data OptInAssetTransaction::serialize() const { Data data; @@ -56,3 +55,5 @@ Data OptInAssetTransaction::serialize() const { return data; } + +} // namespace TW::Algorand diff --git a/src/Algorand/OptInAssetTransaction.h b/src/Algorand/OptInAssetTransaction.h index 2f9b7c55394..49d2a219213 100644 --- a/src/Algorand/OptInAssetTransaction.h +++ b/src/Algorand/OptInAssetTransaction.h @@ -4,9 +4,13 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +#pragma once + +#include + #include "Address.h" #include "BaseTransaction.h" -#include "../Data.h" +#include "Data.h" #include "../proto/Algorand.pb.h" namespace TW::Algorand { @@ -29,7 +33,7 @@ class OptInAssetTransaction: public BaseTransaction { : address(address), fee(fee) , assetId(assetId), firstRound(firstRound) , lastRound(lastRound), note(note) - , type(type), genesisId(genesisId) + , type(std::move(type)), genesisId(genesisId) , genesisHash(genesisHash) {} public: diff --git a/src/Algorand/Signer.cpp b/src/Algorand/Signer.cpp index f979b24fdf9..cfb615def50 100644 --- a/src/Algorand/Signer.cpp +++ b/src/Algorand/Signer.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -11,14 +11,13 @@ #include -using namespace TW; -using namespace TW::Algorand; +namespace TW::Algorand { const Data TRANSACTION_TAG = {84, 88}; const std::string TRANSACTION_PAY = "pay"; const std::string ASSET_TRANSACTION = "axfer"; -Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto protoOutput = Proto::SigningOutput(); auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); auto pubkey = key.getPublicKey(TWPublicKeyTypeED25519); @@ -35,7 +34,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { auto to = Address(message.to_address()); auto transaction = Transfer(from, to, fee, message.amount(), firstRound, - lastRound, note, TRANSACTION_PAY, genesisId, genesisHash); + lastRound, note, TRANSACTION_PAY, genesisId, genesisHash); auto signature = sign(key, transaction); auto serialized = transaction.BaseTransaction::serialize(signature); protoOutput.set_encoded(serialized.data(), serialized.size()); @@ -45,8 +44,8 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { auto transaction = AssetTransfer(from, to, fee, message.amount(), - message.asset_id(), firstRound, lastRound, note, - ASSET_TRANSACTION,genesisId, genesisHash); + message.asset_id(), firstRound, lastRound, note, + ASSET_TRANSACTION, genesisId, genesisHash); auto signature = sign(key, transaction); auto serialized = transaction.BaseTransaction::serialize(signature); protoOutput.set_encoded(serialized.data(), serialized.size()); @@ -55,12 +54,12 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { auto transaction = OptInAssetTransaction(from, fee, message.asset_id(), firstRound, lastRound, note, - ASSET_TRANSACTION,genesisId, genesisHash); + ASSET_TRANSACTION, genesisId, genesisHash); auto signature = sign(key, transaction); auto serialized = transaction.BaseTransaction::serialize(signature); protoOutput.set_encoded(serialized.data(), serialized.size()); } - + return protoOutput; } @@ -76,5 +75,7 @@ Data Signer::sign(const PrivateKey& privateKey, const BaseTransaction& transacti append(data, TRANSACTION_TAG); append(data, transaction.serialize()); auto signature = privateKey.sign(data, TWCurveED25519); - return Data(signature.begin(), signature.end()); + return {signature.begin(), signature.end()}; } + +} // namespace TW::Algorand diff --git a/src/Algorand/Signer.h b/src/Algorand/Signer.h index 7942022585c..4f8ef6a2730 100644 --- a/src/Algorand/Signer.h +++ b/src/Algorand/Signer.h @@ -10,7 +10,7 @@ #include "OptInAssetTransaction.h" #include "Transfer.h" -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" namespace TW::Algorand { diff --git a/src/Algorand/Transfer.cpp b/src/Algorand/Transfer.cpp index b4db29f5d3f..dd470ddae22 100644 --- a/src/Algorand/Transfer.cpp +++ b/src/Algorand/Transfer.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -6,10 +6,8 @@ #include "Transfer.h" #include "BinaryCoding.h" -#include "../HexCoding.h" -using namespace TW; -using namespace TW::Algorand; +namespace TW::Algorand { Data Transfer::serialize() const { /* Algorand transaction is encoded with msgpack @@ -34,11 +32,17 @@ Data Transfer::serialize() const { // note is optional size += 1; } + // don't encode 0 amount + if (amount == 0) { + size -= 1; + } data.push_back(0x80 + size); // encode fields one by one (sorted by name) - encodeString("amt", data); - encodeNumber(amount, data); + if (amount > 0) { + encodeString("amt", data); + encodeNumber(amount, data); + } encodeString("fee", data); encodeNumber(fee, data); @@ -70,3 +74,5 @@ Data Transfer::serialize() const { encodeString(type, data); return data; } + +} // namespace TW::Algorand diff --git a/src/Algorand/Transfer.h b/src/Algorand/Transfer.h index 29b4f6169e5..422ae4d2e4d 100644 --- a/src/Algorand/Transfer.h +++ b/src/Algorand/Transfer.h @@ -6,9 +6,11 @@ #pragma once +#include + #include "Address.h" #include "BaseTransaction.h" -#include "../Data.h" +#include "Data.h" #include "../proto/Algorand.pb.h" namespace TW::Algorand { @@ -32,7 +34,7 @@ class Transfer : public BaseTransaction { : from(from) , to(to) , fee(fee), amount(amount) , firstRound(firstRound), lastRound(lastRound) - , note(note), type(type) + , note(note), type(std::move(type)) , genesisId(genesisIdg), genesisHash(genesisHash) {} public: diff --git a/src/AnyAddress.cpp b/src/AnyAddress.cpp new file mode 100644 index 00000000000..9bb265cc041 --- /dev/null +++ b/src/AnyAddress.cpp @@ -0,0 +1,32 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "AnyAddress.h" + +#include +#include "Coin.h" + +namespace TW { + +Data AnyAddress::getData() const { + return TW::addressToData(coin, address); +} + +AnyAddress* AnyAddress::createAddress(const std::string& address, enum TWCoinType coin, const std::string& hrp) { + auto normalized = TW::normalizeAddress(coin, address, hrp); + if (normalized.empty()) { + return nullptr; + } + + return new AnyAddress{.address = std::move(normalized), .coin = coin}; +} + +AnyAddress* AnyAddress::createAddress(const PublicKey& publicKey, enum TWCoinType coin, const std::string& hrp) { + auto derivedAddress = TW::deriveAddress(coin, publicKey, TWDerivationDefault, hrp); + return new AnyAddress{.address = std::move(derivedAddress), .coin = coin}; +} + +} // namespace TW diff --git a/src/AnyAddress.h b/src/AnyAddress.h new file mode 100644 index 00000000000..7bf773eaf07 --- /dev/null +++ b/src/AnyAddress.h @@ -0,0 +1,41 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Data.h" +#include "PublicKey.h" + +#include +#include +#include +#include + +namespace TW { + +class AnyAddress { +public: + std::string address; + + enum TWCoinType coin; + + static AnyAddress* createAddress(const std::string& address, enum TWCoinType coin, const std::string& hrp = ""); + static AnyAddress* createAddress(const PublicKey& publicKey, enum TWCoinType coin, const std::string& hrp = ""); + + Data getData() const; +}; + +inline bool operator==(const AnyAddress& lhs, const AnyAddress& rhs) { + return lhs.address == rhs.address && lhs.coin == rhs.coin; +} + +} // namespace TW + +/// Wrapper for C interface. +struct TWAnyAddress { + // Pointer to the underlying implementation + TW::AnyAddress* impl; +}; diff --git a/src/Aptos/Address.cpp b/src/Aptos/Address.cpp new file mode 100644 index 00000000000..30c1b587872 --- /dev/null +++ b/src/Aptos/Address.cpp @@ -0,0 +1,88 @@ +// Copyright © 2017-2022 Trust Wallet. +// Author: Clement Doumergue +// +// 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 "Address.h" +#include "HexCoding.h" + +namespace { + +std::string normalize(const std::string& string, std::size_t hexLen) { + std::string hexStr((TW::Aptos::Address::size * 2) - hexLen, '0'); + hexStr.append(string); + return hexStr; +} + +} // namespace + +namespace TW::Aptos { + +bool Address::isValid(const std::string& string) { + auto address = string; + if (address.starts_with("0x")) { + address = address.substr(2); + if (std::size_t hexLen = address.size(); hexLen < Address::size * 2) { + address = normalize(address, hexLen); + } + } + if (address.size() != 2 * Address::size) { + return false; + } + const auto data = parse_hex(address); + return isValid(data); +} + +Address::Address(const std::string& string) { + if (!isValid(string)) { + throw std::invalid_argument("Invalid address string"); + } + auto hexFunctor = [&string]() { + if (std::size_t hexLen = string.size() - 2; string.starts_with("0x") && hexLen < Address::size * 2) { + //! We have specific address like 0x1, padding it. + return parse_hex(normalize(string.substr(2), hexLen)); + } else { + return parse_hex(string); + } + }; + + const auto data = hexFunctor(); + std::copy(data.begin(), data.end(), bytes.begin()); +} + +Address::Address(const Data& data) { + if (!isValid(data)) { + throw std::invalid_argument("Invalid address data"); + } + std::copy(data.begin(), data.end(), bytes.begin()); +} + +Address::Address(const PublicKey& publicKey) { + if (publicKey.type != TWPublicKeyTypeED25519) { + throw std::invalid_argument("Invalid public key type"); + } + auto key_data = publicKey.bytes; + append(key_data, 0x00); + const auto data = Hash::sha3_256(key_data); + std::copy(data.begin(), data.end(), bytes.begin()); +} + +std::string Address::string(bool withPrefix) const { + std::string output = withPrefix ? "0x" : ""; + return output + hex(bytes); +} + +std::string Address::shortString() const { + std::string s = hex(bytes); + s.erase(0, s.find_first_not_of('0')); + return s; +} + +BCS::Serializer& operator<<(BCS::Serializer& stream, Address addr) noexcept { + stream.add_bytes(addr.bytes.begin(), addr.bytes.end()); + return stream; +} + +} // namespace TW::Aptos diff --git a/src/Aptos/Address.h b/src/Aptos/Address.h new file mode 100644 index 00000000000..45fe27aa011 --- /dev/null +++ b/src/Aptos/Address.h @@ -0,0 +1,59 @@ +// Copyright © 2017-2022 Trust Wallet. +// Author: Clement Doumergue +// +// 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 "BCS.h" +#include "Data.h" +#include "../PublicKey.h" + +#include + +namespace TW::Aptos { + +class Address { +public: + static constexpr size_t size = 32; + + std::array bytes; + + /// Determines whether a collection of bytes makes a valid address. + static bool isValid(const Data& data) { return data.size() == size; } + + /// Determines whether a string makes a valid address. + static bool isValid(const std::string& string); + + /// Initializes an Aptos address with a string representation. + explicit Address(const std::string& string); + + /// Initializes an Aptos address with a collection of bytes + explicit Address(const Data& data); + + /// Initializes an Aptos address with a public key. + explicit Address(const PublicKey& publicKey); + + /// Constructor that allow factory programming; + Address() noexcept = default; + + /// Returns a string representation of the address. + [[nodiscard]] std::string string(bool withPrefix = true) const; + + /// Returns a short string representation of the address. E.G 0x1; + [[nodiscard]] std::string shortString() const; +}; + +constexpr inline bool operator==(const Address& lhs, const Address& rhs) noexcept { + return lhs.bytes == rhs.bytes; +} + +inline const Address gAddressZero = Address("0x0"); +inline const Address gAddressOne = Address("0x1"); +inline const Address gAddressThree = Address("0x3"); + +BCS::Serializer& operator<<(BCS::Serializer& stream, Address) noexcept; + +} // namespace TW::Aptos diff --git a/src/Aptos/Entry.cpp b/src/Aptos/Entry.cpp new file mode 100644 index 00000000000..4c299f946a2 --- /dev/null +++ b/src/Aptos/Entry.cpp @@ -0,0 +1,26 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Entry.h" + +#include "Address.h" +#include "Signer.h" + +namespace TW::Aptos { + +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, TW::byte, TW::byte, const char*) const { + return Address::isValid(address); +} + +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { + return Address(publicKey).string(); +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + signTemplate(dataIn, dataOut); +} + +} // namespace TW::Aptos diff --git a/src/Aptos/Entry.h b/src/Aptos/Entry.h new file mode 100644 index 00000000000..0fd1ab1d008 --- /dev/null +++ b/src/Aptos/Entry.h @@ -0,0 +1,22 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "../CoinEntry.h" + +namespace TW::Aptos { + +/// Entry point for implementation of Aptos 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, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; +}; + +} // namespace TW::Aptos diff --git a/src/Aptos/MoveTypes.cpp b/src/Aptos/MoveTypes.cpp new file mode 100644 index 00000000000..4df1a932c5f --- /dev/null +++ b/src/Aptos/MoveTypes.cpp @@ -0,0 +1,152 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include + +namespace TW::Aptos { + +Aptos::ModuleId::ModuleId(Address accountAddress, Identifier name) noexcept + : mAccountAddress(accountAddress), mName(std::move(name)) { +} + +Data ModuleId::accessVector() const noexcept { + BCS::Serializer serializer; + serializer << static_cast(gCodeTag) << mAccountAddress << mName; + return serializer.bytes; +} + +std::string ModuleId::string() const noexcept { + std::stringstream ss; + ss << mAccountAddress.string() << "::" << mName; + return ss.str(); +} + +std::string ModuleId::shortString() const noexcept { + std::stringstream ss; + ss << "0x" << mAccountAddress.shortString() << "::" << mName; + return ss.str(); +} + +#ifdef _MSC_VER +#pragma optimize( "", off ) +#endif +Data StructTag::serialize(bool withResourceTag) const noexcept { + BCS::Serializer serializer; + if (withResourceTag) + { + serializer << gResourceTag; + } + serializer << mAccountAddress << mModule << mName << mTypeParams; + return serializer.bytes; +} +#ifdef _MSC_VER +#pragma optimize("", on) +#endif + +StructTag::StructTag(Address accountAddress, Identifier module, Identifier name, std::vector typeParams) noexcept + : mAccountAddress(accountAddress), mModule(std::move(module)), mName(std::move(name)), mTypeParams(std::move(typeParams)) { +} +std::string StructTag::string() const noexcept { + std::stringstream ss; + ss << "0x" << mAccountAddress.shortString() << "::" << mModule << "::" << mName; + if (!mTypeParams.empty()) { + ss << "<"; + ss << TypeTagToString(*mTypeParams.begin()); + std::for_each(begin(mTypeParams) + 1, end(mTypeParams), [&ss](auto&& cur) { + ss << ", " << TypeTagToString(cur); + }); + ss << ">"; + } + return ss.str(); +} + +BCS::Serializer& operator<<(BCS::Serializer& stream, Bool) noexcept { + stream << Bool::value; + return stream; +} +BCS::Serializer& operator<<(BCS::Serializer& stream, U8) noexcept { + stream << U8::value; + return stream; +} +BCS::Serializer& operator<<(BCS::Serializer& stream, U64) noexcept { + stream << U64::value; + return stream; +} +BCS::Serializer& operator<<(BCS::Serializer& stream, U128) noexcept { + stream << U128::value; + return stream; +} +BCS::Serializer& operator<<(BCS::Serializer& stream, TAddress) noexcept { + stream << TAddress::value; + return stream; +} +BCS::Serializer& operator<<(BCS::Serializer& stream, TSigner) noexcept { + stream << TSigner::value; + return stream; +} + +BCS::Serializer& operator<<(BCS::Serializer& stream, const StructTag& st) noexcept { + auto res = st.serialize(); + stream.add_bytes(begin(res), end(res)); + return stream; +} + +BCS::Serializer& operator<<(BCS::Serializer& stream, const TStructTag& st) noexcept { + stream << TStructTag::value; + auto res = st.st.serialize(false); + stream.add_bytes(begin(res), end(res)); + return stream; +} + +BCS::Serializer& operator<<(BCS::Serializer& stream, const Vector& t) noexcept { + stream << Vector::value; + for (auto&& cur: t.tags) { + stream << cur; + } + return stream; +} + +BCS::Serializer& operator<<(BCS::Serializer& stream, const TypeTag& t) noexcept { + std::visit([&stream](auto&& arg) { stream << arg; }, t.tags); + return stream; +} + +BCS::Serializer& operator<<(BCS::Serializer& stream, const ModuleId& module) noexcept { + stream << module.address() << module.name(); + return stream; +} +std::string TypeTagToString(const TypeTag& typeTag) noexcept { + auto visit_functor = [](const TypeTag::TypeTagVariant& value) -> std::string { + if (std::holds_alternative(value)) { + return "bool"; + } else if (std::holds_alternative(value)) { + return "u8"; + } else if (std::holds_alternative(value)) { + return "u64"; + } else if (std::holds_alternative(value)) { + return "u128"; + } else if (std::holds_alternative(value)) { + return "address"; + } else if (std::holds_alternative(value)) { + return "signer"; + } else if (auto* vectorData = std::get_if(&value); vectorData != nullptr && !vectorData->tags.empty()) { + std::stringstream ss; + ss << "vector<" << TypeTagToString(*vectorData->tags.begin()) << ">"; + return ss.str(); + } else if (auto* structData = std::get_if(&value); structData) { + return structData->string(); + } else if (auto* tStructData = std::get_if(&value); tStructData) { + return tStructData->st.string(); + } else { + return ""; + } + }; + + return std::visit(visit_functor, typeTag.tags); +} + +} // namespace TW::Aptos diff --git a/src/Aptos/MoveTypes.h b/src/Aptos/MoveTypes.h new file mode 100644 index 00000000000..c4360afcd83 --- /dev/null +++ b/src/Aptos/MoveTypes.h @@ -0,0 +1,107 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Aptos/Address.h" +#include "BCS.h" +#include + +namespace TW::Aptos { + +constexpr std::uint8_t gCodeTag{0}; +constexpr std::uint8_t gResourceTag{1}; +using Identifier = std::string; + +class ModuleId { +public: + ///< Constructor + ModuleId(Address accountAddress, Identifier name) noexcept; + + ///< Getters + [[nodiscard]] const std::string& name() const noexcept { return mName; } + [[nodiscard]] const Address& address() const noexcept { return mAccountAddress; } + [[nodiscard]] Data accessVector() const noexcept; + [[nodiscard]] std::string string() const noexcept; + [[nodiscard]] std::string shortString() const noexcept; + +private: + Address mAccountAddress; + Identifier mName; +}; + +inline ModuleId gAptosAccountModule{gAddressOne, "aptos_account"}; +inline ModuleId gAptosCoinModule{gAddressOne, "coin"}; +inline ModuleId gAptosManagedCoinsModule{gAddressOne, "managed_coin"}; +inline ModuleId gAptosTokenTransfersModule{gAddressThree, "token_transfers"}; + +BCS::Serializer& operator<<(BCS::Serializer& stream, const ModuleId& module) noexcept; + +struct TypeTag; + +struct Bool { + static constexpr std::uint8_t value = 0; +}; +struct U8 { + static constexpr std::uint8_t value = 1; +}; +struct U64 { + static constexpr std::uint8_t value = 2; +}; +struct U128 { + static constexpr std::uint8_t value = 3; +}; +struct TAddress { + static constexpr std::uint8_t value = 4; +}; +struct TSigner { + static constexpr std::uint8_t value = 5; +}; +struct Vector { + static constexpr std::uint8_t value = 6; + std::vector tags; +}; + +class StructTag { +public: + explicit StructTag(Address accountAddress, Identifier module, Identifier name, std::vector typeParams) noexcept; + [[nodiscard]] Data serialize(bool withResourceTag = true) const noexcept; + [[nodiscard]] ModuleId moduleID() const noexcept { return {mAccountAddress, mName}; }; + [[nodiscard]] std::string string() const noexcept; + +private: + Address mAccountAddress; + Identifier mModule; + Identifier mName; + std::vector mTypeParams; +}; + +// C++ limitation, the first StructTag will serialize with ResourceTag, the inner one will use the value 7 instead. Tweaking by wrapping the struct +struct TStructTag { + static constexpr std::uint8_t value = 7; + StructTag st; +}; + +struct TypeTag { + using TypeTagVariant = std::variant; + TypeTagVariant tags; +}; + +std::string TypeTagToString(const TypeTag& typeTag) noexcept; +BCS::Serializer& operator<<(BCS::Serializer& stream, const StructTag& st) noexcept; +BCS::Serializer& operator<<(BCS::Serializer& stream, Bool) noexcept; +BCS::Serializer& operator<<(BCS::Serializer& stream, U8) noexcept; +BCS::Serializer& operator<<(BCS::Serializer& stream, U64) noexcept; +BCS::Serializer& operator<<(BCS::Serializer& stream, U128) noexcept; +BCS::Serializer& operator<<(BCS::Serializer& stream, TAddress) noexcept; +BCS::Serializer& operator<<(BCS::Serializer& stream, TSigner) noexcept; +BCS::Serializer& operator<<(BCS::Serializer& stream, const Vector& t) noexcept; +BCS::Serializer& operator<<(BCS::Serializer& stream, const TStructTag& t) noexcept; +BCS::Serializer& operator<<(BCS::Serializer& stream, const TypeTag& t) noexcept; +static const TypeTag gTransferTag = {TypeTag::TypeTagVariant(TStructTag{.st = StructTag(gAddressOne, "aptos_coin", "AptosCoin", {})})}; +static const TypeTag gOfferNftTag = {TypeTag::TypeTagVariant(TStructTag{.st = StructTag(gAddressThree, "token_transfers", "offer_script", {})})}; + +} // namespace TW::Aptos diff --git a/src/Aptos/Signer.cpp b/src/Aptos/Signer.cpp new file mode 100644 index 00000000000..9c15ea200af --- /dev/null +++ b/src/Aptos/Signer.cpp @@ -0,0 +1,208 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Signer.h" +#include "Address.h" +#include "Hash.h" +#include "MoveTypes.h" +#include "TransactionBuilder.h" +#include "TransactionPayload.h" + +namespace { +template +void serializeToArgs(std::vector& args, T&& toSerialize) { + TW::BCS::Serializer serializer; + serializer << std::forward(toSerialize); + args.emplace_back(serializer.bytes); +} +} // namespace + +namespace TW::Aptos { + +template +std::pair, nlohmann::json> commonTransferPayload(const TPayload& input) { + std::vector args; + serializeToArgs(args, Address(input.to())); + serializeToArgs(args, input.amount()); + nlohmann::json argsJson = nlohmann::json::array({input.to(), std::to_string(input.amount())}); + return std::make_pair(args, argsJson); +} + +TransactionPayload transferPayload(const Proto::SigningInput& input) { + auto&& [args, argsJson] = commonTransferPayload(input.transfer()); + TransactionPayload payload = EntryFunction(gAptosAccountModule, "transfer", {}, args, argsJson); + return payload; +} + +TransactionPayload createAccountPayload(const Proto::SigningInput& input) { + std::vector args; + serializeToArgs(args, Address(input.create_account().auth_key())); + nlohmann::json argsJson = nlohmann::json::array({input.create_account().auth_key()}); + TransactionPayload payload = EntryFunction(gAptosAccountModule, "create_account", {}, args, argsJson); + return payload; +} + +TransactionPayload claimNftPayload(const Proto::ClaimNftMessage& msg) { + std::vector args; + serializeToArgs(args, Address(msg.sender())); + serializeToArgs(args, Address(msg.creator())); + serializeToArgs(args, msg.collectionname()); + serializeToArgs(args, msg.name()); + serializeToArgs(args, msg.property_version()); + // clang-format off + nlohmann::json argsJson = nlohmann::json::array( + { + msg.sender(), + msg.creator(), + msg.collectionname(), + msg.name(), + std::to_string(msg.property_version()), + }); + // clang-format on + TransactionPayload payload = EntryFunction(gAptosTokenTransfersModule, "claim_script", {}, args, argsJson); + return payload; +} + +TransactionPayload nftOfferPayload(const Proto::OfferNftMessage& msg) { + std::vector args; + serializeToArgs(args, Address(msg.receiver())); + serializeToArgs(args, Address(msg.creator())); + serializeToArgs(args, msg.collectionname()); + serializeToArgs(args, msg.name()); + serializeToArgs(args, msg.property_version()); + serializeToArgs(args, msg.amount()); + // clang-format off + nlohmann::json argsJson = nlohmann::json::array( + { + msg.receiver(), + msg.creator(), + msg.collectionname(), + msg.name(), + std::to_string(msg.property_version()), + std::to_string(msg.amount()) + }); + // clang-format on + TransactionPayload payload = EntryFunction(gAptosTokenTransfersModule, "offer_script", {}, args, argsJson); + return payload; +} + +TransactionPayload cancelNftOfferPayload(const Proto::CancelOfferNftMessage& msg) { + std::vector args; + serializeToArgs(args, Address(msg.receiver())); + serializeToArgs(args, Address(msg.creator())); + serializeToArgs(args, msg.collectionname()); + serializeToArgs(args, msg.name()); + serializeToArgs(args, msg.property_version()); + // clang-format off + nlohmann::json argsJson = nlohmann::json::array( + { + msg.receiver(), + msg.creator(), + msg.collectionname(), + msg.name(), + std::to_string(msg.property_version()), + }); + // clang-format on + TransactionPayload payload = EntryFunction(gAptosTokenTransfersModule, "cancel_offer_script", {}, args, argsJson); + return payload; +} + +TransactionPayload tokenTransferPayload(const Proto::SigningInput& input) { + + auto&& [args, argsJson] = commonTransferPayload(input.token_transfer()); + auto& function = input.token_transfer().function(); + TypeTag tokenTransferTag = {TypeTag::TypeTagVariant(TStructTag{.st = StructTag(Address(function.account_address()), + function.module(), function.name(), {})})}; + TransactionPayload payload = EntryFunction(gAptosCoinModule, "transfer", {tokenTransferTag}, args, argsJson); + return payload; +} + +TransactionPayload registerTokenPayload(const Proto::SigningInput& input) { + + auto& function = input.register_token().function(); + TypeTag tokenRegisterTag = {TypeTag::TypeTagVariant(TStructTag{.st = StructTag(Address(function.account_address()), + function.module(), function.name(), {})})}; + TransactionPayload payload = EntryFunction(gAptosManagedCoinsModule, "register", {tokenRegisterTag}, {}); + return payload; +} + +Proto::SigningOutput blindSign(const Proto::SigningInput& input) { + auto output = Proto::SigningOutput(); + BCS::Serializer serializer; + auto encodedCall = parse_hex(input.any_encoded()); + serializer.add_bytes(begin(encodedCall), end(encodedCall)); + auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto signature = privateKey.sign(encodedCall, TWCurveED25519); + auto pubKeyData = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes; + output.set_raw_txn(encodedCall.data(), encodedCall.size()); + output.mutable_authenticator()->set_public_key(pubKeyData.data(), pubKeyData.size()); + output.mutable_authenticator()->set_signature(signature.data(), signature.size()); + serializer << BCS::uleb128{.value = 0} << pubKeyData << signature; + output.set_encoded(serializer.bytes.data(), serializer.bytes.size()); + + // clang-format off + nlohmann::json json = { + {"type", "ed25519_signature"}, + {"public_key", hexEncoded(pubKeyData)}, + {"signature", hexEncoded(signature)} + }; + // clang-format on + + output.set_json(json.dump()); + return output; +} + +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) { + auto protoOutput = Proto::SigningOutput(); + if (!input.any_encoded().empty()) { + return blindSign(input); + } + auto nftPayloadFunctor = [](const Proto::NftMessage& nftMessage) { + switch (nftMessage.nft_transaction_payload_case()) { + case Proto::NftMessage::kOfferNft: + return nftOfferPayload(nftMessage.offer_nft()); + case Proto::NftMessage::kCancelOfferNft: + return cancelNftOfferPayload(nftMessage.cancel_offer_nft()); + case Proto::NftMessage::kClaimNft: + return claimNftPayload(nftMessage.claim_nft()); + case Proto::NftMessage::NFT_TRANSACTION_PAYLOAD_NOT_SET: + throw std::runtime_error("Nft message payload not set"); + } + }; + auto payloadFunctor = [&input, &nftPayloadFunctor]() { + switch (input.transaction_payload_case()) { + case Proto::SigningInput::kTransfer: { + return transferPayload(input); + } + case Proto::SigningInput::kTokenTransfer: { + return tokenTransferPayload(input); + } + case Proto::SigningInput::kNftMessage: { + return nftPayloadFunctor(input.nft_message()); + } + case Proto::SigningInput::kCreateAccount: { + return createAccountPayload(input); + } + case Proto::SigningInput::kRegisterToken: { + return registerTokenPayload(input); + } + case Proto::SigningInput::TRANSACTION_PAYLOAD_NOT_SET: + throw std::runtime_error("Transaction payload should be set"); + } + }; + TransactionBuilder::builder() + .sender(Address(input.sender())) + .sequenceNumber(input.sequence_number()) + .payload(payloadFunctor()) + .maxGasAmount(input.max_gas_amount()) + .gasUnitPrice(input.gas_unit_price()) + .expirationTimestampSecs(input.expiration_timestamp_secs()) + .chainId(static_cast(input.chain_id())) + .sign(input, protoOutput); + return protoOutput; +} + +} // namespace TW::Aptos diff --git a/src/Aptos/Signer.h b/src/Aptos/Signer.h new file mode 100644 index 00000000000..ad1f09ffdd8 --- /dev/null +++ b/src/Aptos/Signer.h @@ -0,0 +1,27 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Data.h" +#include "../PrivateKey.h" +#include "../proto/Aptos.pb.h" + +namespace TW::Aptos { + +inline const Data gAptosSalt = data("APTOS::RawTransaction"); + +/// Helper class that performs Aptos transaction signing. +class Signer { +public: + /// Hide default constructor + Signer() = delete; + + /// Signs a Proto::SigningInput transaction + static Proto::SigningOutput sign(const Proto::SigningInput& input); +}; + +} // namespace TW::Aptos diff --git a/src/Aptos/TransactionBuilder.h b/src/Aptos/TransactionBuilder.h new file mode 100644 index 00000000000..20f37a591ff --- /dev/null +++ b/src/Aptos/TransactionBuilder.h @@ -0,0 +1,100 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "HexCoding.h" +#include "TransactionPayload.h" +#include + +namespace TW::Aptos { + +class TransactionBuilder { +public: + TransactionBuilder() noexcept = default; + + static TransactionBuilder builder() noexcept { return {}; } + + TransactionBuilder& sender(Address sender) noexcept { + mSender = sender; + return *this; + } + + TransactionBuilder& sequenceNumber(std::uint64_t sequenceNumber) noexcept { + mSequenceNumber = sequenceNumber; + return *this; + } + + TransactionBuilder& payload(TransactionPayload payload) noexcept { + mPayload = std::move(payload); + return *this; + } + + TransactionBuilder& maxGasAmount(std::uint64_t maxGasAmount) noexcept { + mMaxGasAmount = maxGasAmount; + return *this; + } + + TransactionBuilder& gasUnitPrice(std::uint64_t gasUnitPrice) noexcept { + mGasUnitPrice = gasUnitPrice; + return *this; + } + + TransactionBuilder& expirationTimestampSecs(std::uint64_t expirationTimestampSecs) noexcept { + mExpirationTimestampSecs = expirationTimestampSecs; + return *this; + } + + TransactionBuilder& chainId(std::uint8_t chainId) noexcept { + mChainId = chainId; + return *this; + } + + TransactionBuilder& sign(const Proto::SigningInput& input, Proto::SigningOutput& output) noexcept { + BCS::Serializer serializer; + serializer << mSender << mSequenceNumber << mPayload << mMaxGasAmount << mGasUnitPrice << mExpirationTimestampSecs << mChainId; + auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + output.set_raw_txn(serializer.bytes.data(), serializer.bytes.size()); + auto msgToSign = TW::Hash::sha3_256(gAptosSalt.data(), gAptosSalt.size()); + append(msgToSign, serializer.bytes); + auto signature = privateKey.sign(msgToSign, TWCurveED25519); + auto pubKeyData = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes; + output.mutable_authenticator()->set_public_key(pubKeyData.data(), pubKeyData.size()); + output.mutable_authenticator()->set_signature(signature.data(), signature.size()); + serializer << BCS::uleb128{.value = 0} << pubKeyData << signature; + output.set_encoded(serializer.bytes.data(), serializer.bytes.size()); + + // https://fullnode.devnet.aptoslabs.com/v1/spec#/operations/submit_transaction + // clang-format off + nlohmann::json json = { + {"sender", mSender.string()}, + {"sequence_number", std::to_string(mSequenceNumber)}, + {"max_gas_amount", std::to_string(mMaxGasAmount)}, + {"gas_unit_price", std::to_string(mGasUnitPrice)}, + {"expiration_timestamp_secs", std::to_string(mExpirationTimestampSecs)}, + {"payload", payloadToJson(mPayload)}, + {"signature", { + {"type", "ed25519_signature"}, + {"public_key", hexEncoded(pubKeyData)}, + {"signature", hexEncoded(signature)}} + } + }; + // clang-format on + output.set_json(json.dump()); + return *this; + } + +private: + Address mSender{}; + std::uint64_t mSequenceNumber{}; + TransactionPayload mPayload{}; + std::uint64_t mMaxGasAmount{}; + std::uint64_t mGasUnitPrice{}; + std::uint64_t mExpirationTimestampSecs{}; + std::uint8_t mChainId{}; +}; + +} // namespace TW::Aptos diff --git a/src/Aptos/TransactionPayload.cpp b/src/Aptos/TransactionPayload.cpp new file mode 100644 index 00000000000..1e5b188e290 --- /dev/null +++ b/src/Aptos/TransactionPayload.cpp @@ -0,0 +1,57 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include + +namespace TW::Aptos { + +EntryFunction::EntryFunction(ModuleId module, Identifier function, std::vector tyArgs, std::vector args, nlohmann::json jsonArgs) noexcept + : mModule(std::move(module)), mFunction(std::move(function)), mTyArgs(std::move(tyArgs)), mArgs(std::move(args)), mJsonArgs(std::move(jsonArgs)) { +} + +BCS::Serializer& operator<<(BCS::Serializer& stream, const EntryFunction& entryFunction) noexcept { + stream << entryFunction.module() << entryFunction.function() << entryFunction.tyArgs() << entryFunction.args(); + return stream; +} + +nlohmann::json payloadToJson(const TransactionPayload& payload) { + auto visit_functor = [](const TransactionPayload& value) -> nlohmann::json { + if (auto* entryFunction = std::get_if(&value); entryFunction) { + return entryFunction->json(); + } else { + return {}; + } + }; + + return std::visit(visit_functor, payload); +} + +BCS::Serializer& operator<<(BCS::Serializer& stream, [[maybe_unused]] const Script& script) noexcept { + return stream; +} + +BCS::Serializer& operator<<(BCS::Serializer& stream, [[maybe_unused]] const ModuleBundle& moduleBundle) noexcept { + return stream; +} + +nlohmann::json EntryFunction::json() const noexcept { + nlohmann::json tyArgsJson = nlohmann::json::array(); + for (auto&& cur : mTyArgs) { + tyArgsJson.emplace_back(TypeTagToString(cur)); + } + // clang-format off + nlohmann::json out = { + {"type", "entry_function_payload"}, + {"function", mModule.shortString() + "::" + mFunction}, + {"type_arguments", tyArgsJson}, + {"arguments", mJsonArgs.empty() ? nlohmann::json::array() : mJsonArgs} + }; + // clang-format on + return out; +} + +} // namespace TW::Aptos diff --git a/src/Aptos/TransactionPayload.h b/src/Aptos/TransactionPayload.h new file mode 100644 index 00000000000..e52124f84ff --- /dev/null +++ b/src/Aptos/TransactionPayload.h @@ -0,0 +1,46 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include +#include +#include + +namespace TW::Aptos { + +/// Call a Move entry function. +class EntryFunction { +public: + explicit EntryFunction(ModuleId module, Identifier function, std::vector tyArgs, std::vector args, nlohmann::json jsonArgs = {}) noexcept; + [[nodiscard]] const ModuleId& module() const noexcept { return mModule; } + [[nodiscard]] const Identifier& function() const noexcept { return mFunction; } + [[nodiscard]] const std::vector& tyArgs() const noexcept { return mTyArgs; } + [[nodiscard]] const std::vector& args() const noexcept { return mArgs; } + [[nodiscard]] nlohmann::json json() const noexcept; + +private: + ModuleId mModule; + Identifier mFunction; + std::vector mTyArgs; + std::vector mArgs; + nlohmann::json mJsonArgs; +}; + + +class Script { +}; + +class ModuleBundle { +}; + +BCS::Serializer& operator<<(BCS::Serializer& stream, const EntryFunction& entryFunction) noexcept; +BCS::Serializer& operator<<(BCS::Serializer& stream, const Script& script) noexcept; +BCS::Serializer& operator<<(BCS::Serializer& stream, const ModuleBundle& moduleBundle) noexcept; +using TransactionPayload = std::variant; +nlohmann::json payloadToJson(const TransactionPayload& payload); + +} // namespace TW::Aptos diff --git a/src/BCS.cpp b/src/BCS.cpp new file mode 100644 index 00000000000..1959ba83079 --- /dev/null +++ b/src/BCS.cpp @@ -0,0 +1,42 @@ +// Copyright © 2017-2022 Trust Wallet. +// Created by Clément Doumergue + +// 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 "BCS.h" + +namespace TW::BCS { + +Serializer& operator<<(Serializer& stream, std::byte b) noexcept { + stream.add_byte(b); + return stream; +} + +Serializer& operator<<(Serializer& stream, uleb128 t) noexcept { + integral auto value = t.value; + + while (value >= 0x80) { + // Add the 7 lowest bits of data and set highest bit to 1 + stream << static_cast((value & 0x7f) | 0x80); + value >>= 7; + } + + // Add the remaining bits of data (highest bit is already 0 at this point) + stream << static_cast(value); + return stream; +} + +Serializer& operator<<(Serializer& stream, std::string_view sv) noexcept { + stream << uleb128{static_cast(sv.size())}; + stream.add_bytes(sv.begin(), sv.end()); + return stream; +} + +Serializer& operator<<(Serializer& stream, std::nullopt_t) noexcept { + stream << false; + return stream; +} + +} // namespace TW::BCS diff --git a/src/BCS.h b/src/BCS.h new file mode 100644 index 00000000000..5d795089649 --- /dev/null +++ b/src/BCS.h @@ -0,0 +1,222 @@ +// Copyright © 2017-2022 Trust Wallet. +// Created by Clément Doumergue + +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "Data.h" +#include "concepts/tw_concepts.h" + +namespace TW::BCS { + +/// Implementation of BCS encoding (as specified by the Diem project, see github.com/diem/bcs#detailed-specifications) + +struct Serializer { + Data bytes; + + void add_byte(std::byte b) noexcept { + bytes.emplace_back(static_cast(b)); + } + + template + void add_bytes(Iterator first, Iterator last) noexcept { + std::transform(first, last, std::back_inserter(bytes), [](auto&& c) { + return static_cast(c); + }); + } + + void clear() noexcept { + bytes.clear(); + } +}; + +struct uleb128 { + uint32_t value; +}; + +namespace details { + +template +concept aggregate_struct = std::is_class_v> && std::is_aggregate_v>; + +template +concept map_container = requires(T t) { + typename T::key_type; + typename T::mapped_type; + { std::declval().size() } -> std::same_as; + }; + +template + requires integral || + floating_point || + std::same_as || + std::same_as || + std::same_as || + aggregate_struct || + map_container +struct is_serializable { + static constexpr auto value = true; +}; + +template +struct is_serializable> { + static constexpr auto value = is_serializable::value; +}; + +template +struct is_serializable> { + static constexpr auto value = (is_serializable::value && ...); +}; + +template +struct is_serializable> { + static constexpr auto value = is_serializable::value && is_serializable::value; +}; + +template +struct is_serializable> { + static constexpr auto value = is_serializable::value; +}; + +template +struct is_serializable> { + static constexpr auto value = (is_serializable::value && ...); +}; + +template +Serializer& serialize_integral_impl(Serializer& stream, T t, std::index_sequence) noexcept { + const char* bytes = reinterpret_cast(&t); + // Add each byte in little-endian order + return (stream << ... << static_cast(bytes[Is])); +} + +template +Serializer& serialize_tuple_impl(Serializer& stream, const T& t, std::index_sequence) noexcept { + return (stream << ... << std::get(t)); +} + +template +struct dependent_false { + static constexpr auto value = false; +}; + +template +constexpr auto to_tuple(T&& t) { + if constexpr (std::is_empty_v) { + return std::make_tuple(); + } else if constexpr (requires { [&t] { auto&& [a0] = t; }; }) { + auto&& [a0] = std::forward(t); + return std::make_tuple(a0); + } else if constexpr (requires { [&t] { auto&& [a0, a1] = t; }; }) { + auto&& [a0, a1] = std::forward(t); + return std::make_tuple(a0, a1); + } else if constexpr (requires { [&t] { auto&& [a0, a1, a2] = t; }; }) { + auto&& [a0, a1, a2] = std::forward(t); + return std::make_tuple(a0, a1, a2); + } else if constexpr (requires { [&t] { auto&& [a0, a1, a2, a3] = t; }; }) { + auto&& [a0, a1, a2, a3] = std::forward(t); + return std::make_tuple(a0, a1, a2, a3); + } else if constexpr (requires { [&t] { auto&& [a0, a1, a2, a3, a4] = t; }; }) { + auto&& [a0, a1, a2, a3, a4] = std::forward(t); + return std::make_tuple(a0, a1, a2, a3, a4); + } else if constexpr (requires { [&t] { auto&& [a0, a1, a2, a3, a4, a5] = t; }; }) { + auto&& [a0, a1, a2, a3, a4, a5] = std::forward(t); + return std::make_tuple(a0, a1, a2, a3, a4, a5); + } else { + static_assert(dependent_false::value, "the structure has more than 6 members"); + } +} + +template +Serializer& serialize_struct_impl(Serializer& stream, const T& t) noexcept { + return stream << to_tuple(t); +} +} // namespace details + +template +concept PrimitiveSerializable = details::is_serializable::value; + +template +concept CustomSerializable = requires(T t) { + { std::declval() << t } -> std::same_as; + }; + +template +concept Serializable = PrimitiveSerializable || CustomSerializable; + +Serializer& operator<<(Serializer& stream, std::byte b) noexcept; + +template +Serializer& operator<<(Serializer& stream, T t) noexcept { + return details::serialize_integral_impl(stream, t, std::make_index_sequence{}); +} + +Serializer& operator<<(Serializer& stream, uleb128 t) noexcept; + +Serializer& operator<<(Serializer& stream, std::string_view sv) noexcept; + +template +Serializer& operator<<(Serializer& stream, const std::optional o) noexcept { + if (o.has_value()) { + stream << true; + stream << o.value(); + } else { + stream << false; + } + return stream; +} + +Serializer& operator<<(Serializer& stream, std::nullopt_t) noexcept; + +template +Serializer& operator<<(Serializer& stream, const std::tuple& t) noexcept { + return details::serialize_tuple_impl(stream, t, std::make_index_sequence{}); +} + +template +Serializer& operator<<(Serializer& stream, const std::pair& t) noexcept { + return details::serialize_tuple_impl(stream, t, std::make_index_sequence<2>{}); +} + +template +Serializer& operator<<(Serializer& stream, const T& t) noexcept { + return details::serialize_struct_impl(stream, t); +} + +template +Serializer& operator<<(Serializer& stream, const std::vector& t) noexcept { + stream << uleb128{static_cast(t.size())}; + for (auto&& cur: t) { + stream << cur; + } + return stream; +} + +template +Serializer& operator<<(Serializer& stream, const std::variant& t) noexcept { + stream << uleb128{static_cast(t.index())}; + std::visit([&stream](auto&& value) { stream << value; }, t); + return stream; +} + +template +Serializer& operator<<(Serializer& stream, const T& t) noexcept { + stream << uleb128{static_cast(t.size())}; + for (auto&& [k, v] : t) { + stream << std::make_tuple(k, v); + } + return stream; +} + +} // namespace TW::BCS diff --git a/src/Base32.h b/src/Base32.h index 78f9717d5f9..7c9f5e9cb3e 100644 --- a/src/Base32.h +++ b/src/Base32.h @@ -20,11 +20,13 @@ inline bool decode(const std::string& encoded_in, Data& decoded_out, const char* size_t inLen = encoded_in.size(); // obtain output length first size_t outLen = base32_decoded_length(inLen); + //win #ifdef _MSC_VER uint8_t *buf = (uint8_t *)_alloca(outLen); #else uint8_t buf[outLen]; #endif + if (alphabet_in == nullptr) { alphabet_in = BASE32_ALPHABET_RFC4648; } @@ -43,11 +45,12 @@ inline std::string encode(const Data& val, const char* alphabet = nullptr) { size_t inLen = val.size(); // obtain output length first, reserve for terminator size_t outLen = base32_encoded_length(inLen) + 1; + //win #ifdef _MSC_VER char *buf = (char *)_alloca(outLen); #else char buf[outLen]; -#endif +#endif if (alphabet == nullptr) { alphabet = BASE32_ALPHABET_RFC4648; } diff --git a/src/Base58.cpp b/src/Base58.cpp index c83c690dada..61ed8d4600b 100644 --- a/src/Base58.cpp +++ b/src/Base58.cpp @@ -67,7 +67,7 @@ Data Base58::decodeCheck(const char* begin, const char* end, Hash::Hasher hasher } // re-calculate the checksum, ensure it matches the included 4-byte checksum - auto hash = hasher(result.data(), result.size() - 4); + auto hash = Hash::hash(hasher, result.data(), result.size() - 4); if (!std::equal(hash.begin(), hash.begin() + 4, result.end() - 4)) { return {}; } @@ -144,7 +144,7 @@ Data Base58::decode(const char* begin, const char* end) const { std::string Base58::encodeCheck(const byte* begin, const byte* end, Hash::Hasher hasher) const { // add 4-byte hash check to the end Data dataWithCheck(begin, end); - auto hash = hasher(begin, end - begin); + auto hash = Hash::hash(hasher, begin, end - begin); dataWithCheck.insert(dataWithCheck.end(), hash.begin(), hash.begin() + 4); return encode(dataWithCheck); } diff --git a/src/Base58.h b/src/Base58.h index 9bcc2df7aa9..d5e231d307d 100644 --- a/src/Base58.h +++ b/src/Base58.h @@ -34,12 +34,12 @@ class Base58 { : digits(digits), characterMap(characterMap) {} /// Decodes a base 58 string verifying the checksum, returns empty on failure. - Data decodeCheck(const std::string& string, Hash::Hasher hasher = Hash::sha256d) const { + Data decodeCheck(const std::string& string, Hash::Hasher hasher = Hash::HasherSha256d) const { return decodeCheck(string.data(), string.data() + string.size(), hasher); } /// Decodes a base 58 string verifying the checksum, returns empty on failure. - Data decodeCheck(const char* begin, const char* end, Hash::Hasher hasher = Hash::sha256d) const; + Data decodeCheck(const char* begin, const char* end, Hash::Hasher hasher = Hash::HasherSha256d) const; /// Decodes a base 58 string into `result`, returns `false` on failure. Data decode(const std::string& string) const { @@ -51,12 +51,12 @@ class Base58 { /// Encodes data as a base 58 string with a checksum. template - std::string encodeCheck(const T& data, Hash::Hasher hasher = Hash::sha256d) const { + std::string encodeCheck(const T& data, Hash::Hasher hasher = Hash::HasherSha256d) const { return encodeCheck(data.data(), data.data() + data.size(), hasher); } /// Encodes data as a base 58 string with a checksum. - std::string encodeCheck(const byte* pbegin, const byte* pend, Hash::Hasher hasher = Hash::sha256d) const; + std::string encodeCheck(const byte* pbegin, const byte* pend, Hash::Hasher hasher = Hash::HasherSha256d) const; /// Encodes data as a base 58 string. template diff --git a/src/Base58Address.h b/src/Base58Address.h index c294f364161..ee66c495a93 100644 --- a/src/Base58Address.h +++ b/src/Base58Address.h @@ -80,7 +80,7 @@ class Base58Address { if (publicKey.type != TWPublicKeyTypeSECP256k1) { throw std::invalid_argument("Bitcoin::Address needs a compressed SECP256k1 public key."); } - const auto data = publicKey.hash(prefix, Hash::sha256ripemd); + const auto data = publicKey.hash(prefix, Hash::HasherSha256ripemd); std::copy(data.begin(), data.end(), bytes.begin()); } diff --git a/src/Base64.cpp b/src/Base64.cpp index 6f5aeb44969..cf4605592ad 100644 --- a/src/Base64.cpp +++ b/src/Base64.cpp @@ -63,15 +63,9 @@ void convertToBase64Url(string& b) { } Data decodeBase64Url(const string& val) { - Data bytes; - try { - return decode(val); - } catch (const exception& ex) { - // 2nd try: Base64URL format (replaced by '-' and '_' by '+' and '/' ) - string base64Url = val; - convertFromBase64Url(base64Url); - return decode(base64Url); - } + string base64Url = val; + convertFromBase64Url(base64Url); + return decode(base64Url); } string encodeBase64Url(const Data& val) { diff --git a/src/Base64.h b/src/Base64.h index 089e1c21a93..5bc20e6fc70 100644 --- a/src/Base64.h +++ b/src/Base64.h @@ -20,7 +20,7 @@ std::string encode(const Data& val); // Base64Url format uses '-' and '_' as the two special characters, Base64 uses '+'and '/'. Data decodeBase64Url(const std::string& val); -// Encode bytes into Base64Url string (uses '-' and '_' as pecial characters) +// Encode bytes into Base64Url string (uses '-' and '_' as special characters) std::string encodeBase64Url(const Data& val); } // namespace TW::Base64 diff --git a/src/Bech32.cpp b/src/Bech32.cpp index f1c6699eba6..a309f45c2a2 100644 --- a/src/Bech32.cpp +++ b/src/Bech32.cpp @@ -14,9 +14,11 @@ // Bech32M variant also supported (BIP350) // Max length of 90 constraint is extended here to 120 for other usages -using namespace TW::Bech32; + using namespace TW; +namespace TW::Bech32 { + namespace { /** The Bech32 character set for encoding. */ @@ -26,15 +28,14 @@ const char* charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; constexpr std::array charset_rev = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, -1, 29, - -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, 1, 0, 3, 16, 11, 28, 12, 14, - 6, 4, 2, -1, -1, -1, -1, -1, -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, - 19, -1, 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1}; + -1, -1, -1, -1, 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, -1, 29, + -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, 1, 0, 3, 16, 11, 28, 12, 14, + 6, 4, 2, -1, -1, -1, -1, -1, -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, + 19, -1, 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1}; const uint32_t BECH32_XOR_CONST = 0x01; const uint32_t BECH32M_XOR_CONST = 0x2bc830a3; - /** Find the polynomial with value coefficients mod the generator as 30-bit. */ uint32_t polymod(const Data& values) { uint32_t chk = 1; @@ -105,7 +106,7 @@ Data create_checksum(const std::string& hrp, const Data& values, ChecksumVariant } // namespace /** Encode a Bech32 string. */ -std::string Bech32::encode(const std::string& hrp, const Data& values, ChecksumVariant variant) { +std::string encode(const std::string& hrp, const Data& values, ChecksumVariant variant) { Data checksum = create_checksum(hrp, values, variant); Data combined = values; append(combined, checksum); @@ -118,7 +119,7 @@ std::string Bech32::encode(const std::string& hrp, const Data& values, ChecksumV } /** Decode a Bech32 string. */ -std::tuple Bech32::decode(const std::string& str) { +std::tuple decode(const std::string& str) { if (str.length() > 120 || str.length() < 2) { // too long or too short return std::make_tuple(std::string(), Data(), None); @@ -141,7 +142,7 @@ std::tuple Bech32::decode(const std::string& ok = false; } size_t pos = str.rfind('1'); - if (ok && pos != str.npos && pos >= 1 && pos + 7 <= str.size()) { + if (ok && pos != std::string::npos && pos >= 1 && pos + 7 <= str.size()) { Data values; values.resize(str.size() - 1 - pos); for (size_t i = 0; i < str.size() - 1 - pos; ++i) { @@ -164,3 +165,5 @@ std::tuple Bech32::decode(const std::string& } return std::make_tuple(std::string(), Data(), None); } + +} // namespace TW::Bech32 diff --git a/src/Bech32.h b/src/Bech32.h index 60037fc5794..f9f8311a786 100644 --- a/src/Bech32.h +++ b/src/Bech32.h @@ -5,6 +5,8 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +#pragma once + #include "Data.h" #include diff --git a/src/Bech32Address.cpp b/src/Bech32Address.cpp index d1c36c013b6..da1c7af32a5 100644 --- a/src/Bech32Address.cpp +++ b/src/Bech32Address.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -55,38 +55,16 @@ bool Bech32Address::decode(const std::string& addr, Bech32Address& obj_out, cons return true; } -Bech32Address::Bech32Address(const std::string& hrp, HasherType hasher, const PublicKey& publicKey) +Bech32Address::Bech32Address(const std::string& hrp, Hash::Hasher hasher, const PublicKey& publicKey) : hrp(hrp) { - switch (hasher) { - case HASHER_SHA2_RIPEMD: - { - auto key = Data(20); - ecdsa_get_pubkeyhash(publicKey.compressed().bytes.data(), HASHER_SHA2_RIPEMD, key.data()); - setKey(key); - } - break; - - case HASHER_SHA2: - { - const auto hash = Hash::sha256(publicKey.bytes); - auto key = Data(20); - std::copy(hash.end() - 20, hash.end(), key.begin()); - setKey(key); - } - break; - - case HASHER_SHA3K: - { - const auto hash = publicKey.hash({}, static_cast(Hash::keccak256), true); - auto key = Data(20); - std::copy(hash.end() - 20, hash.end(), key.begin()); - setKey(key); - } - break; - - default: - throw std::invalid_argument("Invalid HasherType in Bech32Address"); + bool skipTypeByte = false; + // Extended-key / keccak-hash skips first byte (Evmos) + if (publicKey.type == TWPublicKeyTypeSECP256k1Extended || hasher == Hash::HasherKeccak256) { + skipTypeByte = true; } + const auto hash = publicKey.hash({}, hasher, skipTypeByte); + auto key = subData(hash, hash.size() - 20, 20); + setKey(key); } std::string Bech32Address::string() const { diff --git a/src/Bech32Address.h b/src/Bech32Address.h index 23b4c06a640..23a88d6a23f 100644 --- a/src/Bech32Address.h +++ b/src/Bech32Address.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -8,7 +8,7 @@ #include "Data.h" #include "PublicKey.h" -#include +#include "Hash.h" #include #include @@ -45,7 +45,7 @@ class Bech32Address { Bech32Address(const std::string& hrp, const Data& keyHash) : hrp(std::move(hrp)), keyHash(std::move(keyHash)) {} /// Initialization from public key --> chain specific hash methods - Bech32Address(const std::string& hrp, HasherType hasher, const PublicKey& publicKey); + Bech32Address(const std::string& hrp, Hash::Hasher hasher, const PublicKey& publicKey); void setHrp(const std::string& hrp_in) { hrp = std::move(hrp_in); } void setKey(const Data& keyHash_in) { keyHash = std::move(keyHash_in); } diff --git a/src/Binance/Address.cpp b/src/Binance/Address.cpp index 481f2eda9ca..67f5bab7559 100644 --- a/src/Binance/Address.cpp +++ b/src/Binance/Address.cpp @@ -1,5 +1,5 @@ // Copyright © 2017 Pieter Wuille -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -10,19 +10,24 @@ #include #include -using namespace TW::Binance; +namespace TW::Binance { -const std::string Address::hrp = HRP_BINANCE; +const std::string Address::_hrp = HRP_BINANCE; const std::string Address::hrpValidator = "bva"; +const std::vector validHrps = {Address::_hrp, Address::hrpValidator, "bnbp", "bvap", "bca", "bcap"}; bool Address::isValid(const std::string& addr) { - std::vector hrps = {hrp, hrpValidator, "bnbp", "bvap", "bca", "bcap"}; - bool result = false; - for (auto& hrp : hrps) { - result = Bech32Address::isValid(addr, hrp); - if (result) { - break; + Address addrNotUsed; + return decode(addr, addrNotUsed); +} + +bool Address::decode(const std::string& addr, Address& obj_out) { + for (const auto& hrp : validHrps) { + if (Bech32Address::decode(addr, obj_out, hrp)) { + return true; } } - return result; + return false; } + +} // namespace TW::Binance diff --git a/src/Binance/Address.h b/src/Binance/Address.h index 2fb7c1fab0d..0735d5db0cf 100644 --- a/src/Binance/Address.h +++ b/src/Binance/Address.h @@ -12,25 +12,23 @@ namespace TW::Binance { -/// Binance address is a Bech32Address, with "bnb" prefix and HASHER_SHA2_RIPEMD hash +/// Binance address is a Bech32Address, with "bnb" prefix and sha256ripemd hash class Address: public Bech32Address { public: - static const std::string hrp; // HRP_BINANCE + static const std::string _hrp; // HRP_BINANCE static const std::string hrpValidator; // HRP_BINANCE static bool isValid(const std::string& addr); - Address() : Bech32Address(hrp) {} + Address() : Bech32Address(_hrp) {} /// Initializes an address with a key hash. - Address(const Data& keyHash) : Bech32Address(hrp, keyHash) {} + Address(const Data& keyHash) : Bech32Address(_hrp, keyHash) {} /// Initializes an address with a public key. - Address(const PublicKey& publicKey) : Bech32Address(hrp, HASHER_SHA2_RIPEMD, publicKey) {} + Address(const PublicKey& publicKey) : Bech32Address(_hrp, Hash::HasherSha256ripemd, publicKey) {} - static bool decode(const std::string& addr, Address& obj_out) { - return Bech32Address::decode(addr, obj_out, hrp); - } + static bool decode(const std::string& addr, Address& obj_out); }; } // namespace TW::Binance diff --git a/src/Binance/Entry.cpp b/src/Binance/Entry.cpp index 3688f01ec81..48d39fc64c2 100644 --- a/src/Binance/Entry.cpp +++ b/src/Binance/Entry.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -6,24 +6,105 @@ #include "Entry.h" +#include "../proto/TransactionCompiler.pb.h" #include "Address.h" #include "Signer.h" -using namespace TW::Binance; -using namespace std; +namespace TW::Binance { -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + Address addr; + if (!Address::decode(address, addr)) { + return {}; + } + return addr.getKeyHash(); +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { +std::string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { return Signer::signJSON(json, key); } + +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + Signer signer(input); + + auto preImageHash = signer.preImageHash(); + auto preImage = signer.signaturePreimage(); + output.set_data_hash(preImageHash.data(), preImageHash.size()); + output.set_data(preImage.data(), preImage.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() == 0 || publicKeys.size() == 0) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return; + } + if (signatures.size() > 1 || publicKeys.size() > 1) { + output.set_error(Common::Proto::Error_no_support_n2n); + output.set_error_message(Common::Proto::SigningError_Name(Common::Proto::Error_no_support_n2n)); + return; + } + output = Signer(input).compile(signatures[0], publicKeys[0]); + }); +} + +Data Entry::buildTransactionInput([[maybe_unused]] TWCoinType coinType, const std::string& from, const std::string& to, const uint256_t& amount, const std::string& asset, const std::string& memo, const std::string& chainId) const { + auto input = Proto::SigningInput(); + input.set_chain_id(chainId); + input.set_account_number(0); + input.set_sequence(0); + input.set_source(0); + input.set_memo(memo); + // do not set private_key! + input.set_private_key(""); + + auto& order = *input.mutable_send_order(); + + Address fromAddress; + if (!Address::decode(from, fromAddress)) { + throw std::invalid_argument("Invalid from address"); + } + const auto fromKeyhash = fromAddress.getKeyHash(); + Address toAddress; + if (!Address::decode(to, toAddress)) { + throw std::invalid_argument("Invalid to address"); + } + const auto toKeyhash = toAddress.getKeyHash(); + + { + auto* sendOrderInputs = order.add_inputs(); + sendOrderInputs->set_address(fromKeyhash.data(), fromKeyhash.size()); + auto* inputCoin = sendOrderInputs->add_coins(); + inputCoin->set_denom(asset); + inputCoin->set_amount(static_cast(amount)); + } + { + auto* output = order.add_outputs(); + output->set_address(toKeyhash.data(), toKeyhash.size()); + auto* outputCoin = output->add_coins(); + outputCoin->set_denom(asset); + outputCoin->set_amount(static_cast(amount)); + } + + const auto txInputData = data(input.SerializeAsString()); + return txInputData; +} + +} // namespace TW::Binance diff --git a/src/Binance/Entry.h b/src/Binance/Entry.h index d9bf47e8bde..da927b68166 100644 --- a/src/Binance/Entry.h +++ b/src/Binance/Entry.h @@ -12,14 +12,18 @@ namespace TW::Binance { /// Binance entry dispatcher. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeBinance}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual bool supportsJSONSigning() const { return true; } - virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool supportsJSONSigning() const { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; + Data buildTransactionInput(TWCoinType coinType, const std::string& from, const std::string& to, const uint256_t& amount, const std::string& asset, const std::string& memo, const std::string& chainId) const; }; } // namespace TW::Binance diff --git a/src/Binance/Serialization.cpp b/src/Binance/Serialization.cpp index 2869341207b..cdf0b83bb58 100644 --- a/src/Binance/Serialization.cpp +++ b/src/Binance/Serialization.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -11,9 +11,7 @@ #include "Ethereum/Address.h" #include "../HexCoding.h" -using namespace TW; -using namespace TW::Binance; -using namespace google::protobuf; +namespace TW::Binance { using json = nlohmann::json; @@ -27,7 +25,7 @@ static inline std::string validatorAddress(const std::string& bytes) { return Bech32Address(Address::hrpValidator, data).string(); } -json Binance::signatureJSON(const Proto::SigningInput& input) { +json signatureJSON(const Proto::SigningInput& input) { json j; j["account_number"] = std::to_string(input.account_number()); j["chain_id"] = input.chain_id(); @@ -39,7 +37,7 @@ json Binance::signatureJSON(const Proto::SigningInput& input) { return j; } -json Binance::orderJSON(const Proto::SigningInput& input) { +json orderJSON(const Proto::SigningInput& input) { json j; if (input.has_trade_order()) { j["id"] = input.trade_order().id(); @@ -98,7 +96,7 @@ json Binance::orderJSON(const Proto::SigningInput& input) { j["type"] = "cosmos-sdk/MsgSideChainDelegate"; j["value"] = { {"delegator_addr", addressString(input.side_delegate_order().delegator_addr())}, - {"validator_addr",validatorAddress(input.side_delegate_order().validator_addr())}, + {"validator_addr", validatorAddress(input.side_delegate_order().validator_addr())}, {"delegation", tokenJSON(input.side_delegate_order().delegation(), true)}, {"side_chain_id", input.side_delegate_order().chain_id()}, }; @@ -131,7 +129,7 @@ json Binance::orderJSON(const Proto::SigningInput& input) { j["description"] = input.time_relock_order().description(); // if amount is empty or omitted, set null to avoid signature verification error j["amount"] = nullptr; - if (amount.size() > 0) { + if (!amount.empty()) { j["amount"] = tokensJSON(amount); } j["lock_time"] = input.time_relock_order().lock_time(); @@ -142,30 +140,26 @@ json Binance::orderJSON(const Proto::SigningInput& input) { return j; } -json Binance::inputsJSON(const Proto::SendOrder& order) { +json inputsJSON(const Proto::SendOrder& order) { json j = json::array(); for (auto& input : order.inputs()) { - j.push_back({ - {"address", addressString(input.address())}, - {"coins", tokensJSON(input.coins())} - }); + j.push_back({{"address", addressString(input.address())}, + {"coins", tokensJSON(input.coins())}}); } return j; } -json Binance::outputsJSON(const Proto::SendOrder& order) { +json outputsJSON(const Proto::SendOrder& order) { json j = json::array(); for (auto& output : order.outputs()) { - j.push_back({ - {"address", addressString(output.address())}, - {"coins", tokensJSON(output.coins())} - }); + j.push_back({{"address", addressString(output.address())}, + {"coins", tokensJSON(output.coins())}}); } return j; } -json Binance::tokenJSON(const Proto::SendOrder_Token& token, bool stringAmount) { - json j = { {"denom", token.denom()} }; +json tokenJSON(const Proto::SendOrder_Token& token, bool stringAmount) { + json j = {{"denom", token.denom()}}; if (stringAmount) { j["amount"] = std::to_string(token.amount()); } else { @@ -174,10 +168,12 @@ json Binance::tokenJSON(const Proto::SendOrder_Token& token, bool stringAmount) return j; } -json Binance::tokensJSON(const RepeatedPtrField& tokens) { +json tokensJSON(const google::protobuf::RepeatedPtrField& tokens) { json j = json::array(); for (auto& token : tokens) { j.push_back(tokenJSON(token)); } return j; } + +} // namespace TW::Binance diff --git a/src/Binance/Signer.cpp b/src/Binance/Signer.cpp index 03736f2dc07..5a06be40181 100644 --- a/src/Binance/Signer.cpp +++ b/src/Binance/Signer.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -6,7 +6,6 @@ #include "Signer.h" #include "Serialization.h" -#include "../Hash.h" #include "../HexCoding.h" #include "../PrivateKey.h" @@ -16,8 +15,7 @@ #include -using namespace TW; -using namespace TW::Binance; +namespace TW::Binance { // Message prefixes // see https://docs.binance.org/api-reference/transactions.html#amino-types @@ -65,10 +63,32 @@ Data Signer::build() const { } Data Signer::sign() const { + auto hash = preImageHash(); auto key = PrivateKey(input.private_key()); - auto hash = Hash::sha256(signaturePreimage()); auto signature = key.sign(hash, TWCurveSECP256k1); - return Data(signature.begin(), signature.end() - 1); + return {signature.begin(), signature.end() - 1}; +} + +Data Signer::preImageHash() const { + return Hash::sha256(signaturePreimage()); +} + +Proto::SigningOutput Signer::compile(const Data& signature, const PublicKey& publicKey) const { + // validate public key + if (publicKey.type != TWPublicKeyTypeSECP256k1) { + throw std::invalid_argument("Invalid public key"); + } + { + // validate correctness of signature + const auto hash = this->preImageHash(); + if (!publicKey.verify(signature, hash)) { + throw std::invalid_argument("Invalid signature/hash/publickey combination"); + } + } + const auto encoded = encodeTransaction(encodeSignature(signature, publicKey)); + auto output = Proto::SigningOutput(); + output.set_encoded(encoded.data(), encoded.size()); + return output; } std::string Signer::signaturePreimage() const { @@ -157,7 +177,10 @@ Data Signer::encodeOrder() const { Data Signer::encodeSignature(const Data& signature) const { auto key = PrivateKey(input.private_key()); auto publicKey = key.getPublicKey(TWPublicKeyTypeSECP256k1); + return encodeSignature(signature, publicKey); +} +Data Signer::encodeSignature(const Data& signature, const PublicKey& publicKey) const { auto encodedPublicKey = pubKeyPrefix; encodedPublicKey.insert(encodedPublicKey.end(), static_cast(publicKey.bytes.size())); encodedPublicKey.insert(encodedPublicKey.end(), publicKey.bytes.begin(), publicKey.bytes.end()); @@ -190,5 +213,7 @@ Data Signer::aminoWrap(const std::string& raw, const Data& typePrefix, bool pref cos.WriteRaw(raw.data(), static_cast(raw.size())); } - return Data(msg.begin(), msg.end()); + return {msg.begin(), msg.end()}; } + +} // namespace TW::Binance diff --git a/src/Binance/Signer.h b/src/Binance/Signer.h index 1539c48d7b7..d66b42bf774 100644 --- a/src/Binance/Signer.h +++ b/src/Binance/Signer.h @@ -7,9 +7,11 @@ #pragma once #include "Data.h" +#include "PublicKey.h" #include "../proto/Binance.pb.h" #include +#include namespace TW::Binance { @@ -24,7 +26,7 @@ class Signer { Proto::SigningInput input; /// Initializes a transaction signer. - explicit Signer(const Proto::SigningInput& input) : input(input) {} + explicit Signer(Proto::SigningInput input) : input(std::move(input)) {} /// Builds a signed transaction. /// @@ -38,11 +40,15 @@ class Signer { /// error. TW::Data sign() const; - private: + TW::Data preImageHash() const; + Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey) const; std::string signaturePreimage() const; + + private: TW::Data encodeTransaction(const TW::Data& signature) const; TW::Data encodeOrder() const; TW::Data encodeSignature(const TW::Data& signature) const; + TW::Data encodeSignature(const TW::Data& signature, const PublicKey& publicKey) const; TW::Data aminoWrap(const std::string& raw, const TW::Data& typePrefix, bool isPrefixLength) const; }; diff --git a/src/BinaryCoding.h b/src/BinaryCoding.h index 233acfaf7e3..88c11ebab31 100644 --- a/src/BinaryCoding.h +++ b/src/BinaryCoding.h @@ -14,6 +14,7 @@ #include #include +//win #ifdef _MSC_VER #define _Nonnull #endif @@ -66,12 +67,12 @@ uint8_t varIntSize(uint64_t value); /// being encoded. It produces fewer bytes for smaller numbers as opposed to a /// fixed-size encoding. Little endian byte order is used. /// -/// @returns the number of bytes written. +/// \returns the number of bytes written. uint8_t encodeVarInt(uint64_t size, std::vector& data); /// Decodes an integer as a variable-length integer. See encodeVarInt(). /// -/// @returns a tuple with a success indicator and the decoded integer. +/// \returns a tuple with a success indicator and the decoded integer. std::tuple decodeVarInt(const Data& in, size_t& indexInOut); /// Encodes a 16-bit big-endian value into the provided buffer. @@ -113,7 +114,7 @@ uint64_t decode64BE(const uint8_t* _Nonnull src); void encodeString(const std::string& str, std::vector& data); /// Decodes an ASCII string prefixed by its length (varInt) -/// @returns a tuple with a success indicator and the decoded string. +/// \returns a tuple with a success indicator and the decoded string. std::tuple decodeString(const Data& in, size_t& indexInOut); } // namespace TW diff --git a/src/Bitcoin/Address.h b/src/Bitcoin/Address.h index 9ab00322ed8..48ae90a8760 100644 --- a/src/Bitcoin/Address.h +++ b/src/Bitcoin/Address.h @@ -7,7 +7,7 @@ #pragma once #include "../Base58Address.h" -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include diff --git a/src/Bitcoin/Amount.h b/src/Bitcoin/Amount.h index 873b9ba871d..d286003d75f 100644 --- a/src/Bitcoin/Amount.h +++ b/src/Bitcoin/Amount.h @@ -15,15 +15,4 @@ namespace TW::Bitcoin { /// Amount in satoshis (can be negative) using Amount = int64_t; -/// One bitcoin in satoshis -static const Amount coin = 100000000; - -/// Maxximum valid amount in satoshis. -static const Amount maxAmount = 21000000 * coin; - -/// Detemines if the provided value is a valid amount. -inline bool isValidAmount(const Amount& amount) { - return (amount >= 0 && amount <= maxAmount); -} - } // namespace TW::Bitcoin diff --git a/src/Bitcoin/CashAddress.cpp b/src/Bitcoin/CashAddress.cpp index f95ef8e8df6..86852f0f472 100644 --- a/src/Bitcoin/CashAddress.cpp +++ b/src/Bitcoin/CashAddress.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -12,67 +12,78 @@ #include #include -#include +#include -using namespace TW::Bitcoin; - -/// Cash address human-readable part -static const std::string cashHRP = "bitcoincash"; +namespace TW::Bitcoin { /// From https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/cashaddr.md +namespace { -static const uint8_t p2khVersion = 0x00; -static const uint8_t p2shVersion = 0x08; +enum class Version : uint8_t { + p2kh = 0x00, + p2sh = 0x08 +}; +constexpr size_t maxHRPSize{20}; +constexpr size_t maxDataSize{104}; -static constexpr size_t maxHRPSize = 20; -static constexpr size_t maxDataSize = 104; +} // namespace -bool CashAddress::isValid(const std::string& string) { - auto withPrefix = string; - if (string.size() < cashHRP.size() || !std::equal(cashHRP.begin(), cashHRP.end(), string.begin())) { - withPrefix = cashHRP + ":" + string; - } +namespace details { - std::array hrp = {0}; - std::array data; - size_t dataLen; - if (cash_decode(hrp.data(), data.data(), &dataLen, withPrefix.c_str()) == 0 || dataLen != CashAddress::size) { - return false; +inline std::string buildPrefix(const std::string& hrp, const std::string& string) noexcept { + if (string.size() < hrp.size() || !std::equal(hrp.begin(), hrp.end(), string.begin())) { + return hrp + ":" + string; } - if (std::strncmp(hrp.data(), cashHRP.c_str(), std::min(cashHRP.size(), maxHRPSize)) != 0) { - return false; - } - return true; + return string; } -CashAddress::CashAddress(const std::string& string) { - auto withPrefix = string; - if (!std::equal(cashHRP.begin(), cashHRP.end(), string.begin())) { - withPrefix = cashHRP + ":" + string; +inline void determinePrefix(TW::Data& data) noexcept { + auto& prefix = data.front(); + switch (static_cast(prefix)) { + case Version::p2kh: + prefix = TW::p2pkhPrefix(TWCoinTypeBitcoinCash); + break; + case Version::p2sh: + prefix = TW::p2shPrefix(TWCoinTypeBitcoinCash); + break; } +} - std::array hrp; - std::array data; +} // namespace details + +bool CashAddress::isValid(const std::string& hrp, const std::string& string) noexcept { + const auto withPrefix = details::buildPrefix(hrp, string); + std::array decodedHRP{0}; + std::array data{}; size_t dataLen; - auto success = cash_decode(hrp.data(), data.data(), &dataLen, withPrefix.c_str()) != 0; - if (!success || std::strncmp(hrp.data(), cashHRP.c_str(), std::min(cashHRP.size(), maxHRPSize)) != 0 || dataLen != CashAddress::size) { - throw std::invalid_argument("Invalid address string"); - } - std::copy(data.begin(), data.begin() + dataLen, bytes.begin()); + const bool decodeValid = + cash_decode(decodedHRP.data(), data.data(), &dataLen, withPrefix.c_str()) != 0 && + dataLen == CashAddress::size; + return decodeValid && + std::string(decodedHRP.data()).compare(0, std::min(hrp.size(), maxHRPSize), hrp) == 0; } -CashAddress::CashAddress(const Data& data) { - if (!isValid(data)) { - throw std::invalid_argument("Invalid address key data"); +CashAddress::CashAddress(const std::string& hrp, const std::string& string) + : hrp(hrp) { + const auto withPrefix = details::buildPrefix(hrp, string); + std::array decodedHRP{}; + std::array data{}; + size_t dataLen; + bool success = cash_decode(decodedHRP.data(), data.data(), &dataLen, withPrefix.c_str()) != 0; + if (!success || + std::string(decodedHRP.data()).compare(0, std::min(hrp.size(), maxHRPSize), hrp) != 0 || + dataLen != CashAddress::size) { + throw std::invalid_argument("Invalid address string"); } - std::copy(data.begin(), data.end(), bytes.begin()); + std::copy(data.begin(), data.begin() + dataLen, bytes.begin()); } -CashAddress::CashAddress(const PublicKey& publicKey) { +CashAddress::CashAddress(std::string hrp, const PublicKey& publicKey) + : hrp(std::move(hrp)) { if (publicKey.type != TWPublicKeyTypeSECP256k1) { throw std::invalid_argument("CashAddress needs a compressed SECP256k1 public key."); } - std::array payload; + std::array payload{}; payload[0] = 0; ecdsa_get_pubkeyhash(publicKey.bytes.data(), HASHER_SHA2_RIPEMD, payload.data() + 1); @@ -83,21 +94,26 @@ CashAddress::CashAddress(const PublicKey& publicKey) { } } -std::string CashAddress::string() const { - std::array result; - cash_encode(result.data(), cashHRP.c_str(), bytes.data(), CashAddress::size); +std::string CashAddress::string() const noexcept { + std::array result{}; + cash_encode(result.data(), hrp.c_str(), bytes.data(), CashAddress::size); return result.data(); } -Address CashAddress::legacyAddress() const { +Address CashAddress::legacyAddress() const noexcept { Data result(Address::size); size_t outlen = 0; cash_data_to_addr(result.data(), &outlen, bytes.data(), CashAddress::size); assert(outlen == 21 && "Invalid length"); - if (result[0] == p2khVersion) { - result[0] = TW::p2pkhPrefix(TWCoinTypeBitcoinCash); - } else if (result[0] == p2shVersion) { - result[0] = TW::p2shPrefix(TWCoinTypeBitcoinCash); - } + details::determinePrefix(result); return Address(result); } + +Data CashAddress::getData() const { + Data data(Address::size); + size_t outlen = 0; + cash_data_to_addr(data.data(), &outlen, bytes.data(), CashAddress::size); + return data; +} + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/CashAddress.h b/src/Bitcoin/CashAddress.h index 089e6cb265d..1ad14b9213f 100644 --- a/src/Bitcoin/CashAddress.h +++ b/src/Bitcoin/CashAddress.h @@ -7,22 +7,30 @@ #pragma once #include "Address.h" +#include "Data.h" #include "../PublicKey.h" -#include "../Data.h" + +#include #include #include namespace TW::Bitcoin { +inline const std::string gBitcoinCashHrp{HRP_BITCOINCASH}; +inline const std::string gECashHrp{HRP_ECASH}; + class CashAddress { - public: +public: /// Number of bytes in an address. - static const size_t size = 34; + static constexpr size_t size{34}; /// Address data consisting of a prefix byte followed by the public key /// hash. - std::array bytes; + std::array bytes{}; + + /// Cash address human-readable part + const std::string hrp; /// Determines whether a collection of bytes makes a valid address. template @@ -31,26 +39,47 @@ class CashAddress { } /// Determines whether a string makes a valid address. - static bool isValid(const std::string& string); + static bool isValid(const std::string& hrp, const std::string& string) noexcept; /// Initializes a address with a string representation. - explicit CashAddress(const std::string& string); - - /// Initializes a address with a collection of bytes. - explicit CashAddress(const Data& data); + explicit CashAddress(const std::string& hrp, const std::string& string); /// Initializes a address with a public key. - explicit CashAddress(const PublicKey& publicKey); + explicit CashAddress(std::string hrp, const PublicKey& publicKey); /// Returns a string representation of the address. - std::string string() const; + [[nodiscard]] std::string string() const noexcept; /// Returns the legacy address representation. - Address legacyAddress() const; + [[nodiscard]] Address legacyAddress() const noexcept; + + Data getData() const; }; -inline bool operator==(const CashAddress& lhs, const CashAddress& rhs) { - return lhs.bytes == rhs.bytes; -} +class BitcoinCashAddress : public CashAddress { +public: + /// Initializes an address with a string representation. + explicit BitcoinCashAddress(const std::string& string) : CashAddress(gBitcoinCashHrp, string) {} + + /// Initializes an address with a public key. + explicit BitcoinCashAddress(const PublicKey& publicKey) : CashAddress(gBitcoinCashHrp, publicKey) {} + + static bool isValid(const std::string& string) noexcept { + return CashAddress::isValid(gBitcoinCashHrp, string); + } +}; + +class ECashAddress : public CashAddress { +public: + /// Initializes an address with a string representation. + explicit ECashAddress(const std::string& string) : CashAddress(gECashHrp, string) {} + + /// Initializes an address with a public key. + explicit ECashAddress(const PublicKey& publicKey) : CashAddress(gECashHrp, publicKey) {} + + static bool isValid(const std::string& string) noexcept { + return CashAddress::isValid(gECashHrp, string); + } +}; } // namespace TW::Bitcoin diff --git a/src/Bitcoin/Entry.cpp b/src/Bitcoin/Entry.cpp index 877e6e3da93..36ee93a94f6 100644 --- a/src/Bitcoin/Entry.cpp +++ b/src/Bitcoin/Entry.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -11,77 +11,178 @@ #include "SegwitAddress.h" #include "Signer.h" -using namespace TW::Bitcoin; -using namespace std; +namespace TW::Bitcoin { -bool Entry::validateAddress(TWCoinType coin, const string& address, byte p2pkh, byte p2sh, const char* hrp) const { +bool Entry::validateAddress(TWCoinType coin, const std::string& address, byte p2pkh, byte p2sh, + const char* hrp) const { switch (coin) { - case TWCoinTypeBitcoin: - case TWCoinTypeDigiByte: - case TWCoinTypeLitecoin: - case TWCoinTypeMonacoin: - case TWCoinTypeQtum: - case TWCoinTypeViacoin: - case TWCoinTypeBitcoinGold: - return SegwitAddress::isValid(address, hrp) - || Address::isValid(address, {{p2pkh}, {p2sh}}); - - case TWCoinTypeBitcoinCash: - return CashAddress::isValid(address) - || Address::isValid(address, {{p2pkh}, {p2sh}}); - - case TWCoinTypeDash: - case TWCoinTypeDogecoin: - case TWCoinTypeRavencoin: - case TWCoinTypeZcoin: - default: - return Address::isValid(address, {{p2pkh}, {p2sh}}); + case TWCoinTypeBitcoin: + case TWCoinTypeDigiByte: + case TWCoinTypeLitecoin: + case TWCoinTypeMonacoin: + case TWCoinTypeQtum: + case TWCoinTypeViacoin: + case TWCoinTypeBitcoinGold: + return SegwitAddress::isValid(address, hrp) || Address::isValid(address, {{p2pkh}, {p2sh}}); + + case TWCoinTypeBitcoinCash: + return BitcoinCashAddress::isValid(address) || Address::isValid(address, {{p2pkh}, {p2sh}}); + + case TWCoinTypeECash: + return ECashAddress::isValid(address) || Address::isValid(address, {{p2pkh}, {p2sh}}); + + case TWCoinTypeDash: + case TWCoinTypeDogecoin: + case TWCoinTypeRavencoin: + case TWCoinTypeFiro: + default: + return Address::isValid(address, {{p2pkh}, {p2sh}}); } } -string Entry::normalizeAddress(TWCoinType coin, const string& address) const { +std::string Entry::normalizeAddress(TWCoinType coin, const std::string& address) const { switch (coin) { - case TWCoinTypeBitcoinCash: - // normalized with bitcoincash: prefix - if (CashAddress::isValid(address)) { - return CashAddress(address).string(); - } else { - return std::string(address); - } + case TWCoinTypeBitcoinCash: + // normalized with bitcoincash: prefix + if (BitcoinCashAddress::isValid(address)) { + return BitcoinCashAddress(address).string(); + } + return address; - default: - // no change - return address; + case TWCoinTypeECash: + // normalized with ecash: prefix + if (ECashAddress::isValid(address)) { + return ECashAddress(address).string(); + } + return address; + + default: + // no change + return address; } } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, byte p2pkh, const char* hrp) const { +std::string Entry::deriveAddress(TWCoinType coin, TWDerivation derivation, const PublicKey& publicKey, + byte p2pkh, const char* hrp) const { switch (coin) { - case TWCoinTypeBitcoin: - case TWCoinTypeDigiByte: - case TWCoinTypeLitecoin: - case TWCoinTypeViacoin: - case TWCoinTypeBitcoinGold: - return SegwitAddress(publicKey, 0, hrp).string(); - - case TWCoinTypeBitcoinCash: - return CashAddress(publicKey).string(); - - case TWCoinTypeDash: - case TWCoinTypeDogecoin: - case TWCoinTypeMonacoin: - case TWCoinTypeQtum: - case TWCoinTypeRavencoin: - case TWCoinTypeZcoin: - default: + case TWCoinTypeBitcoin: + case TWCoinTypeLitecoin: + switch (derivation) { + case TWDerivationBitcoinLegacy: + case TWDerivationLitecoinLegacy: return Address(publicKey, p2pkh).string(); + + case TWDerivationBitcoinTestnet: + return SegwitAddress::createTestnetFromPublicKey(publicKey).string(); + + case TWDerivationBitcoinSegwit: + case TWDerivationDefault: + default: + return SegwitAddress(publicKey, hrp).string(); + } + + case TWCoinTypeDigiByte: + case TWCoinTypeViacoin: + case TWCoinTypeBitcoinGold: + return SegwitAddress(publicKey, hrp).string(); + + case TWCoinTypeBitcoinCash: + return BitcoinCashAddress(publicKey).string(); + + case TWCoinTypeECash: + return ECashAddress(publicKey).string(); + + case TWCoinTypeDash: + case TWCoinTypeDogecoin: + case TWCoinTypeMonacoin: + case TWCoinTypeQtum: + case TWCoinTypeRavencoin: + case TWCoinTypeFiro: + default: + return Address(publicKey, p2pkh).string(); + } +} + +template +inline Data cashAddressToData(const CashAddress&& addr) { + return subData(addr.getData(), 1); +} + +Data Entry::addressToData(TWCoinType coin, const std::string& address) const { + switch (coin) { + case TWCoinTypeBitcoin: + case TWCoinTypeBitcoinGold: + case TWCoinTypeDigiByte: + case TWCoinTypeGroestlcoin: + case TWCoinTypeLitecoin: + case TWCoinTypeViacoin: { + const auto decoded = SegwitAddress::decode(address); + if (!std::get<2>(decoded)) { + return {}; + } + return std::get<0>(decoded).witnessProgram; + } + + case TWCoinTypeBitcoinCash: + return cashAddressToData(BitcoinCashAddress(address)); + + case TWCoinTypeECash: + return cashAddressToData(ECashAddress(address)); + + case TWCoinTypeDash: + case TWCoinTypeDogecoin: + case TWCoinTypeMonacoin: + case TWCoinTypeQtum: + case TWCoinTypeRavencoin: + case TWCoinTypeFiro: { + const auto addr = Address(address); + return {addr.bytes.begin() + 1, addr.bytes.end()}; + } + + default: + return {}; } } -void Entry::sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const Data& dataIn, Data& dataOut) const { signTemplate(dataIn, dataOut); } -void Entry::plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const { +void Entry::plan([[maybe_unused]] TWCoinType coin, const Data& dataIn, Data& dataOut) const { planTemplate(dataIn, dataOut); } + +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](auto&& input, auto&& output) { output = Signer::preImageHashes(input); }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const { + auto txCompilerFunctor = [&signatures, &publicKeys](auto&& input, auto&& output) noexcept { + if (signatures.empty() || publicKeys.empty()) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return; + } + + if (signatures.size() != publicKeys.size()) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("signatures size and publickeys size not equal"); + return; + } + + HashPubkeyList externalSignatures; + auto insertFunctor = [](auto&& signature, auto&& pubkey) noexcept { + return std::make_pair(signature, pubkey.bytes); + }; + transform(begin(signatures), end(signatures), begin(publicKeys), + back_inserter(externalSignatures), insertFunctor); + output = Signer::sign(input, externalSignatures); + }; + + dataOut = txCompilerTemplate(txInputData, + txCompilerFunctor); +} + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/Entry.h b/src/Bitcoin/Entry.h index ccdd4f4932e..713f97685d2 100644 --- a/src/Bitcoin/Entry.h +++ b/src/Bitcoin/Entry.h @@ -11,30 +11,27 @@ namespace TW::Bitcoin { /// Bitcoin entry dispatcher. -/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +/// 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: - virtual const std::vector coinTypes() const { - return { - TWCoinTypeBitcoin, - TWCoinTypeBitcoinCash, - TWCoinTypeBitcoinGold, - TWCoinTypeDash, - TWCoinTypeDigiByte, - TWCoinTypeDogecoin, - TWCoinTypeLitecoin, - TWCoinTypeMonacoin, - TWCoinTypeQtum, - TWCoinTypeRavencoin, - TWCoinTypeViacoin, - TWCoinTypeZcoin, - }; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, + const char* hrp) const; + std::string normalizeAddress(TWCoinType coin, const std::string& address) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, + const char* hrp) const { + return deriveAddress(coin, TWDerivationDefault, publicKey, p2pkh, hrp); } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string normalizeAddress(TWCoinType coin, const std::string& address) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + std::string deriveAddress(TWCoinType coin, TWDerivation derivation, const PublicKey& publicKey, + TW::byte p2pkh, const char* hrp) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const; + // Note: buildTransactionInput is not implemented for Binance chain with UTXOs }; } // namespace TW::Bitcoin diff --git a/src/Bitcoin/FeeCalculator.cpp b/src/Bitcoin/FeeCalculator.cpp index 06a40facdc0..ffae2669621 100644 --- a/src/Bitcoin/FeeCalculator.cpp +++ b/src/Bitcoin/FeeCalculator.cpp @@ -12,25 +12,33 @@ using namespace TW; namespace TW::Bitcoin { -int64_t LinearFeeCalculator::calculate(int64_t inputs, int64_t outputs, int64_t byteFee) const { - const auto txsize = int64_t(std::ceil(bytesPerInput * (double)inputs + bytesPerOutput * (double)outputs + bytesBase)); +constexpr double gDecredBytesPerInput{166}; +constexpr double gDecredBytesPerOutput{38}; +constexpr double gDecredBytesBase{12}; + +int64_t LinearFeeCalculator::calculate(int64_t inputs, int64_t outputs, + int64_t byteFee) const noexcept { + const auto txsize = + static_cast(std::ceil(bytesPerInput * static_cast(inputs) + + bytesPerOutput * static_cast(outputs) + bytesBase)); return txsize * byteFee; } -int64_t LinearFeeCalculator::calculateSingleInput(int64_t byteFee) const { - return int64_t(std::ceil(bytesPerInput)) * byteFee; // std::ceil(101.25) = 102 +int64_t LinearFeeCalculator::calculateSingleInput(int64_t byteFee) const noexcept { + return static_cast(std::ceil(bytesPerInput)) * byteFee; // std::ceil(101.25) = 102 } class DecredFeeCalculator : public LinearFeeCalculator { public: - DecredFeeCalculator(): LinearFeeCalculator(166, 38, 12) {} + constexpr DecredFeeCalculator() noexcept + : LinearFeeCalculator(gDecredBytesPerInput, gDecredBytesPerOutput, gDecredBytesBase) {} }; -DefaultFeeCalculator defaultFeeCalculator; -DecredFeeCalculator decredFeeCalculator; -SegwitFeeCalculator segwitFeeCalculator; +static constexpr DefaultFeeCalculator defaultFeeCalculator{}; +static constexpr DecredFeeCalculator decredFeeCalculator{}; +static constexpr SegwitFeeCalculator segwitFeeCalculator{}; -FeeCalculator& getFeeCalculator(TWCoinType coinType) { +const FeeCalculator& getFeeCalculator(TWCoinType coinType) noexcept { switch (coinType) { case TWCoinTypeDecred: return decredFeeCalculator; diff --git a/src/Bitcoin/FeeCalculator.h b/src/Bitcoin/FeeCalculator.h index 62d1e1504be..49b76b28a75 100644 --- a/src/Bitcoin/FeeCalculator.h +++ b/src/Bitcoin/FeeCalculator.h @@ -10,11 +10,20 @@ namespace TW::Bitcoin { +inline constexpr double gDefaultBytesPerInput{148}; +inline constexpr double gDefaultBytesPerOutput{34}; +inline constexpr double gDefaultBytesBase{10}; +inline constexpr double gSegwitBytesPerInput{101.25}; +inline constexpr double gSegwitBytesPerOutput{31}; +inline constexpr double gSegwitBytesBase{gDefaultBytesBase}; + /// Interface for transaction fee calculator. class FeeCalculator { public: - virtual int64_t calculate(int64_t inputs, int64_t outputs, int64_t byteFee) const = 0; - virtual int64_t calculateSingleInput(int64_t byteFee) const = 0; + virtual ~FeeCalculator() noexcept = default; + [[nodiscard]] virtual int64_t calculate(int64_t inputs, int64_t outputs, + int64_t byteFee) const noexcept = 0; + [[nodiscard]] virtual int64_t calculateSingleInput(int64_t byteFee) const noexcept = 0; }; /// Generic fee calculator with linear input and output size, and a fix size @@ -23,36 +32,43 @@ class LinearFeeCalculator : public FeeCalculator { const double bytesPerInput; const double bytesPerOutput; const double bytesBase; - LinearFeeCalculator(double bytesPerInput, double bytesPerOutput, double bytesBase) - :bytesPerInput(bytesPerInput), bytesPerOutput(bytesPerOutput), bytesBase(bytesBase) {} + explicit constexpr LinearFeeCalculator(double bytesPerInput, double bytesPerOutput, + double bytesBase) noexcept + : bytesPerInput(bytesPerInput), bytesPerOutput(bytesPerOutput), bytesBase(bytesBase) {} - virtual int64_t calculate(int64_t inputs, int64_t outputs, int64_t byteFee) const override; - virtual int64_t calculateSingleInput(int64_t byteFee) const override; + [[nodiscard]] int64_t calculate(int64_t inputs, int64_t outputs, + int64_t byteFee) const noexcept override; + [[nodiscard]] int64_t calculateSingleInput(int64_t byteFee) const noexcept override; }; /// Constant fee calculator class ConstantFeeCalculator : public FeeCalculator { public: const int64_t fee; - ConstantFeeCalculator(int64_t fee) : fee(fee) {} + explicit constexpr ConstantFeeCalculator(int64_t fee) noexcept : fee(fee) {} - virtual int64_t calculate(int64_t inputs, int64_t outputs, int64_t byteFee) const override { return fee; } - virtual int64_t calculateSingleInput(int64_t byteFee) const override { return 0; } + [[nodiscard]] int64_t calculate([[maybe_unused]] int64_t inputs, [[maybe_unused]] int64_t outputs, + [[maybe_unused]] int64_t byteFee) const noexcept final { + return fee; + } + [[nodiscard]] int64_t calculateSingleInput([[maybe_unused]] int64_t byteFee) const noexcept final { return 0; } }; /// Default Bitcoin transaction fee calculator, non-segwit. class DefaultFeeCalculator : public LinearFeeCalculator { public: - DefaultFeeCalculator(): LinearFeeCalculator(148, 34, 10) {} + constexpr DefaultFeeCalculator() noexcept + : LinearFeeCalculator(gDefaultBytesPerInput, gDefaultBytesPerOutput, gDefaultBytesBase) {} }; /// Bitcoin Segwit transaction fee calculator class SegwitFeeCalculator : public LinearFeeCalculator { public: - SegwitFeeCalculator(): LinearFeeCalculator(101.25, 31, 10) {} + constexpr SegwitFeeCalculator() noexcept + : LinearFeeCalculator(gSegwitBytesPerInput, gSegwitBytesPerOutput, gSegwitBytesBase) {} }; /// Return the fee calculator for the given coin. -FeeCalculator& getFeeCalculator(TWCoinType coinType); +const FeeCalculator& getFeeCalculator(TWCoinType coinType) noexcept; } // namespace TW::Bitcoin diff --git a/src/Bitcoin/InputSelector.cpp b/src/Bitcoin/InputSelector.cpp index f643394abf4..9564e67fa20 100644 --- a/src/Bitcoin/InputSelector.cpp +++ b/src/Bitcoin/InputSelector.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,33 +9,36 @@ #include "UTXO.h" #include +#include #include -using namespace TW; -using namespace TW::Bitcoin; - +namespace TW::Bitcoin { template -uint64_t InputSelector::sum(const std::vector& amounts) { +uint64_t InputSelector::sum(const std::vector& amounts) noexcept { uint64_t sum = 0; - for(auto& i: amounts) { + for (auto& i : amounts) { sum += i.amount; } return sum; } template -std::vector InputSelector::filterOutDust(const std::vector& inputs, int64_t byteFee) { +std::vector +InputSelector::filterOutDust(const std::vector& inputs, + int64_t byteFee) noexcept { auto inputFeeLimit = static_cast(feeCalculator.calculateSingleInput(byteFee)); return filterThreshold(inputs, inputFeeLimit); } // Filters utxos that are dust template -std::vector InputSelector::filterThreshold(const std::vector& inputs, uint64_t minimumAmount) { +std::vector +InputSelector::filterThreshold(const std::vector& inputs, + uint64_t minimumAmount) noexcept { std::vector filtered; - for (auto& i: inputs) { - if (i.amount > minimumAmount) { + for (auto& i : inputs) { + if (static_cast(i.amount) > minimumAmount) { filtered.push_back(i); } } @@ -48,9 +51,10 @@ std::vector InputSelector::filterThreshold(const // [[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6], [5, 6, 7], [6, 7, 8], // [7, 8, 9]] template -static inline std::vector> slice(const std::vector& inputs, size_t sliceSize) { +static inline std::vector> +slice(const std::vector& inputs, size_t sliceSize) { std::vector> slices; - for (auto i = 0; i <= inputs.size() - sliceSize; ++i) { + for (auto i = 0ul; i <= inputs.size() - sliceSize; ++i) { slices.emplace_back(); slices[i].reserve(sliceSize); for (auto j = i; j < i + sliceSize; j++) { @@ -61,40 +65,41 @@ static inline std::vector> slice(const std::vector -std::vector InputSelector::select(int64_t targetValue, int64_t byteFee, int64_t numOutputs) { +std::vector +InputSelector::select(uint64_t targetValue, uint64_t byteFee, uint64_t numOutputs) { // if target value is zero, no UTXOs are needed if (targetValue == 0) { return {}; } // total values of utxos should be greater than targetValue - if (inputs.empty() || sum(inputs) < targetValue) { + if (_inputs.empty() || sum(_inputs) < targetValue) { return {}; } - assert(inputs.size() >= 1); + assert(_inputs.size() >= 1); - // definitions for the following caluculation + // definitions for the following calculation const auto doubleTargetValue = targetValue * 2; // Get all possible utxo selections up to a maximum size, sort by total amount, increasing - std::vector sorted = inputs; + std::vector sorted = _inputs; std::sort(sorted.begin(), sorted.end(), - [](const TypeWithAmount& lhs, const TypeWithAmount& rhs) { - return lhs.amount < rhs.amount; - }); + [](const TypeWithAmount& lhs, const TypeWithAmount& rhs) { + return lhs.amount < rhs.amount; + }); // Precompute maximum amount possible to obtain with given number of inputs const auto n = sorted.size(); std::vector maxWithXInputs = std::vector(); maxWithXInputs.push_back(0); int64_t maxSum = 0; - for (auto i = 0; i < n; ++i) { + for (auto i = 0ul; i < n; ++i) { maxSum += sorted[n - 1 - i].amount; maxWithXInputs.push_back(maxSum); } // difference from 2x targetValue - auto distFrom2x = [doubleTargetValue](int64_t val) -> int64_t { + auto distFrom2x = [doubleTargetValue](uint64_t val) -> uint64_t { if (val > doubleTargetValue) { return val - doubleTargetValue; } @@ -118,15 +123,16 @@ std::vector InputSelector::select(int64_t target slices.erase( std::remove_if(slices.begin(), slices.end(), - [targetWithFeeAndDust](const std::vector& slice) { - return sum(slice) < targetWithFeeAndDust; - }), + [targetWithFeeAndDust](const std::vector& slice) { + return sum(slice) < targetWithFeeAndDust; + }), slices.end()); if (!slices.empty()) { std::sort(slices.begin(), slices.end(), - [distFrom2x](const std::vector& lhs, const std::vector& rhs) { - return distFrom2x(sum(lhs)) < distFrom2x(sum(rhs)); - }); + [distFrom2x](const std::vector& lhs, + const std::vector& rhs) { + return distFrom2x(sum(lhs)) < distFrom2x(sum(rhs)); + }); return filterOutDust(slices.front(), byteFee); } } @@ -140,12 +146,11 @@ std::vector InputSelector::select(int64_t target continue; } auto slices = slice(sorted, static_cast(numInputs)); - slices.erase( - std::remove_if(slices.begin(), slices.end(), - [targetWithFee](const std::vector& slice) { - return sum(slice) < targetWithFee; - }), - slices.end()); + slices.erase(std::remove_if(slices.begin(), slices.end(), + [targetWithFee](const std::vector& slice) { + return sum(slice) < targetWithFee; + }), + slices.end()); if (!slices.empty()) { return filterOutDust(slices.front(), byteFee); } @@ -155,25 +160,30 @@ std::vector InputSelector::select(int64_t target } template -std::vector InputSelector::selectSimple(int64_t targetValue, int64_t byteFee, int64_t numOutputs) { +std::vector InputSelector::selectSimple(int64_t targetValue, + int64_t byteFee, + int64_t numOutputs) { // if target value is zero, no UTXOs are needed if (targetValue == 0) { return {}; } - if (inputs.empty()) { + if (_inputs.empty()) { return {}; } - assert(inputs.size() >= 1); + assert(_inputs.size() >= 1); - // target value is larger that original, but not by a factor of 2 (optioized for large UTXO cases) - const auto increasedTargetValue = (uint64_t)((double)targetValue * 1.1 + feeCalculator.calculate(inputs.size(), numOutputs, byteFee) + 1000); + // target value is larger that original, but not by a factor of 2 (optimized for large UTXO + // cases) + const auto increasedTargetValue = + (uint64_t)((double)targetValue * 1.1 + + feeCalculator.calculate(_inputs.size(), numOutputs, byteFee) + 1000); const int64_t dustThreshold = feeCalculator.calculateSingleInput(byteFee); // Go through inputs in a single pass, in the order they appear, no optimization uint64_t sum = 0; std::vector selected; - for (auto& input: inputs) { + for (auto& input : _inputs) { if (input.amount <= dustThreshold) { continue; // skip dust } @@ -190,9 +200,12 @@ std::vector InputSelector::selectSimple(int64_t } template -std::vector InputSelector::selectMaxAmount(int64_t byteFee) { - return filterOutDust(inputs, byteFee); +std::vector +InputSelector::selectMaxAmount(int64_t byteFee) noexcept { + return filterOutDust(_inputs, byteFee); } // Explicitly instantiate template class Bitcoin::InputSelector; + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/InputSelector.h b/src/Bitcoin/InputSelector.h index 027be21f4c2..73298a69729 100644 --- a/src/Bitcoin/InputSelector.h +++ b/src/Bitcoin/InputSelector.h @@ -14,37 +14,46 @@ namespace TW::Bitcoin { -template // TypeWithAmount has to have a uint64_t amount +template // TypeWithAmount has to have an uint64_t amount class InputSelector { - public: +public: /// Selects unspent transactions to use given a target transaction value, using complete logic. /// - /// \returns the list of indices of selected inputs, or an empty list if there are insufficient funds. - std::vector select(int64_t targetValue, int64_t byteFee, int64_t numOutputs = 2); + /// \returns the list of indices of selected inputs, or an empty list if there are insufficient + /// funds. + std::vector select(uint64_t targetValue, uint64_t byteFee, + uint64_t numOutputs = 2); /// Selects unspent transactions to use given a target transaction value; /// Simplified version suitable for large number of inputs /// - /// \returns the list of indices of selected inputs, or an empty list if there are insufficient funds. - std::vector selectSimple(int64_t targetValue, int64_t byteFee, int64_t numOutputs = 2); + /// \returns the list of indices of selected inputs, or an empty list if there are insufficient + /// funds. + std::vector selectSimple(int64_t targetValue, int64_t byteFee, + int64_t numOutputs = 2); - /// Selects UTXOs for max amount; select all except those which would reduce output (dust). Return indIces. - /// One output and no change is assumed. - std::vector selectMaxAmount(int64_t byteFee); + /// Selects UTXOs for max amount; select all except those which would reduce output (dust). + /// Return indices. One output and no change is assumed. + std::vector selectMaxAmount(int64_t byteFee) noexcept; /// Construct, using provided feeCalculator (see getFeeCalculator()). - explicit InputSelector(const std::vector& inputs, const FeeCalculator& feeCalculator) : inputs(inputs), feeCalculator(feeCalculator) {} - InputSelector(const std::vector& inputs) : InputSelector(inputs, getFeeCalculator(TWCoinTypeBitcoin)) {} + explicit InputSelector(const std::vector& inputs, + const FeeCalculator& feeCalculator) noexcept + : _inputs(inputs), feeCalculator(feeCalculator) {} + explicit InputSelector(const std::vector& inputs) noexcept + : InputSelector(inputs, getFeeCalculator(TWCoinTypeBitcoin)) {} /// Sum of input amounts - static uint64_t sum(const std::vector& amounts); + static uint64_t sum(const std::vector& amounts) noexcept; /// Filters out utxos that are dust - std::vector filterOutDust(const std::vector& inputs, int64_t byteFee); - /// Filters out inputs below (or equal) a certain threshold limit - std::vector filterThreshold(const std::vector& inputs, uint64_t minimumAmount); - - private: - const std::vector inputs; + inline std::vector filterOutDust(const std::vector& inputsIn, + int64_t byteFee) noexcept; + /// Filters out inputsIn below (or equal) a certain threshold limit + inline std::vector filterThreshold(const std::vector& inputsIn, + uint64_t minimumAmount) noexcept; + +private: + const std::vector _inputs; const FeeCalculator& feeCalculator; }; diff --git a/src/Bitcoin/MessageSigner.cpp b/src/Bitcoin/MessageSigner.cpp new file mode 100644 index 00000000000..21f3ca90de7 --- /dev/null +++ b/src/Bitcoin/MessageSigner.cpp @@ -0,0 +1,111 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "MessageSigner.h" +#include "Address.h" + +#include "Base64.h" +#include "BinaryCoding.h" +#include "Coin.h" +#include "Data.h" +#include "HexCoding.h" + +using namespace TW; + +namespace TW::Bitcoin { + +// lenght-encode a message string +Data messageToData(const std::string& message) { + Data d; + TW::encodeVarInt(message.size(), d); + TW::append(d, TW::data(message)); + return d; +} + +// append prefix and length-encode message string +Data messageToFullData(const std::string& message) { + Data d = messageToData(MessageSigner::MessagePrefix); + TW::append(d, messageToData(message)); + return d; +} + +Data MessageSigner::messageToHash(const std::string& message) { + Data d = messageToFullData(message); + return Hash::sha256d(d.data(), d.size()); +} + +std::string MessageSigner::signMessage(const PrivateKey& privateKey, const std::string& address, const std::string& message, bool compressed) { + if (!Address::isValid(address)) { + throw std::invalid_argument("Address is not valid (legacy) address"); + } + std::string addrFromKey; + if (compressed) { + const auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + addrFromKey = Address(pubKey, TW::p2pkhPrefix(TWCoinTypeBitcoin)).string(); + } else { + const auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + const auto keyHash = pubKey.hash(Data{TW::p2pkhPrefix(TWCoinTypeBitcoin)}, Hash::HasherSha256ripemd); + addrFromKey = Address(keyHash).string(); + } + if (addrFromKey != address) { + throw std::invalid_argument("Address does not match key"); + } + const auto messageHash = messageToHash(message); + const auto signature = privateKey.sign(messageHash, TWCurveSECP256k1); + + // The V value: add 31 (or 27 for compressed), and move to the first byte + const byte v = signature[SignatureRSLength] + PublicKey::SignatureVOffset + (compressed ? 4ul : 0ul); + auto sigAdjusted = Data{v}; + TW::append(sigAdjusted, TW::subData(signature, 0, SignatureRSLength)); + return Base64::encode(sigAdjusted); +} + +std::string MessageSigner::recoverAddressFromMessage(const std::string& message, const Data& signature) { + if (signature.size() < SignatureRSVLength) { + throw std::invalid_argument("signature too short"); + } + const auto messageHash = MessageSigner::messageToHash(message); + auto recId = signature[0]; + auto compressed = false; + if (recId >= PublicKey::SignatureVOffset + 4) { + recId -= 4; + compressed = true; + } + if (recId >= PublicKey::SignatureVOffset) { + recId -= PublicKey::SignatureVOffset; + } + + const auto publicKeyRecovered = PublicKey::recoverRaw(TW::subData(signature, 1), recId, messageHash); + + if (!compressed) { + // uncompressed public key + const auto keyHash = publicKeyRecovered.hash(Data{TW::p2pkhPrefix(TWCoinTypeBitcoin)}, Hash::HasherSha256ripemd); + return Bitcoin::Address(keyHash).string(); + } + // compressed + const auto publicKeyRecoveredCompressed = publicKeyRecovered.compressed(); + return Bitcoin::Address(publicKeyRecoveredCompressed, TW::p2pkhPrefix(TWCoinTypeBitcoin)).string(); +} + +bool MessageSigner::verifyMessage(const std::string& address, const std::string& message, const std::string& signature) noexcept { + try { + const auto signatureData = Base64::decode(signature); + return verifyMessage(address, message, signatureData); + } catch (...) { + return false; + } +} + +/// May throw +bool MessageSigner::verifyMessage(const std::string& address, const std::string& message, const Data& signature) { + if (!Bitcoin::Address::isValid(address)) { + throw std::invalid_argument("Input address invalid, must be valid legacy"); + } + const auto addressRecovered = recoverAddressFromMessage(message, signature); + return (addressRecovered == address); +} + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/MessageSigner.h b/src/Bitcoin/MessageSigner.h new file mode 100644 index 00000000000..6779bfaf52d --- /dev/null +++ b/src/Bitcoin/MessageSigner.h @@ -0,0 +1,61 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Data.h" +#include "PrivateKey.h" + +#include + +namespace TW::Bitcoin { + +/// Class for message signing and verification. +/// +/// Bitcoin Core and some other wallets support a message signing & verification format, to create a proof (a signature) +/// that someone has access to the private keys of a specific address. +/// This feature currently works on old legacy addresses only. +class MessageSigner { + public: + /// Sign a message. + /// privateKey: the private key used for signing + /// address: the address that matches the privateKey, must be a legacy address (P2PKH) + /// message: A custom message which is input to the signing. + /// compressed: True by default, as addresses are generated from the hash of the compressed public key. + /// However, in some instances key hash is generated from the hash of the extended public key, + /// that's also supported here as well for compatibility. + /// Returns the signature, Base64-encoded. + /// Throws on invalid input. + static std::string signMessage(const PrivateKey& privateKey, const std::string& address, const std::string& message, bool compressed = true); + + /// Verify signature for a message. + /// address: address to use, only legacy is supported + /// message: the message signed (without prefix) + /// signature: in Base64-encoded form. + /// Returns false on any invalid input (does not throw). + static bool verifyMessage(const std::string& address, const std::string& message, const std::string& signature) noexcept; + + /// Verify signature for a message. + /// Address: address to use, only legacy is supported + /// message: the message signed (without prefix) + /// signature: in binary form. + /// May throw + static bool verifyMessage(const std::string& address, const std::string& message, const Data& signature); + + /// Recover address from signature and message. May throw. + static std::string recoverAddressFromMessage(const std::string& message, const Data& signature); + + /// Append prefix and compute hash for a message + static Data messageToHash(const std::string& message); + + static constexpr auto MessagePrefix = "Bitcoin Signed Message:\n"; + static const byte DigestLength = 32; + static const byte SignatureRSLength = 64; + static constexpr byte SignatureRSVLength = SignatureRSLength + 1; + static const byte VOffset = 27; +}; + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/OpCodes.h b/src/Bitcoin/OpCodes.h index f3a4405d697..fa797be4b19 100644 --- a/src/Bitcoin/OpCodes.h +++ b/src/Bitcoin/OpCodes.h @@ -9,139 +9,139 @@ enum OpCode { // push value OP_0 = 0x00, - OP_FALSE = OP_0, + OP_FALSE [[maybe_unused]] = OP_0, OP_PUSHDATA1 = 0x4c, OP_PUSHDATA2 = 0x4d, OP_PUSHDATA4 = 0x4e, - OP_1NEGATE = 0x4f, - OP_RESERVED = 0x50, + OP_1NEGATE [[maybe_unused]] = 0x4f, + OP_RESERVED [[maybe_unused]] = 0x50, OP_1 = 0x51, - OP_TRUE = OP_1, - OP_2 = 0x52, + OP_TRUE [[maybe_unused]] = OP_1, + OP_2 [[maybe_unused]] = 0x52, OP_3 = 0x53, - OP_4 = 0x54, - OP_5 = 0x55, - OP_6 = 0x56, - OP_7 = 0x57, - OP_8 = 0x58, + OP_4 [[maybe_unused]] = 0x54, + OP_5 [[maybe_unused]] = 0x55, + OP_6 [[maybe_unused]] = 0x56, + OP_7 [[maybe_unused]] = 0x57, + OP_8 [[maybe_unused]] = 0x58, OP_9 = 0x59, - OP_10 = 0x5a, - OP_11 = 0x5b, - OP_12 = 0x5c, - OP_13 = 0x5d, - OP_14 = 0x5e, - OP_15 = 0x5f, + OP_10 [[maybe_unused]] = 0x5a, + OP_11 [[maybe_unused]] = 0x5b, + OP_12 [[maybe_unused]] = 0x5c, + OP_13 [[maybe_unused]] = 0x5d, + OP_14 [[maybe_unused]] = 0x5e, + OP_15 [[maybe_unused]] = 0x5f, OP_16 = 0x60, // control - OP_NOP = 0x61, - OP_VER = 0x62, - OP_IF = 0x63, - OP_NOTIF = 0x64, - OP_VERIF = 0x65, - OP_VERNOTIF = 0x66, - OP_ELSE = 0x67, - OP_ENDIF = 0x68, - OP_VERIFY = 0x69, + OP_NOP [[maybe_unused]] = 0x61, + OP_VER [[maybe_unused]] = 0x62, + OP_IF [[maybe_unused]] = 0x63, + OP_NOTIF [[maybe_unused]] = 0x64, + OP_VERIF [[maybe_unused]] = 0x65, + OP_VERNOTIF [[maybe_unused]] = 0x66, + OP_ELSE [[maybe_unused]] = 0x67, + OP_ENDIF [[maybe_unused]] = 0x68, + OP_VERIFY [[maybe_unused]] = 0x69, OP_RETURN = 0x6a, // stack ops - OP_TOALTSTACK = 0x6b, - OP_FROMALTSTACK = 0x6c, - OP_2DROP = 0x6d, - OP_2DUP = 0x6e, - OP_3DUP = 0x6f, - OP_2OVER = 0x70, - OP_2ROT = 0x71, - OP_2SWAP = 0x72, - OP_IFDUP = 0x73, - OP_DEPTH = 0x74, - OP_DROP = 0x75, + OP_TOALTSTACK [[maybe_unused]] = 0x6b, + OP_FROMALTSTACK [[maybe_unused]] = 0x6c, + OP_2DROP [[maybe_unused]] = 0x6d, + OP_2DUP [[maybe_unused]] = 0x6e, + OP_3DUP [[maybe_unused]] = 0x6f, + OP_2OVER [[maybe_unused]] = 0x70, + OP_2ROT [[maybe_unused]] = 0x71, + OP_2SWAP [[maybe_unused]] = 0x72, + OP_IFDUP [[maybe_unused]] = 0x73, + OP_DEPTH [[maybe_unused]] = 0x74, + OP_DROP [[maybe_unused]] = 0x75, OP_DUP = 0x76, - OP_NIP = 0x77, - OP_OVER = 0x78, - OP_PICK = 0x79, - OP_ROLL = 0x7a, - OP_ROT = 0x7b, - OP_SWAP = 0x7c, - OP_TUCK = 0x7d, + OP_NIP [[maybe_unused]] = 0x77, + OP_OVER [[maybe_unused]] = 0x78, + OP_PICK [[maybe_unused]] = 0x79, + OP_ROLL [[maybe_unused]] = 0x7a, + OP_ROT [[maybe_unused]] = 0x7b, + OP_SWAP [[maybe_unused]] = 0x7c, + OP_TUCK [[maybe_unused]] = 0x7d, // splice ops - OP_CAT = 0x7e, - OP_SUBSTR = 0x7f, - OP_LEFT = 0x80, - OP_RIGHT = 0x81, - OP_SIZE = 0x82, + OP_CAT [[maybe_unused]] = 0x7e, + OP_SUBSTR [[maybe_unused]] = 0x7f, + OP_LEFT [[maybe_unused]] = 0x80, + OP_RIGHT [[maybe_unused]] = 0x81, + OP_SIZE [[maybe_unused]] = 0x82, // bit logic - OP_INVERT = 0x83, - OP_AND = 0x84, - OP_OR = 0x85, - OP_XOR = 0x86, + OP_INVERT [[maybe_unused]] = 0x83, + OP_AND [[maybe_unused]] = 0x84, + OP_OR [[maybe_unused]] = 0x85, + OP_XOR [[maybe_unused]] = 0x86, OP_EQUAL = 0x87, OP_EQUALVERIFY = 0x88, - OP_RESERVED1 = 0x89, - OP_RESERVED2 = 0x8a, + OP_RESERVED1 [[maybe_unused]] = 0x89, + OP_RESERVED2 [[maybe_unused]] = 0x8a, // numeric - OP_1ADD = 0x8b, - OP_1SUB = 0x8c, - OP_2MUL = 0x8d, - OP_2DIV = 0x8e, - OP_NEGATE = 0x8f, - OP_ABS = 0x90, - OP_NOT = 0x91, - OP_0NOTEQUAL = 0x92, + OP_1ADD [[maybe_unused]] = 0x8b, + OP_1SUB [[maybe_unused]] = 0x8c, + OP_2MUL [[maybe_unused]] = 0x8d, + OP_2DIV [[maybe_unused]] = 0x8e, + OP_NEGATE [[maybe_unused]] = 0x8f, + OP_ABS [[maybe_unused]] = 0x90, + OP_NOT [[maybe_unused]] = 0x91, + OP_0NOTEQUAL [[maybe_unused]] = 0x92, - OP_ADD = 0x93, - OP_SUB = 0x94, - OP_MUL = 0x95, - OP_DIV = 0x96, - OP_MOD = 0x97, - OP_LSHIFT = 0x98, - OP_RSHIFT = 0x99, + OP_ADD [[maybe_unused]] = 0x93, + OP_SUB [[maybe_unused]] = 0x94, + OP_MUL [[maybe_unused]] = 0x95, + OP_DIV [[maybe_unused]] = 0x96, + OP_MOD [[maybe_unused]] = 0x97, + OP_LSHIFT [[maybe_unused]] = 0x98, + OP_RSHIFT [[maybe_unused]] = 0x99, - OP_BOOLAND = 0x9a, - OP_BOOLOR = 0x9b, - OP_NUMEQUAL = 0x9c, - OP_NUMEQUALVERIFY = 0x9d, - OP_NUMNOTEQUAL = 0x9e, - OP_LESSTHAN = 0x9f, - OP_GREATERTHAN = 0xa0, - OP_LESSTHANOREQUAL = 0xa1, - OP_GREATERTHANOREQUAL = 0xa2, - OP_MIN = 0xa3, - OP_MAX = 0xa4, + OP_BOOLAND [[maybe_unused]] = 0x9a, + OP_BOOLOR [[maybe_unused]] = 0x9b, + OP_NUMEQUAL [[maybe_unused]] = 0x9c, + OP_NUMEQUALVERIFY [[maybe_unused]] = 0x9d, + OP_NUMNOTEQUAL [[maybe_unused]] = 0x9e, + OP_LESSTHAN [[maybe_unused]] = 0x9f, + OP_GREATERTHAN [[maybe_unused]] = 0xa0, + OP_LESSTHANOREQUAL [[maybe_unused]] = 0xa1, + OP_GREATERTHANOREQUAL [[maybe_unused]] = 0xa2, + OP_MIN [[maybe_unused]] = 0xa3, + OP_MAX [[maybe_unused]] = 0xa4, - OP_WITHIN = 0xa5, + OP_WITHIN [[maybe_unused]] = 0xa5, // crypto - OP_RIPEMD160 = 0xa6, - OP_SHA1 = 0xa7, - OP_SHA256 = 0xa8, + OP_RIPEMD160 [[maybe_unused]] = 0xa6, + OP_SHA1 [[maybe_unused]] = 0xa7, + OP_SHA256 [[maybe_unused]] = 0xa8, OP_HASH160 = 0xa9, - OP_HASH256 = 0xaa, - OP_CODESEPARATOR = 0xab, + OP_HASH256 [[maybe_unused]] = 0xaa, + OP_CODESEPARATOR [[maybe_unused]] = 0xab, OP_CHECKSIG = 0xac, - OP_CHECKSIGVERIFY = 0xad, + OP_CHECKSIGVERIFY [[maybe_unused]] = 0xad, OP_CHECKMULTISIG = 0xae, - OP_CHECKMULTISIGVERIFY = 0xaf, + OP_CHECKMULTISIGVERIFY [[maybe_unused]] = 0xaf, // expansion - OP_NOP1 = 0xb0, + OP_NOP1 [[maybe_unused]] = 0xb0, OP_CHECKLOCKTIMEVERIFY = 0xb1, - OP_NOP2 = OP_CHECKLOCKTIMEVERIFY, + OP_NOP2 [[maybe_unused]] = OP_CHECKLOCKTIMEVERIFY, OP_CHECKSEQUENCEVERIFY = 0xb2, - OP_NOP3 = OP_CHECKSEQUENCEVERIFY, - OP_NOP4 = 0xb3, - OP_NOP5 = 0xb4, - OP_NOP6 = 0xb5, - OP_NOP7 = 0xb6, - OP_NOP8 = 0xb7, - OP_NOP9 = 0xb8, - OP_NOP10 = 0xb9, + OP_NOP3 [[maybe_unused]] = OP_CHECKSEQUENCEVERIFY, + OP_NOP4 [[maybe_unused]] = 0xb3, + OP_NOP5 [[maybe_unused]] = 0xb4, + OP_NOP6 [[maybe_unused]] = 0xb5, + OP_NOP7 [[maybe_unused]] = 0xb6, + OP_NOP8 [[maybe_unused]] = 0xb7, + OP_NOP9 [[maybe_unused]] = 0xb8, + OP_NOP10 [[maybe_unused]] = 0xb9, - OP_INVALIDOPCODE = 0xff, + OP_INVALIDOPCODE [[maybe_unused]] = 0xff, }; static inline bool TWOpCodeIsSmallInteger(uint8_t opcode) { diff --git a/src/Bitcoin/OutPoint.cpp b/src/Bitcoin/OutPoint.cpp index 611c837003c..922f23d35de 100644 --- a/src/Bitcoin/OutPoint.cpp +++ b/src/Bitcoin/OutPoint.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -8,10 +8,13 @@ #include "../BinaryCoding.h" -using namespace TW::Bitcoin; +namespace TW::Bitcoin { -void OutPoint::encode(Data& data) const { +void OutPoint::encode(Data& data) const noexcept { std::copy(std::begin(hash), std::end(hash), std::back_inserter(data)); encode32LE(index, data); // sequence is encoded in TransactionInputs } + +} // namespace TW::Bitcoin + diff --git a/src/Bitcoin/OutPoint.h b/src/Bitcoin/OutPoint.h index eb5e61f496d..0abed5a31a1 100644 --- a/src/Bitcoin/OutPoint.h +++ b/src/Bitcoin/OutPoint.h @@ -6,7 +6,8 @@ #pragma once -#include "../Data.h" +#include "algorithm/to_array.h" +#include "Data.h" #include "../proto/Bitcoin.pb.h" #include @@ -16,49 +17,32 @@ namespace TW::Bitcoin { /// Bitcoin transaction out-point reference. -class OutPoint { - public: +struct OutPoint { /// The hash of the referenced transaction. std::array hash; /// The index of the specific output in the transaction. uint32_t index; - /// Sequence number, matches sequence from Proto::OutPoint (not always used, see also TransactionInput.sequence) + /// Sequence number, matches sequence from Proto::OutPoint (not always used, see also + /// TransactionInput.sequence) uint32_t sequence; - OutPoint() = default; + OutPoint() noexcept = default; /// Initializes an out-point reference with hash, index. template - OutPoint(const T& h, uint32_t index, uint32_t sequence = 0 ) { - std::copy(std::begin(h), std::end(h), hash.begin()); - this->index = index; - this->sequence = sequence; - } + OutPoint(const T& h, uint32_t index, uint32_t sequence = 0) noexcept + : hash(to_array(h)), index(index), sequence(sequence) {} /// Initializes an out-point from a Protobuf out-point. - OutPoint(const Proto::OutPoint& other) { + OutPoint(const Proto::OutPoint& other) noexcept + : OutPoint(other.hash(), other.index(), other.sequence()) { assert(other.hash().size() == 32); - std::copy(other.hash().begin(), other.hash().end(), hash.begin()); - index = other.index(); - sequence = other.sequence(); } /// Encodes the out-point into the provided buffer. - void encode(Data& data) const; - - friend bool operator<(const OutPoint& a, const OutPoint& b) { - int cmp = std::memcmp(a.hash.data(), b.hash.data(), 32); - return cmp < 0 || (cmp == 0 && a.index < b.index); - } - - friend bool operator==(const OutPoint& a, const OutPoint& b) { - int cmp = std::memcmp(a.hash.data(), b.hash.data(), 32); - return (cmp == 0 && a.index == b.index); - } - - friend bool operator!=(const OutPoint& a, const OutPoint& b) { return !(a == b); } + void encode(Data& data) const noexcept; Proto::OutPoint proto() const { auto op = Proto::OutPoint(); diff --git a/src/Bitcoin/Script.cpp b/src/Bitcoin/Script.cpp index ca991bc4eed..7000814c396 100644 --- a/src/Bitcoin/Script.cpp +++ b/src/Bitcoin/Script.cpp @@ -1,35 +1,26 @@ -// Copyright © 2017-2021 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "Script.h" - #include "Address.h" #include "CashAddress.h" +#include "OpCodes.h" +#include "Script.h" #include "SegwitAddress.h" -#include "../Base58.h" -#include "../Coin.h" - #include "../BinaryCoding.h" -#include "../Data.h" +#include "../Coin.h" #include "../Decred/Address.h" #include "../Groestlcoin/Address.h" -#include "../Hash.h" -#include "../PublicKey.h" #include "../Zcash/TAddress.h" -#include "OpCodes.h" - #include #include #include -#include -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { Data Script::hash() const { return Hash::ripemd(Hash::sha256(bytes)); @@ -148,8 +139,8 @@ bool Script::matchMultisig(std::vector& keys, int& required) const { return false; } - auto expectedCount = decodeNumber(opcode); - if (keys.size() != expectedCount || expectedCount < required) { + std::size_t expectedCount = decodeNumber(opcode); + if (keys.size() != expectedCount || expectedCount < static_cast(required)) { return false; } if (it + 1 != bytes.size()) { @@ -237,7 +228,7 @@ Script Script::buildPayToScriptHash(const Data& scriptHash) { return script; } -Script Script::buildPayToWitnessProgram(const Data& program) { +Script Script::buildPayToV0WitnessProgram(const Data& program) { assert(program.size() == 20 || program.size() == 32); Script script; script.bytes.push_back(OP_0); @@ -249,12 +240,32 @@ Script Script::buildPayToWitnessProgram(const Data& program) { Script Script::buildPayToWitnessPublicKeyHash(const Data& hash) { assert(hash.size() == 20); - return Script::buildPayToWitnessProgram(hash); + return Script::buildPayToV0WitnessProgram(hash); } Script Script::buildPayToWitnessScriptHash(const Data& scriptHash) { assert(scriptHash.size() == 32); - return Script::buildPayToWitnessProgram(scriptHash); + return Script::buildPayToV0WitnessProgram(scriptHash); +} + +Script Script::buildPayToV1WitnessProgram(const Data& publicKey) { + assert(publicKey.size() == 32); + Script script; + script.bytes.push_back(OP_1); + script.bytes.push_back(static_cast(publicKey.size())); + append(script.bytes, publicKey); + assert(script.bytes.size() == 34); + return script; +} + +Script Script::buildOpReturnScript(const Data& data) { + static const size_t MaxOpReturnLength = 64; + Script script; + script.bytes.push_back(OP_RETURN); + size_t size = std::min(data.size(), MaxOpReturnLength); + script.bytes.push_back(static_cast(size)); + script.bytes.insert(script.bytes.end(), data.begin(), data.begin() + size); + return script; } void Script::encode(Data& data) const { @@ -281,22 +292,31 @@ Script Script::lockScriptForAddress(const std::string& string, enum TWCoinType c return buildPayToScriptHash(data); } } else if (SegwitAddress::isValid(string)) { - auto result = SegwitAddress::decode(string); + const auto result = SegwitAddress::decode(string); // address starts with bc/ltc - auto program = std::get<0>(result).witnessProgram; - return buildPayToWitnessProgram(program); - } else if (CashAddress::isValid(string)) { - auto address = CashAddress(string); + const auto address = std::get<0>(result); + if (address.witnessVersion == 0) { + return buildPayToV0WitnessProgram(address.witnessProgram); + } + if (address.witnessVersion == 1 && address.witnessProgram.size() == 32) { + return buildPayToV1WitnessProgram(address.witnessProgram); + } + } else if (BitcoinCashAddress::isValid(string)) { + auto address = BitcoinCashAddress(string); auto bitcoinAddress = address.legacyAddress(); return lockScriptForAddress(bitcoinAddress.string(), TWCoinTypeBitcoinCash); } else if (Decred::Address::isValid(string)) { - auto bytes = Base58::bitcoin.decodeCheck(string, Hash::blake256d); + auto bytes = Base58::bitcoin.decodeCheck(string, Hash::HasherBlake256d); if (bytes[1] == TW::p2pkhPrefix(TWCoinTypeDecred)) { return buildPayToPublicKeyHash(Data(bytes.begin() + 2, bytes.end())); } if (bytes[1] == TW::p2shPrefix(TWCoinTypeDecred)) { return buildPayToScriptHash(Data(bytes.begin() + 2, bytes.end())); } + } else if (ECashAddress::isValid(string)) { + auto address = ECashAddress(string); + auto bitcoinAddress = address.legacyAddress(); + return lockScriptForAddress(bitcoinAddress.string(), TWCoinTypeECash); } else if (Groestlcoin::Address::isValid(string)) { auto address = Groestlcoin::Address(string); auto data = Data(); @@ -321,3 +341,5 @@ Script Script::lockScriptForAddress(const std::string& string, enum TWCoinType c } return {}; } + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/Script.h b/src/Bitcoin/Script.h index 5890a24093f..39cc5c874fc 100644 --- a/src/Bitcoin/Script.h +++ b/src/Bitcoin/Script.h @@ -6,12 +6,13 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "OpCodes.h" #include #include +#include #include #include @@ -29,8 +30,8 @@ class Script { template Script(It begin, It end) : bytes(begin, end) {} - /// Initializaes a script with a collection of raw bytes by moving. - explicit Script(const Data& bytes) : bytes(bytes) {} + /// Initializes a script with a collection of raw bytes by moving. + explicit Script(Data bytes) : bytes(std::move(bytes)) {} /// Whether the script is empty. bool empty() const { return bytes.empty(); } @@ -47,7 +48,7 @@ class Script { /// Determines whether this is a pay-to-witness-public-key-hash (P2WPKH) script. bool isPayToWitnessPublicKeyHash() const; - /// Determines whether this is a witness programm script. + /// Determines whether this is a witness program script. bool isWitnessProgram() const; /// Matches the script to a pay-to-public-key (P2PK) script. @@ -69,7 +70,7 @@ class Script { bool matchMultisig(std::vector& publicKeys, int& required) const; /// Builds a pay-to-public-key (P2PK) script from a public key. - static Script buildPayToPublicKey(const Data& publickKey); + static Script buildPayToPublicKey(const Data& publicKey); /// Builds a pay-to-public-key-hash (P2PKH) script from a public key hash. static Script buildPayToPublicKeyHash(const Data& hash); @@ -77,9 +78,6 @@ class Script { /// Builds a pay-to-script-hash (P2SH) script from a script hash. static Script buildPayToScriptHash(const Data& scriptHash); - /// Builds a pay-to-witness-program script, P2WSH or P2WPKH. - static Script buildPayToWitnessProgram(const Data& program); - /// Builds a pay-to-witness-public-key-hash (P2WPKH) script from a public /// key hash. static Script buildPayToWitnessPublicKeyHash(const Data& hash); @@ -87,6 +85,15 @@ class Script { /// Builds a pay-to-witness-script-hash (P2WSH) script from a script hash. static Script buildPayToWitnessScriptHash(const Data& scriptHash); + /// Builds a V0 pay-to-witness-program script, P2WSH or P2WPKH. + static Script buildPayToV0WitnessProgram(const Data& program); + + /// Builds a V1 pay-to-witness-program script, P2TR (from a 32-byte Schnorr public key). + static Script buildPayToV1WitnessProgram(const Data& publicKey); + + /// Builds an OP_RETURN script with given data + static Script buildOpReturnScript(const Data& data); + /// Builds a appropriate lock script for the given /// address. static Script lockScriptForAddress(const std::string& address, enum TWCoinType coin); diff --git a/src/Bitcoin/SegwitAddress.cpp b/src/Bitcoin/SegwitAddress.cpp index a658da281cd..4e40ce06ab7 100644 --- a/src/Bitcoin/SegwitAddress.cpp +++ b/src/Bitcoin/SegwitAddress.cpp @@ -1,5 +1,5 @@ // Copyright © 2017 Pieter Wuille -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,9 +9,8 @@ #include "../Bech32.h" #include -#include -using namespace TW::Bitcoin; +namespace TW::Bitcoin { bool SegwitAddress::isValid(const std::string& string) { return std::get<2>(decode(string)); @@ -31,8 +30,8 @@ bool SegwitAddress::isValid(const std::string& string, const std::string& hrp) { return true; } -SegwitAddress::SegwitAddress(const PublicKey& publicKey, int witver, std::string hrp) - : hrp(std::move(hrp)), witnessVersion(witver), witnessProgram() { +SegwitAddress::SegwitAddress(const PublicKey& publicKey, std::string hrp) + : hrp(std::move(hrp)), witnessVersion(0), witnessProgram() { if (publicKey.type != TWPublicKeyTypeSECP256k1) { throw std::invalid_argument("SegwitAddress needs a compressed SECP256k1 public key."); } @@ -73,10 +72,10 @@ std::tuple SegwitAddress::decode(const std::st std::string SegwitAddress::string() const { Data enc; - enc.push_back(static_cast(witnessVersion)); + enc.push_back(witnessVersion); Bech32::convertBits<8, 5, true>(enc, witnessProgram); Bech32::ChecksumVariant variant = Bech32::ChecksumVariant::Bech32; - if (witnessVersion== 0) { + if (witnessVersion == 0) { variant = Bech32::ChecksumVariant::Bech32; } else if (witnessVersion >= 1) { variant = Bech32::ChecksumVariant::Bech32M; @@ -103,3 +102,5 @@ std::pair SegwitAddress::fromRaw(const std::string& hrp, co return std::make_pair(SegwitAddress(hrp, data[0], conv), true); } + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/SegwitAddress.h b/src/Bitcoin/SegwitAddress.h index 68ad227e05a..54803acb7ba 100644 --- a/src/Bitcoin/SegwitAddress.h +++ b/src/Bitcoin/SegwitAddress.h @@ -7,7 +7,7 @@ #pragma once #include "../PublicKey.h" -#include "../Data.h" +#include "Data.h" #include #include @@ -26,11 +26,14 @@ class SegwitAddress { std::string hrp; /// Witness program version. - int witnessVersion; + byte witnessVersion; /// Witness program. Data witnessProgram; + // Prefix for Bitcoin Testnet Segwit addresses + static constexpr auto TestnetPrefix = "tb"; + /// Determines whether a string makes a valid Bech32 address. static bool isValid(const std::string& string); @@ -40,11 +43,15 @@ class SegwitAddress { /// Initializes a Bech32 address with a human-readable part, a witness /// version, and a witness program. - SegwitAddress(std::string hrp, int witver, Data witprog) + SegwitAddress(std::string hrp, byte witver, Data witprog) : hrp(std::move(hrp)), witnessVersion(witver), witnessProgram(std::move(witprog)) {} - /// Initializes a Bech32 address with a public key and a HRP prefix. - SegwitAddress(const PublicKey& publicKey, int witver, std::string hrp); + /// Initializes a segwit-version-0 Bech32 address with a public key and a HRP prefix. + /// Taproot (v>=1) is not supported by this method. + SegwitAddress(const PublicKey& publicKey, std::string hrp); + + /// Create a testnet address + static SegwitAddress createTestnetFromPublicKey(const PublicKey& publicKey) { return SegwitAddress(publicKey, TestnetPrefix); } /// Decodes a SegWit address. /// diff --git a/src/Bitcoin/SigHashType.h b/src/Bitcoin/SigHashType.h index b0540187b00..72329e6a477 100644 --- a/src/Bitcoin/SigHashType.h +++ b/src/Bitcoin/SigHashType.h @@ -17,9 +17,10 @@ static const uint32_t SigHashMask = 0x1f; // Return the default HashType for the given coin, such as TWBitcoinSigHashTypeAll. inline enum TWBitcoinSigHashType hashTypeForCoin(enum TWCoinType coinType) { - // set fork hash type for BCH + // set fork hash type for BCH and XEC switch (coinType) { case TWCoinTypeBitcoinCash: + case TWCoinTypeECash: return (TWBitcoinSigHashType)((uint32_t)TWBitcoinSigHashTypeAll | (uint32_t)TWBitcoinSigHashTypeFork); case TWCoinTypeBitcoinGold: return (TWBitcoinSigHashType)((uint32_t)TWBitcoinSigHashTypeAll | (uint32_t)TWBitcoinSigHashTypeForkBTG); diff --git a/src/Bitcoin/SignatureBuilder.cpp b/src/Bitcoin/SignatureBuilder.cpp index 85398a95016..fe2bb1f6ac3 100644 --- a/src/Bitcoin/SignatureBuilder.cpp +++ b/src/Bitcoin/SignatureBuilder.cpp @@ -1,26 +1,21 @@ -// Copyright © 2017-2021 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. #include "SignatureBuilder.h" - #include "SigHashType.h" #include "TransactionInput.h" #include "TransactionOutput.h" -#include "InputSelector.h" #include "../BinaryCoding.h" -#include "../Hash.h" #include "../HexCoding.h" - #include "../Groestlcoin/Transaction.h" #include "../Zcash/Transaction.h" #include "../Zcash/TransactionBuilder.h" -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { template Result SignatureBuilder::sign() { @@ -28,23 +23,23 @@ Result SignatureBuilder:: // plan with error, fail return Result::failure(plan.error); } - if (transaction.inputs.size() == 0 || plan.utxos.size() == 0) { + if (_transaction.inputs.size() == 0 || plan.utxos.size() == 0) { return Result::failure(Common::Proto::Error_missing_input_utxos); } - transactionToSign = transaction; + transactionToSign = _transaction; transactionToSign.inputs.clear(); - std::copy(std::begin(transaction.inputs), std::end(transaction.inputs), + std::copy(std::begin(_transaction.inputs), std::end(_transaction.inputs), std::back_inserter(transactionToSign.inputs)); const auto hashSingle = hashTypeIsSingle(input.hashType); - for (auto i = 0; i < plan.utxos.size(); i++) { + for (auto i = 0ul; i < plan.utxos.size(); i++) { // Only sign TWBitcoinSigHashTypeSingle if there's a corresponding output - if (hashSingle && i >= transaction.outputs.size()) { + if (hashSingle && i >= _transaction.outputs.size()) { continue; } auto& utxo = plan.utxos[i]; - if (i < transaction.inputs.size()) { + if (i < _transaction.inputs.size()) { auto result = sign(utxo.script, i, utxo); if (!result) { return Result::failure(result.error()); @@ -62,8 +57,8 @@ Result SignatureBuilder:: template Result SignatureBuilder::sign(Script script, size_t index, - const UTXO& utxo) { - assert(index < transaction.inputs.size()); + const UTXO& utxo) { + assert(index < _transaction.inputs.size()); Script redeemScript; std::vector results; @@ -71,7 +66,7 @@ Result SignatureBuilder::sign(Sc uint32_t signatureVersion = [this]() { if ((input.hashType & TWBitcoinSigHashTypeFork) != 0) { return WITNESS_V0; - } + } return BASE; }(); auto result = signStep(script, index, utxo, signatureVersion); @@ -80,15 +75,15 @@ Result SignatureBuilder::sign(Sc } results = result.payload(); assert(results.size() >= 1); - auto txin = transaction.inputs[index]; + auto txin = _transaction.inputs[index]; if (script.isPayToScriptHash()) { script = Script(results[0]); - auto result = signStep(script, index, utxo, signatureVersion); - if (!result) { - return Result::failure(result.error()); + auto signStepResult = signStep(script, index, utxo, signatureVersion); + if (!signStepResult) { + return Result::failure(signStepResult.error()); } - results = result.payload(); + results = signStepResult.payload(); results.push_back(script.bytes); redeemScript = script; } @@ -97,20 +92,20 @@ Result SignatureBuilder::sign(Sc Data data; if (script.matchPayToWitnessPublicKeyHash(data)) { auto witnessScript = Script::buildPayToPublicKeyHash(results[0]); - auto result = signStep(witnessScript, index, utxo, WITNESS_V0); - if (!result) { - return Result::failure(result.error()); + auto _result = signStep(witnessScript, index, utxo, WITNESS_V0); + if (!_result) { + return Result::failure(_result.error()); } - witnessStack = result.payload(); + witnessStack = _result.payload(); results.clear(); } else if (script.matchPayToWitnessScriptHash(data)) { auto witnessScript = Script(results[0]); - auto result = signStep(witnessScript, index, utxo, WITNESS_V0); - if (!result) { - return Result::failure(result.error()); + auto _result = signStep(witnessScript, index, utxo, WITNESS_V0); + if (!_result) { + return Result::failure(_result.error()); } - witnessStack = result.payload(); - witnessStack.push_back(move(witnessScript.bytes)); + witnessStack = _result.payload(); + witnessStack.push_back(std::move(witnessScript.bytes)); results.clear(); } else if (script.isWitnessProgram()) { // Error: Unrecognized witness program. @@ -129,7 +124,7 @@ Result SignatureBuilder::sign(Sc template Result, Common::Proto::SigningError> SignatureBuilder::signStep( - Script script, size_t index, const UTXO& utxo, uint32_t version) const { + Script script, size_t index, const UTXO& utxo, uint32_t version) { Data data; std::vector keys; @@ -156,22 +151,22 @@ Result, Common::Proto::SigningError> SignatureBuilder, Common::Proto::SigningError>::success({data}); } if (script.isWitnessProgram()) { - // Error: Invalid sutput script + // Error: Invalid output script return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_script_output); } if (script.matchMultisig(keys, required)) { auto results = std::vector{{}}; // workaround CHECKMULTISIG bug for (auto& pubKey : keys) { - if (results.size() >= required + 1) { + if (results.size() >= required + 1ul) { break; } auto keyHash = Hash::ripemd(Hash::sha256(pubKey)); auto pair = keyPairForPubKeyHash(keyHash); - if (!pair.has_value() && !estimationMode) { + if (!pair.has_value() && signingMode == SigningMode_Normal) { // Error: missing key return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_missing_private_key); } - auto signature = createSignature(transactionToSign, script, pair, index, utxo.amount, version); + auto signature = createSignature(transactionToSign, script, keyHash, pair, index, utxo.amount, version); if (signature.empty()) { // Error: Failed to sign return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_signing); @@ -184,11 +179,11 @@ Result, Common::Proto::SigningError> SignatureBuilder, Common::Proto::SigningError>::failure(Common::Proto::Error_missing_private_key); } - auto signature = createSignature(transactionToSign, script, pair, index, utxo.amount, version); + auto signature = createSignature(transactionToSign, script, keyHash, pair, index, utxo.amount, version); if (signature.empty()) { // Error: Failed to sign return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_signing); @@ -196,22 +191,35 @@ Result, Common::Proto::SigningError> SignatureBuilder, Common::Proto::SigningError>::success({signature}); } if (script.matchPayToPublicKeyHash(data)) { + // obtain public key auto pair = keyPairForPubKeyHash(data); - if (!pair.has_value() && !estimationMode) { - // Error: Missing keys - return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_missing_private_key); + Data pubkey; + if (!pair.has_value()) { + if (signingMode == SigningMode_SizeEstimationOnly || signingMode == SigningMode_HashOnly) { + // estimation mode, key is missing: use placeholder for public key + pubkey = Data(PublicKey::secp256k1Size); + } else if (signingMode == SigningMode_External) { + size_t _index = hashesForSigning.size(); + if (!externalSignatures.has_value() || externalSignatures.value().size() <= _index) { + // Error: no or not enough signatures provided + return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_signing); + } + pubkey = std::get<1>(externalSignatures.value()[_index]); + } else { + // Error: Missing keys + return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_missing_private_key); + } + } else { + pubkey = std::get<1>(pair.value()).bytes; } - auto signature = createSignature(transactionToSign, script, pair, index, utxo.amount, version); + assert(!pubkey.empty()); + + auto signature = createSignature(transactionToSign, script, data, pair, index, utxo.amount, version); if (signature.empty()) { // Error: Failed to sign return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_signing); } - if (!pair.has_value() && estimationMode) { - // estimation mode, key is missing: use placeholder for public key - return Result, Common::Proto::SigningError>::success({signature, Data(PublicKey::secp256k1Size)}); - } - auto pubkey = std::get<1>(pair.value()); - return Result, Common::Proto::SigningError>::success({signature, pubkey.bytes}); + return Result, Common::Proto::SigningError>::success({signature, pubkey}); } // Error: Invalid output script return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_script_output); @@ -220,21 +228,59 @@ Result, Common::Proto::SigningError> SignatureBuilder Data SignatureBuilder::createSignature( const Transaction& transaction, - const Script& script, + const Script& script, + const Data& publicKeyHash, const std::optional& pair, size_t index, Amount amount, - uint32_t version -) const { - if (estimationMode) { + uint32_t version) { + if (signingMode == SigningMode_SizeEstimationOnly) { // Don't sign, only estimate signature size. It is 71-72 bytes. Return placeholder. return Data(72); } - auto key = std::get<0>(pair.value()); - Data sighash = transaction.getSignatureHash(script, index, input.hashType, amount, - static_cast(version)); - auto pk = PrivateKey(key); - auto sig = pk.signAsDER(sighash, TWCurveSECP256k1); + + const Data sighash = transaction.getSignatureHash(script, index, input.hashType, amount, + static_cast(version)); + + if (signingMode == SigningMode_HashOnly) { + // Don't sign, only store hash-to-be-signed + pubkeyhash. Return placeholder. + hashesForSigning.push_back(std::make_pair(sighash, publicKeyHash)); + return Data(72); + } + + if (signingMode == SigningMode_External) { + // Use externally-provided signature + // Store hash, only for counting + size_t _index = hashesForSigning.size(); + hashesForSigning.push_back(std::make_pair(sighash, publicKeyHash)); + + if (!externalSignatures.has_value() || externalSignatures.value().size() <= _index) { + // Error: no or not enough signatures provided + return {}; + } + + Data externalSignature = std::get<0>(externalSignatures.value()[_index]); + const Data publicKey = std::get<1>(externalSignatures.value()[_index]); + + // Verify provided signature + if (!PublicKey::isValid(publicKey, TWPublicKeyTypeSECP256k1)) { + // Error: invalid public key + return {}; + } + const auto publicKeyObj = PublicKey(publicKey, TWPublicKeyTypeSECP256k1); + if (!publicKeyObj.verifyAsDER(externalSignature, sighash)) { + // Error: Signature does not match publickey+hash + return {}; + } + externalSignature.push_back(static_cast(input.hashType)); + + return externalSignature; + } + + const auto key = std::get<0>(pair.value()); + const auto pk = PrivateKey(key); + + auto sig = pk.signAsDER(sighash); if (!sig.empty()) { sig.push_back(static_cast(input.hashType)); } @@ -293,6 +339,8 @@ Data SignatureBuilder::scriptForScriptHash(const Data& hash) const } // Explicitly instantiate a Signers for compatible transactions. -template class Bitcoin::SignatureBuilder; -template class Bitcoin::SignatureBuilder; -template class Bitcoin::SignatureBuilder; +template class SignatureBuilder; +template class SignatureBuilder; +template class SignatureBuilder; + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/SignatureBuilder.h b/src/Bitcoin/SignatureBuilder.h index 234eac17704..07ad17f6f4c 100644 --- a/src/Bitcoin/SignatureBuilder.h +++ b/src/Bitcoin/SignatureBuilder.h @@ -10,15 +10,28 @@ #include "SigningInput.h" #include "Transaction.h" #include "TransactionInput.h" +#include "Signer.h" #include "../proto/Bitcoin.pb.h" #include "../KeyPair.h" #include "../Result.h" +#include "../PublicKey.h" +#include "../CoinEntry.h" +#include #include #include +#include namespace TW::Bitcoin { +/// Normal and special signature modes +enum SigningMode { + SigningMode_Normal = 0, // normal signing + SigningMode_SizeEstimationOnly, // no signing, only estimate size of the signature + SigningMode_HashOnly, // no signing, only collect hash to be signed + SigningMode_External, // no signing, signatures are provided +}; + /// Class that performs Bitcoin transaction signing. template class SignatureBuilder { @@ -30,18 +43,30 @@ class SignatureBuilder { TransactionPlan plan; /// Transaction being signed. - Transaction transaction; + Transaction _transaction; /// Transaction being signed, with list of signed inputs Transaction transactionToSign; - bool estimationMode = false; + SigningMode signingMode = SigningMode_Normal; + + /// For SigningMode_HashOnly, collect hashes (plus corresponding publickey hashes) here + HashPubkeyList hashesForSigning; + + /// For SigningMode_External, signatures are provided here + std::optional externalSignatures; public: /// Initializes a transaction signer with signing input. /// estimationMode: is set, no real signing is performed, only as much as needed to get the almost-exact signed size - SignatureBuilder(const SigningInput& input, const TransactionPlan& plan, Transaction& transaction, bool estimationMode = false) - : input(input), plan(plan), transaction(transaction), estimationMode(estimationMode) {} + SignatureBuilder( + SigningInput input, + TransactionPlan plan, + Transaction& transaction, + SigningMode signingMode = SigningMode_Normal, + std::optional externalSignatures = {} + ) + : input(std::move(input)), plan(std::move(plan)), _transaction(transaction), signingMode(signingMode), externalSignatures(std::move(externalSignatures)) {} /// Signs the transaction. /// @@ -52,12 +77,16 @@ class SignatureBuilder { // internal, public for testability and Decred static Data pushAll(const std::vector& results); + HashPubkeyList getHashesForSigning() const { return hashesForSigning; } + private: Result sign(Script script, size_t index, const UTXO& utxo); Result, Common::Proto::SigningError> signStep(Script script, size_t index, - const UTXO& utxo, uint32_t version) const; - Data createSignature(const Transaction& transaction, const Script& script, const std::optional&, - size_t index, Amount amount, uint32_t version) const; + const UTXO& utxo, uint32_t version); + + Data createSignature(const Transaction& transaction, const Script& script, + const Data& publicKeyHash, const std::optional& key, + size_t index, Amount amount, uint32_t version); /// Returns the private key for the given public key hash. std::optional keyPairForPubKeyHash(const Data& hash) const; diff --git a/src/Bitcoin/SignatureVersion.h b/src/Bitcoin/SignatureVersion.h index fa3363eec3f..73659efb711 100644 --- a/src/Bitcoin/SignatureVersion.h +++ b/src/Bitcoin/SignatureVersion.h @@ -11,4 +11,4 @@ enum SignatureVersion { BASE, WITNESS_V0 }; -} // TW::Bitcoin namespace +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/Signer.cpp b/src/Bitcoin/Signer.cpp index f0461cfa007..ac00533c2a5 100644 --- a/src/Bitcoin/Signer.cpp +++ b/src/Bitcoin/Signer.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -11,17 +11,18 @@ #include "TransactionBuilder.h" #include "TransactionSigner.h" -using namespace TW; -using namespace TW::Bitcoin; +#include "proto/Common.pb.h" + +namespace TW::Bitcoin { Proto::TransactionPlan Signer::plan(const Proto::SigningInput& input) noexcept { auto plan = TransactionSigner::plan(input); return plan.proto(); } -Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input, std::optional optionalExternalSigs) noexcept { Proto::SigningOutput output; - auto result = TransactionSigner::sign(input); + auto result = TransactionSigner::sign(input, false, optionalExternalSigs); if (!result) { output.set_error(result.error()); return output; @@ -44,3 +45,24 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { output.set_transaction_id(hex(txHash)); return output; } + +Proto::PreSigningOutput Signer::preImageHashes(const Proto::SigningInput& input) noexcept { + Proto::PreSigningOutput output; + auto result = TransactionSigner::preImageHashes(input); + if (!result) { + output.set_error(result.error()); + output.set_error_message(Common::Proto::SigningError_Name(result.error())); + return output; + } + + auto hashList = result.payload(); + auto* hashPubKeys = output.mutable_hash_public_keys(); + for (auto& h : hashList) { + auto* hpk = hashPubKeys->Add(); + hpk->set_data_hash(h.first.data(), h.first.size()); + hpk->set_public_key_hash(h.second.data(), h.second.size()); + } + return output; +} + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/Signer.h b/src/Bitcoin/Signer.h index 81509b49a03..b990d5b7dd2 100644 --- a/src/Bitcoin/Signer.h +++ b/src/Bitcoin/Signer.h @@ -6,9 +6,17 @@ #pragma once #include "../proto/Bitcoin.pb.h" +#include "Data.h" +#include "CoinEntry.h" + +#include +#include +#include namespace TW::Bitcoin { +typedef std::vector> SignaturePubkeyList; + class Signer { public: Signer() = delete; @@ -17,7 +25,10 @@ class Signer { static Proto::TransactionPlan plan(const Proto::SigningInput& input) noexcept; /// Signs a Proto::SigningInput transaction - static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; + static Proto::SigningOutput sign(const Proto::SigningInput& input, std::optional optionalExternalSigs = {}) noexcept; + + /// Collect pre-image hashes to be signed + static Proto::PreSigningOutput preImageHashes(const Proto::SigningInput& input) noexcept; }; } // namespace TW::Bitcoin diff --git a/src/Bitcoin/SigningInput.cpp b/src/Bitcoin/SigningInput.cpp index 2ea2d87c374..a244e62952b 100644 --- a/src/Bitcoin/SigningInput.cpp +++ b/src/Bitcoin/SigningInput.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -6,8 +6,7 @@ #include "SigningInput.h" -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { SigningInput::SigningInput(const Proto::SigningInput& input) { hashType = static_cast(input.hash_type()); @@ -15,19 +14,22 @@ SigningInput::SigningInput(const Proto::SigningInput& input) { byteFee = input.byte_fee(); toAddress = input.to_address(); changeAddress = input.change_address(); - for (auto& key: input.private_key()) { - privateKeys.emplace_back(PrivateKey(key)); + for (auto&& key : input.private_key()) { + privateKeys.emplace_back(key); } - for (auto& script: input.scripts()) { + for (auto&& script : input.scripts()) { scripts[script.first] = Script(script.second.begin(), script.second.end()); } - for (auto& u: input.utxo()) { - utxos.push_back(UTXO(u)); + for (auto&& u : input.utxo()) { + utxos.emplace_back(u); } useMaxAmount = input.use_max_amount(); coinType = static_cast(input.coin_type()); if (input.has_plan()) { plan = TransactionPlan(input.plan()); } + outputOpReturn = data(input.output_op_return()); lockTime = input.lock_time(); } + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/SigningInput.h b/src/Bitcoin/SigningInput.h index 48a69da6719..cc365fd5b49 100644 --- a/src/Bitcoin/SigningInput.h +++ b/src/Bitcoin/SigningInput.h @@ -57,6 +57,8 @@ class SigningInput { // Optional transaction plan std::optional plan; + Data outputOpReturn; + uint32_t lockTime = 0; public: diff --git a/src/Bitcoin/Transaction.cpp b/src/Bitcoin/Transaction.cpp index 8e9b3f5c5c3..152c693d736 100644 --- a/src/Bitcoin/Transaction.cpp +++ b/src/Bitcoin/Transaction.cpp @@ -1,22 +1,19 @@ -// Copyright © 2017-2021 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "SegwitAddress.h" #include "Transaction.h" +#include "SegwitAddress.h" +#include "SignatureVersion.h" #include "SigHashType.h" -#include "../BinaryCoding.h" -#include "../Hash.h" -#include "../Data.h" -#include "SignatureVersion.h" +#include "../BinaryCoding.h" #include -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { Data Transaction::getPreImage(const Script& scriptCode, size_t index, enum TWBitcoinSigHashType hashType, uint64_t amount) const { @@ -25,7 +22,7 @@ Data Transaction::getPreImage(const Script& scriptCode, size_t index, Data data; // Version - encode32LE(version, data); + encode32LE(_version, data); // Input prevouts (none/all, depending on flags) if ((hashType & TWBitcoinSigHashTypeAnyoneCanPay) == 0) { @@ -36,8 +33,8 @@ Data Transaction::getPreImage(const Script& scriptCode, size_t index, } // Input nSequence (none/all, depending on flags) - if ((hashType & TWBitcoinSigHashTypeAnyoneCanPay) == 0 && - !hashTypeIsSingle(hashType) && !hashTypeIsNone(hashType)) { + if ((hashType & TWBitcoinSigHashTypeAnyoneCanPay) == 0 && !hashTypeIsSingle(hashType) && + !hashTypeIsNone(hashType)) { auto hashSequence = getSequenceHash(); std::copy(std::begin(hashSequence), std::end(hashSequence), std::back_inserter(data)); } else { @@ -46,7 +43,7 @@ Data Transaction::getPreImage(const Script& scriptCode, size_t index, // The input being signed (replacing the scriptSig with scriptCode + amount) // The prevout may already be contained in hashPrevout, and the nSequence - // may already be contain in hashSequence. + // may already be contained in hashSequence. reinterpret_cast(inputs[index].previousOutput).encode(data); scriptCode.encode(data); @@ -106,12 +103,18 @@ Data Transaction::getOutputsHash() const { void Transaction::encode(Data& data, enum SegwitFormatMode segwitFormat) const { bool useWitnessFormat = true; switch (segwitFormat) { - case NonSegwit: useWitnessFormat = false; break; - case IfHasWitness: useWitnessFormat = hasWitness(); break; - case Segwit: useWitnessFormat = true; break; + case NonSegwit: + useWitnessFormat = false; + break; + case IfHasWitness: + useWitnessFormat = hasWitness(); + break; + case Segwit: + useWitnessFormat = true; + break; } - encode32LE(version, data); + encode32LE(_version, data); if (useWitnessFormat) { // Use extended format in case witnesses are to be serialized. @@ -145,18 +148,17 @@ void Transaction::encodeWitness(Data& data) const { } bool Transaction::hasWitness() const { - return std::any_of(inputs.begin(), inputs.end(), [](auto& input) { return !input.scriptWitness.empty(); }); + return std::any_of(inputs.begin(), inputs.end(), [](auto& input) { return !input.scriptWitness.empty(); }); } Data Transaction::getSignatureHash(const Script& scriptCode, size_t index, enum TWBitcoinSigHashType hashType, uint64_t amount, enum SignatureVersion version) const { - switch (version) { - case BASE: + if (version == BASE) { return getSignatureHashBase(scriptCode, index, hashType); - case WITNESS_V0: - return getSignatureHashWitnessV0(scriptCode, index, hashType, amount); } + // version == WITNESS_V0 + return getSignatureHashWitnessV0(scriptCode, index, hashType, amount); } /// Generates the signature hash for Witness version 0 scripts. @@ -175,12 +177,12 @@ Data Transaction::getSignatureHashBase(const Script& scriptCode, size_t index, Data data; - encode32LE(version, data); + encode32LE(_version, data); auto serializedInputCount = (hashType & TWBitcoinSigHashTypeAnyoneCanPay) != 0 ? 1 : inputs.size(); encodeVarInt(serializedInputCount, data); - for (auto subindex = 0; subindex < serializedInputCount; subindex += 1) { + for (auto subindex = 0ul; subindex < serializedInputCount; subindex += 1) { serializeInput(subindex, scriptCode, index, hashType, data); } @@ -188,7 +190,7 @@ Data Transaction::getSignatureHashBase(const Script& scriptCode, size_t index, auto hashSingle = hashTypeIsSingle(hashType); auto serializedOutputCount = hashNone ? 0 : (hashSingle ? index + 1 : outputs.size()); encodeVarInt(serializedOutputCount, data); - for (auto subindex = 0; subindex < serializedOutputCount; subindex += 1) { + for (auto subindex = 0ul; subindex < serializedOutputCount; subindex += 1) { if (hashSingle && subindex != index) { auto output = TransactionOutput(-1, {}); output.encode(data); @@ -236,7 +238,7 @@ void Transaction::serializeInput(size_t subindex, const Script& scriptCode, size Proto::Transaction Transaction::proto() const { auto protoTx = Proto::Transaction(); - protoTx.set_version(version); + protoTx.set_version(_version); protoTx.set_locktime(lockTime); for (const auto& input : inputs) { @@ -256,3 +258,5 @@ Proto::Transaction Transaction::proto() const { return protoTx; } + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/Transaction.h b/src/Bitcoin/Transaction.h index e6cb1276d9a..b45fa14c6ab 100644 --- a/src/Bitcoin/Transaction.h +++ b/src/Bitcoin/Transaction.h @@ -14,7 +14,7 @@ #include "UTXO.h" #include "../PrivateKey.h" #include "../Hash.h" -#include "../Data.h" +#include "Data.h" #include "SignatureVersion.h" #include "../proto/Bitcoin.pb.h" @@ -33,7 +33,7 @@ class TransactionOutputs: public std::vector {}; struct Transaction { public: /// Transaction data format version (note, this is signed) - int32_t version = 1; + int32_t _version = 1; /// The block number or timestamp at which this transaction is unlocked /// @@ -53,7 +53,7 @@ struct Transaction { // List of transaction outputs TransactionOutputs outputs; - TW::Hash::Hasher hasher = TW::Hash::sha256d; + TW::Hash::Hasher hasher = TW::Hash::HasherSha256d; /// Used for diagnostics; store previously estimated virtual size (if any; size in bytes) int previousEstimatedVirtualSize = 0; @@ -61,8 +61,8 @@ struct Transaction { public: Transaction() = default; - Transaction(int32_t version, uint32_t lockTime = 0, TW::Hash::Hasher hasher = TW::Hash::sha256d) - : version(version), lockTime(lockTime), inputs(), outputs(), hasher(hasher) {} + Transaction(int32_t version, uint32_t lockTime = 0, TW::Hash::Hasher hasher = TW::Hash::HasherSha256d) + : _version(version), lockTime(lockTime), inputs(), outputs(), hasher(hasher) {} /// Whether the transaction is empty. bool empty() const { return inputs.empty() && outputs.empty(); } @@ -110,8 +110,3 @@ struct Transaction { }; } // namespace TW::Bitcoin - -/// Wrapper for C interface. -struct TWBitcoinTransaction { - TW::Bitcoin::Transaction impl; -}; diff --git a/src/Bitcoin/TransactionBuilder.cpp b/src/Bitcoin/TransactionBuilder.cpp index a9c07a0e295..9e72dee4f5f 100644 --- a/src/Bitcoin/TransactionBuilder.cpp +++ b/src/Bitcoin/TransactionBuilder.cpp @@ -7,9 +7,9 @@ #include "TransactionBuilder.h" #include "Script.h" #include "TransactionSigner.h" +#include "SignatureBuilder.h" #include "../Coin.h" -#include "../proto/Bitcoin.pb.h" #include #include @@ -48,7 +48,7 @@ int64_t estimateSegwitFee(const FeeCalculator& feeCalculator, const TransactionP auto inputWithPlan = std::move(input); inputWithPlan.plan = plan; - auto result = TransactionSigner::sign(inputWithPlan, true); + auto result = TransactionSigner::sign(inputWithPlan, SigningMode_SizeEstimationOnly); if (!result) { // signing failed; return default simple estimate return estimateSimpleFee(feeCalculator, plan, outputSize, input.byteFee); @@ -77,8 +77,16 @@ int64_t estimateSegwitFee(const FeeCalculator& feeCalculator, const TransactionP return fee; } +int extraOutputCount(const SigningInput& input) { + int count = int(input.outputOpReturn.size() > 0); + return count; +} + TransactionPlan TransactionBuilder::plan(const SigningInput& input) { TransactionPlan plan; + if (input.outputOpReturn.size() > 0) { + plan.outputOpReturn = input.outputOpReturn; + } bool maxAmount = input.useMaxAmount; if (input.amount == 0 && !maxAmount) { @@ -93,23 +101,24 @@ TransactionPlan TransactionBuilder::plan(const SigningInput& input) { // select UTXOs plan.amount = input.amount; - // if amount requested is the same or more than available amount, it cannot be satisifed, but + // if amount requested is the same or more than available amount, it cannot be satisfied, but // treat this case as MaxAmount, and send maximum available (which will be less) - if (!maxAmount && input.amount >= inputSum) { + if (!maxAmount && static_cast(input.amount) >= inputSum) { maxAmount = true; } + auto extraOutputs = extraOutputCount(input); auto output_size = 2; UTXOs selectedInputs; if (!maxAmount) { - output_size = 2; // output + change + output_size = 2 + extraOutputs; // output + change if (input.utxos.size() <= SimpleModeLimit && input.utxos.size() <= MaxUtxosHardLimit) { selectedInputs = inputSelector.select(plan.amount, input.byteFee, output_size); } else { selectedInputs = inputSelector.selectSimple(plan.amount, input.byteFee, output_size); } } else { - output_size = 1; // no change + output_size = 1 + extraOutputs; // output, no change selectedInputs = inputSelector.selectMaxAmount(input.byteFee); } if (selectedInputs.size() <= MaxUtxosHardLimit) { @@ -117,7 +126,7 @@ TransactionPlan TransactionBuilder::plan(const SigningInput& input) { } else { // truncate to limit number of selected UTXOs plan.utxos.clear(); - for (auto i = 0; i < MaxUtxosHardLimit; ++i) { + for (auto i = 0ul; i < MaxUtxosHardLimit; ++i) { plan.utxos.push_back(selectedInputs[i]); } } @@ -141,7 +150,7 @@ TransactionPlan TransactionBuilder::plan(const SigningInput& input) { plan.change = 0; } plan.fee = estimateSegwitFee(feeCalculator, plan, output_size, input); - // If fee is larger then availableAmount (can happen in special maxAmount case), we reduce it (and hope it will go through) + // If fee is larger than availableAmount (can happen in special maxAmount case), we reduce it (and hope it will go through) plan.fee = std::min(plan.availableAmount, plan.fee); assert(plan.fee >= 0 && plan.fee <= plan.availableAmount); diff --git a/src/Bitcoin/TransactionBuilder.h b/src/Bitcoin/TransactionBuilder.h index f5b122346a8..b2c9feeb8c0 100644 --- a/src/Bitcoin/TransactionBuilder.h +++ b/src/Bitcoin/TransactionBuilder.h @@ -45,6 +45,12 @@ class TransactionBuilder { tx.inputs.emplace_back(utxo.outPoint, emptyScript, utxo.outPoint.sequence); } + // Optional OP_RETURN output + if (plan.outputOpReturn.size() > 0) { + auto lockingScriptOpReturn = Script::buildOpReturnScript(plan.outputOpReturn); + tx.outputs.emplace_back(0, lockingScriptOpReturn); + } + return tx; } diff --git a/src/Bitcoin/TransactionInput.cpp b/src/Bitcoin/TransactionInput.cpp index 6d59f5536b8..7c43f07e0aa 100644 --- a/src/Bitcoin/TransactionInput.cpp +++ b/src/Bitcoin/TransactionInput.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -8,7 +8,7 @@ #include "../BinaryCoding.h" -using namespace TW::Bitcoin; +namespace TW::Bitcoin { void TransactionInput::encode(Data& data) const { auto& outpoint = reinterpret_cast(previousOutput); @@ -24,3 +24,5 @@ void TransactionInput::encodeWitness(Data& data) const { std::copy(std::begin(item), std::end(item), std::back_inserter(data)); } } + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/TransactionInput.h b/src/Bitcoin/TransactionInput.h index 41e33b0cc91..8a9b28399ae 100644 --- a/src/Bitcoin/TransactionInput.h +++ b/src/Bitcoin/TransactionInput.h @@ -8,7 +8,7 @@ #include "OutPoint.h" #include "Script.h" -#include "../Data.h" +#include "Data.h" #include @@ -45,8 +45,3 @@ class TransactionInput { }; } // namespace TW::Bitcoin - -/// Wrapper for C interface. -struct TWBitcoinTransactionInput { - TW::Bitcoin::TransactionInput impl; -}; diff --git a/src/Bitcoin/TransactionOutput.cpp b/src/Bitcoin/TransactionOutput.cpp index e21b9668737..7f36e02fce7 100644 --- a/src/Bitcoin/TransactionOutput.cpp +++ b/src/Bitcoin/TransactionOutput.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -8,9 +8,11 @@ #include "../BinaryCoding.h" -using namespace TW::Bitcoin; +namespace TW::Bitcoin { void TransactionOutput::encode(Data& data) const { encode64LE(value, data); script.encode(data); } + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/TransactionOutput.h b/src/Bitcoin/TransactionOutput.h index b2264667f56..0a4ac788218 100644 --- a/src/Bitcoin/TransactionOutput.h +++ b/src/Bitcoin/TransactionOutput.h @@ -8,7 +8,7 @@ #include "Amount.h" #include "Script.h" -#include "../Data.h" +#include "Data.h" #include @@ -34,8 +34,3 @@ struct TransactionOutput { }; } // namespace TW::Bitcoin - -/// Wrapper for C interface. -struct TWBitcoinTransactionOutput { - TW::Bitcoin::TransactionOutput impl; -}; diff --git a/src/Bitcoin/TransactionPlan.h b/src/Bitcoin/TransactionPlan.h index 060c4de0852..1225082d307 100644 --- a/src/Bitcoin/TransactionPlan.h +++ b/src/Bitcoin/TransactionPlan.h @@ -8,7 +8,7 @@ #include "Amount.h" #include "UTXO.h" -#include "../Data.h" +#include "Data.h" #include "../proto/Bitcoin.pb.h" namespace TW::Bitcoin { @@ -33,6 +33,8 @@ struct TransactionPlan { /// Zcash branch id Data branchId; + Data outputOpReturn; + Common::Proto::SigningError error = Common::Proto::SigningError::OK; TransactionPlan() = default; @@ -44,6 +46,7 @@ struct TransactionPlan { , change(plan.change()) , utxos(std::vector(plan.utxos().begin(), plan.utxos().end())) , branchId(plan.branch_id().begin(), plan.branch_id().end()) + , outputOpReturn(plan.output_op_return().begin(), plan.output_op_return().end()) , error(plan.error()) {} @@ -57,6 +60,7 @@ struct TransactionPlan { *plan.add_utxos() = utxo.proto(); } plan.set_branch_id(branchId.data(), branchId.size()); + plan.set_output_op_return(outputOpReturn.data(), outputOpReturn.size()); plan.set_error(error); return plan; } diff --git a/src/Bitcoin/TransactionSigner.cpp b/src/Bitcoin/TransactionSigner.cpp index 38a0a672246..0e87be108e8 100644 --- a/src/Bitcoin/TransactionSigner.cpp +++ b/src/Bitcoin/TransactionSigner.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -12,8 +12,7 @@ #include "../Zcash/Transaction.h" #include "../Zcash/TransactionBuilder.h" -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { template TransactionPlan TransactionSigner::plan(const SigningInput& input) { @@ -21,7 +20,7 @@ TransactionPlan TransactionSigner::plan(const S } template -Result TransactionSigner::sign(const SigningInput& input, bool estimationMode) { +Result TransactionSigner::sign(const SigningInput& input, bool estimationMode, std::optional optionalExternalSigs) { TransactionPlan plan; if (input.plan.has_value()) { plan = input.plan.value(); @@ -29,11 +28,33 @@ Result TransactionSigner(plan, input.toAddress, input.changeAddress, input.coinType, input.lockTime); - SignatureBuilder signer(std::move(input), plan, transaction, estimationMode); + SigningMode signingMode = + estimationMode ? SigningMode_SizeEstimationOnly : optionalExternalSigs.has_value() ? SigningMode_External + : SigningMode_Normal; + SignatureBuilder signer(std::move(input), plan, transaction, signingMode, optionalExternalSigs); return signer.sign(); } +template +Result TransactionSigner::preImageHashes(const SigningInput& input) { + TransactionPlan plan; + if (input.plan.has_value()) { + plan = input.plan.value(); + } else { + plan = TransactionBuilder::plan(input); + } + auto transaction = TransactionBuilder::template build(plan, input.toAddress, input.changeAddress, input.coinType, input.lockTime); + SignatureBuilder signer(std::move(input), plan, transaction, SigningMode_HashOnly); + auto signResult = signer.sign(); + if (!signResult) { + return Result::failure(signResult.error()); + } + return Result::success(signer.getHashesForSigning()); +} + // Explicitly instantiate a Signers for compatible transactions. template class Bitcoin::TransactionSigner; template class Bitcoin::TransactionSigner; template class Bitcoin::TransactionSigner; + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/TransactionSigner.h b/src/Bitcoin/TransactionSigner.h index ab290d1294e..f75c88851cb 100644 --- a/src/Bitcoin/TransactionSigner.h +++ b/src/Bitcoin/TransactionSigner.h @@ -9,9 +9,16 @@ #include "SigningInput.h" #include "Transaction.h" #include "TransactionBuilder.h" +#include "Signer.h" +#include "Data.h" #include "../KeyPair.h" #include "../Result.h" #include "../proto/Bitcoin.pb.h" +#include "../CoinEntry.h" + +#include +#include +#include namespace TW::Bitcoin { @@ -23,7 +30,10 @@ class TransactionSigner { static TransactionPlan plan(const SigningInput& input); // Sign an unsigned transaction. Plan it if needed beforehand. - static Result sign(const SigningInput& input, bool estimationMode = false); + static Result sign(const SigningInput& input, bool estimationMode = false, std::optional optionalExternalSigs = {}); + + /// Collect pre-image hashes to be signed + static Result preImageHashes(const SigningInput& input); }; } // namespace TW::Bitcoin diff --git a/src/Cardano/AddressV2.cpp b/src/Cardano/AddressV2.cpp index 5d26a9c2d7d..6d2140ab82f 100644 --- a/src/Cardano/AddressV2.cpp +++ b/src/Cardano/AddressV2.cpp @@ -1,47 +1,42 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. #include "AddressV2.h" -#include "../Cbor.h" -#include "../Data.h" #include "../Base58.h" +#include "../Cbor.h" #include "../Crc.h" -#include "../HexCoding.h" -#include "../Hash.h" #include -using namespace TW; -using namespace TW::Cardano; -using namespace std; +namespace TW::Cardano { bool AddressV2::parseAndCheck(const std::string& addr, Data& root_out, Data& attrs_out, byte& type_out) { // Decode Bas58, decode payload + crc, decode root, attr Data base58decoded = Base58::bitcoin.decode(addr); - if (base58decoded.size() == 0) { - throw invalid_argument("Invalid address: could not Base58 decode"); + if (base58decoded.empty()) { + throw std::invalid_argument("Invalid address: could not Base58 decode"); } auto elems = Cbor::Decode(base58decoded).getArrayElements(); if (elems.size() < 2) { - throw invalid_argument("Could not parse address payload from CBOR data"); + throw std::invalid_argument("Could not parse address payload from CBOR data"); } auto tag = elems[0].getTagValue(); if (tag != PayloadTag) { - throw invalid_argument("wrong tag value"); + throw std::invalid_argument("wrong tag value"); } Data payload = elems[0].getTagElement().getBytes(); uint64_t crcPresent = (uint32_t)elems[1].getValue(); uint32_t crcComputed = TW::Crc::crc32(payload); if (crcPresent != crcComputed) { - throw invalid_argument("CRC mismatch"); + throw std::invalid_argument("CRC mismatch"); } // parse payload, 3 elements auto payloadElems = Cbor::Decode(payload).getArrayElements(); if (payloadElems.size() < 3) { - throw invalid_argument("Could not parse address root and attrs from CBOR data"); + throw std::invalid_argument("Could not parse address root and attrs from CBOR data"); } root_out = payloadElems[0].getBytes(); attrs_out = payloadElems[1].encoded(); // map, but encoded as bytes @@ -54,10 +49,12 @@ bool AddressV2::isValid(const std::string& string) { Data root; Data attrs; byte type = 0; - if (!parseAndCheck(string, root, attrs, type)) { return false; } + if (!parseAndCheck(string, root, attrs, type)) { + return false; + } // valid return true; - } catch (exception& ex) { + } catch (std::exception& ex) { return false; } } @@ -71,18 +68,18 @@ AddressV2::AddressV2(const std::string& string) { AddressV2::AddressV2(const PublicKey& publicKey) { // input is extended pubkey, 64-byte - if (publicKey.type != TWPublicKeyTypeED25519Extended) { + if (publicKey.type != TWPublicKeyTypeED25519Cardano || publicKey.bytes.size() != PublicKey::cardanoKeySize) { throw std::invalid_argument("Invalid public key type"); } type = 0; // public key - root = keyHash(publicKey.bytes); + root = keyHash(subData(publicKey.bytes, 0, 64)); // address attributes: empty map for V2, for V1 encrypted derivation path Cbor::Encode emptyMap = Cbor::Encode::map({}); attrs = emptyMap.encoded(); } Data AddressV2::getCborData() const { - // put together string represenatation, CBOR representation + // put together string representation, CBOR representation // inner data: pubkey, attrs, type auto cbor1 = Cbor::Encode::array({ Cbor::Encode::bytes(root), @@ -90,8 +87,8 @@ Data AddressV2::getCborData() const { Cbor::Encode::uint(type), }); auto payloadData = cbor1.encoded(); - - // crc checksum + + // crc checksum auto crc = TW::Crc::crc32(payloadData); // second pack: tag, base, crc auto cbor2 = Cbor::Encode::array({ @@ -101,25 +98,29 @@ Data AddressV2::getCborData() const { return cbor2.encoded(); } -string AddressV2::string() const { +std::string AddressV2::string() const { // Base58 encode the CBOR data return Base58::bitcoin.encode(getCborData()); } Data AddressV2::keyHash(const TW::Data& xpub) { - if (xpub.size() != 64) { throw invalid_argument("invalid xbub length"); } - // hash of follwoing Cbor-array: [0, [0, xbub], {} ] + if (xpub.size() != 64) { + throw std::invalid_argument("invalid xpub length"); + } + // hash of following Cbor-array: [0, [0, xpub], {} ] // 3rd entry map is empty map for V2, contains derivation path for V1 + // clang-format off Data cborData = Cbor::Encode::array({ Cbor::Encode::uint(0), - Cbor::Encode::array({ - Cbor::Encode::uint(0), - Cbor::Encode::bytes(xpub) - }), + Cbor::Encode::array({Cbor::Encode::uint(0), + Cbor::Encode::bytes(xpub)}), Cbor::Encode::map({}), }).encoded(); + // clang-format on // SHA3 hash, then blake Data firstHash = Hash::sha3_256(cborData); Data blake = Hash::blake2b(firstHash, 28); return blake; } + +} // namespace TW::Cardano diff --git a/src/Cardano/AddressV2.h b/src/Cardano/AddressV2.h index 0f3d6ef5a62..929a52356f9 100644 --- a/src/Cardano/AddressV2.h +++ b/src/Cardano/AddressV2.h @@ -20,7 +20,7 @@ namespace TW::Cardano { * Derivation is BIP39, default derivation path is "m/44'/1815'/0'/0/0", with last element being the account number. * Curve is ED25519 with special variations, custom logic in HDWallet and TrezorCrypto lib. * Private key is ED25519: 32-byte PK is extended with 32-byte extra extension, and 32-byte chain code. - * Private key is derived from mnemonic raw entropy (not seed, as in other cases); 96-byte secret is generated (pk, extrension, and chain code). + * Private key is derived from mnemonic raw entropy (not seed, as in other cases); 96-byte secret is generated (pk, extension, and chain code). * Public key is 64-byte: the 32-byte ED25519 public key plus the 32-byte chain code of the PK. * Address derivation: Only V2, type 0 (=public key) addresses are generated. * - CBOR binary scheme is used inside addresses. @@ -46,7 +46,7 @@ class AddressV2 { Data attrs; /// Type; 0: public key. - TW::byte type; + TW::byte type{}; static const TW::byte PayloadTag = 24; @@ -77,8 +77,3 @@ inline bool operator==(const AddressV2& lhs, const AddressV2& rhs) { } } // namespace TW::Cardano - -/// Wrapper for C interface. -struct TWCardanoAddressV2 { - TW::Cardano::AddressV2 impl; -}; diff --git a/src/Cardano/AddressV3.cpp b/src/Cardano/AddressV3.cpp index a75e5ce55ac..44c34ab0352 100644 --- a/src/Cardano/AddressV3.cpp +++ b/src/Cardano/AddressV3.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -7,23 +7,50 @@ #include "AddressV3.h" #include "AddressV2.h" #include -#include "../Data.h" #include "../Bech32.h" #include "../Base32.h" -#include "../Crc.h" #include "../HexCoding.h" -#include "../Hash.h" #include -using namespace TW; -using namespace TW::Cardano; -using namespace std; +namespace TW::Cardano { -bool AddressV3::parseAndCheckV3(const std::string& addr, Discrimination& discrimination, Kind& kind, Data& key1, Data& key2) { +bool AddressV3::checkLength(Kind kind, size_t length) noexcept { + switch (kind) { + case Kind_Base: + return (length == EncodedSize2); + + case Kind_Enterprise: + case Kind_Reward: + return (length == EncodedSize1); + + default: + // accept other types as well + return true; + } +} + +bool AddressV3::parseAndCheckV3(const Data& raw, NetworkId& networkId, Kind& kind, Data& bytes) noexcept { + if (raw.empty()) { + // too short, cannot extract kind and networkId + return false; + } + kind = kindFromFirstByte(raw[0]); + networkId = networkIdFromFirstByte(raw[0]); + if (networkId != Network_Production) { + return false; + } + + bytes = Data(); + std::copy(cbegin(raw) + 1, cend(raw), std::back_inserter(bytes)); + + return checkLength(kind, raw.size()); +} + +bool AddressV3::parseAndCheckV3(const std::string& addr, NetworkId& networkId, Kind& kind, Data& bytes) noexcept { try { auto bech = Bech32::decode(addr); - if (std::get<1>(bech).size() == 0) { + if (std::get<1>(bech).empty()) { // empty Bech data return false; } @@ -33,95 +60,86 @@ bool AddressV3::parseAndCheckV3(const std::string& addr, Discrimination& discrim if (!success) { return false; } - if (conv.size() != 33 && conv.size() != 65) { - return false; - } - discrimination = (Discrimination)((conv[0] & 0b10000000) >> 7); - kind = (Kind)(conv[0] & 0b01111111); - if (kind <= Kind_Sentinel_Low || kind >= Kind_Sentinel_High) { + + if (!parseAndCheckV3(conv, networkId, kind, bytes)) { return false; } - if ((kind == Kind_Group && conv.size() != 65) || - (kind != Kind_Group && conv.size() != 33)) { + + // check prefix + if (const auto expectedHrp = getHrp(kind); !addr.starts_with(expectedHrp)) { return false; } - switch (kind) { - case Kind_Single: - case Kind_Account: - case Kind_Multisig: - assert(conv.size() == 33); - key1 = Data(32); - std::copy(conv.begin() + 1, conv.begin() + 33, key1.begin()); - return true; - - case Kind_Group: - assert(conv.size() == 65); - key1 = Data(32); - key2 = Data(32); - std::copy(conv.begin() + 1, conv.begin() + 33, key1.begin()); - std::copy(conv.begin() + 33, conv.begin() + 65, key2.begin()); - return true; - - default: - return false; - } + return true; } catch (...) { return false; } } bool AddressV3::isValid(const std::string& addr) { - Discrimination discrimination; + NetworkId networkId; Kind kind; - Data key1; - Data key2; - if (parseAndCheckV3(addr, discrimination, kind, key1, key2)) { + Data key; + return parseAndCheckV3(addr, networkId, kind, key); +} + +bool AddressV3::isValidLegacy(const std::string& addr) { + if (isValid(addr)) { return true; } // not V3, try older return AddressV2::isValid(addr); } -AddressV3 AddressV3::createSingle(Discrimination discrimination_in, const Data& spendingKey) { - if (spendingKey.size() != 32) { - throw std::invalid_argument("Wrong spending key size"); +Data blakeHash(Data d) { + assert(d.size() == 32); + return Hash::blake2b(d.data(), d.size(), AddressV3::HashSize); +} + +AddressV3 AddressV3::createBase(NetworkId networkId, const TW::Data& spendingKeyHash, const TW::Data& stakingKeyHash) { + if (spendingKeyHash.size() != HashSize) { + throw std::invalid_argument("Wrong spending key hash size"); + } + if (stakingKeyHash.size() != HashSize) { + throw std::invalid_argument("Wrong spending key hash size"); } auto addr = AddressV3(); - addr.discrimination = discrimination_in; - addr.kind = Kind_Single; - addr.key1 = spendingKey; + addr.networkId = networkId; + addr.kind = Kind_Base; + + addr.bytes = Data(); + append(addr.bytes, spendingKeyHash); + append(addr.bytes, stakingKeyHash); + return addr; } -AddressV3 AddressV3::createGroup(Discrimination discrimination_in, const Data& spendingKey, const Data& groupKey) { - if (spendingKey.size() != 32) { +AddressV3 AddressV3::createBase(NetworkId networkId, const PublicKey& spendingKey, const PublicKey& stakingKey) { + if (spendingKey.bytes.size() != 32) { throw std::invalid_argument("Wrong spending key size"); } - if (groupKey.size() != 32) { - throw std::invalid_argument("Wrong group key size"); + if (stakingKey.bytes.size() != 32) { + throw std::invalid_argument("Wrong spending key size"); } - auto addr = AddressV3(); - addr.discrimination = discrimination_in; - addr.kind = Kind_Group; - addr.key1 = spendingKey; - addr.groupKey = groupKey; - return addr; + + const Data hash1 = blakeHash(spendingKey.bytes); + const Data hash2 = blakeHash(stakingKey.bytes); + return createBase(networkId, hash1, hash2); } -AddressV3 AddressV3::createAccount(Discrimination discrimination_in, const Data& accountKey) { - if (accountKey.size() != 32) { - throw std::invalid_argument("Wrong spending key size"); +AddressV3 AddressV3::createReward(NetworkId networkId, const TW::Data& stakingKeyHash) { + if (stakingKeyHash.size() != HashSize) { + throw std::invalid_argument("Wrong spending key hash size"); } auto addr = AddressV3(); - addr.discrimination = discrimination_in; - addr.kind = Kind_Account; - addr.key1 = accountKey; + addr.networkId = networkId; + addr.kind = Kind_Reward; + addr.bytes = stakingKeyHash; return addr; } AddressV3::AddressV3(const std::string& addr) { - if (parseAndCheckV3(addr, discrimination, kind, key1, groupKey)) { + if (parseAndCheckV3(addr, networkId, kind, bytes)) { // values stored return; } @@ -131,109 +149,80 @@ AddressV3::AddressV3(const std::string& addr) { } AddressV3::AddressV3(const PublicKey& publicKey) { - // input is extended pubkey, 64-byte - if (publicKey.type != TWPublicKeyTypeED25519Extended) { + // input is double extended pubkey + if (publicKey.type != TWPublicKeyTypeED25519Cardano || publicKey.bytes.size() != PublicKey::cardanoKeySize) { throw std::invalid_argument("Invalid public key type"); } - discrimination = Discrim_Test; - kind = Kind_Group; - key1 = Data(32); - groupKey = Data(32); - std::copy(publicKey.bytes.begin(), publicKey.bytes.begin() + 32, key1.begin()); - std::copy(publicKey.bytes.begin() + 32, publicKey.bytes.begin() + 64, groupKey.begin()); + *this = createBase(Network_Production, PublicKey(subData(publicKey.bytes, 0, 32), TWPublicKeyTypeED25519), PublicKey(subData(publicKey.bytes, 64, 32), TWPublicKeyTypeED25519)); } AddressV3::AddressV3(const Data& data) { - // min 4 bytes, 2 prefix + 2 len - if (data.size() < 4) { throw std::invalid_argument("Address data too short"); } - assert(data.size() >= 4); - int index = 0; - discrimination = (Discrimination)data[index++]; - kind = (Kind)data[index++]; - // key1: - byte len1 = data[index++]; - if (data.size() < 4 + len1) { throw std::invalid_argument("Address data too short"); } - assert(data.size() >= 4 + len1); - key1 = Data(len1); - std::copy(data.begin() + index, data.begin() + index + len1, key1.begin()); - index += len1; - // groupKey: - byte len2 = data[index++]; - if (data.size() < 4 + len1 + len2) { throw std::invalid_argument("Address data too short"); } - assert(data.size() >= 4 + len1 + len2); - groupKey = Data(len2); - std::copy(data.begin() + index, data.begin() + index + len2, groupKey.begin()); + parseAndCheckV3(data, networkId, kind, bytes); } AddressV3::AddressV3(const AddressV3& other) = default; -void AddressV3::operator=(const AddressV3& other) -{ - discrimination = other.discrimination; - kind = other.kind; - key1 = other.key1; - groupKey = other.groupKey; - legacyAddressV2 = other.legacyAddressV2; +uint8_t AddressV3::firstByte(NetworkId networkId, Kind kind) { + byte first = (byte)(((byte)kind << 4) + networkId); + return first; } -string AddressV3::string() const { - std::string hrp; +AddressV3::NetworkId AddressV3::networkIdFromFirstByte(uint8_t first) { + return (NetworkId)(first & 0x0F); +} + +AddressV3::Kind AddressV3::kindFromFirstByte(uint8_t first) { + return (Kind)((first & 0xF0) >> 4); +} + +std::string AddressV3::getHrp(Kind kind) noexcept { switch (kind) { - case Kind_Single: - case Kind_Group: - case Kind_Account: - hrp = stringForHRP(TWHRPCardano); break; - default: - hrp = ""; break; + case Kind_Base: + case Kind_Enterprise: + default: + return stringForHRP(TWHRPCardano); + case Kind_Reward: + return "stake"; } +} + +std::string AddressV3::string() const { + const auto hrp = getHrp(kind); return string(hrp); } -string AddressV3::string(const std::string& hrp) const { +std::string AddressV3::string(const std::string& hrp) const { if (legacyAddressV2.has_value()) { return legacyAddressV2->string(); } - byte first = (byte)kind; - if (discrimination == Discrim_Test) first = first | 0b10000000; - Data keys; - TW::append(keys, first); - TW::append(keys, key1); - if (groupKey.size() > 0) { - TW::append(keys, groupKey); - } + const Data raw = data(); // bech Data bech; - if (!Bech32::convertBits<8, 5, true>(bech, keys)) { + if (!Bech32::convertBits<8, 5, true>(bech, raw)) { return ""; } return Bech32::encode(hrp, bech, Bech32::ChecksumVariant::Bech32); } -string AddressV3::stringBase32() const { +Data AddressV3::data() const noexcept { if (legacyAddressV2.has_value()) { - return legacyAddressV2->string(); + return legacyAddressV2->getCborData(); } - byte first = (byte)kind; - if (discrimination == Discrim_Test) first = first | 0b10000000; - Data keys; - TW::append(keys, first); - TW::append(keys, key1); - if (groupKey.size() > 0) { - TW::append(keys, groupKey); - } - std::string base32 = Base32::encode(keys, "abcdefghijklmnopqrstuvwxyz23456789"); - return base32; + const byte first = firstByte(networkId, kind); + Data raw; + TW::append(raw, first); + TW::append(raw, bytes); + return raw; } -Data AddressV3::data() const { - Data data; - TW::append(data, (uint8_t)discrimination); - TW::append(data, (uint8_t)kind); - TW::append(data, (uint8_t)key1.size()); - TW::append(data, key1); - TW::append(data, (uint8_t)groupKey.size()); - TW::append(data, groupKey); - return data; +std::string AddressV3::getStakingAddress() const noexcept { + if (kind != Kind_Base || bytes.size() != (2 * HashSize)) { + return ""; + } + const auto& stakingKeyHash = TW::subData(bytes, HashSize, HashSize); + return createReward(this->networkId, stakingKeyHash).string(); } + +} // namespace TW::Cardano diff --git a/src/Cardano/AddressV3.h b/src/Cardano/AddressV3.h index 863815bae3c..f9580812afe 100644 --- a/src/Cardano/AddressV3.h +++ b/src/Cardano/AddressV3.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -18,42 +18,55 @@ namespace TW::Cardano { /// A Cardano-Shelley address, V3 or V2. class AddressV3 { public: - enum Discrimination: uint8_t { - Discrim_Production = 0, - Discrim_Test = 1, + enum NetworkId: uint8_t { + Network_Test = 0, + Network_Production = 1, }; enum Kind: uint8_t { - Kind_Sentinel_Low = 2, - Kind_Single = 3, - Kind_Group = 4, - Kind_Account = 5, - Kind_Multisig = 6, - Kind_Sentinel_High = 7, + Kind_Base = 0, // spending + staking key + //Kind_Base_Script_Key = 1, + //Kind_Base_Key_Script_Key = 2, + //Kind_Base_Script_Script_Key = 3, + //Kind_Pointer = 4, + //Kind_Pointer_Script = 5, + Kind_Enterprise = 6, // spending key + //Kind_Enterprise_Script = 7, + //Kind_Bootstrap = 8, // Byron + Kind_Reward = 14, // // staking key + //Kind_Reward_Script = 15, }; - Discrimination discrimination; + static const uint8_t HashSize = 28; - Kind kind; + // First byte header (kind, netowrkId) and 2 hashes + static const uint8_t EncodedSize1 = 1 + HashSize; + static const uint8_t EncodedSize2 = 1 + 2 * HashSize; - /// key1: spending key or account key - Data key1; + NetworkId networkId{Network_Production}; - /// group key (in case of Group address, empty otherwise) - Data groupKey; + Kind kind{Kind_Base}; + + /// raw key/hash bytes + Data bytes; /// Used in case of legacy address (V2) std::optional legacyAddressV2; - /// Determines whether a string makes a valid address. + /// Determines whether a string makes a valid address (V3). static bool isValid(const std::string& addr); - /// Create a single spending key address - static AddressV3 createSingle(Discrimination discrimination_in, const TW::Data& spendingKey); - /// Create a group address - static AddressV3 createGroup(Discrimination discrimination_in, const TW::Data& spendingKey, const TW::Data& groupKey); - /// Create an account address - static AddressV3 createAccount(Discrimination discrimination_in, const TW::Data& accountKey); + /// Determines whether a string makes a valid address, V3 or earlier legacy. + static bool isValidLegacy(const std::string& addr); + + /// Create a base address, given public key hashes + static AddressV3 createBase(NetworkId networkId, const TW::Data& spendingKeyHash, const TW::Data& stakingKeyHash); + + /// Create a base address, given public keys + static AddressV3 createBase(NetworkId networkId, const PublicKey& spendingKey, const PublicKey& stakingKey); + + /// Create a staking (reward) address, given a staking key + static AddressV3 createReward(NetworkId networkId, const TW::Data& stakingKeyHash); /// Initializes a Cardano address with a string representation. Throws if invalid. explicit AddressV3(const std::string& addr); @@ -67,32 +80,38 @@ class AddressV3 { /// Copy constructor AddressV3(const AddressV3& other); - void operator=(const AddressV3& other); + AddressV3& operator=(const AddressV3& other) noexcept = default; /// Returns the Bech string representation of the address, with default HRP. std::string string() const; /// Returns the Bech string representation of the address, with given HRP. std::string string(const std::string& hrp) const; - /// Returns the internal Base32 string representation of the address. - std::string stringBase32() const; + /// Hrp of kind + static std::string getHrp(Kind kind) noexcept; + /// Check whether data length is correct + static bool checkLength(Kind kind, size_t length) noexcept; + /// Check validity of binary address. + static bool parseAndCheckV3(const TW::Data& raw, NetworkId& networkId, Kind& kind, TW::Data& bytes) noexcept; /// Check validity and parse elements of a string address. Used internally by isValid and ctor. - static bool parseAndCheckV3(const std::string& addr, Discrimination& discrimination, Kind& kind, TW::Data& key1, TW::Data& key2); + static bool parseAndCheckV3(const std::string& addr, NetworkId& networkId, Kind& kind, TW::Data& bytes) noexcept; /// Return the binary data representation (keys appended, internal format) - Data data() const; + Data data() const noexcept; + /// Return the staking address associated to (contained in) this address. Must be a Base address. Empty string is returned on error. + std::string getStakingAddress() const noexcept; + + // First encoded byte, from networkId and Kind + static uint8_t firstByte(NetworkId networkId, Kind kind); + static NetworkId networkIdFromFirstByte(uint8_t first); + static Kind kindFromFirstByte(uint8_t first); private: - AddressV3() : discrimination(Discrim_Production), kind(Kind_Single) {} + AddressV3() = default; }; inline bool operator==(const AddressV3& lhs, const AddressV3& rhs) { - return lhs.discrimination == rhs.discrimination && lhs.kind == rhs.kind && lhs.key1 == rhs.key1 && lhs.groupKey == rhs.groupKey; + return lhs.networkId == rhs.networkId && lhs.kind == rhs.kind && lhs.bytes == rhs.bytes; } } // namespace TW::Cardano - -/// Wrapper for C interface. -struct TWCardanoAddress { - TW::Cardano::AddressV3 impl; -}; diff --git a/src/Cardano/Entry.cpp b/src/Cardano/Entry.cpp index 0cf599dcc39..154b44ac407 100644 --- a/src/Cardano/Entry.cpp +++ b/src/Cardano/Entry.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -7,23 +7,30 @@ #include "Entry.h" #include "AddressV3.h" -//#include "Signer.h" +#include "Signer.h" -#include - -using namespace TW::Cardano; -using namespace std; +namespace TW::Cardano { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { - return AddressV3::isValid(address); +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, TW::byte, TW::byte, const char*) const { + return AddressV3::isValidLegacy(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return AddressV3(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - // not implemented yet +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + return AddressV3(address).data(); +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + signTemplate(dataIn, dataOut); } + +void Entry::plan([[maybe_unused]] TWCoinType coin, const Data& dataIn, Data& dataOut) const { + planTemplate(dataIn, dataOut); +} + +} // namespace TW::Cardano diff --git a/src/Cardano/Entry.h b/src/Cardano/Entry.h index c30df02bfba..94766b32303 100644 --- a/src/Cardano/Entry.h +++ b/src/Cardano/Entry.h @@ -12,12 +12,13 @@ namespace TW::Cardano { /// Entry point for implementation of Cardano coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeCardano}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::Cardano diff --git a/src/Cardano/Signer.cpp b/src/Cardano/Signer.cpp new file mode 100644 index 00000000000..103f57f5e92 --- /dev/null +++ b/src/Cardano/Signer.cpp @@ -0,0 +1,535 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Signer.h" +#include "AddressV3.h" + +#include "Cbor.h" +#include "HexCoding.h" +#include "PrivateKey.h" + +#include +#include +#include +#include +#include + +namespace TW::Cardano { + +static const Data placeholderPrivateKey = parse_hex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"); +static const auto PlaceholderFee = 170000; +static const auto ExtraInputAmount = 500000; + +Proto::SigningOutput Signer::sign() { + // plan if needed + if (input.has_plan()) { + _plan = TransactionPlan::fromProto(input.plan()); + } else { + // no plan supplied, plan it + _plan = doPlan(); + } + + return signWithPlan(); +} + +Common::Proto::SigningError Signer::buildTransactionAux(Transaction& tx, const Proto::SigningInput& input, const TransactionPlan& plan) { + tx = Transaction(); + for (const auto& i : plan.utxos) { + tx.inputs.emplace_back(i.txHash, i.outputIndex); + } + + // Spending output + if (!AddressV3::isValidLegacy(input.transfer_message().to_address())) { + return Common::Proto::Error_invalid_address; + } + const auto toAddress = AddressV3(input.transfer_message().to_address()); + tx.outputs.emplace_back(toAddress.data(), plan.amount, plan.outputTokens); + // Change + bool hasChangeToken = any_of(plan.changeTokens.bundle.begin(), plan.changeTokens.bundle.end(), [](auto&& t) { return t.second.amount > 0; }); + if (plan.change > 0 || hasChangeToken) { + if (!AddressV3::isValidLegacy(input.transfer_message().change_address())) { + return Common::Proto::Error_invalid_address; + } + const auto changeAddress = AddressV3(input.transfer_message().change_address()); + tx.outputs.emplace_back(changeAddress.data(), plan.change, plan.changeTokens); + } + tx.fee = plan.fee; + tx.ttl = input.ttl(); + + if (input.has_register_staking_key()) { + const auto stakingAddress = AddressV3(input.register_staking_key().staking_address()); + // here we need the bare staking key + const auto key = stakingAddress.bytes; + tx.certificates.emplace_back(Certificate{Certificate::SkatingKeyRegistration, {CertificateKey{CertificateKey::AddressKeyHash, key}}, Data()}); + } + if (input.has_delegate()) { + const auto stakingAddress = AddressV3(input.delegate().staking_address()); + // here we need the bare staking key + const auto key = stakingAddress.bytes; + const auto poolId = data(input.delegate().pool_id()); + tx.certificates.emplace_back(Certificate{Certificate::Delegation, {CertificateKey{CertificateKey::AddressKeyHash, key}}, poolId}); + } + if (input.has_withdraw()) { + const auto stakingAddress = AddressV3(input.withdraw().staking_address()); + const auto key = stakingAddress.data(); + const auto amount = input.withdraw().withdraw_amount(); + tx.withdrawals.emplace_back(Withdrawal{key, amount}); + } + if (input.has_deregister_staking_key()) { + const auto stakingAddress = AddressV3(input.deregister_staking_key().staking_address()); + // here we need the bare staking key + const auto key = stakingAddress.bytes; + tx.certificates.emplace_back(Certificate{Certificate::StakingKeyDeregistration, {CertificateKey{CertificateKey::AddressKeyHash, key}}, Data()}); + } + + return Common::Proto::OK; +} + +Data deriveStakingPrivateKey(const Data& privateKeyData) { + if (privateKeyData.size() != PrivateKey::cardanoKeySize) { + return {}; + } + assert(privateKeyData.size() == PrivateKey::cardanoKeySize); + const auto halfSize = PrivateKey::cardanoKeySize / 2; + auto stakingPrivKeyData = TW::subData(privateKeyData, halfSize); + TW::append(stakingPrivKeyData, TW::Data(halfSize)); + return stakingPrivKeyData; +} + +Common::Proto::SigningError Signer::assembleSignatures(std::vector>& signatures, const Proto::SigningInput& input, const TransactionPlan& plan, const Data& txId, bool sizeEstimationOnly) { + signatures.clear(); + // Private keys and corresponding addresses + std::map privateKeys; + for (auto i = 0; i < input.private_key_size(); ++i) { + const auto privateKeyData = data(input.private_key(i)); + if (!PrivateKey::isValid(privateKeyData)) { + return Common::Proto::Error_invalid_private_key; + } + + // Add this private key and associated address + const auto privateKey = PrivateKey(privateKeyData); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto address = AddressV3(publicKey); + privateKeys[address.string()] = privateKeyData; + + // Also add the derived staking private key (the 2nd half) and associated address; because staking keys also need signature + const auto stakingPrivKeyData = deriveStakingPrivateKey(privateKeyData); + if (!stakingPrivKeyData.empty()) { + privateKeys[address.getStakingAddress()] = stakingPrivKeyData; + } + } + + // collect every unique input UTXO address, preserving order + std::vector addresses; + for (auto& u : plan.utxos) { + if (!AddressV3::isValid(u.address)) { + return Common::Proto::Error_invalid_address; + } + addresses.emplace_back(u.address); + } + // Staking key is also an address that needs signature + if (input.has_register_staking_key()) { + addresses.emplace_back(input.register_staking_key().staking_address()); + } + if (input.has_deregister_staking_key()) { + addresses.emplace_back(input.deregister_staking_key().staking_address()); + } + if (input.has_delegate()) { + addresses.emplace_back(input.delegate().staking_address()); + } + if (input.has_withdraw()) { + addresses.emplace_back(input.withdraw().staking_address()); + } + // discard duplicates (std::set, std::copy_if, std::unique does not work well here) + std::vector addressesUnique; + for (auto& a: addresses) { + if (find(addressesUnique.begin(), addressesUnique.end(), a) == addressesUnique.end()) { + addressesUnique.emplace_back(a); + } + } + + // create signature for each address + for (auto& a : addressesUnique) { + const auto privKeyFind = privateKeys.find(a); + Data privateKeyData; + if (privKeyFind != privateKeys.end()) { + privateKeyData = privKeyFind->second; + } else { + // private key not found + if (sizeEstimationOnly) { + privateKeyData = placeholderPrivateKey; + } else { + return Common::Proto::Error_missing_private_key; + } + } + const auto privateKey = PrivateKey(privateKeyData); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto signature = privateKey.sign(txId, TWCurveED25519ExtendedCardano); + // public key (first 32 bytes) and signature (64 bytes) + signatures.emplace_back(subData(publicKey.bytes, 0, 32), signature); + } + + return Common::Proto::OK; +} + +Cbor::Encode cborizeSignatures(const std::vector>& signatures) { + // signatures as Cbor + // clang-format off + std::vector sigsCbor; + for (auto& s : signatures) { + sigsCbor.emplace_back(Cbor::Encode::array({ + Cbor::Encode::bytes(s.first), + Cbor::Encode::bytes(s.second) + })); + } + + // Cbor-encode txAux & signatures + return Cbor::Encode::map({ + std::make_pair( + Cbor::Encode::uint(0), + Cbor::Encode::array(sigsCbor) + ) + }); + // clang-format on +} + +Proto::SigningOutput Signer::signWithPlan() const { + auto ret = Proto::SigningOutput(); + if (_plan.error != Common::Proto::OK) { + // plan has error + ret.set_error(_plan.error); + return ret; + } + + Data encoded; + Data txId; + const auto buildRet = encodeTransaction(encoded, txId, input, _plan); + if (buildRet != Common::Proto::OK) { + ret.set_error(buildRet); + return ret; + } + + ret.set_encoded(std::string(encoded.begin(), encoded.end())); + ret.set_tx_id(std::string(txId.begin(), txId.end())); + ret.set_error(Common::Proto::OK); + + return ret; +} + +Common::Proto::SigningError Signer::encodeTransaction(Data& encoded, Data& txId, const Proto::SigningInput& input, const TransactionPlan& plan, bool sizeEstimationOnly) { + if (plan.error != Common::Proto::OK) { + return plan.error; + } + + Transaction txAux; + const auto buildRet = buildTransactionAux(txAux, input, plan); + if (buildRet != Common::Proto::OK) { + return buildRet; + } + txId = txAux.getId(); + + std::vector> signatures; + const auto sigError = assembleSignatures(signatures, input, plan, txId, sizeEstimationOnly); + if (sigError != Common::Proto::OK) { + return sigError; + } + const auto sigsCbor = cborizeSignatures(signatures); + + // Cbor-encode txAux & signatures + const auto cbor = Cbor::Encode::array({ + // txaux + Cbor::Encode::fromRaw(txAux.encode()), + // signatures + sigsCbor, + // aux data + Cbor::Encode::null(), + }); + encoded = cbor.encoded(); + return Common::Proto::OK; +} + +// Select a subset of inputs, to cover desired coin amount. Simple algorithm: pick the largest ones. +std::vector selectInputsSimpleNative(const std::vector& inputs, Amount amount) { + auto ii = std::vector(inputs); + sort(ii.begin(), ii.end(), [](auto&& t1, auto&& t2) { + return t1.amount > t2.amount; + }); + auto selected = std::vector(); + Amount selectedAmount = 0; + + for (const auto& i : ii) { + selected.emplace_back(i); + selectedAmount += i.amount; + if (selectedAmount >= amount) { + break; + } + } + return selected; +} + +// Select a subset of inputs, to cover desired token amount. Simple algorithm: pick the largest ones. +void selectInputsSimpleToken(const std::vector& inputs, std::string key, const uint256_t& amount, std::vector& selectedInputs) { + auto accumulateFunctor = [key]([[maybe_unused]] auto&& sum, auto&& si) { return si.tokenBundle.getAmount(key); }; + uint256_t selectedAmount = std::accumulate(selectedInputs.begin(), selectedInputs.end(), uint256_t(0), accumulateFunctor); + if (selectedAmount >= amount) { + return; // already covered + } + // sort inputs descending + auto ii = std::vector(inputs); + std::sort(ii.begin(), ii.end(), [key](auto&& t1, auto&& t2) { return t1.tokenBundle.getAmount(key) > t2.tokenBundle.getAmount(key); }); + for (const auto& i : ii) { + if (static_cast(distance(selectedInputs.begin(), find(selectedInputs.begin(), selectedInputs.end(), i))) < selectedInputs.size()) { + // already selected + continue; + } + selectedInputs.emplace_back(i); + selectedAmount += i.amount; + if (selectedAmount >= amount) { + return; + } + } + // not enough +} + +// Select a subset of inputs, to cover desired amount. Simple algorithm: pick the largest ones +std::vector Signer::selectInputsWithTokens(const std::vector& inputs, Amount amount, const TokenBundle& requestedTokens) { + auto selected = selectInputsSimpleNative(inputs, amount); + for (auto&& [_, curAmount] : requestedTokens.bundle) { + selectInputsSimpleToken(inputs, curAmount.key(), curAmount.amount, selected); + } + return selected; +} + +// Create a simple plan, used for estimation +TransactionPlan simplePlan(Amount amount, const TokenBundle& requestedTokens, const std::vector& selectedInputs, bool maxAmount, uint64_t deposit, uint64_t undeposit) { + TransactionPlan plan{.utxos = selectedInputs, .amount = amount, .deposit = deposit, .undeposit = undeposit}; + // Sum availableAmount + plan.availableAmount = 0; + for (auto& u : plan.utxos) { + plan.availableAmount += u.amount; + for (auto && [_, curAmount] : u.tokenBundle.bundle) { + plan.availableTokens.add(curAmount); + } + } + plan.fee = PlaceholderFee; // placeholder value + const auto availAfterDeposit = plan.availableAmount + plan.undeposit - plan.deposit; + // adjust/compute output amount and output tokens + if (!maxAmount) { + // reduce amount if needed + plan.amount = std::max(Amount(0), std::min(plan.amount, availAfterDeposit - plan.fee)); + plan.outputTokens = requestedTokens; + } else { + // max available amount + plan.amount = std::max(Amount(0), availAfterDeposit - plan.fee); + plan.outputTokens = plan.availableTokens; // use all + } + + // compute change + plan.change = availAfterDeposit - (plan.amount + plan.fee); + for (auto iter = plan.availableTokens.bundle.begin(); iter != plan.availableTokens.bundle.end(); ++iter) { + const auto key = iter->second.key(); + const auto changeAmount = iter->second.amount - plan.outputTokens.getAmount(key); + assert(changeAmount >= 0); + plan.changeTokens.bundle[key] = iter->second; + plan.changeTokens.bundle[key].amount = changeAmount; + } + return plan; +} + +uint64_t sumDeposits(const Proto::SigningInput& input) { + uint64_t sum = 0; + if (input.has_register_staking_key()) { + sum += input.register_staking_key().deposit_amount(); + } + if (input.has_delegate()) { + sum += input.delegate().deposit_amount(); + } + return sum; +} + +uint64_t sumUndeposits(const Proto::SigningInput& input) { + uint64_t sum = 0; + if (input.has_deregister_staking_key()) { + sum += input.deregister_staking_key().undeposit_amount(); + } + if (input.has_withdraw()) { + sum += input.withdraw().withdraw_amount(); + } + return sum; +} + +// Estimates size of transaction in bytes. +uint64_t estimateTxSize(const Proto::SigningInput& input, Amount amount, const TokenBundle& requestedTokens, const std::vector& selectedInputs) { + auto inputs = std::vector(); + for (auto i = 0; i < input.utxos_size(); ++i) { + inputs.emplace_back(TxInput::fromProto(input.utxos(i))); + } + const auto deposits = sumDeposits(input); + const uint64_t undeposits = sumUndeposits(input); + const auto _simplePlan = simplePlan(amount, requestedTokens, selectedInputs, input.transfer_message().use_max_amount(), deposits, undeposits); + + Data encoded; + Data txId; + const auto encodeError = Signer::encodeTransaction(encoded, txId, input, _simplePlan, true); + if (encodeError != Common::Proto::OK) { + return 0; + } + + return encoded.size(); +} + +// Compute fee from tx size, with some over-estimation +Amount txFeeFunction(uint64_t txSizeInBytes) { + const double fixedTerm = 155381 + 500; + const double linearTerm = 43.946 + 0.1; + + const auto fee = (Amount)(ceil(fixedTerm + (double)txSizeInBytes * linearTerm)); + return fee; +} + +Amount Signer::estimateFee(const Proto::SigningInput& input, Amount amount, const TokenBundle& requestedTokens, const std::vector& selectedInputs) { + return txFeeFunction(estimateTxSize(input, amount, requestedTokens, selectedInputs)); +} + +TransactionPlan Signer::doPlan() const { + auto plan = TransactionPlan(); + + bool maxAmount = input.transfer_message().use_max_amount(); + if (input.transfer_message().amount() == 0 && !maxAmount && input.transfer_message().token_amount().token_size() == 0) { + plan.error = Common::Proto::Error_zero_amount_requested; + return plan; + } + // Check input UTXOs, process, sum ADA and token amounts + auto utxos = std::vector(); + uint64_t inputSum = 0; + for (auto i = 0; i < input.utxos_size(); ++i) { + const auto& utxo = input.utxos(i); + utxos.emplace_back(TxInput::fromProto(utxo)); + inputSum += utxo.amount(); + } + if (inputSum == 0 || input.utxos_size() == 0) { + plan.error = Common::Proto::Error_missing_input_utxos; + return plan; + } + assert(inputSum > 0); + // adjust inputSum with deposited/undeposited amount + plan.deposit = sumDeposits(input); + plan.undeposit = sumUndeposits(input); + const auto inputSumAfterDeposit = inputSum + plan.undeposit - plan.deposit; + + // Amounts requested + plan.amount = input.transfer_message().amount(); + TokenBundle requestedTokens; + for (auto i = 0; i < input.transfer_message().token_amount().token_size(); ++i) { + const auto token = TokenAmount::fromProto(input.transfer_message().token_amount().token(i)); + requestedTokens.add(token); + } + assert(plan.amount > 0 || maxAmount); + if (requestedTokens.size() > 1) { + // We support transfer of only one coin (for simplicity; inputs may contain more coins which are preserved) + plan.error = Common::Proto::Error_invalid_requested_token_amount; + return plan; + } + + // if amount requested is the same or more than available amount, it cannot be satisfied, but + // treat this case as MaxAmount, and send maximum available (which will be less) + if (!maxAmount && input.transfer_message().amount() >= inputSumAfterDeposit) { + maxAmount = true; + } + + // select UTXOs + if (!maxAmount) { + // aim for larger total input, enough for 4/3 of the target amount plus typical fee plus minimal ADA for change plus some extra + auto targetInputAmount = (plan.amount * 4) / 3 + plan.deposit - plan.undeposit + PlaceholderFee + requestedTokens.minAdaAmount() + ExtraInputAmount; + plan.utxos = selectInputsWithTokens(utxos, targetInputAmount, requestedTokens); + } else { + // maxAmount, select all + plan.utxos = utxos; + } + assert(!plan.utxos.empty()); + + // Sum availableAmount + plan.availableAmount = 0; + for (auto& u : plan.utxos) { + plan.availableAmount += u.amount; + for (auto && [_, curAmount] : u.tokenBundle.bundle) { + plan.availableTokens.add(curAmount); + } + } + if (plan.availableAmount == 0) { + plan.error = Common::Proto::Error_missing_input_utxos; + return plan; + } + assert(plan.availableAmount > 0); + // adjust availableAmount with deposited/undeposited amount + const auto availableAmountAfterDeposit = plan.availableAmount + plan.undeposit - plan.deposit; + + // check that there are enough coins in the inputs + if (plan.amount > availableAmountAfterDeposit) { + plan.error = Common::Proto::Error_low_balance; + return plan; + } + assert(plan.amount <= availableAmountAfterDeposit); + // check that there are enough tokens in the inputs + for (auto && [_, curAmount] : requestedTokens.bundle) { + if (curAmount.amount > plan.availableTokens.getAmount(curAmount.key())) { + plan.error = Common::Proto::Error_low_balance; + return plan; + } + } + + // compute fee + if (input.transfer_message().force_fee() == 0) { + plan.fee = estimateFee(input, plan.amount, requestedTokens, plan.utxos); + } else { + // fee provided, use it (capped) + plan.fee = std::max(Amount(0), std::min(availableAmountAfterDeposit - plan.amount, input.transfer_message().force_fee())); + } + assert(plan.fee >= 0 && plan.fee < availableAmountAfterDeposit); + + // adjust/compute output amount + if (!maxAmount) { + // reduce amount if needed + plan.amount = std::max(Amount(0), std::min(plan.amount, availableAmountAfterDeposit - plan.fee)); + } else { + // max available amount + plan.amount = std::max(Amount(0), availableAmountAfterDeposit - plan.fee); + } + assert(plan.amount >= 0 && plan.amount <= availableAmountAfterDeposit); + + if (plan.amount + plan.fee > availableAmountAfterDeposit) { + plan.error = Common::Proto::Error_low_balance; + return plan; + } + assert(plan.amount + plan.fee <= availableAmountAfterDeposit); + + // compute output token amounts + if (!maxAmount) { + plan.outputTokens = requestedTokens; + } else { + plan.outputTokens = plan.availableTokens; // send all + } + + // compute change + plan.change = availableAmountAfterDeposit - (plan.amount + plan.fee); + for (auto iter = plan.availableTokens.bundle.begin(); iter != plan.availableTokens.bundle.end(); ++iter) { + const auto key = iter->second.key(); + const auto changeAmount = iter->second.amount - plan.outputTokens.getAmount(key); + if (changeAmount > 0) { // omit 0-amount tokens + plan.changeTokens.bundle[key] = iter->second; + plan.changeTokens.bundle[key].amount = changeAmount; + } + } + + assert(plan.change >= 0 && plan.change <= availableAmountAfterDeposit); + assert(!maxAmount || plan.change == 0); // change is 0 in max amount case + assert(plan.amount + plan.change + plan.fee == availableAmountAfterDeposit); + assert(plan.amount + plan.change + plan.fee + plan.deposit == plan.availableAmount + plan.undeposit); + + return plan; +} + +} // namespace TW::Cardano diff --git a/src/Cardano/Signer.h b/src/Cardano/Signer.h new file mode 100644 index 00000000000..01d538a617f --- /dev/null +++ b/src/Cardano/Signer.h @@ -0,0 +1,54 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Transaction.h" +#include "../proto/Cardano.pb.h" +#include "Data.h" + +#include +#include +#include + +namespace TW::Cardano { + +class Signer { +public: + /// Signs a Proto::SigningInput transaction + static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept { + Signer signer = Signer(input); + return signer.sign(); + } + +public: + Proto::SigningInput input; + TransactionPlan _plan; + + explicit Signer(Proto::SigningInput input): input(std::move(input)) {} + + Proto::SigningOutput sign(); + // Sign using existing plan + Proto::SigningOutput signWithPlan() const; + // Create plan from signing input + TransactionPlan doPlan() const; + /// Returns a transaction plan (utxo selection, fee estimation) + static Proto::TransactionPlan plan(const Proto::SigningInput& input) noexcept { + const auto signer = Signer(input); + const auto plan = signer.doPlan(); + return plan.toProto(); + } + // Build encoded transaction + static Common::Proto::SigningError encodeTransaction(Data& encoded, Data& txId, const Proto::SigningInput& input, const TransactionPlan& plan, bool sizeEstimationOnly = false); + // Build aux transaction object, using input and plan + static Common::Proto::SigningError buildTransactionAux(Transaction& tx, const Proto::SigningInput& input, const TransactionPlan& plan); + static Amount estimateFee(const Proto::SigningInput& input, Amount amount, const TokenBundle& requestedTokens, const std::vector& selectedInputs); + static std::vector selectInputsWithTokens(const std::vector& inputs, Amount amount, const TokenBundle& requestedTokens); + // Build list of public keys + signature + static Common::Proto::SigningError assembleSignatures(std::vector>& signatures, const Proto::SigningInput& input, const TransactionPlan& plan, const Data& txId, bool sizeEstimationOnly = false); +}; + +} // namespace TW::Cardano diff --git a/src/Cardano/Transaction.cpp b/src/Cardano/Transaction.cpp new file mode 100644 index 00000000000..da6a4924572 --- /dev/null +++ b/src/Cardano/Transaction.cpp @@ -0,0 +1,316 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Transaction.h" + +#include "Cbor.h" +#include "Hash.h" +#include "HexCoding.h" + +namespace TW::Cardano { + +TokenAmount TokenAmount::fromProto(const Proto::TokenAmount& proto) { + return {proto.policy_id(), proto.asset_name(), load(proto.amount())}; +} + +Proto::TokenAmount TokenAmount::toProto() const { + Proto::TokenAmount tokenAmount; + tokenAmount.set_policy_id(policyId.data(), policyId.size()); + tokenAmount.set_asset_name(assetName.data(), assetName.size()); + const auto amountData = store(amount); + tokenAmount.set_amount(amountData.data(), amountData.size()); + return tokenAmount; +} + +TokenBundle TokenBundle::fromProto(const Proto::TokenBundle& proto) { + TokenBundle ret; + const auto addFunctor = [&ret](auto&& cur) { ret.add(TokenAmount::fromProto(cur)); }; + std::for_each(std::cbegin(proto.token()), std::cend(proto.token()), addFunctor); + return ret; +} + +Proto::TokenBundle TokenBundle::toProto() const { + Proto::TokenBundle proto; + for (const auto& t : bundle) { + *(proto.add_token()) = t.second.toProto(); + } + return proto; +} + +void TokenBundle::add(const TokenAmount& ta) { + const auto key = ta.key(); + if (auto&& [it, inserted] = bundle.try_emplace(key, ta); !inserted) { + it->second.amount += ta.amount; + } +} + +uint256_t TokenBundle::getAmount(const std::string& key) const { + const auto& findkey = bundle.find(key); + return findkey == bundle.end() ? 0 : findkey->second.amount; +} + +std::unordered_set TokenBundle::getPolicyIds() const { + std::unordered_set policyIds; + std::transform(bundle.cbegin(), bundle.cend(), + std::inserter(policyIds, policyIds.begin()), + [](auto&& cur) { return cur.second.policyId; }); + return policyIds; +} + +std::vector TokenBundle::getByPolicyId(const std::string& policyId) const { + std::vector filtered; + for (auto&& t : bundle) { + if (t.second.policyId == policyId) { + filtered.emplace_back(t.second); + } + } + return filtered; +} + +uint64_t roundupBytesToWords(uint64_t b) { + return ((b + 7) / 8); +} + +const uint64_t TokenBundle::MinUtxoValue = 1000000; + +uint64_t TokenBundle::minAdaAmountHelper(uint64_t numPids, uint64_t numAssets, uint64_t sumAssetNameLengths) { + if (numPids == 0) { + return MinUtxoValue; + } + + static const uint64_t coinSize = 0; + static const uint64_t utxoEntrySizeWithoutVal = 27; + static const uint64_t adaOnlyUTxOSize = utxoEntrySizeWithoutVal + coinSize; // 27 + static const uint64_t pidSize = 28; + + uint64_t sizeB = 6 + roundupBytesToWords((numAssets * 12) + sumAssetNameLengths + (numPids * pidSize)); + return std::max(MinUtxoValue, (MinUtxoValue / adaOnlyUTxOSize) * (utxoEntrySizeWithoutVal + sizeB)); +} + +uint64_t TokenBundle::minAdaAmount() const { + if (size() == 0) { + // ADA only + return MinUtxoValue; + } + + std::unordered_set policyIdRegistry; + std::unordered_set assetNameRegistry; + uint64_t sumAssetNameLengths = 0; + for (const auto& t : bundle) { + policyIdRegistry.emplace(t.second.policyId); + if (t.second.assetName.length() > 0) { + assetNameRegistry.emplace(t.second.assetName); + } + } + auto numPids = uint64_t(policyIdRegistry.size()); + auto numAssets = uint64_t(assetNameRegistry.size()); + for_each(assetNameRegistry.begin(), assetNameRegistry.end(), [&sumAssetNameLengths](auto&& a) { sumAssetNameLengths += a.length(); }); + return minAdaAmountHelper(numPids, numAssets, sumAssetNameLengths); +} + +TxInput TxInput::fromProto(const Cardano::Proto::TxInput& proto) { + auto ret = TxInput(); + ret.txHash = data(proto.out_point().tx_hash()); + ret.outputIndex = proto.out_point().output_index(); + ret.address = proto.address(); + ret.amount = proto.amount(); + for (auto i = 0; i < proto.token_amount_size(); ++i) { + auto ta = TokenAmount::fromProto(proto.token_amount(i)); + ret.tokenBundle.add(ta); + } + return ret; +} + +Proto::TxInput TxInput::toProto() const { + Proto::TxInput txInput; + txInput.mutable_out_point()->set_tx_hash(txHash.data(), txHash.size()); + txInput.mutable_out_point()->set_output_index(outputIndex); + txInput.set_address(address.data(), address.size()); + txInput.set_amount(amount); + for (const auto& token : tokenBundle.bundle) { + *txInput.add_token_amount() = token.second.toProto(); + } + return txInput; +} + +bool operator==(const TxInput& i1, const TxInput& i2) { + return i1.outputIndex == i2.outputIndex && i1.txHash == i2.txHash; +} + +TransactionPlan TransactionPlan::fromProto(const Proto::TransactionPlan& proto) { + auto ret = TransactionPlan(); + ret.availableAmount = proto.available_amount(); + ret.amount = proto.amount(); + ret.fee = proto.fee(); + ret.change = proto.change(); + ret.deposit = proto.deposit(); + ret.undeposit = proto.undeposit(); + for (auto i = 0; i < proto.available_tokens_size(); ++i) { + ret.availableTokens.add(TokenAmount::fromProto(proto.available_tokens(i))); + } + for (auto i = 0; i < proto.output_tokens_size(); ++i) { + ret.outputTokens.add(TokenAmount::fromProto(proto.output_tokens(i))); + } + for (auto i = 0; i < proto.change_tokens_size(); ++i) { + ret.changeTokens.add(TokenAmount::fromProto(proto.change_tokens(i))); + } + for (auto i = 0; i < proto.utxos_size(); ++i) { + ret.utxos.emplace_back(TxInput::fromProto(proto.utxos(i))); + } + ret.error = proto.error(); + return ret; +} + +Proto::TransactionPlan TransactionPlan::toProto() const { + Proto::TransactionPlan plan; + plan.set_available_amount(availableAmount); + plan.set_amount(amount); + plan.set_fee(fee); + plan.set_change(change); + plan.set_deposit(deposit); + plan.set_undeposit(undeposit); + for (const auto& token : availableTokens.bundle) { + *plan.add_available_tokens() = token.second.toProto(); + } + for (const auto& token : outputTokens.bundle) { + *plan.add_output_tokens() = token.second.toProto(); + } + for (const auto& token : changeTokens.bundle) { + *plan.add_change_tokens() = token.second.toProto(); + } + for (const auto& u : utxos) { + *plan.add_utxos() = u.toProto(); + } + plan.set_error(error); + return plan; +} + +Cbor::Encode cborizeInputs(const std::vector& inputs) { + // clang-format off + std::vector ii; + for (const auto& i : inputs) { + ii.emplace_back(Cbor::Encode::array({ + Cbor::Encode::bytes(i.txHash), + Cbor::Encode::uint(i.outputIndex) + })); + } + // clang-format on + return Cbor::Encode::array(ii); +} + +Cbor::Encode cborizeOutputAmounts(const Amount& amount, const TokenBundle& tokenBundle) { + if (tokenBundle.size() == 0) { + // native amount only + return Cbor::Encode::uint(amount); + } + // native and token amounts + // tokens: organized in two levels: by policyId and by assetName + const auto policyIds = tokenBundle.getPolicyIds(); + std::map tokensMap; + for (const auto& policy : policyIds) { + const auto& subTokens = tokenBundle.getByPolicyId(policy); + std::map subTokensMap; + for (const auto& token : subTokens) { + subTokensMap.emplace( + Cbor::Encode::bytes(data(token.assetName)), + Cbor::Encode::uint(uint64_t(token.amount)) // 64 bits + ); + } + tokensMap.emplace( + Cbor::Encode::bytes(parse_hex(policy)), + Cbor::Encode::map(subTokensMap)); + } + // clang-format off + return Cbor::Encode::array({ + Cbor::Encode::uint(amount), + Cbor::Encode::map(tokensMap) + }); + // clang-format on +} + +Cbor::Encode cborizeOutput(const TxOutput& output) { + // clang-format off + return Cbor::Encode::array({ + Cbor::Encode::bytes(output.address), + cborizeOutputAmounts(output.amount, output.tokenBundle) + }); + // clang-format on +} + +Cbor::Encode cborizeOutputs(const std::vector& outputs) { + std::vector oo; + for (const auto& o : outputs) { + oo.emplace_back(cborizeOutput(o)); + } + return Cbor::Encode::array(oo); +} + +Cbor::Encode cborizeCertificateKey(const CertificateKey& certKey) { + std::vector c; + c.emplace_back(Cbor::Encode::uint(static_cast(certKey.type))); + c.emplace_back(Cbor::Encode::bytes(certKey.key)); + return Cbor::Encode::array(c); +} + +Cbor::Encode cborizeCert(const Certificate& cert) { + std::vector c; + c.emplace_back(Cbor::Encode::uint(static_cast(cert.type))); + c.emplace_back(cborizeCertificateKey(cert.certKey)); + if (!cert.poolId.empty()) { + c.emplace_back(Cbor::Encode::bytes(cert.poolId)); + } + return Cbor::Encode::array(c); +} + +Cbor::Encode cborizeCerts(const std::vector& certs) { + std::vector c; + for (const auto& i : certs) { + c.emplace_back(cborizeCert(i)); + } + return Cbor::Encode::array(c); +} + +Cbor::Encode cborizeWithdrawals(const std::vector& withdrawals) { + std::map mapElems; + for (const auto& w : withdrawals) { + mapElems.emplace(Cbor::Encode::bytes(w.stakingKey), Cbor::Encode::uint(w.amount)); + } + return Cbor::Encode::map(mapElems); +} + +Data Transaction::encode() const { + const auto ii = cborizeInputs(inputs); + const auto oo = cborizeOutputs(outputs); + + // Encode elements in a map, with fixed numbers as keys + std::map mapElems = { + std::make_pair(Cbor::Encode::uint(0), ii), + std::make_pair(Cbor::Encode::uint(1), oo), + std::make_pair(Cbor::Encode::uint(2), Cbor::Encode::uint(fee)), + std::make_pair(Cbor::Encode::uint(3), Cbor::Encode::uint(ttl)), + }; + + if (!certificates.empty()) { + mapElems.emplace(Cbor::Encode::uint(4), cborizeCerts(certificates)); + } + if (!withdrawals.empty()) { + mapElems.emplace(Cbor::Encode::uint(5), cborizeWithdrawals(withdrawals)); + } + + Cbor::Encode encode = Cbor::Encode::map(mapElems); + return encode.encoded(); + + // Note: following fields are not included: + // 7 AUXILIARY_DATA_HASH, 8 VALIDITY_INTERVAL_START +} + +Data Transaction::getId() const { + const auto encoded = encode(); + auto hash = Hash::blake2b(encoded, 32); + return hash; +} + +} // namespace TW::Cardano diff --git a/src/Cardano/Transaction.h b/src/Cardano/Transaction.h new file mode 100644 index 00000000000..a8e7e758000 --- /dev/null +++ b/src/Cardano/Transaction.h @@ -0,0 +1,180 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "AddressV3.h" + +#include "Data.h" +#include "uint256.h" +#include "../proto/Cardano.pb.h" +#include "../proto/Common.pb.h" + +#include +#include +#include +#include + +namespace TW::Cardano { + +typedef uint64_t Amount; + +class TokenAmount { +public: + std::string policyId; + std::string assetName; + uint256_t amount; + + TokenAmount() = default; + TokenAmount(std::string policyId, std::string assetName, uint256_t amount) + : policyId(std::move(policyId)), assetName(std::move(assetName)), amount(std::move(amount)) {} + + static TokenAmount fromProto(const Proto::TokenAmount& proto); + Proto::TokenAmount toProto() const; + /// Key used in TokenBundle + std::string key() const { return policyId + "_" + assetName; } +}; + +class TokenBundle { +public: + std::map bundle; + + TokenBundle() = default; + explicit TokenBundle(const std::vector& tokens) { + for (const auto& t : tokens) { + add(t); + } + } + + static TokenBundle fromProto(const Proto::TokenBundle& proto); + Proto::TokenBundle toProto() const; + + void add(const TokenAmount& ta); + uint256_t getAmount(const std::string& key) const; + size_t size() const { return bundle.size(); } + /// Get the unique policyIds, can be the same number as the elements, or less (in case a policyId appears more than once, with different asset names). + std::unordered_set getPolicyIds() const; + /// Filter by policyIds + std::vector getByPolicyId(const std::string& policyId) const; + + // The minimum ADA amount needed for an ADA-only UTXO + static const uint64_t MinUtxoValue; + // The minimum ADA amount needed for a UTXO with this token bundle. See https://docs.cardano.org/native-tokens/minimum-ada-value-requirement + uint64_t minAdaAmount() const; + static uint64_t minAdaAmountHelper(uint64_t numPids, uint64_t numAssets, uint64_t sumAssetNameLengths); +}; + +class OutPoint { +public: + Data txHash; + uint64_t outputIndex{}; + + OutPoint() = default; + OutPoint(Data txHash, uint64_t outputIndex) + : txHash(std::move(txHash)), outputIndex(outputIndex) {} +}; + +class TxInput : public OutPoint { +public: + std::string address; + + /// ADA amount + Amount amount; + + /// Token amounts (optional) + TokenBundle tokenBundle; + + static TxInput fromProto(const Proto::TxInput& proto); + Proto::TxInput toProto() const; +}; + +bool operator==(const TxInput& i1, const TxInput& i2); + +class TxOutput { +public: + Data address; + + /// ADA amount + Amount amount{}; + + /// Token amounts (optional) + TokenBundle tokenBundle; + + TxOutput() = default; + TxOutput(Data address, Amount amount) + : address(std::move(address)), amount(amount) {} + TxOutput(Data address, Amount amount, TokenBundle tokenBundle) + : address(std::move(address)), amount(amount), tokenBundle(std::move(tokenBundle)) {} +}; + +class TransactionPlan { +public: + std::vector utxos; + Amount availableAmount = 0; // total coins in the input utxos + Amount amount = 0; // coins in the output UTXO + Amount fee = 0; // coin amount deducted as fee + Amount change = 0; // coins in the change UTXO + Amount deposit = 0; // coins deposited (going to deposit) in this TX + Amount undeposit = 0; // coins undeposited (returned from deposit) in this TX + TokenBundle availableTokens; // total tokens in the utxos (optional) + TokenBundle outputTokens; // tokens in the output (optional) + TokenBundle changeTokens; // tokens in the change (optional) + Common::Proto::SigningError error = Common::Proto::SigningError::OK; + + static TransactionPlan fromProto(const Proto::TransactionPlan& proto); + Proto::TransactionPlan toProto() const; +}; + +/// A key with a type, used in a Certificate +class CertificateKey { +public: + enum KeyType : uint8_t { + AddressKeyHash = 0, + // ScriptHash = 1, + }; + KeyType type; + Data key; +}; + +/// Certificate, mainly used for staking +class Certificate { +public: + enum CertificateType : uint8_t { + SkatingKeyRegistration = 0, + StakingKeyDeregistration = 1, + Delegation = 2, + // StakePoolRegistration = 3, // not supported + }; + CertificateType type; + CertificateKey certKey; + /// Optional PoolId, used in delegation + Data poolId; +}; + +/// Staking withdrawal +class Withdrawal { +public: + Data stakingKey; + Amount amount; +}; + +class Transaction { +public: + std::vector inputs; + std::vector outputs; + Amount fee; + uint64_t ttl; + std::vector certificates; + std::vector withdrawals; + + // Encode into CBOR binary format + Data encode() const; + + // Derive Transaction ID from hashed encoded data + Data getId() const; +}; + +} // namespace TW::Cardano diff --git a/src/Cbor.cpp b/src/Cbor.cpp index 41e85aa1106..719c173ec83 100644 --- a/src/Cbor.cpp +++ b/src/Cbor.cpp @@ -19,7 +19,7 @@ TW::Data Encode::encoded() const { if (openIndefCount > 0) { throw invalid_argument("CBOR Unclosed indefinite length building"); } - return data; + return _data; } Encode Encode::uint(uint64_t value) { @@ -46,21 +46,21 @@ Encode Encode::array(const vector& elems) { Encode e; auto n = elems.size(); e.appendValue(Decode::MT_array, n); - for (int i = 0; i < n; ++i) { + for (auto i = 0ul; i < n; ++i) { e.append(elems[i].encoded()); } return e; } -Encode Encode::map(const vector>& elems) { - Encode e; +Encode Encode::map(const std::map& elems) { + Encode enc; auto n = elems.size(); - e.appendValue(Decode::MT_map, n); - for (int i = 0; i < n; ++i) { - e.append(elems[i].first.encoded()); - e.append(elems[i].second.encoded()); + enc.appendValue(Decode::MT_map, n); + for (const auto& e: elems) { + enc.append(e.first.encoded()); + enc.append(e.second.encoded()); } - return e; + return enc; } Encode Encode::tag(uint64_t value, const Encode& elem) { @@ -70,6 +70,12 @@ Encode Encode::tag(uint64_t value, const Encode& elem) { return e; } +Encode Encode::null() { + Encode e; + e.appendValue(Decode::MT_special, 0x16); + return e; +} + Encode Encode::indefArray() { Encode e; e.appendIndefinite(Decode::MT_array); @@ -90,7 +96,7 @@ Encode Encode::closeIndefArray() { throw invalid_argument("CBOR Not inside indefinite-length array"); } // add closing break command - TW::append(data, 0xFF); + TW::append(_data, 0xFF); // close counter --openIndefCount; return *this; @@ -123,9 +129,9 @@ Encode Encode::appendValue(byte majorType, uint64_t value) { minorType = 27; } // add bytes - TW::append(data, (byte)((majorType << 5) | (minorType & 0x1F))); + TW::append(_data, (byte)((majorType << 5) | (minorType & 0x1F))); Data valBytes = Data(byteCount - 1); - for (int i = 0; i < valBytes.size(); ++i) { + for (auto i = 0ul; i < valBytes.size(); ++i) { valBytes[valBytes.size() - 1 - i] = (byte)(value & 0xFF); value = value >> 8; } @@ -135,7 +141,7 @@ Encode Encode::appendValue(byte majorType, uint64_t value) { void Encode::appendIndefinite(byte majorType) { byte minorType = 31; - TW::append(data, (byte)((majorType << 5) | (minorType & 0x1F))); + TW::append(_data, (byte)((majorType << 5) | (minorType & 0x1F))); } @@ -273,7 +279,7 @@ uint32_t Decode::getCompoundLength(uint32_t countMultiplier) const { uint32_t count = typeDesc.isIndefiniteValue ? 0 : (uint32_t)(typeDesc.value * countMultiplier); // process elements len += typeDesc.byteCount; - for (int i = 0; i < count || typeDesc.isIndefiniteValue; ++i) { + for (auto i = 0ul; i < count || typeDesc.isIndefiniteValue; ++i) { Decode nextElem = skipClone(len); if (typeDesc.isIndefiniteValue && nextElem.isBreak()) { // end of indefinite-length @@ -298,7 +304,7 @@ vector Decode::getCompoundElements(uint32_t countMultiplier, TW::byte ex uint32_t count = typeDesc.isIndefiniteValue ? 0 : (uint32_t)(typeDesc.value * countMultiplier); // process elements uint32_t idx = typeDesc.byteCount; - for (int i = 0; i < count || typeDesc.isIndefiniteValue; ++i) { + for (auto i = 0ul; i < count || typeDesc.isIndefiniteValue; ++i) { Decode nextElem = skipClone(idx); if (typeDesc.isIndefiniteValue && nextElem.isBreak()) { // end of indefinite-length @@ -308,7 +314,7 @@ vector Decode::getCompoundElements(uint32_t countMultiplier, TW::byte ex if (idx + elemLen > length()) { throw std::invalid_argument("CBOR array data too short"); } - elems.push_back(Decode(data, subStart + idx, elemLen)); + elems.emplace_back(Decode(data, subStart + idx, elemLen)); idx += elemLen; } return elems; @@ -317,7 +323,7 @@ vector Decode::getCompoundElements(uint32_t countMultiplier, TW::byte ex vector> Decode::getMapElements() const { auto elems = getCompoundElements(2, MT_map); vector> map; - for (int i = 0; i < elems.size(); i += 2) { + for (auto i = 0ul; i < elems.size(); i += 2) { map.emplace_back(make_pair(elems[i], elems[i + 1])); } return map; @@ -363,7 +369,7 @@ bool Decode::isValid() const { if (len > subLen) { return false; } auto count = typeDesc.isIndefiniteValue ? 0 : countMultiplier * typeDesc.value; uint32_t idx = typeDesc.byteCount; - for (int i = 0; i < count || typeDesc.isIndefiniteValue; ++i) + for (auto i = 0ul; i < count || typeDesc.isIndefiniteValue; ++i) { Decode nextElem = skipClone(idx); if (typeDesc.isIndefiniteValue && nextElem.isBreak()) { break; } @@ -410,7 +416,7 @@ string Decode::dumpToStringInternal() const { s << "["; } vector elems = getArrayElements(); - for (int i = 0; i < elems.size(); ++i) { + for (auto i = 0ul; i < elems.size(); ++i) { if (i > 0) s << ", "; s << elems[i].dumpToStringInternal(); } @@ -426,7 +432,7 @@ string Decode::dumpToStringInternal() const { s << "{"; } auto elems = getMapElements(); - for (int i = 0; i < elems.size(); ++i) { + for (auto i = 0ul; i < elems.size(); ++i) { if (i > 0) s << ", "; s << elems[i].first.dumpToStringInternal() << ": " << elems[i].second.dumpToStringInternal(); } @@ -443,7 +449,11 @@ string Decode::dumpToStringInternal() const { if (typeDesc.isIndefiniteValue) { // skip break command } else { - s << "spec " << typeDesc.value; + if (typeDesc.value == 0x16) { + s << "null"; + } else { + s << "spec " << typeDesc.value; + } } break; } diff --git a/src/Cbor.h b/src/Cbor.h index 0b6dee6cf62..06ab9f3feca 100644 --- a/src/Cbor.h +++ b/src/Cbor.h @@ -11,6 +11,8 @@ #include #include #include +#include +#include namespace TW::Cbor { @@ -38,9 +40,11 @@ class Encode { /// encode an array of elements (of different types) static Encode array(const std::vector& elems); /// encode a map - static Encode map(const std::vector>& elems); + static Encode map(const std::map& elems); /// encode a tag and following element static Encode tag(uint64_t value, const Encode& elem); + /// encode a null value (special) + static Encode null(); /// Stateful building (for indefinite length) /// Start an indefinite-length array @@ -52,22 +56,28 @@ class Encode { /// Create from raw content, must be valid CBOR data, may throw static Encode fromRaw(const TW::Data& rawData); + const Data& getDataInternal() const { return _data; } private: Encode() {} - Encode(const TW::Data& rawData) : data(rawData) {} + Encode(const TW::Data& rawData) : _data(rawData) {} /// Append types + value, on variable number of bytes (1..8). Return object to support chain syntax. Encode appendValue(byte majorType, uint64_t value); - inline Encode append(const TW::Data& data) { TW::append(this->data, data); return *this; } + inline Encode append(const TW::Data& data) { TW::append(_data, data); return *this; } void appendIndefinite(byte majorType); private: /// Encoded data is stored here, always well-formed, but my be partial. - TW::Data data; + TW::Data _data; /// number of currently open indefinite buildingds (0, 1, or more for nested) int openIndefCount = 0; }; +/// Comparator, needed for map keys +inline bool operator<(const Encode& lhs, const Encode& rhs) { + return lhs.getDataInternal() < rhs.getDataInternal(); +} + /// CBOR Decoder and container for data for decoding. Contains reference to read-only CBOR data. /// See CborTests.cpp for usage. class Decode { diff --git a/src/Coin.cpp b/src/Coin.cpp index d94cf75a7d8..efe10e47e56 100644 --- a/src/Coin.cpp +++ b/src/Coin.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -16,6 +16,7 @@ #include "Aeternity/Entry.h" #include "Aion/Entry.h" #include "Algorand/Entry.h" +#include "Aptos/Entry.h" #include "Binance/Entry.h" #include "Bitcoin/Entry.h" #include "Cardano/Entry.h" @@ -24,6 +25,7 @@ #include "EOS/Entry.h" #include "Elrond/Entry.h" #include "Ethereum/Entry.h" +#include "Everscale/Entry.h" #include "FIO/Entry.h" #include "Filecoin/Entry.h" #include "Groestlcoin/Entry.h" @@ -36,20 +38,21 @@ #include "NULS/Entry.h" #include "Nano/Entry.h" #include "Nebulas/Entry.h" +#include "Nervos/Entry.h" #include "Nimiq/Entry.h" #include "Oasis/Entry.h" #include "Ontology/Entry.h" #include "Polkadot/Entry.h" -#include "Ripple/Entry.h" +#include "Ronin/Entry.h" #include "Solana/Entry.h" #include "Stellar/Entry.h" #include "THORChain/Entry.h" #include "Tezos/Entry.h" #include "Theta/Entry.h" #include "Tron/Entry.h" -#include "TrustWalletCore/TWCoinType.h" #include "VeChain/Entry.h" #include "Waves/Entry.h" +#include "XRP/Entry.h" #include "Zcash/Entry.h" #include "Zilliqa/Entry.h" // end_of_coin_includes_marker_do_not_modify @@ -61,6 +64,7 @@ using namespace std; Aeternity::Entry aeternityDP; Aion::Entry aionDP; Algorand::Entry algorandDP; +Aptos::Entry AptosDP; Binance::Entry binanceDP; Bitcoin::Entry bitcoinDP; Cardano::Entry cardanoDP; @@ -86,6 +90,7 @@ Ontology::Entry ontologyDP; Oasis::Entry oasisDP; Polkadot::Entry polkadotDP; Ripple::Entry rippleDP; +Ronin::Entry roninDP; Solana::Entry solanaDP; Stellar::Entry stellarDP; Tezos::Entry tezosDP; @@ -96,87 +101,58 @@ VeChain::Entry vechainDP; Waves::Entry wavesDP; Zcash::Entry zcashDP; Zilliqa::Entry zilliqaDP; +Nervos::Entry NervosDP; +Everscale::Entry EverscaleDP; // end_of_coin_dipatcher_declarations_marker_do_not_modify CoinEntry* coinDispatcher(TWCoinType coinType) { // switch is preferred instead of a data structure, due to initialization issues CoinEntry* entry = nullptr; - switch (coinType) { + const auto blockchain = TW::blockchain(coinType); + switch (blockchain) { // #coin-list# - case TWCoinTypeAeternity: entry = &aeternityDP; break; - case TWCoinTypeAion: entry = &aionDP; break; - case TWCoinTypeAlgorand: entry = &algorandDP; break; - case TWCoinTypeBinance: entry = &binanceDP; break; - case TWCoinTypeBitcoin: entry = &bitcoinDP; break; - case TWCoinTypeBitcoinCash: entry = &bitcoinDP; break; - case TWCoinTypeBitcoinGold: entry = &bitcoinDP; break; - case TWCoinTypeDash: entry = &bitcoinDP; break; - case TWCoinTypeDigiByte: entry = &bitcoinDP; break; - case TWCoinTypeDogecoin: entry = &bitcoinDP; break; - case TWCoinTypeLitecoin: entry = &bitcoinDP; break; - case TWCoinTypeMonacoin: entry = &bitcoinDP; break; - case TWCoinTypeQtum: entry = &bitcoinDP; break; - case TWCoinTypeRavencoin: entry = &bitcoinDP; break; - case TWCoinTypeViacoin: entry = &bitcoinDP; break; - case TWCoinTypeZcoin: entry = &bitcoinDP; break; - case TWCoinTypeCardano: entry = &cardanoDP; break; - case TWCoinTypeCosmos: entry = &cosmosDP; break; - case TWCoinTypeKava: entry = &cosmosDP; break; - case TWCoinTypeTerra: entry = &cosmosDP; break; - case TWCoinTypeBandChain: entry = &cosmosDP; break; - case TWCoinTypeBluzelle: entry = &cosmosDP; break; - case TWCoinTypeElrond: entry = &elrondDP; break; - case TWCoinTypeEOS: entry = &eosDP; break; - case TWCoinTypeCallisto: entry = ðereumDP; break; - case TWCoinTypeEthereum: entry = ðereumDP; break; - case TWCoinTypeEthereumClassic: entry = ðereumDP; break; - case TWCoinTypeGoChain: entry = ðereumDP; break; - case TWCoinTypePOANetwork: entry = ðereumDP; break; - case TWCoinTypeThunderToken: entry = ðereumDP; break; - case TWCoinTypeTomoChain: entry = ðereumDP; break; - case TWCoinTypeSmartChainLegacy: entry = ðereumDP; break; - case TWCoinTypeSmartChain: entry = ðereumDP; break; - case TWCoinTypeDecred: entry = &decredDP; break; - case TWCoinTypeFilecoin: entry = &filecoinDP; break; - case TWCoinTypeFIO: entry = &fioDP; break; - case TWCoinTypeGroestlcoin: entry = &groestlcoinDP; break; - case TWCoinTypeHarmony: entry = &harmonyDP; break; - case TWCoinTypeICON: entry = &iconDP; break; - case TWCoinTypeIoTeX: entry = &iotexDP; break; - case TWCoinTypeKusama: entry = &kusamaDP; break; - case TWCoinTypeNano: entry = &nanoDP; break; - case TWCoinTypeNEAR: entry = &nearDP; break; - case TWCoinTypeNebulas: entry = &nebulasDP; break; - case TWCoinTypeNEO: entry = &neoDP; break; - case TWCoinTypeNimiq: entry = &nimiqDP; break; - case TWCoinTypeNULS: entry = &nulsDP; break; - case TWCoinTypeOasis: entry = &oasisDP; break; - case TWCoinTypeOntology: entry = &ontologyDP; break; - case TWCoinTypePolkadot: entry = &polkadotDP; break; - case TWCoinTypeXRP: entry = &rippleDP; break; - case TWCoinTypeSolana: entry = &solanaDP; break; - case TWCoinTypeStellar: entry = &stellarDP; break; - case TWCoinTypeKin: entry = &stellarDP; break; - case TWCoinTypeTezos: entry = &tezosDP; break; - case TWCoinTypeTheta: entry = &thetaDP; break; - case TWCoinTypeTHORChain: entry = &thorchainDP; break; - case TWCoinTypeTron: entry = &tronDP; break; - case TWCoinTypeVeChain: entry = &vechainDP; break; - case TWCoinTypeWanchain: entry = ðereumDP; break; - case TWCoinTypeWaves: entry = &wavesDP; break; - case TWCoinTypeZcash: entry = &zcashDP; break; - case TWCoinTypeZelcash: entry = &zcashDP; break; - case TWCoinTypeZilliqa: entry = &zilliqaDP; break; - case TWCoinTypePolygon: entry = ðereumDP; break; - case TWCoinTypeOptimism: entry = ðereumDP; break; - case TWCoinTypeArbitrum: entry = ðereumDP; break; - case TWCoinTypeECOChain: entry = ðereumDP; break; - case TWCoinTypeAvalancheCChain: entry = ðereumDP; break; - case TWCoinTypeXDai: entry = ðereumDP; break; - case TWCoinTypeFantom: entry = ðereumDP; break; - case TWCoinTypeCelo: entry = ðereumDP; break; - case TWCoinTypeRonin: entry = ðereumDP; break; - case TWCoinTypeCryptoOrg: entry = &cosmosDP; break; + case TWBlockchainBitcoin: entry = &bitcoinDP; break; + case TWBlockchainEthereum: entry = ðereumDP; break; + case TWBlockchainVechain: entry = &vechainDP; break; + case TWBlockchainTron: entry = &tronDP; break; + case TWBlockchainIcon: entry = &iconDP; break; + case TWBlockchainBinance: entry = &binanceDP; break; + case TWBlockchainRipple: entry = &rippleDP; break; + case TWBlockchainTezos: entry = &tezosDP; break; + case TWBlockchainNimiq: entry = &nimiqDP; break; + case TWBlockchainStellar: entry = &stellarDP; break; + case TWBlockchainAion: entry = &aionDP; break; + case TWBlockchainCosmos: entry = &cosmosDP; break; + case TWBlockchainTheta: entry = &thetaDP; break; + case TWBlockchainOntology: entry = &ontologyDP; break; + case TWBlockchainZilliqa: entry = &zilliqaDP; break; + case TWBlockchainIoTeX: entry = &iotexDP; break; + case TWBlockchainEOS: entry = &eosDP; break; + case TWBlockchainNano: entry = &nanoDP; break; + case TWBlockchainNULS: entry = &nulsDP; break; + case TWBlockchainWaves: entry = &wavesDP; break; + case TWBlockchainAeternity: entry = &aeternityDP; break; + case TWBlockchainNebulas: entry = &nebulasDP; break; + case TWBlockchainFIO: entry = &fioDP; break; + case TWBlockchainSolana: entry = &solanaDP; break; + case TWBlockchainHarmony: entry = &harmonyDP; break; + case TWBlockchainNEAR: entry = &nearDP; break; + case TWBlockchainAlgorand: entry = &algorandDP; break; + case TWBlockchainPolkadot: entry = &polkadotDP; break; + case TWBlockchainCardano: entry = &cardanoDP; break; + case TWBlockchainNEO: entry = &neoDP; break; + case TWBlockchainFilecoin: entry = &filecoinDP; break; + case TWBlockchainElrondNetwork: entry = &elrondDP; break; + case TWBlockchainOasisNetwork: entry = &oasisDP; break; + case TWBlockchainDecred: entry = &decredDP; break; + case TWBlockchainGroestlcoin: entry = &groestlcoinDP; break; + case TWBlockchainZcash: entry = &zcashDP; break; + case TWBlockchainThorchain: entry = &thorchainDP; break; + case TWBlockchainRonin: entry = &roninDP; break; + case TWBlockchainKusama: entry = &kusamaDP; break; + case TWBlockchainNervos: entry = &NervosDP; break; + case TWBlockchainEverscale: entry = &EverscaleDP; break; + case TWBlockchainAptos: entry = &AptosDP; break; // end_of_coin_dipatcher_switch_marker_do_not_modify default: entry = nullptr; break; @@ -185,10 +161,21 @@ CoinEntry* coinDispatcher(TWCoinType coinType) { return entry; } -bool TW::validateAddress(TWCoinType coin, const std::string& string) { +const Derivation CoinInfo::derivationByName(TWDerivation nameIn) const { + if (nameIn == TWDerivationDefault && derivation.size() > 0) { + return derivation[0]; + } + for (auto deriv : derivation) { + if (deriv.name == nameIn) { + return deriv; + } + } + return Derivation(); +} + +bool TW::validateAddress(TWCoinType coin, const std::string& string, const char* hrp) { auto p2pkh = TW::p2pkhPrefix(coin); auto p2sh = TW::p2shPrefix(coin); - const auto* hrp = stringForHRP(TW::hrp(coin)); // dispatch auto* dispatcher = coinDispatcher(coin); @@ -196,8 +183,14 @@ bool TW::validateAddress(TWCoinType coin, const std::string& string) { return dispatcher->validateAddress(coin, string, p2pkh, p2sh, hrp); } -std::string TW::normalizeAddress(TWCoinType coin, const std::string& address) { - if (!TW::validateAddress(coin, address)) { +bool TW::validateAddress(TWCoinType coin, const std::string& string) { + const auto* hrp = stringForHRP(TW::hrp(coin)); + return TW::validateAddress(coin, string, hrp); +} + +std::string TW::normalizeAddress(TWCoinType coin, const std::string& address, const std::string& hrp) { + const char* rawHrp = hrp.empty() ? stringForHRP(TW::hrp(coin)) : hrp.c_str(); + if (!TW::validateAddress(coin, address, rawHrp)) { // invalid address, not normalizing return ""; } @@ -209,18 +202,29 @@ std::string TW::normalizeAddress(TWCoinType coin, const std::string& address) { } std::string TW::deriveAddress(TWCoinType coin, const PrivateKey& privateKey) { + return TW::deriveAddress(coin, privateKey, TWDerivationDefault); +} + +std::string TW::deriveAddress(TWCoinType coin, const PrivateKey& privateKey, TWDerivation derivation) { auto keyType = TW::publicKeyType(coin); - return TW::deriveAddress(coin, privateKey.getPublicKey(keyType)); + return TW::deriveAddress(coin, privateKey.getPublicKey(keyType), derivation); } -std::string TW::deriveAddress(TWCoinType coin, const PublicKey& publicKey) { +std::string TW::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const std::string& hrp) { auto p2pkh = TW::p2pkhPrefix(coin); - const auto* hrp = stringForHRP(TW::hrp(coin)); - + const char* hrpRaw = [&hrp, coin]() { + return hrp.empty() ? stringForHRP(TW::hrp(coin)) : hrp.c_str(); + }(); // dispatch auto* dispatcher = coinDispatcher(coin); assert(dispatcher != nullptr); - return dispatcher->deriveAddress(coin, publicKey, p2pkh, hrp); + return dispatcher->deriveAddress(coin, derivation, publicKey, p2pkh, hrpRaw); +} + +Data TW::addressToData(TWCoinType coin, const std::string& address) { + const auto* dispatcher = coinDispatcher(coin); + assert(dispatcher != nullptr); + return dispatcher->addressToData(coin, address); } void TW::anyCoinSign(TWCoinType coinType, const Data& dataIn, Data& dataOut) { @@ -247,6 +251,24 @@ void TW::anyCoinPlan(TWCoinType coinType, const Data& dataIn, Data& dataOut) { dispatcher->plan(coinType, dataIn, dataOut); } +Data TW::anyCoinPreImageHashes(TWCoinType coinType, const Data& txInputData) { + auto* dispatcher = coinDispatcher(coinType); + assert(dispatcher != nullptr); + return dispatcher->preImageHashes(coinType, txInputData); +} + +void TW::anyCoinCompileWithSignatures(TWCoinType coinType, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& txOutputOut) { + auto* dispatcher = coinDispatcher(coinType); + assert(dispatcher != nullptr); + dispatcher->compile(coinType, txInputData, signatures, publicKeys, txOutputOut); +} + +Data TW::anyCoinBuildTransactionInput(TWCoinType coinType, const std::string& from, const std::string& to, const uint256_t& amount, const std::string& asset, const std::string& memo, const std::string& chainId) { + auto* dispatcher = coinDispatcher(coinType); + assert(dispatcher != nullptr); + return dispatcher->buildTransactionInput(coinType, from, to, amount, asset, memo, chainId); +} + // Coin info accessors extern const CoinInfo getCoinInfo(TWCoinType coin); // in generated CoinInfoData.cpp file @@ -264,15 +286,31 @@ TWCurve TW::curve(TWCoinType coin) { } TWHDVersion TW::xpubVersion(TWCoinType coin) { - return getCoinInfo(coin).xpubVersion; + return getCoinInfo(coin).defaultDerivation().xpubVersion; } TWHDVersion TW::xprvVersion(TWCoinType coin) { - return getCoinInfo(coin).xprvVersion; + return getCoinInfo(coin).defaultDerivation().xprvVersion; +} + +TWHDVersion TW::xpubVersionDerivation(TWCoinType coin, TWDerivation derivation) { + return getCoinInfo(coin).derivationByName(derivation).xpubVersion; +} + +TWHDVersion TW::xprvVersionDerivation(TWCoinType coin, TWDerivation derivation) { + return getCoinInfo(coin).derivationByName(derivation).xprvVersion; } DerivationPath TW::derivationPath(TWCoinType coin) { - return DerivationPath(getCoinInfo(coin).derivationPath); + return DerivationPath(getCoinInfo(coin).defaultDerivation().path); +} + +DerivationPath TW::derivationPath(TWCoinType coin, TWDerivation derivation) { + return DerivationPath(getCoinInfo(coin).derivationByName(derivation).path); +} + +const char* TW::derivationName(TWCoinType coin, TWDerivation derivation) { + return getCoinInfo(coin).derivationByName(derivation).nameString; } enum TWPublicKeyType TW::publicKeyType(TWCoinType coin) { @@ -295,6 +333,10 @@ enum TWHRP TW::hrp(TWCoinType coin) { return getCoinInfo(coin).hrp; } +const char* TW::chainId(TWCoinType coin) { + return getCoinInfo(coin).chainId; +} + Hash::Hasher TW::publicKeyHasher(TWCoinType coin) { return getCoinInfo(coin).publicKeyHasher; } @@ -303,6 +345,10 @@ Hash::Hasher TW::base58Hasher(TWCoinType coin) { return getCoinInfo(coin).base58Hasher; } +Hash::Hasher TW::addressHasher(TWCoinType coin) { + return getCoinInfo(coin).addressHasher; +} + uint32_t TW::slip44Id(TWCoinType coin) { return getCoinInfo(coin).slip44; } @@ -334,9 +380,3 @@ TWString* _Nonnull TWCoinTypeConfigurationGetID(enum TWCoinType coin) { TWString* _Nonnull TWCoinTypeConfigurationGetName(enum TWCoinType coin) { return TWStringCreateWithUTF8Bytes(getCoinInfo(coin).name); } - -const std::vector TW::getSimilarCoinTypes(TWCoinType coinType) { - const auto* dispatcher = coinDispatcher(coinType); - assert(dispatcher != nullptr); - return dispatcher->coinTypes(); -} diff --git a/src/Coin.h b/src/Coin.h index d1ea0a1213c..aba8e7adef5 100644 --- a/src/Coin.h +++ b/src/Coin.h @@ -11,12 +11,15 @@ #include "DerivationPath.h" #include "PrivateKey.h" #include "PublicKey.h" +#include "uint256.h" +#include "CoinEntry.h" #include #include #include #include #include +#include #include #include @@ -29,8 +32,11 @@ std::vector getCoinTypes(); /// Validates an address for a particular coin. bool validateAddress(TWCoinType coin, const std::string& address); +/// Validates an address for a particular coin. +bool validateAddress(TWCoinType coin, const std::string& address, const char* hrp); + /// Validates and normalizes an address for a particular coin. -std::string normalizeAddress(TWCoinType coin, const std::string& address); +std::string normalizeAddress(TWCoinType coin, const std::string& address, const std::string& hrp = ""); /// Returns the blockchain for a coin type. TWBlockchain blockchain(TWCoinType coin); @@ -41,30 +47,51 @@ TWPurpose purpose(TWCoinType coin); /// Returns the curve that should be used for a coin type. TWCurve curve(TWCoinType coin); -/// Returns the xpub HD version that should be used for a coin type. +/// Returns the default xpub HD version that should be used for a coin type. TWHDVersion xpubVersion(TWCoinType coin); -/// Returns the xprv HD version that should be used for a coin type. +/// Returns the default xprv HD version that should be used for a coin type. TWHDVersion xprvVersion(TWCoinType coin); +/// Returns the xpub HD version for a TWDerivation. +TWHDVersion xpubVersionDerivation(TWCoinType coin, TWDerivation derivation); + +/// Returns the xprv HD version for a TWDerivation. +TWHDVersion xprvVersionDerivation(TWCoinType coin, TWDerivation derivation); + /// Returns the default derivation path for a particular coin. DerivationPath derivationPath(TWCoinType coin); +/// Returns an alternative derivation path for a particular coin, TWDerivationDefault for default. +DerivationPath derivationPath(TWCoinType coin, TWDerivation derivation); + +/// Returns the string name of a derivation for a particular coin. +const char* derivationName(TWCoinType coin, TWDerivation derivation); + /// Returns the public key type for a particular coin. enum TWPublicKeyType publicKeyType(TWCoinType coin); /// Derives the address for a particular coin from the private key. std::string deriveAddress(TWCoinType coin, const PrivateKey& privateKey); -/// Derives the address for a particular coin from the public key. -std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey); +/// Derives the address for a particular coin from the private key, with given derivation. +std::string deriveAddress(TWCoinType coin, const PrivateKey& privateKey, TWDerivation derivation); + +/// Derives the address for a particular coin from the public key, with given derivation. +std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation = TWDerivationDefault, const std::string& hrp = ""); + +/// Returns the binary representation of a string address +Data addressToData(TWCoinType coin, const std::string& address); -/// Hasher for deriving the public key hash. +/// Hasher for deriving the extended public key Hash::Hasher publicKeyHasher(TWCoinType coin); -/// Hasher to use for base 58 checksums. +/// Hasher to use for base 58 checksums in keys (extended private, public) Hash::Hasher base58Hasher(TWCoinType coin); +/// Hasher used inside address generation (hash of public key) +Hash::Hasher addressHasher(TWCoinType coin); + /// Returns static prefix for a coin type. byte staticPrefix(TWCoinType coin); @@ -77,6 +104,9 @@ byte p2shPrefix(TWCoinType coin); /// Returns human readable part for a coin type. enum TWHRP hrp(TWCoinType coin); +/// Returns chain ID. +const char* chainId(TWCoinType coin); + // Note: use output parameter to avoid unneeded copies void anyCoinSign(TWCoinType coinType, const Data& dataIn, Data& dataOut); @@ -88,8 +118,20 @@ bool supportsJSONSigning(TWCoinType coinType); void anyCoinPlan(TWCoinType coinType, const Data& dataIn, Data& dataOut); -// Return coins handled by the same dispatcher as the given coin (mostly for testing) -const std::vector getSimilarCoinTypes(TWCoinType coinType); +Data anyCoinPreImageHashes(TWCoinType coinType, const Data& txInputData); + +void anyCoinCompileWithSignatures(TWCoinType coinType, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& txOutputOut); + +Data anyCoinBuildTransactionInput(TWCoinType coinType, const std::string& from, const std::string& to, const uint256_t& amount, const std::string& asset, const std::string& memo, const std::string& chainId); + +// Describes a derivation: path + optional format + optional name +struct Derivation { + TWDerivation name = TWDerivationDefault; + const char* path = ""; + const char* nameString = ""; + TWHDVersion xpubVersion = TWHDVersionNone; + TWHDVersion xprvVersion = TWHDVersionNone; +}; // Contains only simple types. struct CoinInfo { @@ -98,21 +140,27 @@ struct CoinInfo { TWBlockchain blockchain; TWPurpose purpose; TWCurve curve; - TWHDVersion xpubVersion; - TWHDVersion xprvVersion; - const char* derivationPath; + std::vector derivation; TWPublicKeyType publicKeyType; byte staticPrefix; byte p2pkhPrefix; byte p2shPrefix; TWHRP hrp; + const char* chainId; Hash::Hasher publicKeyHasher; Hash::Hasher base58Hasher; + Hash::Hasher addressHasher; const char* symbol; int decimals; const char* explorerTransactionUrl; const char* explorerAccountUrl; uint32_t slip44; + + // returns default derivation + const Derivation defaultDerivation() const { + return (derivation.size() > 0) ? derivation[0] : Derivation(); + } + const Derivation derivationByName(TWDerivation name) const; }; } // namespace TW diff --git a/src/CoinEntry.h b/src/CoinEntry.h index c42223d9d2a..b0ab4bd72d3 100644 --- a/src/CoinEntry.h +++ b/src/CoinEntry.h @@ -7,34 +7,58 @@ #pragma once #include +#include #include "Data.h" #include "PublicKey.h" #include "PrivateKey.h" +#include "proto/Common.pb.h" +#include "uint256.h" #include #include +#include namespace TW { +typedef std::vector> HashPubkeyList; + /// Interface for coin-specific entry, used to dispatch calls to coins /// Implement this for all coins. class CoinEntry { public: - // Report the coin types this implementation is responsible of - virtual const std::vector coinTypes() const = 0; + virtual ~CoinEntry() noexcept = default; virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const = 0; // normalizeAddress is optional, it may leave this default, no-change implementation - virtual std::string normalizeAddress(TWCoinType coin, const std::string& address) const { return address; } + virtual std::string normalizeAddress([[maybe_unused]] TWCoinType coin, const std::string& address) const { return address; } + // Address derivation, default derivation virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const = 0; + // Address derivation, by default invoking default + virtual std::string deriveAddress(TWCoinType coin, [[maybe_unused]] TWDerivation derivation, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const { + return deriveAddress(coin, publicKey, p2pkh, hrp); + } + // Return the binary representation of a string address, used by AnyAddress + // It is optional, if not defined, 'AnyAddress' interface will not support this coin. + virtual Data addressToData([[maybe_unused]] TWCoinType coin, [[maybe_unused]] const std::string& address) const { return {}; } // Signing virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const = 0; virtual bool supportsJSONSigning() const { return false; } // It is optional, Signing JSON input with private key - virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const { return ""; } + virtual std::string signJSON([[maybe_unused]] TWCoinType coin, [[maybe_unused]] const std::string& json, [[maybe_unused]] const Data& key) const { return ""; } // Planning, for UTXO chains, in preparation for signing // It is optional, only UTXO chains need it, default impl. leaves empty result. - virtual void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const { return; } + virtual void plan([[maybe_unused]] TWCoinType coin, [[maybe_unused]] const Data& dataIn, [[maybe_unused]] Data& dataOut) const { } + + // Optional method for obtaining hash(es) for signing, needed for external signing. + // It will return a proto object named `PreSigningOutput` which will include hash. + // We provide a default `PreSigningOutput` in TransactionCompiler.proto. + // For some special coins, such as bitcoin, we will create a custom `PreSigningOutput` object in its proto file. + virtual Data preImageHashes([[maybe_unused]] TWCoinType coin, [[maybe_unused]] const Data& txInputData) const { return {}; } + // Optional method for compiling a transaction with externally-supplied signatures & pubkeys. + virtual void compile([[maybe_unused]] TWCoinType coin, [[maybe_unused]] const Data& txInputData, [[maybe_unused]] const std::vector& signatures, [[maybe_unused]] const std::vector& publicKeys, [[maybe_unused]] Data& dataOut) const {} + // Optional helper to prepare a SigningInput from simple parameters. + // Not suitable for UTXO chains. Some parameters, like chain-specific fee/gas paraemters, may need to be set in the SigningInput. + virtual Data buildTransactionInput([[maybe_unused]] TWCoinType coinType, [[maybe_unused]] const std::string& from, [[maybe_unused]] const std::string& to, [[maybe_unused]] const uint256_t& amount, [[maybe_unused]] const std::string& asset, [[maybe_unused]] const std::string& memo, [[maybe_unused]] const std::string& chainId) const { return Data(); } }; // In each coin's Entry.cpp the specific types of the coin are used, this template enforces the Signer implement: @@ -57,4 +81,26 @@ void planTemplate(const Data& dataIn, Data& dataOut) { dataOut.insert(dataOut.end(), serializedOut.begin(), serializedOut.end()); } +// This template will be used for preImageHashes and compile in each coin's Entry.cpp. +// It is a helper function to simplify exception handle. +template +Data txCompilerTemplate(const Data& dataIn, Func&& fnHandler) { + auto input = Input(); + auto output = Output(); + if (!input.ParseFromArray(dataIn.data(), (int)dataIn.size())) { + output.set_error(Common::Proto::Error_input_parse); + output.set_error_message("failed to parse input data"); + return TW::data(output.SerializeAsString());; + } + + try { + // each coin function handler + fnHandler(input, output); + } catch (const std::exception& e) { + output.set_error(Common::Proto::Error_internal); + output.set_error_message(e.what()); + } + return TW::data(output.SerializeAsString()); +} + } // namespace TW diff --git a/src/Cosmos/Address.h b/src/Cosmos/Address.h index c528317f3a5..cb629cfa607 100644 --- a/src/Cosmos/Address.h +++ b/src/Cosmos/Address.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -7,7 +7,7 @@ #pragma once #include "../Bech32Address.h" -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include "../Coin.h" #include @@ -17,7 +17,7 @@ namespace TW::Cosmos { -/// A Bech32 Cosmos address. Hrp has to be specified (e.g. "cosmos", "terra"...), hash is HASHER_SHA2_RIPEMD. +/// A Bech32 Cosmos address. Hrp has to be specified (e.g. "cosmos", "terra"...), hash is coin-specific (from config, usually sha256ripemd). class Address: public Bech32Address { public: Address() : Bech32Address("") {} @@ -29,10 +29,10 @@ class Address: public Bech32Address { Address(const std::string& hrp, const Data& keyHash) : Bech32Address(hrp, keyHash) {} /// Initializes an address with a public key, with prefix of the given coin. - Address(TWCoinType coin, const PublicKey& publicKey) : Bech32Address(stringForHRP(TW::hrp(coin)), HASHER_SHA2_RIPEMD, publicKey) {} + Address(TWCoinType coin, const PublicKey& publicKey) : Bech32Address(stringForHRP(TW::hrp(coin)), TW::addressHasher(coin), publicKey) {} /// Initializes an address with a public key, with given prefix. - Address(const std::string& hrp, const PublicKey& publicKey) : Bech32Address(hrp, HASHER_SHA2_RIPEMD, publicKey) {} + Address(const std::string& hrp, const PublicKey& publicKey, TWCoinType coin = TWCoinTypeCosmos) : Bech32Address(hrp, TW::addressHasher(coin), publicKey) {} /// Determines whether a string makes a valid Bech32 address, and the HRP matches to the coin. static bool isValid(TWCoinType coin, const std::string& addr) { @@ -40,6 +40,11 @@ class Address: public Bech32Address { return Bech32Address::isValid(addr, hrp); } + /// Determines whether a string makes a valid Bech32 address with the given hrp. + static bool isValid(const std::string& addr, const std::string& hrp) { + return Bech32Address::isValid(addr, hrp); + } + /// Creates an address object from the given string, if valid. Returns success. static bool decode(const std::string& addr, Address& obj_out) { return Bech32Address::decode(addr, obj_out, ""); diff --git a/src/Cosmos/Entry.cpp b/src/Cosmos/Entry.cpp index 8b463c102e7..6cf9026fddd 100644 --- a/src/Cosmos/Entry.cpp +++ b/src/Cosmos/Entry.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,23 +9,44 @@ #include "Address.h" #include "Signer.h" -using namespace TW::Cosmos; +using namespace TW; using namespace std; +namespace TW::Cosmos { + // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char* hrp) const { - return Address::isValid(coin, address); + if (hrpForString(hrp) != TWHRPUnknown) { + return Address::isValid(coin, address); + } + return Address::isValid(address, hrp); } string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char* hrp) const { + if (!std::string(hrp).empty()) { + return Address(hrp, publicKey, coin).string(); + } return Address(coin, publicKey).string(); } +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + Address addr; + if (!Address::decode(address, addr)) { + return Data(); + } + return addr.getKeyHash(); +} + void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - signTemplate(dataIn, dataOut); + auto input = Proto::SigningInput(); + input.ParseFromArray(dataIn.data(), (int)dataIn.size()); + auto serializedOut = Signer::sign(input, coin).SerializeAsString(); + dataOut.insert(dataOut.end(), serializedOut.begin(), serializedOut.end()); } -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { - return Signer::signJSON(json, key); +string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { + return Signer::signJSON(json, key, coin); } + +} // namespace TW::Cosmos diff --git a/src/Cosmos/Entry.h b/src/Cosmos/Entry.h index b98b5b005ef..e5be0796622 100644 --- a/src/Cosmos/Entry.h +++ b/src/Cosmos/Entry.h @@ -12,23 +12,14 @@ namespace TW::Cosmos { /// Entry point for implementation of Cosmos coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry : public CoinEntry { public: - virtual const std::vector coinTypes() const { - return { - TWCoinTypeCosmos, - TWCoinTypeKava, - TWCoinTypeTerra, - TWCoinTypeBandChain, - TWCoinTypeBluzelle, - TWCoinTypeCryptoOrg, - }; - } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual bool supportsJSONSigning() const { return true; } - virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const final; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const final; + Data addressToData(TWCoinType coin, const std::string& address) const final; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + bool supportsJSONSigning() const final { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const override; }; } // namespace TW::Cosmos diff --git a/src/Cosmos/Serialization.cpp b/src/Cosmos/JsonSerialization.cpp similarity index 76% rename from src/Cosmos/Serialization.cpp rename to src/Cosmos/JsonSerialization.cpp index 64104df731c..bb7e28c98b8 100644 --- a/src/Cosmos/Serialization.cpp +++ b/src/Cosmos/JsonSerialization.cpp @@ -1,10 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "Serialization.h" +#include "JsonSerialization.h" +#include "ProtobufSerialization.h" #include "../Cosmos/Address.h" #include "../proto/Cosmos.pb.h" @@ -12,7 +13,8 @@ #include "PrivateKey.h" using namespace TW; -using namespace TW::Cosmos; + +namespace TW::Cosmos::Json { using json = nlohmann::json; using string = std::string; @@ -23,6 +25,15 @@ const string TYPE_PREFIX_MSG_UNDELEGATE = "cosmos-sdk/MsgUndelegate"; const string TYPE_PREFIX_MSG_REDELEGATE = "cosmos-sdk/MsgBeginRedelegate"; const string TYPE_PREFIX_MSG_WITHDRAW_REWARD = "cosmos-sdk/MsgWithdrawDelegationReward"; const string TYPE_PREFIX_PUBLIC_KEY = "tendermint/PubKeySecp256k1"; +const string TYPE_EVMOS_PREFIX_PUBLIC_KEY = "ethermint/PubKeyEthSecp256k1"; +const string TYPE_PREFIX_WASM_MSG_EXECUTE = "wasm/MsgExecuteContract"; + +static inline std::string coinTypeToPrefixPublicKey(TWCoinType coin) noexcept { + if (coin == TWCoinTypeNativeEvmos) { + return TYPE_EVMOS_PREFIX_PUBLIC_KEY; + } + return TYPE_PREFIX_PUBLIC_KEY; +} static string broadcastMode(Proto::BroadcastMode mode) { switch (mode) { @@ -43,7 +54,7 @@ static json broadcastJSON(json& j, Proto::BroadcastMode mode) { static json amountJSON(const Proto::Amount& amount) { return { - {"amount", std::to_string(amount.amount())}, + {"amount", amount.amount()}, {"denom", amount.denom()} }; } @@ -134,12 +145,27 @@ static json messageWithdrawReward(const Proto::Message_WithdrawDelegationReward& }; } +json messageWasmTerraTransfer(const Proto::Message_WasmTerraExecuteContractTransfer& msg) { + return { + {"type", TYPE_PREFIX_WASM_MSG_EXECUTE}, + {"value", + { + {"sender", msg.sender_address()}, + {"contract", msg.contract_address()}, + {"execute_msg", Protobuf::wasmTerraExecuteTransferPayload(msg)}, + {"coins", json::array()} // used in case you are sending native tokens along with this message + } + } + }; +} + static json messageRawJSON(const Proto::Message_RawJSON& message) { return { {"type", message.type()}, {"value", json::parse(message.value())}, }; } + static json messagesJSON(const Proto::SigningInput& input) { json j = json::array(); for (auto& msg : input.messages()) { @@ -155,22 +181,27 @@ static json messagesJSON(const Proto::SigningInput& input) { j.push_back(messageRedelegate(msg.restake_message())); } else if (msg.has_raw_json_message()) { j.push_back(messageRawJSON(msg.raw_json_message())); + } else if ((msg.has_wasm_terra_execute_contract_transfer_message())) { + j.push_back(messageWasmTerraTransfer(msg.wasm_terra_execute_contract_transfer_message())); + } else if (msg.has_transfer_tokens_message() || msg.has_wasm_terra_execute_contract_generic()) { + assert(false); // not supported, use protobuf serialization + return json::array(); } } return j; } -static json signatureJSON(const Data& signature, const Data& pubkey) { +json signatureJSON(const Data& signature, const Data& pubkey, TWCoinType coin) { return { {"pub_key", { - {"type", TYPE_PREFIX_PUBLIC_KEY}, + {"type", coinTypeToPrefixPublicKey(coin)}, {"value", Base64::encode(pubkey)} }}, {"signature", Base64::encode(signature)} }; } -json Cosmos::signaturePreimage(const Proto::SigningInput& input) { +json signaturePreimageJSON(const Proto::SigningInput& input) { return { {"account_number", std::to_string(input.account_number())}, {"chain_id", input.chain_id()}, @@ -181,7 +212,7 @@ json Cosmos::signaturePreimage(const Proto::SigningInput& input) { }; } -json Cosmos::transactionJSON(const Proto::SigningInput& input, const Data& signature) { +json transactionJSON(const Proto::SigningInput& input, const Data& signature, TWCoinType coin) { auto privateKey = PrivateKey(input.private_key()); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); json tx = { @@ -189,8 +220,10 @@ json Cosmos::transactionJSON(const Proto::SigningInput& input, const Data& signa {"memo", input.memo()}, {"msg", messagesJSON(input)}, {"signatures", json::array({ - signatureJSON(signature, Data(publicKey.bytes)) + signatureJSON(signature, Data(publicKey.bytes), coin) })} }; return broadcastJSON(tx, input.mode()); } + +} // namespace TW::Cosmos diff --git a/src/Cosmos/JsonSerialization.h b/src/Cosmos/JsonSerialization.h new file mode 100644 index 00000000000..72212d972f9 --- /dev/null +++ b/src/Cosmos/JsonSerialization.h @@ -0,0 +1,31 @@ +// Copyright © 2017-2021 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Data.h" +#include "../proto/Cosmos.pb.h" +#include +#include + +extern const std::string TYPE_PREFIX_MSG_SEND; +extern const std::string TYPE_PREFIX_MSG_TRANSFER; +extern const std::string TYPE_PREFIX_MSG_DELEGATE; +extern const std::string TYPE_PREFIX_MSG_UNDELEGATE; +extern const std::string TYPE_PREFIX_MSG_REDELEGATE; +extern const std::string TYPE_PREFIX_MSG_WITHDRAW_REWARD; +extern const std::string TYPE_PREFIX_PUBLIC_KEY; + +namespace TW::Cosmos::Json { + +using string = std::string; +using json = nlohmann::json; + +json signaturePreimageJSON(const Proto::SigningInput& input); +json transactionJSON(const Proto::SigningInput& input, const Data& signature, TWCoinType coin); +json signatureJSON(const Data& signature, const Data& pubkey, TWCoinType coin); + +} // namespace TW::Cosmos::json diff --git a/src/Cosmos/Protobuf/.clang-tidy b/src/Cosmos/Protobuf/.clang-tidy new file mode 100644 index 00000000000..2c22f7387dd --- /dev/null +++ b/src/Cosmos/Protobuf/.clang-tidy @@ -0,0 +1,6 @@ +--- +InheritParentConfig: false +Checks: '-*,misc-definitions-in-headers' +CheckOptions: + - { key: HeaderFileExtensions, value: "x" } +... diff --git a/src/Cosmos/Protobuf/.gitignore b/src/Cosmos/Protobuf/.gitignore new file mode 100644 index 00000000000..c96d61208c0 --- /dev/null +++ b/src/Cosmos/Protobuf/.gitignore @@ -0,0 +1,3 @@ +*.cc +*.h + diff --git a/src/Cosmos/Protobuf/authz_tx.proto b/src/Cosmos/Protobuf/authz_tx.proto new file mode 100644 index 00000000000..b92c03bd3f8 --- /dev/null +++ b/src/Cosmos/Protobuf/authz_tx.proto @@ -0,0 +1,32 @@ +// Since: cosmos-sdk 0.43 +syntax = "proto3"; +package cosmos.authz.v1beta1; + +import "google/protobuf/any.proto"; +import "google/protobuf/timestamp.proto"; + +// Grant gives permissions to execute +// the provide method with expiration time. +message Grant { + google.protobuf.Any authorization = 1; + // time when the grant will expire and will be pruned. If null, then the grant + // doesn't have a time expiration (other conditions in `authorization` + // may apply to invalidate the grant) + google.protobuf.Timestamp expiration = 2; +} + +// MsgGrant is a request type for Grant method. It declares authorization to the grantee +// on behalf of the granter with the provided expiration time. +message MsgGrant { + string granter = 1; + string grantee = 2; + Grant grant = 3; +} + +// MsgRevoke revokes any authorization with the provided sdk.Msg type on the +// granter's account with that has been granted to the grantee. +message MsgRevoke { + string granter = 1; + string grantee = 2; + string msg_type_url = 3; +} diff --git a/src/Cosmos/Protobuf/bank_tx.proto b/src/Cosmos/Protobuf/bank_tx.proto new file mode 100644 index 00000000000..08d4efa19af --- /dev/null +++ b/src/Cosmos/Protobuf/bank_tx.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; +package cosmos.bank.v1beta1; + +// Src: https://github.com/cosmos/cosmos-sdk/blob/master/proto/cosmos/bank/v1beta1/tx.proto + +import "coin.proto"; + +// MsgSend represents a message to send coins from one account to another. +message MsgSend { + string from_address = 1; + string to_address = 2; + repeated base.v1beta1.Coin amount = 3; +} diff --git a/src/Cosmos/Protobuf/coin.proto b/src/Cosmos/Protobuf/coin.proto new file mode 100644 index 00000000000..93db5482105 --- /dev/null +++ b/src/Cosmos/Protobuf/coin.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; +package cosmos.base.v1beta1; + +// Src: https://github.com/cosmos/cosmos-sdk/blob/master/proto/cosmos/base/v1beta1/coin.proto + +// Coin defines a token with a denomination and an amount. +// Note: The amount field is an Int as string. +message Coin { + string denom = 1; + string amount = 2; +} + +// Omitted: +// DecCoin +// IntProto +// DecProto diff --git a/src/Cosmos/Protobuf/cosmwasm_wasm_v1_tx.proto b/src/Cosmos/Protobuf/cosmwasm_wasm_v1_tx.proto new file mode 100644 index 00000000000..68f0995a016 --- /dev/null +++ b/src/Cosmos/Protobuf/cosmwasm_wasm_v1_tx.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; +package cosmwasm.wasm.v1; + +// Src: https://github.com/CosmWasm/wasmd/blob/main/proto/cosmwasm/wasm/v1/tx.proto + +import "coin.proto"; + +// MsgExecuteContract submits the given message data to a smart contract +message MsgExecuteContract { + // Sender is the that actor that signed the messages + string sender = 1; + // Contract is the address of the smart contract + string contract = 2; + // Msg json encoded message to be passed to the contract + bytes msg = 3; + // Funds coins that are transferred to the contract on execution + // Gap in field numbering is intentional! + repeated cosmos.base.v1beta1.Coin funds = 5; +} diff --git a/src/Cosmos/Protobuf/crypto_multisig.proto b/src/Cosmos/Protobuf/crypto_multisig.proto new file mode 100644 index 00000000000..d87e98dfc76 --- /dev/null +++ b/src/Cosmos/Protobuf/crypto_multisig.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; +package cosmos.multisig.v1beta1; + +// Src: https://github.com/cosmos/cosmos-sdk/blob/master/proto/cosmos/crypto/multisig/v1beta1/multisig.proto + +// MultiSignature is omitted + +// CompactBitArray is an implementation of a space efficient bit array. +// This is used to ensure that the encoded data takes up a minimal amount of +// space after proto encoding. +// This is not thread safe, and is not intended for concurrent usage. +message CompactBitArray { + uint32 extra_bits_stored = 1; + bytes elems = 2; +} diff --git a/src/Cosmos/Protobuf/crypto_secp256k1_keys.proto b/src/Cosmos/Protobuf/crypto_secp256k1_keys.proto new file mode 100644 index 00000000000..4e9035ddec0 --- /dev/null +++ b/src/Cosmos/Protobuf/crypto_secp256k1_keys.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; +package cosmos.crypto.secp256k1; + +// Src: https://github.com/cosmos/cosmos-sdk/blob/master/proto/cosmos/crypto/secp256k1/keys.proto + +// PubKey defines a secp256k1 public key +// Key is the compressed form of the pubkey. The first byte depends is a 0x02 byte +// if the y-coordinate is the lexicographically largest of the two associated with +// the x-coordinate. Otherwise the first byte is a 0x03. +// This prefix is followed with the x-coordinate. +message PubKey { + bytes key = 1; +} + +// PrivKey is omitted diff --git a/src/Cosmos/Protobuf/distribution_tx.proto b/src/Cosmos/Protobuf/distribution_tx.proto new file mode 100644 index 00000000000..1182c562140 --- /dev/null +++ b/src/Cosmos/Protobuf/distribution_tx.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; +package cosmos.distribution.v1beta1; + +// Src: https://github.com/cosmos/cosmos-sdk/blob/master/proto/cosmos/distribution/v1beta1/tx.proto + +// MsgWithdrawDelegatorReward represents delegation withdrawal to a delegator +// from a single validator. +message MsgWithdrawDelegatorReward { + string delegator_address = 1; + string validator_address = 2; +} diff --git a/src/Cosmos/Protobuf/ethermint_keys.proto b/src/Cosmos/Protobuf/ethermint_keys.proto new file mode 100644 index 00000000000..07675c19f45 --- /dev/null +++ b/src/Cosmos/Protobuf/ethermint_keys.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; +package ethermint.crypto.v1.ethsecp256k1; + +// PubKey defines a type alias for an ecdsa.PublicKey that implements +// Tendermint's PubKey interface. It represents the 33-byte compressed public +// key format. +message PubKey { + bytes key = 1; +} diff --git a/src/Cosmos/Protobuf/gov_tx.proto b/src/Cosmos/Protobuf/gov_tx.proto new file mode 100644 index 00000000000..4f018a37e93 --- /dev/null +++ b/src/Cosmos/Protobuf/gov_tx.proto @@ -0,0 +1,24 @@ +// Since: cosmos-sdk 0.43 +syntax = "proto3"; +package cosmos.gov.v1beta1; + +// VoteOption enumerates the valid vote options for a given governance proposal. +enum VoteOption { + // VOTE_OPTION_UNSPECIFIED defines a no-op vote option. + VOTE_OPTION_UNSPECIFIED = 0; + // VOTE_OPTION_YES defines a yes vote option. + VOTE_OPTION_YES = 1; + // VOTE_OPTION_ABSTAIN defines an abstain vote option. + VOTE_OPTION_ABSTAIN = 2; + // VOTE_OPTION_NO defines a no vote option. + VOTE_OPTION_NO = 3; + // VOTE_OPTION_NO_WITH_VETO defines a no with veto vote option. + VOTE_OPTION_NO_WITH_VETO = 4; +} + +// MsgVote defines a message to cast a vote. +message MsgVote { + uint64 proposal_id = 1; + string voter = 2; + VoteOption option = 3; +} diff --git a/src/Cosmos/Protobuf/ibc_applications_transfer_tx.proto b/src/Cosmos/Protobuf/ibc_applications_transfer_tx.proto new file mode 100644 index 00000000000..79631a99000 --- /dev/null +++ b/src/Cosmos/Protobuf/ibc_applications_transfer_tx.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; +package ibc.applications.transfer.v1; + +// Src: https://github.com/cosmos/ibc-go/blob/main/proto/ibc/applications/transfer/v1/tx.proto + +import "coin.proto"; +import "ibc_core_client.proto"; + +// MsgTransfer defines a msg to transfer fungible tokens (i.e Coins) between ICS20 enabled chains. See ICS Spec here: +// https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#data-structures +message MsgTransfer { + // the port on which the packet will be sent + string source_port = 1; + // the channel by which the packet will be sent + string source_channel = 2; + // the tokens to be transferred + cosmos.base.v1beta1.Coin token = 3; + // the sender address + string sender = 4; + // the recipient address on the destination chain + string receiver = 5; + // Timeout height relative to the current block height. + // The timeout is disabled when set to 0. + ibc.core.client.v1.Height timeout_height = 6; + // Timeout timestamp (in nanoseconds) relative to the current block timestamp. + // The timeout is disabled when set to 0. + uint64 timeout_timestamp = 7; +} diff --git a/src/Cosmos/Protobuf/ibc_core_client.proto b/src/Cosmos/Protobuf/ibc_core_client.proto new file mode 100644 index 00000000000..7dc60606ac1 --- /dev/null +++ b/src/Cosmos/Protobuf/ibc_core_client.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; +package ibc.core.client.v1; + +// Src: https://github.com/cosmos/ibc-go/blob/main/proto/ibc/core/client/v1/client.proto + +// Height is a monotonically increasing data type +// that can be compared against another Height for the purposes of updating and +// freezing clients +message Height { + // the revision that the client is currently on + uint64 revision_number = 1; + // the height within the given revision + uint64 revision_height = 2; +} diff --git a/src/Cosmos/Protobuf/staking_tx.proto b/src/Cosmos/Protobuf/staking_tx.proto new file mode 100644 index 00000000000..42beec4a443 --- /dev/null +++ b/src/Cosmos/Protobuf/staking_tx.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; +package cosmos.staking.v1beta1; + +// Src: https://github.com/cosmos/cosmos-sdk/blob/master/proto/cosmos/staking/v1beta1/tx.proto + +import "coin.proto"; + +// MsgDelegate defines a SDK message for performing a delegation of coins +// from a delegator to a validator. +message MsgDelegate { + string delegator_address = 1; + string validator_address = 2; + base.v1beta1.Coin amount = 3; +} + +// MsgUndelegate defines a SDK message for performing an undelegation from a +// delegate and a validator. +message MsgUndelegate { + string delegator_address = 1; + string validator_address = 2; + base.v1beta1.Coin amount = 3; +} + +// MsgBeginRedelegate defines a SDK message for performing a redelegation +// of coins from a delegator and source validator to a destination validator. +message MsgBeginRedelegate { + string delegator_address = 1; + string validator_src_address = 2; + string validator_dst_address = 3; + base.v1beta1.Coin amount = 4; +} diff --git a/src/Cosmos/Protobuf/terra_wasm_v1beta1_tx.proto b/src/Cosmos/Protobuf/terra_wasm_v1beta1_tx.proto new file mode 100644 index 00000000000..a544e58b2ea --- /dev/null +++ b/src/Cosmos/Protobuf/terra_wasm_v1beta1_tx.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; +package terra.wasm.v1beta1; + +// Terra-Classic-specific fork, used only by Terra Classic +// Src: https://github.com/terra-money/core/blob/main/proto/terra/wasm/v1beta1/tx.proto +// original in https://github.com/CosmWasm/wasmd/blob/master/proto/cosmwasm/wasm/v1/tx.proto + +import "coin.proto"; + +// MsgExecuteContract submits the given message data to a smart contract +message MsgExecuteContract { + // Sender is the that actor that signed the messages + string sender = 1; + // Contract is the address of the smart contract + string contract = 2; + // ExecuteMsg json encoded message to be passed to the contract + bytes execute_msg = 3; + // Coins that are transferred to the contract on execution + // Gap in field numbering is intentional + repeated cosmos.base.v1beta1.Coin coins = 5; +} diff --git a/src/Cosmos/Protobuf/thorchain_bank_tx.proto b/src/Cosmos/Protobuf/thorchain_bank_tx.proto new file mode 100644 index 00000000000..0e5870ab162 --- /dev/null +++ b/src/Cosmos/Protobuf/thorchain_bank_tx.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; +package types; + +// Src: https://gitlab.com/thorchain/thornode/-/blob/develop/proto/thorchain/v1/x/thorchain/types/msg_send.proto +// Cosmos original: https://github.com/cosmos/cosmos-sdk/blob/master/proto/cosmos/bank/v1beta1/tx.proto + +import "coin.proto"; + +// MsgSend represents a message to send coins from one account to another. +message MsgSend { + bytes from_address = 1; + bytes to_address = 2; + repeated cosmos.base.v1beta1.Coin amount = 3; +} diff --git a/src/Cosmos/Protobuf/tx.proto b/src/Cosmos/Protobuf/tx.proto new file mode 100644 index 00000000000..b7973cda4bf --- /dev/null +++ b/src/Cosmos/Protobuf/tx.proto @@ -0,0 +1,195 @@ +syntax = "proto3"; +package cosmos; + +// Src: https://github.com/cosmos/cosmos-sdk/blob/master/proto/cosmos/tx/v1beta1/tx.proto + +import "coin.proto"; +import "crypto_multisig.proto"; +import "tx_signing.proto"; +import "google/protobuf/any.proto"; + +// Tx is the standard type used for broadcasting transactions. +message Tx { + // body is the processable content of the transaction + TxBody body = 1; + + // auth_info is the authorization related content of the transaction, + // specifically signers, signer modes and fee + AuthInfo auth_info = 2; + + // signatures is a list of signatures that matches the length and order of + // AuthInfo's signer_infos to allow connecting signature meta information like + // public key and signing mode by position. + repeated bytes signatures = 3; +} + +// TxRaw is a variant of Tx that pins the signer's exact binary representation +// of body and auth_info. This is used for signing, broadcasting and +// verification. The binary `serialize(tx: TxRaw)` is stored in Tendermint and +// the hash `sha256(serialize(tx: TxRaw))` becomes the "txhash", commonly used +// as the transaction ID. +message TxRaw { + // body_bytes is a protobuf serialization of a TxBody that matches the + // representation in SignDoc. + bytes body_bytes = 1; + + // auth_info_bytes is a protobuf serialization of an AuthInfo that matches the + // representation in SignDoc. + bytes auth_info_bytes = 2; + + // signatures is a list of signatures that matches the length and order of + // AuthInfo's signer_infos to allow connecting signature meta information like + // public key and signing mode by position. + repeated bytes signatures = 3; +} + +// SignDoc is the type used for generating sign bytes for SIGN_MODE_DIRECT. +message SignDoc { + // body_bytes is protobuf serialization of a TxBody that matches the + // representation in TxRaw. + bytes body_bytes = 1; + + // auth_info_bytes is a protobuf serialization of an AuthInfo that matches the + // representation in TxRaw. + bytes auth_info_bytes = 2; + + // chain_id is the unique identifier of the chain this transaction targets. + // It prevents signed transactions from being used on another chain by an + // attacker + string chain_id = 3; + + // account_number is the account number of the account in state + uint64 account_number = 4; +} + +// SignDocDirectAux is omitted + +// TxBody is the body of a transaction that all signers sign over. +message TxBody { + // messages is a list of messages to be executed. The required signers of + // those messages define the number and order of elements in AuthInfo's + // signer_infos and Tx's signatures. Each required signer address is added to + // the list only the first time it occurs. + // By convention, the first required signer (usually from the first message) + // is referred to as the primary signer and pays the fee for the whole + // transaction. + repeated google.protobuf.Any messages = 1; + + // memo is any arbitrary note/comment to be added to the transaction. + // WARNING: in clients, any publicly exposed text should not be called memo, + // but should be called `note` instead (see https://github.com/cosmos/cosmos-sdk/issues/9122). + string memo = 2; + + // timeout is the block height after which this transaction will not + // be processed by the chain + uint64 timeout_height = 3; + + // extension_options are arbitrary options that can be added by chains + // when the default options are not sufficient. If any of these are present + // and can't be handled, the transaction will be rejected + repeated google.protobuf.Any extension_options = 1023; + + // extension_options are arbitrary options that can be added by chains + // when the default options are not sufficient. If any of these are present + // and can't be handled, they will be ignored + repeated google.protobuf.Any non_critical_extension_options = 2047; +} + +// AuthInfo describes the fee and signer modes that are used to sign a +// transaction. +message AuthInfo { + // signer_infos defines the signing modes for the required signers. The number + // and order of elements must match the required signers from TxBody's + // messages. The first element is the primary signer and the one which pays + // the fee. + repeated SignerInfo signer_infos = 1; + + // Fee is the fee and gas limit for the transaction. The first signer is the + // primary signer and the one which pays the fee. The fee can be calculated + // based on the cost of evaluating the body and doing signature verification + // of the signers. This can be estimated via simulation. + Fee fee = 2; + + // Tip is the optional tip used for meta-transactions. + Tip tip = 3; +} + +// SignerInfo describes the public key and signing mode of a single top-level +// signer. +message SignerInfo { + // public_key is the public key of the signer. It is optional for accounts + // that already exist in state. If unset, the verifier can use the required \ + // signer address for this position and lookup the public key. + google.protobuf.Any public_key = 1; + + // mode_info describes the signing mode of the signer and is a nested + // structure to support nested multisig pubkey's + ModeInfo mode_info = 2; + + // sequence is the sequence of the account, which describes the + // number of committed transactions signed by a given address. It is used to + // prevent replay attacks. + uint64 sequence = 3; +} + +// ModeInfo describes the signing mode of a single or nested multisig signer. +message ModeInfo { + // sum is the oneof that specifies whether this represents a single or nested + // multisig signer + oneof sum { + // single represents a single signer + Single single = 1; + + // multi represents a nested multisig signer + Multi multi = 2; + } + + // Single is the mode info for a single signer. It is structured as a message + // to allow for additional fields such as locale for SIGN_MODE_TEXTUAL in the + // future + message Single { + // mode is the signing mode of the single signer + signing.v1beta1.SignMode mode = 1; + } + + // Multi is the mode info for a multisig public key + message Multi { + // bitarray specifies which keys within the multisig are signing + multisig.v1beta1.CompactBitArray bitarray = 1; + + // mode_infos is the corresponding modes of the signers of the multisig + // which could include nested multisig public keys + repeated ModeInfo mode_infos = 2; + } +} + +// Fee includes the amount of coins paid in fees and the maximum +// gas to be used by the transaction. The ratio yields an effective "gasprice", +// which must be above some miminum to be accepted into the mempool. +message Fee { + // amount is the amount of coins to be paid as a fee + repeated base.v1beta1.Coin amount = 1; + + // gas_limit is the maximum gas that can be used in transaction processing + // before an out of gas error occurs + uint64 gas_limit = 2; + + // if unset, the first signer is responsible for paying the fees. If set, the specified account must pay the fees. + // the payer must be a tx signer (and thus have signed this field in AuthInfo). + // setting this field does *not* change the ordering of required signers for the transaction. + string payer = 3; + + // if set, the fee payer (either the first signer or the value of the payer field) requests that a fee grant be used + // to pay fees instead of the fee payer's own balance. If an appropriate fee grant does not exist or the chain does + // not support fee grants, this will fail + string granter = 4; +} + +// Tip is the tip used for meta-transactions. +message Tip { + // amount is the amount of the tip + repeated base.v1beta1.Coin amount = 1; + + // tipper is the address of the account paying for the tip + string tipper = 2; +} diff --git a/src/Cosmos/Protobuf/tx_signing.proto b/src/Cosmos/Protobuf/tx_signing.proto new file mode 100644 index 00000000000..8eb22240548 --- /dev/null +++ b/src/Cosmos/Protobuf/tx_signing.proto @@ -0,0 +1,89 @@ +syntax = "proto3"; +package cosmos.signing.v1beta1; + +// Src: https://github.com/cosmos/cosmos-sdk/blob/master/proto/cosmos/tx/signing/v1beta1/signing.proto + +import "crypto_multisig.proto"; +import "google/protobuf/any.proto"; + +// SignMode represents a signing mode with its own security guarantees. +enum SignMode { + // SIGN_MODE_UNSPECIFIED specifies an unknown signing mode and will be + // rejected. + SIGN_MODE_UNSPECIFIED = 0; + + // SIGN_MODE_DIRECT specifies a signing mode which uses SignDoc and is + // verified with raw bytes from Tx. + SIGN_MODE_DIRECT = 1; + + // SIGN_MODE_TEXTUAL is a future signing mode that will verify some + // human-readable textual representation on top of the binary representation + // from SIGN_MODE_DIRECT. It is currently not supported. + SIGN_MODE_TEXTUAL = 2; + + // SIGN_MODE_DIRECT_AUX specifies a signing mode which uses + // SignDocDirectAux. As opposed to SIGN_MODE_DIRECT, this sign mode does not + // require signers signing over other signers' `signer_info`. It also allows + // for adding Tips in transactions. + SIGN_MODE_DIRECT_AUX = 3; + + // SIGN_MODE_AMINO_AUX specifies a signing mode which uses + // SignDocAminoAux. + SIGN_MODE_AMINO_AUX = 4; + + // SIGN_MODE_LEGACY_AMINO_JSON is a backwards compatibility mode which uses + // Amino JSON and will be removed in the future. + SIGN_MODE_LEGACY_AMINO_JSON = 127; +} + +// SignatureDescriptors wraps multiple SignatureDescriptor's. +message SignatureDescriptors { + // signatures are the signature descriptors + repeated SignatureDescriptor signatures = 1; +} + +// SignatureDescriptor is a convenience type which represents the full data for +// a signature including the public key of the signer, signing modes and the +// signature itself. It is primarily used for coordinating signatures between +// clients. +message SignatureDescriptor { + // public_key is the public key of the signer + google.protobuf.Any public_key = 1; + + Data data = 2; + + // sequence is the sequence of the account, which describes the + // number of committed transactions signed by a given address. It is used to prevent + // replay attacks. + uint64 sequence = 3; + + // Data represents signature data + message Data { + // sum is the oneof that specifies whether this represents single or multi-signature data + oneof sum { + // single represents a single signer + Single single = 1; + + // multi represents a multisig signer + Multi multi = 2; + } + + // Single is the signature data for a single signer + message Single { + // mode is the signing mode of the single signer + SignMode mode = 1; + + // signature is the raw signature bytes + bytes signature = 2; + } + + // Multi is the signature data for a multisig public key + message Multi { + // bitarray specifies which keys within the multisig are signing + multisig.v1beta1.CompactBitArray bitarray = 1; + + // signatures is the signatures of the multi-signature + repeated Data signatures = 2; + } + } +} diff --git a/src/Cosmos/ProtobufSerialization.cpp b/src/Cosmos/ProtobufSerialization.cpp new file mode 100644 index 00000000000..e2f2e070890 --- /dev/null +++ b/src/Cosmos/ProtobufSerialization.cpp @@ -0,0 +1,457 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "ProtobufSerialization.h" +#include "JsonSerialization.h" +#include "../proto/Cosmos.pb.h" +#include "Protobuf/coin.pb.h" +#include "Protobuf/bank_tx.pb.h" +#include "Protobuf/cosmwasm_wasm_v1_tx.pb.h" +#include "Protobuf/distribution_tx.pb.h" +#include "Protobuf/staking_tx.pb.h" +#include "Protobuf/authz_tx.pb.h" +#include "Protobuf/tx.pb.h" +#include "Protobuf/gov_tx.pb.h" +#include "Protobuf/crypto_secp256k1_keys.pb.h" +#include "Protobuf/ibc_applications_transfer_tx.pb.h" +#include "Protobuf/terra_wasm_v1beta1_tx.pb.h" +#include "Protobuf/thorchain_bank_tx.pb.h" +#include "Protobuf/ethermint_keys.pb.h" + +#include "PrivateKey.h" +#include "Data.h" +#include "Hash.h" +#include "Base64.h" +#include "uint256.h" + +#include + +using namespace TW; + +namespace TW::Cosmos::Protobuf { + +using json = nlohmann::json; +using string = std::string; +const auto ProtobufAnyNamespacePrefix = ""; // to override default 'type.googleapis.com' + +cosmos::base::v1beta1::Coin convertCoin(const Proto::Amount& amount) { + cosmos::base::v1beta1::Coin coin; + coin.set_denom(amount.denom()); + coin.set_amount(amount.amount()); + return coin; +} + +// Convert messages from external protobuf to internal protobuf +google::protobuf::Any convertMessage(const Proto::Message& msg) { + google::protobuf::Any any; + switch (msg.message_oneof_case()) { + case Proto::Message::kSendCoinsMessage: + { + assert(msg.has_send_coins_message()); + const auto& send = msg.send_coins_message(); + auto msgSend = cosmos::bank::v1beta1::MsgSend(); + msgSend.set_from_address(send.from_address()); + msgSend.set_to_address(send.to_address()); + for (auto i = 0; i < send.amounts_size(); ++i) { + *msgSend.add_amount() = convertCoin(send.amounts(i)); + } + any.PackFrom(msgSend, ProtobufAnyNamespacePrefix); + return any; + } + + case Proto::Message::kTransferTokensMessage: + { + assert(msg.has_transfer_tokens_message()); + const auto& transfer = msg.transfer_tokens_message(); + auto msgTransfer = ibc::applications::transfer::v1::MsgTransfer(); + msgTransfer.set_source_port(transfer.source_port()); + msgTransfer.set_source_channel(transfer.source_channel()); + *msgTransfer.mutable_token() = convertCoin(transfer.token()); + msgTransfer.set_sender(transfer.sender()); + msgTransfer.set_receiver(transfer.receiver()); + msgTransfer.mutable_timeout_height()->set_revision_number(transfer.timeout_height().revision_number()); + msgTransfer.mutable_timeout_height()->set_revision_height(transfer.timeout_height().revision_height()); + msgTransfer.set_timeout_timestamp(transfer.timeout_timestamp()); + any.PackFrom(msgTransfer, ProtobufAnyNamespacePrefix); + return any; + } + + case Proto::Message::kStakeMessage: + { + assert(msg.has_stake_message()); + const auto& stake = msg.stake_message(); + auto msgDelegate = cosmos::staking::v1beta1::MsgDelegate(); + msgDelegate.set_delegator_address(stake.delegator_address()); + msgDelegate.set_validator_address(stake.validator_address()); + *msgDelegate.mutable_amount() = convertCoin(stake.amount()); + any.PackFrom(msgDelegate, ProtobufAnyNamespacePrefix); + return any; + } + + case Proto::Message::kUnstakeMessage: + { + assert(msg.has_unstake_message()); + const auto& unstake = msg.unstake_message(); + auto msgUndelegate = cosmos::staking::v1beta1::MsgUndelegate(); + msgUndelegate.set_delegator_address(unstake.delegator_address()); + msgUndelegate.set_validator_address(unstake.validator_address()); + *msgUndelegate.mutable_amount() = convertCoin(unstake.amount()); + any.PackFrom(msgUndelegate, ProtobufAnyNamespacePrefix); + return any; + } + + case Proto::Message::kRestakeMessage: + { + assert(msg.has_restake_message()); + const auto& restake = msg.restake_message(); + auto msgRedelegate = cosmos::staking::v1beta1::MsgBeginRedelegate(); + msgRedelegate.set_delegator_address(restake.delegator_address()); + msgRedelegate.set_validator_src_address(restake.validator_src_address()); + msgRedelegate.set_validator_dst_address(restake.validator_dst_address()); + *msgRedelegate.mutable_amount() = convertCoin(restake.amount()); + any.PackFrom(msgRedelegate, ProtobufAnyNamespacePrefix); + return any; + } + + case Proto::Message::kWithdrawStakeRewardMessage: + { + assert(msg.has_withdraw_stake_reward_message()); + const auto& withdraw = msg.withdraw_stake_reward_message(); + auto msgWithdraw = cosmos::distribution::v1beta1::MsgWithdrawDelegatorReward(); + msgWithdraw.set_delegator_address(withdraw.delegator_address()); + msgWithdraw.set_validator_address(withdraw.validator_address()); + any.PackFrom(msgWithdraw, ProtobufAnyNamespacePrefix); + return any; + } + + case Proto::Message::kWasmTerraExecuteContractTransferMessage: + { + assert(msg.has_wasm_terra_execute_contract_transfer_message()); + const auto& wasmExecute = msg.wasm_terra_execute_contract_transfer_message(); + auto msgExecute = terra::wasm::v1beta1::MsgExecuteContract(); + msgExecute.set_sender(wasmExecute.sender_address()); + msgExecute.set_contract(wasmExecute.contract_address()); + const std::string payload = wasmTerraExecuteTransferPayload(wasmExecute).dump(); + msgExecute.set_execute_msg(payload); + any.PackFrom(msgExecute, ProtobufAnyNamespacePrefix); + return any; + } + + case Proto::Message::kWasmTerraExecuteContractSendMessage: + { + assert(msg.has_wasm_terra_execute_contract_send_message()); + const auto& wasmExecute = msg.wasm_terra_execute_contract_send_message(); + auto msgExecute = terra::wasm::v1beta1::MsgExecuteContract(); + msgExecute.set_sender(wasmExecute.sender_address()); + msgExecute.set_contract(wasmExecute.contract_address()); + const std::string payload = wasmTerraExecuteSendPayload(wasmExecute).dump(); + msgExecute.set_execute_msg(payload); + any.PackFrom(msgExecute, ProtobufAnyNamespacePrefix); + return any; + } + + case Proto::Message::kThorchainSendMessage: + { + assert(msg.has_thorchain_send_message()); + const auto& send = msg.thorchain_send_message(); + auto msgSend =types::MsgSend(); + msgSend.set_from_address(send.from_address()); + msgSend.set_to_address(send.to_address()); + for (auto i = 0; i < send.amounts_size(); ++i) { + *msgSend.add_amount() = convertCoin(send.amounts(i)); + } + any.PackFrom(msgSend, ProtobufAnyNamespacePrefix); + return any; + } + + case Proto::Message::kWasmTerraExecuteContractGeneric: { + assert(msg.has_wasm_terra_execute_contract_generic()); + const auto& wasmExecute = msg.wasm_terra_execute_contract_generic(); + auto msgExecute = terra::wasm::v1beta1::MsgExecuteContract(); + msgExecute.set_sender(wasmExecute.sender_address()); + msgExecute.set_contract(wasmExecute.contract_address()); + msgExecute.set_execute_msg(wasmExecute.execute_msg()); + + for (auto i = 0; i < wasmExecute.coins_size(); ++i) { + *msgExecute.add_coins() = convertCoin(wasmExecute.coins(i)); + } + any.PackFrom(msgExecute, ProtobufAnyNamespacePrefix); + return any; + } + + case Proto::Message::kWasmExecuteContractTransferMessage: + { + assert(msg.has_wasm_execute_contract_transfer_message()); + const auto& wasmExecute = msg.wasm_execute_contract_transfer_message(); + auto msgExecute = cosmwasm::wasm::v1::MsgExecuteContract(); + msgExecute.set_sender(wasmExecute.sender_address()); + msgExecute.set_contract(wasmExecute.contract_address()); + const std::string payload = wasmExecuteTransferPayload(wasmExecute).dump(); + msgExecute.set_msg(payload); + any.PackFrom(msgExecute, ProtobufAnyNamespacePrefix); + return any; + } + + case Proto::Message::kWasmExecuteContractSendMessage: + { + assert(msg.has_wasm_execute_contract_send_message()); + const auto& wasmExecute = msg.wasm_execute_contract_send_message(); + auto msgExecute = cosmwasm::wasm::v1::MsgExecuteContract(); + msgExecute.set_sender(wasmExecute.sender_address()); + msgExecute.set_contract(wasmExecute.contract_address()); + const std::string payload = wasmExecuteSendPayload(wasmExecute).dump(); + msgExecute.set_msg(payload); + any.PackFrom(msgExecute, ProtobufAnyNamespacePrefix); + return any; + } + + case Proto::Message::kWasmExecuteContractGeneric: { + assert(msg.has_wasm_execute_contract_generic()); + const auto& wasmExecute = msg.wasm_execute_contract_generic(); + auto msgExecute = cosmwasm::wasm::v1::MsgExecuteContract(); + msgExecute.set_sender(wasmExecute.sender_address()); + msgExecute.set_contract(wasmExecute.contract_address()); + msgExecute.set_msg(wasmExecute.execute_msg()); + + for (auto i = 0; i < wasmExecute.coins_size(); ++i) { + *msgExecute.add_funds() = convertCoin(wasmExecute.coins(i)); + } + any.PackFrom(msgExecute, ProtobufAnyNamespacePrefix); + return any; + } + + case Proto::Message::kAuthGrant: { + assert(msg.has_auth_grant()); + const auto& authGrant = msg.auth_grant(); + auto msgAuthGrant = cosmos::authz::v1beta1::MsgGrant(); + msgAuthGrant.set_grantee(authGrant.grantee()); + msgAuthGrant.set_granter(authGrant.granter()); + auto* mtAuth = msgAuthGrant.mutable_grant()->mutable_authorization(); + // There is multiple grant possibilities, but we add support staking/compounding only for now. + switch (authGrant.grant_type_case()) { + case Proto::Message_AuthGrant::kGrantStake: + mtAuth->PackFrom(authGrant.grant_stake(), ProtobufAnyNamespacePrefix); + mtAuth->set_type_url("/cosmos.staking.v1beta1.StakeAuthorization"); + break; + case Proto::Message_AuthGrant::GRANT_TYPE_NOT_SET: + break; + } + auto* mtExp = msgAuthGrant.mutable_grant()->mutable_expiration(); + mtExp->set_seconds(authGrant.expiration()); + any.PackFrom(msgAuthGrant, ProtobufAnyNamespacePrefix); + return any; + } + + case Proto::Message::kAuthRevoke: { + assert(msg.has_auth_revoke()); + const auto& authRevoke = msg.auth_revoke(); + auto msgAuthRevoke = cosmos::authz::v1beta1::MsgRevoke(); + msgAuthRevoke.set_granter(authRevoke.granter()); + msgAuthRevoke.set_grantee(authRevoke.grantee()); + msgAuthRevoke.set_msg_type_url(authRevoke.msg_type_url()); + any.PackFrom(msgAuthRevoke, ProtobufAnyNamespacePrefix); + return any; + } + case Proto::Message::kMsgVote: { + assert(msg.has_msg_vote()); + const auto& vote = msg.msg_vote(); + auto msgVote = cosmos::gov::v1beta1::MsgVote(); + // LCOV_EXCL_START + switch (vote.option()) { + case Proto::Message_VoteOption__UNSPECIFIED: + msgVote.set_option(cosmos::gov::v1beta1::VOTE_OPTION_UNSPECIFIED); + break; + case Proto::Message_VoteOption_YES: + msgVote.set_option(cosmos::gov::v1beta1::VOTE_OPTION_YES); + break; + case Proto::Message_VoteOption_ABSTAIN: + msgVote.set_option(cosmos::gov::v1beta1::VOTE_OPTION_ABSTAIN); + break; + case Proto::Message_VoteOption_NO: + msgVote.set_option(cosmos::gov::v1beta1::VOTE_OPTION_NO); + break; + case Proto::Message_VoteOption_NO_WITH_VETO: + msgVote.set_option(cosmos::gov::v1beta1::VOTE_OPTION_NO_WITH_VETO); + break; + case Proto::Message_VoteOption_Message_VoteOption_INT_MIN_SENTINEL_DO_NOT_USE_: + msgVote.set_option(cosmos::gov::v1beta1::VoteOption_INT_MIN_SENTINEL_DO_NOT_USE_); + break; + case Proto::Message_VoteOption_Message_VoteOption_INT_MAX_SENTINEL_DO_NOT_USE_: + msgVote.set_option(cosmos::gov::v1beta1::VoteOption_INT_MAX_SENTINEL_DO_NOT_USE_); + break; + } + // LCOV_EXCL_STOP + msgVote.set_proposal_id(vote.proposal_id()); + msgVote.set_voter(vote.voter()); + any.PackFrom(msgVote, ProtobufAnyNamespacePrefix); + return any; + } + + default: + throw std::invalid_argument(std::string("Message not supported ") + std::to_string(msg.message_oneof_case())); + } +} + +std::string buildProtoTxBody(const Proto::SigningInput& input) { + if (input.messages_size() >= 1 && input.messages(0).has_sign_direct_message()) { + return input.messages(0).sign_direct_message().body_bytes(); + } + + if (input.messages_size() < 1) { + throw std::invalid_argument("No message found"); + } + assert(input.messages_size() >= 1); + auto txBody = cosmos::TxBody(); + for (auto i = 0; i < input.messages_size(); ++i) { + const auto msgAny = convertMessage(input.messages(i)); + *txBody.add_messages() = msgAny; + } + txBody.set_memo(input.memo()); + txBody.set_timeout_height(0); + + return txBody.SerializeAsString(); +} + +std::string buildAuthInfo(const Proto::SigningInput& input, TWCoinType coin) { + if (input.messages_size() >= 1 && input.messages(0).has_sign_direct_message()) { + return input.messages(0).sign_direct_message().auth_info_bytes(); + } + + // AuthInfo + const auto privateKey = PrivateKey(input.private_key()); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + auto authInfo = cosmos::AuthInfo(); + auto* signerInfo = authInfo.add_signer_infos(); + + signerInfo->mutable_mode_info()->mutable_single()->set_mode(cosmos::signing::v1beta1::SIGN_MODE_DIRECT); + signerInfo->set_sequence(input.sequence()); + switch(coin) { + case TWCoinTypeNativeEvmos: { + auto pubKey = ethermint::crypto::v1::ethsecp256k1::PubKey(); + pubKey.set_key(publicKey.bytes.data(), publicKey.bytes.size()); + signerInfo->mutable_public_key()->PackFrom(pubKey, ProtobufAnyNamespacePrefix); + break; + } + default: { + auto pubKey = cosmos::crypto::secp256k1::PubKey(); + pubKey.set_key(publicKey.bytes.data(), publicKey.bytes.size()); + signerInfo->mutable_public_key()->PackFrom(pubKey, ProtobufAnyNamespacePrefix); + } + } + + auto* fee = authInfo.mutable_fee(); + for (auto i = 0; i < input.fee().amounts_size(); ++i) { + *fee->add_amount() = convertCoin(input.fee().amounts(i)); + } + + fee->set_gas_limit(input.fee().gas()); + fee->set_payer(""); + fee->set_granter(""); + // tip is omitted + return authInfo.SerializeAsString(); +} + +Data buildSignature(const Proto::SigningInput& input, const std::string& serializedTxBody, const std::string& serializedAuthInfo, TWCoinType coin) { + // SignDoc Preimage + auto signDoc = cosmos::SignDoc(); + signDoc.set_body_bytes(serializedTxBody); + signDoc.set_auth_info_bytes(serializedAuthInfo); + signDoc.set_chain_id(input.chain_id()); + signDoc.set_account_number(input.account_number()); + const auto serializedSignDoc = signDoc.SerializeAsString(); + + Data hashToSign; + switch(coin) { + case TWCoinTypeNativeEvmos: { + hashToSign = Hash::keccak256(serializedSignDoc); + break; + } + default: { + hashToSign = Hash::sha256(serializedSignDoc); + } + } + + const auto privateKey = PrivateKey(input.private_key()); + auto signedHash = privateKey.sign(hashToSign, TWCurveSECP256k1); + auto signature = Data(signedHash.begin(), signedHash.end() - 1); + return signature; +} + +std::string buildProtoTxRaw(const std::string& serializedTxBody, const std::string& serializedAuthInfo, const Data& signature) { + auto txRaw = cosmos::TxRaw(); + txRaw.set_body_bytes(serializedTxBody); + txRaw.set_auth_info_bytes(serializedAuthInfo); + *txRaw.add_signatures() = std::string(signature.begin(), signature.end()); + return txRaw.SerializeAsString(); +} + +static string broadcastMode(Proto::BroadcastMode mode) { + switch (mode) { + case Proto::BroadcastMode::BLOCK: + return "BROADCAST_MODE_BLOCK"; + case Proto::BroadcastMode::ASYNC: + return "BROADCAST_MODE_ASYNC"; + case Proto::BroadcastMode::SYNC: + default: return "BROADCAST_MODE_SYNC"; + } +} + +std::string buildProtoTxJson(const Proto::SigningInput& input, const std::string& serializedTx) { + const string serializedBase64 = Base64::encode(TW::data(serializedTx)); + const json jsonSerialized = { + {"tx_bytes", serializedBase64}, + {"mode", broadcastMode(input.mode())} + }; + return jsonSerialized.dump(); +} + +json wasmExecuteTransferPayload(const Proto::Message_WasmExecuteContractTransfer& msg) { + return { + {"transfer", + { + {"amount", toString(load(data(msg.amount())))}, + {"recipient", msg.recipient_address()} + } + } + }; +} + +json wasmExecuteSendPayload(const Proto::Message_WasmExecuteContractSend& msg) { + return { + {"send", + { + {"amount", toString(load(data(msg.amount())))}, + {"contract", msg.recipient_contract_address()}, + {"msg", msg.msg()} + } + } + }; +} + +json wasmTerraExecuteTransferPayload(const Proto::Message_WasmTerraExecuteContractTransfer& msg) { + return { + {"transfer", + { + {"amount", toString(load(data(msg.amount())))}, + {"recipient", msg.recipient_address()} + } + } + }; +} + +json wasmTerraExecuteSendPayload(const Proto::Message_WasmTerraExecuteContractSend& msg) { + return { + {"send", + { + {"amount", toString(load(data(msg.amount())))}, + {"contract", msg.recipient_contract_address()}, + {"msg", msg.msg()} + } + } + }; +} + +} // namespace diff --git a/src/Cosmos/ProtobufSerialization.h b/src/Cosmos/ProtobufSerialization.h new file mode 100644 index 00000000000..035bb7f2c7e --- /dev/null +++ b/src/Cosmos/ProtobufSerialization.h @@ -0,0 +1,37 @@ +// Copyright © 2017-2021 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Data.h" +#include "../proto/Cosmos.pb.h" + +#include +#include + +#include + +namespace TW::Cosmos::Protobuf { + +std::string buildProtoTxBody(const Proto::SigningInput& input); + +std::string buildAuthInfo(const Proto::SigningInput& input, TWCoinType coin); + +Data buildSignature(const Proto::SigningInput& input, const std::string& serializedTxBody, const std::string& serializedAuthInfo, TWCoinType coin); + +std::string buildProtoTxRaw(const std::string& serializedTxBody, const std::string& serializedAuthInfo, const Data& signature); + +std::string buildProtoTxJson(const Proto::SigningInput& input, const std::string& serializedTx); + +nlohmann::json wasmExecuteTransferPayload(const Proto::Message_WasmExecuteContractTransfer& msg); + +nlohmann::json wasmExecuteSendPayload(const Proto::Message_WasmExecuteContractSend& msg); + +nlohmann::json wasmTerraExecuteTransferPayload(const Proto::Message_WasmTerraExecuteContractTransfer& msg); + +nlohmann::json wasmTerraExecuteSendPayload(const Proto::Message_WasmTerraExecuteContractSend& msg); + +} // namespace TW::Cosmos::protobuf diff --git a/src/Cosmos/Serialization.h b/src/Cosmos/Serialization.h deleted file mode 100644 index bd049eb903b..00000000000 --- a/src/Cosmos/Serialization.h +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "../proto/Cosmos.pb.h" -#include "Data.h" -#include - -using string = std::string; -using json = nlohmann::json; - -extern const string TYPE_PREFIX_MSG_SEND; -extern const string TYPE_PREFIX_MSG_DELEGATE; -extern const string TYPE_PREFIX_MSG_UNDELEGATE; -extern const string TYPE_PREFIX_MSG_REDELEGATE; -extern const string TYPE_PREFIX_MSG_WITHDRAW_REWARD; -extern const string TYPE_PREFIX_PUBLIC_KEY; - -namespace TW::Cosmos { - -json signaturePreimage(const Proto::SigningInput& input); -json transactionJSON(const Proto::SigningInput& input, const Data& signature); - -} // namespace diff --git a/src/Cosmos/Signer.cpp b/src/Cosmos/Signer.cpp index 7bdbe7b4494..b8b538ad8f5 100644 --- a/src/Cosmos/Signer.cpp +++ b/src/Cosmos/Signer.cpp @@ -1,39 +1,79 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. #include "Signer.h" -#include "PrivateKey.h" -#include "Serialization.h" +#include "JsonSerialization.h" +#include "ProtobufSerialization.h" +#include "PrivateKey.h" #include "Data.h" -#include "Hash.h" - #include -using namespace TW; -using namespace TW::Cosmos; +namespace TW::Cosmos { + +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input, TWCoinType coin) noexcept { + switch (input.signing_mode()) { + case Proto::JSON: + return signJsonSerialized(input, coin); + + case Proto::Protobuf: + default: + return signProtobuf(input, coin); + } +} -Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { +Proto::SigningOutput Signer::signJsonSerialized(const Proto::SigningInput& input, TWCoinType coin) noexcept { auto key = PrivateKey(input.private_key()); - auto preimage = signaturePreimage(input).dump(); + auto preimage = Json::signaturePreimageJSON(input).dump(); auto hash = Hash::sha256(preimage); auto signedHash = key.sign(hash, TWCurveSECP256k1); auto output = Proto::SigningOutput(); auto signature = Data(signedHash.begin(), signedHash.end() - 1); - auto txJson = transactionJSON(input, signature); + auto txJson = Json::transactionJSON(input, signature, coin); output.set_json(txJson.dump()); output.set_signature(signature.data(), signature.size()); + output.set_serialized(""); + output.set_error(""); + output.set_signature_json(txJson["tx"]["signatures"].dump()); return output; } -std::string Signer::signJSON(const std::string& json, const Data& key) { +Proto::SigningOutput Signer::signProtobuf(const Proto::SigningInput& input, TWCoinType coin) noexcept { + using namespace Protobuf; + using namespace Json; + try { + const auto serializedTxBody = buildProtoTxBody(input); + const auto serializedAuthInfo = buildAuthInfo(input, coin); + const auto signature = buildSignature(input, serializedTxBody, serializedAuthInfo, coin); + auto serializedTxRaw = buildProtoTxRaw(serializedTxBody, serializedAuthInfo, signature); + + auto output = Proto::SigningOutput(); + const std::string jsonSerialized = buildProtoTxJson(input, serializedTxRaw); + auto publicKey = PrivateKey(input.private_key()).getPublicKey(TWPublicKeyTypeSECP256k1); + auto signatures = nlohmann::json::array({signatureJSON(signature, publicKey.bytes, coin)}); + output.set_serialized(jsonSerialized); + output.set_signature(signature.data(), signature.size()); + output.set_json(""); + output.set_error(""); + output.set_signature_json(signatures.dump()); + return output; + } catch (const std::exception& ex) { + auto output = Proto::SigningOutput(); + output.set_error(std::string("Error: ") + ex.what()); + return output; + } +} + +std::string Signer::signJSON(const std::string& json, const Data& key, TWCoinType coin) { auto input = Proto::SigningInput(); google::protobuf::util::JsonStringToMessage(json, &input); input.set_private_key(key.data(), key.size()); - auto output = Signer::sign(input); + auto output = Signer::sign(input, coin); return output.json(); } + +} // namespace TW::Cosmos diff --git a/src/Cosmos/Signer.h b/src/Cosmos/Signer.h index 4af4193d17f..4a3407021e7 100644 --- a/src/Cosmos/Signer.h +++ b/src/Cosmos/Signer.h @@ -6,18 +6,27 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "../proto/Cosmos.pb.h" +#include + namespace TW::Cosmos { /// Helper class that performs Cosmos transaction signing. class Signer { public: /// Signs a Proto::SigningInput transaction - static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; + static Proto::SigningOutput sign(const Proto::SigningInput& input, TWCoinType coin) noexcept; + + /// Signs a Proto::SigningInput transaction, using Json serialization + static Proto::SigningOutput signJsonSerialized(const Proto::SigningInput& input, TWCoinType coin) noexcept; + + /// Signs a Proto::SigningInput transaction, using binary Protobuf serialization + static Proto::SigningOutput signProtobuf(const Proto::SigningInput& input, TWCoinType coin) noexcept; + /// Signs a json Proto::SigningInput with private key - static std::string signJSON(const std::string& json, const Data& key); + static std::string signJSON(const std::string& json, const Data& key, TWCoinType coin); }; } // namespace TW::Cosmos diff --git a/src/Crc.cpp b/src/Crc.cpp index 7a388a7c03c..1cbbe6d2696 100644 --- a/src/Crc.cpp +++ b/src/Crc.cpp @@ -6,8 +6,7 @@ #include "Crc.h" -#include // for boost::crc_32_type - +#include #include using namespace TW; @@ -17,7 +16,7 @@ uint16_t Crc::crc16(uint8_t* bytes, uint32_t length) { uint16_t crc = 0x0000; const uint16_t polynomial = 0x1021; - for (auto i = 0; i < length; i++) { + for (auto i = 0ul; i < length; i++) { const auto byte = bytes[i]; for (auto bitidx = 0; bitidx < 8; bitidx++) { const auto bit = ((byte >> (7 - bitidx) & 1) == 1); @@ -32,17 +31,12 @@ uint16_t Crc::crc16(uint8_t* bytes, uint32_t length) { return crc & 0xffff; } -uint32_t Crc::crc32(const Data& data) -{ - boost::crc_32_type result; - result.process_bytes((const void*)data.data(), data.size()); - return (uint32_t)result.checksum(); -} - -uint32_t Crc::crc32C(const Data& data) -{ - using crc_32c_type = boost::crc_optimal<32, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, true, true>; - crc_32c_type result; - result.process_bytes((const void*)data.data(), data.size()); - return (uint32_t)result.checksum(); +// Algorithm inspired by this old-style C implementation: +// https://web.mit.edu/freebsd/head/sys/libkern/crc32.c (Public Domain code) +uint32_t Crc::crc32(const Data& data) { + uint32_t c = std::numeric_limits::max(); + for (const auto byte : data) { + c = crc32_table[(c ^ byte) & 0xFF] ^ (c >> 8); + } + return ~c; } diff --git a/src/Crc.h b/src/Crc.h index 88bb3b28854..1180035491b 100644 --- a/src/Crc.h +++ b/src/Crc.h @@ -17,6 +17,50 @@ uint16_t crc16(uint8_t* bytes, uint32_t length); uint32_t crc32(const TW::Data& data); -uint32_t crc32C(const TW::Data& data); - +// 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. +static constexpr uint32_t crc32_table[] = { + 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}; } // namespace TW::Crc diff --git a/src/Data.cpp b/src/Data.cpp index 15a42af6f07..8eea5816ce5 100644 --- a/src/Data.cpp +++ b/src/Data.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,13 +9,18 @@ namespace TW { Data subData(const Data& data, size_t startIndex, size_t length) { - size_t subLength = length; - if (startIndex + subLength > data.size()) { subLength = data.size() - startIndex; } // guard against over-length + if (startIndex >= data.size()) { + return Data(); + } + const size_t subLength = std::min(length, data.size() - startIndex); // guard against over-length return TW::data(data.data() + startIndex, subLength); } Data subData(const Data& data, size_t startIndex) { - size_t subLength = data.size() - startIndex; + if (startIndex >= data.size()) { + return Data(); + } + const size_t subLength = data.size() - startIndex; return TW::data(data.data() + startIndex, subLength); } diff --git a/src/Data.h b/src/Data.h index e7521a92ee6..1c43fdc68b0 100644 --- a/src/Data.h +++ b/src/Data.h @@ -21,11 +21,11 @@ inline void pad_left(Data& data, const uint32_t size) { } inline Data data(const std::string& data) { - return std::vector(data.begin(), data.end()); + return Data(data.begin(), data.end()); } inline Data data(const byte* data, size_t size) { - return std::vector(data, data + size); + return Data(data, data + size); } inline void append(Data& data, const Data& suffix) { diff --git a/src/Decred/Address.cpp b/src/Decred/Address.cpp index 1a7496c7a13..cd3685f9463 100644 --- a/src/Decred/Address.cpp +++ b/src/Decred/Address.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -7,19 +7,17 @@ #include "Address.h" #include "../Base58.h" -#include "../Hash.h" #include "../Coin.h" #include -using namespace TW; -using namespace TW::Decred; +namespace TW::Decred { static const auto keyhashSize = Hash::ripemdSize; static const auto addressDataSize = keyhashSize + 2; bool Address::isValid(const std::string& string) noexcept { - const auto data = Base58::bitcoin.decodeCheck(string, Hash::blake256d); + const auto data = Base58::bitcoin.decodeCheck(string, Hash::HasherBlake256d); if (data.size() != addressDataSize) { return false; } @@ -27,12 +25,12 @@ bool Address::isValid(const std::string& string) noexcept { return false; } - return (data[1] == TW::p2pkhPrefix(TWCoinTypeDecred) || + return (data[1] == TW::p2pkhPrefix(TWCoinTypeDecred) || data[1] == TW::p2shPrefix(TWCoinTypeDecred)); } Address::Address(const std::string& string) { - const auto data = Base58::bitcoin.decodeCheck(string, Hash::blake256d); + const auto data = Base58::bitcoin.decodeCheck(string, Hash::HasherBlake256d); if (data.size() != addressDataSize) { throw std::invalid_argument("Invalid address string"); } @@ -51,5 +49,7 @@ Address::Address(const PublicKey& publicKey) { } std::string Address::string() const { - return Base58::bitcoin.encodeCheck(bytes, Hash::blake256d); + return Base58::bitcoin.encodeCheck(bytes, Hash::HasherBlake256d); } + +} // namespace TW::Decred diff --git a/src/Decred/Address.h b/src/Decred/Address.h index d86bfcba2bf..fb534ccffc0 100644 --- a/src/Decred/Address.h +++ b/src/Decred/Address.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include diff --git a/src/Decred/Entry.cpp b/src/Decred/Entry.cpp index e6ecaf91aae..2bfc7149e39 100644 --- a/src/Decred/Entry.cpp +++ b/src/Decred/Entry.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,21 +9,27 @@ #include "Address.h" #include "Signer.h" -using namespace TW::Decred; -using namespace std; +namespace TW::Decred { -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] TW::byte p2pkh, [[maybe_unused]] TW::byte p2sh, [[maybe_unused]] const char* hrp) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TW::byte p2pkh, [[maybe_unused]] const char* hrp) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + const auto addr = Address(address); + return {addr.bytes.begin() + 2, addr.bytes.end()}; +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } -void Entry::plan(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::plan([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { planTemplate(dataIn, dataOut); } + +} // namespace TW::Decred diff --git a/src/Decred/Entry.h b/src/Decred/Entry.h index c840d0d9648..0d21789afa8 100644 --- a/src/Decred/Entry.h +++ b/src/Decred/Entry.h @@ -12,13 +12,13 @@ namespace TW::Decred { /// Decred entry dispatcher. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeDecred}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::Decred diff --git a/src/Decred/OutPoint.cpp b/src/Decred/OutPoint.cpp index 27cb836c50a..e33f39badd4 100644 --- a/src/Decred/OutPoint.cpp +++ b/src/Decred/OutPoint.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -8,10 +8,12 @@ #include "../BinaryCoding.h" -using namespace TW::Decred; +namespace TW::Decred { void OutPoint::encode(Data& data) const { std::copy(std::begin(hash), std::end(hash), std::back_inserter(data)); encode32LE(index, data); data.push_back(static_cast(tree)); } + +} // namespace TW::Decred diff --git a/src/Decred/OutPoint.h b/src/Decred/OutPoint.h index 3ce3b5b169a..315d78e693d 100644 --- a/src/Decred/OutPoint.h +++ b/src/Decred/OutPoint.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "../Bitcoin/OutPoint.h" #include "../proto/Bitcoin.pb.h" diff --git a/src/Decred/Signer.cpp b/src/Decred/Signer.cpp index ed02b0ebf48..5cb610c137e 100644 --- a/src/Decred/Signer.cpp +++ b/src/Decred/Signer.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -10,23 +10,18 @@ #include "TransactionOutput.h" #include "../Bitcoin/SigHashType.h" #include "../Bitcoin/SignatureBuilder.h" - #include "../BinaryCoding.h" -#include "../Hash.h" #include "../HexCoding.h" -#include "Bitcoin/OpCodes.h" - -using namespace TW; -using namespace TW::Decred; +namespace TW::Decred { Bitcoin::Proto::TransactionPlan Signer::plan(const Bitcoin::Proto::SigningInput& input) noexcept { - auto signer = Signer(std::move(input)); + auto signer = Signer(input); return signer.txPlan.proto(); } Proto::SigningOutput Signer::sign(const Bitcoin::Proto::SigningInput& input) noexcept { - auto signer = Signer(std::move(input)); + auto signer = Signer(input); auto result = signer.sign(); auto output = Proto::SigningOutput(); if (!result) { @@ -47,18 +42,18 @@ Proto::SigningOutput Signer::sign(const Bitcoin::Proto::SigningInput& input) noe } Result Signer::sign() { - if (txPlan.utxos.size() == 0 || transaction.inputs.size() == 0) { + if (txPlan.utxos.empty() || _transaction.inputs.empty()) { return Result::failure(Common::Proto::Error_missing_input_utxos); } - signedInputs = transaction.inputs; + signedInputs = _transaction.inputs; const auto hashSingle = Bitcoin::hashTypeIsSingle(static_cast(input.hash_type())); - for (auto i = 0; i < txPlan.utxos.size(); i += 1) { + for (auto i = 0ul; i < txPlan.utxos.size(); i += 1) { auto& utxo = txPlan.utxos[i]; // Only sign TWBitcoinSigHashTypeSingle if there's a corresponding output - if (hashSingle && i >= transaction.outputs.size()) { + if (hashSingle && i >= _transaction.outputs.size()) { continue; } auto result = sign(utxo.script, i); @@ -68,14 +63,14 @@ Result Signer::sign() { signedInputs[i].script = result.payload(); } - Transaction tx(transaction); - tx.inputs = move(signedInputs); - tx.outputs = transaction.outputs; + Transaction tx(_transaction); + tx.inputs = std::move(signedInputs); + tx.outputs = _transaction.outputs; return Result::success(std::move(tx)); } Result Signer::sign(Bitcoin::Script script, size_t index) { - assert(index < transaction.inputs.size()); + assert(index < _transaction.inputs.size()); Bitcoin::Script redeemScript; std::vector results; @@ -86,15 +81,15 @@ Result Signer::sign(Bitcoin::Scrip } else { return Result::failure(result.error()); } - auto txin = transaction.inputs[index]; + auto txin = _transaction.inputs[index]; if (script.isPayToScriptHash()) { script = Bitcoin::Script(results.front().begin(), results.front().end()); - auto result = signStep(script, index); - if (!result) { - return Result::failure(result.error()); + auto result_ = signStep(script, index); + if (!result_) { + return Result::failure(result_.error()); } - results = result.payload(); + results = result_.payload(); results.push_back(script.bytes); redeemScript = script; results.push_back(redeemScript.bytes); @@ -104,9 +99,9 @@ Result Signer::sign(Bitcoin::Scrip } Result, Common::Proto::SigningError> Signer::signStep(Bitcoin::Script script, size_t index) { - Transaction transactionToSign(transaction); + Transaction transactionToSign(_transaction); transactionToSign.inputs = signedInputs; - transactionToSign.outputs = transaction.outputs; + transactionToSign.outputs = _transaction.outputs; Data data; std::vector keys; @@ -149,7 +144,7 @@ Result, Common::Proto::SigningError> Signer::signStep(Bitcoin: } else if (script.matchMultisig(keys, required)) { auto results = std::vector{{}}; for (auto& pubKey : keys) { - if (results.size() >= required + 1) { + if (results.size() >= required + 1ul) { break; } auto keyHash = TW::Hash::ripemd(TW::Hash::blake256(pubKey)); @@ -177,7 +172,7 @@ Data Signer::createSignature(const Transaction& transaction, const Bitcoin::Scri const Data& key, size_t index) { auto sighash = transaction.computeSignatureHash(script, index, static_cast(input.hash_type())); auto pk = PrivateKey(key); - auto signature = pk.signAsDER(Data(begin(sighash), end(sighash)), TWCurveSECP256k1); + auto signature = pk.signAsDER(Data(begin(sighash), end(sighash))); if (script.empty()) { return {}; } @@ -206,3 +201,5 @@ Data Signer::scriptForScriptHash(const Data& hash) const { } return Data(it->second.begin(), it->second.end()); } + +} // namespace TW::Decred diff --git a/src/Decred/Signer.h b/src/Decred/Signer.h index 6e299fa65d6..170c5e8b818 100644 --- a/src/Decred/Signer.h +++ b/src/Decred/Signer.h @@ -41,7 +41,7 @@ class Signer { Bitcoin::TransactionPlan txPlan; /// Transaction being signed. - Transaction transaction; + Transaction _transaction; private: /// List of signed inputs. @@ -59,7 +59,7 @@ class Signer { } else { txPlan = TransactionBuilder::plan(input); } - transaction = TransactionBuilder::build(txPlan, input.to_address(), input.change_address()); + _transaction = TransactionBuilder::build(txPlan, input.to_address(), input.change_address()); } /// Signs the transaction. diff --git a/src/Decred/Transaction.cpp b/src/Decred/Transaction.cpp index 7f924d59204..958a843cd49 100644 --- a/src/Decred/Transaction.cpp +++ b/src/Decred/Transaction.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -8,14 +8,10 @@ #include "../Bitcoin/SigHashType.h" #include "../BinaryCoding.h" -#include "../Hash.h" - -#include "Bitcoin/SignatureVersion.h" #include -using namespace TW; -using namespace TW::Decred; +namespace TW::Decred { namespace { // Indicates the serialization does not include any witness data. @@ -52,7 +48,7 @@ Data Transaction::computeSignatureHash(const Bitcoin::Script& prevOutScript, siz break; case TWBitcoinSigHashTypeSingle: outputsToSign.clear(); - std::copy(outputs.begin(), outputs.begin() + index + 1, outputsToSign.end()); + std::copy(outputs.begin(), outputs.begin() + index + 1, std::back_inserter(outputsToSign)); break; default: // Keep all outputs @@ -86,7 +82,7 @@ Data Transaction::computePrefixHash(const std::vector& inputsT // Commit to the relevant transaction inputs. encodeVarInt(inputsToSign.size(), preimage); - for (auto i = 0; i < inputsToSign.size(); i += 1) { + for (auto i = 0ul; i < inputsToSign.size(); i += 1) { auto& input = inputsToSign[i]; input.previousOutput.encode(preimage); @@ -100,7 +96,7 @@ Data Transaction::computePrefixHash(const std::vector& inputsT // Commit to the relevant transaction outputs. encodeVarInt(outputsToSign.size(), preimage); - for (auto i = 0; i < outputsToSign.size(); i += 1) { + for (auto i = 0ul; i < outputsToSign.size(); i += 1) { auto& output = outputsToSign[i]; auto value = output.value; auto pkScript = output.script; @@ -133,7 +129,7 @@ Data Transaction::computeWitnessHash(const std::vector& inputs // Commit to the relevant transaction inputs. encodeVarInt(inputsToSign.size(), witnessBuf); - for (auto i = 0; i < inputsToSign.size(); i += 1) { + for (auto i = 0ul; i < inputsToSign.size(); i += 1) { if (i == signIndex) { signScript.encode(witnessBuf); } else { @@ -239,3 +235,5 @@ std::size_t sigHashWitnessSize(const std::vector& inputs, signScript.bytes.size(); } } // namespace + +} // namespace TW::Decred diff --git a/src/Decred/Transaction.h b/src/Decred/Transaction.h index ba53dcba98f..ee2a36fc7d2 100644 --- a/src/Decred/Transaction.h +++ b/src/Decred/Transaction.h @@ -11,7 +11,7 @@ #include "TransactionOutput.h" #include "Bitcoin/Transaction.h" #include "Bitcoin/Script.h" -#include "../Data.h" +#include "Data.h" #include "../proto/Decred.pb.h" #include "Bitcoin/SignatureVersion.h" diff --git a/src/Decred/TransactionInput.cpp b/src/Decred/TransactionInput.cpp index 0cf57aba6d8..47308501e2e 100644 --- a/src/Decred/TransactionInput.cpp +++ b/src/Decred/TransactionInput.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -8,7 +8,7 @@ #include "../BinaryCoding.h" -using namespace TW::Decred; +namespace TW::Decred { void TransactionInput::encode(Data& data) const { previousOutput.encode(data); @@ -21,3 +21,5 @@ void TransactionInput::encodeWitness(Data& data) const { encode32LE(blockIndex, data); script.encode(data); } + +} // namespace TW::Decred diff --git a/src/Decred/TransactionInput.h b/src/Decred/TransactionInput.h index 98e1a2933ac..dde3c075055 100644 --- a/src/Decred/TransactionInput.h +++ b/src/Decred/TransactionInput.h @@ -8,7 +8,7 @@ #include "OutPoint.h" #include "../Bitcoin/Script.h" -#include "../Data.h" +#include "Data.h" #include #include diff --git a/src/Decred/TransactionOutput.cpp b/src/Decred/TransactionOutput.cpp index 9bc46efde7d..1aadfd41d37 100644 --- a/src/Decred/TransactionOutput.cpp +++ b/src/Decred/TransactionOutput.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -8,10 +8,12 @@ #include "../BinaryCoding.h" -using namespace TW::Decred; +namespace TW::Decred { void TransactionOutput::encode(Data& data) const { encode64LE(value, data); encode16LE(version, data); script.encode(data); } + +} // namespace TW::Decred \ No newline at end of file diff --git a/src/Decred/TransactionOutput.h b/src/Decred/TransactionOutput.h index 7d5cf89b30f..2d9575d4a08 100644 --- a/src/Decred/TransactionOutput.h +++ b/src/Decred/TransactionOutput.h @@ -8,7 +8,7 @@ #include "../Bitcoin/Amount.h" #include "../Bitcoin/Script.h" -#include "../Data.h" +#include "Data.h" namespace TW::Decred { diff --git a/src/Defer.h b/src/Defer.h new file mode 100644 index 00000000000..744eba85655 --- /dev/null +++ b/src/Defer.h @@ -0,0 +1,19 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#ifndef defer // https://stackoverflow.com/a/42060129/411431 + +struct defer_dummy {}; +template struct deferrer { F f; ~deferrer() { f(); } }; +template deferrer operator*(defer_dummy, F f) { return {f}; } + +#define DEFER_(LINE) zz_defer##LINE +#define DEFER(LINE) DEFER_(LINE) +#define defer auto DEFER(__LINE__) = defer_dummy{} *[&]() + +#endif // defer diff --git a/src/DerivationPath.h b/src/DerivationPath.h index be6b6602d07..6ab0bca83ef 100644 --- a/src/DerivationPath.h +++ b/src/DerivationPath.h @@ -21,7 +21,8 @@ struct DerivationPathIndex { bool hardened = true; DerivationPathIndex() = default; - DerivationPathIndex(uint32_t value, bool hardened = true) : value(value), hardened(hardened) {} + DerivationPathIndex(uint32_t value, bool hardened = true) + : value(value), hardened(hardened) {} /// The derivation index. uint32_t derivationIndex() const { @@ -46,63 +47,85 @@ struct DerivationPath { std::vector indices; TWPurpose purpose() const { - if (indices.size() == 0) { return TWPurposeBIP44; } + if (indices.size() == 0) { + return TWPurposeBIP44; + } return static_cast(indices[0].value); } void setPurpose(TWPurpose v) { - if (indices.size() == 0) { return; } + if (indices.size() == 0) { + return; + } indices[0] = DerivationPathIndex(v, /* hardened: */ true); } uint32_t coin() const { - if (indices.size() <= 1) { return TWCoinTypeBitcoin; } + if (indices.size() <= 1) { + return TWCoinTypeBitcoin; + } return indices[1].value; } void setCoin(uint32_t v) { - if (indices.size() <= 1) { return; } + if (indices.size() <= 1) { + return; + } indices[1] = DerivationPathIndex(v, /* hardened: */ true); } uint32_t account() const { - if (indices.size() <= 2) { return 0; } + if (indices.size() <= 2) { + return 0; + } return indices[2].value; } void setAccount(uint32_t v) { - if (indices.size() <= 2) { return; } + if (indices.size() <= 2) { + return; + } indices[2] = DerivationPathIndex(v, /* hardened: */ true); } uint32_t change() const { - if (indices.size() <= 3) { return 0; } + if (indices.size() <= 3) { + return 0; + } return indices[3].value; } void setChange(uint32_t v) { - if (indices.size() <= 3) { return; } + if (indices.size() <= 3) { + return; + } indices[3] = DerivationPathIndex(v, /* hardened: */ false); } uint32_t address() const { - if (indices.size() <= 4) { return 0; } + if (indices.size() <= 4) { + return 0; + } return indices[4].value; } void setAddress(uint32_t v) { - if (indices.size() <= 4) { return; } + if (indices.size() <= 4) { + return; + } indices[4] = DerivationPathIndex(v, /* hardened: */ false); } DerivationPath() = default; - explicit DerivationPath(std::initializer_list l) : indices(l) {} - explicit DerivationPath(std::vector indices) : indices(std::move(indices)) {} + explicit DerivationPath(std::initializer_list l) + : indices(l) {} + explicit DerivationPath(std::vector indices) + : indices(std::move(indices)) {} /// Creates a `DerivationPath` by BIP44 components. DerivationPath(TWPurpose purpose, uint32_t coin, uint32_t account, uint32_t change, - uint32_t address) - : indices(std::vector(5)) { + uint32_t address) + : indices(std::vector(5)) { setPurpose(purpose); setCoin(coin); setAccount(account); @@ -112,7 +135,7 @@ struct DerivationPath { /// Creates a derivation path with a string description like `m/10/0/2'/3` /// - /// @throws std::invalid_argument if the string is not a valid derivation + /// \throws std::invalid_argument if the string is not a valid derivation /// path. explicit DerivationPath(const std::string& string); @@ -130,3 +153,12 @@ inline bool operator==(const DerivationPath& lhs, const DerivationPath& rhs) { } } // namespace TW + +/// Wrapper for C interface. +struct TWDerivationPath { + TW::DerivationPath impl; +}; + +struct TWDerivationPathIndex { + TW::DerivationPathIndex impl; +}; diff --git a/src/EOS/Action.cpp b/src/EOS/Action.cpp index dae38bbabf7..e47c40f2fa2 100644 --- a/src/EOS/Action.cpp +++ b/src/EOS/Action.cpp @@ -6,11 +6,8 @@ #include "Action.h" #include "../HexCoding.h" -#include "../EOS/Serialization.h" -using namespace TW; -using namespace TW::EOS; -using json = nlohmann::json; +namespace TW::EOS { void PermissionLevel::serialize(Data& o) const { actor.serialize(o); @@ -41,11 +38,11 @@ json Action::serialize() const noexcept { return obj; } -TransferAction::TransferAction( const std::string& currency, - const std::string& from, - const std::string& to, - const Asset& asset, - const std::string& memo) { +TransferAction::TransferAction(const std::string& currency, + const std::string& from, + const std::string& to, + const Asset& asset, + const std::string& memo) { account = Name(currency); name = Name("transfer"); authorization.emplace_back(PermissionLevel(Name(from), Name("active"))); @@ -63,3 +60,5 @@ void TransferAction::setData(const std::string& from, const std::string& to, con asset.serialize(data); encodeString(memo, data); } + +} // namespace TW::EOS diff --git a/src/EOS/Action.h b/src/EOS/Action.h index cd55cb47fb5..c2ebcb6fa8f 100644 --- a/src/EOS/Action.h +++ b/src/EOS/Action.h @@ -12,8 +12,6 @@ #include #include -using Data = TW::Data; - namespace TW::EOS { class PermissionLevel { diff --git a/src/EOS/Address.cpp b/src/EOS/Address.cpp index 7d8d1a6d8fa..db97a6d7853 100644 --- a/src/EOS/Address.cpp +++ b/src/EOS/Address.cpp @@ -4,16 +4,15 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +#include "Address.h" #include "../Base58.h" #include "../BinaryCoding.h" -#include "Address.h" #include #include -using namespace TW; -using namespace TW::EOS; +namespace TW::EOS { bool Address::isValid(const std::string& string) { return extractKeyData(string); @@ -22,11 +21,15 @@ bool Address::isValid(const std::string& string) { /// Determines whether the given byte vector is a valid keyBuffer /// Verifies the buffer's size and it's checksum bytes bool Address::isValid(const Data& bytes, EOS::Type type) { - if (bytes.size() != KeyDataSize) return false; + if (bytes.size() != KeyDataSize) { + return false; + } // last Address::ChecksumSize bytes are a checksum uint32_t checksum = decode32LE(bytes.data() + PublicKeyDataSize); - if (createChecksum(bytes, type) != checksum) return false; + if (createChecksum(bytes, type) != checksum) { + return false; + } return true; } @@ -48,15 +51,15 @@ uint32_t Address::createChecksum(const Data& bytes, Type type) { break; case Type::ModernK1: - ripemd160_Update(&ctx, - (const uint8_t *) Modern::K1::prefix.c_str(), - static_cast(Modern::K1::prefix.size())); + ripemd160_Update(&ctx, + (const uint8_t*)Modern::K1::prefix.c_str(), + static_cast(Modern::K1::prefix.size())); break; case Type::ModernR1: - ripemd160_Update(&ctx, - (const uint8_t *) Modern::R1::prefix.c_str(), - static_cast(Modern::R1::prefix.size())); + ripemd160_Update(&ctx, + (const uint8_t*)Modern::R1::prefix.c_str(), + static_cast(Modern::R1::prefix.size())); break; } @@ -67,9 +70,9 @@ uint32_t Address::createChecksum(const Data& bytes, Type type) { } /// Extracts and verifies the key data from a base58 string. -/// If the second arg is provided, the keyData and isTestNet +/// If the second arg is provided, the keyData and isTestNet /// properties of that object are set from the extracted data. -bool Address::extractKeyData(const std::string& string, Address *address) { +bool Address::extractKeyData(const std::string& string, Address* address) { // verify if the string has one of the valid prefixes Type type; size_t prefixSize; @@ -111,14 +114,16 @@ Address::Address(const std::string& string) { } /// Initializes a EOS address from raw bytes -Address::Address(const Data& data, Type type) : keyData(data), type(type) { +Address::Address(const Data& data, Type type) + : keyData(data), type(type) { if (!isValid(data, type)) { throw std::invalid_argument("Invalid byte size!"); } } /// Initializes a EOS address from a public key. -Address::Address(const PublicKey& publicKey, Type type) : type(type) { +Address::Address(const PublicKey& publicKey, Type type) + : type(type) { assert(PublicKeyDataSize == TW::PublicKey::secp256k1Size); // copy the raw, compressed key data @@ -136,3 +141,5 @@ Address::Address(const PublicKey& publicKey, Type type) : type(type) { std::string Address::string() const { return prefix() + Base58::bitcoin.encode(keyData); } + +} // namespace TW::EOS diff --git a/src/EOS/Address.h b/src/EOS/Address.h index f0484c046c1..0749b8244b7 100644 --- a/src/EOS/Address.h +++ b/src/EOS/Address.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include "Prefixes.h" diff --git a/src/EOS/Asset.cpp b/src/EOS/Asset.cpp index 17d03be7976..e2f246315e1 100644 --- a/src/EOS/Asset.cpp +++ b/src/EOS/Asset.cpp @@ -10,7 +10,7 @@ #include #include -using namespace TW::EOS; +namespace TW::EOS { static const int64_t Precision = 1000; static const uint8_t MaxDecimals = 18; @@ -21,12 +21,12 @@ Asset::Asset(int64_t amount, uint8_t decimals, const std::string& symbol) { } this->symbol |= decimals; - if (symbol.size() < 1 || symbol.size() > 7) { + if (symbol.empty() || symbol.size() > 7) { throw std::invalid_argument("Symbol size invalid!"); } - for (int i = 0; i < symbol.size(); i++) { - uint64_t c = symbol[i]; + for (std::size_t i = 0; i < symbol.size(); i++) { + uint64_t c = (unsigned char) symbol[i]; if (c < 'A' || c > 'Z') { throw std::invalid_argument("Invalid symbol " + symbol + ".\n Symbol can only have upper case alphabets!"); } @@ -61,8 +61,8 @@ Asset Asset::fromString(std::string assetString) { if (dotPosition != string::npos) { decimals = static_cast(amountString.size() - dotPosition - 1); } - - int64_t precision = static_cast(pow(10, static_cast(decimals))); + + auto precision = static_cast(pow(10, static_cast(decimals))); // Parse amount int64_t intPart, fractPart = 0; @@ -110,10 +110,10 @@ std::string Asset::string() const { auto decimals = getDecimals(); - int charsWritten = snprintf(buffer, maxBufferSize, "%.*f %s", - decimals, - static_cast(amount) / Precision, - getSymbol().c_str()); + int charsWritten = snprintf(buffer, maxBufferSize, "%.*f %s", + decimals, + static_cast(amount) / Precision, + getSymbol().c_str()); if (charsWritten < 0 || charsWritten > maxBufferSize) { throw std::runtime_error("Failed to create string representation of asset!"); @@ -133,3 +133,5 @@ std::string Asset::getSymbol() const noexcept { return str; } + +} // namespace TW::EOS diff --git a/src/EOS/Entry.cpp b/src/EOS/Entry.cpp index 37de7a06c4c..fa3af508bb4 100644 --- a/src/EOS/Entry.cpp +++ b/src/EOS/Entry.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,19 +9,20 @@ #include "Address.h" #include "Signer.h" -using namespace TW::EOS; -using namespace std; +namespace TW::EOS { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +} diff --git a/src/EOS/Entry.h b/src/EOS/Entry.h index f6e9f4fcd56..63823c8c020 100644 --- a/src/EOS/Entry.h +++ b/src/EOS/Entry.h @@ -12,12 +12,11 @@ namespace TW::EOS { /// Entry point for implementation of EOS coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeEOS}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::EOS diff --git a/src/EOS/Name.cpp b/src/EOS/Name.cpp index 07ec3a131e9..9cb6805bf87 100644 --- a/src/EOS/Name.cpp +++ b/src/EOS/Name.cpp @@ -10,15 +10,14 @@ #include #include -using namespace TW; -using namespace TW::EOS; +namespace TW::EOS { Name::Name(const std::string& str) { if (str.size() > 13) { throw std::invalid_argument(str + ": size too long!"); } - int i = 0; + std::size_t i = 0; while (i < std::min(size_t(12), str.size())) { value |= (toSymbol(str[i]) & 0x1f) << (64 - (5 * (i + 1))); i++; @@ -28,7 +27,7 @@ Name::Name(const std::string& str) { value |= (toSymbol(str[i]) & 0x0f); } -uint64_t Name::toSymbol(char c) const noexcept { +uint64_t Name::toSymbol(char c) noexcept { if (c >= 'a' && c <= 'z') return c - 'a' + 6; @@ -41,22 +40,24 @@ uint64_t Name::toSymbol(char c) const noexcept { std::string Name::string() const noexcept { static const char* charMap = ".12345abcdefghijklmnopqrstuvwxyz"; - std::string str(13,'.'); + std::string str(13, '.'); uint64_t tmp = value; str[12] = charMap[tmp & 0x0f]; tmp >>= 4; - for( uint32_t i = 1; i <= 12; ++i ) { + for (uint32_t i = 1; i <= 12; ++i) { char c = charMap[tmp & 0x1f]; - str[12-i] = c; + str[12 - i] = c; tmp >>= 5; } - boost::algorithm::trim_right_if( str, []( char c ){ return c == '.'; } ); + boost::algorithm::trim_right_if(str, [](char c) { return c == '.'; }); return str; } -void Name::serialize(Data& o) const noexcept { +void Name::serialize(Data& o) const noexcept { encode64LE(value, o); -} \ No newline at end of file +} + +} // namespace TW::EOS diff --git a/src/EOS/Name.h b/src/EOS/Name.h index 3d3d3721a47..6fb75b9e7e4 100644 --- a/src/EOS/Name.h +++ b/src/EOS/Name.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" namespace TW::EOS { @@ -14,9 +14,9 @@ class Name { public: uint64_t value = 0; - Name() { } + Name() = default; Name(const std::string& str); - uint64_t toSymbol(char c) const noexcept; + static uint64_t toSymbol(char c) noexcept; std::string string() const noexcept; void serialize(TW::Data& o) const noexcept; diff --git a/src/EOS/PackedTransaction.cpp b/src/EOS/PackedTransaction.cpp index 56708474227..a1d3e74ea3d 100644 --- a/src/EOS/PackedTransaction.cpp +++ b/src/EOS/PackedTransaction.cpp @@ -8,15 +8,14 @@ #include "../HexCoding.h" -using namespace TW; -using namespace TW::EOS; -using json = nlohmann::json; +namespace TW::EOS { -PackedTransaction::PackedTransaction(const Transaction& transaction, CompressionType type) noexcept : compression(type) { +PackedTransaction::PackedTransaction(const Transaction& transaction, CompressionType type) noexcept + : compression(type) { transaction.serialize(packedTrx); const Data& cfd = transaction.contextFreeData; - if (cfd.size()) { + if (!cfd.empty()) { packedCFD.push_back(1); encodeVarInt64(cfd.size(), packedCFD); append(packedCFD, cfd); @@ -49,4 +48,6 @@ json PackedTransaction::serialize() const noexcept { obj["packed_trx"] = hex(packedTrx); return obj; -} \ No newline at end of file +} + +} // namespace TW::EOS diff --git a/src/EOS/Serialization.h b/src/EOS/Serialization.h index af6f3820fa4..f6aa5c83680 100644 --- a/src/EOS/Serialization.h +++ b/src/EOS/Serialization.h @@ -2,7 +2,7 @@ #include -#include "../Data.h" +#include "Data.h" #include "../BinaryCoding.h" #include diff --git a/src/EOS/Signer.cpp b/src/EOS/Signer.cpp index c268761018b..b01ba0051e9 100644 --- a/src/EOS/Signer.cpp +++ b/src/EOS/Signer.cpp @@ -7,21 +7,16 @@ #include "Signer.h" #include "Asset.h" #include "PackedTransaction.h" -#include "../proto/Common.pb.h" -#include "../HexCoding.h" #include -#include -#include -using namespace TW; -using namespace TW::EOS; +namespace TW::EOS { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { Proto::SigningOutput output; try { // create an asset object - auto assetData = input.asset(); + const auto& assetData = input.asset(); auto asset = Asset(assetData.amount(), static_cast(assetData.decimals()), assetData.symbol()); @@ -31,7 +26,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { // create a Transaction and add the transfer action auto tx = Transaction(Data(input.reference_block_id().begin(), input.reference_block_id().end()), - input.reference_block_time()); + input.reference_block_time()); tx.actions.push_back(action); // get key type @@ -93,7 +88,7 @@ TW::Data Signer::hash(const Transaction& transaction) const noexcept { transaction.serialize(hashInput); Data cfdHash(Hash::sha256Size); // default value for empty cfd - if (transaction.contextFreeData.size()) { + if (!transaction.contextFreeData.empty()) { cfdHash = Hash::sha256(transaction.contextFreeData); } @@ -102,9 +97,13 @@ TW::Data Signer::hash(const Transaction& transaction) const noexcept { } // canonical check for EOS -int Signer::isCanonical(uint8_t by, uint8_t sig[64]) { +int Signer::isCanonical([[maybe_unused]] uint8_t by, uint8_t sig[64]) { + // clang-format off return !(sig[0] & 0x80) && !(sig[0] == 0 && !(sig[1] & 0x80)) && !(sig[32] & 0x80) && !(sig[32] == 0 && !(sig[33] & 0x80)); + // clang-format on } + +} // namespace TW::EOS diff --git a/src/EOS/Signer.h b/src/EOS/Signer.h index 1aee6d97364..95832639489 100644 --- a/src/EOS/Signer.h +++ b/src/EOS/Signer.h @@ -8,7 +8,7 @@ #include "Prefixes.h" #include "Transaction.h" -#include "../Data.h" +#include "Data.h" #include "../Hash.h" #include "../PrivateKey.h" #include "../proto/EOS.pb.h" diff --git a/src/EOS/Transaction.cpp b/src/EOS/Transaction.cpp index e7ae58700aa..72c2791b460 100644 --- a/src/EOS/Transaction.cpp +++ b/src/EOS/Transaction.cpp @@ -5,7 +5,6 @@ // file LICENSE at the root of the source code distribution tree. #include "../Base58.h" -#include "../Hash.h" #include "../HexCoding.h" #include "Transaction.h" @@ -13,12 +12,12 @@ #include #include +#include -using namespace TW; -using namespace TW::EOS; -using json = nlohmann::json; +namespace TW::EOS { -Signature::Signature(const Data& sig, Type type) : data(sig), type(type) { +Signature::Signature(const Data& sig, Type type) + : data(sig), type(type) { if (sig.size() != DataSize) { throw std::invalid_argument("Invalid signature size!"); } @@ -54,7 +53,7 @@ std::string Signature::string() const noexcept { // drop the subPrefix and append the checksum to the bufer buffer.resize(DataSize); - for(size_t i = 0; i < ChecksumSize; i++) { + for (size_t i = 0; i < ChecksumSize; i++) { buffer.push_back(hash[i]); } @@ -85,7 +84,7 @@ void Transaction::setReferenceBlock(const Data& refBlockId) { refBlockPrefix = decode32LE(refBlockId.data() + 8); } -void Transaction::serialize(Data& os) const noexcept{ +void Transaction::serialize(Data& os) const noexcept { encode32LE(expiration, os); encode16LE(refBlockNumber, os); @@ -99,14 +98,19 @@ void Transaction::serialize(Data& os) const noexcept{ encodeCollection(transactionExtensions, os); } -json Transaction::serialize() const { +std::string Transaction::formatDate(int32_t date) { + // format is "2022-06-02T08:53:20", always 19 characters long + constexpr size_t DateSize = 19; + constexpr size_t BufferSize = DateSize + 1; + char formattedDate[BufferSize]; + auto time = static_cast(date); + const size_t len = strftime(formattedDate, BufferSize, "%FT%T", std::gmtime(&time)); + assert(len == DateSize); + return {formattedDate, len}; +} - // get a formatted date - char formattedDate[20]; - time_t time = expiration; - if (strftime(formattedDate, 19, "%FT%T", std::gmtime(&time)) != 19) { - std::runtime_error("Error creating a formatted string!"); - } +json Transaction::serialize() const { + const auto expirationDateFormatted = formatDate(expiration); // create a json array of signatures json sigs = json::array(); @@ -118,7 +122,7 @@ json Transaction::serialize() const { json obj; obj["ref_block_num"] = refBlockNumber; obj["ref_block_prefix"] = refBlockPrefix; - obj["expiration"] = std::string(formattedDate, 19); + obj["expiration"] = expirationDateFormatted; obj["max_net_usage_words"] = maxNetUsageWords; obj["max_cpu_usage_ms"] = maxCPUUsageInMS; obj["delay_sec"] = delaySeconds; @@ -130,3 +134,5 @@ json Transaction::serialize() const { return obj; } + +} // namespace TW::EOS diff --git a/src/EOS/Transaction.h b/src/EOS/Transaction.h index 5d6660c462a..63a93edad09 100644 --- a/src/EOS/Transaction.h +++ b/src/EOS/Transaction.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "Action.h" #include "Prefixes.h" @@ -68,5 +68,7 @@ class Transaction { void setReferenceBlock(const Data& referenceBlockId); static const int32_t ExpirySeconds = 30; + /// Get formatted date + static std::string formatDate(int32_t date); }; } // namespace TW::EOS \ No newline at end of file diff --git a/src/Elrond/Address.cpp b/src/Elrond/Address.cpp index 1af84ace200..581f95b1e9e 100644 --- a/src/Elrond/Address.cpp +++ b/src/Elrond/Address.cpp @@ -8,10 +8,12 @@ #include "Address.h" -using namespace TW::Elrond; +namespace TW::Elrond { const std::string Address::hrp = HRP_ELROND; bool Address::isValid(const std::string& string) { return Bech32Address::isValid(string, hrp); } + +} // namespace TW::Elrond diff --git a/src/Elrond/Address.h b/src/Elrond/Address.h index 1f96edc6700..a638b4af8bd 100644 --- a/src/Elrond/Address.h +++ b/src/Elrond/Address.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include "../Bech32Address.h" @@ -16,7 +16,7 @@ namespace TW::Elrond { class Address : public Bech32Address { public: - // The human-readable part of the address, as defined in "coins.json" + /// The human-readable part of the address, as defined in "registry.json" static const std::string hrp; // HRP_ELROND /// Determines whether a string makes a valid address. diff --git a/src/Elrond/Codec.cpp b/src/Elrond/Codec.cpp new file mode 100644 index 00000000000..dbdf0859291 --- /dev/null +++ b/src/Elrond/Codec.cpp @@ -0,0 +1,45 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Codec.h" + +#include "HexCoding.h" +#include "uint256.h" + +namespace TW::Elrond { + +std::string Codec::encodeString(const std::string& value) { + std::string encoded = hex(TW::data(value)); + return encoded; +} + +std::string Codec::encodeUint64(uint64_t value) { + std::string encoded = hex(store(uint256_t(value))); + return encoded; +} + +std::string Codec::encodeBigInt(const std::string& value) { + return encodeBigInt(uint256_t(value)); +} + +// For reference, see https://docs.elrond.com/developers/developer-reference/elrond-serialization-format/#arbitrary-width-big-numbers. +std::string Codec::encodeBigInt(uint256_t value) { + std::string encoded = hex(store(value)); + return encoded; +} + +std::string Codec::encodeAddress(const std::string& bech32Address) { + Address address; + Address::decode(bech32Address, address); + return encodeAddress(address); +} + +std::string Codec::encodeAddress(const Address& address) { + std::string encoded = hex(address.getKeyHash()); + return encoded; +} + +} // namespace TW::Elrond diff --git a/src/Elrond/Codec.h b/src/Elrond/Codec.h new file mode 100644 index 00000000000..b2ffd73405b --- /dev/null +++ b/src/Elrond/Codec.h @@ -0,0 +1,29 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Address.h" +#include "uint256.h" + +namespace TW::Elrond { + +/// A stripped-down variant of the Elrond codec. +/// For reference, see: +/// - https://docs.elrond.com/developers/developer-reference/elrond-serialization-format +/// - https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/smartcontracts/codec +/// - https://github.com/ElrondNetwork/elrond-wasm-rs/tree/master/elrond-codec +class Codec { +public: + static std::string encodeString(const std::string& value); + static std::string encodeUint64(uint64_t value); + static std::string encodeBigInt(const std::string& value); + static std::string encodeBigInt(TW::uint256_t value); + static std::string encodeAddress(const std::string& bech32Address); + static std::string encodeAddress(const Address& address); +}; + +} // namespace diff --git a/src/Elrond/Entry.cpp b/src/Elrond/Entry.cpp index 76b806ff128..2535f0bec96 100644 --- a/src/Elrond/Entry.cpp +++ b/src/Elrond/Entry.cpp @@ -9,23 +9,34 @@ #include "Address.h" #include "Signer.h" -using namespace TW::Elrond; +using namespace TW; using namespace std; +namespace TW::Elrond { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + Address addr; + if (!Elrond::Address::decode(address, addr)) { + return Data(); + } + return addr.getKeyHash(); +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { +string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { return Signer::signJSON(json, key); } + +} // namespace TW::Elrond diff --git a/src/Elrond/Entry.h b/src/Elrond/Entry.h index d0cc67d631e..e0c4ed7b0a1 100644 --- a/src/Elrond/Entry.h +++ b/src/Elrond/Entry.h @@ -12,14 +12,14 @@ namespace TW::Elrond { /// Entry point for implementation of Elrond coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final: public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeElrond}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual bool supportsJSONSigning() const { return true; } - virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool supportsJSONSigning() const { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; }; } // namespace TW::Elrond diff --git a/src/Elrond/GasEstimator.cpp b/src/Elrond/GasEstimator.cpp new file mode 100644 index 00000000000..6db7cbc9580 --- /dev/null +++ b/src/Elrond/GasEstimator.cpp @@ -0,0 +1,50 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "GasEstimator.h" + +namespace TW::Elrond { + +// Additional gas to account for eventual increases in gas requirements (thus avoid breaking changes in clients of TW-core). +const uint64_t ADDITIONAL_GAS_FOR_ESDT_TRANSFER = 100000; + +// Additional gas to account for extra blockchain operations (e.g. data movement (between accounts) for NFTs), +// and for eventual increases in gas requirements (thus avoid breaking changes in clients of TW-core). +const uint64_t ADDITIONAL_GAS_FOR_ESDT_NFT_TRANSFER = 500000; + +GasEstimator::GasEstimator(const NetworkConfig& networkConfig) { + this->networkConfig = networkConfig; +} + +uint64_t GasEstimator::forEGLDTransfer(size_t dataLength) const { + uint64_t gasLimit = + this->networkConfig.getMinGasLimit() + + this->networkConfig.getGasPerDataByte() * dataLength; + + return gasLimit; +} + +uint64_t GasEstimator::forESDTTransfer(size_t dataLength) const { + uint64_t gasLimit = + this->networkConfig.getMinGasLimit() + + this->networkConfig.getGasCostESDTTransfer() + + this->networkConfig.getGasPerDataByte() * dataLength + + ADDITIONAL_GAS_FOR_ESDT_TRANSFER; + + return gasLimit; +} + +uint64_t GasEstimator::forESDTNFTTransfer(size_t dataLength) const { + uint64_t gasLimit = + this->networkConfig.getMinGasLimit() + + this->networkConfig.getGasCostESDTNFTTransfer() + + this->networkConfig.getGasPerDataByte() * dataLength + + ADDITIONAL_GAS_FOR_ESDT_NFT_TRANSFER; + + return gasLimit; +} + +} // namespace TW::Elrond diff --git a/src/Elrond/GasEstimator.h b/src/Elrond/GasEstimator.h new file mode 100644 index 00000000000..4c14ed434d6 --- /dev/null +++ b/src/Elrond/GasEstimator.h @@ -0,0 +1,24 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include +#include "NetworkConfig.h" + +namespace TW::Elrond { + +class GasEstimator { + NetworkConfig networkConfig; +public: + GasEstimator(const NetworkConfig& networkConfig); + + uint64_t forEGLDTransfer(size_t dataLength) const; + uint64_t forESDTTransfer(size_t dataLength) const; + uint64_t forESDTNFTTransfer(size_t dataLength) const; +}; + +} // namespace diff --git a/src/Elrond/NetworkConfig.cpp b/src/Elrond/NetworkConfig.cpp new file mode 100644 index 00000000000..ba514e9183f --- /dev/null +++ b/src/Elrond/NetworkConfig.cpp @@ -0,0 +1,85 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "NetworkConfig.h" + +#include + +namespace TW::Elrond { + +NetworkConfig::NetworkConfig() + : chainId("1") /* Mainnet */ { +} + +const std::string& NetworkConfig::getChainId() const { + return this->chainId; +} + +void NetworkConfig::setChainId(const std::string& value) { + this->chainId = value; +} + +uint32_t NetworkConfig::getGasPerDataByte() const { + return this->gasPerDataByte; +} + +void NetworkConfig::setGasPerDataByte(uint32_t value) { + this->gasPerDataByte = value; +} + +uint32_t NetworkConfig::getMinGasLimit() const { + return this->minGasLimit; +} + +void NetworkConfig::setMinGasLimit(uint32_t value) { + this->minGasLimit = value; +} + +uint64_t NetworkConfig::getMinGasPrice() const { + return this->minGasPrice; +} + +void NetworkConfig::setMinGasPrice(uint64_t value) { + this->minGasPrice = value; +} + +uint32_t NetworkConfig::getGasCostESDTTransfer() const { + return this->gasCostESDTTransfer; +} + +void NetworkConfig::setGasCostESDTTransfer(uint32_t value) { + this->gasCostESDTTransfer = value; +} + +uint32_t NetworkConfig::getGasCostESDTNFTTransfer() const { + return this->gasCostESDTNFTTransfer; +} + +void NetworkConfig::setGasCostESDTNFTTransfer(uint32_t value) { + this->gasCostESDTNFTTransfer = value; +} + +NetworkConfig NetworkConfig::GetDefault() { + const uint64_t timestamp = duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + return GetByTimestamp(timestamp); +} + +NetworkConfig NetworkConfig::GetByTimestamp(uint64_t timestamp) { + NetworkConfig networkConfig; + + // Mainnet values at the time of defining the "NetworkConfig" component (December 2021). + if (timestamp > 0) { + networkConfig.setGasPerDataByte(1500); + networkConfig.setMinGasLimit(50000); + networkConfig.setMinGasPrice(1000000000); + networkConfig.setGasCostESDTTransfer(200000); + networkConfig.setGasCostESDTNFTTransfer(200000); + } + + return networkConfig; +} + +} // namespace TW::Elrond diff --git a/src/Elrond/NetworkConfig.h b/src/Elrond/NetworkConfig.h new file mode 100644 index 00000000000..79b5ebf61d0 --- /dev/null +++ b/src/Elrond/NetworkConfig.h @@ -0,0 +1,54 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include + +namespace TW::Elrond { + +/// A "NetworkConfig" object holds the network parameters relevant to creating transactions (e.g. minimum gas limit, minimum gas price). +class NetworkConfig { + /// The following fields can (should) be fetched from https://api.elrond.com/network/config. + /// However, a "NetworkConfig" object is initialized with proper default values for Elrond Mainnet (as of December 2021). + std::string chainId; + uint32_t gasPerDataByte; + uint32_t minGasLimit; + uint64_t minGasPrice; + + /// GasSchedule entries of interest (only one at this moment), according to: https://github.com/ElrondNetwork/elrond-config-mainnet/blob/master/gasSchedules. + /// Here, for the sake of simplicity, we define the gas costs of interest directly in the class "NetworkConfig" + /// (that is, without defining extra nested structures such as "GasSchedule" and "BuiltInCosts"). + uint32_t gasCostESDTTransfer; + uint32_t gasCostESDTNFTTransfer; +public: + NetworkConfig(); + + const std::string& getChainId() const; + void setChainId(const std::string& value); + + uint32_t getGasPerDataByte() const; + void setGasPerDataByte(uint32_t value); + + uint32_t getMinGasLimit() const; + void setMinGasLimit(uint32_t value); + + uint64_t getMinGasPrice() const; + void setMinGasPrice(uint64_t value); + + uint32_t getGasCostESDTTransfer() const; + void setGasCostESDTTransfer(uint32_t value); + + uint32_t getGasCostESDTNFTTransfer() const; + void setGasCostESDTNFTTransfer(uint32_t value); + + static NetworkConfig GetDefault(); + + /// Useful to implement upwards-compatible changes of the network configuration (a TWCore client can receive planned configuration updates, in advance). + static NetworkConfig GetByTimestamp(uint64_t timestamp); +}; + +} // namespace diff --git a/src/Elrond/Serialization.cpp b/src/Elrond/Serialization.cpp index eaccaabf417..ffe2241bd08 100644 --- a/src/Elrond/Serialization.cpp +++ b/src/Elrond/Serialization.cpp @@ -6,63 +6,76 @@ #include "Serialization.h" -#include "../Elrond/Address.h" -#include "../proto/Elrond.pb.h" +#include "Address.h" #include "Base64.h" -#include "PrivateKey.h" using namespace TW; -std::map fields_order { +std::map fields_order{ {"nonce", 1}, {"value", 2}, {"receiver", 3}, {"sender", 4}, - {"gasPrice", 5}, - {"gasLimit", 6}, - {"data", 7}, - {"chainID", 8}, - {"version", 9}, - {"signature", 10} -}; + {"senderUsername", 5}, + {"receiverUsername", 6}, + {"gasPrice", 7}, + {"gasLimit", 8}, + {"data", 9}, + {"chainID", 10}, + {"version", 11}, + {"options", 12}, + {"signature", 13}}; struct FieldsSorter { - bool operator() (const string& lhs, const string& rhs) const { - return fields_order[lhs] < fields_order[rhs]; + bool operator()(const std::string& lhs, const std::string& rhs) const { + return fields_order[lhs] < fields_order[rhs]; } }; -template +template using sorted_map = std::map; using sorted_json = nlohmann::basic_json; -sorted_json preparePayload(const Elrond::Proto::TransactionMessage& message) { - sorted_json payload { - {"nonce", json(message.nonce())}, - {"value", json(message.value())}, - {"receiver", json(message.receiver())}, - {"sender", json(message.sender())}, - {"gasPrice", json(message.gas_price())}, - {"gasLimit", json(message.gas_limit())}, +sorted_json preparePayload(const Elrond::Transaction& transaction) { + using namespace nlohmann; + sorted_json payload{ + {"nonce", json(transaction.nonce)}, + {"value", json(transaction.value)}, + {"receiver", json(transaction.receiver)}, + {"sender", json(transaction.sender)}, + {"gasPrice", json(transaction.gasPrice)}, + {"gasLimit", json(transaction.gasLimit)}, }; - if (!message.data().empty()) { - payload["data"] = json(TW::Base64::encode(TW::data(message.data()))); + if (!transaction.senderUsername.empty()) { + payload["senderUsername"] = json(Base64::encode(data(transaction.senderUsername))); + } + + if (!transaction.receiverUsername.empty()) { + payload["receiverUsername"] = json(Base64::encode(data(transaction.receiverUsername))); } - payload["chainID"] = json(message.chain_id()); - payload["version"] = json(message.version()); + if (!transaction.data.empty()) { + payload["data"] = json(Base64::encode(data(transaction.data))); + } + + payload["chainID"] = json(transaction.chainID); + payload["version"] = json(transaction.version); + + if (transaction.options != 0) { + payload["options"] = json(transaction.options); + } return payload; } -string Elrond::serializeTransaction(const Proto::TransactionMessage& message) { - sorted_json payload = preparePayload(message); +std::string Elrond::serializeTransaction(const Elrond::Transaction& transaction) { + sorted_json payload = preparePayload(transaction); return payload.dump(); } -string Elrond::serializeSignedTransaction(const Proto::TransactionMessage& message, string signature) { - sorted_json payload = preparePayload(message); - payload["signature"] = json(signature); +std::string Elrond::serializeSignedTransaction(const Elrond::Transaction& transaction, std::string signature) { + sorted_json payload = preparePayload(transaction); + payload["signature"] = nlohmann::json(signature); return payload.dump(); } diff --git a/src/Elrond/Serialization.h b/src/Elrond/Serialization.h index e56d8a5bf87..64023d47213 100644 --- a/src/Elrond/Serialization.h +++ b/src/Elrond/Serialization.h @@ -6,16 +6,16 @@ #pragma once -#include "../proto/Elrond.pb.h" #include "Data.h" +#include "Transaction.h" #include +namespace TW::Elrond { + using string = std::string; using json = nlohmann::json; -namespace TW::Elrond { - -string serializeTransaction(const Proto::TransactionMessage& message); -string serializeSignedTransaction(const Proto::TransactionMessage& message, string encodedSignature); +string serializeTransaction(const Transaction& transaction); +string serializeSignedTransaction(const Transaction& transaction, string encodedSignature); -} // namespace +} // namespace TW::Elrond diff --git a/src/Elrond/Signer.cpp b/src/Elrond/Signer.cpp index 20519c8b80a..f26e6c0460c 100644 --- a/src/Elrond/Signer.cpp +++ b/src/Elrond/Signer.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -7,21 +7,23 @@ #include "Signer.h" #include "Address.h" #include "Serialization.h" -#include "../PublicKey.h" +#include "TransactionFactory.h" #include "HexCoding.h" #include -using namespace TW; -using namespace TW::Elrond; +namespace TW::Elrond { -Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { + TransactionFactory factory; + + auto transaction = factory.create(input); auto privateKey = PrivateKey(input.private_key()); - auto signableAsString = serializeTransaction(input.transaction()); + auto signableAsString = serializeTransaction(transaction); auto signableAsData = TW::data(signableAsString); auto signature = privateKey.sign(signableAsData, TWCurveED25519); auto encodedSignature = hex(signature); - auto encoded = serializeSignedTransaction(input.transaction(), encodedSignature); + auto encoded = serializeSignedTransaction(transaction, encodedSignature); auto protoOutput = Proto::SigningOutput(); protoOutput.set_signature(encodedSignature); @@ -36,3 +38,5 @@ std::string Signer::signJSON(const std::string& json, const Data& key) { auto output = sign(input); return output.encoded(); } + +} // namespace TW::Elrond diff --git a/src/Elrond/Signer.h b/src/Elrond/Signer.h index 046f5e52d67..df43bf1295d 100644 --- a/src/Elrond/Signer.h +++ b/src/Elrond/Signer.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" #include "../proto/Elrond.pb.h" diff --git a/src/Elrond/Transaction.cpp b/src/Elrond/Transaction.cpp new file mode 100644 index 00000000000..24c8373e9c5 --- /dev/null +++ b/src/Elrond/Transaction.cpp @@ -0,0 +1,16 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Transaction.h" + +namespace TW::Elrond { + +Transaction::Transaction() + : nonce(0), sender(""), senderUsername(""), receiver(""), receiverUsername(""), value("0"), data(""), gasPrice(0), gasLimit(0), chainID(""), version(0), options(0) { +} + +} // namespace TW::Elrond + diff --git a/src/Elrond/Transaction.h b/src/Elrond/Transaction.h new file mode 100644 index 00000000000..a98e7435360 --- /dev/null +++ b/src/Elrond/Transaction.h @@ -0,0 +1,31 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include + +namespace TW::Elrond { + +class Transaction { +public: + uint64_t nonce; + std::string sender; + std::string senderUsername; + std::string receiver; + std::string receiverUsername; + std::string value; + std::string data; + uint64_t gasPrice; + uint64_t gasLimit; + std::string chainID; + uint32_t version; + uint32_t options; + + Transaction(); +}; + +} // namespace diff --git a/src/Elrond/TransactionFactory.cpp b/src/Elrond/TransactionFactory.cpp new file mode 100644 index 00000000000..4e3a271165e --- /dev/null +++ b/src/Elrond/TransactionFactory.cpp @@ -0,0 +1,151 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "TransactionFactory.h" + +#include "Codec.h" + +namespace TW::Elrond { + +const int TX_VERSION = 1; + +TransactionFactory::TransactionFactory() + : TransactionFactory(NetworkConfig::GetDefault()) { +} + +TransactionFactory::TransactionFactory(const NetworkConfig& networkConfig) + : networkConfig(networkConfig), gasEstimator(networkConfig) { +} + +Transaction TransactionFactory::create(const Proto::SigningInput& input) { + if (input.has_egld_transfer()) { + return fromEGLDTransfer(input); + } else if (input.has_esdt_transfer()) { + return fromESDTTransfer(input); + } else if (input.has_esdtnft_transfer()) { + return fromESDTNFTTransfer(input); + } else { + return fromGenericAction(input); + } +} + +/// Copies the input fields into a transaction object, without any other logic. +Transaction TransactionFactory::fromGenericAction(const Proto::SigningInput& input) { + auto action = input.generic_action(); + + Transaction transaction; + transaction.nonce = action.accounts().sender_nonce(); + transaction.sender = action.accounts().sender(); + transaction.senderUsername = action.accounts().sender_username(); + transaction.receiver = action.accounts().receiver(); + transaction.receiverUsername = action.accounts().receiver_username(); + transaction.value = action.value(); + transaction.data = action.data(); + transaction.gasLimit = input.gas_limit(); + transaction.gasPrice = input.gas_price(); + transaction.chainID = input.chain_id(); + transaction.version = action.version(); + transaction.options = action.options(); + + return transaction; +} + +Transaction TransactionFactory::fromEGLDTransfer(const Proto::SigningInput& input) { + auto transfer = input.egld_transfer(); + + uint64_t estimatedGasLimit = this->gasEstimator.forEGLDTransfer(0); + + Transaction transaction; + transaction.nonce = transfer.accounts().sender_nonce(); + transaction.sender = transfer.accounts().sender(); + transaction.senderUsername = transfer.accounts().sender_username(); + transaction.receiver = transfer.accounts().receiver(); + transaction.receiverUsername = transfer.accounts().receiver_username(); + transaction.value = transfer.amount(); + transaction.gasLimit = coalesceGasLimit(input.gas_limit(), estimatedGasLimit); + transaction.gasPrice = coalesceGasPrice(input.gas_price()); + transaction.chainID = coalesceChainId(input.chain_id()); + transaction.version = TX_VERSION; + + return transaction; +} + +Transaction TransactionFactory::fromESDTTransfer(const Proto::SigningInput& input) { + auto transfer = input.esdt_transfer(); + + std::string encodedTokenIdentifier = Codec::encodeString(transfer.token_identifier()); + std::string encodedAmount = Codec::encodeBigInt(transfer.amount()); + std::string data = prepareFunctionCall("ESDTTransfer", {encodedTokenIdentifier, encodedAmount}); + uint64_t estimatedGasLimit = this->gasEstimator.forESDTTransfer(data.size()); + + Transaction transaction; + transaction.nonce = transfer.accounts().sender_nonce(); + transaction.sender = transfer.accounts().sender(); + transaction.senderUsername = transfer.accounts().sender_username(); + transaction.receiver = transfer.accounts().receiver(); + transaction.receiverUsername = transfer.accounts().receiver_username(); + transaction.value = "0"; + transaction.data = data; + transaction.gasLimit = coalesceGasLimit(input.gas_limit(), estimatedGasLimit); + transaction.gasPrice = coalesceGasPrice(input.gas_price()); + transaction.chainID = coalesceChainId(input.chain_id()); + transaction.version = TX_VERSION; + + return transaction; +} + +Transaction TransactionFactory::fromESDTNFTTransfer(const Proto::SigningInput& input) { + auto transfer = input.esdtnft_transfer(); + + std::string encodedCollection = Codec::encodeString(transfer.token_collection()); + std::string encodedNonce = Codec::encodeUint64(transfer.token_nonce()); + std::string encodedQuantity = Codec::encodeBigInt(transfer.amount()); + std::string encodedReceiver = Codec::encodeAddress(transfer.accounts().receiver()); + std::string data = prepareFunctionCall("ESDTNFTTransfer", {encodedCollection, encodedNonce, encodedQuantity, encodedReceiver}); + uint64_t estimatedGasLimit = this->gasEstimator.forESDTNFTTransfer(data.size()); + + Transaction transaction; + transaction.nonce = transfer.accounts().sender_nonce(); + // For NFT, SFT and MetaESDT, transaction.sender == transaction.receiver. + transaction.sender = transfer.accounts().sender(); + transaction.receiver = transfer.accounts().sender(); + transaction.value = "0"; + transaction.data = data; + transaction.gasLimit = coalesceGasLimit(input.gas_limit(), estimatedGasLimit); + transaction.gasPrice = coalesceGasPrice(input.gas_price()); + transaction.chainID = coalesceChainId(input.chain_id()); + transaction.version = TX_VERSION; + + return transaction; +} + +uint64_t TransactionFactory::coalesceGasLimit(uint64_t providedGasLimit, uint64_t estimatedGasLimit) { + return providedGasLimit > 0 ? providedGasLimit : estimatedGasLimit; +} + +uint64_t TransactionFactory::coalesceGasPrice(uint64_t gasPrice) { + return gasPrice > 0 ? gasPrice : this->networkConfig.getMinGasPrice(); +} + +std::string TransactionFactory::coalesceChainId(std::string chainID) { + return chainID.empty() ? this->networkConfig.getChainId() : chainID; +} + +std::string TransactionFactory::prepareFunctionCall(const std::string& function, std::initializer_list arguments) { + const auto ARGUMENTS_SEPARATOR = "@"; + std::string result; + + result.append(function); + + for (auto argument : arguments) { + result.append(ARGUMENTS_SEPARATOR); + result.append(argument); + } + + return result; +} + +} // namespace TW::Elrond diff --git a/src/Elrond/TransactionFactory.h b/src/Elrond/TransactionFactory.h new file mode 100644 index 00000000000..db3b3058b9d --- /dev/null +++ b/src/Elrond/TransactionFactory.h @@ -0,0 +1,57 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "../proto/Elrond.pb.h" +#include "Address.h" +#include "NetworkConfig.h" +#include "GasEstimator.h" +#include "Transaction.h" +#include "uint256.h" + +namespace TW::Elrond { + +/// Creates specific transaction objects, wrt. the provided "NetworkConfig". +class TransactionFactory { +private: + NetworkConfig networkConfig; + GasEstimator gasEstimator; +public: + TransactionFactory(); + TransactionFactory(const NetworkConfig& networkConfig); + + /// Creates the appropriate transaction object, with respect to the "oneof" field (substructure) of Proto::SigningInput. + Transaction create(const Proto::SigningInput &input); + + Transaction fromGenericAction(const Proto::SigningInput& input); + + /// This should be used to transfer EGLD. + /// For reference, see: https://docs.elrond.com/developers/signing-transactions/signing-transactions/#general-structure. + Transaction fromEGLDTransfer(const Proto::SigningInput& input); + + /// This should be used to transfer regular ESDTs (fungible tokens). + /// For reference, see: https://docs.elrond.com/developers/esdt-tokens/#transfers + /// + /// The "regular" ESDT tokens held by an account can be fetched from https://api.elrond.com/accounts/{address}/tokens. + Transaction fromESDTTransfer(const Proto::SigningInput& input); + + /// This should be used to transfer NFTs, SFTs and Meta ESDTs. + /// For reference, see: https://docs.elrond.com/developers/nft-tokens/#transfers + /// + /// The semi-fungible and non-fungible tokens held by an account can be fetched from https://api.elrond.com/accounts/{address}/nfts?type=SemiFungibleESDT,NonFungibleESDT. + /// The Meta ESDTs (a special kind of SFTs) held by an account can be fetched from https://api.elrond.com/accounts/{address}/nfts?type=MetaESDT. + /// + /// The fields "token_collection" and "token_nonce" are found as well in the HTTP response of the API call (as "collection" and "nonce", respectively). + Transaction fromESDTNFTTransfer(const Proto::SigningInput& input); +private: + uint64_t coalesceGasLimit(uint64_t providedGasLimit, uint64_t estimatedGasLimit); + uint64_t coalesceGasPrice(uint64_t gasPrice); + std::string coalesceChainId(std::string chainID); + std::string prepareFunctionCall(const std::string& function, std::initializer_list arguments); +}; + +} // namespace diff --git a/src/Encrypt.h b/src/Encrypt.h index 5b514b3b8c5..e024186f653 100644 --- a/src/Encrypt.h +++ b/src/Encrypt.h @@ -11,7 +11,7 @@ namespace TW::Encrypt { -/// Determind needed padding size (used internally) +/// Determined needed padding size (used internally) size_t paddingSize(size_t origSize, size_t blockSize, TWAESPaddingMode paddingMode); /// Encrypts a block of data using AES in Cipher Block Chaining (CBC) mode. diff --git a/src/Ethereum/ABI/Array.cpp b/src/Ethereum/ABI/Array.cpp index cc5f9255bbd..90071786336 100644 --- a/src/Ethereum/ABI/Array.cpp +++ b/src/Ethereum/ABI/Array.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -13,28 +13,41 @@ #include -using namespace TW::Ethereum::ABI; +namespace TW::Ethereum::ABI { + using namespace TW; using json = nlohmann::json; int ParamArray::addParam(const std::shared_ptr& param) { assert(param != nullptr); - if (param == nullptr) { return -1; } - if (_params.getCount() >= 1 && param->getType() != getFirstType()) { return -2; } // do not add different types + if (param == nullptr) { + return -1; + } + if (_params.getCount() >= 1 && param->getType() != getProtoType()) { + return -2; + } // do not add different types return _params.addParam(param); } void ParamArray::addParams(const std::vector>& params) { - for (auto p: params) { addParam(p); } + for (auto p : params) { + addParam(p); + } +} + +std::shared_ptr ParamArray::getProtoElem() const { + if (_params.getCount() >= 1) { + return _params.getParamUnsafe(0); + } + return _proto; } -std::string ParamArray::getFirstType() const { - if (_params.getCount() == 0) { return "empty"; } - return _params.getParamUnsafe(0)->getType(); +std::string ParamArray::getProtoType() const { + const auto proto = getProtoElem(); + return (proto != nullptr) ? proto->getType() : "__empty__"; } -size_t ParamArray::getSize() const -{ +size_t ParamArray::getSize() const { return 32 + _params.getSize(); } @@ -92,10 +105,10 @@ bool ParamArray::setValueJson(const std::string& value) { } // make sure enough elements are in the array while (_params.getCount() < valuesJson.size()) { - addParam(ParamFactory::make(getFirstType())); + addParam(ParamFactory::make(getProtoType())); } int cnt = 0; - for (const auto& e: valuesJson) { + for (const auto& e : valuesJson) { std::string eString = e.is_string() ? e.get() : e.dump(); _params.getParamUnsafe(cnt)->setValueJson(eString); ++cnt; @@ -104,6 +117,9 @@ bool ParamArray::setValueJson(const std::string& value) { } Data ParamArray::hashStruct() const { + if (_params.getCount() == 0) { + return Hash::keccak256(Data()); + } Data hash(32); Data hashes = _params.encodeHashes(); if (hashes.size() > 0) { @@ -113,9 +129,44 @@ Data ParamArray::hashStruct() const { } std::string ParamArray::getExtraTypes(std::vector& ignoreList) const { - std::shared_ptr p; - if (!_params.getParam(0, p)) { - return ""; + const auto& proto = getProtoElem(); + return (proto != nullptr) ? proto->getExtraTypes(ignoreList) : ""; +} + +void ParamArrayFix::encode(Data& data) const { + this->_params.encode(data); +} + +bool ParamArrayFix::decode(const Data& encoded, size_t& offset_inout) { + return this->_params.decode(encoded, offset_inout); +} + +bool ParamArrayFix::setValueJson(const std::string& value) { + auto valuesJson = json::parse(value, nullptr, false); + if (valuesJson.is_discarded() || !valuesJson.is_array() || _params.getCount() != valuesJson.size()) { + return false; + } + + std::size_t idx{0}; + for (auto&& e : valuesJson) { + std::string eString = e.is_string() ? e.get() : e.dump(); + _params.getParamUnsafe(idx)->setValueJson(eString); + ++idx; } - return p->getExtraTypes(ignoreList); + return true; } + +void ParamArrayFix::addParams(const Params& params) { + auto addParamFunctor = [this](auto&& param) { + if (param == nullptr) { + throw std::runtime_error("param can't be nullptr"); + } + if (_params.getCount() >= 1 && param->getType() != _params.getParamUnsafe(0)->getType()) { + throw std::runtime_error("params need to be the same type"); + } // do not add different types + _params.addParam(param); + }; + std::for_each(begin(params), end(params), addParamFunctor); +} + +} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/Array.h b/src/Ethereum/ABI/Array.h index c7b29f04c7b..08138aad6a2 100644 --- a/src/Ethereum/ABI/Array.h +++ b/src/Ethereum/ABI/Array.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -13,22 +13,30 @@ namespace TW::Ethereum::ABI { /// Dynamic array of the same types, "[]" -class ParamArray: public ParamCollection -{ +/// Normally has at least one element. Empty array can have prototype set so its type is known. +/// Empty with no prototype is possible, but should be avoided. +class ParamArray : public ParamCollection { private: ParamSet _params; + std::shared_ptr _proto; // an optional prototype element, determines the array type, useful in empty array case + +private: + std::shared_ptr getProtoElem() const; // the first element if exists, otherwise the proto element + std::string getProtoType() const; public: ParamArray() = default; - ParamArray(const std::shared_ptr& param1) : ParamCollection() { addParam(param1); } - ParamArray(const std::vector>& params) : ParamCollection() { setVal(params); } + ParamArray(const std::shared_ptr& param1) + : ParamCollection() { addParam(param1); } + ParamArray(const std::vector>& params) + : ParamCollection() { setVal(params); } void setVal(const std::vector>& params) { addParams(params); } std::vector> const& getVal() const { return _params.getParams(); } int addParam(const std::shared_ptr& param); void addParams(const std::vector>& params); - std::string getFirstType() const; + void setProto(const std::shared_ptr& proto) { _proto = proto; } std::shared_ptr getParam(int paramIndex) { return _params.getParamUnsafe(paramIndex); } - virtual std::string getType() const { return getFirstType() + "[]"; } + virtual std::string getType() const { return getProtoType() + "[]"; } virtual size_t getSize() const; virtual bool isDynamic() const { return true; } virtual size_t getCount() const { return _params.getCount(); } @@ -39,4 +47,33 @@ class ParamArray: public ParamCollection virtual std::string getExtraTypes(std::vector& ignoreList) const; }; +/// Fixed-size array of the same type e.g, "type[4]" +class ParamArrayFix final : public ParamCollection { +public: + //! Public Definitions + using Params = std::vector>; + + //! Public constructor + explicit ParamArrayFix(const Params& params) noexcept(false) + : ParamCollection() { + this->addParams(params); + } + + //! Public member methods + [[nodiscard]] std::size_t getCount() const final { return _params.getCount(); } + [[nodiscard]] size_t getSize() const final { return _params.getSize(); } + [[nodiscard]] bool isDynamic() const final { return false; } + [[nodiscard]] std::string getType() const final { return _params.getParamUnsafe(0)->getType() + "[" + std::to_string(_params.getCount()) + "]"; } + void encode(Data& data) const final; + bool decode(const Data& encoded, size_t& offset_inout) final; + bool setValueJson(const std::string& value) final; + +private: + //! Private member functions + void addParams(const Params& params) noexcept(false); + + //! Private member fields + ParamSet _params; +}; + } // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/Bytes.cpp b/src/Ethereum/ABI/Bytes.cpp index 164991a2dba..0b40d521bb1 100644 --- a/src/Ethereum/ABI/Bytes.cpp +++ b/src/Ethereum/ABI/Bytes.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -12,8 +12,7 @@ #include -using namespace TW::Ethereum::ABI; -using namespace TW; +namespace TW::Ethereum::ABI { void ParamByteArray::encodeBytes(const Data& bytes, Data& data) { ValueEncoder::encodeUInt256(uint256_t(bytes.size()), data); @@ -123,8 +122,8 @@ bool ParamString::decodeString(const Data& encoded, std::string& decoded, size_t Data ParamString::hashStruct() const { Data hash(32); Data encoded = data(_str); - if (encoded.size() > 0) { - hash = Hash::keccak256(encoded); - } + hash = Hash::keccak256(encoded); return hash; } + +} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/Function.cpp b/src/Ethereum/ABI/Function.cpp index b2c067fcb26..9057d539d74 100644 --- a/src/Ethereum/ABI/Function.cpp +++ b/src/Ethereum/ABI/Function.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -6,12 +6,9 @@ #include "Function.h" -#include "../../Data.h" - #include -using namespace TW; -using namespace TW::Ethereum::ABI; +namespace TW::Ethereum::ABI { Data Function::getSignature() const { auto typ = getType(); @@ -28,14 +25,18 @@ void Function::encode(Data& data) const { bool Function::decodeOutput(const Data& encoded, size_t& offset_inout) { // read parameter values - if (!_outParams.decode(encoded, offset_inout)) { return false; } + if (!_outParams.decode(encoded, offset_inout)) { + return false; + } return true; } bool Function::decodeInput(const Data& encoded, size_t& offset_inout) { // read 4-byte hash auto p = ParamByteArrayFix(4); - if (!p.decode(encoded, offset_inout)) { return false; } + if (!p.decode(encoded, offset_inout)) { + return false; + } std::vector hash = p.getVal(); // adjust offset; hash is NOT padded to 32 bytes offset_inout = offset_inout - 32 + 4; @@ -46,6 +47,10 @@ bool Function::decodeInput(const Data& encoded, size_t& offset_inout) { return false; } // read parameters - if (!_inParams.decode(encoded, offset_inout)) { return false; } + if (!_inParams.decode(encoded, offset_inout)) { + return false; + } return true; } + +} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ParamAddress.cpp b/src/Ethereum/ABI/ParamAddress.cpp index de7663b4603..2d06aca161e 100644 --- a/src/Ethereum/ABI/ParamAddress.cpp +++ b/src/Ethereum/ABI/ParamAddress.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -8,19 +8,16 @@ #include #include -using namespace TW::Ethereum::ABI; -using namespace TW; +namespace TW::Ethereum::ABI { Data ParamAddress::getData() const { - Data data = store(getVal()); - if (data.size() >= bytes) { return data; } - // need to pad - Data padded(bytes - data.size()); - append(padded, data); - return padded; + Data data = store(getVal(), bytes); + return data; } bool ParamAddress::setValueJson(const std::string& value) { setVal(load(parse_hex(value))); return true; } + +} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ParamBase.h b/src/Ethereum/ABI/ParamBase.h index 464fa92c109..161abdc7c0c 100644 --- a/src/Ethereum/ABI/ParamBase.h +++ b/src/Ethereum/ABI/ParamBase.h @@ -26,7 +26,7 @@ class ParamBase // EIP712-style hash of the value (used for signing); default implementation virtual Data hashStruct() const; // Helper for EIP712 encoding; provide full type of all used types (recursively). Default is empty implementation. - virtual std::string getExtraTypes(std::vector& ignoreList) const { return ""; } + virtual std::string getExtraTypes([[maybe_unused]] std::vector& ignoreList) const { return ""; } }; /// Collection of parameters base class diff --git a/src/Ethereum/ABI/ParamFactory.cpp b/src/Ethereum/ABI/ParamFactory.cpp index 85660ad06e1..e82ce201246 100644 --- a/src/Ethereum/ABI/ParamFactory.cpp +++ b/src/Ethereum/ABI/ParamFactory.cpp @@ -117,6 +117,23 @@ std::shared_ptr ParamFactory::makeNamed(const std::string& name, con return std::make_shared(name, param); } +bool ParamFactory::isPrimitive(const std::string& type) { + if (starts_with(type, "address")) { + return true; + } else if (starts_with(type, "uint")) { + return true; + } else if (starts_with(type, "int")) { + return true; + } else if (starts_with(type, "bool")) { + return true; + } else if (starts_with(type, "bytes")) { + return true; + } else if (starts_with(type, "string")) { + return true; + } + return false; +} + std::string ParamFactory::getValue(const std::shared_ptr& param, const std::string& type) { std::string result = ""; if (isArrayType(type)) { @@ -126,7 +143,9 @@ std::string ParamFactory::getValue(const std::shared_ptr& param, cons auto value = dynamic_pointer_cast(param); result = hexEncoded(value->getData()); } else if (type == "uint8") { - result = boost::lexical_cast((unsigned int)dynamic_pointer_cast(param)->getVal()); + //result = boost::lexical_cast((uint)dynamic_pointer_cast(param)->getVal()); + result = boost::lexical_cast((unsigned int)dynamic_pointer_cast(param)->getVal());//win + } else if (type == "uint16") { result = boost::lexical_cast(dynamic_pointer_cast(param)->getVal()); } else if (type == "uint32") { @@ -178,7 +197,7 @@ std::vector ParamFactory::getArrayValue(const std::shared_ptrgetVal(); std::vector values(elems.size()); - for (auto i = 0; i < elems.size(); ++i) { + for (auto i = 0ul; i < elems.size(); ++i) { values[i] = getValue(elems[i], elemType); } return values; diff --git a/src/Ethereum/ABI/ParamFactory.h b/src/Ethereum/ABI/ParamFactory.h index bd7e875fb30..7ccdb948684 100644 --- a/src/Ethereum/ABI/ParamFactory.h +++ b/src/Ethereum/ABI/ParamFactory.h @@ -14,19 +14,23 @@ #include #include +#include "Wasm.h" + namespace TW::Ethereum::ABI { /// Factory creates concrete ParamBase class from string type. -class ParamFactory -{ +class ParamFactory { public: /// Create a param of given type static std::shared_ptr make(const std::string& type); /// Create a named param, with given name and type static std::shared_ptr makeNamed(const std::string& name, const std::string& type); + /// Check if given type is a primitive type + static bool isPrimitive(const std::string& type); static std::string getValue(const std::shared_ptr& param, const std::string& type); - static std::vector getArrayValue(const std::shared_ptr& param, const std::string& type); + static std::vector getArrayValue(const std::shared_ptr& param, + const std::string& type); }; } // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ParamNumber.cpp b/src/Ethereum/ABI/ParamNumber.cpp index ba4308cc985..07c05516883 100644 --- a/src/Ethereum/ABI/ParamNumber.cpp +++ b/src/Ethereum/ABI/ParamNumber.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -15,9 +15,7 @@ #include #include -using namespace TW; -using namespace TW::Ethereum::ABI; - +namespace TW::Ethereum::ABI { bool ParamUInt256::setUInt256FromValueJson(uint256_t& dest, const std::string& value) { // try hex string or number @@ -38,21 +36,31 @@ bool ParamInt256::setInt256FromValueJson(int256_t& dest, const std::string& valu } bool ParamBool::setValueJson(const std::string& value) { - if (value == "true" || value == "1") { setVal(true); return true; } - if (value == "false" || value == "0") { setVal(false); return true; } + if (value == "true" || value == "1") { + setVal(true); + return true; + } + if (value == "false" || value == "0") { + setVal(false); + return true; + } return false; } bool ParamUInt8::setValueJson(const std::string& value) { uint16_t val; - if (!boost::conversion::detail::try_lexical_convert(value, val)) { return false; } + if (!boost::conversion::detail::try_lexical_convert(value, val)) { + return false; + } setVal(static_cast(val)); return true; } bool ParamInt8::setValueJson(const std::string& value) { int16_t val; - if (!boost::conversion::detail::try_lexical_convert(value, val)) { return false; } + if (!boost::conversion::detail::try_lexical_convert(value, val)) { + return false; + } setVal(static_cast(val)); return true; } @@ -103,7 +111,8 @@ bool ParamIntN::decode(const Data& encoded, size_t& offset_inout) { return res; } -void ParamIntN::init() -{ +void ParamIntN::init() { _mask = ParamUIntN::maskForBits(bits); } + +} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ParamStruct.cpp b/src/Ethereum/ABI/ParamStruct.cpp index a23d2fcbf1d..7910c0db7f5 100644 --- a/src/Ethereum/ABI/ParamStruct.cpp +++ b/src/Ethereum/ABI/ParamStruct.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -7,23 +7,22 @@ #include "ParamStruct.h" #include "ValueEncoder.h" #include "ParamFactory.h" -#include "ParamAddress.h" #include #include +#include #include #include #include -using namespace TW::Ethereum::ABI; -using namespace TW; +namespace TW::Ethereum::ABI { + using json = nlohmann::json; static const Data EipStructPrefix = parse_hex("1901"); static const auto Eip712Domain = "EIP712Domain"; - std::string ParamNamed::getType() const { return _param->getType() + " " + _name; } @@ -63,7 +62,7 @@ std::string ParamSetNamed::getType() const { Data ParamSetNamed::encodeHashes() const { Data hashes; - for (auto p: _params) { + for (auto p : _params) { append(hashes, p->hashStruct()); } return hashes; @@ -71,7 +70,22 @@ Data ParamSetNamed::encodeHashes() const { std::string ParamSetNamed::getExtraTypes(std::vector& ignoreList) const { std::string types; - for (auto& p: _params) { + + auto params = _params; + /// referenced struct type should be sorted by name see: https://eips.ethereum.org/EIPS/eip-712#definition-of-encodetype + std::stable_sort(params.begin(), params.end(), [](auto& a, auto& b) { + auto lhs = a->getType(); + auto rhs = b->getType(); + if (ParamFactory::isPrimitive(lhs)) { + return true; + } + if (ParamFactory::isPrimitive(rhs)) { + return true; + } + return lhs.compare(rhs) < 0; + }); + + for (auto& p : params) { auto pType = p->_param->getType(); if (std::find(ignoreList.begin(), ignoreList.end(), pType) == ignoreList.end()) { types += p->getExtraTypes(ignoreList); @@ -82,7 +96,7 @@ std::string ParamSetNamed::getExtraTypes(std::vector& ignoreList) c } std::shared_ptr ParamSetNamed::findParamByName(const std::string& name) const { - for (auto& p: _params) { + for (auto& p : _params) { if (p->_name == name) { return p; } @@ -160,15 +174,16 @@ Data ParamStruct::hashStructJson(const std::string& messageJson) { message["message"].dump(), message["types"].dump()); if (messageStruct) { - TW::append(hashes, messageStruct->hashStruct()); + const auto messageHash = messageStruct->hashStruct(); + TW::append(hashes, messageHash); return Hash::keccak256(hashes); } } - return {}; // fallback + return {}; // fallback } std::shared_ptr findType(const std::string& typeName, const std::vector>& types) { - for (auto& t: types) { + for (auto& t : types) { if (t->getType() == typeName) { return t; } @@ -195,9 +210,9 @@ std::shared_ptr ParamStruct::makeStruct(const std::string& structTy std::vector> params; const auto& typeParams = typeInfo->getParams(); // iterate through the type; order is important and field order in the value json is not defined - for (int i = 0; i < typeParams.getCount(); ++i) { - auto name = typeParams.getParam(i)->getName(); - auto type = typeParams.getParam(i)->getParam()->getType(); + for (auto i = 0ul; i < typeParams.getCount(); ++i) { + auto name = typeParams.getParam(static_cast(i))->getName(); + auto type = typeParams.getParam(static_cast(i))->getParam()->getType(); // look for it in value (may throw) auto value = values[name]; // first try simple params @@ -221,15 +236,27 @@ std::shared_ptr ParamStruct::makeStruct(const std::string& structTy throw std::invalid_argument("Value must be array for type " + type); } std::vector> paramsArray; - for (const auto& e: value) { - auto subStruct = makeStruct(arrayType, e.dump(), typesJson); + if (value.size() == 0) { + // empty array + auto subStruct = makeStruct(arrayType, "{}", typesJson); if (!subStruct) { - throw std::invalid_argument("Could not process array sub-struct " + arrayType + " " + e.dump()); + throw std::invalid_argument("Could not process array sub-struct " + arrayType + " " + "{}"); } assert(subStruct); - paramsArray.push_back(subStruct); + auto tmp = std::make_shared(paramsArray); + tmp->setProto(subStruct); + params.push_back(std::make_shared(name, tmp)); + } else { + for (const auto& e : value) { + auto subStruct = makeStruct(arrayType, e.dump(), typesJson); + if (!subStruct) { + throw std::invalid_argument("Could not process array sub-struct " + arrayType + " " + e.dump()); + } + assert(subStruct); + paramsArray.push_back(subStruct); + } + params.push_back(std::make_shared(name, std::make_shared(paramsArray))); } - params.push_back(std::make_shared(name, std::make_shared(paramsArray))); } else { // try if sub struct auto subTypeInfo = findType(type, types); @@ -271,7 +298,7 @@ std::shared_ptr ParamStruct::makeType(const std::string& structName throw std::invalid_argument("Expecting array"); } std::vector> params; - for(auto& p2: jsonValue) { + for (auto& p2 : jsonValue) { auto name = p2["name"].get(); auto type = p2["type"].get(); if (name.empty() || type.empty()) { @@ -354,3 +381,5 @@ std::vector> ParamStruct::makeTypes(const std::stri throw std::invalid_argument("Could not process Json"); } } + +} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ParamStruct.h b/src/Ethereum/ABI/ParamStruct.h index 53eb8d42711..816720905dd 100644 --- a/src/Ethereum/ABI/ParamStruct.h +++ b/src/Ethereum/ABI/ParamStruct.h @@ -85,9 +85,9 @@ class ParamStruct: public ParamCollection virtual size_t getSize() const { return _params.getCount(); } virtual bool isDynamic() const { return true; } virtual size_t getCount() const { return _params.getCount(); } - virtual void encode(Data& data) const {} - virtual bool decode(const Data& encoded, size_t& offset_inout) { return true; } - virtual bool setValueJson(const std::string& value) { return false; } // see makeStruct + virtual void encode([[maybe_unused]] Data& data) const {} + virtual bool decode([[maybe_unused]] const Data& encoded, [[maybe_unused]] size_t& offset_inout) { return true; } + virtual bool setValueJson([[maybe_unused]] const std::string& value) { return false; } // see makeStruct Data encodeHashes() const; virtual std::string getExtraTypes(std::vector& ignoreList) const; std::shared_ptr findParamByName(const std::string& name) const { return _params.findParamByName(name); } diff --git a/src/Ethereum/ABI/Parameters.cpp b/src/Ethereum/ABI/Parameters.cpp index ed734d78257..3cc66daefd9 100644 --- a/src/Ethereum/ABI/Parameters.cpp +++ b/src/Ethereum/ABI/Parameters.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -11,8 +11,7 @@ #include #include -using namespace TW::Ethereum::ABI; -using namespace TW; +namespace TW::Ethereum::ABI { ParamSet::~ParamSet() { _params.clear(); @@ -34,7 +33,7 @@ void ParamSet::addParams(const std::vector>& params) } } -bool ParamSet::getParam(int paramIndex, std::shared_ptr& param_out) const { +bool ParamSet::getParam(size_t paramIndex, std::shared_ptr& param_out) const { if (paramIndex >= _params.size() || paramIndex < 0) { return false; } @@ -42,7 +41,7 @@ bool ParamSet::getParam(int paramIndex, std::shared_ptr& param_out) c return true; } -std::shared_ptr ParamSet::getParamUnsafe(int paramIndex) const { +std::shared_ptr ParamSet::getParamUnsafe(size_t paramIndex) const { if (_params.size() == 0) { // zero parameter, nothing to return. This may cause trouble (segfault) return nullptr; @@ -69,7 +68,7 @@ std::string ParamSet::getType() const { } bool ParamSet::isDynamic() const { - for (const auto& p: _params) { + for (const auto& p : _params) { if (p->isDynamic()) { return true; } @@ -80,7 +79,7 @@ bool ParamSet::isDynamic() const { size_t ParamSet::getSize() const { // 2-pass encoding size_t s = 0; - for (auto p: _params) { + for (auto p : _params) { if (p->isDynamic() || p->getSize() > ValueEncoder::encodedIntSize) { // offset used s += 32; @@ -156,7 +155,7 @@ bool ParamSet::decode(const Data& encoded, size_t& offset_inout) { Data ParamSet::encodeHashes() const { Data hashes; - for (auto p: _params) { + for (auto p : _params) { append(hashes, p->hashStruct()); } return hashes; @@ -170,3 +169,5 @@ Data Parameters::hashStruct() const { } return hash; } + +} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/Parameters.h b/src/Ethereum/ABI/Parameters.h index 4f62ff8f326..ac315235fbe 100644 --- a/src/Ethereum/ABI/Parameters.h +++ b/src/Ethereum/ABI/Parameters.h @@ -29,8 +29,8 @@ class ParamSet { /// Returns the index of the parameter int addParam(const std::shared_ptr& param); void addParams(const std::vector>& params); - bool getParam(int paramIndex, std::shared_ptr& param_out) const; - std::shared_ptr getParamUnsafe(int paramIndex) const; + bool getParam(size_t paramIndex, std::shared_ptr& param_out) const; + std::shared_ptr getParamUnsafe(size_t paramIndex) const; size_t getCount() const { return _params.size(); } std::vector> const& getParams() const { return _params; } /// Return the function type signature, of the form "baz(int32,uint256)" @@ -56,14 +56,14 @@ class Parameters: public ParamCollection Parameters(const std::vector>& params) : ParamCollection(), _params(ParamSet(params)) {} void addParam(const std::shared_ptr& param) { _params.addParam(param); } void addParams(const std::vector>& params) { _params.addParams(params); } - std::shared_ptr getParam(int paramIndex) const { return _params.getParamUnsafe(paramIndex); } + std::shared_ptr getParam(size_t paramIndex) const { return _params.getParamUnsafe(paramIndex); } virtual std::string getType() const { return _params.getType(); } virtual size_t getSize() const { return _params.getSize(); } virtual bool isDynamic() const { return true; } virtual size_t getCount() const { return _params.getCount(); } virtual void encode(Data& data) const { _params.encode(data); } virtual bool decode(const Data& encoded, size_t& offset_inout) { return _params.decode(encoded, offset_inout); } - virtual bool setValueJson(const std::string& value) { return false; } + virtual bool setValueJson([[maybe_unused]] const std::string& value) { return false; } virtual Data hashStruct() const; }; diff --git a/src/Ethereum/ABI/Tuple.cpp b/src/Ethereum/ABI/Tuple.cpp index 72c6a6a66d2..c674592ff2b 100644 --- a/src/Ethereum/ABI/Tuple.cpp +++ b/src/Ethereum/ABI/Tuple.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -6,14 +6,12 @@ #include "Tuple.h" -#include "Data.h" - #include -using namespace TW; -using namespace TW::Ethereum::ABI; - +namespace TW::Ethereum::ABI { int ParamTuple::addParam(std::shared_ptr param) { return _params.addParam(param); } + +} // namespace TW::Ethereum::ABI \ No newline at end of file diff --git a/src/Ethereum/ABI/Tuple.h b/src/Ethereum/ABI/Tuple.h index b9aca129e28..6579f9784f6 100644 --- a/src/Ethereum/ABI/Tuple.h +++ b/src/Ethereum/ABI/Tuple.h @@ -34,7 +34,7 @@ class ParamTuple: public ParamCollection virtual bool isDynamic() const { return _params.isDynamic(); } virtual void encode(Data& data) const { return _params.encode(data); } virtual bool decode(const Data& encoded, size_t& offset_inout) { return _params.decode(encoded, offset_inout); } - virtual bool setValueJson(const std::string& value) { return false; } + virtual bool setValueJson([[maybe_unused]] const std::string& value) { return false; } virtual size_t getCount() const { return _params.getCount(); } }; diff --git a/src/Ethereum/ABI/ValueEncoder.cpp b/src/Ethereum/ABI/ValueEncoder.cpp index 44cb7aa7f76..d244a5acbf0 100644 --- a/src/Ethereum/ABI/ValueEncoder.cpp +++ b/src/Ethereum/ABI/ValueEncoder.cpp @@ -46,7 +46,7 @@ inline Data paddedOnLeft(const Data& inout) { } void ValueEncoder::encodeUInt256(const uint256_t& value, Data& inout) { - append(inout, paddedOnLeft(store(value))); + append(inout, store(value, 32)); } /// Encoding primitive: encode a number of bytes by taking hash diff --git a/src/Ethereum/Address.cpp b/src/Ethereum/Address.cpp index 09a7c893f54..48d013bde0c 100644 --- a/src/Ethereum/Address.cpp +++ b/src/Ethereum/Address.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -6,10 +6,9 @@ #include "Address.h" #include "AddressChecksum.h" -#include "../Hash.h" #include "../HexCoding.h" -using namespace TW::Ethereum; +namespace TW::Ethereum { bool Address::isValid(const std::string& string) { if (string.size() != 42 || string[0] != '0' || string[1] != 'x') { @@ -38,10 +37,12 @@ Address::Address(const PublicKey& publicKey) { if (publicKey.type != TWPublicKeyTypeSECP256k1Extended) { throw std::invalid_argument("Ethereum::Address needs an extended SECP256k1 public key."); } - const auto data = publicKey.hash({}, static_cast(Hash::keccak256), true); + const auto data = publicKey.hash({}, Hash::HasherKeccak256, true); std::copy(data.end() - Address::size, data.end(), bytes.begin()); } std::string Address::string() const { - return checksumed(*this, ChecksumType::eip55); + return checksumed(*this); } + +} // namespace TW::Ethereum diff --git a/src/Ethereum/Address.h b/src/Ethereum/Address.h index 1f9e25d3490..c14a56146f1 100644 --- a/src/Ethereum/Address.h +++ b/src/Ethereum/Address.h @@ -40,6 +40,8 @@ class Address { /// Returns a string representation of the address. std::string string() const; + protected: + Address() = default; }; inline bool operator==(const Address& lhs, const Address& rhs) { @@ -47,8 +49,3 @@ inline bool operator==(const Address& lhs, const Address& rhs) { } } // namespace TW::Ethereum - -/// Wrapper for C interface. -struct TWEthereumAddress { - TW::Ethereum::Address impl; -}; diff --git a/src/Ethereum/AddressChecksum.cpp b/src/Ethereum/AddressChecksum.cpp index 65fa5ba458f..da69f905a7e 100644 --- a/src/Ethereum/AddressChecksum.cpp +++ b/src/Ethereum/AddressChecksum.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -6,19 +6,17 @@ #include "AddressChecksum.h" -#include "../Hash.h" #include "../HexCoding.h" #include -using namespace TW; -using namespace TW::Ethereum; +namespace TW::Ethereum { -std::string Ethereum::checksumed(const Address& address, enum ChecksumType type) { +std::string checksumed(const Address& address) { const auto addressString = hex(address.bytes); const auto hash = hex(Hash::keccak256(addressString)); std::string string = "0x"; - for (auto i = 0; i < std::min(addressString.size(), hash.size()); i += 1) { + for (auto i = 0ul; i < std::min(addressString.size(), hash.size()); i += 1) { const auto a = addressString[i]; const auto h = hash[i]; if (a >= '0' && a <= '9') { @@ -32,3 +30,5 @@ std::string Ethereum::checksumed(const Address& address, enum ChecksumType type) return string; } + +} // namespace TW::Ethereum diff --git a/src/Ethereum/AddressChecksum.h b/src/Ethereum/AddressChecksum.h index 2aa11e989d4..922110c6869 100644 --- a/src/Ethereum/AddressChecksum.h +++ b/src/Ethereum/AddressChecksum.h @@ -11,12 +11,6 @@ namespace TW::Ethereum { -/// Checksum types for Ethereum-based blockchains. -enum ChecksumType { - eip55 = 0, - wanchain = 1, -}; - -std::string checksumed(const Address& address, enum ChecksumType type); +std::string checksumed(const Address& address); } // namespace TW::Ethereum diff --git a/src/Ethereum/ContractCall.cpp b/src/Ethereum/ContractCall.cpp index 4409577e914..0ae3a65db23 100644 --- a/src/Ethereum/ContractCall.cpp +++ b/src/Ethereum/ContractCall.cpp @@ -16,10 +16,9 @@ using json = nlohmann::json; namespace TW::Ethereum::ABI { static void fillArray(ParamSet& paramSet, const string& type) { - auto param = make_shared(); auto baseType = string(type.begin(), type.end() - 2); auto value = ParamFactory::make(baseType); - param->addParam(value); + auto param = make_shared(value); paramSet.addParam(param); } @@ -40,7 +39,7 @@ static vector getArrayValue(ParamSet& paramSet, const string& type, int static json buildInputs(ParamSet& paramSet, const json& registry); // forward -static json getTupleValue(ParamSet& paramSet, const string& type, int idx, const json& typeInfo) { +static json getTupleValue(ParamSet& paramSet, [[maybe_unused]] const string& type, int idx, const json& typeInfo) { shared_ptr param; paramSet.getParam(idx, param); auto paramTuple = dynamic_pointer_cast(param); @@ -58,7 +57,7 @@ static string getValue(ParamSet& paramSet, const string& type, int idx) { static json buildInputs(ParamSet& paramSet, const json& registry) { auto inputs = json::array(); - for (int i = 0; i < registry.size(); i++) { + for (auto i = 0ul; i < registry.size(); i++) { auto info = registry[i]; auto type = info["type"]; auto input = json{ @@ -66,13 +65,13 @@ static json buildInputs(ParamSet& paramSet, const json& registry) { {"type", type} }; if (boost::algorithm::ends_with(type.get(), "[]")) { - input["value"] = json(getArrayValue(paramSet, type, i)); + input["value"] = json(getArrayValue(paramSet, type, static_cast(i))); } else if (type == "tuple") { - input["components"] = getTupleValue(paramSet, type, i, info); + input["components"] = getTupleValue(paramSet, type, static_cast(i), info); } else if (type == "bool") { - input["value"] = getValue(paramSet, type, i) == "true" ? json(true) : json(false); + input["value"] = getValue(paramSet, type, static_cast(i)) == "true" ? json(true) : json(false); } else { - input["value"] = getValue(paramSet, type, i); + input["value"] = getValue(paramSet, type, static_cast(i)); } inputs.push_back(input); } diff --git a/src/Ethereum/Entry.cpp b/src/Ethereum/Entry.cpp index 35297782d23..f5a7c7ab87e 100644 --- a/src/Ethereum/Entry.cpp +++ b/src/Ethereum/Entry.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,26 +9,86 @@ #include "Address.h" #include "Signer.h" -using namespace TW::Ethereum; +#include "proto/TransactionCompiler.pb.h" + +namespace TW::Ethereum { + using namespace std; -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address); } -string Entry::normalizeAddress(TWCoinType coin, const string& address) const { +string Entry::normalizeAddress([[maybe_unused]] TWCoinType coin, const string& address) const { // normalized with EIP55 checksum return Address(address).string(); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + const auto addr = Address(address); + return {addr.bytes.begin(), addr.bytes.end()}; +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { +string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { return Signer::signJSON(json, key); } + +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + const auto transaction = Signer::build(input); + const auto chainId = load(data(input.chain_id())); // retrieve chainId from input + auto preHash = transaction->preHash(chainId); + auto preImage = transaction->serialize(chainId); + output.set_data_hash(preHash.data(), preHash.size()); + output.set_data(preImage.data(), preImage.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, [[maybe_unused]] const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() != 1) { + output.set_error(Common::Proto::Error_signatures_count); + output.set_error_message(Common::Proto::SigningError_Name(Common::Proto::Error_signatures_count)); + return; + } + output = Signer::compile(input, signatures[0]); + }); +} + +Data Entry::buildTransactionInput([[maybe_unused]] TWCoinType coinType, [[maybe_unused]] const std::string& from, const std::string& to, const uint256_t& amount, [[maybe_unused]] const std::string& asset, [[maybe_unused]] const std::string& memo, const std::string& chainId) const { + Proto::SigningInput input; + + auto chainIdData = store(uint256_t(1)); + if (chainId.length() > 0) { + // parse amount + uint256_t chainIdUint256{chainId}; + chainIdData = store(chainIdUint256); + } + input.set_chain_id(chainIdData.data(), chainIdData.size()); + + if (!Address::isValid(to)) { + throw std::invalid_argument("Invalid to address"); + } + input.set_to_address(to); + + auto& transfer = *input.mutable_transaction()->mutable_transfer(); + const auto amountData = store(amount); + transfer.set_amount(amountData.data(), amountData.size()); + + // not set: nonce, gasPrice, gasLimit, tx_mode (need to be set afterwards) + + const auto txInputData = data(input.SerializeAsString()); + return txInputData; +} + +} // namespace TW::Ethereum diff --git a/src/Ethereum/Entry.h b/src/Ethereum/Entry.h index 9fe4bcc6c90..23a31b2c8d1 100644 --- a/src/Ethereum/Entry.h +++ b/src/Ethereum/Entry.h @@ -12,37 +12,19 @@ namespace TW::Ethereum { /// Entry point for Ethereum and Ethereum-fork coins. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry : public CoinEntry { public: - virtual const std::vector coinTypes() const { - return { - TWCoinTypeCallisto, - TWCoinTypeEthereum, - TWCoinTypeEthereumClassic, - TWCoinTypeGoChain, - TWCoinTypePOANetwork, - TWCoinTypeThunderToken, - TWCoinTypeTomoChain, - TWCoinTypeSmartChainLegacy, - TWCoinTypeSmartChain, - TWCoinTypePolygon, - TWCoinTypeWanchain, - TWCoinTypeOptimism, - TWCoinTypeArbitrum, - TWCoinTypeECOChain, - TWCoinTypeAvalancheCChain, - TWCoinTypeXDai, - TWCoinTypeFantom, - TWCoinTypeCelo, - TWCoinTypeRonin, - }; - } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string normalizeAddress(TWCoinType coin, const std::string& address) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual bool supportsJSONSigning() const { return true; } - virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const final; + std::string normalizeAddress(TWCoinType coin, const std::string& address) const final; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const final; + Data addressToData(TWCoinType coin, const std::string& address) const final; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + bool supportsJSONSigning() const final { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const final; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const final; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const final; + Data buildTransactionInput(TWCoinType coinType, const std::string& from, const std::string& to, const uint256_t& amount, const std::string& asset, const std::string& memo, const std::string& chainId) const final; }; } // namespace TW::Ethereum diff --git a/src/Ethereum/Fee.cpp b/src/Ethereum/Fee.cpp deleted file mode 100644 index 0416faddb0e..00000000000 --- a/src/Ethereum/Fee.cpp +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright © 2017-2021 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. - -#ifdef _MSC_VER -#define _USE_MATH_DEFINES -#endif - -#include "Fee.h" - -#include "Data.h" -#include "HexCoding.h" -#include "uint256.h" -// always includes nvp.hpp before cpp_bin_float.hpp -#include -#include - -#include -#include -#include - -using json = nlohmann::json; -using float128_t = boost::multiprecision::cpp_bin_float_quad; -using namespace std; - -namespace TW::Ethereum::Fee { - -static const uint256_t defaultTip = 2000000000; - -auto samplingCurve(double sumWeight) -> double { - static const double sampleMin = 0.1; - static const double sampleMax = 0.3; - - if (sumWeight <= sampleMin) { - return 0.0; - } - - if (sumWeight >= sampleMax) { - return 1.0; - } - - return (1 - cos((sumWeight - sampleMin) * 2 * M_PI / (sampleMax - sampleMin))) / 2; -} - -auto suggestTip(const json& feeHistory) -> uint256_t { - - if (!feeHistory.contains("reward")) { - return defaultTip; - } - - vector rewards; - - for (auto& item : feeHistory["reward"]) { - const auto hex = parse_hex(item[0], true); - const auto reward = load(hex); - if (reward > 0) { - rewards.push_back(reward); - } - } - - if (rewards.empty()) { - return defaultTip; - } - - // return the median of the rewards - sort(rewards.begin(), rewards.end()); - return rewards[rewards.size() / 2]; -} - -auto suggestBaseFee(vector baseFees, vector order, double timeFactor) - -> uint256_t { - if (timeFactor < 1e-6) { - return baseFees.back(); - } - - double pendingWeight = (1 - exp(-1 / timeFactor)) / (1 - exp(-1 * double(baseFees.size()) / timeFactor)); - double sumWeight = 0.0; - double samplingCurveLast = 0.0; - float128_t result = 0; - for (size_t i = 0; i < order.size(); i++) { - sumWeight += pendingWeight * exp((double(order[i]) - baseFees.size() + 1) / timeFactor); - double samplingCurveValue = samplingCurve(sumWeight); - result += (samplingCurveValue - samplingCurveLast) * float128_t(baseFees[order[i]]); - if (samplingCurveValue >= 1) { - return uint256_t(boost::multiprecision::ceil(result)); - } - samplingCurveLast = samplingCurveValue; - } - return uint256_t(boost::multiprecision::ceil(result)); -} - -auto suggestFee(const json& feeHistory) -> json { - // tailored from: - // https://github.com/zsfelfoldi/ethereum-docs/blob/master/eip1559/feeHistory_example.js - vector baseFees; - vector order; - - const auto& array = feeHistory["baseFeePerGas"]; - for (size_t i = 0; i < array.size(); i++) { - const auto hex = parse_hex(array[i], true); - baseFees.push_back(load(hex)); - order.push_back(i); - } - - // The last (pending) block is also assumed to end up being full - baseFees.back() = baseFees.back() * 9 / 8; - - const auto& gasRatio = feeHistory["gasUsedRatio"]; - for (size_t i = gasRatio.size() - 1; i > 0; i--) { - if (i < gasRatio.size() && gasRatio[i] > 0.9) { - baseFees[i] = baseFees[i + 1]; - } - } - - std::sort(order.begin(), order.end(), [&baseFees](const size_t lhs, const size_t rhs) { - return baseFees[lhs] > baseFees[rhs]; - }); - - const auto baseFee = suggestBaseFee(baseFees, order, 15); - const auto tip = suggestTip(feeHistory); - - return json{ - {"baseFee", toString(baseFee)}, - {"maxPriorityFee", toString(tip)}, - }; -} - -} // namespace TW::Ethereum::Fee diff --git a/src/Ethereum/RLP.cpp b/src/Ethereum/RLP.cpp index 94a2a39420e..50817bbd467 100644 --- a/src/Ethereum/RLP.cpp +++ b/src/Ethereum/RLP.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -6,14 +6,11 @@ #include "RLP.h" -#include "../Data.h" -#include "../uint256.h" #include "../BinaryCoding.h" #include -using namespace TW; -using namespace TW::Ethereum; +namespace TW::Ethereum { Data RLP::encode(const uint256_t& value) noexcept { using boost::multiprecision::cpp_int; @@ -63,7 +60,7 @@ Data RLP::encodeHeader(uint64_t size, uint8_t smallTag, uint8_t largeTag) noexce Data RLP::putVarInt(uint64_t i) noexcept { Data bytes; // accumulate bytes here, in reverse order do { - // take LSB byte, append + // take LSB byte, append bytes.push_back(i & 0xff); i = i >> 8; } while (i); @@ -83,7 +80,7 @@ uint64_t RLP::parseVarInt(size_t size, const Data& data, size_t index) { throw std::invalid_argument("multi-byte length must have no leading zero"); } uint64_t val = 0; - for (auto i = 0; i < size; ++i) { + for (auto i = 0ul; i < size; ++i) { val = val << 8; val += data[index + i]; } @@ -93,7 +90,7 @@ uint64_t RLP::parseVarInt(size_t size, const Data& data, size_t index) { RLP::DecodedItem RLP::decodeList(const Data& input) { RLP::DecodedItem item; auto remainder = input; - while(true) { + while (true) { auto listItem = RLP::decode(remainder); item.decoded.push_back(listItem.decoded[0]); if (listItem.remainder.size() == 0) { @@ -114,7 +111,7 @@ RLP::DecodedItem RLP::decode(const Data& input) { auto prefix = input[0]; if (prefix <= 0x7f) { // 00--7f: a single byte whose value is in the [0x00, 0x7f] range, that byte is its own RLP encoding. - item.decoded.push_back(Data{input[0]}); + item.decoded.emplace_back(Data{input[0]}); item.remainder = subData(input, 1); return item; } @@ -135,18 +132,18 @@ RLP::DecodedItem RLP::decode(const Data& input) { throw std::invalid_argument("single byte below 128 must be encoded as itself"); } - if (inputLen < (1 + strLen)) { + if (inputLen < (1ul + strLen)) { throw std::invalid_argument(std::string("invalid short string, length ") + std::to_string(strLen)); } item.decoded.push_back(subData(input, 1, strLen)); item.remainder = subData(input, 1 + strLen); return item; - } + } if (prefix <= 0xbf) { // b8--bf: long string - auto lenOfStrLen = prefix - 0xb7; - auto strLen = parseVarInt(lenOfStrLen, input, 1); + auto lenOfStrLen = size_t(prefix - 0xb7); + auto strLen = static_cast(parseVarInt(lenOfStrLen, input, 1)); if (inputLen < lenOfStrLen || inputLen < (1 + lenOfStrLen + strLen)) { throw std::invalid_argument(std::string("Invalid rlp encoding length, length ") + std::to_string(strLen)); } @@ -154,10 +151,10 @@ RLP::DecodedItem RLP::decode(const Data& input) { item.decoded.push_back(data); item.remainder = subData(input, 1 + lenOfStrLen + strLen); return item; - } + } if (prefix <= 0xf7) { // c0--f7: a list between 0-55 bytes long - auto listLen = prefix - 0xc0; + auto listLen = size_t(prefix - 0xc0); if (inputLen < (1 + listLen)) { throw std::invalid_argument(std::string("Invalid rlp string length, length ") + std::to_string(listLen)); } @@ -174,10 +171,10 @@ RLP::DecodedItem RLP::decode(const Data& input) { } item.remainder = subData(input, 1 + listLen); return item; - } - // f8--ff - auto lenOfListLen = prefix - 0xf7; - auto listLen = parseVarInt(lenOfListLen, input, 1); + } + // f8--ff + auto lenOfListLen = size_t(prefix - 0xf7); + auto listLen = static_cast(parseVarInt(lenOfListLen, input, 1)); if (listLen < 56) { throw std::invalid_argument("length below 56 must be encoded in one byte"); } @@ -193,3 +190,5 @@ RLP::DecodedItem RLP::decode(const Data& input) { item.remainder = subData(input, 1 + lenOfListLen + listLen); return item; } + +} // namespace TW::Ethereum diff --git a/src/Ethereum/RLP.h b/src/Ethereum/RLP.h index 7bdc17c60ed..c65ecbbc6cb 100644 --- a/src/Ethereum/RLP.h +++ b/src/Ethereum/RLP.h @@ -7,7 +7,7 @@ #pragma once #include "Transaction.h" -#include "../Data.h" +#include "Data.h" #include "../uint256.h" #include diff --git a/src/Ethereum/Signer.cpp b/src/Ethereum/Signer.cpp index 6d8ce136a53..edfca7a0620 100644 --- a/src/Ethereum/Signer.cpp +++ b/src/Ethereum/Signer.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -6,10 +6,11 @@ #include "Signer.h" #include "HexCoding.h" +#include "Coin.h" +#include #include -using namespace TW; -using namespace TW::Ethereum; +namespace TW::Ethereum { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { try { @@ -24,11 +25,11 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto encoded = transaction->encoded(signature, chainID); output.set_encoded(encoded.data(), encoded.size()); - auto v = store(signature.v); + auto v = store(signature.v, 1); output.set_v(v.data(), v.size()); - auto r = store(signature.r); + auto r = store(signature.r, 32); output.set_r(r.data(), r.size()); - auto s = store(signature.s); + auto s = store(signature.s, 32); output.set_s(s.data(), s.size()); output.set_data(transaction->payload.data(), transaction->payload.size()); @@ -47,7 +48,41 @@ std::string Signer::signJSON(const std::string& json, const Data& key) { return hex(output.encoded()); } -Signature Signer::signatureDataToStruct(const Data& signature) noexcept { +Proto::SigningOutput Signer::compile(const Proto::SigningInput& input, const Data& signature) noexcept { + try { + uint256_t chainID = load(input.chain_id()); + auto transaction = Signer::build(input); + + // prepare Signature + const Signature sigStruct = signatureDataToStruct(signature, transaction->usesReplayProtection(), chainID); + + auto output = Proto::SigningOutput(); + + auto encoded = transaction->encoded(sigStruct, chainID); + output.set_encoded(encoded.data(), encoded.size()); + + auto v = store(sigStruct.v, 1); + output.set_v(v.data(), v.size()); + auto r = store(sigStruct.r, 32); + output.set_r(r.data(), r.size()); + auto s = store(sigStruct.s, 32); + output.set_s(s.data(), s.size()); + + output.set_data(transaction->payload.data(), transaction->payload.size()); + return output; + } catch (std::exception&) { + return Proto::SigningOutput(); + } +} + +Signature Signer::signatureDataToStruct(const Data& signature, bool includeEip155, const uint256_t& chainID) noexcept { + if (!includeEip155) { + return signatureDataToStructSimple(signature); + } + return signatureDataToStructWithEip155(chainID, signature); +} + +Signature Signer::signatureDataToStructSimple(const Data& signature) noexcept { boost::multiprecision::uint256_t r, s, v; import_bits(r, signature.begin(), signature.begin() + 32); import_bits(s, signature.begin() + 32, signature.begin() + 64); @@ -56,7 +91,7 @@ Signature Signer::signatureDataToStruct(const Data& signature) noexcept { } Signature Signer::signatureDataToStructWithEip155(const uint256_t& chainID, const Data& signature) noexcept { - Signature rsv = signatureDataToStruct(signature); + Signature rsv = signatureDataToStructSimple(signature); // Embed chainID in V param, for replay protection, legacy (EIP155) if (chainID != 0) { rsv.v += 35 + chainID + chainID; @@ -68,10 +103,7 @@ Signature Signer::signatureDataToStructWithEip155(const uint256_t& chainID, cons Signature Signer::sign(const PrivateKey& privateKey, const Data& hash, bool includeEip155, const uint256_t& chainID) noexcept { auto signature = privateKey.sign(hash, TWCurveSECP256k1); - if (!includeEip155) { - return signatureDataToStruct(signature); - } - return signatureDataToStructWithEip155(chainID, signature); + return signatureDataToStruct(signature, includeEip155, chainID); } // May throw @@ -79,11 +111,11 @@ Data addressStringToData(const std::string& asString) { if (asString.empty()) { return {}; } - auto address = Address(asString); - Data asData; - asData.resize(20); - std::copy(address.bytes.begin(), address.bytes.end(), asData.data()); - return asData; + // only ronin address prefix is not 0x + if (asString.compare(0, 2, "0x") != 0) { + return TW::addressToData(TWCoinTypeRonin, asString); + } + return TW::addressToData(TWCoinTypeEthereum, asString); } std::shared_ptr Signer::build(const Proto::SigningInput& input) { @@ -94,140 +126,134 @@ std::shared_ptr Signer::build(const Proto::SigningInput& input) uint256_t maxInclusionFeePerGas = load(input.max_inclusion_fee_per_gas()); uint256_t maxFeePerGas = load(input.max_fee_per_gas()); switch (input.transaction().transaction_oneof_case()) { - case Proto::Transaction::kTransfer: - { - switch (input.tx_mode()) { - case Proto::TransactionMode::Legacy: - default: - return TransactionNonTyped::buildNativeTransfer( - nonce, gasPrice, gasLimit, - /* to: */ toAddress, - /* amount: */ load(input.transaction().transfer().amount()), - /* optional data: */ Data(input.transaction().transfer().data().begin(), input.transaction().transfer().data().end())); - - case Proto::TransactionMode::Enveloped: // Eip1559 - return TransactionEip1559::buildNativeTransfer( - nonce, maxInclusionFeePerGas, maxFeePerGas, gasLimit, - /* to: */ toAddress, - /* amount: */ load(input.transaction().transfer().amount()), - /* optional data: */ Data(input.transaction().transfer().data().begin(), input.transaction().transfer().data().end())); - } - } - - case Proto::Transaction::kErc20Transfer: - { - Data tokenToAddress = addressStringToData(input.transaction().erc20_transfer().to()); - switch (input.tx_mode()) { - case Proto::TransactionMode::Legacy: - default: - return TransactionNonTyped::buildERC20Transfer( - nonce, gasPrice, gasLimit, - /* tokenContract: */ toAddress, - /* toAddress */ tokenToAddress, - /* amount: */ load(input.transaction().erc20_transfer().amount())); - - case Proto::TransactionMode::Enveloped: // Eip1559 - return TransactionEip1559::buildERC20Transfer( - nonce, maxInclusionFeePerGas, maxFeePerGas, gasLimit, - /* tokenContract: */ toAddress, - /* toAddress */ tokenToAddress, - /* amount: */ load(input.transaction().erc20_transfer().amount())); - } - } - - case Proto::Transaction::kErc20Approve: - { - Data spenderAddress = addressStringToData(input.transaction().erc20_approve().spender()); - switch (input.tx_mode()) { - case Proto::TransactionMode::Legacy: - default: - return TransactionNonTyped::buildERC20Approve( - nonce, gasPrice, gasLimit, - /* tokenContract: */ toAddress, - /* toAddress */ spenderAddress, - /* amount: */ load(input.transaction().erc20_approve().amount())); - - case Proto::TransactionMode::Enveloped: // Eip1559 - return TransactionEip1559::buildERC20Approve( - nonce, maxInclusionFeePerGas, maxFeePerGas, gasLimit, - /* tokenContract: */ toAddress, - /* toAddress */ spenderAddress, - /* amount: */ load(input.transaction().erc20_approve().amount())); - } - } - - case Proto::Transaction::kErc721Transfer: - { - Data tokenToAddress = addressStringToData(input.transaction().erc721_transfer().to()); - Data tokenFromAddress = addressStringToData(input.transaction().erc721_transfer().from()); - switch (input.tx_mode()) { - case Proto::TransactionMode::Legacy: - default: - return TransactionNonTyped::buildERC721Transfer( - nonce, gasPrice, gasLimit, - /* tokenContract: */ toAddress, - /* fromAddress: */ tokenFromAddress, - /* toAddress */ tokenToAddress, - /* tokenId: */ load(input.transaction().erc721_transfer().token_id())); - - case Proto::TransactionMode::Enveloped: // Eip1559 - return TransactionEip1559::buildERC721Transfer( - nonce, maxInclusionFeePerGas, maxFeePerGas, gasLimit, - /* tokenContract: */ toAddress, - /* fromAddress: */ tokenFromAddress, - /* toAddress */ tokenToAddress, - /* tokenId: */ load(input.transaction().erc721_transfer().token_id())); - } - } - - case Proto::Transaction::kErc1155Transfer: - { - Data tokenToAddress = addressStringToData(input.transaction().erc1155_transfer().to()); - Data tokenFromAddress = addressStringToData(input.transaction().erc1155_transfer().from()); - switch (input.tx_mode()) { - case Proto::TransactionMode::Legacy: - default: - return TransactionNonTyped::buildERC1155Transfer( - nonce, gasPrice, gasLimit, - /* tokenContract: */ toAddress, - /* fromAddress: */ tokenFromAddress, - /* toAddress */ tokenToAddress, - /* tokenId: */ load(input.transaction().erc1155_transfer().token_id()), - /* value */ load(input.transaction().erc1155_transfer().value()), - /* data */ Data(input.transaction().erc1155_transfer().data().begin(), input.transaction().erc1155_transfer().data().end())); - - case Proto::TransactionMode::Enveloped: // Eip1559 - return TransactionEip1559::buildERC1155Transfer( - nonce, maxInclusionFeePerGas, maxFeePerGas, gasLimit, - /* tokenContract: */ toAddress, - /* fromAddress: */ tokenFromAddress, - /* toAddress */ tokenToAddress, - /* tokenId: */ load(input.transaction().erc1155_transfer().token_id()), - /* value */ load(input.transaction().erc1155_transfer().value()), - /* data */ Data(input.transaction().erc1155_transfer().data().begin(), input.transaction().erc1155_transfer().data().end())); - } - } - - case Proto::Transaction::kContractGeneric: + case Proto::Transaction::kTransfer: { + switch (input.tx_mode()) { + case Proto::TransactionMode::Legacy: + default: + return TransactionNonTyped::buildNativeTransfer( + nonce, gasPrice, gasLimit, + /* to: */ toAddress, + /* amount: */ load(input.transaction().transfer().amount()), + /* optional data: */ Data(input.transaction().transfer().data().begin(), input.transaction().transfer().data().end())); + + case Proto::TransactionMode::Enveloped: // Eip1559 + return TransactionEip1559::buildNativeTransfer( + nonce, maxInclusionFeePerGas, maxFeePerGas, gasLimit, + /* to: */ toAddress, + /* amount: */ load(input.transaction().transfer().amount()), + /* optional data: */ Data(input.transaction().transfer().data().begin(), input.transaction().transfer().data().end())); + } + } + + case Proto::Transaction::kErc20Transfer: { + Data tokenToAddress = addressStringToData(input.transaction().erc20_transfer().to()); + switch (input.tx_mode()) { + case Proto::TransactionMode::Legacy: + default: + return TransactionNonTyped::buildERC20Transfer( + nonce, gasPrice, gasLimit, + /* tokenContract: */ toAddress, + /* toAddress */ tokenToAddress, + /* amount: */ load(input.transaction().erc20_transfer().amount())); + + case Proto::TransactionMode::Enveloped: // Eip1559 + return TransactionEip1559::buildERC20Transfer( + nonce, maxInclusionFeePerGas, maxFeePerGas, gasLimit, + /* tokenContract: */ toAddress, + /* toAddress */ tokenToAddress, + /* amount: */ load(input.transaction().erc20_transfer().amount())); + } + } + + case Proto::Transaction::kErc20Approve: { + Data spenderAddress = addressStringToData(input.transaction().erc20_approve().spender()); + switch (input.tx_mode()) { + case Proto::TransactionMode::Legacy: + default: + return TransactionNonTyped::buildERC20Approve( + nonce, gasPrice, gasLimit, + /* tokenContract: */ toAddress, + /* toAddress */ spenderAddress, + /* amount: */ load(input.transaction().erc20_approve().amount())); + + case Proto::TransactionMode::Enveloped: // Eip1559 + return TransactionEip1559::buildERC20Approve( + nonce, maxInclusionFeePerGas, maxFeePerGas, gasLimit, + /* tokenContract: */ toAddress, + /* toAddress */ spenderAddress, + /* amount: */ load(input.transaction().erc20_approve().amount())); + } + } + + case Proto::Transaction::kErc721Transfer: { + Data tokenToAddress = addressStringToData(input.transaction().erc721_transfer().to()); + Data tokenFromAddress = addressStringToData(input.transaction().erc721_transfer().from()); + switch (input.tx_mode()) { + case Proto::TransactionMode::Legacy: default: - { - switch (input.tx_mode()) { - case Proto::TransactionMode::Legacy: - default: - return TransactionNonTyped::buildNativeTransfer( - nonce, gasPrice, gasLimit, - /* to: */ toAddress, - /* amount: */ load(input.transaction().contract_generic().amount()), - /* transaction: */ Data(input.transaction().contract_generic().data().begin(), input.transaction().contract_generic().data().end())); - - case Proto::TransactionMode::Enveloped: // Eip1559 - return TransactionEip1559::buildNativeTransfer( - nonce, maxInclusionFeePerGas, maxFeePerGas, gasLimit, - /* to: */ toAddress, - /* amount: */ load(input.transaction().contract_generic().amount()), - /* transaction: */ Data(input.transaction().contract_generic().data().begin(), input.transaction().contract_generic().data().end())); - } - } + return TransactionNonTyped::buildERC721Transfer( + nonce, gasPrice, gasLimit, + /* tokenContract: */ toAddress, + /* fromAddress: */ tokenFromAddress, + /* toAddress */ tokenToAddress, + /* tokenId: */ load(input.transaction().erc721_transfer().token_id())); + + case Proto::TransactionMode::Enveloped: // Eip1559 + return TransactionEip1559::buildERC721Transfer( + nonce, maxInclusionFeePerGas, maxFeePerGas, gasLimit, + /* tokenContract: */ toAddress, + /* fromAddress: */ tokenFromAddress, + /* toAddress */ tokenToAddress, + /* tokenId: */ load(input.transaction().erc721_transfer().token_id())); + } + } + + case Proto::Transaction::kErc1155Transfer: { + Data tokenToAddress = addressStringToData(input.transaction().erc1155_transfer().to()); + Data tokenFromAddress = addressStringToData(input.transaction().erc1155_transfer().from()); + switch (input.tx_mode()) { + case Proto::TransactionMode::Legacy: + default: + return TransactionNonTyped::buildERC1155Transfer( + nonce, gasPrice, gasLimit, + /* tokenContract: */ toAddress, + /* fromAddress: */ tokenFromAddress, + /* toAddress */ tokenToAddress, + /* tokenId: */ load(input.transaction().erc1155_transfer().token_id()), + /* value */ load(input.transaction().erc1155_transfer().value()), + /* data */ Data(input.transaction().erc1155_transfer().data().begin(), input.transaction().erc1155_transfer().data().end())); + + case Proto::TransactionMode::Enveloped: // Eip1559 + return TransactionEip1559::buildERC1155Transfer( + nonce, maxInclusionFeePerGas, maxFeePerGas, gasLimit, + /* tokenContract: */ toAddress, + /* fromAddress: */ tokenFromAddress, + /* toAddress */ tokenToAddress, + /* tokenId: */ load(input.transaction().erc1155_transfer().token_id()), + /* value */ load(input.transaction().erc1155_transfer().value()), + /* data */ Data(input.transaction().erc1155_transfer().data().begin(), input.transaction().erc1155_transfer().data().end())); + } + } + + case Proto::Transaction::kContractGeneric: + default: { + switch (input.tx_mode()) { + case Proto::TransactionMode::Legacy: + default: + return TransactionNonTyped::buildNativeTransfer( + nonce, gasPrice, gasLimit, + /* to: */ toAddress, + /* amount: */ load(input.transaction().contract_generic().amount()), + /* transaction: */ Data(input.transaction().contract_generic().data().begin(), input.transaction().contract_generic().data().end())); + + case Proto::TransactionMode::Enveloped: // Eip1559 + return TransactionEip1559::buildNativeTransfer( + nonce, maxInclusionFeePerGas, maxFeePerGas, gasLimit, + /* to: */ toAddress, + /* amount: */ load(input.transaction().contract_generic().amount()), + /* transaction: */ Data(input.transaction().contract_generic().data().begin(), input.transaction().contract_generic().data().end())); + } + } } } @@ -235,3 +261,5 @@ Signature Signer::sign(const PrivateKey& privateKey, const uint256_t& chainID, s auto preHash = transaction->preHash(chainID); return Signer::sign(privateKey, preHash, transaction->usesReplayProtection(), chainID); } + +} // namespace TW::Ethereum diff --git a/src/Ethereum/Signer.h b/src/Ethereum/Signer.h index 2111b5428b3..3fb42172c1d 100644 --- a/src/Ethereum/Signer.h +++ b/src/Ethereum/Signer.h @@ -8,7 +8,7 @@ #include "RLP.h" #include "Transaction.h" -#include "../Data.h" +#include "Data.h" #include "../Hash.h" #include "../PrivateKey.h" #include "../proto/Ethereum.pb.h" @@ -34,6 +34,8 @@ class Signer { /// Signs the given transaction. static Signature sign(const PrivateKey& privateKey, const uint256_t& chainID, std::shared_ptr transaction) noexcept; + /// Compiles a Proto::SigningInput transaction, with external signature + static Proto::SigningOutput compile(const Proto::SigningInput& input, const Data& signature) noexcept; public: /// build Transaction from signing input @@ -41,21 +43,20 @@ class Signer { /// Signs a hash with the given private key for the given chain identifier. /// - /// @returns the r, s, and v values of the transaction signature + /// \returns the r, s, and v values of the transaction signature static Signature sign(const PrivateKey& privateKey, const Data& hash, bool includeEip155, const uint256_t& chainID) noexcept; /// Break up the signature into the R, S, and V values. - /// @returns the r, s, and v values of the transaction signature - static Signature signatureDataToStruct(const Data& signature) noexcept; + /// \returns the r, s, and v values of the transaction signature + static Signature signatureDataToStruct(const Data& signature, bool includeEip155, const uint256_t& chainID) noexcept; + + /// Break up the signature into the R, S, and V values, with no replay protection. + /// \returns the r, s, and v values of the transaction signature + static Signature signatureDataToStructSimple(const Data& signature) noexcept; /// Break up the signature into the R, S, and V values, and include chainID in V for replay protection (Eip155) - /// @returns the r, s, and v values of the transaction signature + /// \returns the r, s, and v values of the transaction signature static Signature signatureDataToStructWithEip155(const uint256_t& chainID, const Data& signature) noexcept; }; } // namespace TW::Ethereum - -/// Wrapper for C interface. -struct TWEthereumSigner { - TW::Ethereum::Signer impl; -}; diff --git a/src/Ethereum/Transaction.cpp b/src/Ethereum/Transaction.cpp index aed18318f36..d033dbb6f80 100644 --- a/src/Ethereum/Transaction.cpp +++ b/src/Ethereum/Transaction.cpp @@ -6,49 +6,55 @@ #include "Transaction.h" #include "ABI/Function.h" -#include "ABI/ParamBase.h" #include "ABI/ParamAddress.h" -#include "RLP.h" +#include "ABI/ParamBase.h" #include "HexCoding.h" +#include "RLP.h" -using namespace TW::Ethereum::ABI; -using namespace TW::Ethereum; -using namespace TW; - +namespace TW::Ethereum { static const Data EmptyListEncoded = parse_hex("c0"); -std::shared_ptr TransactionNonTyped::buildNativeTransfer(const uint256_t& nonce, - const uint256_t& gasPrice, const uint256_t& gasLimit, - const Data& toAddress, const uint256_t& amount, const Data& data) { +std::shared_ptr +TransactionNonTyped::buildNativeTransfer(const uint256_t& nonce, + const uint256_t& gasPrice, const uint256_t& gasLimit, + const Data& toAddress, const uint256_t& amount, const Data& data) { return std::make_shared(nonce, gasPrice, gasLimit, toAddress, amount, data); } -std::shared_ptr TransactionNonTyped::buildERC20Transfer(const uint256_t& nonce, - const uint256_t& gasPrice, const uint256_t& gasLimit, - const Data& tokenContract, const Data& toAddress, const uint256_t& amount) { +std::shared_ptr +TransactionNonTyped::buildERC20Transfer(const uint256_t& nonce, + const uint256_t& gasPrice, const uint256_t& gasLimit, + const Data& tokenContract, const Data& toAddress, const uint256_t& amount) { return std::make_shared(nonce, gasPrice, gasLimit, tokenContract, 0, buildERC20TransferCall(toAddress, amount)); } -std::shared_ptr TransactionNonTyped::buildERC20Approve(const uint256_t& nonce, - const uint256_t& gasPrice, const uint256_t& gasLimit, - const Data& tokenContract, const Data& spenderAddress, const uint256_t& amount) { +std::shared_ptr +TransactionNonTyped::buildERC20Approve(const uint256_t& nonce, + const uint256_t& gasPrice, const uint256_t& gasLimit, + const Data& tokenContract, const Data& spenderAddress, const uint256_t& amount) { return std::make_shared(nonce, gasPrice, gasLimit, tokenContract, 0, buildERC20ApproveCall(spenderAddress, amount)); } -std::shared_ptr TransactionNonTyped::buildERC721Transfer(const uint256_t& nonce, - const uint256_t& gasPrice, const uint256_t& gasLimit, - const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId) { +std::shared_ptr +TransactionNonTyped::buildERC721Transfer(const uint256_t& nonce, + const uint256_t& gasPrice, const uint256_t& gasLimit, + const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId) { return std::make_shared(nonce, gasPrice, gasLimit, tokenContract, 0, buildERC721TransferFromCall(from, to, tokenId)); } -std::shared_ptr TransactionNonTyped::buildERC1155Transfer(const uint256_t& nonce, - const uint256_t& gasPrice, const uint256_t& gasLimit, - const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId, const uint256_t& value, const Data& data) { +std::shared_ptr +TransactionNonTyped::buildERC1155Transfer(const uint256_t& nonce, + const uint256_t& gasPrice, const uint256_t& gasLimit, + const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId, const uint256_t& value, const Data& data) { return std::make_shared(nonce, gasPrice, gasLimit, tokenContract, 0, buildERC1155TransferFromCall(from, to, tokenId, value, data)); } Data TransactionNonTyped::preHash(const uint256_t chainID) const { + return Hash::keccak256(serialize(chainID)); +} + +Data TransactionNonTyped::serialize(const uint256_t chainID) const { Data encoded; append(encoded, RLP::encode(nonce)); append(encoded, RLP::encode(gasPrice)); @@ -59,10 +65,10 @@ Data TransactionNonTyped::preHash(const uint256_t chainID) const { append(encoded, RLP::encode(chainID)); append(encoded, RLP::encode(0)); append(encoded, RLP::encode(0)); - return Hash::keccak256(RLP::encodeList(encoded)); + return RLP::encodeList(encoded); } -Data TransactionNonTyped::encoded(const Signature& signature, const uint256_t chainID) const { +Data TransactionNonTyped::encoded(const Signature& signature, [[maybe_unused]] const uint256_t chainID) const { Data encoded; append(encoded, RLP::encode(nonce)); append(encoded, RLP::encode(gasPrice)); @@ -77,50 +83,62 @@ Data TransactionNonTyped::encoded(const Signature& signature, const uint256_t ch } Data TransactionNonTyped::buildERC20TransferCall(const Data& to, const uint256_t& amount) { - auto func = Function("transfer", std::vector>{ - std::make_shared(to), - std::make_shared(amount) + // clang-format off + auto func = ABI::Function("transfer", std::vector>{ + std::make_shared(to), + std::make_shared(amount) }); + // clang-format on Data payload; func.encode(payload); return payload; } Data TransactionNonTyped::buildERC20ApproveCall(const Data& spender, const uint256_t& amount) { - auto func = Function("approve", std::vector>{ - std::make_shared(spender), - std::make_shared(amount) + // clang-format off + auto func = ABI::Function("approve", std::vector>{ + std::make_shared(spender), + std::make_shared(amount) }); + // clang-format on Data payload; func.encode(payload); return payload; } Data TransactionNonTyped::buildERC721TransferFromCall(const Data& from, const Data& to, const uint256_t& tokenId) { - auto func = Function("transferFrom", std::vector>{ - std::make_shared(from), - std::make_shared(to), - std::make_shared(tokenId) + // clang-format off + auto func = ABI::Function("transferFrom", std::vector>{ + std::make_shared(from), + std::make_shared(to), + std::make_shared(tokenId) }); + // clang-format on Data payload; func.encode(payload); return payload; } Data TransactionNonTyped::buildERC1155TransferFromCall(const Data& from, const Data& to, const uint256_t& tokenId, const uint256_t& value, const Data& data) { - auto func = Function("safeTransferFrom", std::vector>{ - std::make_shared(from), - std::make_shared(to), - std::make_shared(tokenId), - std::make_shared(value), - std::make_shared(data) + // clang-format off + auto func = ABI::Function("safeTransferFrom", std::vector>{ + std::make_shared(from), + std::make_shared(to), + std::make_shared(tokenId), + std::make_shared(value), + std::make_shared(data) }); + // clang-format on Data payload; func.encode(payload); return payload; } Data TransactionEip1559::preHash(const uint256_t chainID) const { + return Hash::keccak256(serialize(chainID)); +} + +Data TransactionEip1559::serialize(const uint256_t chainID) const { Data encoded; append(encoded, RLP::encode(chainID)); append(encoded, RLP::encode(nonce)); @@ -135,7 +153,7 @@ Data TransactionEip1559::preHash(const uint256_t chainID) const { Data envelope; append(envelope, static_cast(type)); append(envelope, RLP::encodeList(encoded)); - return Hash::keccak256(envelope); + return envelope; } Data TransactionEip1559::encoded(const Signature& signature, const uint256_t chainID) const { @@ -159,32 +177,39 @@ Data TransactionEip1559::encoded(const Signature& signature, const uint256_t cha return envelope; } -std::shared_ptr TransactionEip1559::buildNativeTransfer(const uint256_t& nonce, - const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, - const Data& toAddress, const uint256_t& amount, const Data& data) { +std::shared_ptr +TransactionEip1559::buildNativeTransfer(const uint256_t& nonce, + const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, + const Data& toAddress, const uint256_t& amount, const Data& data) { return std::make_shared(nonce, maxInclusionFeePerGas, maxFeePerGas, gasPrice, toAddress, amount, data); } -std::shared_ptr TransactionEip1559::buildERC20Transfer(const uint256_t& nonce, - const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, - const Data& tokenContract, const Data& toAddress, const uint256_t& amount) { +std::shared_ptr +TransactionEip1559::buildERC20Transfer(const uint256_t& nonce, + const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, + const Data& tokenContract, const Data& toAddress, const uint256_t& amount) { return std::make_shared(nonce, maxInclusionFeePerGas, maxFeePerGas, gasPrice, tokenContract, 0, TransactionNonTyped::buildERC20TransferCall(toAddress, amount)); } -std::shared_ptr TransactionEip1559::buildERC20Approve(const uint256_t& nonce, - const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, - const Data& tokenContract, const Data& spenderAddress, const uint256_t& amount) { +std::shared_ptr +TransactionEip1559::buildERC20Approve(const uint256_t& nonce, + const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, + const Data& tokenContract, const Data& spenderAddress, const uint256_t& amount) { return std::make_shared(nonce, maxInclusionFeePerGas, maxFeePerGas, gasPrice, tokenContract, 0, TransactionNonTyped::buildERC20ApproveCall(spenderAddress, amount)); } -std::shared_ptr TransactionEip1559::buildERC721Transfer(const uint256_t& nonce, - const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, - const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId) { +std::shared_ptr +TransactionEip1559::buildERC721Transfer(const uint256_t& nonce, + const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, + const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId) { return std::make_shared(nonce, maxInclusionFeePerGas, maxFeePerGas, gasPrice, tokenContract, 0, TransactionNonTyped::buildERC721TransferFromCall(from, to, tokenId)); } -std::shared_ptr TransactionEip1559::buildERC1155Transfer(const uint256_t& nonce, - const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, - const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId, const uint256_t& value, const Data& data) { +std::shared_ptr +TransactionEip1559::buildERC1155Transfer(const uint256_t& nonce, + const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, + const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId, const uint256_t& value, const Data& data) { return std::make_shared(nonce, maxInclusionFeePerGas, maxFeePerGas, gasPrice, tokenContract, 0, TransactionNonTyped::buildERC1155TransferFromCall(from, to, tokenId, value, data)); } + +} // namespace TW::Ethereum diff --git a/src/Ethereum/Transaction.h b/src/Ethereum/Transaction.h index 64bbe8fe729..d43fd77aa10 100644 --- a/src/Ethereum/Transaction.h +++ b/src/Ethereum/Transaction.h @@ -37,6 +37,8 @@ class TransactionBase { virtual ~TransactionBase() {} // pre-sign hash of the tx, for signing virtual Data preHash(const uint256_t chainID) const = 0; + // pre-sign image of tx + virtual Data serialize(const uint256_t chainID) const = 0; // encoded tx (signed) virtual Data encoded(const Signature& signature, const uint256_t chainID) const = 0; // Signals wether this tx type uses Eip155-style replay protection in the signature @@ -88,6 +90,7 @@ class TransactionNonTyped: public TransactionBase { static Data buildERC1155TransferFromCall(const Data& from, const Data& to, const uint256_t& tokenId, const uint256_t& value, const Data& data); virtual Data preHash(const uint256_t chainID) const; + virtual Data serialize(const uint256_t chainID) const; virtual Data encoded(const Signature& signature, const uint256_t chainID) const; virtual bool usesReplayProtection() const { return true; } @@ -151,6 +154,7 @@ class TransactionEip1559: public TransactionTyped { const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId, const uint256_t& value, const Data& data); virtual Data preHash(const uint256_t chainID) const; + virtual Data serialize(const uint256_t chainID) const; virtual Data encoded(const Signature& signature, const uint256_t chainID) const; public: diff --git a/src/Everscale/Address.cpp b/src/Everscale/Address.cpp new file mode 100644 index 00000000000..177313473cd --- /dev/null +++ b/src/Everscale/Address.cpp @@ -0,0 +1,77 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include + +#include "Address.h" +#include "HexCoding.h" +#include "Wallet.h" +#include "WorkchainType.h" + +using namespace TW; + +namespace TW::Everscale { + +using MaybeWorkchain = std::optional>; + +MaybeWorkchain parseWorkchainId(const std::string& string) { + if (auto pos = string.find(':'); pos != std::string::npos) { + try { + auto workchainId = static_cast(std::stoi(string.substr(0, pos))); + return std::make_pair(workchainId, pos + 1); + } catch (...) { + // Do nothing and return empty value later + } + } + + return {}; +} + +bool Address::isValid(const std::string& string) noexcept { + auto parsed = parseWorkchainId(string); + if (!parsed.has_value()) { + return false; + } + + auto [workchainId, pos] = *parsed; + + if (workchainId != WorkchainType::Basechain && workchainId != WorkchainType::Masterchain) { + return false; + } + + if (string.size() != pos + hexAddrLen) { + return false; + } + + std::string addr = string.substr(pos); + return parse_hex(addr).size() == size; +} + +Address::Address(const std::string& string) { + if (!Address::isValid(string)) { + throw std::invalid_argument("Invalid address string!"); + } + + auto parsed = parseWorkchainId(string); + auto [parsedWorkchainId, pos] = *parsed; + + workchainId = parsedWorkchainId; + + const auto parsedHash = parse_hex(string.substr(pos)); + std::copy(begin(parsedHash), end(parsedHash), begin(hash)); +} + +Address::Address(const PublicKey& publicKey, int8_t workchainId) + : Address(InitData(publicKey).computeAddr(workchainId)) { +} + +std::string Address::string() const { + std::string address = std::to_string(workchainId) + ":" + hex(hash); + return address; +} + +} // namespace TW::Everscale diff --git a/src/Everscale/Address.h b/src/Everscale/Address.h new file mode 100644 index 00000000000..d97ec61f44c --- /dev/null +++ b/src/Everscale/Address.h @@ -0,0 +1,51 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Data.h" +#include "../PublicKey.h" + +#include +#include + +namespace TW::Everscale { + +class Address { +public: + /// Number of bytes in an address + static const size_t size = Hash::sha256Size; + + /// Hex address length + static const size_t hexAddrLen = size * 2; + + /// Workchain ID (-1 for masterchain, 0 for base workchain) + std::int8_t workchainId; + /// StateInit hash + std::array hash{}; + + /// Determines whether a string makes a valid address. + [[nodiscard]] static bool isValid(const std::string& string) noexcept; + + /// Initializes an Everscale address with a string representation. + explicit Address(const std::string& string); + + /// Initializes an Everscale address with a public key and a workchain id. + explicit Address(const PublicKey& publicKey, int8_t workchainId); + + /// Initializes an Everscale address with its parts + explicit Address(int8_t workchainId, std::array hash) + : workchainId(workchainId), hash(hash) {} + + /// Returns a string representation of the address. + [[nodiscard]] std::string string() const; +}; + +inline bool operator==(const Address& lhs, const Address& rhs) { + return lhs.workchainId == rhs.workchainId && lhs.hash == rhs.hash; +} + +} // namespace TW::Everscale diff --git a/src/Everscale/Cell.cpp b/src/Everscale/Cell.cpp new file mode 100644 index 00000000000..8d1349937c9 --- /dev/null +++ b/src/Everscale/Cell.cpp @@ -0,0 +1,405 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Cell.h" + +#include +#include +#include +#include + +#include "../BinaryCoding.h" + +#include +#include + +#ifdef _MSC_VER +#include +typedef SSIZE_T ssize_t; +#endif + +using namespace TW; + +namespace TW::Everscale { + +constexpr static uint32_t BOC_MAGIC = 0xb5ee9c72; + +uint16_t computeBitLen(const Data& data, bool aligned) { + auto bitLen = static_cast(data.size() * 8); + if (aligned) { + return bitLen; + } + + for (auto i = static_cast(data.size() - 1); i >= 0; --i) { + if (data[i] == 0) { + bitLen -= 8; + } else { + auto skip = 1; + uint8_t mask = 1; + while ((data[i] & mask) == 0) { + skip += 1; + mask <<= 1; + } + bitLen -= skip; + break; + } + } + return bitLen; +} + +struct Reader { + const uint8_t* _Nonnull buffer; + size_t bufferLen; + size_t offset = 0; + + explicit Reader(const uint8_t* _Nonnull buffer, size_t len) noexcept + : buffer(buffer), bufferLen(len) { + } + + void require(size_t bytes) const { + if (offset + bytes > bufferLen) { + throw std::runtime_error("unexpected eof"); + } + } + + void advance(size_t bytes) { + offset += bytes; + } + + const uint8_t* _Nonnull data() const { + return buffer + offset; + } + + size_t readNextUint(uint8_t len) { + const auto* _Nonnull p = data(); + advance(len); + + switch (len) { + case 1: + return static_cast(*p); + case 2: + return static_cast(decode16BE(p)); + case 3: + return static_cast(p[2]) | (static_cast(p[1]) << 8) | (static_cast(p[0]) << 16); + case 4: + return static_cast(decode32BE(p)); + default: + // Unreachable in valid cells + return 0; + } + } +}; + +std::shared_ptr Cell::fromBase64(const std::string& encoded) { + // `Hash::base64` trims \0 bytes from the end of the _decoded_ data, so + // raw transform is used here + using namespace boost::archive::iterators; + using It = transform_width, 8, 6>; + Data boc(It(begin(encoded)), It(end(encoded))); + return Cell::deserialize(boc.data(), boc.size()); +} + +std::shared_ptr Cell::deserialize(const uint8_t* _Nonnull data, size_t len) { + Reader reader(data, len); + + // Parse header + reader.require(6); + // 1. Magic + if (reader.readNextUint(sizeof(BOC_MAGIC)) != BOC_MAGIC) { + throw std::runtime_error("unknown magic"); + } + // 2. Flags + struct Flags { + uint8_t refSize : 3; + uint8_t : 2; // unused + bool hasCacheBits : 1; + bool hasCrc : 1; + bool indexIncluded : 1; + }; + + static_assert(sizeof(Flags) == 1, "flags must be represented as 1 byte"); + const auto flags = reinterpret_cast(reader.data())[0]; + const auto refSize = flags.refSize; + const auto offsetSize = reader.data()[1]; + reader.advance(2); + + // 3. Counters and root index + reader.require(refSize * 3 + offsetSize + refSize); + const auto cellCount = reader.readNextUint(refSize); + const auto rootCount = reader.readNextUint(refSize); + if (rootCount != 1) { + throw std::runtime_error("unsupported root count"); + } + if (rootCount > cellCount) { + throw std::runtime_error("root count is greater than cell count"); + } + const auto absent_count = reader.readNextUint(refSize); + if (absent_count > 0) { + throw std::runtime_error("absent cells are not supported"); + } + + reader.readNextUint(offsetSize); // total cell size + + const auto rootIndex = reader.readNextUint(refSize); + + // 4. Cell offsets (skip if specified) + if (flags.indexIncluded) { + reader.advance(cellCount * offsetSize); + } + + // 5. Deserialize cells + struct IntermediateCell { + uint16_t bitLen; + Data data; + std::vector references; + }; + + std::vector intermediate{}; + intermediate.reserve(cellCount); + + for (size_t i = 0; i < cellCount; ++i) { + struct Descriptor { + uint8_t refCount : 3; + bool exotic : 1; + bool storeHashes : 1; + uint8_t level : 3; + }; + + static_assert(sizeof(Descriptor) == 1, "cell descriptor must be represented as 1 byte"); + + reader.require(2); + const auto d1 = reinterpret_cast(reader.data())[0]; + if (d1.level != 0) { + throw std::runtime_error("non-zero level is not supported"); + } + if (d1.exotic) { + throw std::runtime_error("exotic cells are not supported"); + } + if (d1.refCount == 7 && d1.storeHashes) { + throw std::runtime_error("absent cells are not supported"); + } + if (d1.refCount > 4) { + throw std::runtime_error("invalid ref count"); + } + const auto d2 = reader.data()[1]; + const auto byteLen = (d2 >> 1) + (d2 & 0b1); + reader.advance(2); + + // Skip stored hashes + if (d1.storeHashes) { + reader.advance(sizeof(uint16_t) + Hash::sha256Size); + } + + reader.require(byteLen); + Data cellData(byteLen); + std::memcpy(cellData.data(), reader.data(), byteLen); + reader.advance(byteLen); + + std::vector references{}; + references.reserve(refSize * d1.refCount); + reader.require(refSize * d1.refCount); + for (size_t r = 0; r < d1.refCount; ++r) { + const auto index = reader.readNextUint(refSize); + if (index > cellCount || index <= i) { + throw std::runtime_error("invalid child index"); + } + + references.push_back(index); + } + + const auto bitLen = computeBitLen(cellData, (d2 & 0b1) == 0); + intermediate.emplace_back( + IntermediateCell{ + .bitLen = bitLen, + .data = std::move(cellData), + .references = std::move(references), + }); + } + + std::unordered_map doneCells{}; + + size_t index = cellCount; + for (auto it = intermediate.rbegin(); it != intermediate.rend(); ++it, --index) { + auto& raw = *it; + + Cell::Refs references{}; + for (size_t r = 0; r < raw.references.size(); ++r) { + const auto child = doneCells.find(raw.references[r]); + if (child == doneCells.end()) { + throw std::runtime_error("child cell not found"); + } + references[r] = child->second; + } + + auto cell = std::make_shared(raw.bitLen, std::move(raw.data), raw.references.size(), std::move(references)); + cell->finalize(); + doneCells.emplace(index - 1, cell); + } + + const auto root = doneCells.find(rootIndex); + if (root == doneCells.end()) { + throw std::runtime_error("root cell not found"); + } + return std::move(root->second); +} + +class SerializationContext { +public: + static SerializationContext build(const Cell& cell) { + SerializationContext ctx{}; + fillContext(cell, ctx); + return ctx; + } + + void encode(Data& os) const { + os.reserve(os.size() + HEADER_SIZE + cellsSize); + + const auto cellCount = static_cast(reversedCells.size()); + + // Write header + encode32BE(BOC_MAGIC, os); + os.push_back(REF_SIZE); + os.push_back(OFFSET_SIZE); + encode16BE(static_cast(cellCount), os); + encode16BE(1, os); // root count + encode16BE(0, os); // absent cell count + encode16BE(static_cast(cellsSize), os); + encode16BE(0, os); // root cell index + + // Write cells + size_t i = cellCount - 1; + while (true) { + const auto& cell = *reversedCells[i]; + + // Write cell data + const auto [d1, d2] = cell.getDescriptorBytes(); + os.push_back(d1); + os.push_back(d2); + os.insert(os.end(), cell.data.begin(), cell.data.end()); + + // Write cell references + for (const auto& child : cell.references) { + if (child == nullptr) { + break; + } + + // Map cell hash to index (which must be presented) + const auto it = indices.find(child->hash); + assert(it != indices.end()); + + encode16BE(cellCount - it->second - 1, os); + } + + if (i == 0) { + break; + } else { + --i; + } + } + } + +private: + // uint16_t will be enough for wallet transactions (e.g. 64k is the size of the whole elector) + using ref_t = uint16_t; + using offset_t = uint16_t; + + constexpr static uint8_t REF_SIZE = sizeof(ref_t); + constexpr static uint8_t OFFSET_SIZE = sizeof(offset_t); + constexpr static size_t HEADER_SIZE = + /*magic*/ sizeof(BOC_MAGIC) + + /*ref_size*/ 1 + + /*offset_size*/ 1 + + /*cell_count*/ REF_SIZE + + /*root_count*/ REF_SIZE + + /*absent_count*/ REF_SIZE + + /*data_size*/ OFFSET_SIZE + + /*root_cell_index*/ REF_SIZE; + + size_t cellsSize = 0; + ref_t index = 0; + std::map indices{}; + std::vector reversedCells{}; + + static void fillContext(const Cell& cell, SerializationContext& ctx) { + if (ctx.indices.find(cell.hash) != ctx.indices.end()) { + return; + } + + for (const auto& ref : cell.references) { + if (ref == nullptr) { + break; + } + fillContext(*ref, ctx); + } + + ctx.indices.insert(std::make_pair(cell.hash, ctx.index++)); + ctx.reversedCells.emplace_back(&cell); + ctx.cellsSize += cell.serializedSize(REF_SIZE); + } +}; + +void Cell::serialize(Data& os) const { + assert(finalized); + const auto ctx = SerializationContext::build(*this); + ctx.encode(os); +} + +void Cell::finalize() { + if (finalized) { + return; + } + + if (bitLen > Cell::MAX_BITS || refCount > Cell::MAX_REFS) { + throw std::invalid_argument("invalid cell"); + } + + // Finalize child cells and update current cell depth + // NOTE: Use before context creation to reduce stack size + for (const auto& ref : references) { + if (ref == nullptr) { + break; + } + ref->finalize(); + depth = std::max(depth, static_cast(ref->depth + 1)); + } + + // Compute cell hash + const auto dataSize = std::min(data.size(), static_cast((bitLen + 7) / 8)); + + Data normalized{}; + normalized.reserve(/* descriptor bytes */ 2 + dataSize + refCount * (sizeof(uint16_t) + Hash::sha256Size)); + + // Write descriptor bytes + const auto [d1, d2] = getDescriptorBytes(); + normalized.push_back(d1); + normalized.push_back(d2); + std::copy(data.begin(), std::next(data.begin(), static_cast(dataSize)), std::back_inserter(normalized)); + + // Write all children depths + for (const auto& ref : references) { + if (ref == nullptr) { + break; + } + encode16BE(ref->depth, normalized); + } + + // Write all children hashes + for (const auto& ref : references) { + if (ref == nullptr) { + break; + } + std::copy(ref->hash.begin(), ref->hash.end(), std::back_inserter(normalized)); + } + + // Done + const auto computedHash = Hash::sha256(normalized); + assert(computedHash.size() == Hash::sha256Size); + + std::copy(computedHash.begin(), computedHash.end(), hash.begin()); + finalized = true; +} + +} // namespace TW::Everscale diff --git a/src/Everscale/Cell.h b/src/Everscale/Cell.h new file mode 100644 index 00000000000..8ff98f09c28 --- /dev/null +++ b/src/Everscale/Cell.h @@ -0,0 +1,70 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include +#include +#include + +#include + +#include "Data.h" +#include "../Hash.h" + +//win +#ifdef _MSC_VER +#define _Nonnull +#endif +namespace TW::Everscale { + +class Cell { +public: + constexpr static uint16_t MAX_BITS = 1023; + constexpr static uint8_t MAX_REFS = 4; + + using Ref = std::shared_ptr; + using Refs = std::array; + using CellHash = std::array; + + bool finalized = false; + uint16_t bitLen = 0; + std::vector data{}; + uint8_t refCount = 0; + Refs references{}; + + uint16_t depth = 0; + CellHash hash{}; + + Cell() = default; + + Cell(uint16_t bitLen, std::vector data, uint8_t refCount, Refs references) + : bitLen(bitLen), data(std::move(data)), refCount(refCount), references(std::move(references)) {} + + // Deserialize from Base64 + static std::shared_ptr fromBase64(const std::string& encoded); + + // Deserialize from BOC representation + static std::shared_ptr deserialize(const uint8_t* _Nonnull data, size_t len); + + // Serialize to binary stream + void serialize(Data& os) const; + + // Compute cell depth and hash + void finalize(); + + [[nodiscard]] inline std::pair getDescriptorBytes() const noexcept { + const uint8_t d1 = refCount; + const uint8_t d2 = (static_cast(bitLen >> 2) & 0b11111110) | (bitLen % 8 != 0); + return std::pair{d1, d2}; + } + + [[nodiscard]] inline size_t serializedSize(uint8_t refSize) const noexcept { + return 2 + (bitLen + 7) / 8 + refCount * refSize; + } +}; + +} // namespace TW::Everscale diff --git a/src/Everscale/CellBuilder.cpp b/src/Everscale/CellBuilder.cpp new file mode 100644 index 00000000000..8ddc3ccbc94 --- /dev/null +++ b/src/Everscale/CellBuilder.cpp @@ -0,0 +1,260 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "CellBuilder.h" +#include "Cell.h" + +#include +#include +#include + +#include "../BinaryCoding.h" + +using namespace TW; + +namespace TW::Everscale { + +CellBuilder::CellBuilder(Data& appendedData, uint16_t bits) { + assert(bits <= appendedData.size() * 8); + assert(bits < Cell::MAX_BITS); + assert(bits % 8 == 0); + + appendedData.resize(bits / 8); + + data = appendedData; + bitLen = bits; + references = {}; +} + +void CellBuilder::appendBitZero() { + Data appendedData{0x00}; + appendRaw(appendedData, 1); +} + +void CellBuilder::appendBitOne() { + Data appendedData{0xFF}; + appendRaw(appendedData, 1); +} + +void CellBuilder::appendBitBool(bool bit) { + auto getAppendedData = [](bool bit) { + if (bit) { + return Data{0xFF}; + } else { + return Data{0x00}; + } + }; + auto appendedData = getAppendedData(bit); + appendRaw(appendedData, 1); +} + +void CellBuilder::appendU8(uint8_t value) { + Data appendedData{value}; + appendRaw(appendedData, 8); +} + +void CellBuilder::appendU32(uint32_t value) { + Data appendedData; + encode32BE(value, appendedData); + appendRaw(appendedData, 32); +} + +void CellBuilder::appendU64(uint64_t value) { + Data appendedData; + encode64BE(value, appendedData); + appendRaw(appendedData, 64); +} + +void CellBuilder::appendU128(const uint128_t& value) { + uint8_t bits = 4; + uint16_t bytes = 16 - clzU128(value) / 8; + + appendBits(bytes, bits); + + Data encodedValue; + encode128BE(value, encodedValue); + + auto offset = static_cast(encodedValue.size() - bytes); + + Data appendedData(encodedValue.begin() + offset, encodedValue.end()); + appendRaw(appendedData, bytes * 8); +} + +void CellBuilder::appendI8(int8_t value) { + Data appendedData{static_cast(value)}; + appendRaw(appendedData, 8); +} + +void CellBuilder::appendBits(uint64_t value, uint8_t bits) { + assert(bits >= 1 && bits <= 7); + + Data appendedData; + + auto val = static_cast(value << (8 - bits)); + appendedData.push_back(val); + + appendRaw(appendedData, bits); +} + +void CellBuilder::appendRaw(const Data& appendedData, uint16_t bits) { + if (appendedData.size() * 8 < bits) { + throw std::invalid_argument("invalid builder data"); + } else if (bitLen + bits > Cell::MAX_BITS) { + throw std::runtime_error("cell data overflow"); + } else if (bits != 0) { + if ((bitLen % 8) == 0) { + if ((bits % 8) == 0) { + appendWithoutShifting(appendedData, bits); + } else { + appendWithSliceShifting(appendedData, bits); + } + } else { + appendWithDoubleShifting(appendedData, bits); + } + } + assert(bitLen <= Cell::MAX_BITS); + assert(data.size() * 8 <= Cell::MAX_BITS + 1); +} + +void CellBuilder::prependRaw(Data& appendedData, uint16_t bits) { + if (bits != 0) { + auto buffer = CellBuilder(appendedData, bits); + buffer.appendRaw(data, bitLen); + + data = buffer.data; + bitLen = buffer.bitLen; + } +} + +void CellBuilder::appendReferenceCell(std::shared_ptr child) { + if (child) { + if (references.size() + 1 > Cell::MAX_REFS) { + throw std::runtime_error("cell refs overflow"); + } + references.emplace_back(std::move(child)); + } +} + +void CellBuilder::appendBuilder(const CellBuilder& builder) { + appendRaw(builder.data, builder.bitLen); + for (const auto& reference : builder.references) { + appendReferenceCell(reference); + } +} + +void CellBuilder::appendCellSlice(const CellSlice& other) { + Data appendedData(other.cell->data); + appendRaw(appendedData, other.cell->bitLen); + + for (const auto& cell : other.cell->references) { + appendReferenceCell(cell); + } +} + +Cell::Ref CellBuilder::intoCell() { + // Append tag + if (bitLen & 7) { + const auto mask = static_cast(0x80 >> (bitLen & 7)); + const auto l = bitLen / 8; + data[l] = static_cast((data[l] & ~mask) | mask); + } + + auto refCount = references.size(); + + Cell::Refs refs; + std::move(references.begin(), references.end(), refs.begin()); + + auto cell = std::make_shared(bitLen, std::move(data), refCount, std::move(refs)); + cell->finalize(); + + bitLen = 0; + refCount = 0; + + return cell; +} + +void CellBuilder::appendWithoutShifting(const Data& appendedData, uint16_t bits) { + assert(bits % 8 == 0); + assert(bitLen % 8 == 0); + + data.resize(bitLen / 8); + data.insert(data.end(), appendedData.begin(), appendedData.end()); + bitLen += bits; + data.resize(bitLen / 8); +} + +void CellBuilder::appendWithSliceShifting(const Data& appendedData, uint16_t bits) { + assert(bits % 8 != 0); + assert(bitLen % 8 == 0); + + data.resize(bitLen / 8); + data.insert(data.end(), appendedData.begin(), appendedData.end()); + bitLen += bits; + data.resize(1 + bitLen / 8); + + data.back() &= ~static_cast(0xff >> (bits % 8)); +} + +void CellBuilder::appendWithDoubleShifting(const Data& appendedData, uint16_t bits) { + auto selfShift = bitLen % 8; + data.resize(1 + bitLen / 8); + bitLen += bits; + + // yyyyy000 -> 00000000 000yyyyy + auto y = static_cast(data.back() >> (8 - selfShift)); + data.pop_back(); + + for (const auto x : appendedData) { + // 00000000 000yyyyy -> 000yyyyy xxxxxxxx + y = static_cast(y << 8) | x; + // 000yyyyy xxxxxxxx -> 00000000 yyyyyxxx + data.push_back(static_cast(y >> selfShift)); + } + // 00000000 yyyyyxxx + data.push_back(static_cast(y << (8 - selfShift))); + + auto shift = bitLen % 8; + if (shift == 0) { + data.resize(bitLen / 8); + } else { + data.resize(bitLen / 8 + 1); + data.back() &= ~static_cast(0xff >> (bitLen % 8)); + } +} + +uint8_t CellBuilder::clzU128(const uint128_t& u) { + auto hi = static_cast(u >> 64); + auto lo = static_cast(u); + + if (lo == 0 && hi == 0) { + return 128; + } else if (hi == 0) { + return static_cast(std::countl_zero(lo) + 64); + } else { + return static_cast(std::countl_zero(hi)); + } +} + +void CellBuilder::encode128BE(const uint128_t& val, Data& data) { + data.emplace_back(static_cast((val >> 120))); + data.emplace_back(static_cast((val >> 112))); + data.emplace_back(static_cast((val >> 104))); + data.emplace_back(static_cast((val >> 96))); + data.emplace_back(static_cast((val >> 88))); + data.emplace_back(static_cast((val >> 80))); + data.emplace_back(static_cast((val >> 72))); + data.emplace_back(static_cast((val >> 64))); + data.emplace_back(static_cast((val >> 56))); + data.emplace_back(static_cast((val >> 48))); + data.emplace_back(static_cast((val >> 40))); + data.emplace_back(static_cast((val >> 32))); + data.emplace_back(static_cast((val >> 24))); + data.emplace_back(static_cast((val >> 16))); + data.emplace_back(static_cast((val >> 8))); + data.emplace_back(static_cast(val)); +} + +} // namespace TW::Everscale diff --git a/src/Everscale/CellBuilder.h b/src/Everscale/CellBuilder.h new file mode 100644 index 00000000000..8b192ec42b3 --- /dev/null +++ b/src/Everscale/CellBuilder.h @@ -0,0 +1,55 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include +#include +#include + +#include "Cell.h" +#include "CellSlice.h" + +namespace TW::Everscale { + +class CellBuilder { + uint16_t bitLen = 0; + std::vector data{}; + std::vector references{}; + +public: + using uint128_t = boost::multiprecision::uint128_t; + + CellBuilder() = default; + CellBuilder(Data& appendedData, uint16_t bits); + + void appendBitZero(); + void appendBitOne(); + void appendU8(uint8_t value); + void appendBitBool(bool bit); + void appendU32(uint32_t value); + void appendU64(uint64_t value); + void appendU128(const uint128_t& value); + void appendI8(int8_t value); + void appendBits(uint64_t value, uint8_t bits); + void appendRaw(const Data& appendedData, uint16_t bits); + void prependRaw(Data& appendedData, uint16_t bits); + void appendReferenceCell(Cell::Ref child); + void appendBuilder(const CellBuilder& builder); + void appendCellSlice(const CellSlice& other); + + Cell::Ref intoCell(); + +private: + void appendWithoutShifting(const Data& data, uint16_t bits); + void appendWithSliceShifting(const Data& data, uint16_t bits); + void appendWithDoubleShifting(const Data& data, uint16_t bits); + + static uint8_t clzU128(const uint128_t& u); + static void encode128BE(const uint128_t& value, Data& data); +}; + +} // namespace TW::Everscale diff --git a/src/Everscale/CellSlice.cpp b/src/Everscale/CellSlice.cpp new file mode 100644 index 00000000000..346f4a65365 --- /dev/null +++ b/src/Everscale/CellSlice.cpp @@ -0,0 +1,59 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "CellSlice.h" + +#include + +#include "../BinaryCoding.h" + +using namespace TW; + +namespace TW::Everscale { + +uint32_t CellSlice::getNextU32() { + const auto bytes = getNextBytes(sizeof(uint32_t)); + return decode32BE(bytes.data()); +} + +Data CellSlice::getNextBytes(uint8_t bytes) { + if (bytes == 0) { + return Data{}; + } + require(bytes * 8); + Data result{}; + result.reserve(bytes); + + const size_t q = dataOffset / 8; + const auto r = dataOffset % 8; + const auto invR = 8 - r; + + dataOffset += bytes * 8; + + if (r == 0) { + const auto begin = cell->data.begin() + q; + const auto end = begin + bytes; + std::copy(begin, end, std::back_inserter(result)); + return result; + } + + for (size_t byte = q; byte < q + bytes; ++byte) { + auto bits = static_cast(static_cast(cell->data[byte]) << 8); + if (byte + 1 < cell->data.size()) { + bits |= static_cast(cell->data[byte + 1]); + } + result.push_back(static_cast(bits >> invR)); + } + return result; +} + +void CellSlice::require(uint16_t bits) const { + if (dataOffset + bits > cell->bitLen) { + throw std::runtime_error("cell data underflow"); + } +} + +} // namespace TW::Everscale diff --git a/src/Everscale/CellSlice.h b/src/Everscale/CellSlice.h new file mode 100644 index 00000000000..f7446f1482f --- /dev/null +++ b/src/Everscale/CellSlice.h @@ -0,0 +1,36 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include +#include +#include + +#include "Cell.h" +//win +#ifdef _MSC_VER +#define _Nonnull +#endif + +namespace TW::Everscale { + +class CellSlice { +public: + const Cell* _Nonnull cell; + uint16_t dataOffset = 0; + + explicit CellSlice(const Cell* _Nonnull cell) noexcept + : cell(cell) {} + + uint32_t getNextU32(); + Data getNextBytes(uint8_t bytes); + +private: + void require(uint16_t bits) const; +}; + +} // namespace TW::Everscale diff --git a/src/Everscale/Entry.cpp b/src/Everscale/Entry.cpp new file mode 100644 index 00000000000..e17aa9d5997 --- /dev/null +++ b/src/Everscale/Entry.cpp @@ -0,0 +1,36 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Entry.h" + +#include "Address.h" +#include "Signer.h" +#include "WorkchainType.h" + +using namespace TW; +using namespace std; + +namespace TW::Everscale { + +// Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. + +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { + return Address::isValid(address); +} + +string Entry::normalizeAddress([[maybe_unused]] TWCoinType coin, const string& address) const { + return Address(address).string(); +} + +string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { + return Address(publicKey, WorkchainType::Basechain).string(); +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + signTemplate(dataIn, dataOut); +} + +} // namespace TW::Everscale diff --git a/src/Everscale/Entry.h b/src/Everscale/Entry.h new file mode 100644 index 00000000000..c83a0d133ad --- /dev/null +++ b/src/Everscale/Entry.h @@ -0,0 +1,23 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "../CoinEntry.h" + +namespace TW::Everscale { + +/// Entry point for implementation of Everscale 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, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string normalizeAddress(TWCoinType coin, const std::string& address) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* d) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; +}; + +} // namespace TW::Everscale diff --git a/src/Everscale/Messages.cpp b/src/Everscale/Messages.cpp new file mode 100644 index 00000000000..a20ee263f08 --- /dev/null +++ b/src/Everscale/Messages.cpp @@ -0,0 +1,146 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Messages.h" +#include "WorkchainType.h" + +using namespace TW; + +namespace TW::Everscale { + +void ExternalInboundMessageHeader::writeTo(CellBuilder& builder) const { + builder.appendBitOne(); + builder.appendBitZero(); + + // addr src (none) + builder.appendRaw(Data{0x00}, 2); + + // addr dst + Data dstAddr(_dst.hash.begin(), _dst.hash.end()); + + Data prefix{0x80}; + builder.appendRaw(prefix, 2); + + builder.appendBitZero(); + builder.appendI8(_dst.workchainId); + builder.appendRaw(dstAddr, 256); + + // fee + builder.appendU128(_importFee); +} + +void InternalMessageHeader::writeTo(CellBuilder& builder) const { + // tag + builder.appendBitZero(); + + builder.appendBitBool(_ihrDisabled); + builder.appendBitBool(_bounce); + builder.appendBitBool(_bounced); + + // addr src (none) + builder.appendRaw(Data{0x00}, 2); + + // addr dst + Data dstAddr(_dst.hash.begin(), _dst.hash.end()); + + Data prefix{0x80}; + builder.appendRaw(prefix, 2); + + builder.appendBitZero(); + builder.appendI8(_dst.workchainId); + builder.appendRaw(dstAddr, 256); + + // value + builder.appendU128(_value); + builder.appendBitZero(); + + // fee + builder.appendU128(_ihrFee); + builder.appendU128(_fwdFee); + + // created + builder.appendU64(_createdLt); + builder.appendU32(_createdAt); +} + +Cell::Ref Message::intoCell() const { + CellBuilder builder; + + // write Header + _header->writeTo(builder); + + // write StateInit + if (_init.has_value()) { + auto initBuilder = _init.value().writeTo(); + + builder.appendBitOne(); + builder.appendBitZero(); + builder.appendBuilder(initBuilder); + } else { + builder.appendBitZero(); + } + + // write Body + if (_body.has_value()) { + builder.appendBitZero(); + builder.appendCellSlice(_body.value()); + } else { + builder.appendBitZero(); + } + + return builder.intoCell(); +} + +Data createSignedMessage(PublicKey& publicKey, PrivateKey& key, bool bounce, uint32_t flags, uint64_t amount, uint32_t expiredAt, + Address to, const Cell::Ref& contractData) { + auto getInitData = [](const PublicKey& publicKey, const Cell::Ref& contractData) { + if (contractData != nullptr) { + auto cellSlice = CellSlice(contractData.get()); + return std::make_pair(InitData(cellSlice), /* withInitState */ false); + } else { + return std::make_pair(InitData(publicKey), /* withInitState */ true); + } + }; + + auto [initData, withInitState] = getInitData(publicKey, contractData); + + auto gift = Wallet::Gift{ + .bounce = bounce, + .amount = amount, + .to = to, + .flags = static_cast(flags), + }; + + auto payload = initData.makeTransferPayload(expiredAt, gift); + + auto payloadCopy = payload; + auto payloadCell = payloadCopy.intoCell(); + + Data data(payloadCell->hash.begin(), payloadCell->hash.end()); + auto signature = key.sign(data, TWCurveED25519); + payload.prependRaw(signature, static_cast(signature.size()) * 8); + + auto header = std::make_shared(InitData(publicKey).computeAddr(WorkchainType::Basechain)); + auto message = Message(header); + + if (withInitState) { + message.setStateInit(initData.makeStateInit()); + } + + auto cell = payload.intoCell(); + auto body = CellSlice(cell.get()); + + message.setBody(body); + + const auto messageCell = message.intoCell(); + + Data result{}; + messageCell->serialize(result); + + return result; +} + +} // namespace TW::Everscale diff --git a/src/Everscale/Messages.h b/src/Everscale/Messages.h new file mode 100644 index 00000000000..345f75667ec --- /dev/null +++ b/src/Everscale/Messages.h @@ -0,0 +1,81 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include +#include + +#include "Address.h" +#include "CellBuilder.h" +#include "CellSlice.h" +#include "Wallet.h" + +#include "../PrivateKey.h" + +namespace TW::Everscale { + +using uint128_t = CellBuilder::uint128_t; + +class CommonMsgInfo { +public: + virtual void writeTo(CellBuilder& builder) const = 0; + + virtual ~CommonMsgInfo() = default; +}; + +class ExternalInboundMessageHeader : public CommonMsgInfo { + Address _dst; + uint128_t _importFee{}; + +public: + explicit ExternalInboundMessageHeader(Address dst) + : _dst(dst) {} + + void writeTo(CellBuilder& builder) const override; +}; + +class InternalMessageHeader : public CommonMsgInfo { + bool _ihrDisabled; + bool _bounce; + Address _dst; + uint128_t _value; + + bool _bounced{}; + uint128_t _ihrFee{}; + uint128_t _fwdFee{}; + uint64_t _createdLt{}; + uint32_t _createdAt{}; + +public: + InternalMessageHeader(bool ihrDisabled, bool bounce, Address dst, uint64_t value) + : _ihrDisabled(ihrDisabled), _bounce(bounce), _dst(dst), _value(static_cast(value)) {} + + void writeTo(CellBuilder& builder) const override; +}; + +class Message { +private: + std::shared_ptr _header; + + std::optional _init{}; + std::optional _body{}; + +public: + using HeaderRef = std::shared_ptr; + + explicit Message(HeaderRef header) + : _header(std::move(header)) {} + + [[nodiscard]] Cell::Ref intoCell() const; + inline void setBody(CellSlice body) { _body = body; } + inline void setStateInit(const StateInit& stateInit) { _init = stateInit; } +}; + +Data createSignedMessage(PublicKey& publicKey, PrivateKey& key, bool bounce, uint32_t flags, uint64_t amount, + uint32_t expiredAt, Address destination, const Cell::Ref& contractData); + +} // namespace TW::Everscale diff --git a/src/Everscale/Signer.cpp b/src/Everscale/Signer.cpp new file mode 100644 index 00000000000..1f2a0fb9d10 --- /dev/null +++ b/src/Everscale/Signer.cpp @@ -0,0 +1,69 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Signer.h" +#include "Address.h" +#include "Messages.h" + +#include "../Base64.h" + +using namespace TW; +using namespace std::chrono; + +namespace TW::Everscale { + +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { + auto key = PrivateKey(input.private_key()); + auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); + + auto protoOutput = Proto::SigningOutput(); + + switch (input.action_oneof_case()) { + case Proto::SigningInput::ActionOneofCase::kTransfer: { + const auto& transfer = input.transfer(); + + uint8_t flags; + switch (transfer.behavior()) { + case Proto::MessageBehavior::SendAllBalance: { + flags = Wallet::sendAllBalanceFlags; + break; + } + default: { + flags = Wallet::simpleTransferFlags; + break; + } + } + + Cell::Ref contractData{}; + switch (transfer.account_state_oneof_case()) { + case Proto::Transfer::AccountStateOneofCase::kEncodedContractData: { + contractData = Cell::fromBase64(transfer.encoded_contract_data()); + break; + } + default: + break; + } + + auto signedMessage = createSignedMessage( + publicKey, + key, + transfer.bounce(), + flags, + transfer.amount(), + transfer.expired_at(), + Address(transfer.to()), + contractData); + protoOutput.set_encoded(TW::Base64::encode(signedMessage)); + break; + } + default: + break; + } + + return protoOutput; +} + +} // namespace TW::Everscale diff --git a/src/Everscale/Signer.h b/src/Everscale/Signer.h new file mode 100644 index 00000000000..35cfcd953a0 --- /dev/null +++ b/src/Everscale/Signer.h @@ -0,0 +1,25 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Data.h" +#include "../PrivateKey.h" +#include "../proto/Everscale.pb.h" + +namespace TW::Everscale { + +/// Helper class that performs Everscale transaction signing. +class Signer { +public: + /// Hide default constructor + Signer() = delete; + + /// Signs a Proto::SigningInput transaction + static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; +}; + +} // namespace TW::Everscale diff --git a/src/Everscale/Wallet.cpp b/src/Everscale/Wallet.cpp new file mode 100644 index 00000000000..09bf1e442e5 --- /dev/null +++ b/src/Everscale/Wallet.cpp @@ -0,0 +1,82 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Wallet.h" +#include "CellBuilder.h" +#include "Messages.h" + +#include "HexCoding.h" + +using namespace TW; + +namespace TW::Everscale { + +// WalletV3 contract https://github.com/tonlabs/ton-1/blob/master/crypto/smartcont/wallet3-code.fc +const Data Wallet::code = parse_hex("b5ee9c720101010100710000deff0020dd2082014c97ba218201339cbab19f71b0ed44d0d31fd31f31d70bffe304e0a4f2608308d71820d31fd31fd31ff82313bbf263ed44d0d31fd31fd3ffd15132baf2a15144baf2a204f901541055f910f2a3f8009320d74a96d307d402fb00e8d101a4c8cb1fcb1fcbffc9ed54"); + +CellBuilder InitData::writeTo() const { + CellBuilder builder; + + builder.appendU32(_seqno); + builder.appendU32(_walletId); + builder.appendRaw(_publicKey.bytes, 256); + + return builder; +} + +Address InitData::computeAddr(int8_t workchainId) const { + auto builder = this->writeTo(); + + StateInit stateInit{ + .code = Cell::deserialize(Wallet::code.data(), Wallet::code.size()), + .data = builder.intoCell(), + }; + return Address(workchainId, stateInit.writeTo().intoCell()->hash); +} + +StateInit InitData::makeStateInit() const { + auto builder = this->writeTo(); + + return StateInit{ + .code = Cell::deserialize(Wallet::code.data(), Wallet::code.size()), + .data = builder.intoCell(), + }; +} + +CellBuilder InitData::makeTransferPayload(uint32_t expireAt, const Wallet::Gift& gift) const { + CellBuilder payload; + + // insert prefix + payload.appendU32(_walletId); + payload.appendU32(expireAt); + payload.appendU32(_seqno); + + // create internal message + Message::HeaderRef header = std::make_shared(true, gift.bounce, gift.to, gift.amount); + auto message = Message(header); + + // append it to the body + payload.appendU8(gift.flags); + payload.appendReferenceCell(message.intoCell()); + + return payload; +} + +CellBuilder StateInit::writeTo() const { + CellBuilder builder; + + builder.appendBitZero(); // split_depth + builder.appendBitZero(); // special + builder.appendBitOne(); // code + builder.appendReferenceCell(code); + builder.appendBitOne(); // data + builder.appendReferenceCell(data); + builder.appendBitZero(); // library + + return builder; +} + +} // namespace TW::Everscale diff --git a/src/Everscale/Wallet.h b/src/Everscale/Wallet.h new file mode 100644 index 00000000000..a0216c465db --- /dev/null +++ b/src/Everscale/Wallet.h @@ -0,0 +1,77 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include +#include + +#include "../PublicKey.h" + +#include "Address.h" +#include "Cell.h" +#include "CellBuilder.h" +#include "CellSlice.h" + +const uint32_t WALLET_ID = 0x4BA92D8A; + +namespace TW::Everscale { + +class Wallet { +public: + enum MessageFlags : uint8_t { + // Sender wants to pay transfer fees separately + // (from account balance instead of message balance) + FeesFromAccountBalance = (1 << 0), + + // If there are some errors during the action phase it should be ignored + IgnoreActionPhaseErrors = (1 << 1), + + // Message will carry all the remaining balance + AttachAllBalance = (1 << 7), + }; + + struct Gift { + bool bounce; + uint64_t amount; + Address to; + uint8_t flags; + }; + + static constexpr uint8_t simpleTransferFlags = + MessageFlags::FeesFromAccountBalance | MessageFlags::IgnoreActionPhaseErrors; + static constexpr uint8_t sendAllBalanceFlags = + MessageFlags::AttachAllBalance | MessageFlags::IgnoreActionPhaseErrors; + + static const Data code; +}; + +class StateInit { +public: + Cell::Ref code; + Cell::Ref data; + + [[nodiscard]] CellBuilder writeTo() const; +}; + +class InitData { + uint32_t _seqno; + uint32_t _walletId; + PublicKey _publicKey; + +public: + explicit InitData(PublicKey publicKey) + : _seqno(0), _walletId(WALLET_ID), _publicKey(std::move(publicKey)) {} + explicit InitData(CellSlice cs) + : _seqno(cs.getNextU32()), _walletId(cs.getNextU32()), _publicKey(PublicKey(cs.getNextBytes(32), TWPublicKeyTypeED25519)) {} + + [[nodiscard]] CellBuilder writeTo() const; + [[nodiscard]] StateInit makeStateInit() const; + [[nodiscard]] Address computeAddr(int8_t workchainId) const; + [[nodiscard]] CellBuilder makeTransferPayload(uint32_t expireAt, const Wallet::Gift& gift) const; +}; + +} // namespace TW::Everscale diff --git a/src/Bitcoin/Address.cpp b/src/Everscale/WorkchainType.h similarity index 56% rename from src/Bitcoin/Address.cpp rename to src/Everscale/WorkchainType.h index 5aa19d29eab..786343e1065 100644 --- a/src/Bitcoin/Address.cpp +++ b/src/Everscale/WorkchainType.h @@ -1,11 +1,16 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "Address.h" +#pragma once -#include "../Base58.h" +namespace TW::Everscale { -using namespace TW::Bitcoin; +enum WorkchainType { + Masterchain = -1, + Basechain = 0, +}; + +} // namespace TW::Everscale diff --git a/src/FIO/Action.cpp b/src/FIO/Action.cpp index 9a64137e7bb..db6a0c9d536 100644 --- a/src/FIO/Action.cpp +++ b/src/FIO/Action.cpp @@ -5,7 +5,7 @@ // file LICENSE at the root of the source code distribution tree. #include "Action.h" -#include "../Data.h" +#include "Data.h" namespace TW::FIO { diff --git a/src/FIO/Action.h b/src/FIO/Action.h index c79ef65a500..70566cfd7ba 100644 --- a/src/FIO/Action.h +++ b/src/FIO/Action.h @@ -7,7 +7,7 @@ #pragma once #include "EOS/Name.h" // Name is reused -#include "../Data.h" +#include "Data.h" #include "../BinaryCoding.h" #include @@ -16,7 +16,7 @@ namespace TW::FIO { /// Encodes a value as a variable-length integer. -/// @returns the number of bytes written. +/// \returns the number of bytes written. uint8_t encodeVarInt(uint64_t num, Data& data); /// Encodes an ASCII string prefixed by the length (varInt) diff --git a/src/FIO/Actor.cpp b/src/FIO/Actor.cpp index 550b52489bd..f30c351e34f 100644 --- a/src/FIO/Actor.cpp +++ b/src/FIO/Actor.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -8,49 +8,50 @@ #include -using namespace TW::FIO; -using namespace std; +namespace TW::FIO { -static const auto pattern = regex(R"(\b([a-z1-5]{3,})[.@]?\b)"); -string Actor::actor(const Address& addr) -{ +static const auto pattern = std::regex(R"(\b([a-z1-5]{3,})[.@]?\b)"); +std::string Actor::actor(const Address& addr) { uint64_t shortenedKey = shortenKey(addr.bytes); - string name13 = name(shortenedKey); + std::string name13 = name(shortenedKey); // trim to 12 chracters return name13.substr(0, 12); } bool Actor::validate(const std::string& addr) { - smatch match; + std::smatch match; return regex_search(addr, match, pattern); } -uint64_t Actor::shortenKey(const array& addrKey) -{ +uint64_t Actor::shortenKey(const std::array& addrKey) { uint64_t res = 0; int i = 1; // Ignore key head int len = 0; while (len <= 12) { assert(i < 33); // Means the key has > 20 bytes with trailing zeroes... - + auto trimmed_char = uint64_t(addrKey[i] & (len == 12 ? 0x0f : 0x1f)); - if (trimmed_char == 0) { i++; continue; } // Skip a zero and move to next + if (trimmed_char == 0) { + i++; + continue; + } // Skip a zero and move to next auto shuffle = len == 12 ? 0 : 5 * (12 - len) - 1; res |= trimmed_char << shuffle; - len++; i++; + len++; + i++; } return res; } -string Actor::name(uint64_t shortKey) { +std::string Actor::name(uint64_t shortKey) { static const char* charmap = ".12345abcdefghijklmnopqrstuvwxyz"; - string str(13,'.'); //We are forcing the string to be 13 characters + std::string str(13, '.'); // We are forcing the string to be 13 characters uint64_t tmp = shortKey; - for(uint32_t i = 0; i <= 12; i++ ) { + for (uint32_t i = 0; i <= 12; i++) { char c = charmap[tmp & (i == 0 ? 0x0f : 0x1f)]; str[12 - i] = c; tmp >>= (i == 0 ? 4 : 5); @@ -58,3 +59,5 @@ string Actor::name(uint64_t shortKey) { return str; } + +} // namespace TW::FIO diff --git a/src/FIO/Address.cpp b/src/FIO/Address.cpp index 362cd7c6a9d..52d1086e48f 100644 --- a/src/FIO/Address.cpp +++ b/src/FIO/Address.cpp @@ -4,16 +4,17 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +#include "Address.h" #include "../Base58.h" #include "../BinaryCoding.h" -#include "Address.h" #include #include using namespace TW; -using namespace TW::FIO; + +namespace TW::FIO { bool Address::isValid(const std::string& string) { return decodeKeyData(string).has_value(); @@ -101,3 +102,5 @@ PublicKey Address::publicKey() const { const Data keyData = TW::data(bytes.data(), PublicKey::secp256k1Size); return PublicKey(keyData, TWPublicKeyTypeSECP256k1); } + +} // namespace TW::FIO diff --git a/src/FIO/Address.h b/src/FIO/Address.h index 25dba8e7bf5..2d00fa70dfa 100644 --- a/src/FIO/Address.h +++ b/src/FIO/Address.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include diff --git a/src/FIO/Encryption.h b/src/FIO/Encryption.h index ecc7106f8b8..e293f3358fd 100644 --- a/src/FIO/Encryption.h +++ b/src/FIO/Encryption.h @@ -6,35 +6,35 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" #include "../PublicKey.h" namespace TW::FIO { /// Payload message encryption/decryption. -/// See also https://github.com/fioprotocol/fiojs/blob/master/src/encryption-check.ts +/// \see https://github.com/fioprotocol/fiojs/blob/master/src/encryption-check.ts class Encryption { public: /** * Provides AES-256-CBC encryption and message authentication. * The CBC cipher is used for good platform native compatability. - * @see https://security.stackexchange.com/a/63134 - * @see https://security.stackexchange.com/a/20493 - * @arg secret - Shared secret (64-bytes). - * @arg message - Plaintext message (arbitrary length). - * @arg iv - An unpredictable strong random value (16 bytes) is required + * \see https://security.stackexchange.com/a/63134 + * \see https://security.stackexchange.com/a/20493 + * \param secret - Shared secret (64-bytes). + * \param message - Plaintext message (arbitrary length). + * \param iv - An unpredictable strong random value (16 bytes) is required * and supplied by default. Unit tests may provide a static value to achieve predictable results. - * @throws {Error} invalid IV size + * \throws {Error} invalid IV size */ static Data checkEncrypt(const Data& secret, const Data& message, Data& iv); /** * Provides AES-256-CBC message authentication then decryption. - * @arg secret - Shared secret (64-bytes). - * @arg message - Ciphertext (from checkEncrypt()) - * @throws {Error} Message too short - * @throws {Error} Decrypt failed, HMAC mismatch + * \param secret - Shared secret (64-bytes). + * \param message - Ciphertext (from checkEncrypt()) + * \throws {Error} Message too short + * \throws {Error} Decrypt failed, HMAC mismatch */ static Data checkDecrypt(const Data& secret, const Data& message); diff --git a/src/FIO/Entry.cpp b/src/FIO/Entry.cpp index 1caec6f5a68..a9194cb53ae 100644 --- a/src/FIO/Entry.cpp +++ b/src/FIO/Entry.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,19 +9,20 @@ #include "Address.h" #include "Signer.h" -using namespace TW::FIO; -using namespace std; +namespace TW::FIO { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +} // namespace TW::FIO diff --git a/src/FIO/Entry.h b/src/FIO/Entry.h index 3978da39256..cb9ab8c38a2 100644 --- a/src/FIO/Entry.h +++ b/src/FIO/Entry.h @@ -12,12 +12,11 @@ namespace TW::FIO { /// Entry point for implementation of FIO coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeFIO}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::FIO diff --git a/src/FIO/NewFundsRequest.h b/src/FIO/NewFundsRequest.h index e3602fd54b1..3b58d2c3a40 100644 --- a/src/FIO/NewFundsRequest.h +++ b/src/FIO/NewFundsRequest.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include diff --git a/src/FIO/Signer.cpp b/src/FIO/Signer.cpp index f3a96ad0954..e4390dd3b44 100644 --- a/src/FIO/Signer.cpp +++ b/src/FIO/Signer.cpp @@ -40,7 +40,7 @@ Data Signer::signData(const PrivateKey& privKey, const Data& data) { return signature; } -std::string Signer::signatureToBsase58(const Data& sig) { +std::string Signer::signatureToBase58(const Data& sig) { Data sigWithSuffix(sig); append(sigWithSuffix, TW::data(SignatureSuffix)); // take hash, ripemd, first 4 bytes @@ -56,7 +56,7 @@ bool Signer::verify(const PublicKey& pubKey, const Data& data, const Data& signa } // canonical check for FIO, both R and S lenght is 32 -int Signer::isCanonical(uint8_t by, uint8_t sig[64]) { +int Signer::isCanonical([[maybe_unused]] uint8_t by, uint8_t sig[64]) { return !(sig[0] & 0x80) && !(sig[0] == 0 && !(sig[1] & 0x80)) && !(sig[32] & 0x80) diff --git a/src/FIO/Signer.h b/src/FIO/Signer.h index 7604ca4f953..7e0800d71d9 100644 --- a/src/FIO/Signer.h +++ b/src/FIO/Signer.h @@ -7,7 +7,7 @@ #pragma once #include "Address.h" -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" #include "../PublicKey.h" #include "../proto/FIO.pb.h" @@ -30,7 +30,7 @@ class Signer { static Data signData(const PrivateKey& privKey, const Data& data); /// Used internally, encode signature to base58 with prefix. Ex.: "SIG_K1_K54CA1jmhgWrSdvrNrkokPyvqh7dwsSoQHNU9xgD3Ezf6cJySzhKeUubVRqmpYdnjoP1DM6SorroVAgrCu3qqvJ9coAQ6u" - static std::string signatureToBsase58(const Data& sig); + static std::string signatureToBase58(const Data& sig); /// Verify a signature, used in testing static bool verify(const PublicKey& pubKey, const Data& data, const Data& signature); diff --git a/src/FIO/Transaction.h b/src/FIO/Transaction.h index 67f70939ff7..1bfa26369b2 100644 --- a/src/FIO/Transaction.h +++ b/src/FIO/Transaction.h @@ -7,7 +7,7 @@ #pragma once #include "Action.h" -#include "../Data.h" +#include "Data.h" #include diff --git a/src/FIO/TransactionBuilder.cpp b/src/FIO/TransactionBuilder.cpp index c0203ec9911..1c5fa2b4d8b 100644 --- a/src/FIO/TransactionBuilder.cpp +++ b/src/FIO/TransactionBuilder.cpp @@ -240,7 +240,7 @@ string TransactionBuilder::signAdnBuildTx(const Data& chainId, const Data& packe Data sigBuf(chainId); append(sigBuf, packedTx); append(sigBuf, TW::Data(32)); // context_free - string signature = Signer::signatureToBsase58(Signer::signData(privateKey, sigBuf)); + string signature = Signer::signatureToBase58(Signer::signData(privateKey, sigBuf)); // Build json json tx = { diff --git a/src/FIO/TransactionBuilder.h b/src/FIO/TransactionBuilder.h index eede5d91ca2..607e0d48963 100644 --- a/src/FIO/TransactionBuilder.h +++ b/src/FIO/TransactionBuilder.h @@ -10,7 +10,7 @@ #include "Address.h" #include "../proto/FIO.pb.h" -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" #include diff --git a/src/Filecoin/Address.cpp b/src/Filecoin/Address.cpp index 28ca9daf774..56fc2b9963b 100644 --- a/src/Filecoin/Address.cpp +++ b/src/Filecoin/Address.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust. +// Copyright © 2017-2022 Trust. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the @@ -7,12 +7,11 @@ #include "Address.h" #include +#include #include "../Base32.h" -#include "../Data.h" -using namespace TW; -using namespace TW::Filecoin; +namespace TW::Filecoin { static const char BASE32_ALPHABET_FILECOIN[] = "abcdefghijklmnopqrstuvwxyz234567"; static constexpr size_t checksumSize = 4; @@ -32,7 +31,7 @@ bool Address::isValid(const Data& data) { if (data.size() == 11 && data[10] > 0x01) { return false; } - int i; + std::size_t i; for (i = 1; i < data.size(); i++) { if ((data[i] & 0x80) == 0) { break; @@ -47,7 +46,7 @@ bool Address::isValid(const Data& data) { static bool isValidID(const std::string& string) { if (string.length() > 22) return false; - for (int i = 2; i < string.length(); i++) { + for (auto i = 2ul; i < string.length(); i++) { if (string[i] < '0' || string[i] > '9') { return false; } @@ -150,12 +149,12 @@ std::string Address::string() const { if (type() == Type::ID) { uint64_t id = 0; unsigned shift = 0; - for (int i = 1; i < bytes.size(); i++) { - if (bytes[i] < 0x80) { - id |= bytes[i] << shift; + for (auto i = 1ul; i < bytes.size(); i++) { + if (bytes[i] <= SCHAR_MAX) { + id |= static_cast(bytes[i]) << shift; break; } else { - id |= ((uint64_t)(bytes[i] & 0x7F)) << shift; + id |= static_cast(bytes[i] & SCHAR_MAX) << shift; shift += 7; } } @@ -178,3 +177,5 @@ std::string Address::string() const { return s; } + +} // namespace TW::Filecoin diff --git a/src/Filecoin/Entry.cpp b/src/Filecoin/Entry.cpp index 39db5385ded..5ab6065a3ca 100644 --- a/src/Filecoin/Entry.cpp +++ b/src/Filecoin/Entry.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,25 +9,26 @@ #include "Address.h" #include "Signer.h" -using namespace TW::Filecoin; -using namespace std; +namespace TW::Filecoin { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { +std::string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { return Signer::signJSON(json, key); } + +} // namespace TW::Filecoin diff --git a/src/Filecoin/Entry.h b/src/Filecoin/Entry.h index f972089aa03..a989db0676e 100644 --- a/src/Filecoin/Entry.h +++ b/src/Filecoin/Entry.h @@ -13,16 +13,15 @@ namespace TW::Filecoin { /// Entry point for implementation of Filecoin coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific /// includes in this file -class Entry : public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeFilecoin}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual bool supportsJSONSigning() const { return true; } - virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool supportsJSONSigning() const { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; }; } // namespace TW::Filecoin diff --git a/src/Filecoin/Signer.cpp b/src/Filecoin/Signer.cpp index 88f48a7e740..67676a9eeb7 100644 --- a/src/Filecoin/Signer.cpp +++ b/src/Filecoin/Signer.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust. +// Copyright © 2017-2022 Trust. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the @@ -8,8 +8,7 @@ #include "HexCoding.h" #include -using namespace TW; -using namespace TW::Filecoin; +namespace TW::Filecoin { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { // Load private key and transaction from Protobuf input. @@ -24,8 +23,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { /* value */ load(input.value()), /* gasLimit */ input.gas_limit(), /* gasFeeCap */ load(input.gas_fee_cap()), - /* gasPremium */ load(input.gas_premium()) - ); + /* gasPremium */ load(input.gas_premium())); // Sign transaction. auto signature = sign(key, transaction); @@ -50,3 +48,5 @@ std::string Signer::signJSON(const std::string& json, const Data& key) { auto output = Signer::sign(input); return output.json(); } + +} // namespace TW::Filecoin diff --git a/src/Filecoin/Signer.h b/src/Filecoin/Signer.h index 3d20617bcb4..b266e95e120 100644 --- a/src/Filecoin/Signer.h +++ b/src/Filecoin/Signer.h @@ -8,7 +8,7 @@ #include "Transaction.h" -#include "../Data.h" +#include "Data.h" #include "../Hash.h" #include "../PrivateKey.h" #include "../proto/Filecoin.pb.h" @@ -31,8 +31,3 @@ class Signer { }; } // namespace TW::Filecoin - -/// Wrapper for C interface. -struct TWFilecoinSigner { - TW::Filecoin::Signer impl; -}; diff --git a/src/Filecoin/Transaction.cpp b/src/Filecoin/Transaction.cpp index ff9a2960fcc..311f71ecf97 100644 --- a/src/Filecoin/Transaction.cpp +++ b/src/Filecoin/Transaction.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust. +// Copyright © 2017-2022 Trust. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the @@ -8,12 +8,12 @@ #include #include "Base64.h" +namespace TW::Filecoin { + using json = nlohmann::json; -using namespace TW; -using namespace TW::Filecoin; // encodeBigInt encodes a Filecoin BigInt to CBOR. -Data TW::Filecoin::encodeBigInt(const uint256_t& value) { +Data encodeBigInt(const uint256_t& value) { if (value.is_zero()) { return {}; } @@ -61,6 +61,7 @@ Data Transaction::cid() const { return cid; } std::string Transaction::serialize(Data& signature) const { + // clang-format off json tx = { {"Message", json{ {"To", to.string()}, @@ -78,5 +79,8 @@ std::string Transaction::serialize(Data& signature) const { } }, }; + // clang-format on return tx.dump(); } + +} // namespace TW::Filecoin diff --git a/src/Generated/.clang-tidy b/src/Generated/.clang-tidy new file mode 100644 index 00000000000..2c22f7387dd --- /dev/null +++ b/src/Generated/.clang-tidy @@ -0,0 +1,6 @@ +--- +InheritParentConfig: false +Checks: '-*,misc-definitions-in-headers' +CheckOptions: + - { key: HeaderFileExtensions, value: "x" } +... diff --git a/src/Groestlcoin/Address.cpp b/src/Groestlcoin/Address.cpp index bf77a0f7ddb..290b097ad69 100644 --- a/src/Groestlcoin/Address.cpp +++ b/src/Groestlcoin/Address.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,12 +9,10 @@ #include "../Base58.h" #include -#include - -using namespace TW::Groestlcoin; +namespace TW::Groestlcoin { bool Address::isValid(const std::string& string) { - const auto decoded = Base58::bitcoin.decodeCheck(string, Hash::groestl512d); + const auto decoded = Base58::bitcoin.decodeCheck(string, Hash::HasherGroestl512d); if (decoded.size() != Address::size) { return false; } @@ -23,7 +21,7 @@ bool Address::isValid(const std::string& string) { } bool Address::isValid(const std::string& string, const std::vector& validPrefixes) { - const auto decoded = Base58::bitcoin.decodeCheck(string, Hash::groestl512d); + const auto decoded = Base58::bitcoin.decodeCheck(string, Hash::HasherGroestl512d); if (decoded.size() != Address::size) { return false; } @@ -34,7 +32,7 @@ bool Address::isValid(const std::string& string, const std::vector& validP } Address::Address(const std::string& string) { - const auto decoded = Base58::bitcoin.decodeCheck(string, Hash::groestl512d); + const auto decoded = Base58::bitcoin.decodeCheck(string, Hash::HasherGroestl512d); if (decoded.size() != Address::size) { throw std::invalid_argument("Invalid address string"); } @@ -58,5 +56,7 @@ Address::Address(const PublicKey& publicKey, uint8_t prefix) { } std::string Address::string() const { - return Base58::bitcoin.encodeCheck(bytes, Hash::groestl512d); + return Base58::bitcoin.encodeCheck(bytes, Hash::HasherGroestl512d); } + +} // namespace TW::Groestlcoin diff --git a/src/Groestlcoin/Address.h b/src/Groestlcoin/Address.h index 2084cea1b9f..d29c37d4e68 100644 --- a/src/Groestlcoin/Address.h +++ b/src/Groestlcoin/Address.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include diff --git a/src/Groestlcoin/Entry.cpp b/src/Groestlcoin/Entry.cpp index 20878ff6eef..ae12284620a 100644 --- a/src/Groestlcoin/Entry.cpp +++ b/src/Groestlcoin/Entry.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -10,22 +10,22 @@ #include "../Bitcoin/SegwitAddress.h" #include "Signer.h" -using namespace TW::Groestlcoin; -using namespace std; +namespace TW::Groestlcoin { -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const { - return TW::Bitcoin::SegwitAddress::isValid(address, hrp) - || Address::isValid(address, {p2pkh, p2sh}); +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const { + return TW::Bitcoin::SegwitAddress::isValid(address, hrp) || Address::isValid(address, {p2pkh, p2sh}); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const { - return TW::Bitcoin::SegwitAddress(publicKey, 0, hrp).string(); +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TW::byte p2pkh, const char* hrp) const { + return TW::Bitcoin::SegwitAddress(publicKey, hrp).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } -void Entry::plan(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::plan([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { planTemplate(dataIn, dataOut); } + +} // namespace TW::Groestlcoin diff --git a/src/Groestlcoin/Entry.h b/src/Groestlcoin/Entry.h index 33470bbc05d..069cc75074e 100644 --- a/src/Groestlcoin/Entry.h +++ b/src/Groestlcoin/Entry.h @@ -12,13 +12,12 @@ namespace TW::Groestlcoin { /// Groestlcoin entry dispatcher. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeGroestlcoin}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::Groestlcoin diff --git a/src/Groestlcoin/Signer.cpp b/src/Groestlcoin/Signer.cpp index d395fadc901..7b87e290ff6 100644 --- a/src/Groestlcoin/Signer.cpp +++ b/src/Groestlcoin/Signer.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -12,8 +12,7 @@ #include "HexCoding.h" #include "Transaction.h" -using namespace TW; -using namespace TW::Groestlcoin; +namespace TW::Groestlcoin { using TransactionBuilder = Bitcoin::TransactionBuilder; @@ -46,3 +45,5 @@ SigningOutput Signer::sign(const SigningInput& input) noexcept { output.set_transaction_id(hex(txHash)); return output; } + +} // namespace TW::Groestlcoin diff --git a/src/Groestlcoin/Transaction.h b/src/Groestlcoin/Transaction.h index 6b3929a5956..10e678c7de3 100644 --- a/src/Groestlcoin/Transaction.h +++ b/src/Groestlcoin/Transaction.h @@ -11,9 +11,9 @@ namespace TW::Groestlcoin { struct Transaction : public Bitcoin::Transaction { - Transaction() : Bitcoin::Transaction(1, 0, static_cast(Hash::sha256)) {} + Transaction() : Bitcoin::Transaction(1, 0, Hash::HasherSha256) {} Transaction(int32_t version, uint32_t lockTime = 0) : - Bitcoin::Transaction(version, lockTime, static_cast(Hash::sha256)) {} + Bitcoin::Transaction(version, lockTime, Hash::HasherSha256) {} }; } // namespace TW::Groestlcoin diff --git a/src/HDWallet.cpp b/src/HDWallet.cpp index b4229ad62ef..068de7f20e5 100644 --- a/src/HDWallet.cpp +++ b/src/HDWallet.cpp @@ -8,18 +8,22 @@ #include "Base58.h" #include "BinaryCoding.h" -#include "Bitcoin/SegwitAddress.h" #include "Bitcoin/CashAddress.h" +#include "Bitcoin/SegwitAddress.h" #include "Coin.h" #include "Mnemonic.h" +#include "memory/memzero_wrapper.h" #include #include -#include +#include + #include #include +#include #include +#include #include #include @@ -29,9 +33,9 @@ using namespace TW; namespace { -uint32_t fingerprint(HDNode *node, Hash::Hasher hasher); -std::string serialize(const HDNode *node, uint32_t fingerprint, uint32_t version, bool use_public, Hash::Hasher hasher); -bool deserialize(const std::string& extended, TWCurve curve, Hash::Hasher hasher, HDNode *node); +uint32_t fingerprint(HDNode* node, Hash::Hasher hasher); +std::string serialize(const HDNode* node, uint32_t fingerprint, uint32_t version, bool use_public, Hash::Hasher hasher); +bool deserialize(const std::string& extended, TWCurve curve, Hash::Hasher hasher, HDNode* node); HDNode getNode(const HDWallet& wallet, TWCurve curve, const DerivationPath& derivationPath); HDNode getMasterNode(const HDWallet& wallet, TWCurve curve); @@ -43,16 +47,18 @@ const int MnemonicBufLength = Mnemonic::MaxWords * (BIP39_MAX_WORD_LENGTH + 3) + HDWallet::HDWallet(int strength, const std::string& passphrase) : passphrase(passphrase) { char buf[MnemonicBufLength]; + //win if (!random_init()) { throw std::runtime_error("Failed to initialize random number generator"); } const char* mnemonic_chars = mnemonic_generate(strength, buf, MnemonicBufLength); if (mnemonic_chars == nullptr) { + //win random_release(); throw std::invalid_argument("Invalid strength"); } mnemonic = mnemonic_chars; - memzero(buf, MnemonicBufLength); + TW::memzero(buf, MnemonicBufLength); updateSeedAndEntropy(); } @@ -62,6 +68,7 @@ HDWallet::HDWallet(const std::string& mnemonic, const std::string& passphrase, c (check && !Mnemonic::isValid(mnemonic))) { throw std::invalid_argument("Invalid mnemonic"); } + //win if (!random_init()) { throw std::runtime_error("Failed to initialize random number generator"); } @@ -76,7 +83,8 @@ HDWallet::HDWallet(const Data& entropy, const std::string& passphrase) throw std::invalid_argument("Invalid mnemonic data"); } mnemonic = mnemonic_chars; - memzero(buf, MnemonicBufLength); + TW::memzero(buf, MnemonicBufLength); + //win if (!random_init()) { throw std::runtime_error("Failed to initialize random number generator"); } @@ -84,7 +92,7 @@ HDWallet::HDWallet(const Data& entropy, const std::string& passphrase) } HDWallet::~HDWallet() { - random_release(); + random_release();//win std::fill(seed.begin(), seed.end(), 0); std::fill(mnemonic.begin(), mnemonic.end(), 0); std::fill(passphrase.begin(), passphrase.end(), 0); @@ -107,34 +115,64 @@ void HDWallet::updateSeedAndEntropy(bool check) { PrivateKey HDWallet::getMasterKey(TWCurve curve) const { auto node = getMasterNode(*this, curve); - auto data = Data(node.private_key, node.private_key + PrivateKey::size); + auto data = Data(node.private_key, node.private_key + PrivateKey::_size); return PrivateKey(data); } PrivateKey HDWallet::getMasterKeyExtension(TWCurve curve) const { auto node = getMasterNode(*this, curve); - auto data = Data(node.private_key_extension, node.private_key_extension + PrivateKey::size); + auto data = Data(node.private_key_extension, node.private_key_extension + PrivateKey::_size); return PrivateKey(data); } +PrivateKey HDWallet::getKey(TWCoinType coin, TWDerivation derivation) const { + const auto path = TW::derivationPath(coin, derivation); + return getKey(coin, path); +} + +DerivationPath HDWallet::cardanoStakingDerivationPath(const DerivationPath& path) { + DerivationPath stakingPath = path; + stakingPath.indices[3].value = 2; + stakingPath.indices[4].value = 0; + return stakingPath; +} + PrivateKey HDWallet::getKey(TWCoinType coin, const DerivationPath& derivationPath) const { const auto curve = TWCoinTypeCurve(coin); - const auto privateKeyType = getPrivateKeyType(curve); + return getKeyByCurve(curve, derivationPath); +} + +PrivateKey HDWallet::getKeyByCurve(TWCurve curve, const DerivationPath& derivationPath) const { + const auto privateKeyType = PrivateKey::getType(curve); auto node = getNode(*this, curve, derivationPath); switch (privateKeyType) { - case PrivateKeyTypeExtended96: - { - auto pkData = Data(node.private_key, node.private_key + PrivateKey::size); - auto extData = Data(node.private_key_extension, node.private_key_extension + PrivateKey::size); - auto chainCode = Data(node.chain_code, node.chain_code + PrivateKey::size); - return PrivateKey(pkData, extData, chainCode); - } - - case PrivateKeyTypeDefault32: - default: - // default path - auto data = Data(node.private_key, node.private_key + PrivateKey::size); - return PrivateKey(data); + case TWPrivateKeyTypeCardano: { + if (derivationPath.indices.size() < 4 || derivationPath.indices[3].value > 1) { + // invalid derivation path + return PrivateKey(Data(PrivateKey::cardanoKeySize)); + } + const DerivationPath stakingPath = cardanoStakingDerivationPath(derivationPath); + + auto pkData = Data(node.private_key, node.private_key + PrivateKey::_size); + auto extData = Data(node.private_key_extension, node.private_key_extension + PrivateKey::_size); + auto chainCode = Data(node.chain_code, node.chain_code + PrivateKey::_size); + + // repeat with staking path + const auto node2 = getNode(*this, curve, stakingPath); + auto pkData2 = Data(node2.private_key, node2.private_key + PrivateKey::_size); + auto extData2 = Data(node2.private_key_extension, node2.private_key_extension + PrivateKey::_size); + auto chainCode2 = Data(node2.chain_code, node2.chain_code + PrivateKey::_size); + + TW::memzero(&node); + return PrivateKey(pkData, extData, chainCode, pkData2, extData2, chainCode2); + } + + case TWPrivateKeyTypeDefault: + default: + // default path + auto data = Data(node.private_key, node.private_key + PrivateKey::_size); + TW::memzero(&node); + return PrivateKey(data); } } @@ -145,33 +183,39 @@ std::string HDWallet::getRootKey(TWCoinType coin, TWHDVersion version) const { } std::string HDWallet::deriveAddress(TWCoinType coin) const { - const auto derivationPath = TW::derivationPath(coin); - return TW::deriveAddress(coin, getKey(coin, derivationPath)); + return deriveAddress(coin, TWDerivationDefault); +} + +std::string HDWallet::deriveAddress(TWCoinType coin, TWDerivation derivation) const { + const auto derivationPath = TW::derivationPath(coin, derivation); + return TW::deriveAddress(coin, getKey(coin, derivationPath), derivation); } -std::string HDWallet::getExtendedPrivateKey(TWPurpose purpose, TWCoinType coin, TWHDVersion version) const { +std::string HDWallet::getExtendedPrivateKeyAccount(TWPurpose purpose, TWCoinType coin, TWDerivation derivation, TWHDVersion version, uint32_t account) const { if (version == TWHDVersionNone) { return ""; } - + const auto curve = TWCoinTypeCurve(coin); - auto derivationPath = TW::DerivationPath({DerivationPathIndex(purpose, true), DerivationPathIndex(coin, true)}); + const auto path = TW::derivationPath(coin, derivation); + auto derivationPath = DerivationPath({DerivationPathIndex(purpose, true), DerivationPathIndex(path.coin(), true)}); auto node = getNode(*this, curve, derivationPath); auto fingerprintValue = fingerprint(&node, publicKeyHasher(coin)); - hdnode_private_ckd(&node, 0x80000000); + hdnode_private_ckd(&node, account + 0x80000000); return serialize(&node, fingerprintValue, version, false, base58Hasher(coin)); } -std::string HDWallet::getExtendedPublicKey(TWPurpose purpose, TWCoinType coin, TWHDVersion version) const { +std::string HDWallet::getExtendedPublicKeyAccount(TWPurpose purpose, TWCoinType coin, TWDerivation derivation, TWHDVersion version, uint32_t account) const { if (version == TWHDVersionNone) { return ""; } - + const auto curve = TWCoinTypeCurve(coin); - auto derivationPath = TW::DerivationPath({DerivationPathIndex(purpose, true), DerivationPathIndex(coin, true)}); + const auto path = TW::derivationPath(coin, derivation); + auto derivationPath = DerivationPath({DerivationPathIndex(purpose, true), DerivationPathIndex(path.coin(), true)}); auto node = getNode(*this, curve, derivationPath); auto fingerprintValue = fingerprint(&node, publicKeyHasher(coin)); - hdnode_private_ckd(&node, 0x80000000); + hdnode_private_ckd(&node, account + 0x80000000); hdnode_fill_public_key(&node); return serialize(&node, fingerprintValue, version, true, base58Hasher(coin)); } @@ -192,7 +236,7 @@ std::optional HDWallet::getPublicKeyFromExtended(const std::string& e hdnode_fill_public_key(&node); // These public key type are not applicable. Handled above, as node.curve->params is null - assert(curve != TWCurveED25519 && curve != TWCurveED25519Blake2bNano && curve != TWCurveED25519Extended && curve != TWCurveCurve25519); + assert(curve != TWCurveED25519 && curve != TWCurveED25519Blake2bNano && curve != TWCurveED25519ExtendedCardano && curve != TWCurveCurve25519); TWPublicKeyType keyType = TW::publicKeyType(coin); if (curve == TWCurveSECP256k1) { auto pubkey = PublicKey(Data(node.public_key, node.public_key + 33), TWPublicKeyTypeSECP256k1); @@ -226,26 +270,15 @@ std::optional HDWallet::getPrivateKeyFromExtended(const std::string& return PrivateKey(Data(node.private_key, node.private_key + 32)); } -HDWallet::PrivateKeyType HDWallet::getPrivateKeyType(TWCurve curve) { - switch (curve) { - case TWCurve::TWCurveED25519Extended: - // used by Cardano - return PrivateKeyTypeExtended96; - default: - // default - return PrivateKeyTypeDefault32; - } -} - namespace { -uint32_t fingerprint(HDNode *node, Hash::Hasher hasher) { +uint32_t fingerprint(HDNode* node, Hash::Hasher hasher) { hdnode_fill_public_key(node); - auto digest = hasher(node->public_key, 33); - return ((uint32_t) digest[0] << 24) + (digest[1] << 16) + (digest[2] << 8) + digest[3]; + auto digest = Hash::hash(hasher, node->public_key, 33); + return ((uint32_t)digest[0] << 24) + (digest[1] << 16) + (digest[2] << 8) + digest[3]; } -std::string serialize(const HDNode *node, uint32_t fingerprint, uint32_t version, bool use_public, Hash::Hasher hasher) { +std::string serialize(const HDNode* node, uint32_t fingerprint, uint32_t version, bool use_public, Hash::Hasher hasher) { Data node_data; node_data.reserve(78); @@ -265,7 +298,7 @@ std::string serialize(const HDNode *node, uint32_t fingerprint, uint32_t version } bool deserialize(const std::string& extended, TWCurve curve, Hash::Hasher hasher, HDNode* node) { - memset(node, 0, sizeof(HDNode)); + TW::memzero(node); const char* curveNameStr = curveName(curve); if (curveNameStr == nullptr || ::strlen(curveNameStr) == 0) { return false; @@ -296,35 +329,40 @@ bool deserialize(const std::string& extended, TWCurve curve, Hash::Hasher hasher } HDNode getNode(const HDWallet& wallet, TWCurve curve, const DerivationPath& derivationPath) { - const auto privateKeyType = HDWallet::getPrivateKeyType(curve); + const auto privateKeyType = PrivateKey::getType(curve); auto node = getMasterNode(wallet, curve); for (auto& index : derivationPath.indices) { switch (privateKeyType) { - case HDWallet::PrivateKeyTypeExtended96: - // special handling for extended - hdnode_private_ckd_cardano(&node, index.derivationIndex()); - break; - case HDWallet::PrivateKeyTypeDefault32: - default: - hdnode_private_ckd(&node, index.derivationIndex()); - break; + case TWPrivateKeyTypeCardano: + hdnode_private_ckd_cardano(&node, index.derivationIndex()); + break; + case TWPrivateKeyTypeDefault: + default: + hdnode_private_ckd(&node, index.derivationIndex()); + break; } } return node; } HDNode getMasterNode(const HDWallet& wallet, TWCurve curve) { - const auto privateKeyType = HDWallet::getPrivateKeyType(curve); - auto node = HDNode(); + const auto privateKeyType = PrivateKey::getType(curve); + HDNode node; switch (privateKeyType) { - case HDWallet::PrivateKeyTypeExtended96: - // special handling for extended, use entropy (not seed) - hdnode_from_entropy_cardano_icarus((const uint8_t*)"", 0, wallet.getEntropy().data(), (int)wallet.getEntropy().size(), &node); - break; - case HDWallet::PrivateKeyTypeDefault32: - default: - hdnode_from_seed(wallet.getSeed().data(), HDWallet::seedSize, curveName(curve), &node); - break; + case TWPrivateKeyTypeCardano: { + // Derives the root Cardano HDNode from a passphrase and the entropy encoded in + // a BIP-0039 mnemonic using the Icarus derivation (V2) scheme + const auto entropy = wallet.getEntropy(); + uint8_t secret[CARDANO_SECRET_LENGTH]; + secret_from_entropy_cardano_icarus((const uint8_t*)"", 0, entropy.data(), int(entropy.size()), secret, nullptr); + hdnode_from_secret_cardano(secret, &node); + TW::memzero(secret, CARDANO_SECRET_LENGTH); + break; + } + case TWPrivateKeyTypeDefault: + default: + hdnode_from_seed(wallet.getSeed().data(), HDWallet::seedSize, curveName(curve), &node); + break; } return node; } @@ -337,7 +375,7 @@ const char* curveName(TWCurve curve) { return ED25519_NAME; case TWCurveED25519Blake2bNano: return ED25519_BLAKE2B_NANO_NAME; - case TWCurveED25519Extended: + case TWCurveED25519ExtendedCardano: return ED25519_CARDANO_NAME; case TWCurveNIST256p1: return NIST256P1_NAME; diff --git a/src/HDWallet.h b/src/HDWallet.h index 9d683d0ffd0..0579591c82e 100644 --- a/src/HDWallet.h +++ b/src/HDWallet.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -74,17 +75,46 @@ class HDWallet { /// Returns the master private key extension (32 byte). PrivateKey getMasterKeyExtension(TWCurve curve) const; + /// Returns the private key with the given derivation. + PrivateKey getKey(const TWCoinType coin, TWDerivation derivation) const; + /// Returns the private key at the given derivation path. PrivateKey getKey(const TWCoinType coin, const DerivationPath& derivationPath) const; - /// Derives the address for a coin. + /// Returns the private key at the given derivation path and curve. + PrivateKey getKeyByCurve(TWCurve curve, const DerivationPath& derivationPath) const; + + /// Derives the address for a coin (default derivation). std::string deriveAddress(TWCoinType coin) const; - /// Returns the extended private key. - std::string getExtendedPrivateKey(TWPurpose purpose, TWCoinType coin, TWHDVersion version) const; + /// Derives the address for a coin with given derivation. + std::string deriveAddress(TWCoinType coin, TWDerivation derivation) const; + + /// Returns the extended private key for default 0 account with the given derivation. + std::string getExtendedPrivateKeyDerivation(TWPurpose purpose, TWCoinType coin, TWDerivation derivation, TWHDVersion version) const { + return getExtendedPrivateKeyAccount(purpose, coin, derivation, version, 0); + } + + /// Returns the extended public key for default 0 account with the given derivation. + std::string getExtendedPublicKeyDerivation(TWPurpose purpose, TWCoinType coin, TWDerivation derivation, TWHDVersion version) const { + return getExtendedPublicKeyAccount(purpose, coin, derivation, version, 0); + } + + /// Returns the extended private key for default 0 account; derivation path used is "m/purpose'/coin'/0'". + std::string getExtendedPrivateKey(TWPurpose purpose, TWCoinType coin, TWHDVersion version) const { + return getExtendedPrivateKeyAccount(purpose, coin, TWDerivationDefault, version, 0); + } - /// Returns the extended public key. - std::string getExtendedPublicKey(TWPurpose purpose, TWCoinType coin, TWHDVersion version) const; + /// Returns the extended public key for default 0 account; derivation path used is "m/purpose'/coin'/0'". + std::string getExtendedPublicKey(TWPurpose purpose, TWCoinType coin, TWHDVersion version) const { + return getExtendedPublicKeyAccount(purpose, coin, TWDerivationDefault, version, 0); + } + + /// Returns the extended private key for a custom account; derivation path used is "m/purpose'/coin'/account'". + std::string getExtendedPrivateKeyAccount(TWPurpose purpose, TWCoinType coin, TWDerivation derivation, TWHDVersion version, uint32_t account) const; + + /// Returns the extended public key for a custom account; derivation path used is "m/purpose'/coin'/account'". + std::string getExtendedPublicKeyAccount(TWPurpose purpose, TWCoinType coin, TWDerivation derivation, TWHDVersion version, uint32_t account) const; /// Returns the BIP32 Root Key (private) std::string getRootKey(TWCoinType coin, TWHDVersion version) const; @@ -95,18 +125,11 @@ class HDWallet { /// Computes the private key from an extended private key representation. static std::optional getPrivateKeyFromExtended(const std::string& extended, TWCoinType coin, const DerivationPath& path); - public: - // Private key type (later could be moved out of HDWallet) - enum PrivateKeyType { - PrivateKeyTypeDefault32 = 0, // 32-byte private key - PrivateKeyTypeExtended96 = 1, // 3*32-byte extended private key - }; - - // obtain privateKeyType used by the coin/curve - static PrivateKeyType getPrivateKeyType(TWCurve curve); - private: void updateSeedAndEntropy(bool check = true); + + // For Cardano, derive 2nd staking derivation path from the primary one + static DerivationPath cardanoStakingDerivationPath(const DerivationPath& path); }; } // namespace TW diff --git a/src/Harmony/Address.cpp b/src/Harmony/Address.cpp index 69a68fab06a..e4be72aa81f 100644 --- a/src/Harmony/Address.cpp +++ b/src/Harmony/Address.cpp @@ -10,7 +10,8 @@ #include -using namespace TW::Harmony; +namespace TW::Harmony { const std::string Address::hrp = HRP_HARMONY; +} diff --git a/src/Harmony/Address.h b/src/Harmony/Address.h index a36d8507ec1..da4748f7a15 100644 --- a/src/Harmony/Address.h +++ b/src/Harmony/Address.h @@ -30,7 +30,7 @@ class Address: public Bech32Address { } /// Initializes an address with a public key. - Address(const PublicKey& publicKey) : Bech32Address(hrp, HASHER_SHA3K, publicKey) { + Address(const PublicKey& publicKey) : Bech32Address(hrp, Hash::HasherKeccak256, publicKey) { if (publicKey.type != TWPublicKeyTypeSECP256k1Extended) { throw std::invalid_argument("address may only be an extended SECP256k1 public key"); } diff --git a/src/Harmony/Entry.cpp b/src/Harmony/Entry.cpp index 3f926546bbb..ad652105739 100644 --- a/src/Harmony/Entry.cpp +++ b/src/Harmony/Entry.cpp @@ -9,23 +9,35 @@ #include "Address.h" #include "Signer.h" -using namespace TW::Harmony; +using namespace TW; using namespace std; +namespace TW::Harmony { + // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + Address addr; + if (!Address::decode(address, addr)) { + return Data(); + } + return addr.getKeyHash(); +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { +string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { return Signer::signJSON(json, key); } + +} // namespace TW::Harmony diff --git a/src/Harmony/Entry.h b/src/Harmony/Entry.h index ba288c5371c..1feefb3ca70 100644 --- a/src/Harmony/Entry.h +++ b/src/Harmony/Entry.h @@ -12,14 +12,14 @@ namespace TW::Harmony { /// Entry point for implementation of Harmony coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeHarmony}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual bool supportsJSONSigning() const { return true; } - virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool supportsJSONSigning() const { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; }; } // namespace TW::Harmony diff --git a/src/Harmony/Signer.cpp b/src/Harmony/Signer.cpp index 0cfd1abe36c..1c7d2c4a803 100644 --- a/src/Harmony/Signer.cpp +++ b/src/Harmony/Signer.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,11 +9,9 @@ #include "../HexCoding.h" #include +namespace TW::Harmony { -using namespace TW; -using namespace TW::Harmony; - -std::tuple Signer::values(const uint256_t &chainID, +std::tuple Signer::values(const uint256_t& chainID, const Data& signature) noexcept { auto r = load(Data(signature.begin(), signature.begin() + 32)); auto s = load(Data(signature.begin() + 32, signature.begin() + 64)); @@ -23,18 +21,18 @@ std::tuple Signer::values(const uint256_t &chai } std::tuple -Signer::sign(const uint256_t &chainID, const PrivateKey &privateKey, const Data& hash) noexcept { +Signer::sign(const uint256_t& chainID, const PrivateKey& privateKey, const Data& hash) noexcept { auto signature = privateKey.sign(hash, TWCurveSECP256k1); return values(chainID, signature); } template -Proto::SigningOutput Signer::prepareOutput(const Data& encoded, const T &transaction) noexcept { +Proto::SigningOutput Signer::prepareOutput(const Data& encoded, const T& transaction) noexcept { auto protoOutput = Proto::SigningOutput(); - auto v = store(transaction.v); - auto r = store(transaction.r); - auto s = store(transaction.s); + auto v = store(transaction.v, 1); + auto r = store(transaction.r, 32); + auto s = store(transaction.s, 32); protoOutput.set_encoded(encoded.data(), encoded.size()); protoOutput.set_v(v.data(), v.size()); @@ -44,7 +42,7 @@ Proto::SigningOutput Signer::prepareOutput(const Data& encoded, const T &transac return protoOutput; } -Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { if (input.has_transaction_message()) { return signTransaction(input); } @@ -76,7 +74,7 @@ std::string Signer::signJSON(const std::string& json, const Data& key) { return hex(Signer::sign(input).encoded()); } -Proto::SigningOutput Signer::signTransaction(const Proto::SigningInput &input) noexcept { +Proto::SigningOutput Signer::signTransaction(const Proto::SigningInput& input) noexcept { auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); Address toAddr; if (!Address::decode(input.transaction_message().to_address(), toAddr)) { @@ -104,7 +102,7 @@ Proto::SigningOutput Signer::signTransaction(const Proto::SigningInput &input) n return prepareOutput(encoded, transaction); } -Proto::SigningOutput Signer::signCreateValidator(const Proto::SigningInput &input) noexcept { +Proto::SigningOutput Signer::signCreateValidator(const Proto::SigningInput& input) noexcept { auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); auto description = Description( /* name */ input.staking_message().create_validator_message().description().name(), @@ -186,7 +184,7 @@ Proto::SigningOutput Signer::signCreateValidator(const Proto::SigningInput &inpu return prepareOutput>(encoded, stakingTx); } -Proto::SigningOutput Signer::signEditValidator(const Proto::SigningInput &input) noexcept { +Proto::SigningOutput Signer::signEditValidator(const Proto::SigningInput& input) noexcept { auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); auto description = Description( @@ -245,7 +243,7 @@ Proto::SigningOutput Signer::signEditValidator(const Proto::SigningInput &input) return prepareOutput>(encoded, stakingTx); } -Proto::SigningOutput Signer::signDelegate(const Proto::SigningInput &input) noexcept { +Proto::SigningOutput Signer::signDelegate(const Proto::SigningInput& input) noexcept { auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); Address delegatorAddr; @@ -275,7 +273,7 @@ Proto::SigningOutput Signer::signDelegate(const Proto::SigningInput &input) noex return prepareOutput>(encoded, stakingTx); } -Proto::SigningOutput Signer::signUndelegate(const Proto::SigningInput &input) noexcept { +Proto::SigningOutput Signer::signUndelegate(const Proto::SigningInput& input) noexcept { auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); Address delegatorAddr; @@ -305,7 +303,7 @@ Proto::SigningOutput Signer::signUndelegate(const Proto::SigningInput &input) no return prepareOutput>(encoded, stakingTx); } -Proto::SigningOutput Signer::signCollectRewards(const Proto::SigningInput &input) noexcept { +Proto::SigningOutput Signer::signCollectRewards(const Proto::SigningInput& input) noexcept { auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); Address delegatorAddr; @@ -329,16 +327,16 @@ Proto::SigningOutput Signer::signCollectRewards(const Proto::SigningInput &input } template -void Signer::sign(const PrivateKey &privateKey, const Data& hash, T &transaction) const noexcept { +void Signer::sign(const PrivateKey& privateKey, const Data& hash, T& transaction) const noexcept { auto tuple = sign(chainID, privateKey, hash); transaction.r = std::get<0>(tuple); transaction.s = std::get<1>(tuple); transaction.v = std::get<2>(tuple); } -Data Signer::rlpNoHash(const Transaction &transaction, const bool include_vrs) const noexcept { +Data Signer::rlpNoHash(const Transaction& transaction, const bool include_vrs) const noexcept { auto encoded = Data(); - using namespace TW::Ethereum; + using RLP = TW::Ethereum::RLP; append(encoded, RLP::encode(transaction.nonce)); append(encoded, RLP::encode(transaction.gasPrice)); append(encoded, RLP::encode(transaction.gasLimit)); @@ -360,10 +358,11 @@ Data Signer::rlpNoHash(const Transaction &transaction, const bool include_vrs) c } template -Data Signer::rlpNoHash(const Staking &transaction, const bool include_vrs) const +Data Signer::rlpNoHash(const Staking& transaction, const bool include_vrs) const noexcept { auto encoded = Data(); - using namespace TW::Ethereum; + using RLP = TW::Ethereum::RLP; + append(encoded, RLP::encode(transaction.directive)); append(encoded, rlpNoHashDirective(transaction)); @@ -382,9 +381,9 @@ Data Signer::rlpNoHash(const Staking &transaction, const bool include return RLP::encodeList(encoded); } -Data Signer::rlpNoHashDirective(const Staking &transaction) const noexcept { +Data Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { auto encoded = Data(); - using namespace TW::Ethereum; + using RLP = TW::Ethereum::RLP; append(encoded, RLP::encode(transaction.stakeMsg.validatorAddress.getKeyHash())); @@ -433,9 +432,9 @@ Data Signer::rlpNoHashDirective(const Staking &transaction) con return RLP::encodeList(encoded); } -Data Signer::rlpNoHashDirective(const Staking &transaction) const noexcept { +Data Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { auto encoded = Data(); - using namespace TW::Ethereum; + using RLP = TW::Ethereum::RLP; append(encoded, RLP::encode(transaction.stakeMsg.validatorAddress.getKeyHash())); @@ -466,45 +465,47 @@ Data Signer::rlpNoHashDirective(const Staking &transaction) const return RLP::encodeList(encoded); } -Data Signer::rlpNoHashDirective(const Staking &transaction) const noexcept { +Data Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { auto encoded = Data(); - using namespace TW::Ethereum; + using RLP = TW::Ethereum::RLP; append(encoded, RLP::encode(transaction.stakeMsg.delegatorAddress.getKeyHash())); append(encoded, RLP::encode(transaction.stakeMsg.validatorAddress.getKeyHash())); append(encoded, RLP::encode(transaction.stakeMsg.amount)); return RLP::encodeList(encoded); } -Data Signer::rlpNoHashDirective(const Staking &transaction) const noexcept { +Data Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { auto encoded = Data(); - using namespace TW::Ethereum; + using RLP = TW::Ethereum::RLP; append(encoded, RLP::encode(transaction.stakeMsg.delegatorAddress.getKeyHash())); append(encoded, RLP::encode(transaction.stakeMsg.validatorAddress.getKeyHash())); append(encoded, RLP::encode(transaction.stakeMsg.amount)); return RLP::encodeList(encoded); } -Data Signer::rlpNoHashDirective(const Staking &transaction) const noexcept { +Data Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { auto encoded = Data(); - using namespace TW::Ethereum; + using RLP = TW::Ethereum::RLP; append(encoded, RLP::encode(transaction.stakeMsg.delegatorAddress.getKeyHash())); return RLP::encodeList(encoded); } -std::string Signer::txnAsRLPHex(Transaction &transaction) const noexcept { +std::string Signer::txnAsRLPHex(Transaction& transaction) const noexcept { return TW::hex(rlpNoHash(transaction, false)); } template -std::string Signer::txnAsRLPHex(Staking &transaction) const noexcept { +std::string Signer::txnAsRLPHex(Staking& transaction) const noexcept { return TW::hex(rlpNoHash(transaction, false)); } -Data Signer::hash(const Transaction &transaction) const noexcept { +Data Signer::hash(const Transaction& transaction) const noexcept { return Hash::keccak256(rlpNoHash(transaction, false)); } template -Data Signer::hash(const Staking &transaction) const noexcept { +Data Signer::hash(const Staking& transaction) const noexcept { return Hash::keccak256(rlpNoHash(transaction, false)); } + +} // namespace TW::Harmony diff --git a/src/Harmony/Signer.h b/src/Harmony/Signer.h index 722cf2a7558..dc0cac9ada9 100644 --- a/src/Harmony/Signer.h +++ b/src/Harmony/Signer.h @@ -8,7 +8,7 @@ #include "Staking.h" #include "Transaction.h" -#include "../Data.h" +#include "Data.h" #include "../Hash.h" #include "../PrivateKey.h" #include "../proto/Harmony.pb.h" @@ -62,13 +62,13 @@ class Signer { /// Signs a hash with the given private key for the given chain identifier. /// - /// @returns the r, s, and v values of the transaction signature + /// \returns the r, s, and v values of the transaction signature static std::tuple sign(const uint256_t &chainID, const PrivateKey &privateKey, const Data& hash) noexcept; /// R, S, and V values for the given chain identifier and signature. /// - /// @returns the r, s, and v values of the transaction signature + /// \returns the r, s, and v values of the transaction signature static std::tuple values(const uint256_t &chainID, const Data& signature) noexcept; @@ -97,8 +97,3 @@ class Signer { }; } // namespace TW::Harmony - -/// Wrapper for C interface. -struct TWHarmonySigner { - TW::Harmony::Signer impl; -}; diff --git a/src/Harmony/Staking.cpp b/src/Harmony/Staking.cpp index a3f6291bbd8..7e99697541b 100644 --- a/src/Harmony/Staking.cpp +++ b/src/Harmony/Staking.cpp @@ -3,7 +3,3 @@ // 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 "Staking.h" - -using namespace TW::Harmony; diff --git a/src/Harmony/Staking.h b/src/Harmony/Staking.h index 56c43735389..bf08cb0d199 100644 --- a/src/Harmony/Staking.h +++ b/src/Harmony/Staking.h @@ -37,14 +37,14 @@ class Staking { Staking(uint8_t directive, Directive stakeMsg, uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, uint256_t v, uint256_t r, uint256_t s) - : directive(move(directive)) - , stakeMsg(move(stakeMsg)) - , nonce(move(nonce)) - , gasPrice(move(gasPrice)) - , gasLimit(move(gasLimit)) - , v(move(v)) - , r(move(r)) - , s(move(s)) {} + : directive(std::move(directive)) + , stakeMsg(std::move(stakeMsg)) + , nonce(std::move(nonce)) + , gasPrice(std::move(gasPrice)) + , gasLimit(std::move(gasLimit)) + , v(std::move(v)) + , r(std::move(r)) + , s(std::move(s)) {} }; enum Directive : uint8_t { @@ -65,11 +65,11 @@ class Description { Description(string name, string identity, string website, string securityContact, string details) - : name(move(name)) - , identity(move(identity)) - , website(move(website)) - , securityContact(move(securityContact)) - , details(move(details)) {} + : name(std::move(name)) + , identity(std::move(identity)) + , website(std::move(website)) + , securityContact(std::move(securityContact)) + , details(std::move(details)) {} }; const uint256_t MAX_PRECISION = 18; @@ -95,7 +95,7 @@ class CommissionRate { Decimal maxChangeRate; CommissionRate(Decimal rate, Decimal maxRate, Decimal maxChangeRate) - : rate(move(rate)), maxRate(move(maxRate)), maxChangeRate(move(maxChangeRate)) {} + : rate(std::move(rate)), maxRate(std::move(maxRate)), maxChangeRate(std::move(maxChangeRate)) {} }; class CreateValidator { @@ -113,14 +113,14 @@ class CreateValidator { CommissionRate commissionRates, uint256_t minSelfDelegation, uint256_t maxTotalDelegation, vector> slotPubKeys, vector> slotKeySigs,uint256_t amount) - : validatorAddress(move(validatorAddress)) - , description(move(description)) - , commissionRates(move(commissionRates)) - , minSelfDelegation(move(minSelfDelegation)) - , maxTotalDelegation(move(maxTotalDelegation)) - , amount(move(amount)) - , slotPubKeys(move(slotPubKeys)) - , slotKeySigs(move(slotKeySigs)) {} + : validatorAddress(std::move(validatorAddress)) + , description(std::move(description)) + , commissionRates(std::move(commissionRates)) + , minSelfDelegation(std::move(minSelfDelegation)) + , maxTotalDelegation(std::move(maxTotalDelegation)) + , amount(std::move(amount)) + , slotPubKeys(std::move(slotPubKeys)) + , slotKeySigs(std::move(slotKeySigs)) {} }; class EditValidator { @@ -139,15 +139,15 @@ class EditValidator { uint256_t minSelfDelegation, uint256_t maxTotalDelegation, vector slotKeyToRemove, vector slotKeyToAdd, vector slotKeyToAddSig, uint256_t active) - : validatorAddress(move(validatorAddress)) - , description(move(description)) - , commissionRate(move(commissionRate)) - , minSelfDelegation(move(minSelfDelegation)) - , maxTotalDelegation(move(maxTotalDelegation)) - , slotKeyToRemove(move(slotKeyToRemove)) - , slotKeyToAdd(move(slotKeyToAdd)) - , slotKeyToAddSig(move(slotKeyToAddSig)) - , active(move(active)){} + : validatorAddress(std::move(validatorAddress)) + , description(std::move(description)) + , commissionRate(std::move(commissionRate)) + , minSelfDelegation(std::move(minSelfDelegation)) + , maxTotalDelegation(std::move(maxTotalDelegation)) + , slotKeyToRemove(std::move(slotKeyToRemove)) + , slotKeyToAdd(std::move(slotKeyToAdd)) + , slotKeyToAddSig(std::move(slotKeyToAddSig)) + , active(std::move(active)){} }; class Delegate { @@ -157,9 +157,9 @@ class Delegate { uint256_t amount; Delegate(Address delegatorAddress, Address validatorAddress, uint256_t amount) - : delegatorAddress(move(delegatorAddress)) - , validatorAddress(move(validatorAddress)) - , amount(move(amount)) {} + : delegatorAddress(std::move(delegatorAddress)) + , validatorAddress(std::move(validatorAddress)) + , amount(std::move(amount)) {} }; class Undelegate { @@ -169,16 +169,16 @@ class Undelegate { uint256_t amount; Undelegate(Address delegatorAddress, Address validatorAddress, uint256_t amount) - : delegatorAddress(move(delegatorAddress)) - , validatorAddress(move(validatorAddress)) - , amount(move(amount)) {} + : delegatorAddress(std::move(delegatorAddress)) + , validatorAddress(std::move(validatorAddress)) + , amount(std::move(amount)) {} }; class CollectRewards { public: Address delegatorAddress; - CollectRewards(Address delegatorAddress) : delegatorAddress(move(delegatorAddress)) {} + CollectRewards(Address delegatorAddress) : delegatorAddress(std::move(delegatorAddress)) {} }; } // namespace TW::Harmony diff --git a/src/Harmony/Transaction.cpp b/src/Harmony/Transaction.cpp deleted file mode 100644 index b63874ff7fb..00000000000 --- a/src/Harmony/Transaction.cpp +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Transaction.h" - -using namespace TW::Harmony; diff --git a/src/Hash.cpp b/src/Hash.cpp index 925d3c28fe5..aaf4c5c1339 100644 --- a/src/Hash.cpp +++ b/src/Hash.cpp @@ -1,11 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. #include "Hash.h" -#include "XXHash64.h" #include "BinaryCoding.h" #include @@ -20,6 +19,28 @@ using namespace TW; +TW::Hash::HasherSimpleType Hash::functionPointerFromEnum(TW::Hash::Hasher hasher) { + switch (hasher) { + case Hash::HasherSha1: return Hash::sha1; + default: case Hash::HasherSha256: return Hash::sha256; + case Hash::HasherSha512: return Hash::sha512; + case Hash::HasherSha512_256: return Hash::sha512_256; + case Hash::HasherKeccak256: return Hash::keccak256; + case Hash::HasherKeccak512: return Hash::keccak512; + case Hash::HasherSha3_256: return Hash::sha3_256; + case Hash::HasherSha3_512: return Hash::sha3_512; + case Hash::HasherRipemd: return Hash::ripemd; + case Hash::HasherBlake256: return Hash::blake256; + case Hash::HasherGroestl512: return Hash::groestl512; + case Hash::HasherSha256d: return Hash::sha256d; + case Hash::HasherSha256ripemd: return Hash::sha256ripemd; + case Hash::HasherSha3_256ripemd: return Hash::sha3_256ripemd; + case Hash::HasherBlake256d: return Hash::blake256d; + case Hash::HasherBlake256ripemd: return Hash::blake256ripemd; + case Hash::HasherGroestl512d: return Hash::groestl512d; + } +} + Data Hash::sha1(const byte* data, size_t size) { Data result(sha1Size); sha1_Raw(data, size, result.data()); @@ -101,27 +122,6 @@ Data Hash::groestl512(const byte* data, size_t size) { return result; } -uint64_t Hash::xxhash(const byte* data, size_t size, uint64_t seed) -{ - return XXHash64::hash(data, size, seed); -} - -Data Hash::xxhash64(const byte* data, size_t size, uint64_t seed) -{ - const auto hash = XXHash64::hash(data, size, seed); - Data result; - encode64LE(hash, result); - return result; -} - -Data Hash::xxhash64concat(const byte* data, size_t size) -{ - auto key1 = xxhash64(data, size, 0); - const auto key2 = xxhash64(data, size, 1); - TW::append(key1, key2); - return key1; -} - Data Hash::hmac256(const Data& key, const Data& message) { Data hmac(SHA256_DIGEST_LENGTH); hmac_sha256(key.data(), static_cast(key.size()), message.data(), static_cast(message.size()), hmac.data()); diff --git a/src/Hash.h b/src/Hash.h index 47171dedcb6..7d16544976e 100644 --- a/src/Hash.h +++ b/src/Hash.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -12,9 +12,32 @@ namespace TW::Hash { +/// Enum selector for the supported hash functions +enum Hasher { + HasherSha1 = 0, // SHA1 + HasherSha256, // SHA256 + HasherSha512, // SHA512 + HasherSha512_256, // SHA512/256 + HasherKeccak256, // Keccak SHA256 + HasherKeccak512, // Keccak SHA512 + HasherSha3_256, // version 3 SHA256 + HasherSha3_512, // version 3 SHA512 + HasherRipemd, // RIPEMD160 + HasherBlake256, // Blake256 + HasherGroestl512, // Groestl 512 + HasherSha256d, // SHA256 hash of the SHA256 hash + HasherSha256ripemd, // ripemd hash of the SHA256 hash + HasherSha3_256ripemd, // ripemd hash of the SHA256 hash + HasherBlake256d, // Blake256 hash of the Blake256 hash + HasherBlake256ripemd, // ripemd hash of the Blake256 hash + HasherGroestl512d, // Groestl512 hash of the Groestl512 hash +}; + /// Hashing function. typedef TW::Data (*HasherSimpleType)(const TW::byte*, size_t); -using Hasher = std::function; + +/// Hash function (pointer type) from enum +TW::Hash::HasherSimpleType functionPointerFromEnum(TW::Hash::Hasher hasher); // Digest size constants, duplicating constants from underlying lib /// Number of bytes in a SHA1 hash. @@ -67,32 +90,21 @@ Data blake2b(const byte* data, size_t dataSize, size_t hsshSize, const Data& per /// Computes the Groestl 512 hash. Data groestl512(const byte* data, size_t size); -/// Computes the XXHash hash. -uint64_t xxhash(const byte* data, size_t size, uint64_t seed); - -/// Computes the XXHash hash with 64 encoding. -Data xxhash64(const byte* data, size_t size, uint64_t seed); - -/// Computes the XXHash hash concatenated, xxhash64 with seed 0 and 1, -Data xxhash64concat(const byte* data, size_t size); - -/// Computes the XXHash hash. -uint64_t xxhash(const byte* data, const byte* end, uint64_t seed); - -/// Computes the XXHash hash with 64 encoding. -Data xxhash64(const byte* data, const byte* end, uint64_t seed); - -/// Computes the XXHash hash concatenated, xxhash64 with seed 0 and 1, -Data xxhash64concat(const byte* data, const byte* end); - -// Templated versions for any type with data() and size() +/// Computes requested hash for data (hasher enum, bytes) +inline Data hash(Hasher hasher, const byte* data, size_t dataSize) { + const auto func = functionPointerFromEnum(hasher); + return func(data, dataSize); +} -/// Computes requested hash for data. +/// Computes requested hash for data (hasher enum) template Data hash(Hasher hasher, const T& data) { - return hasher(reinterpret_cast(data.data()), data.size()); + const auto func = functionPointerFromEnum(hasher); + return func(reinterpret_cast(data.data()), data.size()); } +// Templated versions for any type with data() and size() + /// Computes the SHA1 hash. template Data sha1(const T& data) { diff --git a/src/Icon/Address.cpp b/src/Icon/Address.cpp index 4c86ae521d9..6f219a3b537 100644 --- a/src/Icon/Address.cpp +++ b/src/Icon/Address.cpp @@ -6,14 +6,11 @@ #include "Address.h" -#include "../Hash.h" #include "../HexCoding.h" -#include "../PrivateKey.h" #include -using namespace TW; -using namespace TW::Icon; +namespace TW::Icon { static const std::string addressPrefix = "hx"; static const std::string contractPrefix = "cx"; @@ -46,7 +43,8 @@ Address::Address(const std::string& string) { std::copy(data.begin(), data.end(), bytes.begin()); } -Address::Address(const PublicKey& publicKey, enum AddressType type) : type(type) { +Address::Address(const PublicKey& publicKey, enum AddressType type) + : type(type) { auto hash = std::array(); sha3_256(publicKey.bytes.data() + 1, publicKey.bytes.size() - 1, hash.data()); std::copy(hash.end() - Address::size, hash.end(), bytes.begin()); @@ -62,3 +60,5 @@ std::string Address::string() const { return ""; } } + +} // namespace TW::Icon diff --git a/src/Icon/Address.h b/src/Icon/Address.h index 1c2a8645f1c..8c699e91f71 100644 --- a/src/Icon/Address.h +++ b/src/Icon/Address.h @@ -23,7 +23,7 @@ class Address { /// Address data consisting of a prefix byte followed by the public key /// hash. - std::array bytes; + std::array bytes{}; /// Address type. enum AddressType type; diff --git a/src/Icon/Entry.cpp b/src/Icon/Entry.cpp index 2786ebd14b1..66c6ff5d2ad 100644 --- a/src/Icon/Entry.cpp +++ b/src/Icon/Entry.cpp @@ -9,19 +9,20 @@ #include "Address.h" #include "Signer.h" -using namespace TW::Icon; -using namespace std; +namespace TW::Icon { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey, Icon::TypeAddress).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +} // namespace TW::Icon diff --git a/src/Icon/Entry.h b/src/Icon/Entry.h index 699cb8be0d7..9547997f419 100644 --- a/src/Icon/Entry.h +++ b/src/Icon/Entry.h @@ -12,12 +12,11 @@ namespace TW::Icon { /// Entry point for implementation of ICON coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeICON}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::Icon diff --git a/src/Icon/Signer.cpp b/src/Icon/Signer.cpp index 9028f327d3c..b9c290c867e 100644 --- a/src/Icon/Signer.cpp +++ b/src/Icon/Signer.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -10,17 +10,14 @@ #include "../Hash.h" #include "../HexCoding.h" #include "../PrivateKey.h" -#include "../uint256.h" #include #include #include -#include #include -using namespace TW; -using namespace TW::Icon; +namespace TW::Icon { std::string to_hex(int64_t i) { std::stringstream ss; @@ -92,3 +89,5 @@ Proto::SigningOutput Signer::sign() const noexcept { return output; } + +} // namespace TW::Icon diff --git a/src/Icon/Signer.h b/src/Icon/Signer.h index 987ae90e546..209757dc00f 100644 --- a/src/Icon/Signer.h +++ b/src/Icon/Signer.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "../proto/Icon.pb.h" #include diff --git a/src/IoTeX/Address.cpp b/src/IoTeX/Address.cpp index 606af156391..7aca2f45b63 100644 --- a/src/IoTeX/Address.cpp +++ b/src/IoTeX/Address.cpp @@ -8,6 +8,8 @@ #include -using namespace TW::IoTeX; +namespace TW::IoTeX { const std::string Address::hrp = HRP_IOTEX; + +} diff --git a/src/IoTeX/Address.h b/src/IoTeX/Address.h index a76743a4250..5dbf03b4a39 100644 --- a/src/IoTeX/Address.h +++ b/src/IoTeX/Address.h @@ -30,7 +30,7 @@ class Address: public Bech32Address { } /// Initializes an address with a public key. - Address(const PublicKey& publicKey) : Bech32Address(hrp, HASHER_SHA3K, publicKey) { + Address(const PublicKey& publicKey) : Bech32Address(hrp, Hash::HasherKeccak256, publicKey) { if (publicKey.type != TWPublicKeyTypeSECP256k1Extended) { throw std::invalid_argument("address may only be an extended SECP256k1 public key"); } diff --git a/src/IoTeX/Entry.cpp b/src/IoTeX/Entry.cpp index dc4c73dcc9d..5e427d85a67 100644 --- a/src/IoTeX/Entry.cpp +++ b/src/IoTeX/Entry.cpp @@ -9,19 +9,22 @@ #include "Address.h" #include "Signer.h" -using namespace TW::IoTeX; using namespace std; +namespace TW::IoTeX { + // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +} // namespace TW::IoTeX diff --git a/src/IoTeX/Entry.h b/src/IoTeX/Entry.h index 8695c74e08e..ecf04236fc7 100644 --- a/src/IoTeX/Entry.h +++ b/src/IoTeX/Entry.h @@ -12,12 +12,11 @@ namespace TW::IoTeX { /// Entry point for implementation of IoTeX coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeIoTeX}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::IoTeX diff --git a/src/IoTeX/Signer.cpp b/src/IoTeX/Signer.cpp index 6b0e6d6ec1a..11f4f9d9067 100644 --- a/src/IoTeX/Signer.cpp +++ b/src/IoTeX/Signer.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -6,12 +6,11 @@ #include "Signer.h" #include "Hash.h" -#include "HexCoding.h" -#include "IoTeX/Staking.h" #include "PrivateKey.h" using namespace TW; -using namespace TW::IoTeX; + +namespace TW::IoTeX { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto signer = Signer(input); @@ -48,3 +47,5 @@ void Signer::toActionCore() { action.ParseFromString(input.SerializeAsString()); action.DiscardUnknownFields(); } + +} // namespace TW::IoTeX diff --git a/src/Keystore/AESParameters.cpp b/src/Keystore/AESParameters.cpp index 146e1a12727..6aa7bfdc5b0 100644 --- a/src/Keystore/AESParameters.cpp +++ b/src/Keystore/AESParameters.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -11,7 +11,8 @@ #include using namespace TW; -using namespace TW::Keystore; + +namespace TW::Keystore { AESParameters::AESParameters() { iv = Data(blockSize, 0); @@ -33,3 +34,5 @@ nlohmann::json AESParameters::json() const { j[CodingKeys::iv] = hex(iv); return j; } + +} // namespace TW::Keystore diff --git a/src/Keystore/AESParameters.h b/src/Keystore/AESParameters.h index 5f6b994157c..edae9ab9b4f 100644 --- a/src/Keystore/AESParameters.h +++ b/src/Keystore/AESParameters.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include diff --git a/src/Keystore/Account.cpp b/src/Keystore/Account.cpp index c27b7dbfca7..6c061ce30e7 100644 --- a/src/Keystore/Account.cpp +++ b/src/Keystore/Account.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -8,22 +8,28 @@ #include "../Base64.h" #include "../Coin.h" -#include "../HexCoding.h" using namespace TW; -using namespace TW::Keystore; + +namespace TW::Keystore { namespace CodingKeys { - static const auto address = "address"; - static const auto derivationPath = "derivationPath"; - static const auto extendedPublicKey = "extendedPublicKey"; - static const auto indices = "indices"; - static const auto value = "value"; - static const auto hardened = "hardened"; - static const auto coin = "coin"; +static const auto address = "address"; +static const auto derivation = "derivation"; +static const auto derivationPath = "derivationPath"; +static const auto extendedPublicKey = "extendedPublicKey"; +static const auto indices = "indices"; +static const auto value = "value"; +static const auto hardened = "hardened"; +static const auto coin = "coin"; +static const auto publicKey = "publicKey"; } // namespace CodingKeys Account::Account(const nlohmann::json& json) { + if (json.find(CodingKeys::derivation) != json.end()) { + derivation = TWDerivation(json[CodingKeys::derivation].get()); + } + if (json[CodingKeys::derivationPath].is_object()) { const auto indices = json[CodingKeys::derivationPath][CodingKeys::indices]; for (auto& indexJSON : indices) { @@ -51,15 +57,28 @@ Account::Account(const nlohmann::json& json) { json[CodingKeys::extendedPublicKey].is_string()) { extendedPublicKey = json[CodingKeys::extendedPublicKey].get(); } + + if (json.count(CodingKeys::publicKey) > 0 && + json[CodingKeys::publicKey].is_string()) { + publicKey = json[CodingKeys::publicKey].get(); + } } nlohmann::json Account::json() const { nlohmann::json j; j[CodingKeys::address] = address; + if (derivation != TWDerivationDefault) { + j[CodingKeys::derivation] = static_cast(derivation); + } j[CodingKeys::derivationPath] = derivationPath.string(); j[CodingKeys::coin] = coin; if (!extendedPublicKey.empty()) { j[CodingKeys::extendedPublicKey] = extendedPublicKey; } + if (!publicKey.empty()) { + j[CodingKeys::publicKey] = publicKey; + } return j; } + +} // namespace TW::Keystore diff --git a/src/Keystore/Account.h b/src/Keystore/Account.h index fe5c137fe9a..6c48ec8723f 100644 --- a/src/Keystore/Account.h +++ b/src/Keystore/Account.h @@ -7,6 +7,7 @@ #pragma once #include "../DerivationPath.h" +#include #include #include @@ -16,24 +17,32 @@ namespace TW::Keystore { /// Account for a particular coin within a wallet. class Account { public: + /// Coin this account is for + TWCoinType coin; + /// Account public address std::string address; - /// Account derivation path, only relevant for HD wallets. + /// Account derivation. May be missing or unreliable in Json stored format. + TWDerivation derivation = TWDerivationDefault; + + /// Account derivation path, only relevant for HD wallets; info only. DerivationPath derivationPath; - /// Extended public key. - std::string extendedPublicKey; + /// Account public key in hex format. + std::string publicKey; - /// Coin this account is for. - TWCoinType coin; + /// Extended public key, info only. + std::string extendedPublicKey; Account() = default; - Account(std::string address, TWCoinType coin, DerivationPath derivationPath, std::string extendedPublicKey = "") - : address(std::move(address)) + Account(std::string address, TWCoinType coin, TWDerivation derivation, DerivationPath derivationPath, std::string publicKey, std::string extendedPublicKey) + : coin(coin) + , address(std::move(address)) + , derivation(derivation) , derivationPath(std::move(derivationPath)) - , extendedPublicKey(std::move(extendedPublicKey)) - , coin(coin) {} + , publicKey(std::move(publicKey)) + , extendedPublicKey(std::move(extendedPublicKey)) {} /// Initializes `Account` with a JSON object. Account(const nlohmann::json& json); diff --git a/src/Keystore/EncryptionParameters.cpp b/src/Keystore/EncryptionParameters.cpp index b166d724919..d3e08dd6ebe 100644 --- a/src/Keystore/EncryptionParameters.cpp +++ b/src/Keystore/EncryptionParameters.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -7,17 +7,15 @@ #include "EncryptionParameters.h" #include "../Hash.h" -#include "../HexCoding.h" #include #include #include - -#include #include using namespace TW; -using namespace TW::Keystore; + +namespace TW::Keystore { template static Data computeMAC(Iter begin, Iter end, const Data& key) { @@ -28,8 +26,50 @@ static Data computeMAC(Iter begin, Iter end, const Data& key) { return Hash::keccak256(data); } -EncryptionParameters::EncryptionParameters(const Data& password, const Data& data) : mac() { - auto scryptParams = boost::get(kdfParams); +// ----------------- +// Encoding/Decoding +// ----------------- + +namespace CodingKeys { +static const auto encrypted = "ciphertext"; +static const auto cipher = "cipher"; +static const auto cipherParams = "cipherparams"; +static const auto kdf = "kdf"; +static const auto kdfParams = "kdfparams"; +static const auto mac = "mac"; +} // namespace CodingKeys + +EncryptionParameters::EncryptionParameters(const nlohmann::json& json) { + cipher = json[CodingKeys::cipher].get(); + cipherParams = AESParameters(json[CodingKeys::cipherParams]); + + auto kdf = json[CodingKeys::kdf].get(); + if (kdf == "scrypt") { + kdfParams = ScryptParameters(json[CodingKeys::kdfParams]); + } else if (kdf == "pbkdf2") { + kdfParams = PBKDF2Parameters(json[CodingKeys::kdfParams]); + } +} + +nlohmann::json EncryptionParameters::json() const { + nlohmann::json j; + j[CodingKeys::cipher] = cipher; + j[CodingKeys::cipherParams] = cipherParams.json(); + + if (auto* scryptParams = std::get_if(&kdfParams); scryptParams) { + j[CodingKeys::kdf] = "scrypt"; + j[CodingKeys::kdfParams] = scryptParams->json(); + } else if (auto* pbkdf2Params = std::get_if(&kdfParams); pbkdf2Params) { + j[CodingKeys::kdf] = "pbkdf2"; + j[CodingKeys::kdfParams] = pbkdf2Params->json(); + } + + return j; +} + +EncryptedPayload::EncryptedPayload(const Data& password, const Data& data, const EncryptionParameters& params) + : params(std::move(params)), _mac() { + auto scryptParams = std::get(this->params.kdfParams); auto derivedKey = Data(scryptParams.desiredKeyLength); scrypt(reinterpret_cast(password.data()), password.size(), scryptParams.salt.data(), scryptParams.salt.size(), scryptParams.n, scryptParams.r, scryptParams.p, derivedKey.data(), @@ -39,59 +79,62 @@ EncryptionParameters::EncryptionParameters(const Data& password, const Data& dat auto result = aes_encrypt_key128(derivedKey.data(), &ctx); assert(result == EXIT_SUCCESS); if (result == EXIT_SUCCESS) { - Data iv = cipherParams.iv; + Data iv = this->params.cipherParams.iv; encrypted = Data(data.size()); aes_ctr_encrypt(data.data(), encrypted.data(), static_cast(data.size()), iv.data(), aes_ctr_cbuf_inc, &ctx); - mac = computeMAC(derivedKey.end() - 16, derivedKey.end(), encrypted); + _mac = computeMAC(derivedKey.end() - 16, derivedKey.end(), encrypted); } } -EncryptionParameters::~EncryptionParameters() { +EncryptedPayload::~EncryptedPayload() { std::fill(encrypted.begin(), encrypted.end(), 0); + std::fill(_mac.begin(), _mac.end(), 0); } -Data EncryptionParameters::decrypt(const Data& password) const { +Data EncryptedPayload::decrypt(const Data& password) const { auto derivedKey = Data(); auto mac = Data(); - if (kdfParams.which() == 0) { - auto scryptParams = boost::get(kdfParams); - derivedKey.resize(scryptParams.defaultDesiredKeyLength); - scrypt(password.data(), password.size(), scryptParams.salt.data(), - scryptParams.salt.size(), scryptParams.n, scryptParams.r, scryptParams.p, derivedKey.data(), - scryptParams.defaultDesiredKeyLength); + if (auto* scryptParams = std::get_if(¶ms.kdfParams); scryptParams) { + derivedKey.resize(scryptParams->defaultDesiredKeyLength); + scrypt(password.data(), password.size(), scryptParams->salt.data(), + scryptParams->salt.size(), scryptParams->n, scryptParams->r, scryptParams->p, derivedKey.data(), + scryptParams->defaultDesiredKeyLength); mac = computeMAC(derivedKey.end() - 16, derivedKey.end(), encrypted); - } else if (kdfParams.which() == 1) { - auto pbkdf2Params = boost::get(kdfParams); - derivedKey.resize(pbkdf2Params.defaultDesiredKeyLength); - pbkdf2_hmac_sha256(password.data(), static_cast(password.size()), pbkdf2Params.salt.data(), - static_cast(pbkdf2Params.salt.size()), pbkdf2Params.iterations, derivedKey.data(), - pbkdf2Params.defaultDesiredKeyLength); + } else if (auto* pbkdf2Params = std::get_if(¶ms.kdfParams); pbkdf2Params) { + derivedKey.resize(pbkdf2Params->defaultDesiredKeyLength); + pbkdf2_hmac_sha256(password.data(), static_cast(password.size()), pbkdf2Params->salt.data(), + static_cast(pbkdf2Params->salt.size()), pbkdf2Params->iterations, derivedKey.data(), + pbkdf2Params->defaultDesiredKeyLength); mac = computeMAC(derivedKey.end() - 16, derivedKey.end(), encrypted); } else { throw DecryptionError::unsupportedKDF; } - if (mac != this->mac) { + if (mac != _mac) { throw DecryptionError::invalidPassword; } Data decrypted(encrypted.size()); - Data iv = cipherParams.iv; - if (cipher == "aes-128-ctr") { + Data iv = params.cipherParams.iv; + if (params.cipher == "aes-128-ctr") { aes_encrypt_ctx ctx; - [[maybe_unused]] auto result = aes_encrypt_key(derivedKey.data(), 16, &ctx); + //win + //auto __attribute__((unused)) result = aes_encrypt_key(derivedKey.data(), 16, &ctx); + [[maybe_unused]] auto result = aes_encrypt_key(derivedKey.data(), 16, &ctx); assert(result != EXIT_FAILURE); aes_ctr_decrypt(encrypted.data(), decrypted.data(), static_cast(encrypted.size()), iv.data(), aes_ctr_cbuf_inc, &ctx); - } else if (cipher == "aes-128-cbc") { + } else if (params.cipher == "aes-128-cbc") { aes_decrypt_ctx ctx; + //win + //auto __attribute__((unused)) result = aes_decrypt_key(derivedKey.data(), 16, &ctx); [[maybe_unused]] auto result = aes_decrypt_key(derivedKey.data(), 16, &ctx); assert(result != EXIT_FAILURE); - for (auto i = 0; i < encrypted.size(); i += 16) { + for (auto i = 0ul; i < encrypted.size(); i += 16) { aes_cbc_decrypt(encrypted.data() + i, decrypted.data() + i, 16, iv.data(), &ctx); } } else { @@ -101,50 +144,17 @@ Data EncryptionParameters::decrypt(const Data& password) const { return decrypted; } -// ----------------- -// Encoding/Decoding -// ----------------- - -namespace CodingKeys { -static const auto encrypted = "ciphertext"; -static const auto cipher = "cipher"; -static const auto cipherParams = "cipherparams"; -static const auto kdf = "kdf"; -static const auto kdfParams = "kdfparams"; -static const auto mac = "mac"; -} // namespace CodingKeys - -EncryptionParameters::EncryptionParameters(const nlohmann::json& json) { +EncryptedPayload::EncryptedPayload(const nlohmann::json& json) { + params = EncryptionParameters(json); encrypted = parse_hex(json[CodingKeys::encrypted].get()); - cipher = json[CodingKeys::cipher].get(); - cipherParams = AESParameters(json[CodingKeys::cipherParams]); - mac = parse_hex(json[CodingKeys::mac].get()); - - auto kdf = json[CodingKeys::kdf].get(); - if (kdf == "scrypt") { - kdfParams = ScryptParameters(json[CodingKeys::kdfParams]); - } else if (kdf == "pbkdf2") { - kdfParams = PBKDF2Parameters(json[CodingKeys::kdfParams]); - } + _mac = parse_hex(json[CodingKeys::mac].get()); } -nlohmann::json EncryptionParameters::json() const { - nlohmann::json j; +nlohmann::json EncryptedPayload::json() const { + nlohmann::json j = params.json(); j[CodingKeys::encrypted] = hex(encrypted); - j[CodingKeys::cipher] = cipher; - j[CodingKeys::cipherParams] = cipherParams.json(); - j[CodingKeys::mac] = hex(mac); - - if (kdfParams.which() == 0) { - auto scryptParams = boost::get(kdfParams); - j[CodingKeys::kdf] = "scrypt"; - j[CodingKeys::kdfParams] = scryptParams.json(); - } else if (kdfParams.which() == 1) { - auto pbkdf2Params = boost::get(kdfParams); - j[CodingKeys::kdf] = "pbkdf2"; - j[CodingKeys::kdfParams] = pbkdf2Params.json(); - - } - + j[CodingKeys::mac] = hex(_mac); return j; } + +} // namespace TW::Keystore diff --git a/src/Keystore/EncryptionParameters.h b/src/Keystore/EncryptionParameters.h index e1e68933977..6592b9756cd 100644 --- a/src/Keystore/EncryptionParameters.h +++ b/src/Keystore/EncryptionParameters.h @@ -9,14 +9,60 @@ #include "AESParameters.h" #include "PBKDF2Parameters.h" #include "ScryptParameters.h" -#include "../Data.h" +#include "Data.h" +#include -#include +#include #include #include namespace TW::Keystore { +/// Set of parameters used when encoding +struct EncryptionParameters { + static EncryptionParameters getPreset(enum TWStoredKeyEncryptionLevel preset) { + switch (preset) { + case TWStoredKeyEncryptionLevelMinimal: + return EncryptionParameters(AESParameters(), ScryptParameters::Minimal); + case TWStoredKeyEncryptionLevelWeak: + case TWStoredKeyEncryptionLevelDefault: + default: + return EncryptionParameters(AESParameters(), ScryptParameters::Weak); + case TWStoredKeyEncryptionLevelStandard: + return EncryptionParameters(AESParameters(), ScryptParameters::Standard); + } + } + + /// Cipher algorithm. + std::string cipher = "aes-128-ctr"; + + /// Cipher parameters. + AESParameters cipherParams = AESParameters(); + + /// Key derivation function parameters. + std::variant kdfParams = ScryptParameters(); + + EncryptionParameters() = default; + + /// Initializes with standard values. + EncryptionParameters(AESParameters cipherParams, std::variant kdfParams) + : cipherParams(std::move(cipherParams)) + , kdfParams(std::move(kdfParams)) {} + + /// Initializes with a JSON object. + EncryptionParameters(const nlohmann::json& json); + + /// Saves `this` as a JSON object. + nlohmann::json json() const; + + EncryptionParameters(const EncryptionParameters& other) = default; + EncryptionParameters(EncryptionParameters&& other) = default; + EncryptionParameters& operator=(const EncryptionParameters& other) = default; + EncryptionParameters& operator=(EncryptionParameters&& other) = default; + + virtual ~EncryptionParameters() = default; +}; + /// Errors thrown when decrypting a key. enum class DecryptionError { unsupportedKDF, @@ -27,37 +73,31 @@ enum class DecryptionError { invalidPassword, }; -struct EncryptionParameters { +/// An encrypted payload data +struct EncryptedPayload { +public: + EncryptionParameters params; + /// Encrypted data. Data encrypted; - /// Cipher algorithm. - std::string cipher = "aes-128-ctr"; - - /// Cipher parameters. - AESParameters cipherParams = AESParameters(); - - /// Key derivation function parameters. - boost::variant kdfParams = ScryptParameters(); - /// Message authentication code. - Data mac; + Data _mac; - EncryptionParameters() = default; + EncryptedPayload() = default; - /// Initializes `EncryptionParameters` with standard values. - EncryptionParameters(const Data& encrypted, AESParameters cipherParams, boost::variant kdfParams, const Data& mac) - : encrypted(std::move(encrypted)) - , cipherParams(std::move(cipherParams)) - , kdfParams(std::move(kdfParams)) - , mac(std::move(mac)) {} + /// Initializes with standard values. + EncryptedPayload(const EncryptionParameters& params, const Data& encrypted, const Data& mac) + : params(std::move(params)) + , encrypted(std::move(encrypted)) + , _mac(std::move(mac)) {} - /// Initializes `EncryptionParameters` by encrypting data with a password + /// Initializes by encrypting data with a password /// using standard values. - EncryptionParameters(const Data& password, const Data& data); + EncryptedPayload(const Data& password, const Data& data, const EncryptionParameters& params); - /// Initializes `EncryptionParameters` with a JSON object. - EncryptionParameters(const nlohmann::json& json); + /// Initializes with a JSON object. + EncryptedPayload(const nlohmann::json& json); /// Decrypts the payload with the given password. Data decrypt(const Data& password) const; @@ -65,12 +105,12 @@ struct EncryptionParameters { /// Saves `this` as a JSON object. nlohmann::json json() const; - EncryptionParameters(const EncryptionParameters& other) = default; - EncryptionParameters(EncryptionParameters&& other) = default; - EncryptionParameters& operator=(const EncryptionParameters& other) = default; - EncryptionParameters& operator=(EncryptionParameters&& other) = default; + EncryptedPayload(const EncryptedPayload& other) = default; + EncryptedPayload(EncryptedPayload&& other) = default; + EncryptedPayload& operator=(const EncryptedPayload& other) = default; + EncryptedPayload& operator=(EncryptedPayload&& other) = default; - virtual ~EncryptionParameters(); + virtual ~EncryptedPayload(); }; } // namespace TW::Keystore diff --git a/src/Keystore/PBKDF2Parameters.cpp b/src/Keystore/PBKDF2Parameters.cpp index fef2ebe5197..d364184db07 100644 --- a/src/Keystore/PBKDF2Parameters.cpp +++ b/src/Keystore/PBKDF2Parameters.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -7,12 +7,13 @@ #include "PBKDF2Parameters.h" #include -#include using namespace TW; -using namespace TW::Keystore; -PBKDF2Parameters::PBKDF2Parameters() : salt(32) { +namespace TW::Keystore { + +PBKDF2Parameters::PBKDF2Parameters() + : salt(32) { random_buffer(salt.data(), salt.size()); } @@ -41,3 +42,5 @@ nlohmann::json PBKDF2Parameters::json() const { j[CodingKeys::iterations] = iterations; return j; } + +} // namespace TW::Keystore diff --git a/src/Keystore/PBKDF2Parameters.h b/src/Keystore/PBKDF2Parameters.h index 0d473b5615b..175a5106157 100644 --- a/src/Keystore/PBKDF2Parameters.h +++ b/src/Keystore/PBKDF2Parameters.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "../HexCoding.h" #include diff --git a/src/Keystore/ScryptParameters.cpp b/src/Keystore/ScryptParameters.cpp index 16ccd489c4c..cc32e0357a0 100644 --- a/src/Keystore/ScryptParameters.cpp +++ b/src/Keystore/ScryptParameters.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -10,16 +10,22 @@ #include using namespace TW; -using namespace TW::Keystore; -ScryptParameters::ScryptParameters() : salt(32) { +namespace TW::Keystore { + +ScryptParameters ScryptParameters::Minimal = ScryptParameters(Data(), minimalN, defaultR, minimalP, defaultDesiredKeyLength); +ScryptParameters ScryptParameters::Weak = ScryptParameters(Data(), weakN, defaultR, weakP, defaultDesiredKeyLength); +ScryptParameters ScryptParameters::Standard = ScryptParameters(Data(), standardN, defaultR, standardP, defaultDesiredKeyLength); + +ScryptParameters::ScryptParameters() + : salt(32) { random_buffer(salt.data(), salt.size()); } #pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare" std::optional ScryptParameters::validate() const { - if (desiredKeyLength > ((1ULL << 32) - 1) * 32) { // depending on size_t size on platform, may be always false + if (desiredKeyLength > ((1ULL << 32) - 1) * 32) { // depending on size_t size on platform, may be always false return ScryptValidationError::desiredKeyLengthTooLarge; } if (static_cast(r) * static_cast(p) >= (1 << 30)) { @@ -39,32 +45,36 @@ std::optional ScryptParameters::validate() const { // Encoding/Decoding // ----------------- -namespace CodingKeys { +namespace CodingKeys::SP { + static const auto salt = "salt"; static const auto desiredKeyLength = "dklen"; static const auto n = "n"; static const auto p = "p"; static const auto r = "r"; -} // namespace CodingKeys + +} // namespace CodingKeys::SP ScryptParameters::ScryptParameters(const nlohmann::json& json) { - salt = parse_hex(json[CodingKeys::salt].get()); - desiredKeyLength = json[CodingKeys::desiredKeyLength]; - if (json.count(CodingKeys::n) != 0) - n = json[CodingKeys::n]; - if (json.count(CodingKeys::n) != 0) - p = json[CodingKeys::p]; - if (json.count(CodingKeys::n) != 0) - r = json[CodingKeys::r]; + salt = parse_hex(json[CodingKeys::SP::salt].get()); + desiredKeyLength = json[CodingKeys::SP::desiredKeyLength]; + if (json.count(CodingKeys::SP::n) != 0) + n = json[CodingKeys::SP::n]; + if (json.count(CodingKeys::SP::n) != 0) + p = json[CodingKeys::SP::p]; + if (json.count(CodingKeys::SP::n) != 0) + r = json[CodingKeys::SP::r]; } /// Saves `this` as a JSON object. nlohmann::json ScryptParameters::json() const { nlohmann::json j; - j[CodingKeys::salt] = hex(salt); - j[CodingKeys::desiredKeyLength] = desiredKeyLength; - j[CodingKeys::n] = n; - j[CodingKeys::p] = p; - j[CodingKeys::r] = r; + j[CodingKeys::SP::salt] = hex(salt); + j[CodingKeys::SP::desiredKeyLength] = desiredKeyLength; + j[CodingKeys::SP::n] = n; + j[CodingKeys::SP::p] = p; + j[CodingKeys::SP::r] = r; return j; } + +} // namespace TW::Keystore diff --git a/src/Keystore/ScryptParameters.h b/src/Keystore/ScryptParameters.h index 10e7c019bd6..b29faa0e344 100644 --- a/src/Keystore/ScryptParameters.h +++ b/src/Keystore/ScryptParameters.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "../HexCoding.h" #include @@ -23,21 +23,22 @@ enum class ScryptValidationError { /// Scrypt function parameters. struct ScryptParameters { - /// The N parameter of Scrypt encryption algorithm, using 256MB memory and - /// taking approximately 1s CPU time on a modern processor. - static const uint32_t standardN = 1 << 18; + static ScryptParameters Minimal; + static ScryptParameters Weak; + static ScryptParameters Standard; - /// The P parameter of Scrypt encryption algorithm, using 256MB memory and + /// The N and P parameters of Scrypt encryption algorithm, using 256MB memory and /// taking approximately 1s CPU time on a modern processor. + static const uint32_t standardN = 1 << 18; static const uint32_t standardP = 1; - /// The N parameter of Scrypt encryption algorithm, using 4MB memory and - /// taking approximately 100ms CPU time on a modern processor. - static const uint32_t lightN = 1 << 12; + static const uint32_t weakN = 1 << 14; + static const uint32_t weakP = 4; - /// The P parameter of Scrypt encryption algorithm, using 4MB memory and + /// The N and P parameters of Scrypt encryption algorithm, using 4MB memory and /// taking approximately 100ms CPU time on a modern processor. - static const uint32_t lightP = 6; + static const uint32_t minimalN = 1 << 12; + static const uint32_t minimalP = 6; /// Default `R` parameter of Scrypt encryption algorithm. static const uint32_t defaultR = 8; @@ -52,10 +53,10 @@ struct ScryptParameters { std::size_t desiredKeyLength = defaultDesiredKeyLength; /// CPU/Memory cost factor. - uint32_t n = lightN; + uint32_t n = minimalN; /// Parallelization factor (1..232-1 * hLen/MFlen). - uint32_t p = lightP; + uint32_t p = minimalP; /// Block size factor. uint32_t r = defaultR; diff --git a/src/Keystore/StoredKey.cpp b/src/Keystore/StoredKey.cpp index ee7b5d9dcc5..add26c2ca26 100644 --- a/src/Keystore/StoredKey.cpp +++ b/src/Keystore/StoredKey.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -7,60 +7,51 @@ #include "StoredKey.h" #include "Coin.h" +#include "HexCoding.h" #include "Mnemonic.h" #include "PrivateKey.h" #define BOOST_UUID_RANDOM_PROVIDER_FORCE_POSIX 1 #include -#include #include #include #include +#include #include -#include -#include #include -#include using namespace TW; -using namespace TW::Keystore; -StoredKey StoredKey::createWithMnemonic(const std::string& name, const Data& password, const std::string& mnemonic) { +namespace TW::Keystore { + +StoredKey StoredKey::createWithMnemonic(const std::string& name, const Data& password, const std::string& mnemonic, TWStoredKeyEncryptionLevel encryptionLevel) { if (!Mnemonic::isValid(mnemonic)) { throw std::invalid_argument("Invalid mnemonic"); } - + Data mnemonicData = TW::Data(mnemonic.begin(), mnemonic.end()); - StoredKey key = StoredKey(StoredKeyType::mnemonicPhrase, name, password, mnemonicData); - return key; + return StoredKey(StoredKeyType::mnemonicPhrase, name, password, mnemonicData, encryptionLevel); } -StoredKey StoredKey::createWithMnemonicRandom(const std::string& name, const Data& password) { +StoredKey StoredKey::createWithMnemonicRandom(const std::string& name, const Data& password, TWStoredKeyEncryptionLevel encryptionLevel) { const auto wallet = TW::HDWallet(128, ""); const auto& mnemonic = wallet.getMnemonic(); assert(Mnemonic::isValid(mnemonic)); Data mnemonicData = TW::Data(mnemonic.begin(), mnemonic.end()); - StoredKey key = StoredKey(StoredKeyType::mnemonicPhrase, name, password, mnemonicData); - return key; + return StoredKey(StoredKeyType::mnemonicPhrase, name, password, mnemonicData, encryptionLevel); } StoredKey StoredKey::createWithMnemonicAddDefaultAddress(const std::string& name, const Data& password, const std::string& mnemonic, TWCoinType coin) { - StoredKey key = createWithMnemonic(name, password, mnemonic); - - const auto wallet = HDWallet(mnemonic, ""); - const auto derivationPath = TW::derivationPath(coin); - const auto address = TW::deriveAddress(coin, wallet.getKey(coin, derivationPath)); - const auto extendedKey = wallet.getExtendedPublicKey(TW::purpose(coin), coin, TW::xpubVersion(coin)); - key.accounts.emplace_back(address, coin, derivationPath, extendedKey); - + StoredKey key = createWithMnemonic(name, password, mnemonic, TWStoredKeyEncryptionLevelDefault); + const auto wallet = key.wallet(password); + key.account(coin, &wallet); return key; } StoredKey StoredKey::createWithPrivateKey(const std::string& name, const Data& password, const Data& privateKeyData) { - StoredKey key = StoredKey(StoredKeyType::privateKey, name, password, privateKeyData); - return key; + return StoredKey(StoredKeyType::privateKey, name, password, privateKeyData, TWStoredKeyEncryptionLevelDefault); } StoredKey StoredKey::createWithPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const Data& privateKeyData) { @@ -70,16 +61,18 @@ StoredKey StoredKey::createWithPrivateKeyAddDefaultAddress(const std::string& na } StoredKey key = createWithPrivateKey(name, password, privateKeyData); - const auto derivationPath = TW::derivationPath(coin); + const auto pubKeyType = TW::publicKeyType(coin); + const auto pubKey = PrivateKey(privateKeyData).getPublicKey(pubKeyType); const auto address = TW::deriveAddress(coin, PrivateKey(privateKeyData)); - key.accounts.emplace_back(address, coin, derivationPath); - + key.accounts.emplace_back(address, coin, TWDerivationDefault, derivationPath, hex(pubKey.bytes), ""); return key; } -StoredKey::StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data) - : type(type), id(), name(std::move(name)), payload(password, data), accounts() { +StoredKey::StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data, TWStoredKeyEncryptionLevel encryptionLevel) + : type(type), id(), name(std::move(name)), accounts() { + const auto encryptionParams = EncryptionParameters::getPreset(encryptionLevel); + payload = EncryptedPayload(password, data, encryptionParams); boost::uuids::random_generator gen; id = boost::lexical_cast(gen()); } @@ -93,91 +86,210 @@ const HDWallet StoredKey::wallet(const Data& password) const { return HDWallet(mnemonic, ""); } -std::optional StoredKey::account(TWCoinType coin) const { +std::vector StoredKey::getAccounts(TWCoinType coin) const { + std::vector result; for (auto& account : accounts) { if (account.coin == coin) { + result.push_back(account); + } + } + return result; +} + +std::optional StoredKey::getDefaultAccount(TWCoinType coin, const HDWallet* wallet) const { + // there are multiple, try to look for default + if (wallet != nullptr) { + const auto address = wallet->deriveAddress(coin); + const auto defaultAccount = getAccount(coin, address); + if (defaultAccount.has_value()) { + return defaultAccount; + } + } + // no wallet or not found, rely on derivation=0 condition + const auto coinAccounts = getAccounts(coin); + for (auto& account : coinAccounts) { + if (account.derivation == TWDerivationDefault) { return account; } } return std::nullopt; } -std::optional StoredKey::account(TWCoinType coin, const HDWallet* wallet) { - if (wallet == nullptr) { - return account(coin); +std::optional StoredKey::getDefaultAccountOrAny(TWCoinType coin, const HDWallet* wallet) const { + const auto defaultAccount = getDefaultAccount(coin, wallet); + if (defaultAccount.has_value()) { + return defaultAccount; } - assert(wallet != nullptr); + // return any + const auto coinAccounts = getAccounts(coin); + if (coinAccounts.size() > 0) { + return coinAccounts[0]; + } + return std::nullopt; +} +std::optional StoredKey::getAccount(TWCoinType coin, const std::string& address) const { for (auto& account : accounts) { - if (account.coin == coin) { - if (account.address.empty()) { - account.address = wallet->deriveAddress(coin); - } + if (account.coin == coin && account.address == address) { return account; } } + return std::nullopt; +} + +std::optional StoredKey::getAccount(TWCoinType coin, TWDerivation derivation, const HDWallet& wallet) const { + // obtain address + const auto address = wallet.deriveAddress(coin, derivation); + return getAccount(coin, address); +} + +Account StoredKey::fillAddressIfMissing(Account& account, const HDWallet* wallet) const { + if (account.address.empty() && wallet != nullptr) { + account.address = wallet->deriveAddress(account.coin, account.derivation); + } + if (account.publicKey.empty() && wallet != nullptr) { + const auto pubKeyType = TW::publicKeyType(account.coin); + const auto pubKey = wallet->getKey(account.coin, account.derivationPath).getPublicKey(pubKeyType); + account.publicKey = hex(pubKey.bytes); + } + return account; +} +std::optional StoredKey::account(TWCoinType coin, const HDWallet* wallet) { + const auto account = getDefaultAccountOrAny(coin, wallet); + if (account.has_value()) { + Account accountLval = account.value(); + return fillAddressIfMissing(accountLval, wallet); + } + // not found, add + if (wallet == nullptr) { + return std::nullopt; + } + assert(wallet != nullptr); const auto derivationPath = TW::derivationPath(coin); const auto address = wallet->deriveAddress(coin); - const auto version = TW::xpubVersion(coin); const auto extendedPublicKey = wallet->getExtendedPublicKey(derivationPath.purpose(), coin, version); + const auto pubKeyType = TW::publicKeyType(coin); + const auto pubKey = wallet->getKey(coin, derivationPath).getPublicKey(pubKeyType); - accounts.emplace_back(address, coin, derivationPath, extendedPublicKey); + addAccount(address, coin, TWDerivationDefault, derivationPath, hex(pubKey.bytes), extendedPublicKey); + return accounts.back(); +} + +Account StoredKey::account(TWCoinType coin, TWDerivation derivation, const HDWallet& wallet) { + const auto coinAccount = getAccount(coin, derivation, wallet); + if (coinAccount.has_value()) { + Account accountLval = coinAccount.value(); + return fillAddressIfMissing(accountLval, &wallet); + } + // not found, add + const auto derivationPath = TW::derivationPath(coin, derivation); + const auto address = wallet.deriveAddress(coin, derivation); + const auto version = TW::xpubVersionDerivation(coin, derivation); + const auto extendedPublicKey = wallet.getExtendedPublicKey(derivationPath.purpose(), coin, version); + const auto pubKeyType = TW::publicKeyType(coin); + const auto pubKey = wallet.getKey(coin, derivationPath).getPublicKey(pubKeyType); + + addAccount(address, coin, derivation, derivationPath, hex(pubKey.bytes), extendedPublicKey); return accounts.back(); } -void StoredKey::addAccount(const std::string& address, TWCoinType coin, const DerivationPath& derivationPath, const std::string& extetndedPublicKey) { - accounts.emplace_back(address, coin, derivationPath, extetndedPublicKey); +std::optional StoredKey::account(TWCoinType coin) const { + return getDefaultAccountOrAny(coin, nullptr); +} + +std::optional StoredKey::account(TWCoinType coin, TWDerivation derivation, const HDWallet& wallet) const { + const auto account = getAccount(coin, derivation, wallet); + if (account.has_value()) { + Account accountLval = account.value(); + return fillAddressIfMissing(accountLval, &wallet); + } + return std::nullopt; +} + +void StoredKey::addAccount( + const std::string& address, + TWCoinType coin, + TWDerivation derivation, + const DerivationPath& derivationPath, + const std::string& publicKey, + const std::string& extendedPublicKey) { + if (getAccount(coin, address).has_value()) { + // address already present + return; + } + accounts.emplace_back(address, coin, derivation, derivationPath, publicKey, extendedPublicKey); } void StoredKey::removeAccount(TWCoinType coin) { - accounts.erase(std::remove_if(accounts.begin(), accounts.end(), [coin](Account& account) -> bool { - return account.coin == coin; - }), accounts.end()); + accounts.erase( + std::remove_if(accounts.begin(), accounts.end(), [coin](Account& account) -> bool { return account.coin == coin; }), + accounts.end()); +} + +void StoredKey::removeAccount(TWCoinType coin, TWDerivation derivation) { + accounts.erase( + std::remove_if(accounts.begin(), accounts.end(), [coin, derivation](Account& account) -> bool { + return account.coin == coin && account.derivation == derivation; + }), + accounts.end()); +} + +void StoredKey::removeAccount(TWCoinType coin, DerivationPath derivationPath) { + accounts.erase( + std::remove_if(accounts.begin(), accounts.end(), [coin, derivationPath](Account& account) -> bool { + return account.coin == coin && account.derivationPath == derivationPath; + }), + accounts.end()); } const PrivateKey StoredKey::privateKey(TWCoinType coin, const Data& password) { - switch (type) { - case StoredKeyType::mnemonicPhrase: { + return privateKey(coin, TWDerivationDefault, password); +} + +const PrivateKey StoredKey::privateKey(TWCoinType coin, [[maybe_unused]] TWDerivation derivation, const Data& password) { + if (type == StoredKeyType::mnemonicPhrase) { const auto wallet = this->wallet(password); - const auto account = this->account(coin, &wallet); - return wallet.getKey(coin, account->derivationPath); - } - case StoredKeyType::privateKey: - return PrivateKey(payload.decrypt(password)); + const Account& account = this->account(coin, derivation, wallet); + return wallet.getKey(coin, account.derivationPath); } + // type == StoredKeyType::privateKey + return PrivateKey(payload.decrypt(password)); } void StoredKey::fixAddresses(const Data& password) { switch (type) { - case StoredKeyType::mnemonicPhrase: { - const auto wallet = this->wallet(password); - for (auto& account : accounts) { - if (!account.address.empty() && TW::validateAddress(account.coin, account.address)) { - continue; - } - const auto& derivationPath = account.derivationPath; - const auto key = wallet.getKey(account.coin, derivationPath); - account.address = TW::deriveAddress(account.coin, key); - } + case StoredKeyType::mnemonicPhrase: { + const auto wallet = this->wallet(password); + for (auto& account : accounts) { + if (!account.address.empty() && !account.publicKey.empty() && + TW::validateAddress(account.coin, account.address)) { + continue; } - break; - - case StoredKeyType::privateKey: { - auto key = PrivateKey(payload.decrypt(password)); - for (auto& account : accounts) { - if (!account.address.empty() && TW::validateAddress(account.coin, account.address)) { - continue; - } - account.address = TW::deriveAddress(account.coin, key); - } + const auto& derivationPath = account.derivationPath; + const auto key = wallet.getKey(account.coin, derivationPath); + const auto pubKey = key.getPublicKey(TW::publicKeyType(account.coin)); + account.address = TW::deriveAddress(account.coin, pubKey, account.derivation); + account.publicKey = hex(pubKey.bytes); + } + } break; + + case StoredKeyType::privateKey: { + auto key = PrivateKey(payload.decrypt(password)); + for (auto& account : accounts) { + if (!account.address.empty() && !account.publicKey.empty() && + TW::validateAddress(account.coin, account.address)) { + continue; } - break; + const auto pubKey = key.getPublicKey(TW::publicKeyType(account.coin)); + account.address = TW::deriveAddress(account.coin, pubKey, account.derivation); + account.publicKey = hex(pubKey.bytes); + } + } break; } } - // ----------------- // Encoding/Decoding // ----------------- @@ -188,93 +300,96 @@ StoredKey StoredKey::createWithJson(const nlohmann::json& json) { return storedKey; } -namespace CodingKeys { - static const auto address = "address"; - static const auto type = "type"; - static const auto name = "name"; - static const auto id = "id"; - static const auto crypto = "crypto"; - static const auto activeAccounts = "activeAccounts"; - static const auto version = "version"; - static const auto coin = "coin"; -} // namespace CodingKeys +namespace CodingKeys::SK { + +static const auto address = "address"; +static const auto type = "type"; +static const auto name = "name"; +static const auto id = "id"; +static const auto crypto = "crypto"; +static const auto activeAccounts = "activeAccounts"; +static const auto version = "version"; +static const auto coin = "coin"; + +} // namespace CodingKeys::SK namespace UppercaseCodingKeys { - static const auto crypto = "Crypto"; +static const auto crypto = "Crypto"; } // namespace UppercaseCodingKeys namespace TypeString { - static const auto privateKey = "private-key"; - static const auto mnemonic = "mnemonic"; +static const auto privateKey = "private-key"; +static const auto mnemonic = "mnemonic"; } // namespace TypeString void StoredKey::loadJson(const nlohmann::json& json) { - if (json.count(CodingKeys::type) != 0 && - json[CodingKeys::type].get() == TypeString::mnemonic) { + if (json.count(CodingKeys::SK::type) != 0 && + json[CodingKeys::SK::type].get() == TypeString::mnemonic) { type = StoredKeyType::mnemonicPhrase; } else { type = StoredKeyType::privateKey; } - if (json.count(CodingKeys::name) != 0) { - name = json[CodingKeys::name].get(); + if (json.count(CodingKeys::SK::name) != 0) { + name = json[CodingKeys::SK::name].get(); } - if (json.count(CodingKeys::id) != 0) { - id = json[CodingKeys::id].get(); + if (json.count(CodingKeys::SK::id) != 0) { + id = json[CodingKeys::SK::id].get(); } - if (json.count(CodingKeys::crypto) != 0) { - payload = EncryptionParameters(json[CodingKeys::crypto]); + if (json.count(CodingKeys::SK::crypto) != 0) { + payload = EncryptedPayload(json[CodingKeys::SK::crypto]); } else if (json.count(UppercaseCodingKeys::crypto) != 0) { // Workaround for myEtherWallet files - payload = EncryptionParameters(json[UppercaseCodingKeys::crypto]); + payload = EncryptedPayload(json[UppercaseCodingKeys::crypto]); } else { throw DecryptionError::invalidKeyFile; } - if (json.count(CodingKeys::activeAccounts) != 0 && - json[CodingKeys::activeAccounts].is_array()) { - for (auto& accountJSON : json[CodingKeys::activeAccounts]) { + if (json.count(CodingKeys::SK::activeAccounts) != 0 && + json[CodingKeys::SK::activeAccounts].is_array()) { + for (auto& accountJSON : json[CodingKeys::SK::activeAccounts]) { accounts.emplace_back(accountJSON); } } - if (accounts.empty() && json.count(CodingKeys::address) != 0 && json[CodingKeys::address].is_string()) { + if (accounts.empty() && json.count(CodingKeys::SK::address) != 0 && + json[CodingKeys::SK::address].is_string()) { TWCoinType coin = TWCoinTypeEthereum; - if (json.count(CodingKeys::coin) != 0) { - coin = json[CodingKeys::coin].get(); + if (json.count(CodingKeys::SK::coin) != 0) { + coin = json[CodingKeys::SK::coin].get(); } - auto address = json[CodingKeys::address].get(); - accounts.emplace_back(address, coin, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(coin), 0, 0, 0)); + auto address = json[CodingKeys::SK::address].get(); + accounts.emplace_back(address, coin, TWDerivationDefault, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(coin), 0, 0, 0), "", ""); } } nlohmann::json StoredKey::json() const { nlohmann::json j; - j[CodingKeys::version] = 3; + j[CodingKeys::SK::version] = 3; switch (type) { case StoredKeyType::privateKey: - j[CodingKeys::type] = TypeString::privateKey; + j[CodingKeys::SK::type] = TypeString::privateKey; break; case StoredKeyType::mnemonicPhrase: - j[CodingKeys::type] = TypeString::mnemonic; + j[CodingKeys::SK::type] = TypeString::mnemonic; break; } if (id) { - j[CodingKeys::id] = *id; + j[CodingKeys::SK::id] = *id; } - j[CodingKeys::name] = name; - j[CodingKeys::crypto] = payload.json(); + j[CodingKeys::SK::name] = name; + j[CodingKeys::SK::crypto] = payload.json(); nlohmann::json accountsJSON = nlohmann::json::array(); for (const auto& account : accounts) { accountsJSON.push_back(account.json()); } - j[CodingKeys::activeAccounts] = accountsJSON; + j[CodingKeys::SK::activeAccounts] = accountsJSON; return j; } @@ -296,3 +411,5 @@ StoredKey StoredKey::load(const std::string& path) { return createWithJson(j); } + +} // namespace TW::Keystore diff --git a/src/Keystore/StoredKey.h b/src/Keystore/StoredKey.h index 4a019c5b5b3..96d3010064b 100644 --- a/src/Keystore/StoredKey.h +++ b/src/Keystore/StoredKey.h @@ -8,14 +8,16 @@ #include "Account.h" #include "EncryptionParameters.h" -#include "../Data.h" +#include "Data.h" #include "../HDWallet.h" #include +#include #include #include #include +#include namespace TW::Keystore { @@ -36,29 +38,29 @@ class StoredKey { std::string name; /// Encrypted payload. - EncryptionParameters payload; + EncryptedPayload payload; - /// Active accounts. + /// Active accounts. Address should be unique. std::vector accounts; /// Create a new StoredKey, with the given name, mnemonic and password. /// @throws std::invalid_argument if mnemonic is invalid - static StoredKey createWithMnemonic(const std::string& name, const Data& password, const std::string& mnemonic); + static StoredKey createWithMnemonic(const std::string& name, const Data& password, const std::string& mnemonic, TWStoredKeyEncryptionLevel encryptionLevel); /// Create a new StoredKey, with the given name, mnemonic and password. /// @throws std::invalid_argument if mnemonic is invalid - static StoredKey createWithMnemonicRandom(const std::string& name, const Data& password); + static StoredKey createWithMnemonicRandom(const std::string& name, const Data& password, TWStoredKeyEncryptionLevel encryptionLevel); /// Create a new StoredKey, with the given name, mnemonic and password, and also add the default address for the given coin.. /// @throws std::invalid_argument if mnemonic is invalid static StoredKey createWithMnemonicAddDefaultAddress(const std::string& name, const Data& password, const std::string& mnemonic, TWCoinType coin); /// Create a new StoredKey, with the given name and private key. - /// @throws std::invalid_argument if privateKeyData is not a vald private key + /// @throws std::invalid_argument if privateKeyData is not a valid private key static StoredKey createWithPrivateKey(const std::string& name, const Data& password, const Data& privateKeyData); /// Create a new StoredKey, with the given name and private key, and also add the default address for the given coin.. - /// @throws std::invalid_argument if privateKeyData is not a vald private key + /// @throws std::invalid_argument if privateKeyData is not a valid private key static StoredKey createWithPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const Data& privateKeyData); /// Create a StoredKey from a JSON object. @@ -69,35 +71,66 @@ class StoredKey { /// @throws std::invalid_argument if this key is of a type other than `mnemonicPhrase`. const HDWallet wallet(const Data& password) const; - /// Returns the account for a specific coin, creating it if necessary and - /// the provided wallet is not `nullptr`. + /// Returns all the accounts for a specific coin: 0, 1, or more. + std::vector getAccounts(TWCoinType coin) const; + + /// If found, returns the account for a specific coin. In case of muliple accounts, the default derivation is returned, or the first one is returned. + /// If none exists, and wallet is not null, an account is created (with default derivation). std::optional account(TWCoinType coin, const HDWallet* wallet); + /// If found, returns the account for a specific coin and derivation. In case of muliple accounts, the first one is returned. + /// If none exists, an account is created. + Account account(TWCoinType coin, TWDerivation derivation, const HDWallet& wallet); + /// Returns the account for a specific coin if it exists. + /// In case of muliple accounts, the default derivation is returned, or the first one is returned. std::optional account(TWCoinType coin) const; - /// Add an account - void addAccount(const std::string& address, TWCoinType coin, const DerivationPath& derivationPath, const std::string& extetndedPublicKey); - - /// Remove the account for a specific coin + /// Returns the account for a specific coin and derivation, if it exists. + std::optional account(TWCoinType coin, TWDerivation derivation, const HDWallet& wallet) const; + + /// Add an account with aribitrary address/derivation path. Discouraged, use account() versions. + /// Address must be unique (for a coin). + void addAccount( + const std::string& address, + TWCoinType coin, + TWDerivation derivation, + const DerivationPath& derivationPath, + const std::string& publicKey, + const std::string& extendedPublicKey + ); + + /// Remove the account(s) for a specific coin void removeAccount(TWCoinType coin); - - /// Returns the private key for a specific coin, creating an account if necessary. + + /// Remove the account for a specific coin with the given derivation. + void removeAccount(TWCoinType coin, TWDerivation derivation); + + /// Remove the account for a specific coin with the given derivation path. + void removeAccount(TWCoinType coin, DerivationPath derivationPath); + + /// Returns the private key for a specific coin, using default derivation, creating an account if necessary. /// - /// @throws std::invalid_argument if this key is of a type other than + /// \throws std::invalid_argument if this key is of a type other than /// `mnemonicPhrase` and a coin other than the default is requested. const PrivateKey privateKey(TWCoinType coin, const Data& password); + /// Returns the private key for a specific coin, creating an account if necessary. + /// + /// \throws std::invalid_argument if this key is of a type other than + /// `mnemonicPhrase` and a coin other than the default is requested. + const PrivateKey privateKey(TWCoinType coin, TWDerivation derivation, const Data& password); + /// Loads and decrypts a stored key from a file. /// - /// @param path file path to load from. - /// @returns descrypted key. - /// @throws DecryptionError + /// \param path file path to load from. + /// \returns decrypted key. + /// \throws DecryptionError static StoredKey load(const std::string& path); /// Stores the key into an encrypted file. /// - /// @param path file path to store in. + /// \param path file path to store in. void store(const std::string& path); /// Initializes `StoredKey` with a JSON object. @@ -106,7 +139,7 @@ class StoredKey { /// Saves `this` as a JSON object. nlohmann::json json() const; - /// Fills in all empty and invalid addresses. + /// Fills in all empty or invalid addresses and public keys. /// /// Use to fix legacy wallets with invalid address data. This method needs /// the encryption password to re-derive addresses from private keys. @@ -117,9 +150,26 @@ class StoredKey { StoredKey() : type(StoredKeyType::mnemonicPhrase) {} /// Initializes a `StoredKey` with a type, an encryption password, and unencrypted data. - /// This contstructor will encrypt the provided data with default encryption + /// This constructor will encrypt the provided data with default encryption /// parameters. - StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data); + StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data, TWStoredKeyEncryptionLevel encryptionLevel); + + /// Find default account for coin, if exists. If multiple exist, default is returned. + /// Optional wallet is needed to derive default address + std::optional getDefaultAccount(TWCoinType coin, const HDWallet* wallet) const; + + /// Find account for coin, if exists. If multiple exist, default is returned, or any. + /// Optional wallet is needed to derive default address + std::optional getDefaultAccountOrAny(TWCoinType coin, const HDWallet* wallet) const; + + /// Find account by coin+address (should be one, if multiple, first is returned) + std::optional getAccount(TWCoinType coin, const std::string& address) const; + + /// Find account by coin+derivation (should be one, if multiple, first is returned) + std::optional getAccount(TWCoinType coin, TWDerivation derivation, const HDWallet& wallet) const; + + /// Re-derive account address if missing + Account fillAddressIfMissing(Account& account, const HDWallet* wallet) const; }; } // namespace TW::Keystore diff --git a/src/Kusama/Address.h b/src/Kusama/Address.h index 40163a1cb3f..9635b6972ca 100644 --- a/src/Kusama/Address.h +++ b/src/Kusama/Address.h @@ -6,9 +6,9 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" -#include "../SS58Address.h" +#include "../Polkadot/SS58Address.h" #include #include diff --git a/src/Kusama/Entry.cpp b/src/Kusama/Entry.cpp index fa1bb8671dc..28e56d9bfb0 100644 --- a/src/Kusama/Entry.cpp +++ b/src/Kusama/Entry.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,19 +9,25 @@ #include "Address.h" #include "Polkadot/Signer.h" -using namespace TW::Kusama; -using namespace std; +namespace TW::Kusama { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + const auto addr = Address(address); + return {addr.bytes.begin() + 1, addr.bytes.end()}; +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +} // namespace TW::Kusama diff --git a/src/Kusama/Entry.h b/src/Kusama/Entry.h index d766dd678a0..f9d566239a1 100644 --- a/src/Kusama/Entry.h +++ b/src/Kusama/Entry.h @@ -12,12 +12,12 @@ namespace TW::Kusama { /// Entry point for implementation of Kusama coin. See also Polkadot. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeKusama}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::Kusama diff --git a/src/Mnemonic.cpp b/src/Mnemonic.cpp index 08a3112ec2a..1bff4d76ea7 100644 --- a/src/Mnemonic.cpp +++ b/src/Mnemonic.cpp @@ -28,16 +28,15 @@ inline const char* const* mnemonicWordlist() { return wordlist; } bool Mnemonic::isValidWord(const std::string& word) { const char* wordC = word.c_str(); const auto len = word.length(); + // Although this operation is not security-critical, we aim for constant-time operation here as well + // (i.e., no early exit on match) + auto found = false; for (const char* const* w = mnemonicWordlist(); *w != nullptr; ++w) { - if (strlen(*w) != len) { - continue; - } - if (strncmp(*w, wordC, len) == 0) { - return true; + if (strlen(*w) == len && strncmp(*w, wordC, len) == 0) { + found = true; } } - // not found - return false; + return found; } std::string Mnemonic::suggest(const std::string& prefix) { diff --git a/src/NEAR/Account.cpp b/src/NEAR/Account.cpp index bf9762fbb3c..0bb0bbf42fd 100644 --- a/src/NEAR/Account.cpp +++ b/src/NEAR/Account.cpp @@ -8,10 +8,10 @@ #include -using namespace TW; -using namespace TW::NEAR; +namespace TW::NEAR { static auto pattern = std::regex(R"(^(([a-z\d]+[\-_])*[a-z\d]+\.)*([a-z\d]+[\-_])*[a-z\d]+$)"); + bool Account::isValid(const std::string& string) { // https://docs.near.org/docs/concepts/account#account-id-rules if (string.size() < 2 || string.size() > 64) { @@ -20,3 +20,5 @@ bool Account::isValid(const std::string& string) { std::smatch match; return regex_search(string, match, pattern); } + +} // namespace TW::NEAR diff --git a/src/NEAR/Address.cpp b/src/NEAR/Address.cpp index 3d8eeff1fad..1d669e3d449 100644 --- a/src/NEAR/Address.cpp +++ b/src/NEAR/Address.cpp @@ -4,20 +4,21 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +#include "Address.h" #include "Base58.h" #include "HexCoding.h" -#include "Address.h" #include using namespace TW; -using namespace TW::NEAR; + +namespace TW::NEAR { bool Address::isValid(const std::string& string) { const auto data = Address::decodeLegacyAddress(string); if (data.has_value()) { return true; - } + } const auto parsed = parse_hex(string); return parsed.size() == PublicKey::ed25519Size; } @@ -40,7 +41,7 @@ Address::Address(const std::string& string) { std::copy(std::begin(*data), std::end(*data), std::begin(bytes)); } else { if (!Address::isValid(string)) { - throw std::invalid_argument("Invalid address string!"); + throw std::invalid_argument("Invalid address string!"); } const auto parsed = parse_hex(string); std::copy(std::begin(parsed), std::end(parsed), std::begin(bytes)); @@ -58,3 +59,5 @@ Address::Address(const PublicKey& publicKey) { std::string Address::string() const { return hex(bytes); } + +} // namespace TW::NEAR diff --git a/src/NEAR/Address.h b/src/NEAR/Address.h index 5a81fd24c03..791aa107b9f 100644 --- a/src/NEAR/Address.h +++ b/src/NEAR/Address.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include diff --git a/src/NEAR/Entry.cpp b/src/NEAR/Entry.cpp index 6fc0ccbd793..c33987cc422 100644 --- a/src/NEAR/Entry.cpp +++ b/src/NEAR/Entry.cpp @@ -9,23 +9,32 @@ #include "Address.h" #include "Signer.h" -using namespace TW::NEAR; +using namespace TW; using namespace std; +namespace TW::NEAR { + // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address); } -string Entry::normalizeAddress(TWCoinType coin, const string& address) const { +string Entry::normalizeAddress([[maybe_unused]] TWCoinType coin, const string& address) const { return Address(address).string(); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + const auto addr = Address(address); + return {addr.bytes.begin(), addr.bytes.end()}; +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +} // namespace TW::NEAR diff --git a/src/NEAR/Entry.h b/src/NEAR/Entry.h index 46b568f4214..20a0870762c 100644 --- a/src/NEAR/Entry.h +++ b/src/NEAR/Entry.h @@ -12,13 +12,13 @@ namespace TW::NEAR { /// Entry point for implementation of NEAR coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeNEAR}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string normalizeAddress(TWCoinType coin, const std::string& address) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string normalizeAddress(TWCoinType coin, const std::string& address) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::NEAR diff --git a/src/NEAR/Serialization.cpp b/src/NEAR/Serialization.cpp index 4b6e24826de..9997fc84128 100644 --- a/src/NEAR/Serialization.cpp +++ b/src/NEAR/Serialization.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,10 +9,7 @@ #include "../BinaryCoding.h" #include "../PrivateKey.h" -using namespace TW; -using namespace TW::NEAR; -using namespace TW::NEAR::Proto; - +namespace TW::NEAR { static void writeU8(Data& data, uint8_t number) { data.push_back(number); @@ -30,7 +27,8 @@ static void writeU128(Data& data, const std::string& numberData) { data.insert(std::end(data), std::begin(numberData), std::end(numberData)); } -template static void writeRawBuffer(Data& data, const T& buf) { +template +static void writeRawBuffer(Data& data, const T& buf) { data.insert(std::end(data), std::begin(buf), std::end(buf)); } @@ -49,18 +47,90 @@ static void writeTransfer(Data& data, const Proto::Transfer& transfer) { writeU128(data, transfer.deposit()); } +static void writeFunctionCall(Data& data, const Proto::FunctionCall& functionCall) { + writeString(data, functionCall.method_name()); + + writeU32(data, static_cast(functionCall.args().size())); + writeRawBuffer(data, functionCall.args()); + + writeU64(data, functionCall.gas()); + writeU128(data, functionCall.deposit()); +} + +static void writeStake(Data& data, const Proto::Stake& stake) { + writeU128(data, stake.stake()); + writePublicKey(data, stake.public_key()); +} + +static void writeFunctionCallPermission(Data& data, const Proto::FunctionCallPermission& functionCallPermission) { + if (functionCallPermission.allowance().empty()) { + writeU8(data, 0); + } else { + writeU8(data, 1); + writeU128(data, functionCallPermission.allowance()); + } + writeString(data, functionCallPermission.receiver_id()); + writeU32(data, static_cast(functionCallPermission.method_names().size())); + for (auto&& methodName : functionCallPermission.method_names()) { + writeString(data, methodName); + } +} + +static void writeAccessKey(Data& data, const Proto::AccessKey& accessKey) { + writeU64(data, accessKey.nonce()); + switch (accessKey.permission_case()) { + case Proto::AccessKey::kFunctionCall: + writeU8(data, 0); + writeFunctionCallPermission(data, accessKey.function_call()); + break; + case Proto::AccessKey::kFullAccess: + writeU8(data, 1); + break; + case Proto::AccessKey::PERMISSION_NOT_SET: + break; + } +} + +static void writeAddKey(Data& data, const Proto::AddKey& addKey) { + writePublicKey(data, addKey.public_key()); + writeAccessKey(data, addKey.access_key()); +} + +static void writeDeleteKey(Data& data, const Proto::DeleteKey& deleteKey) { + writePublicKey(data, deleteKey.public_key()); +} + +static void writeDeleteAccount(Data& data, const Proto::DeleteAccount& deleteAccount) { + writeString(data, deleteAccount.beneficiary_id()); +} + static void writeAction(Data& data, const Proto::Action& action) { writeU8(data, action.payload_case() - Proto::Action::kCreateAccount); switch (action.payload_case()) { - case Proto::Action::kTransfer: - writeTransfer(data, action.transfer()); - return; - default: - return; + case Proto::Action::kTransfer: + writeTransfer(data, action.transfer()); + return; + case Proto::Action::kFunctionCall: + writeFunctionCall(data, action.function_call()); + return; + case Proto::Action::kStake: + writeStake(data, action.stake()); + return; + case Proto::Action::kAddKey: + writeAddKey(data, action.add_key()); + return; + case Proto::Action::kDeleteKey: + writeDeleteKey(data, action.delete_key()); + return; + case Proto::Action::kDeleteAccount: + writeDeleteAccount(data, action.delete_account()); + return; + default: + return; } } -Data TW::NEAR::transactionData(const Proto::SigningInput& input) { +Data transactionData(const Proto::SigningInput& input) { Data data; writeString(data, input.signer_id()); auto key = PrivateKey(input.private_key()); @@ -79,10 +149,12 @@ Data TW::NEAR::transactionData(const Proto::SigningInput& input) { return data; } -Data TW::NEAR::signedTransactionData(const Data& transactionData, const Data& signatureData) { +Data signedTransactionData(const Data& transactionData, const Data& signatureData) { Data data; writeRawBuffer(data, transactionData); writeU8(data, 0); writeRawBuffer(data, signatureData); return data; } + +} // namespace TW::NEAR diff --git a/src/NEAR/Serialization.h b/src/NEAR/Serialization.h index 1e2ecd9e493..54a9d7041d9 100644 --- a/src/NEAR/Serialization.h +++ b/src/NEAR/Serialization.h @@ -7,7 +7,7 @@ #pragma once #include "../proto/NEAR.pb.h" -#include "../Data.h" +#include "Data.h" namespace TW::NEAR { diff --git a/src/NEAR/Signer.cpp b/src/NEAR/Signer.cpp index 7434d646ea3..65031bce7a2 100644 --- a/src/NEAR/Signer.cpp +++ b/src/NEAR/Signer.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -10,8 +10,7 @@ #include "../Hash.h" #include "../PrivateKey.h" -using namespace TW; -using namespace TW::NEAR; +namespace TW::NEAR { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto transaction = transactionData(input); @@ -21,5 +20,8 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto output = Proto::SigningOutput(); auto signedTransaction = signedTransactionData(transaction, signature); output.set_signed_transaction(signedTransaction.data(), signedTransaction.size()); + output.set_hash(hash.data(), hash.size()); return output; } + +} // namespace TW::NEAR diff --git a/src/NEO/Address.cpp b/src/NEO/Address.cpp index 229e61f27d4..64c4c28a808 100644 --- a/src/NEO/Address.cpp +++ b/src/NEO/Address.cpp @@ -4,16 +4,17 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../Ontology/ParamsBuilder.h" +#include "OpCode.h" #include "../Base58.h" +#include "Data.h" #include "../Hash.h" -#include "../Data.h" -#include "OpCode.h" +#include "../Ontology/ParamsBuilder.h" #include "Address.h" using namespace TW; -using namespace TW::NEO; + +namespace TW::NEO { bool Address::isValid(const std::string& string) { const auto decoded = Base58::bitcoin.decodeCheck(string); @@ -22,7 +23,7 @@ bool Address::isValid(const std::string& string) { Address::Address() { Data keyHash; - for (int i = 0; i < Address::size; i++) { + for (auto i = 0ul; i < Address::size; i++) { keyHash.push_back(0); } std::copy(keyHash.data(), keyHash.data() + Address::size, bytes.begin()); @@ -36,7 +37,7 @@ Address::Address(const PublicKey& publicKey) { pkdata.push_back(CHECKSIG); auto keyHash = Hash::ripemd(Hash::sha256(pkdata)); - keyHash.insert(keyHash.begin(), (byte) Address::version); + keyHash.insert(keyHash.begin(), (byte)Address::version); if (keyHash.size() != Address::size) { throw std::invalid_argument("Invalid address key data"); @@ -60,3 +61,5 @@ Data Address::toScriptHash() const { std::copy(bytes.begin() + 1, bytes.begin() + Hash::ripemdSize + 1, data.begin()); return data; } + +} // namespace TW::NEO diff --git a/src/NEO/Address.h b/src/NEO/Address.h index d3d78ab7088..993cba859c3 100644 --- a/src/NEO/Address.h +++ b/src/NEO/Address.h @@ -9,7 +9,7 @@ #include #include "../Base58Address.h" -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" namespace TW::NEO { diff --git a/src/NEO/CoinReference.h b/src/NEO/CoinReference.h index ad185c0e143..8890f537d3d 100644 --- a/src/NEO/CoinReference.h +++ b/src/NEO/CoinReference.h @@ -7,7 +7,7 @@ #pragma once #include "../uint256.h" -#include "../Data.h" +#include "Data.h" #include "../Hash.h" #include "../BinaryCoding.h" #include "ISerializable.h" @@ -19,6 +19,7 @@ class CoinReference : public Serializable { public: /// Number of bytes for prevIndex. static const size_t prevIndexSize = 2; + static const size_t prevHashSize = 32; uint256_t prevHash; uint16_t prevIndex = 0; @@ -35,7 +36,7 @@ class CoinReference : public Serializable { } Data serialize() const override { - auto resp = store(prevHash); + auto resp = store(prevHash, prevHashSize); encode16LE(prevIndex, resp); return resp; } @@ -46,4 +47,4 @@ class CoinReference : public Serializable { } }; -} \ No newline at end of file +} // namespace TW::NEO diff --git a/src/NEO/Constants.h b/src/NEO/Constants.h new file mode 100644 index 00000000000..817dc8840cf --- /dev/null +++ b/src/NEO/Constants.h @@ -0,0 +1,16 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +namespace TW::NEO { + +static const size_t assetIdSize = 32; +static const size_t contractHashSize = 32; +static const size_t valueSize = 8; +static const size_t scriptHashSize = 20; + +} // namespace TW::NEO diff --git a/src/NEO/Entry.cpp b/src/NEO/Entry.cpp index f51b0e07335..f9e55d72637 100644 --- a/src/NEO/Entry.cpp +++ b/src/NEO/Entry.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,21 +9,30 @@ #include "Address.h" #include "Signer.h" -using namespace TW::NEO; +using namespace TW; using namespace std; -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const { +namespace TW::NEO { + +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, [[maybe_unused]] const string& address, [[maybe_unused]] TW::byte p2pkh, [[maybe_unused]] TW::byte p2sh, [[maybe_unused]] const char* hrp) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const { +string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TW::byte p2pkh, [[maybe_unused]] const char* hrp) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + const auto addr = Address(address); + return {addr.bytes.begin(), addr.bytes.end()}; +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } -void Entry::plan(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::plan([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { planTemplate(dataIn, dataOut); } + +} // namespace TW::NEO diff --git a/src/NEO/Entry.h b/src/NEO/Entry.h index ff632623175..6a396f753c2 100644 --- a/src/NEO/Entry.h +++ b/src/NEO/Entry.h @@ -12,13 +12,13 @@ namespace TW::NEO { /// NEO entry dispatcher. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeNEO}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::NEO diff --git a/src/NEO/ISerializable.h b/src/NEO/ISerializable.h index 64e30ee0754..ce2245a643d 100644 --- a/src/NEO/ISerializable.h +++ b/src/NEO/ISerializable.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "../BinaryCoding.h" #include "ReadData.h" diff --git a/src/NEO/MinerTransaction.h b/src/NEO/MinerTransaction.h index ca73b306958..73ff8daf129 100644 --- a/src/NEO/MinerTransaction.h +++ b/src/NEO/MinerTransaction.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "Transaction.h" namespace TW::NEO { diff --git a/src/NEO/ReadData.cpp b/src/NEO/ReadData.cpp index e8f3487e4d8..f0889883487 100644 --- a/src/NEO/ReadData.cpp +++ b/src/NEO/ReadData.cpp @@ -4,13 +4,13 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../Data.h" +#include "Data.h" #include "ReadData.h" #include TW::Data TW::readBytes(const TW::Data& from, int max, int initial_pos) { - if (from.size() - initial_pos < max) { + if (from.size() - static_cast(initial_pos) < static_cast(max)) { throw std::invalid_argument("Data::Cannot read enough bytes!"); } return TW::Data(from.begin() + initial_pos, from.begin() + initial_pos + max); diff --git a/src/NEO/ReadData.h b/src/NEO/ReadData.h index 3c273d75dec..fbe297c9ba3 100644 --- a/src/NEO/ReadData.h +++ b/src/NEO/ReadData.h @@ -9,7 +9,7 @@ #include #include -#include "../Data.h" +#include "Data.h" #include "../BinaryCoding.h" namespace TW { @@ -27,7 +27,7 @@ template static std::vector concat(const std::vector& v1, const std::vector& v2) { std::vector v(v1); v.insert(v.end(), v2.begin(), v2.end()); - return std::move(v); + return v; } } // namespace TW diff --git a/src/NEO/Script.h b/src/NEO/Script.h index 64d47982754..80090432607 100644 --- a/src/NEO/Script.h +++ b/src/NEO/Script.h @@ -5,7 +5,7 @@ // file LICENSE at the root of the source code distribution tree. #pragma once -#include "../Data.h" +#include "Data.h" namespace TW::NEO { diff --git a/src/NEO/Signer.cpp b/src/NEO/Signer.cpp index 1449f328d7d..1824af1b940 100644 --- a/src/NEO/Signer.cpp +++ b/src/NEO/Signer.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -6,19 +6,15 @@ #include "Signer.h" #include "Script.h" -#include "../Hash.h" #include "../HexCoding.h" -#include "../PrivateKey.h" -#include "../PublicKey.h" -#include "../proto/NEO.pb.h" -#include "../proto/Common.pb.h" -using namespace TW; -using namespace TW::NEO; using namespace std; +using namespace TW; +namespace TW::NEO { -Signer::Signer(const PrivateKey& priKey) : privateKey(std::move(priKey)) { +Signer::Signer(const PrivateKey& priKey) + : privateKey(std::move(priKey)) { auto pub = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1); publicKey = pub.bytes; address = Address(pub); @@ -82,7 +78,7 @@ Proto::TransactionPlan Signer::plan(const Proto::SigningInput& input) { for (int i = 0; i < input.outputs_size(); i++) { auto* outputPlan = plan.add_outputs(); - if (available.find(input.inputs(i).asset_id()) == available.end() || + if (available.find(input.outputs(i).asset_id()) == available.end() || available[input.outputs(i).asset_id()] < input.outputs(i).amount()) { throw Common::Proto::SigningError(Common::Proto::Error_low_balance); } @@ -224,3 +220,5 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { return output; } + +} // namespace TW::NEO diff --git a/src/NEO/Signer.h b/src/NEO/Signer.h index bb00b08e09e..55c68339ce2 100644 --- a/src/NEO/Signer.h +++ b/src/NEO/Signer.h @@ -8,7 +8,7 @@ #include "Address.h" #include "Transaction.h" -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" #include "../proto/NEO.pb.h" diff --git a/src/NEO/Transaction.cpp b/src/NEO/Transaction.cpp index 66b88d9ef83..5108a85e581 100644 --- a/src/NEO/Transaction.cpp +++ b/src/NEO/Transaction.cpp @@ -1,28 +1,25 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include - #include "../uint256.h" -#include "../Data.h" #include "../Hash.h" #include "Transaction.h" #include "MinerTransaction.h" using namespace std; - using namespace TW; -using namespace TW::NEO; + +namespace TW::NEO { int64_t Transaction::size() const { return serialize().size(); } void Transaction::deserialize(const Data& data, int initial_pos) { - type = (TransactionType) data[initial_pos++]; + type = (TransactionType)data[initial_pos++]; version = data[initial_pos++]; initial_pos = deserializeExclusiveData(data, initial_pos); attributes.clear(); @@ -33,15 +30,15 @@ void Transaction::deserialize(const Data& data, int initial_pos) { Serializable::deserialize(outputs, data, initial_pos); } -Transaction * Transaction::deserializeFrom(const Data& data, int initial_pos) { - Transaction * resp = nullptr; - switch ((TransactionType) data[initial_pos]) { - case TransactionType::TT_MinerTransaction: - resp = new MinerTransaction(); - break; - default: - throw std::invalid_argument("Transaction::deserializeFrom Invalid transaction type"); - break; +Transaction* Transaction::deserializeFrom(const Data& data, int initial_pos) { + Transaction* resp = nullptr; + switch ((TransactionType)data[initial_pos]) { + case TransactionType::TT_MinerTransaction: + resp = new MinerTransaction(); + break; + default: + throw std::invalid_argument("Transaction::deserializeFrom Invalid transaction type"); + break; } resp->deserialize(data, initial_pos); return resp; @@ -49,27 +46,27 @@ Transaction * Transaction::deserializeFrom(const Data& data, int initial_pos) { Data Transaction::serialize() const { Data resp; - resp.push_back((byte) type); + resp.push_back((byte)type); resp.push_back(version); append(resp, serializeExclusiveData()); append(resp, Serializable::serialize(attributes)); append(resp, Serializable::serialize(inInputs)); append(resp, Serializable::serialize(outputs)); - if(witnesses.size()) - { - resp.push_back((byte) witnesses.size()); - for (const auto& witnesse : witnesses) - append(resp, witnesse.serialize()); - } + if (witnesses.size()) { + resp.push_back((byte)witnesses.size()); + for (const auto& witnesse : witnesses) + append(resp, witnesse.serialize()); + } return resp; } -bool Transaction::operator==(const Transaction &other) const { +bool Transaction::operator==(const Transaction& other) const { if (this == &other) { return true; } + // clang-format off return this->type == other.type && this->version == other.version && this->attributes.size() == other.attributes.size() @@ -78,6 +75,7 @@ bool Transaction::operator==(const Transaction &other) const { && this->attributes == other.attributes && this->inInputs == other.inInputs && this->outputs == other.outputs; + // clang-format on } Data Transaction::getHash() const { @@ -87,3 +85,5 @@ Data Transaction::getHash() const { uint256_t Transaction::getHashUInt256() const { return load(getHash()); } + +} // namespace TW::NEO diff --git a/src/NEO/Transaction.h b/src/NEO/Transaction.h index 50a311e3d9a..7523995398c 100644 --- a/src/NEO/Transaction.h +++ b/src/NEO/Transaction.h @@ -33,7 +33,7 @@ class Transaction : public Serializable { bool operator==(const Transaction &other) const; - virtual int deserializeExclusiveData(const Data& data, int initial_pos = 0) { return initial_pos; } + virtual int deserializeExclusiveData([[maybe_unused]] const Data& data, int initial_pos = 0) { return initial_pos; } virtual Data serializeExclusiveData() const { return Data(); } Data getHash() const; diff --git a/src/NEO/TransactionAttribute.h b/src/NEO/TransactionAttribute.h index 8d8c5976c0f..e1ac1235651 100644 --- a/src/NEO/TransactionAttribute.h +++ b/src/NEO/TransactionAttribute.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -6,56 +6,103 @@ #pragma once -#include "TransactionAttributeUsage.h" +#include "Constants.h" #include "ISerializable.h" #include "Serializable.h" -#include "../Data.h" +#include "TransactionAttributeUsage.h" +#include "Data.h" namespace TW::NEO { class TransactionAttribute : public Serializable { - public: +public: TransactionAttributeUsage usage = TAU_ContractHash; - Data data; + Data _data; virtual ~TransactionAttribute() {} int64_t size() const override { - return 1 + data.size(); + switch (usage) { + case TransactionAttributeUsage::TAU_ContractHash: + case TransactionAttributeUsage::TAU_ECDH02: + case TransactionAttributeUsage::TAU_ECDH03: + case TransactionAttributeUsage::TAU_Vote: + return 1 + contractHashSize; + case TransactionAttributeUsage::TAU_Script: + return 1 + scriptHashSize; + default: + if (usage >= TransactionAttributeUsage::TAU_Hash1 && + usage <= TransactionAttributeUsage::TAU_Hash15) { + return 1 + contractHashSize; + } + return 1 + varIntSize(_data.size()) + _data.size(); + } } void deserialize(const Data& data, int initial_pos = 0) override { - if (data.size() < initial_pos + 1) { + if (static_cast(data.size()) < initial_pos + 1) { throw std::invalid_argument("Invalid data for deserialization"); } - usage = (TransactionAttributeUsage) data[initial_pos]; - if (usage == TransactionAttributeUsage::TAU_ContractHash || usage == TransactionAttributeUsage::TAU_Vote || - (usage >= TransactionAttributeUsage::TAU_Hash1 && usage <= TransactionAttributeUsage::TAU_Hash15)) { - this->data = readBytes(data, 32, initial_pos + 1); - } else if (usage == TransactionAttributeUsage::TAU_ECDH02 || - usage == TransactionAttributeUsage::TAU_ECDH03) { - this->data = readBytes(data, 32, initial_pos + 1); - } else if (usage == TransactionAttributeUsage::TAU_Script) { - this->data = readBytes(data, 20, initial_pos + 1); - } else if (usage == TransactionAttributeUsage::TAU_DescriptionUrl) { - this->data = readBytes(data, 1, initial_pos + 1); - } else if (usage == TransactionAttributeUsage::TAU_Description || - usage >= TransactionAttributeUsage::TAU_Remark) { - this->data = readBytes(data, int(data.size()) - 1 - initial_pos, initial_pos + 1); - } else { + + // see: https://github.com/neo-project/neo/blob/v2.12.0/neo/Network/P2P/Payloads/TransactionAttribute.cs#L32 + usage = (TransactionAttributeUsage)data[initial_pos]; + switch (usage) { + case TransactionAttributeUsage::TAU_ECDH02: + case TransactionAttributeUsage::TAU_ECDH03: { + this->_data = concat({(TW::byte)usage}, readBytes(data, contractHashSize, initial_pos + 1)); + break; + } + + case TransactionAttributeUsage::TAU_Script: { + this->_data = readBytes(data, scriptHashSize, initial_pos + 1); + break; + } + + case TransactionAttributeUsage::TAU_DescriptionUrl: + case TransactionAttributeUsage::TAU_Description: + case TransactionAttributeUsage::TAU_Remark: { + this->_data = readVarBytes(data, initial_pos + 1); + break; + } + + default: + if (usage == TransactionAttributeUsage::TAU_ContractHash || + usage == TransactionAttributeUsage::TAU_Vote || + (usage >= TransactionAttributeUsage::TAU_Hash1 && usage <= TransactionAttributeUsage::TAU_Hash15)) { + this->_data = readBytes(data, contractHashSize, initial_pos + 1); + break; + } throw std::invalid_argument("TransactionAttribute Deserialize FormatException"); } } Data serialize() const override { - return concat(Data({static_cast(usage)}), data); + Data result; + result.push_back((TW::byte)usage); + + // see: https://github.com/neo-project/neo/blob/v2.12.0/neo/Network/P2P/Payloads/TransactionAttribute.cs#L49 + if (usage == TransactionAttributeUsage::TAU_DescriptionUrl || + usage == TransactionAttributeUsage::TAU_Description || + usage >= TransactionAttributeUsage::TAU_Remark) { + Data resp; + encodeVarInt((uint64_t)_data.size(), resp); + result.insert(result.end(), resp.begin(), resp.end()); + } + if (usage == TransactionAttributeUsage::TAU_ECDH02 || + usage == TransactionAttributeUsage::TAU_ECDH03) { + result.insert(result.end(), _data.begin() + 1, _data.begin() + 1 + contractHashSize); + } else { + result.insert(result.end(), _data.begin(), _data.end()); + } + + return result; } bool operator==(const TransactionAttribute &other) const { return this->usage == other.usage - && this->data.size() == other.data.size() - && this->data == other.data; + && _data.size() == other._data.size() + && _data == other._data; } }; -} \ No newline at end of file +} // namespace TW::NEO diff --git a/src/NEO/TransactionOutput.h b/src/NEO/TransactionOutput.h index 86b51af36bc..cc52b032301 100644 --- a/src/NEO/TransactionOutput.h +++ b/src/NEO/TransactionOutput.h @@ -7,7 +7,8 @@ #pragma once #include "../uint256.h" -#include "../Data.h" +#include "Constants.h" +#include "Data.h" #include "../BinaryCoding.h" #include "ReadData.h" #include "ISerializable.h" @@ -17,10 +18,6 @@ namespace TW::NEO { class TransactionOutput : public Serializable { public: - static const size_t assetIdSize = 32; - static const size_t valueSize = 8; - static const size_t scriptHashSize = 20; - uint256_t assetId; int64_t value = 0; uint256_t scriptHash; @@ -28,7 +25,7 @@ class TransactionOutput : public Serializable { virtual ~TransactionOutput() {} int64_t size() const override { - return store(assetId).size() + valueSize + store(scriptHash).size(); + return store(assetId, assetIdSize).size() + valueSize + store(scriptHash, scriptHashSize).size(); } void deserialize(const Data& data, int initial_pos = 0) override { @@ -38,9 +35,9 @@ class TransactionOutput : public Serializable { } Data serialize() const override { - auto resp = store(assetId); + auto resp = store(assetId, assetIdSize); encode64LE(value, resp); - return concat(resp, store(scriptHash)); + return concat(resp, store(scriptHash, scriptHashSize)); } bool operator==(const TransactionOutput &other) const { @@ -50,4 +47,4 @@ class TransactionOutput : public Serializable { } }; -} \ No newline at end of file +} // namespace TW::NEO diff --git a/src/NEO/Witness.h b/src/NEO/Witness.h index 6f0e7c9d139..2562bbb7f4e 100644 --- a/src/NEO/Witness.h +++ b/src/NEO/Witness.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "ISerializable.h" #include "Serializable.h" diff --git a/src/NULS/Address.cpp b/src/NULS/Address.cpp index 93917e257c5..494131dcb1e 100644 --- a/src/NULS/Address.cpp +++ b/src/NULS/Address.cpp @@ -12,7 +12,8 @@ #include "../HexCoding.h" using namespace TW; -using namespace TW::NULS; + +namespace TW::NULS { const std::string Address::prefix("NULSd"); const std::array Address::mainnetId = {0x01, 0x00}; @@ -21,7 +22,7 @@ bool Address::isValid(const std::string& string) { if (string.empty()) { return false; } - if (string.length() <= prefix.length()) { + if (string.length() <= prefix.length()) { return false; } @@ -46,15 +47,16 @@ Address::Address(const TW::PublicKey& publicKey) { bytes[1] = mainnetId[1]; // Address Type bytes[2] = addressType; - ecdsa_get_pubkeyhash(publicKey.bytes.data(), HASHER_SHA2_RIPEMD, &bytes[3]); + //ecdsa_get_pubkeyhash(publicKey.bytes.data(), HASHER_SHA2_RIPEMD, bytes.begin() + 3); + ecdsa_get_pubkeyhash(publicKey.bytes.data(), HASHER_SHA2_RIPEMD, &bytes[3]);//win bytes[23] = checksum(bytes); } Address::Address(const std::string& string) { - if (false == isValid(string)){ + if (false == isValid(string)) { throw std::invalid_argument("Invalid address string"); } - std::string address = string.substr(prefix.length(), string.length() - prefix.length()); + std::string address = string.substr(prefix.length(), string.length() - prefix.length()); const auto decoded = Base58::bitcoin.decode(address); std::copy(decoded.begin(), decoded.end(), bytes.begin()); } @@ -68,10 +70,11 @@ uint8_t Address::type() const { } std::string Address::string() const { - return prefix + Base58::bitcoin.encode(&bytes[0], &bytes[0] + bytes.size()); + return prefix + Base58::bitcoin.encode(&bytes[0], &bytes[0] + bytes.size());//win + //return prefix + Base58::bitcoin.encode(bytes.begin(), bytes.end()); } -uint8_t Address::checksum(std::array& byteArray) const{ +uint8_t Address::checksum(std::array& byteArray) const { uint8_t checkSum = 0x00; for (int i = 0; i < 23; ++i) { checkSum ^= byteArray[i]; @@ -79,4 +82,4 @@ uint8_t Address::checksum(std::array& byteArray) const{ return checkSum; } - +} // namespace TW::NULS diff --git a/src/NULS/BinaryCoding.h b/src/NULS/BinaryCoding.h index 077bf30cf85..51de36f5ec9 100644 --- a/src/NULS/BinaryCoding.h +++ b/src/NULS/BinaryCoding.h @@ -6,14 +6,15 @@ #pragma once -#include "../uint256.h" +#include "Address.h" #include "../BinaryCoding.h" -#include "../proto/NULS.pb.h" #include "../HexCoding.h" -#include "Address.h" +#include "../proto/NULS.pb.h" +#include "../uint256.h" using namespace TW; -using namespace TW::NULS; + +namespace TW::NULS { static inline void serializerRemark(std::string& remark, Data& data) { encodeVarInt(remark.length(), data); @@ -21,7 +22,7 @@ static inline void serializerRemark(std::string& remark, Data& data) { } static inline void serializerInput(const Proto::TransactionCoinFrom& input, Data& data) { - encodeVarInt(1, data); //there is one coinFrom + encodeVarInt(1, data); // there is one coinFrom const auto& fromAddress = input.from_address(); if (!NULS::Address::isValid(fromAddress)) { throw std::invalid_argument("Invalid address"); @@ -39,7 +40,7 @@ static inline void serializerInput(const Proto::TransactionCoinFrom& input, Data } static inline void serializerOutput(const Proto::TransactionCoinTo& output, Data& data) { - encodeVarInt(1, data); //there is one coinTo + encodeVarInt(1, data); // there is one coinTo const auto& toAddress = output.to_address(); if (!NULS::Address::isValid(toAddress)) { @@ -65,10 +66,10 @@ static inline Data makeTransactionSignature(PrivateKey& privateKey, Data& txHash Data transactionSignature = Data(); encodeVarInt(pubKey.bytes.size(), transactionSignature); std::copy(pubKey.bytes.begin(), pubKey.bytes.end(), std::back_inserter(transactionSignature)); - auto signature = privateKey.signAsDER(txHash, TWCurve::TWCurveSECP256k1); + auto signature = privateKey.signAsDER(txHash); encodeVarInt(signature.size(), transactionSignature); std::copy(signature.begin(), signature.end(), std::back_inserter(transactionSignature)); return transactionSignature; } - +} // namespace TW::NULS diff --git a/src/NULS/Entry.cpp b/src/NULS/Entry.cpp index 2dee29d9e31..db59dba1bb1 100644 --- a/src/NULS/Entry.cpp +++ b/src/NULS/Entry.cpp @@ -9,19 +9,22 @@ #include "Address.h" #include "Signer.h" -using namespace TW::NULS; using namespace std; +namespace TW::NULS { + // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +} // namespace TW::NULS diff --git a/src/NULS/Entry.h b/src/NULS/Entry.h index 283acb94797..b11bcc629bb 100644 --- a/src/NULS/Entry.h +++ b/src/NULS/Entry.h @@ -12,12 +12,11 @@ namespace TW::NULS { /// Entry point for implementation of NULS coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeNULS}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::NULS diff --git a/src/NULS/Signer.cpp b/src/NULS/Signer.cpp index 1e0c903d2a0..77b83e6ebae 100644 --- a/src/NULS/Signer.cpp +++ b/src/NULS/Signer.cpp @@ -12,7 +12,8 @@ #include "../PrivateKey.h" using namespace TW; -using namespace TW::NULS; + +namespace TW::NULS { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto output = Proto::SigningOutput(); @@ -20,20 +21,21 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto signer = Signer(input); auto data = signer.sign(); output.set_encoded(data.data(), data.size()); + } catch (...) { } - catch(...) {} return output; } -Signer::Signer(const Proto::SigningInput& input) : input(input) { +Signer::Signer(const Proto::SigningInput& input) + : input(input) { Proto::TransactionCoinFrom coinFrom; coinFrom.set_from_address(input.from()); coinFrom.set_assets_chainid(input.chain_id()); coinFrom.set_assets_id(input.idassets_id()); - //need to update with amount + fee + // need to update with amount + fee coinFrom.set_id_amount(input.amount()); coinFrom.set_nonce(input.nonce()); - //default unlocked + // default unlocked coinFrom.set_locked(0); *tx.mutable_input() = coinFrom; @@ -88,13 +90,13 @@ Data Signer::sign() const { encode16LE(static_cast(tx.type()), dataRet); // Timestamp encode32LE(tx.timestamp(), dataRet); - // Remark + // Remark std::string remark = tx.remark(); serializerRemark(remark, dataRet); // txData encodeVarInt(0, dataRet); - //coinFrom and coinTo size + // coinFrom and coinTo size encodeVarInt(TRANSACTION_INPUT_SIZE + TRANSACTION_OUTPUT_SIZE, dataRet); // CoinData Input @@ -105,7 +107,7 @@ Data Signer::sign() const { // Calc transaction hash Data txHash = calcTransactionDigest(dataRet); - + Data privKey = data(input.private_key()); auto priv = PrivateKey(privKey); auto transactionSignature = makeTransactionSignature(priv, txHash); @@ -117,7 +119,7 @@ Data Signer::sign() const { uint32_t Signer::CalculatorTransactionSize(uint32_t inputCount, uint32_t outputCount, uint32_t remarkSize) const { uint32_t size = TRANSACTION_FIX_SIZE + TRANSACTION_SIG_MAX_SIZE + TRANSACTION_INPUT_SIZE * inputCount + - TRANSACTION_OUTPUT_SIZE * outputCount + remarkSize; + TRANSACTION_OUTPUT_SIZE * outputCount + remarkSize; return size; } @@ -127,4 +129,6 @@ uint64_t Signer::CalculatorTransactionFee(uint64_t size) const { fee += MIN_PRICE_PRE_1024_BYTES; } return fee; -} \ No newline at end of file +} + +} // namespace TW::NULS diff --git a/src/Nano/Address.cpp b/src/Nano/Address.cpp index ba16435bfd6..9788472b089 100644 --- a/src/Nano/Address.cpp +++ b/src/Nano/Address.cpp @@ -1,5 +1,5 @@ // Copyright © 2019 Mart Roosmaa. -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -10,7 +10,7 @@ #include -using namespace TW::Nano; +namespace TW::Nano { const std::string kPrefixNano{"nano_"}; const std::string kPrefixXrb{"xrb_"}; @@ -21,14 +21,12 @@ bool Address::isValid(const std::string& address) { valid = nano_validate_address( kPrefixNano.c_str(), kPrefixNano.length(), address.c_str(), address.length(), - nullptr - ); + nullptr); if (!valid) { valid = nano_validate_address( kPrefixXrb.c_str(), kPrefixXrb.length(), address.c_str(), address.length(), - nullptr - ); + nullptr); } return valid; @@ -68,11 +66,13 @@ std::string Address::string() const { std::array out = {0}; size_t count = nano_get_address( - bytes.data(), - kPrefixNano.c_str(), kPrefixNano.length(), - out.data(), out.size()); + bytes.data(), + kPrefixNano.c_str(), kPrefixNano.length(), + out.data(), out.size()); // closing \0 assert(count < out.size()); out[count] = 0; - return std::string(out.data()); + return {out.data()}; } + +} // namespace TW::Nano diff --git a/src/Nano/Entry.cpp b/src/Nano/Entry.cpp index 2c0493f1cd0..6cfdb73e52b 100644 --- a/src/Nano/Entry.cpp +++ b/src/Nano/Entry.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,23 +9,29 @@ #include "Address.h" #include "Signer.h" -using namespace TW::Nano; -using namespace std; +namespace TW::Nano { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + const auto addr = Address(address); + return {addr.bytes.begin(), addr.bytes.end()}; +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { +std::string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { return Signer::signJSON(json, key); } + +} // namespace TW::Nano diff --git a/src/Nano/Entry.h b/src/Nano/Entry.h index a444938ad4a..1a27f029e23 100644 --- a/src/Nano/Entry.h +++ b/src/Nano/Entry.h @@ -12,14 +12,14 @@ namespace TW::Nano { /// Entry point for implementation of Nano coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeNano}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual bool supportsJSONSigning() const { return true; } - virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool supportsJSONSigning() const { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; }; } // namespace TW::Nano diff --git a/src/Nano/Signer.h b/src/Nano/Signer.h index ea41ff542b2..8ca6fe318d5 100644 --- a/src/Nano/Signer.h +++ b/src/Nano/Signer.h @@ -7,7 +7,7 @@ #pragma once #include "Address.h" -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" #include diff --git a/src/Nebulas/Address.cpp b/src/Nebulas/Address.cpp index bbc81447f43..4b113b6cd8c 100644 --- a/src/Nebulas/Address.cpp +++ b/src/Nebulas/Address.cpp @@ -9,7 +9,7 @@ #include "../Hash.h" #include "../HexCoding.h" -using namespace TW::Nebulas; +namespace TW::Nebulas { bool Address::isValid(const std::string& string) { auto data = Base58::bitcoin.decode(string); @@ -46,14 +46,14 @@ Address::Address(const Data& data) { std::copy(data.begin(), data.end(), bytes.begin()); } -Address::Address(const PublicKey &publicKey) { +Address::Address(const PublicKey& publicKey) { if (publicKey.type != TWPublicKeyTypeSECP256k1Extended) { throw std::invalid_argument("Nebulas::Address needs an extended SECP256k1 public key."); } const auto data = publicKey.hash( {Address::AddressPrefix, Address::NormalType}, - static_cast(Hash::sha3_256ripemd), false); - + Hash::HasherSha3_256ripemd, false); + std::copy(data.begin(), data.end(), bytes.begin()); auto checksum = Hash::sha3_256(data); std::copy(checksum.begin(), checksum.begin() + 4, bytes.begin() + 22); @@ -62,3 +62,5 @@ Address::Address(const PublicKey &publicKey) { std::string Address::string() const { return Base58::bitcoin.encode(bytes); } + +} // namespace TW::Nebulas diff --git a/src/Nebulas/Entry.cpp b/src/Nebulas/Entry.cpp index a432250c5da..a8b4571e527 100644 --- a/src/Nebulas/Entry.cpp +++ b/src/Nebulas/Entry.cpp @@ -9,19 +9,21 @@ #include "Address.h" #include "Signer.h" -using namespace TW::Nebulas; using namespace std; +namespace TW::Nebulas { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +} // namespace TW::Nebulas diff --git a/src/Nebulas/Entry.h b/src/Nebulas/Entry.h index 5de8d9fd91b..0bdcaded71c 100644 --- a/src/Nebulas/Entry.h +++ b/src/Nebulas/Entry.h @@ -12,12 +12,11 @@ namespace TW::Nebulas { /// Entry point for implementation of Nebulas coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeNebulas}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::Nebulas diff --git a/src/Nebulas/Signer.cpp b/src/Nebulas/Signer.cpp index 383ae6b3575..1d0159eeed4 100644 --- a/src/Nebulas/Signer.cpp +++ b/src/Nebulas/Signer.cpp @@ -9,21 +9,21 @@ #include "../HexCoding.h" using namespace TW; -using namespace TW::Nebulas; + +namespace TW::Nebulas { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto signer = Signer(load(input.chain_id())); auto tx = Transaction(Address(input.from_address()), - load(input.nonce()), - load(input.gas_price()), - load(input.gas_limit()), - Address(input.to_address()), - load(input.amount()), - load(input.timestamp()), - input.payload() - ); - + load(input.nonce()), + load(input.gas_price()), + load(input.gas_limit()), + Address(input.to_address()), + load(input.amount()), + load(input.timestamp()), + input.payload()); + auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); signer.sign(privateKey, tx); @@ -34,7 +34,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { return output; } -void Signer::sign(const PrivateKey &privateKey, Transaction &transaction) const noexcept { +void Signer::sign(const PrivateKey& privateKey, Transaction& transaction) const noexcept { transaction.hash = this->hash(transaction); transaction.chainID = chainID; transaction.algorithm = 1; @@ -42,12 +42,12 @@ void Signer::sign(const PrivateKey &privateKey, Transaction &transaction) const transaction.serializeToRaw(); } -Data Signer::hash(const Transaction &transaction) const noexcept { +Data Signer::hash(const Transaction& transaction) const noexcept { auto encoded = Data(); auto payload = Data(); auto* data = Transaction::newPayloadData(transaction.payload); payload.resize(data->ByteSizeLong()); - data->SerializePartialToArray(payload.data(),(int)payload.size()); + data->SerializePartialToArray(payload.data(), (int)payload.size()); delete data; encoded.insert(encoded.end(), transaction.from.bytes.begin(), transaction.from.bytes.end()); @@ -61,3 +61,5 @@ Data Signer::hash(const Transaction &transaction) const noexcept { encode256BE(encoded, transaction.gasLimit, 128); return Hash::sha3_256(encoded); } + +} // namespace TW::Nebulas diff --git a/src/Nebulas/Signer.h b/src/Nebulas/Signer.h index 86a74d2bed2..2beed1448ce 100644 --- a/src/Nebulas/Signer.h +++ b/src/Nebulas/Signer.h @@ -7,7 +7,7 @@ #pragma once #include "Transaction.h" -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" #include "../proto/Nebulas.pb.h" #include "../uint256.h" diff --git a/src/Nebulas/Transaction.cpp b/src/Nebulas/Transaction.cpp index c243e3cd769..ce70cb617da 100644 --- a/src/Nebulas/Transaction.cpp +++ b/src/Nebulas/Transaction.cpp @@ -6,65 +6,73 @@ // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include + #include "Transaction.h" #include "../HexCoding.h" +#include using namespace TW; -using namespace TW::Nebulas; - -const char *Transaction::TxPayloadBinaryType = "binary"; -const char *Transaction::TxPayloadDeployType = "deploy"; -const char *Transaction::TxPayloadCallType = "call"; std::string htmlescape(const std::string& str) { std::string result; - for(size_t i=0; i': result += "\\u003e"; break; - case '<': result += "\\u003c"; break; - case 0x20: - if(i+1 < str.size()) { - if(str[i+1]==0x28) { - result += "\\u2028"; - ++i; - break; - } - else if (str[i+1]==0x29) { - result += "\\u2029"; - ++i; - break; - } + for (size_t i = 0; i < str.size(); ++i) { + switch (str[i]) { + case '&': + result += "\\u0026"; + break; + case '>': + result += "\\u003e"; + break; + case '<': + result += "\\u003c"; + break; + case 0x20: + if (i + 1 < str.size()) { + if (str[i + 1] == 0x28) { + result += "\\u2028"; + ++i; + break; + } else if (str[i + 1] == 0x29) { + result += "\\u2029"; + ++i; + break; } - default: result += str[i]; break; + } + default: + result += str[i]; + break; } } return result; } -Proto::Data* Transaction::newPayloadData(const std::string& payload){ +namespace TW::Nebulas { + +const char* Transaction::TxPayloadBinaryType = "binary"; +const char* Transaction::TxPayloadDeployType = "deploy"; +const char* Transaction::TxPayloadCallType = "call"; + +Proto::Data* Transaction::newPayloadData(const std::string& payload) { auto* data = new Proto::Data(); data->set_type(Transaction::TxPayloadBinaryType); nlohmann::json payloadData; - if(!payload.empty()) { + if (!payload.empty()) { auto json = nlohmann::json::parse(payload); - if(json.find("binary")!=json.end()) { + if (json.find("binary") != json.end()) { std::string binary_data = json["binary"]; - auto buff = Data(binary_data.begin(),binary_data.end()); + auto buff = Data(binary_data.begin(), binary_data.end()); payloadData["Data"]["type"] = "Buffer"; payloadData["Data"]["data"] = nlohmann::json(buff); } } - if(!payloadData.empty()) + if (!payloadData.empty()) data->set_payload(htmlescape(payloadData.dump())); return data; } -void Transaction::serializeToRaw(){ - if(signature.empty()) { +void Transaction::serializeToRaw() { + if (signature.empty()) { throw std::logic_error("The transaction is unsigned!"); } @@ -74,23 +82,25 @@ void Transaction::serializeToRaw(){ auto value = Data(); auto gas_price = Data(); auto gas_limit = Data(); - tx.set_hash(reinterpret_cast(hash.data()),hash.size()); - tx.set_from(from.bytes.data(),from.size); - tx.set_to(to.bytes.data(),to.size); + tx.set_hash(reinterpret_cast(hash.data()), hash.size()); + tx.set_from(from.bytes.data(), from.size); + tx.set_to(to.bytes.data(), to.size); encode256BE(value, amount, 128); - tx.set_value(value.data(),value.size()); + tx.set_value(value.data(), value.size()); tx.set_nonce((uint64_t)nonce); tx.set_timestamp((int64_t)timestamp); tx.set_allocated_data(data); tx.set_chain_id((uint32_t)chainID); encode256BE(gas_price, gasPrice, 128); - tx.set_gas_price(gas_price.data(),gas_price.size()); + tx.set_gas_price(gas_price.data(), gas_price.size()); encode256BE(gas_limit, gasLimit, 128); - tx.set_gas_limit(gas_limit.data(),gas_limit.size()); - + tx.set_gas_limit(gas_limit.data(), gas_limit.size()); + tx.set_alg((uint32_t)algorithm); - tx.set_sign(reinterpret_cast(signature.data()),signature.size()); + tx.set_sign(reinterpret_cast(signature.data()), signature.size()); raw.resize(tx.ByteSizeLong()); - tx.SerializeToArray(raw.data(),(int)raw.size()); -} \ No newline at end of file + tx.SerializeToArray(raw.data(), (int)raw.size()); +} + +} // namespace TW::Nebulas diff --git a/src/Nervos/Address.cpp b/src/Nervos/Address.cpp new file mode 100644 index 00000000000..7740a96657e --- /dev/null +++ b/src/Nervos/Address.cpp @@ -0,0 +1,177 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Address.h" +#include "Constants.h" + +#include "../Bech32.h" +#include "../Coin.h" + +#include +#include +#include + +namespace TW::Nervos { + +[[nodiscard]] bool Address::isValid(const std::string& string) noexcept { + return Address::isValid(string, HRP_NERVOS); +} + +[[nodiscard]] bool Address::isValid(const std::string& string, const char* hrp) noexcept { + return Address().decode(string, hrp); +} + +Address::Address(const std::string& string, const char* hrp) { + if (!decode(string, hrp)) { + throw std::invalid_argument("Invalid address string"); + } +} + +bool Address::decode(const std::string& string, const char* hrp) noexcept { + _hrp = hrp; + auto decoded = Bech32::decode(string); + auto&& [decodedHrp, decodedData, decodedVariant] = decoded; + if (decodedHrp.compare(hrp)) { + return false; + } + Data decodedPayload; + if (!Bech32::convertBits<5, 8, false>(decodedPayload, decodedData)) { + return false; + } + if (decodedPayload.empty()) { + return false; + } + addressType = AddressType(decodedPayload[0]); + switch (addressType) { + case AddressType::FullVersion: { + size_t codeHashOffset = 1; + size_t codeHashSize = 32; + size_t hashTypeOffset = codeHashOffset + codeHashSize; + size_t hashTypeSize = 1; + size_t argsOffset = hashTypeOffset + hashTypeSize; + if (decodedVariant != Bech32::ChecksumVariant::Bech32M) { + return false; + } + if (decodedPayload.size() < argsOffset) { + return false; + } + codeHashIndex = -1; + codeHash = Data(decodedPayload.begin() + codeHashOffset, + decodedPayload.begin() + codeHashOffset + codeHashSize); + hashType = HashType(decodedPayload[hashTypeOffset]); + args = Data(decodedPayload.begin() + argsOffset, decodedPayload.end()); + break; + } + case AddressType::HashIdx: { + size_t codeHashIndexOffset = 1; + size_t codeHashIndexSize = 1; + size_t argsOffset = codeHashIndexOffset + codeHashIndexSize; + size_t argsSize = 20; + if (decodedVariant != Bech32::ChecksumVariant::Bech32) { + return false; + } + if (decodedPayload.size() != argsOffset + argsSize) { + return false; + } + codeHashIndex = decodedPayload[codeHashIndexOffset]; + if (codeHashIndex != 0) { + return false; + } + codeHash = Constants::gSecp256k1CodeHash; + hashType = HashType::Type1; + args = Data(decodedPayload.begin() + argsOffset, decodedPayload.end()); + break; + } + case AddressType::DataCodeHash: + case AddressType::TypeCodeHash: { + size_t codeHashOffset = 1; + size_t codeHashSize = 32; + size_t argsOffset = codeHashOffset + codeHashSize; + if (decodedVariant != Bech32::ChecksumVariant::Bech32) { + return false; + } + if (decodedPayload.size() < argsOffset) { + return false; + } + codeHashIndex = -1; + codeHash = Data(decodedPayload.begin() + codeHashOffset, + decodedPayload.begin() + codeHashOffset + codeHashSize); + hashType = addressType == AddressType::DataCodeHash ? HashType::Data0 : HashType::Type1; + args = Data(decodedPayload.begin() + argsOffset, decodedPayload.end()); + break; + } + default: { + return false; + } + } + return true; +} + +Address::Address(const PublicKey& publicKey, const char* hrp) + : _hrp(hrp) { + if (publicKey.type != TWPublicKeyTypeSECP256k1) { + throw std::invalid_argument("Nervos::Address needs a SECP256k1 public key."); + } + addressType = AddressType::FullVersion; + codeHashIndex = -1; + codeHash = Constants::gSecp256k1CodeHash; + hashType = HashType::Type1; + Data publicKeyHash = Hash::blake2b(publicKey.bytes, 32, Constants::gHashPersonalization); + Data truncatedPublicKeyHash = Data(publicKeyHash.begin(), publicKeyHash.begin() + 20); + args = truncatedPublicKeyHash; +} + +std::string Address::string() const { + auto data = Data(); + data.emplace_back(addressType); + Bech32::ChecksumVariant checksumVariant; + switch (addressType) { + case AddressType::FullVersion: { + data.insert(data.end(), codeHash.begin(), codeHash.end()); + data.emplace_back(hashType); + data.insert(data.end(), args.begin(), args.end()); + checksumVariant = Bech32::ChecksumVariant::Bech32M; + break; + } + case AddressType::HashIdx: { + data.emplace_back(codeHashIndex); + data.insert(data.end(), args.begin(), args.end()); + checksumVariant = Bech32::ChecksumVariant::Bech32; + break; + } + case AddressType::DataCodeHash: + case AddressType::TypeCodeHash: { + data.insert(data.end(), codeHash.begin(), codeHash.end()); + data.insert(data.end(), args.begin(), args.end()); + checksumVariant = Bech32::ChecksumVariant::Bech32; + break; + } + default: { + return ""; + } + } + Data payload; + if (!Bech32::convertBits<8, 5, true>(payload, data)) { + return ""; + } + return Bech32::encode(_hrp, payload, checksumVariant); +} + +std::string Address::hashTypeString() const { + switch (hashType) { + case HashType::Data0: { + return HashTypeString[0]; + } + case HashType::Type1: { + return HashTypeString[1]; + } + case HashType::Data1: { + return HashTypeString[2]; + } + } +} + +} // namespace TW::Nervos diff --git a/src/Nervos/Address.h b/src/Nervos/Address.h new file mode 100644 index 00000000000..3b8c28fab7c --- /dev/null +++ b/src/Nervos/Address.h @@ -0,0 +1,80 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include + +#include "Data.h" +#include "../PublicKey.h" + +#include + +namespace TW::Nervos { + +enum HashType { + Data0 = 0, + Type1 = 1, + Data1 = 2 +}; + +static const char* HashTypeString[] { + "data", + "type", + "data1" +}; + +enum AddressType { + FullVersion = 0, // full version identifies the hash_type + HashIdx = 1, // short version for locks with popular codehash, deprecated + DataCodeHash = 2, // full version with hash type 'Data', deprecated + TypeCodeHash = 4, // full version with hash type 'Type', deprecated +}; + +class Address { +public: + const char* _hrp; + AddressType addressType; + TW::byte codeHashIndex; + Data codeHash; + HashType hashType; + Data args; + + /// Determines whether a string makes a valid address. + [[nodiscard]] static bool isValid(const std::string& string) noexcept; + [[nodiscard]] static bool isValid(const std::string& string, const char* hrp) noexcept; + + /// Initializes a Nervos address with a string representation. + explicit Address(const std::string& string) : Address(string, HRP_NERVOS) {} + explicit Address(const std::string& string, const char* hrp); + + /// Initializes a Nervos address with a public key. + explicit Address(const PublicKey& publicKey) : Address(publicKey, HRP_NERVOS) {} + explicit Address(const PublicKey& publicKey, const char* hrp); + + /// Returns a string representation of the address. + std::string string() const; + + std::string hashTypeString() const; + +private: + Address() = default; + + // Decodes address from string + bool decode(const std::string& string, const char* hrp) noexcept; +}; + +inline bool operator==(const Address& lhs, const Address& rhs) { + return (lhs.codeHash == rhs.codeHash) && (lhs.hashType == rhs.hashType) && + (lhs.args == rhs.args); +} + +} // namespace TW::Nervos + +/// Wrapper for C interface. +struct TWNervosAddress { + TW::Nervos::Address impl; +}; diff --git a/src/Nervos/Cell.h b/src/Nervos/Cell.h new file mode 100644 index 00000000000..750f1b0fc58 --- /dev/null +++ b/src/Nervos/Cell.h @@ -0,0 +1,108 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "OutPoint.h" +#include "Script.h" +#include "../proto/Nervos.pb.h" + +#include + +namespace TW::Nervos { + +struct Cell { + OutPoint outPoint; + uint64_t capacity; + Script lock; + Script type; + Data data; + uint64_t blockNumber; + Data blockHash; + uint64_t since; + Data inputType; + Data outputType; + + Cell() = default; + + // Copy constructor + Cell(const Cell& cell) + : outPoint(cell.outPoint) + , capacity(cell.capacity) + , lock(cell.lock) + , type(cell.type) + , data(cell.data) + , blockNumber(cell.blockNumber) + , blockHash(cell.blockHash) + , since(cell.since) + , inputType(cell.inputType) + , outputType(cell.outputType) {} + + // Move constructor + Cell(Cell&& cell) + : outPoint(std::move(cell.outPoint)) + , capacity(cell.capacity) + , lock(std::move(cell.lock)) + , type(std::move(cell.type)) + , data(std::move(cell.data)) + , blockNumber(cell.blockNumber) + , blockHash(std::move(cell.blockHash)) + , since(cell.since) + , inputType(std::move(cell.inputType)) + , outputType(std::move(cell.outputType)) {} + + // Copy assignment operator + Cell& operator=(const Cell& cell) { + outPoint = cell.outPoint; + capacity = cell.capacity; + lock = cell.lock; + type = cell.type; + data = cell.data; + blockNumber = cell.blockNumber; + blockHash = cell.blockHash; + since = cell.since; + inputType = cell.inputType; + outputType = cell.outputType; + return *this; + } + + Cell(const Proto::Cell& cell) + : outPoint(cell.out_point()) + , capacity(cell.capacity()) + , lock(cell.lock()) + , type(cell.type()) + , blockNumber(cell.block_number()) + , since(cell.since()) { + auto&& cellData = cell.data(); + data.insert(data.end(), cellData.begin(), cellData.end()); + auto&& cellBlockHash = cell.block_hash(); + blockHash.insert(blockHash.end(), cellBlockHash.begin(), cellBlockHash.end()); + auto&& cellInputType = cell.input_type(); + inputType.insert(inputType.end(), cellInputType.begin(), cellInputType.end()); + auto&& cellOutputType = cell.output_type(); + outputType.insert(outputType.end(), cellOutputType.begin(), cellOutputType.end()); + } + + Proto::Cell proto() const { + auto cell = Proto::Cell(); + *cell.mutable_out_point() = outPoint.proto(); + cell.set_capacity(capacity); + *cell.mutable_lock() = lock.proto(); + *cell.mutable_type() = type.proto(); + cell.set_data(std::string(data.begin(), data.end())); + cell.set_block_number(blockNumber); + cell.set_block_hash(std::string(blockHash.begin(), blockHash.end())); + cell.set_since(since); + cell.set_input_type(std::string(inputType.begin(), inputType.end())); + cell.set_output_type(std::string(outputType.begin(), outputType.end())); + return cell; + } +}; + +/// A list of Cell's +using Cells = std::vector; + +} // namespace TW::Nervos diff --git a/src/Nervos/CellDep.cpp b/src/Nervos/CellDep.cpp new file mode 100644 index 00000000000..a9acf139ae3 --- /dev/null +++ b/src/Nervos/CellDep.cpp @@ -0,0 +1,39 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "CellDep.h" + +#include "../BinaryCoding.h" + +namespace TW::Nervos { + +CellDep::CellDep(const Proto::CellDep& cellDep) + : outPoint(cellDep.out_point()) { + auto&& depTypeString = cellDep.dep_type(); + for (int i = 0; i < (int)(sizeof(DepTypeString) / sizeof(DepTypeString[0])); i++) { + if (depTypeString == DepTypeString[i]) { + depType = (DepType)i; + } + } +} + +Proto::CellDep CellDep::proto() const { + auto cellDep = Proto::CellDep(); + *cellDep.mutable_out_point() = outPoint.proto(); + cellDep.set_dep_type(DepTypeString[depType]); + return cellDep; +} + +void CellDep::encode(Data& data) const { + outPoint.encode(data); + data.emplace_back(depType); +} + +nlohmann::json CellDep::json() const { + return nlohmann::json{{"out_point", outPoint.json()}, {"dep_type", DepTypeString[depType]}}; +} + +} // namespace TW::Nervos diff --git a/src/Nervos/CellDep.h b/src/Nervos/CellDep.h new file mode 100644 index 00000000000..9318017803e --- /dev/null +++ b/src/Nervos/CellDep.h @@ -0,0 +1,45 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "OutPoint.h" +#include "../proto/Nervos.pb.h" +#include + +namespace TW::Nervos { + +enum DepType { + Code = 0, + DepGroup = 1 +}; + +static const char* DepTypeString[] { + "code", + "dep_group" +}; + +/// Nervos cell dep. +struct CellDep { + OutPoint outPoint; + DepType depType; + + /// Initializes a cell dep with a previous output and depType + CellDep(OutPoint outPoint, DepType depType) : outPoint(std::move(outPoint)), depType(depType) {} + + CellDep(const Proto::CellDep& cellDep); + + /// Encodes the transaction into the provided buffer. + void encode(Data& data) const; + nlohmann::json json() const; + + Proto::CellDep proto() const; +}; + +/// A list of Cell Deps +using CellDeps = std::vector; + +} // namespace TW::Nervos diff --git a/src/Nervos/CellInput.cpp b/src/Nervos/CellInput.cpp new file mode 100644 index 00000000000..61810b9686b --- /dev/null +++ b/src/Nervos/CellInput.cpp @@ -0,0 +1,22 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "CellInput.h" +#include "Serialization.h" + +namespace TW::Nervos { + +void CellInput::encode(Data& data) const { + encode64LE(since, data); + previousOutput.encode(data); +} + +nlohmann::json CellInput::json() const { + return nlohmann::json{{"previous_output", previousOutput.json()}, + {"since", Serialization::numberToHex(since)}}; +} + +} // namespace TW::Nervos diff --git a/src/Nervos/CellInput.h b/src/Nervos/CellInput.h new file mode 100644 index 00000000000..afb8488cf05 --- /dev/null +++ b/src/Nervos/CellInput.h @@ -0,0 +1,36 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "OutPoint.h" +#include + +namespace TW::Nervos { + +/// Nervos cell input. +struct CellInput { + /// Reference to the previous transaction's output. + OutPoint previousOutput; + + /// Prevents the transaction to be mined before an absolute or relative time. + uint64_t since; + + /// Initializes a cell input with a previous output and since + CellInput(OutPoint previousOutput, uint64_t since) + : previousOutput(std::move(previousOutput)), since(since) {} + + /// Encodes the transaction into the provided buffer. + void encode(Data& data) const; + + /// Encodes the output into json format. + nlohmann::json json() const; +}; + +/// A list of Cell Inputs +using CellInputs = std::vector; + +} // namespace TW::Nervos diff --git a/src/Nervos/CellOutput.cpp b/src/Nervos/CellOutput.cpp new file mode 100644 index 00000000000..ed1d98b86b8 --- /dev/null +++ b/src/Nervos/CellOutput.cpp @@ -0,0 +1,28 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "CellOutput.h" +#include "Serialization.h" + +namespace TW::Nervos { + +void CellOutput::encode(Data& data) const { + Data capacityData; + Data lockData; + Data typeData; + encode64LE(capacity, capacityData); + lock.encode(lockData); + type.encode(typeData); + Serialization::encodeDataArray(std::vector{capacityData, lockData, typeData}, data); +} + +nlohmann::json CellOutput::json() const { + return nlohmann::json{{"capacity", Serialization::numberToHex(capacity)}, + {"lock", lock.json()}, + {"type", type.json()}}; +} + +} // namespace TW::Nervos diff --git a/src/Nervos/CellOutput.h b/src/Nervos/CellOutput.h new file mode 100644 index 00000000000..ca2b267d0cf --- /dev/null +++ b/src/Nervos/CellOutput.h @@ -0,0 +1,53 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Script.h" +#include "Data.h" +#include "../proto/Nervos.pb.h" +#include + +#include + +namespace TW::Nervos { + +/// Nervos cell output. +struct CellOutput { + uint64_t capacity; + Script lock; + Script type; + + /// Initializes an empty cell output. + CellOutput() = default; + + /// Initializes a cell output with a capacity and scripts. + CellOutput(uint64_t capacity, Script&& lock, Script&& type) + : capacity(capacity), lock(std::move(lock)), type(std::move(type)) {} + + /// Initializes a CellInput from a Protobuf CellInput. + CellOutput(const Proto::CellOutput& cellOutput) + : capacity(cellOutput.capacity()), lock(cellOutput.lock()), type(cellOutput.type()) {} + + /// Encodes the output into the provided buffer. + void encode(Data& data) const; + + /// Encodes the output into json format. + nlohmann::json json() const; + + Proto::CellOutput proto() const { + auto cellOutput = Proto::CellOutput(); + cellOutput.set_capacity(capacity); + *cellOutput.mutable_lock() = lock.proto(); + *cellOutput.mutable_type() = type.proto(); + return cellOutput; + } +}; + +/// A list of Cell Outputs +using CellOutputs = std::vector; + +} // namespace TW::Nervos diff --git a/src/Nervos/Constants.h b/src/Nervos/Constants.h new file mode 100644 index 00000000000..97678b77bd7 --- /dev/null +++ b/src/Nervos/Constants.h @@ -0,0 +1,53 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Address.h" +#include "CellDep.h" +#include "OutPoint.h" +#include "Data.h" +#include "../HexCoding.h" + +#include +#include + +namespace TW::Nervos::Constants { + +static const uint64_t gTransactionBaseSize = 72; +static const uint64_t gCellDepSize = 37; +static const uint64_t gHeaderDepSize = 32; +static const uint64_t gSingleInputAndWitnessBaseSize = 44; +static const uint64_t gBlankWitnessBytes = 65; +static const uint64_t gUint32Size = 4; +static const uint64_t gMinCellCapacityForNativeToken = 6100000000; +static const uint64_t gMinCellCapacityForSUDT = 14400000000; + +static const Data gHashPersonalization{'c', 'k', 'b', '-', 'd', 'e', 'f', 'a', + 'u', 'l', 't', '-', 'h', 'a', 's', 'h'}; + +static const Data gSecp256k1CodeHash = + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8"); + +static const CellDep gSecp256k1CellDep = CellDep( + OutPoint(parse_hex("71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c"), 0), + DepType::DepGroup); + +static const Data gSUDTCodeHash = + parse_hex("5e7a36a77e68eecc013dfa2fe6a23f3b6c344b04005808694ae6dd45eea4cfd5"); + +static const CellDep gSUDTCellDep = CellDep( + OutPoint(parse_hex("c7813f6a415144643970c2e88e0bb6ca6a8edc5dd7c1022746f628284a9936d5"), 0), + DepType::Code); + +static const Data gDAOCodeHash = + parse_hex("82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e"); + +static const CellDep gDAOCellDep = CellDep( + OutPoint(parse_hex("e2fb199810d49a4d8beec56718ba2593b665db9d52299a0f9e6e75416d73ff5c"), 2), + DepType::Code); + +} // namespace TW::Nervos::Constants diff --git a/src/Nervos/Entry.cpp b/src/Nervos/Entry.cpp new file mode 100644 index 00000000000..44e725ae6a3 --- /dev/null +++ b/src/Nervos/Entry.cpp @@ -0,0 +1,33 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Entry.h" + +#include "Address.h" +#include "Signer.h" + +namespace TW::Nervos { +using namespace std; + +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const string& address, byte, byte, + const char* hrp) const { + return Address::isValid(address, hrp); +} + +string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, byte, + const char* hrp) const { + return Address(publicKey, hrp).string(); +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const Data& dataIn, Data& dataOut) const { + signTemplate(dataIn, dataOut); +} + +void Entry::plan([[maybe_unused]] TWCoinType coin, const Data& dataIn, Data& dataOut) const { + planTemplate(dataIn, dataOut); +} + +} // namespace TW::Nervos diff --git a/src/Nervos/Entry.h b/src/Nervos/Entry.h new file mode 100644 index 00000000000..0d3919dcefb --- /dev/null +++ b/src/Nervos/Entry.h @@ -0,0 +1,28 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "../CoinEntry.h" + +using namespace TW; + +namespace TW::Nervos { + +/// Entry point for implementation of Nervos 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, byte p2pkh, byte p2sh, + const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, byte p2pkh, + const char* hrp) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; +}; + +} // namespace TW::Nervos diff --git a/src/Ethereum/Fee.h b/src/Nervos/HeaderDep.h similarity index 51% rename from src/Ethereum/Fee.h rename to src/Nervos/HeaderDep.h index 6fe94672b87..af94b115db8 100644 --- a/src/Ethereum/Fee.h +++ b/src/Nervos/HeaderDep.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -6,11 +6,14 @@ #pragma once -#include "uint256.h" -#include +#include +#include -namespace TW::Ethereum::Fee { +namespace TW::Nervos { -auto suggestFee(const nlohmann::json& feeHistory) -> nlohmann::json; +using HeaderDep = Data; -} // namespace TW::Ethereum::Fee +/// A list of header deps +using HeaderDeps = std::vector; + +} // namespace TW::Nervos diff --git a/src/Nervos/OutPoint.cpp b/src/Nervos/OutPoint.cpp new file mode 100644 index 00000000000..a9cfbb1599d --- /dev/null +++ b/src/Nervos/OutPoint.cpp @@ -0,0 +1,22 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "OutPoint.h" +#include "Serialization.h" + +namespace TW::Nervos { + +void OutPoint::encode(Data& data) const { + data.insert(data.end(), txHash.begin(), txHash.end()); + encode32LE(index, data); +} + +nlohmann::json OutPoint::json() const { + return nlohmann::json{{"tx_hash", hexEncoded(txHash)}, + {"index", Serialization::numberToHex(uint64_t(index))}}; +} + +} // namespace TW::Nervos \ No newline at end of file diff --git a/src/Nervos/OutPoint.h b/src/Nervos/OutPoint.h new file mode 100644 index 00000000000..f6fc82e90f0 --- /dev/null +++ b/src/Nervos/OutPoint.h @@ -0,0 +1,54 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Data.h" +#include "../proto/Nervos.pb.h" +#include + +#include +#include +#include + +namespace TW::Nervos { + +/// Nervos transaction out-point reference. +struct OutPoint { + /// The hash of the referenced transaction. + Data txHash; + + /// The index of the specific output in the transaction. + uint32_t index; + + OutPoint() = default; + + /// Initializes an out-point reference with hash, index. + template + OutPoint(const T& h, uint32_t index) : txHash(std::begin(h), std::end(h)), index(index) {} + + /// Initializes an out-point from a Protobuf out-point. + OutPoint(const Proto::OutPoint& outPoint) + : txHash(std::begin(outPoint.tx_hash()), std::end(outPoint.tx_hash())) + , index(outPoint.index()) {} + + /// Encodes the out-point into the provided buffer. + void encode(Data& data) const; + nlohmann::json json() const; + + friend bool operator==(const OutPoint& lhs, const OutPoint& rhs) { + return (lhs.txHash == rhs.txHash && lhs.index == rhs.index); + } + + Proto::OutPoint proto() const { + auto outPoint = Proto::OutPoint(); + outPoint.set_tx_hash(std::string(txHash.begin(), txHash.end())); + outPoint.set_index(index); + return outPoint; + } +}; + +} // namespace TW::Nervos diff --git a/src/Nervos/Script.cpp b/src/Nervos/Script.cpp new file mode 100644 index 00000000000..b0f688f5797 --- /dev/null +++ b/src/Nervos/Script.cpp @@ -0,0 +1,49 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Script.h" +#include "Constants.h" +#include "Serialization.h" +#include "../Bech32.h" + +#include +#include + +namespace TW::Nervos { + +Data Script::hash() const { + Data data; + encode(data); + return Hash::blake2b(data, 32, Constants::gHashPersonalization); +} + +[[nodiscard]] bool Script::empty() const { + return std::all_of(codeHash.begin(), codeHash.end(), [](byte element) { return element == 0; }); +} + +void Script::encode(Data& data) const { + Data hashTypeData(1); + Data argsData; + if (empty()) { + return; + } + hashTypeData[0] = hashType; + encode32LE(uint32_t(args.size()), argsData); + argsData.insert(argsData.end(), args.begin(), args.end()); + Serialization::encodeDataArray(std::vector{codeHash, hashTypeData, argsData}, data); +} + +nlohmann::json Script::json() const { + if (empty()) { + return nullptr; + } else { + return nlohmann::json{{"code_hash", hexEncoded(codeHash)}, + {"hash_type", HashTypeString[hashType]}, + {"args", hexEncoded(args)}}; + } +} + +} // namespace TW::Nervos diff --git a/src/Nervos/Script.h b/src/Nervos/Script.h new file mode 100644 index 00000000000..7c0abffec5c --- /dev/null +++ b/src/Nervos/Script.h @@ -0,0 +1,107 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Address.h" +#include "Constants.h" +#include "Data.h" +#include "../proto/Nervos.pb.h" +#include + +#include +#include + +namespace TW::Nervos { + +struct Script { + Data codeHash; + HashType hashType; + Data args; + + /// Initializes an empty script. + Script() { + hashType = HashType::Data0; + } + + /// Copy constructor + Script(const Script& script) + : codeHash(script.codeHash), hashType(script.hashType), args(script.args) {} + + /// Move constructor + Script(Script&& script) + : codeHash(std::move(script.codeHash)) + , hashType(script.hashType) + , args(std::move(script.args)) {} + + /// Initializes a script with codeHash, args and hashType. + Script(const Data& codeHash, const HashType hashType, const Data& args) + : codeHash(codeHash), hashType(hashType), args(args) {} + + /// Initializes a script with the given address. + Script(const Address& address) + : codeHash(address.codeHash), hashType(address.hashType), args(address.args) {} + + // Copy assignment operator + Script& operator=(const Script& script) { + codeHash = script.codeHash; + hashType = script.hashType; + args = script.args; + return *this; + } + + // Move assignment operator + Script& operator=(Script&& script) { + codeHash = std::move(script.codeHash); + hashType = script.hashType; + args = std::move(script.args); + return *this; + } + + friend bool operator==(const Script& lhs, const Script& rhs) { + return (lhs.codeHash == rhs.codeHash) && (lhs.hashType == rhs.hashType) && + (lhs.args == rhs.args); + } + + friend bool operator!=(const Script& lhs, const Script& rhs) { return !(lhs == rhs); } + + /// Returns the script's script hash. + Data hash() const; + + /// Whether the script is empty. + [[nodiscard]] bool empty() const; + + /// Initializes an script from a Protobuf script. + Script(const Proto::Script& script) { + auto&& scriptCodeHash = script.code_hash(); + codeHash.insert(codeHash.end(), scriptCodeHash.begin(), scriptCodeHash.end()); + auto&& hashTypeString = script.hash_type(); + hashType = HashType::Data0; + for (int i = 0; i < (int)(sizeof(HashTypeString) / sizeof(HashTypeString[0])); i++) { + if (hashTypeString == HashTypeString[i]) { + hashType = (HashType)i; + } + } + auto&& scriptArgs = script.args(); + args.insert(args.end(), scriptArgs.begin(), scriptArgs.end()); + } + + /// Encodes the script. + void encode(Data& data) const; + + /// Encodes the script into json format. + nlohmann::json json() const; + + Proto::Script proto() const { + auto script = Proto::Script(); + script.set_code_hash(std::string(codeHash.begin(), codeHash.end())); + script.set_hash_type(HashTypeString[hashType]); + script.set_args(std::string(args.begin(), args.end())); + return script; + } +}; + +} // namespace TW::Nervos diff --git a/src/Nervos/Serialization.h b/src/Nervos/Serialization.h new file mode 100644 index 00000000000..9a2c66dee71 --- /dev/null +++ b/src/Nervos/Serialization.h @@ -0,0 +1,60 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "../BinaryCoding.h" +#include "Data.h" +#include "../HexCoding.h" +#include "../uint256.h" + +#include +#include + +namespace TW::Nervos { + +struct Serialization { + static void encodeDataArray(const std::vector& dataArray, Data& data) { + uint32_t dataLength = std::accumulate(dataArray.begin(), dataArray.end(), uint32_t(0), + [](const uint32_t total, const Data& element) { + return total + uint32_t(element.size()); + }); + uint32_t headerLength = 4 + 4 * uint32_t(dataArray.size()); + uint32_t fullLength = headerLength + dataLength; + encode32LE(fullLength, data); + std::accumulate(dataArray.begin(), dataArray.end(), headerLength, + [&data](const uint32_t offset, const Data& element) { + encode32LE(offset, data); + return offset + uint32_t(element.size()); + }); + for (auto&& element : dataArray) { + data.insert(data.end(), element.begin(), element.end()); + } + } + + static Data encodeUint256(uint256_t number, byte minLen = 0) { + auto data = store(number, minLen); + std::reverse(data.begin(), data.end()); + return data; + } + + static uint256_t decodeUint256(const Data& data) { + auto data1 = Data(data); + std::reverse(data1.begin(), data1.end()); + return load(data1); + } + + static std::string numberToHex(uint64_t number) { + auto str = hex(number); + str.erase(0, str.find_first_not_of('0')); + if (str.length() == 0) { + return "0x0"; + } else { + return str.insert(0, "0x"); + } + } +}; +} // namespace TW::Nervos diff --git a/src/Nervos/Signer.cpp b/src/Nervos/Signer.cpp new file mode 100644 index 00000000000..3a39ed1eb45 --- /dev/null +++ b/src/Nervos/Signer.cpp @@ -0,0 +1,55 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Signer.h" +#include "Transaction.h" +#include "TransactionPlan.h" + +namespace TW::Nervos { + +Proto::TransactionPlan Signer::plan(const Proto::SigningInput& signingInput) noexcept { + TransactionPlan txPlan; + txPlan.plan(signingInput); + return txPlan.proto(); +} + +Proto::SigningOutput Signer::sign(const Proto::SigningInput& signingInput) noexcept { + Proto::SigningOutput output; + + TransactionPlan txPlan; + if (signingInput.has_plan()) { + txPlan = TransactionPlan(signingInput.plan()); + } else { + txPlan.plan(signingInput); + } + if (txPlan.error != Common::Proto::OK) { + // Planning failed + output.set_error(txPlan.error); + return output; + } + + Transaction tx; + tx.build(txPlan); + std::vector privateKeys; + privateKeys.reserve(signingInput.private_key_size()); + for (auto&& privateKey : signingInput.private_key()) { + privateKeys.emplace_back(privateKey); + } + auto error = tx.sign(privateKeys); + if (error != Common::Proto::OK) { + // Signing failed + output.set_error(error); + return output; + } + + output.set_transaction_json(tx.json().dump()); + output.set_transaction_id(hexEncoded(tx.hash())); + output.set_error(Common::Proto::OK); + + return output; +} + +} // namespace TW::Nervos diff --git a/src/Nervos/Signer.h b/src/Nervos/Signer.h new file mode 100644 index 00000000000..7c7c0c1d279 --- /dev/null +++ b/src/Nervos/Signer.h @@ -0,0 +1,26 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "CoinEntry.h" +#include "Data.h" +#include "../proto/Nervos.pb.h" + +namespace TW::Nervos { + +class Signer { +public: + Signer() = delete; + + /// Returns a transaction plan (utxo selection, fee estimation) + static Proto::TransactionPlan plan(const Proto::SigningInput& signingInputProto) noexcept; + + /// Signs a Proto::SigningInput transaction + static Proto::SigningOutput sign(const Proto::SigningInput& signingInputProto) noexcept; +}; + +} // namespace TW::Nervos diff --git a/src/Nervos/Transaction.cpp b/src/Nervos/Transaction.cpp new file mode 100644 index 00000000000..bf61915e2f8 --- /dev/null +++ b/src/Nervos/Transaction.cpp @@ -0,0 +1,212 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Transaction.h" +#include "Constants.h" +#include "Serialization.h" + +#include +#include +#include +#include + +#include + +namespace TW::Nervos { + +Data Transaction::hash() const { + Data data; + std::vector dataArray; + dataArray.reserve(6); + + // version + Data versionData; + encode32LE(version, versionData); + dataArray.emplace_back(versionData); + + // cell deps + Data cellDepsData; + encode32LE(uint32_t(cellDeps.size()), cellDepsData); + for (auto&& cellDep : cellDeps) { + cellDep.encode(cellDepsData); + } + dataArray.emplace_back(cellDepsData); + + // header deps + Data headerDepsData; + encode32LE(uint32_t(headerDeps.size()), headerDepsData); + for (auto&& headerDep : headerDeps) { + headerDepsData.insert(headerDepsData.end(), headerDep.begin(), headerDep.end()); + } + dataArray.emplace_back(headerDepsData); + + // inputs + Data inputsData; + encode32LE(uint32_t(inputs.size()), inputsData); + for (auto&& input : inputs) { + input.encode(inputsData); + } + dataArray.emplace_back(inputsData); + + // outputs + Data outputsData1; + std::vector outputsData1Array; + outputsData1Array.reserve(outputs.size()); + for (auto&& output : outputs) { + Data outputData1; + output.encode(outputData1); + outputsData1Array.emplace_back(outputData1); + } + Serialization::encodeDataArray(outputsData1Array, outputsData1); + dataArray.emplace_back(outputsData1); + + // outputs data + Data outputsData2; + std::vector outputsData2Array; + outputsData2Array.reserve(outputsData.size()); + for (auto&& outputData : outputsData) { + Data outputData2; + encode32LE(uint32_t(outputData.size()), outputData2); + outputData2.insert(outputData2.end(), outputData.begin(), outputData.end()); + outputsData2Array.emplace_back(outputData2); + } + Serialization::encodeDataArray(outputsData2Array, outputsData2); + dataArray.emplace_back(outputsData2); + + Serialization::encodeDataArray(dataArray, data); + + return Hash::blake2b(data, 32, Constants::gHashPersonalization); +} + +nlohmann::json Transaction::json() const { + auto json = nlohmann::json(); + json["version"] = "0x0"; + auto cellDepsJSON = nlohmann::json::array(); + for (auto&& cellDep : cellDeps) { + cellDepsJSON.push_back(cellDep.json()); + } + json["cell_deps"] = cellDepsJSON; + auto headerDepsJSON = nlohmann::json::array(); + for (auto&& headerDep : headerDeps) { + headerDepsJSON.push_back(hexEncoded(headerDep)); + } + json["header_deps"] = headerDepsJSON; + auto inputsJSON = nlohmann::json::array(); + for (auto&& input : inputs) { + inputsJSON.push_back(input.json()); + } + json["inputs"] = inputsJSON; + auto outputsJSON = nlohmann::json::array(); + for (auto&& output : outputs) { + outputsJSON.push_back(output.json()); + } + json["outputs"] = outputsJSON; + auto outputsDataJSON = nlohmann::json::array(); + for (auto&& outputData : outputsData) { + outputsDataJSON.push_back(hexEncoded(outputData)); + } + json["outputs_data"] = outputsDataJSON; + auto witnessesJSON = nlohmann::json::array(); + for (auto&& serializedWitness : serializedWitnesses) { + witnessesJSON.push_back(hexEncoded(serializedWitness)); + } + json["witnesses"] = witnessesJSON; + return json; +} + +void Transaction::build(const TransactionPlan& txPlan) { + cellDeps = txPlan.cellDeps; + headerDeps = txPlan.headerDeps; + selectedCells = txPlan.selectedCells; + outputs = txPlan.outputs; + outputsData = txPlan.outputsData; + for (auto&& cell : selectedCells) { + inputs.emplace_back(cell.outPoint, cell.since); + } +} + +Common::Proto::SigningError Transaction::sign(const std::vector& privateKeys) { + formGroups(); + return signGroups(privateKeys); +} + +void Transaction::formGroups() { + for (size_t index = 0; index < selectedCells.size(); index++) { + auto&& cell = selectedCells[index]; + auto lockHash = cell.lock.hash(); + int groupNum = -1; + for (size_t groupNum1 = 0; groupNum1 < m_groupNumToLockHash.size(); groupNum1++) { + if (lockHash == m_groupNumToLockHash[groupNum1]) { + // Group found. Add to existing group. + groupNum = int(groupNum1); + break; + } + } + if (groupNum == -1) { + // Group not found. Create new group. + groupNum = int(m_groupNumToLockHash.size()); + m_groupNumToLockHash.emplace_back(lockHash); + m_groupNumToInputIndices.emplace_back(); + m_groupNumToWitnesses.emplace_back(); + } + m_groupNumToInputIndices[groupNum].emplace_back(index); + m_groupNumToWitnesses[groupNum].emplace_back(Data(), cell.inputType, cell.outputType); + serializedWitnesses.emplace_back(); + } +} + +Common::Proto::SigningError Transaction::signGroups(const std::vector& privateKeys) { + const Data txHash = hash(); + for (size_t groupNum = 0; groupNum < m_groupNumToLockHash.size(); groupNum++) { + auto&& cell = selectedCells[m_groupNumToInputIndices[groupNum][0]]; + const PrivateKey* privateKey = nullptr; + for (auto&& privateKey1 : privateKeys) { + auto publicKey1 = privateKey1.getPublicKey(TWPublicKeyTypeSECP256k1); + auto address = Address(publicKey1, HRP_NERVOS); + auto script = Script(address); + if (script == cell.lock) { + privateKey = &privateKey1; + break; + } + } + if (!privateKey) { + return Common::Proto::Error_missing_private_key; + } + auto result = signWitnesses(*privateKey, txHash, m_groupNumToWitnesses[groupNum]); + if (result != Common::Proto::OK) { + return result; + } + m_groupNumToWitnesses[groupNum][0].encode(serializedWitnesses[m_groupNumToInputIndices[groupNum][0]]); + } + return Common::Proto::OK; +} + +Common::Proto::SigningError Transaction::signWitnesses(const PrivateKey& privateKey, + const Data& txHash, Witnesses& witnesses) { + Data message; + message.insert(message.end(), txHash.begin(), txHash.end()); + + witnesses[0].lock = Data(Constants::gBlankWitnessBytes, 0); + + for (auto&& witness : witnesses) { + Data serializedWitness; + witness.encode(serializedWitness); + encode64LE(serializedWitness.size(), message); + message.insert(message.end(), serializedWitness.begin(), serializedWitness.end()); + } + + auto messageHash = Hash::blake2b(message, 32, Constants::gHashPersonalization); + auto signature = privateKey.sign(messageHash, TWCurveSECP256k1); + if (signature.empty()) { + // Error: Failed to sign + return Common::Proto::Error_signing; + } + witnesses[0].lock = signature; + + return Common::Proto::OK; +} + +} // namespace TW::Nervos diff --git a/src/Nervos/Transaction.h b/src/Nervos/Transaction.h new file mode 100644 index 00000000000..6121b0a6866 --- /dev/null +++ b/src/Nervos/Transaction.h @@ -0,0 +1,76 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Address.h" +#include "Cell.h" +#include "CellDep.h" +#include "CellInput.h" +#include "CellOutput.h" +#include "HeaderDep.h" +#include "Script.h" +#include "TransactionPlan.h" +#include "Witness.h" + +#include "../Coin.h" +#include "../CoinEntry.h" +#include "Data.h" +#include "../Hash.h" +#include "../KeyPair.h" +#include "../PrivateKey.h" +#include "../PublicKey.h" +#include "../Result.h" + +#include + +namespace TW::Nervos { + +class Transaction { +public: + /// Transaction data format version (note, this is signed) + int32_t version = 0; + + // List of cell deps + CellDeps cellDeps; + + // List of header deps + HeaderDeps headerDeps; + + // List of cell inputs + CellInputs inputs; + + // List of cell outputs + CellOutputs outputs; + + // List of outputs data + std::vector outputsData; + + // List of serialized witnesses + std::vector serializedWitnesses; + + // List of cells selected for this transaction + Cells selectedCells; + + Transaction() = default; + + Data hash() const; + nlohmann::json json() const; + void build(const TransactionPlan& txPlan); + Common::Proto::SigningError sign(const std::vector& privateKeys); + +private: + std::vector m_groupNumToLockHash; + std::vector> m_groupNumToInputIndices; + std::vector m_groupNumToWitnesses; + + void formGroups(); + Common::Proto::SigningError signGroups(const std::vector& privateKeys); + Common::Proto::SigningError signWitnesses(const PrivateKey& privateKey, const Data& txHash, + Witnesses& witnesses); +}; + +} // namespace TW::Nervos diff --git a/src/Nervos/TransactionPlan.cpp b/src/Nervos/TransactionPlan.cpp new file mode 100644 index 00000000000..c4135505af8 --- /dev/null +++ b/src/Nervos/TransactionPlan.cpp @@ -0,0 +1,328 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "TransactionPlan.h" +#include "Constants.h" +#include "Serialization.h" +#include "Witness.h" +#include "../BinaryCoding.h" +#include "../HexCoding.h" + +#include + +#include +#include +#include + +using namespace TW; +namespace TW::Nervos { + +void TransactionPlan::plan(const Proto::SigningInput& signingInput) { + error = Common::Proto::OK; + + m_byteFee = signingInput.byte_fee(); + + if (signingInput.cell_size() == 0) { + error = Common::Proto::Error_missing_input_utxos; + return; + } + for (auto&& cell : signingInput.cell()) { + m_availableCells.emplace_back(cell); + } + + switch (signingInput.operation_oneof_case()) { + case Proto::SigningInput::kNativeTransfer: { + planNativeTransfer(signingInput); + break; + } + case Proto::SigningInput::kSudtTransfer: { + planSudtTransfer(signingInput); + break; + } + case Proto::SigningInput::kDaoDeposit: { + planDaoDeposit(signingInput); + break; + } + case Proto::SigningInput::kDaoWithdrawPhase1: { + planDaoWithdrawPhase1(signingInput); + break; + } + case Proto::SigningInput::kDaoWithdrawPhase2: { + planDaoWithdrawPhase2(signingInput); + break; + } + default: { + error = Common::Proto::Error_invalid_params; + } + } +} + +void TransactionPlan::planNativeTransfer(const Proto::SigningInput& signingInput) { + auto useMaxAmount = signingInput.native_transfer().use_max_amount(); + auto amount = signingInput.native_transfer().amount(); + if ((amount == 0) && !useMaxAmount) { + error = Common::Proto::Error_zero_amount_requested; + return; + } + + cellDeps.emplace_back(Constants::gSecp256k1CellDep); + + outputs.emplace_back(amount, Script(Address(signingInput.native_transfer().to_address())), + Script()); + outputsData.emplace_back(); + + if (useMaxAmount) { + selectMaximumCapacity(); + } else { + auto changeAddress = Address(signingInput.native_transfer().change_address()); + selectRequiredCapacity(changeAddress); + } +} + +void TransactionPlan::planSudtTransfer(const Proto::SigningInput& signingInput) { + auto useMaxAmount = signingInput.sudt_transfer().use_max_amount(); + uint256_t amount = uint256_t(signingInput.sudt_transfer().amount()); + if ((amount == 0) && !useMaxAmount) { + error = Common::Proto::Error_zero_amount_requested; + return; + } + + cellDeps.emplace_back(Constants::gSecp256k1CellDep); + cellDeps.emplace_back(Constants::gSUDTCellDep); + + outputs.emplace_back(Constants::gMinCellCapacityForSUDT, + Script(Address(signingInput.sudt_transfer().to_address())), + Script(Constants::gSUDTCodeHash, HashType::Type1, + data(signingInput.sudt_transfer().sudt_address()))); + outputsData.emplace_back(); + + auto changeAddress = Address(signingInput.sudt_transfer().change_address()); + selectSudtTokens(useMaxAmount, amount, changeAddress); + selectRequiredCapacity(changeAddress); +} + +void TransactionPlan::planDaoDeposit(const Proto::SigningInput& signingInput) { + auto amount = signingInput.dao_deposit().amount(); + + cellDeps.emplace_back(Constants::gSecp256k1CellDep); + cellDeps.emplace_back(Constants::gDAOCellDep); + + outputs.emplace_back(amount, Script(Address(signingInput.dao_deposit().to_address())), + Script(Constants::gDAOCodeHash, HashType::Type1, Data())); + outputsData.emplace_back(); + encode64LE(0, outputsData[outputsData.size() - 1]); + + auto changeAddress = Address(signingInput.dao_deposit().change_address()); + selectRequiredCapacity(changeAddress); +} + +void TransactionPlan::planDaoWithdrawPhase1(const Proto::SigningInput& signingInput) { + cellDeps.emplace_back(Constants::gSecp256k1CellDep); + cellDeps.emplace_back(Constants::gDAOCellDep); + + auto depositCell = Cell(signingInput.dao_withdraw_phase1().deposit_cell()); + selectedCells.emplace_back(depositCell); + m_availableCells.erase(std::remove_if( + m_availableCells.begin(), m_availableCells.end(), + [&depositCell](const Cell& cell) { return cell.outPoint == depositCell.outPoint; })); + + headerDeps.emplace_back(depositCell.blockHash); + + outputs.emplace_back(depositCell.capacity, Script(depositCell.lock), Script(depositCell.type)); + outputsData.emplace_back(); + encode64LE(depositCell.blockNumber, outputsData[outputsData.size() - 1]); + + auto changeAddress = Address(signingInput.dao_withdraw_phase1().change_address()); + selectRequiredCapacity(changeAddress); +} + +void TransactionPlan::planDaoWithdrawPhase2(const Proto::SigningInput& signingInput) { + cellDeps.emplace_back(Constants::gSecp256k1CellDep); + cellDeps.emplace_back(Constants::gDAOCellDep); + + auto depositCell = Cell(signingInput.dao_withdraw_phase2().deposit_cell()); + auto withdrawingCell = Cell(signingInput.dao_withdraw_phase2().withdrawing_cell()); + selectedCells.emplace_back(withdrawingCell); + encode64LE(0, selectedCells[selectedCells.size() - 1].inputType); + + headerDeps.emplace_back(depositCell.blockHash); + headerDeps.emplace_back(withdrawingCell.blockHash); + + outputs.emplace_back(signingInput.dao_withdraw_phase2().amount(), Script(withdrawingCell.lock), + Script()); + outputsData.emplace_back(); + + outputs[0].capacity -= calculateFee(); +} + +void TransactionPlan::selectMaximumCapacity() { + uint64_t selectedCapacity = + std::accumulate(m_availableCells.begin(), m_availableCells.end(), uint64_t(0), + [&](const uint64_t total, const Cell& cell) { + if (cell.type.empty()) { + selectedCells.emplace_back(cell); + return total + cell.capacity; + } else { + return total; + } + }); + uint64_t fee = calculateFee(); + outputs[0].capacity = selectedCapacity - fee; +} + +void TransactionPlan::selectRequiredCapacity(const Address& changeAddress) { + uint64_t requiredCapacity = getRequiredCapacity(); + uint64_t fee = calculateFee(); + uint64_t feeForChangeOutput = sizeOfSingleOutput(changeAddress) * m_byteFee; + uint64_t selectedCapacity = getSelectedCapacity(); + uint64_t requiredCapacityPlusFees = requiredCapacity + fee + feeForChangeOutput; + if (selectedCapacity >= requiredCapacityPlusFees + Constants::gMinCellCapacityForNativeToken) { + outputs.emplace_back(selectedCapacity - requiredCapacityPlusFees, Script(changeAddress), + Script()); + outputsData.emplace_back(); + return; + } + sortAccordingToCapacity(); + bool gotEnough = false; + for (auto&& cell : m_availableCells) { + if (!cell.type.empty()) { + continue; + } + selectedCells.emplace_back(cell); + selectedCapacity += cell.capacity; + fee += sizeOfSingleInputAndWitness(cell.inputType, cell.outputType) * m_byteFee; + if (selectedCapacity >= requiredCapacity + fee) { + gotEnough = true; + uint64_t remainingCapacity = selectedCapacity - requiredCapacity - fee; + if (remainingCapacity >= + feeForChangeOutput + Constants::gMinCellCapacityForNativeToken) { + // If change is enough, add it to the change address + outputs.emplace_back(remainingCapacity - feeForChangeOutput, Script(changeAddress), + Script()); + outputsData.emplace_back(); + } else { + // If change is not enough, add it to the destination address + outputs[outputs.size() - 1].capacity += remainingCapacity; + } + break; + } + } + if (!gotEnough) { + error = Common::Proto::Error_not_enough_utxos; + } +} + +void TransactionPlan::selectSudtTokens(const bool useMaxAmount, const uint256_t amount, + const Address& changeAddress) { + uint256_t selectedSudtAmount = 0; + sortAccordingToTypeAndData(outputs[0].type); + bool gotEnough = false; + auto cell = m_availableCells.begin(); + while (cell != m_availableCells.end()) { + if (cell->type != outputs[0].type) { + cell++; + continue; + } + selectedCells.emplace_back(*cell); + selectedSudtAmount += Serialization::decodeUint256(cell->data); + cell = m_availableCells.erase(cell); + if (useMaxAmount) { + // Transfer maximum available tokens + gotEnough = true; + } else if (selectedSudtAmount >= amount) { + // Transfer exact number of tokens + gotEnough = true; + uint256_t changeValue = selectedSudtAmount - amount; + if (changeValue > 0) { + outputs.emplace_back(Constants::gMinCellCapacityForSUDT, Script(changeAddress), + Script(outputs[0].type)); + outputsData.emplace_back(Serialization::encodeUint256(changeValue, 16)); + } + break; + } + } + if (!gotEnough) { + error = Common::Proto::Error_not_enough_utxos; + return; + } + outputsData[0] = Serialization::encodeUint256(useMaxAmount ? selectedSudtAmount : amount, 16); +} + +uint64_t TransactionPlan::sizeWithoutInputs() { + uint64_t size = Constants::gTransactionBaseSize; + size += Constants::gCellDepSize * cellDeps.size(); + size += Constants::gHeaderDepSize * headerDeps.size(); + size += std::accumulate(outputs.begin(), outputs.end(), 0, + [](const uint64_t size, const CellOutput& output) { + Data outputData1; + output.encode(outputData1); + return size + outputData1.size() + Constants::gUint32Size; + }); + size += std::accumulate( + outputsData.begin(), outputsData.end(), 0, [](const uint64_t size, const Data& outputData) { + return size + Constants::gUint32Size + outputData.size() + Constants::gUint32Size; + }); + return size; +} + +uint64_t TransactionPlan::sizeOfSingleInputAndWitness(const Data& inputType, + const Data& outputType) { + uint64_t size = Constants::gSingleInputAndWitnessBaseSize; + auto witness = Witness(Data(Constants::gBlankWitnessBytes, 0), inputType, outputType); + Data witnessData; + witness.encode(witnessData); + size += Constants::gUint32Size + witnessData.size() + Constants::gUint32Size; + return size; +} + +uint64_t TransactionPlan::sizeOfSingleOutput(const Address& address) { + uint64_t size = 0; + auto output = CellOutput(0, Script(address), Script()); + Data outputData1; + output.encode(outputData1); + size += outputData1.size() + Constants::gUint32Size; // output + size += Constants::gUint32Size + 0 + Constants::gUint32Size; // blank outputData + return size; +} + +uint64_t TransactionPlan::calculateFee() { + uint64_t size = sizeWithoutInputs(); + size += std::accumulate(selectedCells.begin(), selectedCells.end(), uint64_t(0), + [&](const uint64_t total, const Cell& cell) { + return total + + sizeOfSingleInputAndWitness(cell.inputType, cell.outputType); + }); + return size * m_byteFee; +} + +void TransactionPlan::sortAccordingToCapacity() { + std::sort(m_availableCells.begin(), m_availableCells.end(), + [](const Cell& lhs, const Cell& rhs) { return lhs.capacity < rhs.capacity; }); +} + +void TransactionPlan::sortAccordingToTypeAndData(const Script& type) { + std::sort( + m_availableCells.begin(), m_availableCells.end(), [&](const Cell& lhs, const Cell& rhs) { + uint256_t lhsAmount = (lhs.type == type) ? Serialization::decodeUint256(lhs.data) : 0; + uint256_t rhsAmount = (rhs.type == type) ? Serialization::decodeUint256(rhs.data) : 0; + return lhsAmount < rhsAmount; + }); +} + +uint64_t TransactionPlan::getRequiredCapacity() { + return std::accumulate(outputs.begin(), outputs.end(), uint64_t(0), + [](const uint64_t total, const CellOutput& cellOutput) { + return total + cellOutput.capacity; + }); +} + +uint64_t TransactionPlan::getSelectedCapacity() { + return std::accumulate( + selectedCells.begin(), selectedCells.end(), uint64_t(0), + [](const uint64_t total, const Cell& cell) { return total + cell.capacity; }); +} + +} // namespace TW::Nervos diff --git a/src/Nervos/TransactionPlan.h b/src/Nervos/TransactionPlan.h new file mode 100644 index 00000000000..7fc800e44a3 --- /dev/null +++ b/src/Nervos/TransactionPlan.h @@ -0,0 +1,123 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Address.h" +#include "Cell.h" +#include "CellDep.h" +#include "CellInput.h" +#include "CellOutput.h" +#include "HeaderDep.h" +#include "Script.h" + +#include "../Coin.h" +#include "../CoinEntry.h" +#include "Data.h" +#include "../Hash.h" +#include "../KeyPair.h" +#include "../PrivateKey.h" +#include "../PublicKey.h" +#include "../Result.h" +#include "../proto/Nervos.pb.h" + +#include + +namespace TW::Nervos { + +class TransactionPlan { +public: + // List of cell deps + CellDeps cellDeps; + + // List of header deps + HeaderDeps headerDeps; + + // List of cells selected for this transaction + Cells selectedCells; + + // List of cell outputs + CellOutputs outputs; + + // List of outputs data + std::vector outputsData; + + // Error during transaction planning + Common::Proto::SigningError error; + + TransactionPlan() = default; + + /// Initializes a transaction from a Protobuf transaction. + TransactionPlan(const Proto::TransactionPlan& txPlan) { + for (auto&& cellDep : txPlan.cell_deps()) { + cellDeps.emplace_back(cellDep); + } + for (auto&& headerDep : txPlan.header_deps()) { + Data data; + data.insert(data.end(), headerDep.begin(), headerDep.end()); + headerDeps.emplace_back(data); + } + for (auto&& cell : txPlan.selected_cells()) { + selectedCells.emplace_back(cell); + } + for (auto&& output : txPlan.outputs()) { + outputs.emplace_back(output); + } + for (auto&& outputData : txPlan.outputs_data()) { + Data data; + data.insert(data.end(), outputData.begin(), outputData.end()); + outputsData.emplace_back(data); + } + error = txPlan.error(); + } + + /// Converts to Protobuf model + Proto::TransactionPlan proto() const { + auto txPlan = Proto::TransactionPlan(); + for (auto&& cellDep : cellDeps) { + *txPlan.add_cell_deps() = cellDep.proto(); + } + for (auto&& headerDep : headerDeps) { + txPlan.add_header_deps(headerDep.data(), headerDep.size()); + } + for (auto&& cell : selectedCells) { + *txPlan.add_selected_cells() = cell.proto(); + } + for (auto&& output : outputs) { + *txPlan.add_outputs() = output.proto(); + } + for (auto&& outputData : outputsData) { + txPlan.add_outputs_data(outputData.data(), outputData.size()); + } + return txPlan; + } + + void plan(const Proto::SigningInput& signingInput); + +private: + uint64_t m_byteFee; + Cells m_availableCells; + + void planNativeTransfer(const Proto::SigningInput& signingInput); + void planSudtTransfer(const Proto::SigningInput& signingInput); + void planDaoDeposit(const Proto::SigningInput& signingInput); + void planDaoWithdrawPhase1(const Proto::SigningInput& signingInput); + void planDaoWithdrawPhase2(const Proto::SigningInput& signingInput); + void selectMaximumCapacity(); + void selectRequiredCapacity(const Address& changeAddress); + void selectSudtTokens(const bool useMaxAmount, const uint256_t amount, + const Address& changeAddress); + uint64_t sizeWithoutInputs(); + uint64_t sizeOfSingleInputAndWitness(const Data& inputType, const Data& outputType); + uint64_t sizeOfSingleOutput(const Address& address); + uint64_t calculateFee(); + void sortAccordingToCapacity(); + void sortAccordingToTypeAndData(const Script& type); + uint64_t getRequiredCapacity(); + uint64_t getSelectedCapacity(); +}; + +} // namespace TW::Nervos diff --git a/src/Nervos/Witness.cpp b/src/Nervos/Witness.cpp new file mode 100644 index 00000000000..73a7e37fed8 --- /dev/null +++ b/src/Nervos/Witness.cpp @@ -0,0 +1,29 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Witness.h" +#include "Serialization.h" + +namespace TW::Nervos { + +void Witness::encode(Data& data) const { + if ((lock.empty()) && (inputType.empty()) && (outputType.empty())) { + return; + } + std::vector dataArray; + dataArray.reserve(3); + for (auto&& data1 : std::vector({lock, inputType, outputType})) { + Data data2; + if (!data1.empty()) { + encode32LE(uint32_t(data1.size()), data2); + data2.insert(data2.end(), data1.begin(), data1.end()); + } + dataArray.emplace_back(data2); + } + Serialization::encodeDataArray(dataArray, data); +} + +} // namespace TW::Nervos diff --git a/src/Nervos/Witness.h b/src/Nervos/Witness.h new file mode 100644 index 00000000000..f53aa398f7a --- /dev/null +++ b/src/Nervos/Witness.h @@ -0,0 +1,33 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Data.h" + +#include + +namespace TW::Nervos { + +struct Witness { + Data lock; + Data inputType; + Data outputType; + + Witness() = default; + + /// Initializes a witness with lock, inputType and outputType. + Witness(const Data& lock, const Data& inputType, const Data& outputType) + : lock(lock), inputType(inputType), outputType(outputType) {} + + /// Encodes the witness into the provided buffer. + void encode(Data& data) const; +}; + +/// A list of Witness's +using Witnesses = std::vector; + +} // namespace TW::Nervos diff --git a/src/Nimiq/Address.cpp b/src/Nimiq/Address.cpp index 458507aa506..46b6cb3ace7 100644 --- a/src/Nimiq/Address.cpp +++ b/src/Nimiq/Address.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -7,16 +7,12 @@ #include "Address.h" #include "../Base32.h" -#include "../Hash.h" -#include "../HexCoding.h" #include #include -#include -#include -using namespace TW::Nimiq; +namespace TW::Nimiq { static const char* BASE32_ALPHABET_NIMIQ = "0123456789ABCDEFGHJKLMNPQRSTUVXY"; @@ -150,3 +146,5 @@ static inline int check_add(int check, int num) { ; return (check + num) % 97; } + +} // namespace TW::Nimiq diff --git a/src/Nimiq/Address.h b/src/Nimiq/Address.h index 66acb288bad..ff60c756f9e 100644 --- a/src/Nimiq/Address.h +++ b/src/Nimiq/Address.h @@ -22,7 +22,7 @@ class Address { /// Address data consisting of a prefix byte followed by the public key /// hash. - std::array bytes; + std::array bytes{}; /// Determines whether a collection of bytes makes a valid address. static bool isValid(const std::vector& data) { return data.size() == size; } diff --git a/src/Nimiq/Entry.cpp b/src/Nimiq/Entry.cpp index 9e795074477..92279053f4f 100644 --- a/src/Nimiq/Entry.cpp +++ b/src/Nimiq/Entry.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,19 +9,21 @@ #include "Address.h" #include "Signer.h" -using namespace TW::Nimiq; -using namespace std; +namespace TW::Nimiq { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +} // namespace TW::Nimiq + diff --git a/src/Nimiq/Entry.h b/src/Nimiq/Entry.h index 59c18f3e890..cad09e0fc65 100644 --- a/src/Nimiq/Entry.h +++ b/src/Nimiq/Entry.h @@ -12,12 +12,11 @@ namespace TW::Nimiq { /// Entry point for implementation of Nimiq coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeNimiq}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::Nimiq diff --git a/src/Nimiq/Signer.cpp b/src/Nimiq/Signer.cpp index 2d3201e2892..5433b88aa86 100644 --- a/src/Nimiq/Signer.cpp +++ b/src/Nimiq/Signer.cpp @@ -9,8 +9,7 @@ #include -using namespace TW; -using namespace TW::Nimiq; +namespace TW::Nimiq { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); @@ -39,3 +38,5 @@ void Signer::sign(const PrivateKey& privateKey, Transaction& transaction) const auto signature = privateKey.sign(preImage, TWCurveED25519); std::copy(signature.begin(), signature.end(), transaction.signature.begin()); } + +} // namespace TW::Nimiq diff --git a/src/Nimiq/Signer.h b/src/Nimiq/Signer.h index 52baa9f03f0..022f98ba424 100644 --- a/src/Nimiq/Signer.h +++ b/src/Nimiq/Signer.h @@ -7,7 +7,7 @@ #pragma once #include "Transaction.h" -#include "../Data.h" +#include "Data.h" #include "../Hash.h" #include "../PrivateKey.h" #include "../proto/Nimiq.pb.h" diff --git a/src/Nimiq/Transaction.cpp b/src/Nimiq/Transaction.cpp index 4498166bad3..c7828ed73a8 100644 --- a/src/Nimiq/Transaction.cpp +++ b/src/Nimiq/Transaction.cpp @@ -8,11 +8,8 @@ #include "Signer.h" #include "../BinaryCoding.h" -#include "../HexCoding.h" -#include "../PublicKey.h" -using namespace TW; -using namespace TW::Nimiq; +namespace TW::Nimiq { const uint8_t NETWORK_ID = 42; const uint8_t EMPTY_FLAGS = 0; @@ -50,3 +47,5 @@ std::vector Transaction::getPreImage() const { return data; } + +} // namespace TW::Nimiq diff --git a/src/NumericLiteral.h b/src/NumericLiteral.h new file mode 100644 index 00000000000..4674dda8d88 --- /dev/null +++ b/src/NumericLiteral.h @@ -0,0 +1,14 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include // std::size_t + +// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0330r8.html +inline constexpr std::size_t operator"" _uz(unsigned long long int n) { + return n; +} \ No newline at end of file diff --git a/src/Oasis/Address.cpp b/src/Oasis/Address.cpp index af150206a80..ee4102f4d8f 100644 --- a/src/Oasis/Address.cpp +++ b/src/Oasis/Address.cpp @@ -9,19 +9,21 @@ #include #define COIN_ADDRESS_CONTEXT "oasis-core/address: staking" -#define COIN_ADDRESS_VERSION 0 +#define COIN_ADDRESS_VERSION 0 -using namespace TW::Oasis; +namespace TW::Oasis { const std::string Address::hrp = HRP_OASIS; -Address::Address(const Data& keyHash) : Bech32Address(hrp, keyHash) { +Address::Address(const Data& keyHash) + : Bech32Address(hrp, keyHash) { if (getKeyHash().size() != Address::size) { throw std::invalid_argument("invalid address data"); } } -Address::Address(const TW::PublicKey& publicKey) : Bech32Address(hrp){ +Address::Address(const TW::PublicKey& publicKey) + : Bech32Address(hrp) { if (publicKey.type != TWPublicKeyTypeED25519) { throw std::invalid_argument("address may only be an extended ED25519 public key"); } @@ -39,8 +41,9 @@ Address::Address(const TW::PublicKey& publicKey) : Bech32Address(hrp){ setKey(key); } -Address::Address(const std::string& addr) : Bech32Address(addr) { - if(!isValid(addr)) { +Address::Address(const std::string& addr) + : Bech32Address(addr) { + if (!isValid(addr)) { throw std::invalid_argument("invalid address string"); } } @@ -49,3 +52,4 @@ bool Address::isValid(const std::string& addr) { return Bech32Address::isValid(addr, hrp); } +} // namespace TW::Oasis diff --git a/src/Oasis/Address.h b/src/Oasis/Address.h index d9784cd63f1..c4eb73f3ef0 100644 --- a/src/Oasis/Address.h +++ b/src/Oasis/Address.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include "../Bech32Address.h" diff --git a/src/Oasis/Entry.cpp b/src/Oasis/Entry.cpp index 44c756948a3..3dbaeeb4d24 100644 --- a/src/Oasis/Entry.cpp +++ b/src/Oasis/Entry.cpp @@ -9,19 +9,22 @@ #include "Address.h" #include "Signer.h" -using namespace TW::Oasis; using namespace std; +namespace TW::Oasis { + // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +} // namespace TW::Oasis diff --git a/src/Oasis/Entry.h b/src/Oasis/Entry.h index a82478f4050..c4b3fb6240f 100644 --- a/src/Oasis/Entry.h +++ b/src/Oasis/Entry.h @@ -12,12 +12,11 @@ namespace TW::Oasis { /// Entry point for implementation of Oasis coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeOasis}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::Oasis diff --git a/src/Oasis/Signer.cpp b/src/Oasis/Signer.cpp index 7babc53ee84..b9263a7d8d9 100644 --- a/src/Oasis/Signer.cpp +++ b/src/Oasis/Signer.cpp @@ -6,14 +6,14 @@ #include -#include "Signer.h" #include "Address.h" +#include "Signer.h" #define TRANSFER_METHOD "staking.Transfer" using namespace TW; -using namespace TW::Oasis; +namespace TW::Oasis { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto output = Proto::SigningOutput(); @@ -40,7 +40,7 @@ Data Signer::build() const { gasAmountStream >> gasAmount; Transaction transaction( - /* to */ address, + /* to */ address, /* method */ TRANSFER_METHOD, /* gasPrice */ input.transfer().gas_price(), /* gasAmount */ gasAmount, @@ -48,7 +48,6 @@ Data Signer::build() const { /* nonce */ input.transfer().nonce(), /* context */ input.transfer().context()); - auto privateKey = PrivateKey(input.private_key()); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); @@ -70,3 +69,5 @@ Data Signer::sign(Transaction& tx) const { auto signature = privateKey.sign(hash, TWCurveED25519); return Data(signature.begin(), signature.end()); } + +} // namespace TW::Oasis diff --git a/src/Oasis/Signer.h b/src/Oasis/Signer.h index 76df838c029..90e92d0e5b9 100644 --- a/src/Oasis/Signer.h +++ b/src/Oasis/Signer.h @@ -9,7 +9,7 @@ #include -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" #include "../proto/Oasis.pb.h" #include "Transaction.h" diff --git a/src/Oasis/Transaction.cpp b/src/Oasis/Transaction.cpp index c2f1cc88ffc..dece41bfb53 100644 --- a/src/Oasis/Transaction.cpp +++ b/src/Oasis/Transaction.cpp @@ -7,7 +7,10 @@ #include "Transaction.h" using namespace TW; -using namespace TW::Oasis; + +namespace TW::Oasis { + +// clang-format off // encodeVaruint encodes a 256-bit number into a big endian encoding, omitting leading zeros. static Data encodeVaruint(const uint256_t& value) { @@ -56,3 +59,7 @@ Data Transaction::serialize(Data& signature, PublicKey& publicKey) const { }); return signedMessage.encoded(); } + +// clang-format on + +} // namespace TW::Oasis diff --git a/src/Ontology/Address.cpp b/src/Ontology/Address.cpp index 979d204dc05..40333d62217 100644 --- a/src/Ontology/Address.cpp +++ b/src/Ontology/Address.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -16,14 +16,15 @@ #include using namespace TW; -using namespace TW::Ontology; + +namespace TW::Ontology { Address::Address(const PublicKey& publicKey) { std::vector builder(publicKey.bytes); builder.insert(builder.begin(), PUSH_BYTE_33); builder.push_back(CHECK_SIG); auto builderData = toScriptHash(builder); - std::copy(builderData.begin(), builderData.end(), data.begin()); + std::copy(builderData.begin(), builderData.end(), _data.begin()); } Address::Address(const std::string& b58Address) { @@ -32,19 +33,19 @@ Address::Address(const std::string& b58Address) { } Data addressWithVersion(size + 1); base58_decode_check(b58Address.c_str(), HASHER_SHA2D, addressWithVersion.data(), size + 1); - std::copy(addressWithVersion.begin() + 1, addressWithVersion.end(), data.begin()); + std::copy(addressWithVersion.begin() + 1, addressWithVersion.end(), _data.begin()); } Address::Address(const std::vector& bytes) { if (bytes.size() != size) { throw std::runtime_error("Invalid bytes data."); } - std::copy(bytes.begin(), bytes.end(), data.begin()); + std::copy(bytes.begin(), bytes.end(), _data.begin()); } Address::Address(uint8_t m, const std::vector& publicKeys) { auto builderData = toScriptHash(ParamsBuilder::fromMultiPubkey(m, publicKeys)); - std::copy(builderData.begin(), builderData.end(), data.begin()); + std::copy(builderData.begin(), builderData.end(), _data.begin()); } Data Address::toScriptHash(const Data& data) { @@ -64,10 +65,12 @@ bool Address::isValid(const std::string& b58Address) noexcept { std::string Address::string() const { std::vector encodeData(size + 1); encodeData[0] = version; - std::copy(data.begin(), data.end(), encodeData.begin() + 1); + std::copy(_data.begin(), _data.end(), encodeData.begin() + 1); size_t b58StrSize = 34; std::string b58Str(b58StrSize, ' '); base58_encode_check(encodeData.data(), (int)encodeData.size(), HASHER_SHA2D, &b58Str[0], (int)b58StrSize + 1); return b58Str; } + +} // namespace TW::Ontology diff --git a/src/Ontology/Address.h b/src/Ontology/Address.h index 7d5b2377b31..b55ebf1f3dc 100644 --- a/src/Ontology/Address.h +++ b/src/Ontology/Address.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -22,7 +22,7 @@ class Address { static const size_t size = 20; static const uint8_t version = 0x17; - std::array data; + std::array _data; /// Initializes an address with a public key. explicit Address(const PublicKey& publicKey); @@ -44,7 +44,7 @@ class Address { }; inline bool operator==(const Address& lhs, const Address& rhs) { - return lhs.data == rhs.data; + return lhs._data == rhs._data; } } // namespace TW::Ontology diff --git a/src/Ontology/Asset.h b/src/Ontology/Asset.h index 123b6738ab7..a393db0f18e 100644 --- a/src/Ontology/Asset.h +++ b/src/Ontology/Asset.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -10,7 +10,7 @@ #include "Signer.h" #include "Transaction.h" #include "../BinaryCoding.h" -#include "../Data.h" +#include "Data.h" #include #include @@ -19,18 +19,19 @@ namespace TW::Ontology { class Asset { - protected: +protected: const uint8_t txType = 0xD1; - public: +public: + virtual ~Asset() noexcept = default; virtual Data contractAddress() = 0; virtual Transaction decimals(uint32_t nonce) = 0; - virtual Transaction balanceOf(const Address &address, uint32_t nonce) = 0; + virtual Transaction balanceOf(const Address& address, uint32_t nonce) = 0; - virtual Transaction transfer(const Signer &from, const Address &to, uint64_t amount, - const Signer &payer, uint64_t gasPrice, uint64_t gasLimit, + virtual Transaction transfer(const Signer& from, const Address& to, uint64_t amount, + const Signer& payer, uint64_t gasPrice, uint64_t gasLimit, uint32_t nonce) = 0; }; } // namespace TW::Ontology diff --git a/src/Ontology/Entry.cpp b/src/Ontology/Entry.cpp index f0129ed1e08..9f4de742362 100644 --- a/src/Ontology/Entry.cpp +++ b/src/Ontology/Entry.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,19 +9,20 @@ #include "Address.h" #include "Signer.h" -using namespace TW::Ontology; -using namespace std; +namespace TW::Ontology { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +} // namespace TW::Ontology diff --git a/src/Ontology/Entry.h b/src/Ontology/Entry.h index ba3cf582d26..68df17b8baf 100644 --- a/src/Ontology/Entry.h +++ b/src/Ontology/Entry.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -12,12 +12,11 @@ namespace TW::Ontology { /// Entry point for implementation of Ontology coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeOntology}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::Ontology diff --git a/src/Ontology/Oep4.cpp b/src/Ontology/Oep4.cpp new file mode 100644 index 00000000000..3c08c8af512 --- /dev/null +++ b/src/Ontology/Oep4.cpp @@ -0,0 +1,69 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Oep4.h" +#include + +namespace TW::Ontology { +Oep4::Oep4(const Address addr) noexcept + : oep4Contract(addr._data.begin(), addr._data.end()) { +} + +Oep4::Oep4(const Data bin) noexcept + : oep4Contract(bin) { +} + +Transaction Oep4::readOnlyMethod(std::string method_name, uint32_t nonce) { + Address addr(oep4Contract); + NeoVmParamValue::ParamArray args{}; + auto invokeCode = ParamsBuilder::buildOep4InvokeCode(addr, method_name, {args}); + + return Transaction(0, 0xD1, nonce, 0, 0, "", invokeCode); +} + +Transaction Oep4::name(uint32_t nonce) { + return Oep4::readOnlyMethod("name", nonce); +} + +Transaction Oep4::symbol(uint32_t nonce) { + return Oep4::readOnlyMethod("symbol", nonce); +} + +Transaction Oep4::decimals(uint32_t nonce) { + return Oep4::readOnlyMethod("decimals", nonce); +} + +Transaction Oep4::totalSupply(uint32_t nonce) { + return Oep4::readOnlyMethod("totalSupply", nonce); +} + +Transaction Oep4::balanceOf(const Address& user, uint32_t nonce) { + Address contract(oep4Contract); + Data d(std::begin(user._data), std::end(user._data)); + NeoVmParamValue::ParamArray args{d}; + auto invokeCode = ParamsBuilder::buildOep4InvokeCode(contract, "balanceOf", {args}); + return Transaction(0, 0xD1, nonce, 0, 0, "", invokeCode); +} + +Transaction Oep4::transfer(const Signer& from, const Address& to, uint64_t amount, + const Signer& payer, uint64_t gasPrice, uint64_t gasLimit, + uint32_t nonce) { + Address contract(oep4Contract); + + auto fromAddr = from.getAddress(); + NeoVmParamValue::ParamArray args{fromAddr._data, to._data, amount}; + // yes, invoke neovm is not like ont transfer + std::reverse(args.begin(), args.end()); + auto invokeCode = ParamsBuilder::buildOep4InvokeCode(contract, "transfer", {args}); + + auto tx = Transaction(0, 0xD1, nonce, gasPrice, gasLimit, payer.getAddress().string(), invokeCode); + from.sign(tx); + payer.addSign(tx); + + return tx; +} + +} // namespace TW::Ontology diff --git a/src/Ontology/Oep4.h b/src/Ontology/Oep4.h new file mode 100644 index 00000000000..d121d8a1ceb --- /dev/null +++ b/src/Ontology/Oep4.h @@ -0,0 +1,41 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Address.h" +#include "Asset.h" +#include "ParamsBuilder.h" +#include "Transaction.h" +#include "Data.h" + +namespace TW::Ontology { + +class Oep4 { +private: + static constexpr uint8_t version = 0x00; + + Data oep4Contract; + // building neovm instruction for oep4 readonly method(name, symbol...) + // are all the same except the method name + Transaction readOnlyMethod(std::string methodName, uint32_t nonce); + +public: + explicit Oep4(const Address addr) noexcept; + explicit Oep4(const Data bin) noexcept; + Oep4() = delete; + Data contractAddress() { return oep4Contract; } + Transaction name(uint32_t nonce); + Transaction symbol(uint32_t nonce); + Transaction decimals(uint32_t nonce); + Transaction totalSupply(uint32_t nonce); + Transaction balanceOf(const Address& address, uint32_t nonce); + Transaction transfer(const Signer& from, const Address& to, uint64_t amount, + const Signer& payer, uint64_t gasPrice, uint64_t gasLimit, + uint32_t nonce); +}; + +} // namespace TW::Ontology diff --git a/src/Ontology/Oep4TxBuilder.cpp b/src/Ontology/Oep4TxBuilder.cpp new file mode 100644 index 00000000000..04e5f2c108c --- /dev/null +++ b/src/Ontology/Oep4TxBuilder.cpp @@ -0,0 +1,51 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Oep4TxBuilder.h" +#include "../HexCoding.h" + +namespace TW::Ontology { + +Data Oep4TxBuilder::decimals(const Ontology::Proto::SigningInput& input) { + Oep4 oep4(parse_hex(input.contract())); + auto transaction = oep4.decimals(input.nonce()); + auto encoded = transaction.serialize(); + return encoded; +} + +Data Oep4TxBuilder::balanceOf(const Ontology::Proto::SigningInput& input) { + Oep4 oep4(parse_hex(input.contract())); + auto queryAddress = Address(input.query_address()); + auto transaction = oep4.balanceOf(queryAddress, input.nonce()); + auto encoded = transaction.serialize(); + return encoded; +} + +Data Oep4TxBuilder::transfer(const Ontology::Proto::SigningInput& input) { + Oep4 oep4(parse_hex(input.contract())); + auto payerSigner = Signer(PrivateKey(input.payer_private_key())); + auto fromSigner = Signer(PrivateKey(input.owner_private_key())); + auto toAddress = Address(input.to_address()); + auto tranferTx = oep4.transfer(fromSigner, toAddress, input.amount(), payerSigner, + input.gas_price(), input.gas_limit(), input.nonce()); + auto encoded = tranferTx.serialize(); + return encoded; +} + +Data Oep4TxBuilder::build(const Ontology::Proto::SigningInput& input) { + auto method = std::string(input.method().begin(), input.method().end()); + if (method == "transfer") { + return Oep4TxBuilder::transfer(input); + } else if (method == "balanceOf") { + return Oep4TxBuilder::balanceOf(input); + } else if (method == "decimals") { + return Oep4TxBuilder::decimals(input); + } + + return Data(); +} + +} // namespace TW::Ontology diff --git a/src/Ontology/Oep4TxBuilder.h b/src/Ontology/Oep4TxBuilder.h new file mode 100644 index 00000000000..138f955aa82 --- /dev/null +++ b/src/Ontology/Oep4TxBuilder.h @@ -0,0 +1,29 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Oep4.h" + +#include "../proto/Ontology.pb.h" + +#include + +namespace TW::Ontology { + +class Oep4TxBuilder { + +public: + static Data decimals(const Ontology::Proto::SigningInput& input); + + static Data balanceOf(const Ontology::Proto::SigningInput& input); + + static Data transfer(const Ontology::Proto::SigningInput& input); + + static Data build(const Ontology::Proto::SigningInput& input); +}; + +} // namespace TW::Ontology diff --git a/src/Ontology/Ong.cpp b/src/Ontology/Ong.cpp index 383a411e549..e37ff4ae6a7 100644 --- a/src/Ontology/Ong.cpp +++ b/src/Ontology/Ong.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -10,34 +10,33 @@ #include -using namespace TW; -using namespace TW::Ontology; +namespace TW::Ontology { Transaction Ong::decimals(uint32_t nonce) { auto builder = ParamsBuilder(); auto invokeCode = - ParamsBuilder::buildNativeInvokeCode(contractAddress(), version, "decimals", Data()); + ParamsBuilder::buildNativeInvokeCode(contractAddress(), version, "decimals", {Data()}); auto tx = Transaction(version, txType, nonce, (uint64_t)0, (uint64_t)0, (std::string) "", invokeCode); return tx; } -Transaction Ong::balanceOf(const Address &address, uint32_t nonce) { +Transaction Ong::balanceOf(const Address& address, uint32_t nonce) { auto builder = ParamsBuilder(); auto invokeCode = - ParamsBuilder::buildNativeInvokeCode(contractAddress(), version, "balanceOf", address.data); + ParamsBuilder::buildNativeInvokeCode(contractAddress(), version, "balanceOf", {address._data}); auto tx = Transaction(version, txType, nonce, (uint64_t)0, (uint64_t)0, (std::string) "", invokeCode); return tx; } -Transaction Ong::transfer(const Signer &from, const Address &to, uint64_t amount, - const Signer &payer, uint64_t gasPrice, uint64_t gasLimit, +Transaction Ong::transfer(const Signer& from, const Address& to, uint64_t amount, + const Signer& payer, uint64_t gasPrice, uint64_t gasLimit, uint32_t nonce) { - std::list transferParam{from.getAddress().data, to.data, amount}; - std::vector args{transferParam}; + NeoVmParamValue::ParamList transferParam{from.getAddress()._data, to._data, amount}; + NeoVmParamValue::ParamArray args{transferParam}; auto invokeCode = - ParamsBuilder::buildNativeInvokeCode(contractAddress(), 0x00, "transfer", args); + ParamsBuilder::buildNativeInvokeCode(contractAddress(), 0x00, "transfer", {args}); auto tx = Transaction(version, txType, nonce, gasPrice, gasLimit, payer.getAddress().string(), invokeCode); from.sign(tx); @@ -45,16 +44,18 @@ Transaction Ong::transfer(const Signer &from, const Address &to, uint64_t amount return tx; } -Transaction Ong::withdraw(const Signer &claimer, const Address &receiver, uint64_t amount, - const Signer &payer, uint64_t gasPrice, uint64_t gasLimit, +Transaction Ong::withdraw(const Signer& claimer, const Address& receiver, uint64_t amount, + const Signer& payer, uint64_t gasPrice, uint64_t gasLimit, uint32_t nonce) { auto ontContract = Address("AFmseVrdL9f9oyCzZefL9tG6UbvhUMqNMV"); - std::list args{claimer.getAddress().data, ontContract.data, receiver.data, amount}; + NeoVmParamValue::ParamList args{claimer.getAddress()._data, ontContract._data, receiver._data, amount}; auto invokeCode = - ParamsBuilder::buildNativeInvokeCode(contractAddress(), 0x00, "transferFrom", args); + ParamsBuilder::buildNativeInvokeCode(contractAddress(), 0x00, "transferFrom", {args}); auto tx = Transaction(version, txType, nonce, gasPrice, gasLimit, payer.getAddress().string(), invokeCode); claimer.sign(tx); payer.addSign(tx); return tx; -} \ No newline at end of file +} + +} // namespace TW::Ontology diff --git a/src/Ontology/Ong.h b/src/Ontology/Ong.h index 27de0c1d108..8537f605967 100644 --- a/src/Ontology/Ong.h +++ b/src/Ontology/Ong.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -7,7 +7,7 @@ #pragma once #include "Asset.h" -#include "../Data.h" +#include "Data.h" namespace TW::Ontology { diff --git a/src/Ontology/OngTxBuilder.cpp b/src/Ontology/OngTxBuilder.cpp index 8fc3c7f89d0..f44d46a14b9 100644 --- a/src/Ontology/OngTxBuilder.cpp +++ b/src/Ontology/OngTxBuilder.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -6,23 +6,22 @@ #include "OngTxBuilder.h" -using namespace TW; -using namespace TW::Ontology; +namespace TW::Ontology { -Data OngTxBuilder::decimals(const Ontology::Proto::SigningInput &input) { +Data OngTxBuilder::decimals(const Ontology::Proto::SigningInput& input) { auto transaction = Ong().decimals(input.nonce()); auto encoded = transaction.serialize(); return encoded; } -Data OngTxBuilder::balanceOf(const Ontology::Proto::SigningInput &input) { +Data OngTxBuilder::balanceOf(const Ontology::Proto::SigningInput& input) { auto queryAddress = Address(input.query_address()); auto transaction = Ong().balanceOf(queryAddress, input.nonce()); auto encoded = transaction.serialize(); return encoded; } -Data OngTxBuilder::transfer(const Ontology::Proto::SigningInput &input) { +Data OngTxBuilder::transfer(const Ontology::Proto::SigningInput& input) { auto payer = Signer(PrivateKey(input.payer_private_key())); auto owner = Signer(PrivateKey(input.owner_private_key())); auto toAddress = Address(input.to_address()); @@ -32,7 +31,7 @@ Data OngTxBuilder::transfer(const Ontology::Proto::SigningInput &input) { return encoded; } -Data OngTxBuilder::withdraw(const Ontology::Proto::SigningInput &input) { +Data OngTxBuilder::withdraw(const Ontology::Proto::SigningInput& input) { auto payer = Signer(PrivateKey(input.payer_private_key())); auto owner = Signer(PrivateKey(input.owner_private_key())); auto toAddress = Address(input.to_address()); @@ -42,7 +41,7 @@ Data OngTxBuilder::withdraw(const Ontology::Proto::SigningInput &input) { return encoded; } -Data OngTxBuilder::build(const Ontology::Proto::SigningInput &input) { +Data OngTxBuilder::build(const Ontology::Proto::SigningInput& input) { auto method = std::string(input.method().begin(), input.method().end()); if (method == "transfer") { return OngTxBuilder::transfer(input); @@ -55,3 +54,5 @@ Data OngTxBuilder::build(const Ontology::Proto::SigningInput &input) { } return Data(); } + +} // namespace TW::Ontology diff --git a/src/Ontology/OngTxBuilder.h b/src/Ontology/OngTxBuilder.h index e5843a0909b..a97c169c5fd 100644 --- a/src/Ontology/OngTxBuilder.h +++ b/src/Ontology/OngTxBuilder.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 diff --git a/src/Ontology/Ont.cpp b/src/Ontology/Ont.cpp index 08ce5847328..55a9bf5f2a8 100644 --- a/src/Ontology/Ont.cpp +++ b/src/Ontology/Ont.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -10,37 +10,38 @@ #include -using namespace TW; -using namespace TW::Ontology; +namespace TW::Ontology { Transaction Ont::decimals(uint32_t nonce) { auto builder = ParamsBuilder(); auto invokeCode = - ParamsBuilder::buildNativeInvokeCode(contractAddress(), version, "decimals", Data()); + ParamsBuilder::buildNativeInvokeCode(contractAddress(), version, "decimals", {Data()}); auto tx = Transaction((uint8_t)0, txType, nonce, (uint64_t)0, (uint64_t)0, (std::string) "", invokeCode); return tx; } -Transaction Ont::balanceOf(const Address &address, uint32_t nonce) { +Transaction Ont::balanceOf(const Address& address, uint32_t nonce) { auto builder = ParamsBuilder(); auto invokeCode = - ParamsBuilder::buildNativeInvokeCode(contractAddress(), version, "balanceOf", address.data); + ParamsBuilder::buildNativeInvokeCode(contractAddress(), version, "balanceOf", {address._data}); auto tx = Transaction((uint8_t)0, txType, nonce, (uint64_t)0, (uint64_t)0, (std::string) "", invokeCode); return tx; } -Transaction Ont::transfer(const Signer &from, const Address &to, uint64_t amount, - const Signer &payer, uint64_t gasPrice, uint64_t gasLimit, +Transaction Ont::transfer(const Signer& from, const Address& to, uint64_t amount, + const Signer& payer, uint64_t gasPrice, uint64_t gasLimit, uint32_t nonce) { - std::list transferParam{from.getAddress().data, to.data, amount}; - std::vector args{transferParam}; + NeoVmParamValue::ParamList transferParam{from.getAddress()._data, to._data, amount}; + NeoVmParamValue::ParamArray args{transferParam}; auto invokeCode = - ParamsBuilder::buildNativeInvokeCode(contractAddress(), 0x00, "transfer", args); + ParamsBuilder::buildNativeInvokeCode(contractAddress(), 0x00, "transfer", {args}); auto tx = Transaction(version, txType, nonce, gasPrice, gasLimit, payer.getAddress().string(), invokeCode); from.sign(tx); payer.addSign(tx); return tx; } + +} // namespace TW::Ontology diff --git a/src/Ontology/Ont.h b/src/Ontology/Ont.h index d0dce26e64d..88806c2a197 100644 --- a/src/Ontology/Ont.h +++ b/src/Ontology/Ont.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -7,7 +7,7 @@ #pragma once #include "Asset.h" -#include "../Data.h" +#include "Data.h" namespace TW::Ontology { diff --git a/src/Ontology/OntTxBuilder.cpp b/src/Ontology/OntTxBuilder.cpp index 9b727313fae..735681c14fc 100644 --- a/src/Ontology/OntTxBuilder.cpp +++ b/src/Ontology/OntTxBuilder.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -6,23 +6,22 @@ #include "OntTxBuilder.h" -using namespace TW; -using namespace TW::Ontology; +namespace TW::Ontology { -Data OntTxBuilder::decimals(const Ontology::Proto::SigningInput &input) { +Data OntTxBuilder::decimals(const Ontology::Proto::SigningInput& input) { auto transaction = Ont().decimals(input.nonce()); auto encoded = transaction.serialize(); return encoded; } -Data OntTxBuilder::balanceOf(const Ontology::Proto::SigningInput &input) { +Data OntTxBuilder::balanceOf(const Ontology::Proto::SigningInput& input) { auto queryAddress = Address(input.query_address()); auto transaction = Ont().balanceOf(queryAddress, input.nonce()); auto encoded = transaction.serialize(); return encoded; } -Data OntTxBuilder::transfer(const Ontology::Proto::SigningInput &input) { +Data OntTxBuilder::transfer(const Ontology::Proto::SigningInput& input) { auto payerSigner = Signer(PrivateKey(input.payer_private_key())); auto fromSigner = Signer(PrivateKey(input.owner_private_key())); auto toAddress = Address(input.to_address()); @@ -32,7 +31,7 @@ Data OntTxBuilder::transfer(const Ontology::Proto::SigningInput &input) { return encoded; } -Data OntTxBuilder::build(const Ontology::Proto::SigningInput &input) { +Data OntTxBuilder::build(const Ontology::Proto::SigningInput& input) { auto method = std::string(input.method().begin(), input.method().end()); if (method == "transfer") { return OntTxBuilder::transfer(input); @@ -43,3 +42,5 @@ Data OntTxBuilder::build(const Ontology::Proto::SigningInput &input) { } return Data(); } + +} // namespace TW::Ontology diff --git a/src/Ontology/OntTxBuilder.h b/src/Ontology/OntTxBuilder.h index aabc1cc8e40..5cf64cbe16c 100644 --- a/src/Ontology/OntTxBuilder.h +++ b/src/Ontology/OntTxBuilder.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 diff --git a/src/Ontology/OpCode.h b/src/Ontology/OpCode.h index 6c44454b1a3..3f7904242fd 100644 --- a/src/Ontology/OpCode.h +++ b/src/Ontology/OpCode.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -25,5 +25,6 @@ static const uint8_t TO_ALT_STACK{0x6B}; static const uint8_t FROM_ALT_STACK{0x6C}; static const uint8_t SWAP{0x7C}; static const uint8_t HAS_KEY{0xC8}; +static const uint8_t APP_CALL{0x67}; -} // namespace TW::Ontology \ No newline at end of file +} // namespace TW::Ontology diff --git a/src/Ontology/ParamsBuilder.cpp b/src/Ontology/ParamsBuilder.cpp index 367863b085e..c4db460cfb7 100644 --- a/src/Ontology/ParamsBuilder.cpp +++ b/src/Ontology/ParamsBuilder.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -12,34 +12,40 @@ #include #include -#include #include +#include -using namespace TW; -using namespace TW::Ontology; - -void ParamsBuilder::buildNeoVmParam(ParamsBuilder& builder, const boost::any& param) { - if (param.type() == typeid(std::string)) { - builder.push(boost::any_cast(param)); - } else if (param.type() == typeid(std::array)) { - builder.push(boost::any_cast>(param)); - } else if (param.type() == typeid(Data)) { - builder.push(boost::any_cast(param)); - } else if (param.type() == typeid(uint64_t)) { - builder.push(boost::any_cast(param)); - } else if (param.type() == typeid(std::vector)) { - auto paramVec = boost::any_cast>(param); - for (const auto& item : paramVec) { - ParamsBuilder::buildNeoVmParam(builder, item); +namespace TW::Ontology { + +void ParamsBuilder::buildNeoVmParam(ParamsBuilder& builder, const NeoVmParamValue& param) { + + if (auto* paramStr = std::get_if(¶m.params); paramStr) { + builder.push(*paramStr); + } else if (auto* paramFixedArray = std::get_if(¶m.params); paramFixedArray) { + builder.push(*paramFixedArray); + } else if (auto* paramData = std::get_if(¶m.params); paramData) { + builder.push(*paramData); + } else if (auto* paramInteger = std::get_if(¶m.params); paramInteger) { + builder.push(*paramInteger); + } else if (auto* paramArray = std::get_if(¶m.params); paramArray) { + for (auto&& item : *paramArray) { + std::visit([&builder](auto&& arg) { + NeoVmParamValue::ParamVariant value = arg; + ParamsBuilder::buildNeoVmParam(builder, {value}); + }, + item); } - builder.push(static_cast(paramVec.size())); + builder.push(static_cast(paramArray->size())); builder.pushBack(PACK); - } else if (param.type() == typeid(std::list)) { + } else if (auto* paramList = std::get_if(¶m.params); paramList) { builder.pushBack(PUSH0); builder.pushBack(NEW_STRUCT); builder.pushBack(TO_ALT_STACK); - for (auto const& p : boost::any_cast>(param)) { - ParamsBuilder::buildNeoVmParam(builder, p); + for (auto const& p : *paramList) { + std::visit([&builder](auto&& arg) { + NeoVmParamValue::ParamVariant value = arg; + ParamsBuilder::buildNeoVmParam(builder, {value}); + }, p); builder.pushBack(DUP_FROM_ALT_STACK); builder.pushBack(SWAP); builder.pushBack(HAS_KEY); @@ -221,7 +227,7 @@ Data ParamsBuilder::fromMultiPubkey(uint8_t m, const std::vector& pubKeys) } Data ParamsBuilder::buildNativeInvokeCode(const Data& contractAddress, uint8_t version, - const std::string& method, const boost::any& params) { + const std::string& method, const NeoVmParamValue& params) { ParamsBuilder builder; ParamsBuilder::buildNeoVmParam(builder, params); builder.push(Data(method.begin(), method.end())); @@ -231,4 +237,18 @@ Data ParamsBuilder::buildNativeInvokeCode(const Data& contractAddress, uint8_t v std::string nativeInvoke = "Ontology.Native.Invoke"; builder.push(Data(nativeInvoke.begin(), nativeInvoke.end())); return builder.getBytes(); -} \ No newline at end of file +} + +Data ParamsBuilder::buildOep4InvokeCode(const Address& contractAddress, const std::string& method, const NeoVmParamValue& params) { + ParamsBuilder builder; + ParamsBuilder::buildNeoVmParam(builder, params); + builder.push(method); + builder.pushBack(APP_CALL); + Address clone = contractAddress; + std::reverse(std::begin(clone._data), std::end(clone._data)); + builder.pushBack(clone._data); + + return builder.getBytes(); +} + +} // namespace TW::Ontology diff --git a/src/Ontology/ParamsBuilder.h b/src/Ontology/ParamsBuilder.h index cf2b3202003..0a4acfd55c5 100644 --- a/src/Ontology/ParamsBuilder.h +++ b/src/Ontology/ParamsBuilder.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -7,23 +7,35 @@ #pragma once #include "../BinaryCoding.h" -#include "../Data.h" - -#include +#include "Data.h" #include #include #include #include +#include +#include + +#include "Address.h" namespace TW::Ontology { +struct NeoVmParamValue; + +struct NeoVmParamValue { + using ParamFixedArray = std::array; + using ParamList = std::list>; + using ParamArray = std::vector>; + using ParamVariant = std::variant; + ParamVariant params; +}; + class ParamsBuilder { - private: +private: std::vector bytes; - public: +public: static const size_t MAX_PK_SIZE = 16; std::vector getBytes() { return bytes; } @@ -36,7 +48,7 @@ class ParamsBuilder { static Data fromMultiPubkey(uint8_t m, const std::vector& pubKeys); - static void buildNeoVmParam(ParamsBuilder& builder, const boost::any& param); + static void buildNeoVmParam(ParamsBuilder& builder, const NeoVmParamValue& param); static void buildNeoVmParam(ParamsBuilder& builder, const std::string& param); @@ -77,7 +89,9 @@ class ParamsBuilder { static std::vector buildNativeInvokeCode(const std::vector& contractAddress, uint8_t version, const std::string& method, - const boost::any& params); + const NeoVmParamValue& params); + + static std::vector buildOep4InvokeCode(const Address& contractAddress, const std::string& method, const NeoVmParamValue& params); }; -} // namespace TW::Ontology \ No newline at end of file +} // namespace TW::Ontology diff --git a/src/Ontology/SigData.cpp b/src/Ontology/SigData.cpp index e1f52e1b1e2..857504de0e2 100644 --- a/src/Ontology/SigData.cpp +++ b/src/Ontology/SigData.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,8 +9,7 @@ #include "ParamsBuilder.h" #include "SigData.h" -using namespace TW; -using namespace TW::Ontology; +namespace TW::Ontology { Data SigData::serialize() { auto sigInfo = ParamsBuilder::fromSigs(sigs); @@ -28,3 +27,5 @@ Data SigData::serialize() { builder.pushVar(verifyInfo); return builder.getBytes(); } + +} // namespace TW::Ontology diff --git a/src/Ontology/SigData.h b/src/Ontology/SigData.h index eaa054c4115..0ee703922c1 100644 --- a/src/Ontology/SigData.h +++ b/src/Ontology/SigData.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" namespace TW::Ontology { diff --git a/src/Ontology/Signer.cpp b/src/Ontology/Signer.cpp index 70fbc9e0661..876b97bf9c7 100644 --- a/src/Ontology/Signer.cpp +++ b/src/Ontology/Signer.cpp @@ -1,21 +1,21 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. #include "Signer.h" + #include "HexCoding.h" #include "SigData.h" +#include "../Ontology/Oep4TxBuilder.h" + #include "../Ontology/OngTxBuilder.h" #include "../Ontology/OntTxBuilder.h" -#include "../Hash.h" - #include -using namespace TW; -using namespace TW::Ontology; +namespace TW::Ontology { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto contract = std::string(input.contract().begin(), input.contract().end()); @@ -27,20 +27,25 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { } else if (contract == "ONG") { auto encoded = OngTxBuilder::build(input); output.set_encoded(encoded.data(), encoded.size()); + } else { + // then assume it's oep4 address + auto encoded = Oep4TxBuilder::build(input); + output.set_encoded(encoded.data(), encoded.size()); } } catch (...) { } return output; } -Signer::Signer(TW::PrivateKey priKey) : privateKey(std::move(priKey)) { - auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1); +Signer::Signer(TW::PrivateKey priKey) + : privKey(std::move(priKey)) { + auto pubKey = privKey.getPublicKey(TWPublicKeyTypeNIST256p1); publicKey = pubKey.bytes; address = Address(pubKey).string(); } PrivateKey Signer::getPrivateKey() const { - return privateKey; + return privKey; } PublicKey Signer::getPublicKey() const { @@ -68,3 +73,5 @@ void Signer::addSign(Transaction& tx) const { signature.pop_back(); tx.sigVec.emplace_back(publicKey, signature, 1); } + +} // namespace TW::Ontology diff --git a/src/Ontology/Signer.h b/src/Ontology/Signer.h index d9e8166ae9d..4f2dc8459ca 100644 --- a/src/Ontology/Signer.h +++ b/src/Ontology/Signer.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -15,19 +15,19 @@ #include #include - namespace TW::Ontology { class Signer { - public: +public: /// Signs a Proto::SigningInput transaction static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; - private: + +private: Data publicKey; - TW::PrivateKey privateKey; + TW::PrivateKey privKey; std::string address; - public: +public: explicit Signer(TW::PrivateKey priKey); PrivateKey getPrivateKey() const; @@ -41,8 +41,3 @@ class Signer { void addSign(Transaction& tx) const; }; } // namespace TW::Ontology - -/// Wrapper for C interface. -struct TWOntologySigner { - TW::Ontology::Signer impl; -}; diff --git a/src/Ontology/Transaction.cpp b/src/Ontology/Transaction.cpp index a3306871b75..7bf47d716f4 100644 --- a/src/Ontology/Transaction.cpp +++ b/src/Ontology/Transaction.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -8,13 +8,9 @@ #include "Address.h" #include "ParamsBuilder.h" -#include "../Hash.h" -#include "../HexCoding.h" - #include -using namespace TW; -using namespace TW::Ontology; +namespace TW::Ontology { const std::string Transaction::ZERO_PAYER = "AFmseVrdL9f9oyCzZefL9tG6UbvhPbdYzM"; @@ -25,7 +21,7 @@ std::vector Transaction::serializeUnsigned() { builder.pushBack(nonce); builder.pushBack(gasPrice); builder.pushBack(gasLimit); - builder.pushBack(Address(payer).data); + builder.pushBack(Address(payer)._data); if (!payload.empty()) { builder.pushVar(payload); } @@ -58,3 +54,5 @@ std::vector Transaction::serialize(const PublicKey& pk) { builder.pushBack((uint8_t)0xAC); return builder.getBytes(); } + +} // namespace TW::Ontology diff --git a/src/Ontology/Transaction.h b/src/Ontology/Transaction.h index e4f9796b4f6..0f1388b3eb7 100644 --- a/src/Ontology/Transaction.h +++ b/src/Ontology/Transaction.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 diff --git a/src/Polkadot/Address.h b/src/Polkadot/Address.h index 183018ea3db..10f3828c2a0 100644 --- a/src/Polkadot/Address.h +++ b/src/Polkadot/Address.h @@ -6,9 +6,9 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" -#include "../SS58Address.h" +#include "SS58Address.h" #include #include diff --git a/src/Polkadot/Entry.cpp b/src/Polkadot/Entry.cpp index 4db8a184a19..f97ae843ca9 100644 --- a/src/Polkadot/Entry.cpp +++ b/src/Polkadot/Entry.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,19 +9,25 @@ #include "Address.h" #include "Signer.h" -using namespace TW::Polkadot; -using namespace std; +namespace TW::Polkadot { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + const auto addr = Address(address); + return {addr.bytes.begin() + 1, addr.bytes.end()}; +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +} // namespace TW::Polkadot diff --git a/src/Polkadot/Entry.h b/src/Polkadot/Entry.h index e0932bba62a..d36ac8845af 100644 --- a/src/Polkadot/Entry.h +++ b/src/Polkadot/Entry.h @@ -12,12 +12,12 @@ namespace TW::Polkadot { /// Entry point for implementation of Polkadot coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypePolkadot}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::Polkadot diff --git a/src/Polkadot/Extrinsic.cpp b/src/Polkadot/Extrinsic.cpp index e77eff45821..8cab2fb5cef 100644 --- a/src/Polkadot/Extrinsic.cpp +++ b/src/Polkadot/Extrinsic.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -8,8 +8,7 @@ #include #include -using namespace TW; -using namespace TW::Polkadot; +namespace TW::Polkadot { static constexpr uint8_t signedBit = 0x80; static constexpr uint8_t sigTypeEd25519 = 0x00; @@ -18,7 +17,7 @@ static constexpr uint32_t multiAddrSpecVersion = 28; static constexpr uint32_t multiAddrSpecVersionKsm = 2028; static const std::string balanceTransfer = "Balances.transfer"; -static const std::string utilityBatch = "Utility.batch"; +static const std::string utilityBatch = "Utility.batch_all"; static const std::string stakingBond = "Staking.bond"; static const std::string stakingBondExtra = "Staking.bond_extra"; static const std::string stakingUnbond = "Staking.unbond"; @@ -28,41 +27,40 @@ static const std::string stakingChill = "Staking.chill"; // Readable decoded call index can be found from https://polkascan.io static std::map polkadotCallIndices = { - {balanceTransfer, Data{0x05, 0x00}}, - {stakingBond, Data{0x07, 0x00}}, - {stakingBondExtra, Data{0x07, 0x01}}, - {stakingUnbond, Data{0x07, 0x02}}, + {balanceTransfer, Data{0x05, 0x00}}, + {stakingBond, Data{0x07, 0x00}}, + {stakingBondExtra, Data{0x07, 0x01}}, + {stakingUnbond, Data{0x07, 0x02}}, {stakingWithdrawUnbond, Data{0x07, 0x03}}, - {stakingNominate, Data{0x07, 0x05}}, - {stakingChill, Data{0x07, 0x06}}, - {utilityBatch, Data{0x1a, 0x02}}, + {stakingNominate, Data{0x07, 0x05}}, + {stakingChill, Data{0x07, 0x06}}, + {utilityBatch, Data{0x1a, 0x02}}, }; static std::map kusamaCallIndices = { - {balanceTransfer, Data{0x04, 0x00}}, - {stakingBond, Data{0x06, 0x00}}, - {stakingBondExtra, Data{0x06, 0x01}}, - {stakingUnbond, Data{0x06, 0x02}}, + {balanceTransfer, Data{0x04, 0x00}}, + {stakingBond, Data{0x06, 0x00}}, + {stakingBondExtra, Data{0x06, 0x01}}, + {stakingUnbond, Data{0x06, 0x02}}, {stakingWithdrawUnbond, Data{0x06, 0x03}}, - {stakingNominate, Data{0x06, 0x05}}, - {stakingChill, Data{0x06, 0x06}}, - {utilityBatch, Data{0x18, 0x02}}, + {stakingNominate, Data{0x06, 0x05}}, + {stakingChill, Data{0x06, 0x06}}, + {utilityBatch, Data{0x18, 0x02}}, }; static Data getCallIndex(TWSS58AddressType network, const std::string& key) { - switch (network) { - case TWSS58AddressTypePolkadot: + if (network == TWSS58AddressTypePolkadot) { return polkadotCallIndices[key]; - case TWSS58AddressTypeKusama: - return kusamaCallIndices[key]; } + // network == TWSS58AddressTypeKusama + return kusamaCallIndices[key]; } bool Extrinsic::encodeRawAccount(TWSS58AddressType network, uint32_t specVersion) { if ((network == TWSS58AddressTypePolkadot && specVersion >= multiAddrSpecVersion) || (network == TWSS58AddressTypeKusama && specVersion >= multiAddrSpecVersionKsm)) { - return false; - } + return false; + } return true; } @@ -113,103 +111,115 @@ Data Extrinsic::encodeBatchCall(const std::vector& calls, TWSS58AddressTyp Data Extrinsic::encodeStakingCall(const Proto::Staking& staking, TWSS58AddressType network, uint32_t specVersion) { Data data; switch (staking.message_oneof_case()) { - case Proto::Staking::kBond: - { - auto address = SS58Address(staking.bond().controller(), byte(network)); - auto value = load(staking.bond().value()); - auto reward = byte(staking.bond().reward_destination()); - // call index - append(data, getCallIndex(network, stakingBond)); - // controller - append(data, encodeAccountId(address.keyBytes(), encodeRawAccount(network, specVersion))); - // value - append(data, encodeCompact(value)); - // reward destination - append(data, reward); - } - break; - - case Proto::Staking::kBondAndNominate: - { - // encode call1 - Data call1; - { - auto staking1 = Proto::Staking(); - auto* bond = staking1.mutable_bond(); - bond->set_controller(staking.bond_and_nominate().controller()); - bond->set_value(staking.bond_and_nominate().value()); - bond->set_reward_destination(staking.bond_and_nominate().reward_destination()); - // recursive call - call1 = encodeStakingCall(staking1, network, specVersion); - } - - // encode call2 - Data call2; - { - auto staking2 = Proto::Staking(); - auto* nominate = staking2.mutable_nominate(); - for (auto i = 0; i < staking.bond_and_nominate().nominators_size(); ++i) { - nominate->add_nominators(staking.bond_and_nominate().nominators(i)); - } - // recursive call - call2 = encodeStakingCall(staking2, network, specVersion); - } - - auto calls = std::vector{call1, call2}; - data = encodeBatchCall(calls, network); - } - break; - - case Proto::Staking::kBondExtra: - { - auto value = load(staking.bond_extra().value()); - // call index - append(data, getCallIndex(network, stakingBondExtra)); - // value - append(data, encodeCompact(value)); - } - break; - - case Proto::Staking::kUnbond: - { - auto value = load(staking.unbond().value()); - // call index - append(data, getCallIndex(network, stakingUnbond)); - // value - append(data, encodeCompact(value)); - } - break; - - case Proto::Staking::kWithdrawUnbonded: - { - auto spans = staking.withdraw_unbonded().slashing_spans(); - // call index - append(data, getCallIndex(network, stakingWithdrawUnbond)); - // num_slashing_spans - encode32LE(spans, data); - } - break; - - case Proto::Staking::kNominate: - { - std::vector accountIds; - for (auto& n : staking.nominate().nominators()) { - accountIds.emplace_back(SS58Address(n, network)); - } - // call index - append(data, getCallIndex(network, stakingNominate)); - // nominators - append(data, encodeAccountIds(accountIds, encodeRawAccount(network, specVersion))); + case Proto::Staking::kBond: { + auto address = SS58Address(staking.bond().controller(), byte(network)); + auto value = load(staking.bond().value()); + auto reward = byte(staking.bond().reward_destination()); + // call index + append(data, getCallIndex(network, stakingBond)); + // controller + append(data, encodeAccountId(address.keyBytes(), encodeRawAccount(network, specVersion))); + // value + append(data, encodeCompact(value)); + // reward destination + append(data, reward); + } break; + + case Proto::Staking::kBondAndNominate: { + // encode call1 + Data call1; + { + auto staking1 = Proto::Staking(); + auto* bond = staking1.mutable_bond(); + bond->set_controller(staking.bond_and_nominate().controller()); + bond->set_value(staking.bond_and_nominate().value()); + bond->set_reward_destination(staking.bond_and_nominate().reward_destination()); + // recursive call + call1 = encodeStakingCall(staking1, network, specVersion); + } + + // encode call2 + Data call2; + { + auto staking2 = Proto::Staking(); + auto* nominate = staking2.mutable_nominate(); + for (auto i = 0; i < staking.bond_and_nominate().nominators_size(); ++i) { + nominate->add_nominators(staking.bond_and_nominate().nominators(i)); } - break; + // recursive call + call2 = encodeStakingCall(staking2, network, specVersion); + } + + auto calls = std::vector{call1, call2}; + data = encodeBatchCall(calls, network); + } break; - case Proto::Staking::kChill: - // call index - append(data, getCallIndex(network, stakingChill)); - break; + case Proto::Staking::kBondExtra: { + auto value = load(staking.bond_extra().value()); + // call index + append(data, getCallIndex(network, stakingBondExtra)); + // value + append(data, encodeCompact(value)); + } break; - default: - break; + case Proto::Staking::kUnbond: { + auto value = load(staking.unbond().value()); + // call index + append(data, getCallIndex(network, stakingUnbond)); + // value + append(data, encodeCompact(value)); + } break; + + case Proto::Staking::kWithdrawUnbonded: { + auto spans = staking.withdraw_unbonded().slashing_spans(); + // call index + append(data, getCallIndex(network, stakingWithdrawUnbond)); + // num_slashing_spans + encode32LE(spans, data); + } break; + + case Proto::Staking::kNominate: { + std::vector accountIds; + for (auto& n : staking.nominate().nominators()) { + accountIds.emplace_back(SS58Address(n, network)); + } + // call index + append(data, getCallIndex(network, stakingNominate)); + // nominators + append(data, encodeAccountIds(accountIds, encodeRawAccount(network, specVersion))); + } break; + + case Proto::Staking::kChill: + // call index + append(data, getCallIndex(network, stakingChill)); + break; + + case Proto::Staking::kChillAndUnbond: { + // encode call1 + Data call1; + { + auto staking1 = Proto::Staking(); + staking1.mutable_chill(); + // recursive call + call1 = encodeStakingCall(staking1, network, specVersion); + } + + // encode call2 + Data call2; + { + auto staking2 = Proto::Staking(); + auto* unbond = staking2.mutable_unbond(); + unbond->set_value(staking.chill_and_unbond().value()); + // recursive call + call2 = encodeStakingCall(staking2, network, specVersion); + } + + auto calls = std::vector{call1, call2}; + data = encodeBatchCall(calls, network); + } break; + + default: + break; } return data; } @@ -249,3 +259,5 @@ Data Extrinsic::encodeSignature(const PublicKey& signer, const Data& signature) encodeLengthPrefix(data); return data; } + +} // namespace TW::Polkadot diff --git a/src/Polkadot/Extrinsic.h b/src/Polkadot/Extrinsic.h index dc20eb75908..ccac8260998 100644 --- a/src/Polkadot/Extrinsic.h +++ b/src/Polkadot/Extrinsic.h @@ -7,7 +7,7 @@ #pragma once #include "Address.h" -#include "../Data.h" +#include "Data.h" #include "../proto/Polkadot.pb.h" #include "../uint256.h" #include "ScaleCodec.h" diff --git a/src/Polkadot/SS58Address.cpp b/src/Polkadot/SS58Address.cpp new file mode 100644 index 00000000000..34a25f7c263 --- /dev/null +++ b/src/Polkadot/SS58Address.cpp @@ -0,0 +1,117 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "SS58Address.h" + +using namespace TW; +using namespace std; + +bool SS58Address::isValid(const std::string& string, uint32_t network) { + const auto decoded = Base58::bitcoin.decode(string); + byte decodedNetworkSize = 0; + uint32_t decodedNetwork = 0; + if (!decodeNetwork(decoded, decodedNetworkSize, decodedNetwork)) { + return false; + } + // check size + if ((decodedNetworkSize + PublicKey::ed25519Size + checksumSize) != decoded.size()) { + return false; + } + // compare network + if (decodedNetwork != network) { + return false; + } + auto checksum = computeChecksum(Data(decoded.begin(), decoded.end() - checksumSize)); + // compare checksum + if (!std::equal(decoded.end() - checksumSize, decoded.end(), checksum.begin())) { + return false; + } + return true; +} + +template +Data SS58Address::computeChecksum(const T& data) { + auto prefix = Data(SS58Prefix.begin(), SS58Prefix.end()); + append(prefix, Data(data.begin(), data.end())); + auto hash = Hash::blake2b(prefix, 64); + auto checksum = Data(checksumSize); + std::copy(hash.begin(), hash.begin() + checksumSize, checksum.data()); + return checksum; +} + +/// Initializes an address with a string representation. +SS58Address::SS58Address(const std::string& string, uint32_t network) { + if (!isValid(string, network)) { + throw std::invalid_argument("Invalid address string"); + } + const auto decoded = Base58::bitcoin.decode(string); + bytes.resize(decoded.size() - checksumSize); + std::copy(decoded.begin(), decoded.end() - checksumSize, bytes.begin()); +} + +/// Initializes an address with a public key and network. +SS58Address::SS58Address(const PublicKey& publicKey, uint32_t network) { + if (publicKey.type != TWPublicKeyTypeED25519) { + throw std::invalid_argument("SS58Address expects an ed25519 public key."); + } + if (!encodeNetwork(network, bytes)) { + throw std::invalid_argument(std::string("network out of range ") + std::to_string(network)); + } + TW::append(bytes, publicKey.bytes); +} + +/// Returns a string representation of the address. +std::string SS58Address::string() const { + auto result = Data(bytes.begin(), bytes.end()); + auto checksum = computeChecksum(bytes); + append(result, checksum); + return Base58::bitcoin.encode(result); +} + +/// Returns public key bytes +Data SS58Address::keyBytes() const { + byte networkSize; + uint32_t networkTemp; + decodeNetwork(bytes, networkSize, networkTemp); + return Data(bytes.begin() + networkSize, bytes.end()); +} + +// Return true and the network size (1 or 2) and network if input is valid +bool SS58Address::decodeNetwork(const Data& data, byte& networkSize, uint32_t& network) { + networkSize = 0; + network = 0; + if (data.size() >= 1 && data[0] < networkSimpleLimit) { // 0 -- 63 + networkSize = 1; + network = (uint32_t)(data[0]); + return true; + } + // src https://github.com/paritytech/substrate/blob/master/primitives/core/src/crypto.rs + if (data.size() >= 2 && data[0] >= networkSimpleLimit && data[0] < networkFullLimit) { // 64 -- 127 + networkSize = 2; + byte lower = (byte)((data[0] & 0b00111111) << 2) | (byte)((data[1] & 0b11000000) >> 6); + byte upper = data[1] & 0b00111111; + network = ((uint32_t)upper << 8) + lower; + return (network >= networkSimpleLimit); + } + return false; +} + +bool SS58Address::encodeNetwork(uint32_t network, Data& data) { + if (network < networkSimpleLimit) { // 0 -- 63 + // Simple account/address/network + data = {(byte)network}; + return true; + } + if (network < 0x4000) { // 64 -- 16383 + // Full address/address/network identifier. + byte first = networkSimpleLimit + (byte)((network & 0b0000000011111100) >> 2); + byte second = (byte)((network & 0b0011111100000000) >> 8) | (byte)((byte)(network & 0b0000000000000011) << 6); + data = {first, second}; + return true; + } + // not supported + return false; +} diff --git a/src/Polkadot/SS58Address.h b/src/Polkadot/SS58Address.h new file mode 100644 index 00000000000..20d4e292f70 --- /dev/null +++ b/src/Polkadot/SS58Address.h @@ -0,0 +1,64 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Base58.h" +#include "Data.h" +#include "PublicKey.h" + +#include + +inline const std::string SS58Prefix{"SS58PRE"}; + +namespace TW { + +/// Base-58-encodeed Substrate address. +class SS58Address { + public: + static const size_t checksumSize = 2; + + // networks 0 -- 63 are encoded in one byte (00aaaaaa) + static const byte networkSimpleLimit = 0x40; + // networks 64 -- 16383 are encoded in 2 bytes: network 00cccccc_aaaaaabb is encoded as 01aaaaaa, bbcccccc (first byte between 64 and 127) + // see: https://github.com/paritytech/substrate/blob/master/primitives/core/src/crypto.rs + // https://docs.substrate.io/v3/advanced/ss58/#address-type + static const byte networkFullLimit = 0x80; + + /// Address data consisting of one or more network byte(s) followed by the public key. + Data bytes; + + /// Determines whether a string makes a valid address + static bool isValid(const std::string& string, uint32_t network); + + template + static Data computeChecksum(const T& data); + + SS58Address() = default; + + /// Initializes an address with a string representation. + SS58Address(const std::string& string, uint32_t network); + + /// Initializes an address with a public key and network. + SS58Address(const PublicKey& publicKey, uint32_t network); + + /// Returns a string representation of the address. + std::string string() const; + + /// Returns public key bytes + Data keyBytes() const; + + // Return true and the network size (1 or 2) and network if input is valid + static bool decodeNetwork(const Data& data, byte& networkSize, uint32_t& network); + + static bool encodeNetwork(uint32_t network, Data& data); +}; + +inline bool operator==(const SS58Address& lhs, const SS58Address& rhs) { + return lhs.bytes == rhs.bytes; +} + +} // namespace TW diff --git a/src/Polkadot/ScaleCodec.h b/src/Polkadot/ScaleCodec.h index 6a244383440..e0d1c3ca20d 100644 --- a/src/Polkadot/ScaleCodec.h +++ b/src/Polkadot/ScaleCodec.h @@ -7,9 +7,9 @@ #pragma once #include "../BinaryCoding.h" -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" -#include "../SS58Address.h" +#include "SS58Address.h" #include #include #include diff --git a/src/Polkadot/Signer.cpp b/src/Polkadot/Signer.cpp index c7f708adcc7..b5f083a522b 100644 --- a/src/Polkadot/Signer.cpp +++ b/src/Polkadot/Signer.cpp @@ -9,8 +9,7 @@ #include "../Hash.h" #include "../PrivateKey.h" -using namespace TW; -using namespace TW::Polkadot; +namespace TW::Polkadot { static constexpr size_t hashTreshold = 256; @@ -30,3 +29,5 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { protoOutput.set_encoded(encoded.data(), encoded.size()); return protoOutput; } + +} // namespace TW::Polkadot diff --git a/src/Polkadot/Signer.h b/src/Polkadot/Signer.h index 4b790bd8b42..d893d64a010 100644 --- a/src/Polkadot/Signer.h +++ b/src/Polkadot/Signer.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" #include "../proto/Polkadot.pb.h" diff --git a/src/PrivateKey.cpp b/src/PrivateKey.cpp index 4c98f968263..898953100ed 100644 --- a/src/PrivateKey.cpp +++ b/src/PrivateKey.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -15,22 +15,22 @@ #include #include #include -#include #include #include +#include #include using namespace TW; bool PrivateKey::isValid(const Data& data) { - // Check length. Extended key needs 3*32 bytes. - if (data.size() != size && data.size() != extendedSize) { + // Check length + if (data.size() != _size && data.size() != cardanoKeySize) { return false; } // Check for zero address - for (size_t i = 0; i < size; ++i) { + for (size_t i = 0; i < _size; ++i) { if (data[i] != 0) { return true; } @@ -39,17 +39,15 @@ bool PrivateKey::isValid(const Data& data) { return false; } -bool PrivateKey::isValid(const Data& data, TWCurve curve) -{ +bool PrivateKey::isValid(const Data& data, TWCurve curve) { // check size bool valid = isValid(data); if (!valid) { return false; } - const ecdsa_curve *ec_curve = nullptr; - switch (curve) - { + const ecdsa_curve* ec_curve = nullptr; + switch (curve) { case TWCurveSECP256k1: ec_curve = &secp256k1; break; @@ -58,7 +56,7 @@ bool PrivateKey::isValid(const Data& data, TWCurve curve) break; case TWCurveED25519: case TWCurveED25519Blake2bNano: - case TWCurveED25519Extended: + case TWCurveED25519ExtendedCardano: case TWCurveCurve25519: case TWCurveNone: default: @@ -77,29 +75,35 @@ bool PrivateKey::isValid(const Data& data, TWCurve curve) return true; } +TWPrivateKeyType PrivateKey::getType(TWCurve curve) noexcept { + switch (curve) { + case TWCurve::TWCurveED25519ExtendedCardano: + return TWPrivateKeyTypeCardano; + default: + return TWPrivateKeyTypeDefault; + } +} + PrivateKey::PrivateKey(const Data& data) { if (!isValid(data)) { throw std::invalid_argument("Invalid private key data"); } - if (data.size() == extendedSize) { - // special extended case - *this = PrivateKey( - TW::data(data.data(), 32), - TW::data(data.data() + 32, 32), - TW::data(data.data() + 64, 32)); - } else { - // default case - bytes = data; - } + bytes = data; } -PrivateKey::PrivateKey(const Data& data, const Data& ext, const Data& chainCode) { - if (!isValid(data) || !isValid(ext) || !isValid(chainCode)) { +PrivateKey::PrivateKey( + const Data& key1, const Data& extension1, const Data& chainCode1, + const Data& key2, const Data& extension2, const Data& chainCode2) { + if (key1.size() != _size || extension1.size() != _size || chainCode1.size() != _size || + key2.size() != _size || extension2.size() != _size || chainCode2.size() != _size) { throw std::invalid_argument("Invalid private key or extended key data"); } - bytes = data; - extensionBytes = ext; - chainCodeBytes = chainCode; + bytes = key1; + append(bytes, extension1); + append(bytes, chainCode1); + append(bytes, key2); + append(bytes, extension2); + append(bytes, chainCode2); } PublicKey PrivateKey::getPublicKey(TWPublicKeyType type) const { @@ -107,38 +111,47 @@ PublicKey PrivateKey::getPublicKey(TWPublicKeyType type) const { switch (type) { case TWPublicKeyTypeSECP256k1: result.resize(PublicKey::secp256k1Size); - ecdsa_get_public_key33(&secp256k1, bytes.data(), result.data()); + ecdsa_get_public_key33(&secp256k1, key().data(), result.data()); break; case TWPublicKeyTypeSECP256k1Extended: result.resize(PublicKey::secp256k1ExtendedSize); - ecdsa_get_public_key65(&secp256k1, bytes.data(), result.data()); + ecdsa_get_public_key65(&secp256k1, key().data(), result.data()); break; case TWPublicKeyTypeNIST256p1: result.resize(PublicKey::secp256k1Size); - ecdsa_get_public_key33(&nist256p1, bytes.data(), result.data()); + ecdsa_get_public_key33(&nist256p1, key().data(), result.data()); break; case TWPublicKeyTypeNIST256p1Extended: result.resize(PublicKey::secp256k1ExtendedSize); - ecdsa_get_public_key65(&nist256p1, bytes.data(), result.data()); + ecdsa_get_public_key65(&nist256p1, key().data(), result.data()); break; case TWPublicKeyTypeED25519: result.resize(PublicKey::ed25519Size); - ed25519_publickey(bytes.data(), result.data()); + ed25519_publickey(key().data(), result.data()); break; case TWPublicKeyTypeED25519Blake2b: result.resize(PublicKey::ed25519Size); - ed25519_publickey_blake2b(bytes.data(), result.data()); + ed25519_publickey_blake2b(key().data(), result.data()); break; - case TWPublicKeyTypeED25519Extended: - // must be extended key - if (bytes.size() + extensionBytes.size() + chainCodeBytes.size() != extendedSize) { + case TWPublicKeyTypeED25519Cardano: { + // must be double extended key + if (bytes.size() != cardanoKeySize) { throw std::invalid_argument("Invalid extended key"); } - result.resize(PublicKey::ed25519ExtendedSize); - ed25519_publickey_ext(bytes.data(), extensionBytes.data(), result.data()); - // append chainCode to the end of the public key - std::copy(chainCodeBytes.begin(), chainCodeBytes.end(), result.begin() + 32); - break; + Data pubKey(PublicKey::ed25519Size); + + // first key + ed25519_publickey_ext(key().data(), pubKey.data()); + append(result, pubKey); + // copy chainCode + append(result, chainCode()); + + // second key + ed25519_publickey_ext(secondKey().data(), pubKey.data()); + append(result, pubKey); + append(result, secondChainCode()); + } break; + case TWPublicKeyTypeCURVE25519: result.resize(PublicKey::ed25519Size); PublicKey ed25519PublicKey = getPublicKey(TWPublicKeyTypeED25519); @@ -154,7 +167,7 @@ Data PrivateKey::getSharedKey(const PublicKey& pubKey, TWCurve curve) const { } Data result(PublicKey::secp256k1ExtendedSize); - bool success = ecdh_multiply(&secp256k1, bytes.data(), + bool success = ecdh_multiply(&secp256k1, key().data(), pubKey.bytes.data(), result.data()) == 0; if (success) { @@ -166,7 +179,7 @@ Data PrivateKey::getSharedKey(const PublicKey& pubKey, TWCurve curve) const { return {}; } -int ecdsa_sign_digest_checked(const ecdsa_curve *curve, const uint8_t *priv_key, const uint8_t *digest, size_t digest_size, uint8_t *sig, uint8_t *pby, int (*is_canonical)(uint8_t by, uint8_t sig[64])) { +int ecdsa_sign_digest_checked(const ecdsa_curve* curve, const uint8_t* priv_key, const uint8_t* digest, size_t digest_size, uint8_t* sig, uint8_t* pby, int (*is_canonical)(uint8_t by, uint8_t sig[64])) { if (digest_size < 32) { return -1; } @@ -180,33 +193,27 @@ Data PrivateKey::sign(const Data& digest, TWCurve curve) const { switch (curve) { case TWCurveSECP256k1: { result.resize(65); - success = ecdsa_sign_digest_checked(&secp256k1, bytes.data(), digest.data(), digest.size(), result.data(), - result.data() + 64, nullptr) == 0; + success = ecdsa_sign_digest_checked(&secp256k1, key().data(), digest.data(), digest.size(), result.data(), result.data() + 64, nullptr) == 0; } break; case TWCurveED25519: { result.resize(64); - const auto publicKey = getPublicKey(TWPublicKeyTypeED25519); - ed25519_sign(digest.data(), digest.size(), bytes.data(), publicKey.bytes.data(), result.data()); + ed25519_sign(digest.data(), digest.size(), key().data(), result.data()); success = true; } break; case TWCurveED25519Blake2bNano: { result.resize(64); - const auto publicKey = getPublicKey(TWPublicKeyTypeED25519Blake2b); - ed25519_sign_blake2b(digest.data(), digest.size(), bytes.data(), - publicKey.bytes.data(), result.data()); + ed25519_sign_blake2b(digest.data(), digest.size(), key().data(), result.data()); success = true; } break; - case TWCurveED25519Extended: { + case TWCurveED25519ExtendedCardano: { result.resize(64); - const auto publicKey = getPublicKey(TWPublicKeyTypeED25519Extended); - ed25519_sign_ext(digest.data(), digest.size(), bytes.data(), extensionBytes.data(), publicKey.bytes.data(), result.data()); + ed25519_sign_ext(digest.data(), digest.size(), key().data(), extension().data(), result.data()); success = true; } break; case TWCurveCurve25519: { result.resize(64); const auto publicKey = getPublicKey(TWPublicKeyTypeED25519); - ed25519_sign(digest.data(), digest.size(), bytes.data(), publicKey.bytes.data(), - result.data()); + ed25519_sign(digest.data(), digest.size(), key().data(), result.data()); const auto sign_bit = publicKey.bytes[31] & 0x80; result[63] = result[63] & 127; result[63] |= sign_bit; @@ -214,11 +221,10 @@ Data PrivateKey::sign(const Data& digest, TWCurve curve) const { } break; case TWCurveNIST256p1: { result.resize(65); - success = ecdsa_sign_digest_checked(&nist256p1, bytes.data(), digest.data(), digest.size(), result.data(), - result.data() + 64, nullptr) == 0; + success = ecdsa_sign_digest_checked(&nist256p1, key().data(), digest.data(), digest.size(), result.data(), result.data() + 64, nullptr) == 0; } break; case TWCurveNone: - default: + default: break; } @@ -228,24 +234,22 @@ Data PrivateKey::sign(const Data& digest, TWCurve curve) const { return result; } -Data PrivateKey::sign(const Data& digest, TWCurve curve, int(*canonicalChecker)(uint8_t by, uint8_t sig[64])) const { +Data PrivateKey::sign(const Data& digest, TWCurve curve, int (*canonicalChecker)(uint8_t by, uint8_t sig[64])) const { Data result; bool success = false; switch (curve) { case TWCurveSECP256k1: { result.resize(65); - success = ecdsa_sign_digest_checked(&secp256k1, bytes.data(), digest.data(), digest.size(), result.data() + 1, - result.data(), canonicalChecker) == 0; + success = ecdsa_sign_digest_checked(&secp256k1, key().data(), digest.data(), digest.size(), result.data() + 1, result.data(), canonicalChecker) == 0; } break; - case TWCurveED25519: // not supported - case TWCurveED25519Blake2bNano: // not supported - case TWCurveED25519Extended: // not supported - case TWCurveCurve25519: // not supported + case TWCurveED25519: // not supported + case TWCurveED25519Blake2bNano: // not supported + case TWCurveED25519ExtendedCardano: // not supported + case TWCurveCurve25519: // not supported break; case TWCurveNIST256p1: { result.resize(65); - success = ecdsa_sign_digest_checked(&nist256p1, bytes.data(), digest.data(), digest.size(), result.data() + 1, - result.data(), canonicalChecker) == 0; + success = ecdsa_sign_digest_checked(&nist256p1, key().data(), digest.data(), digest.size(), result.data() + 1, result.data(), canonicalChecker) == 0; } break; case TWCurveNone: default: @@ -261,10 +265,10 @@ Data PrivateKey::sign(const Data& digest, TWCurve curve, int(*canonicalChecker)( return result; } -Data PrivateKey::signAsDER(const Data& digest, TWCurve curve) const { +Data PrivateKey::signAsDER(const Data& digest) const { Data sig(64); bool success = - ecdsa_sign_digest(&secp256k1, bytes.data(), digest.data(), sig.data(), nullptr, nullptr) == 0; + ecdsa_sign_digest(&secp256k1, key().data(), digest.data(), sig.data(), nullptr, nullptr) == 0; if (!success) { return {}; } @@ -277,24 +281,9 @@ Data PrivateKey::signAsDER(const Data& digest, TWCurve curve) const { return result; } -Data PrivateKey::signSchnorr(const Data& message, TWCurve curve) const { - bool success = false; +Data PrivateKey::signZilliqa(const Data& message) const { Data sig(64); - switch (curve) { - case TWCurveSECP256k1: { - success = zil_schnorr_sign(&secp256k1, bytes.data(), message.data(), static_cast(message.size()), sig.data()) == 0; - } break; - - case TWCurveNIST256p1: - case TWCurveED25519: - case TWCurveED25519Blake2bNano: - case TWCurveED25519Extended: - case TWCurveCurve25519: - case TWCurveNone: - default: - // not support - break; - } + bool success = zil_schnorr_sign(&secp256k1, key().data(), message.data(), static_cast(message.size()), sig.data()) == 0; if (!success) { return {}; @@ -304,6 +293,4 @@ Data PrivateKey::signSchnorr(const Data& message, TWCurve curve) const { void PrivateKey::cleanup() { std::fill(bytes.begin(), bytes.end(), 0); - std::fill(extensionBytes.begin(), extensionBytes.end(), 0); - std::fill(chainCodeBytes.begin(), chainCodeBytes.end(), 0); } diff --git a/src/PrivateKey.h b/src/PrivateKey.h index 08d01d76d9d..32b2877a0ff 100644 --- a/src/PrivateKey.h +++ b/src/PrivateKey.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,6 +9,7 @@ #include "Data.h" #include "PublicKey.h" +#include #include namespace TW { @@ -16,16 +17,22 @@ namespace TW { class PrivateKey { public: /// The number of bytes in a private key. - static const size_t size = 32; - /// The number of bytes in an extended private key. - static const size_t extendedSize = 3 * 32; + static const size_t _size = 32; + /// The number of bytes in a Cardano key (two extended ed25519 keys + chain code) + static const size_t cardanoKeySize = 2 * 3 * 32; - /// The private key bytes. + /// The private key bytes: + /// - common case: 'size' bytes + /// - double extended case: 'cardanoKeySize' bytes, key+extension+chainCode+second+secondExtension+secondChainCode Data bytes; - /// Optional extended part of the key (additional 32 bytes) - Data extensionBytes; - /// Optional chain code (additional 32 bytes) - Data chainCodeBytes; + + /// Optional members for extended keys and second extended keys + Data key() const { return subData(bytes, 0, 32); } + Data extension() const { return subData(bytes, 32, 32); } + Data chainCode() const { return subData(bytes, 2*32, 32); } + Data secondKey() const { return subData(bytes, 3*32, 32); } + Data secondExtension() const { return subData(bytes, 4*32, 32); } + Data secondChainCode() const { return subData(bytes, 5*32, 32); } /// Determines if a collection of bytes makes a valid private key. static bool isValid(const Data& data); @@ -33,14 +40,19 @@ class PrivateKey { /// Determines if a collection of bytes and curve make a valid private key. static bool isValid(const Data& data, TWCurve curve); - /// Initializes a private key with an array of bytes. Size must be exact (normally 32, or 96 for extended) + // obtain private key type used by the curve/coin + static TWPrivateKeyType getType(TWCurve curve) noexcept; + + /// Initializes a private key with an array of bytes. Size must be exact (normally 32, or 192 for extended) explicit PrivateKey(const Data& data); - /// Initializes a private key from a string of bytes (convenience method). + /// Initializes a private key from a string of bytes. explicit PrivateKey(const std::string& data) : PrivateKey(TW::data(data)) {} - /// Initializes an extended private key with key, extended key, and chain code. - explicit PrivateKey(const Data& data, const Data& ext, const Data& chainCode); + /// Initializes a Cardano style key + explicit PrivateKey( + const Data& bytes1, const Data& extension1, const Data& chainCode1, + const Data& bytes2, const Data& extension2, const Data& chainCode2); PrivateKey(const PrivateKey& other) = default; PrivateKey& operator=(const PrivateKey& other) = default; @@ -66,10 +78,10 @@ class PrivateKey { /// Signs a digest using the given ECDSA curve. The result is encoded with /// DER. - Data signAsDER(const Data& digest, TWCurve curve) const; + Data signAsDER(const Data& digest) const; - /// Signs a digest using given ECDSA curve, returns schnorr signature - Data signSchnorr(const Data& message, TWCurve curve) const; + /// Signs a digest using given ECDSA curve, returns Zilliqa schnorr signature + Data signZilliqa(const Data& message) const; /// Cleanup contents (fill with 0s), called before destruction void cleanup(); diff --git a/src/PublicKey.cpp b/src/PublicKey.cpp index 8c4cf532eef..0fcd53c3a3b 100644 --- a/src/PublicKey.cpp +++ b/src/PublicKey.cpp @@ -1,18 +1,20 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. #include "PublicKey.h" +#include "PrivateKey.h" #include "Data.h" #include #include +#include #include #include #include -#include +#include #include @@ -31,8 +33,8 @@ bool PublicKey::isValid(const Data& data, enum TWPublicKeyType type) { case TWPublicKeyTypeCURVE25519: case TWPublicKeyTypeED25519Blake2b: return size == ed25519Size; - case TWPublicKeyTypeED25519Extended: - return size == ed25519ExtendedSize; + case TWPublicKeyTypeED25519Cardano: + return size == cardanoKeySize; case TWPublicKeyTypeSECP256k1: case TWPublicKeyTypeNIST256p1: return size == secp256k1Size && (data[0] == 0x02 || data[0] == 0x03); @@ -46,8 +48,9 @@ bool PublicKey::isValid(const Data& data, enum TWPublicKeyType type) { /// Initializes a public key with a collection of bytes. /// -/// @throws std::invalid_argument if the data is not a valid public key. -PublicKey::PublicKey(const Data& data, enum TWPublicKeyType type) : type(type) { +/// \throws std::invalid_argument if the data is not a valid public key. +PublicKey::PublicKey(const Data& data, enum TWPublicKeyType type) + : type(type) { if (!isValid(data, type)) { throw std::invalid_argument("Invalid public key data"); } @@ -74,8 +77,8 @@ PublicKey::PublicKey(const Data& data, enum TWPublicKeyType type) : type(type) { assert(data.size() == ed25519Size); // ensured by isValid() above std::copy(std::begin(data), std::end(data), std::back_inserter(bytes)); break; - case TWPublicKeyTypeED25519Extended: - bytes.reserve(ed25519ExtendedSize); + case TWPublicKeyTypeED25519Cardano: + bytes.reserve(cardanoKeySize); std::copy(std::begin(data), std::end(data), std::back_inserter(bytes)); } } @@ -118,8 +121,10 @@ PublicKey PublicKey::extended() const { case TWPublicKeyTypeED25519: case TWPublicKeyTypeCURVE25519: case TWPublicKeyTypeED25519Blake2b: - case TWPublicKeyTypeED25519Extended: - return *this; + case TWPublicKeyTypeED25519Cardano: + return *this; + default: + return *this; } } @@ -135,10 +140,11 @@ bool PublicKey::verify(const Data& signature, const Data& message) const { return ed25519_sign_open(message.data(), message.size(), bytes.data(), signature.data()) == 0; case TWPublicKeyTypeED25519Blake2b: return ed25519_sign_open_blake2b(message.data(), message.size(), bytes.data(), signature.data()) == 0; - case TWPublicKeyTypeED25519Extended: - throw std::logic_error("Not yet implemented"); - //ed25519_sign_open(message.data(), message.size(), bytes.data(), signature.data()) == 0; - case TWPublicKeyTypeCURVE25519: + case TWPublicKeyTypeED25519Cardano: { + const auto key = subData(bytes, 0, ed25519Size); + return ed25519_sign_open(message.data(), message.size(), key.data(), signature.data()) == 0; + } + case TWPublicKeyTypeCURVE25519: { auto ed25519PublicKey = Data(); ed25519PublicKey.resize(PublicKey::ed25519Size); curve25519_pk_to_ed25519(ed25519PublicKey.data(), bytes.data()); @@ -150,12 +156,31 @@ bool PublicKey::verify(const Data& signature, const Data& message) const { auto verifyBuffer = Data(); append(verifyBuffer, signature); verifyBuffer[63] &= 127; - return ed25519_sign_open(message.data(), message.size(), ed25519PublicKey.data(), - verifyBuffer.data()) == 0; + return ed25519_sign_open(message.data(), message.size(), ed25519PublicKey.data(), verifyBuffer.data()) == 0; + } + default: + throw std::logic_error("Not yet implemented"); } } -bool PublicKey::verifySchnorr(const Data& signature, const Data& message) const { +bool PublicKey::verifyAsDER(const Data& signature, const Data& message) const { + switch (type) { + case TWPublicKeyTypeSECP256k1: + case TWPublicKeyTypeSECP256k1Extended: { + Data sig(64); + int ret = ecdsa_sig_from_der(signature.data(), signature.size(), sig.data()); + if (ret) { + return false; + } + return ecdsa_verify_digest(&secp256k1, bytes.data(), sig.data(), message.data()) == 0; + } + + default: + return false; + } +} + +bool PublicKey::verifyZilliqa(const Data& signature, const Data& message) const { switch (type) { case TWPublicKeyTypeSECP256k1: case TWPublicKeyTypeSECP256k1Extended: @@ -164,7 +189,7 @@ bool PublicKey::verifySchnorr(const Data& signature, const Data& message) const case TWPublicKeyTypeNIST256p1Extended: case TWPublicKeyTypeED25519: case TWPublicKeyTypeED25519Blake2b: - case TWPublicKeyTypeED25519Extended: + case TWPublicKeyTypeED25519Cardano: case TWPublicKeyTypeCURVE25519: default: return false; @@ -173,7 +198,7 @@ bool PublicKey::verifySchnorr(const Data& signature, const Data& message) const Data PublicKey::hash(const Data& prefix, Hash::Hasher hasher, bool skipTypeByte) const { const auto offset = std::size_t(skipTypeByte ? 1 : 0); - const auto hash = hasher(bytes.data() + offset, bytes.size() - offset); + const auto hash = Hash::hash(hasher, bytes.data() + offset, bytes.size() - offset); auto result = Data(); result.reserve(prefix.size() + hash.size()); @@ -182,21 +207,35 @@ Data PublicKey::hash(const Data& prefix, Hash::Hasher hasher, bool skipTypeByte) return result; } -PublicKey PublicKey::recover(const Data& signature, const Data& message) { - if (signature.size() < 65) { +PublicKey PublicKey::recoverRaw(const Data& signatureRS, byte recId, const Data& messageDigest) { + if (signatureRS.size() < 2 * PrivateKey::_size) { throw std::invalid_argument("signature too short"); } - auto v = signature[64]; - if (v >= 27) { - v -= 27; + if (recId >= 4) { + throw std::invalid_argument("Invalid recId (>=4)"); } - TW::Data result(65); - if (ecdsa_recover_pub_from_sig(&secp256k1, result.data(), signature.data(), message.data(), v) != 0) { - throw std::invalid_argument("recover failed"); + if (messageDigest.size() < PrivateKey::_size) { + throw std::invalid_argument("digest too short"); + } + TW::Data result(secp256k1SignatureSize); + if (auto ret = ecdsa_recover_pub_from_sig(&secp256k1, result.data(), signatureRS.data(), messageDigest.data(), recId); ret != 0) { + throw std::invalid_argument("recover failed " + std::to_string(ret)); } return PublicKey(result, TWPublicKeyTypeSECP256k1Extended); } +PublicKey PublicKey::recover(const Data& signature, const Data& messageDigest) { + if (signature.size() < secp256k1SignatureSize) { + throw std::invalid_argument("signature too short"); + } + auto v = signature[secp256k1SignatureSize - 1]; + // handle EIP155 Eth encoding of V, of the form 27+v, or 35+chainID*2+v + if (v >= PublicKey::SignatureVOffset) { + v = !(v & 0x01); + } + return recoverRaw(signature, v, messageDigest); +} + bool PublicKey::isValidED25519() const { if (type != TWPublicKeyTypeED25519) { return false; diff --git a/src/PublicKey.h b/src/PublicKey.h index 27daa26a267..111f0fdf306 100644 --- a/src/PublicKey.h +++ b/src/PublicKey.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -21,14 +21,21 @@ class PublicKey { /// The number of bytes in a secp256k1 and nist256p1 public key. static const size_t secp256k1Size = 33; - /// The number of bytes in a ed25519 public key. + /// The number of bytes in an ed25519 public key. static const size_t ed25519Size = 32; - static const size_t ed25519ExtendedSize = 64; + /// The number of bytes in a Cardano public key (two ed25519 public key + chain code). + static const size_t cardanoKeySize = 2 * 2 * 32; /// The number of bytes in a secp256k1 and nist256p1 extended public key. static const size_t secp256k1ExtendedSize = 65; + /// The number of bytes in a secp256k1 signature. + static const size_t secp256k1SignatureSize = 65; + + /// Magic number used in V compnent encoding + static const byte SignatureVOffset = 27; + /// The public key bytes. Data bytes; @@ -44,7 +51,7 @@ class PublicKey { /// Initializes a public key with a collection of bytes. /// - /// @throws std::invalid_argument if the data is not a valid public key. + /// \throws std::invalid_argument if the data is not a valid public key. explicit PublicKey(const Data& data, enum TWPublicKeyType type); /// Determines if this is a compressed public key. @@ -61,17 +68,31 @@ class PublicKey { /// Verifies a signature for the provided message. bool verify(const Data& signature, const Data& message) const; - /// Verifies a schnorr signature for the provided message. - bool verifySchnorr(const Data& signature, const Data& message) const; + /// Verifies a signature in DER format. + bool verifyAsDER(const Data& signature, const Data& message) const; + + /// Verifies a Zilliqa schnorr signature for the provided message. + bool verifyZilliqa(const Data& signature, const Data& message) const; /// Computes the public key hash. /// /// The public key hash is computed by applying the hasher to the public key /// bytes and then prepending the prefix. - Data hash(const Data& prefix, Hash::Hasher hasher = Hash::sha256ripemd, bool skipTypeByte = false) const; + Data hash(const Data& prefix, Hash::Hasher hasher = Hash::HasherSha256ripemd, bool skipTypeByte = false) const; + + /// Recover public key (SECP256k1Extended) from signature R, S, V values + /// signatureRS: 2x32 bytes with the R and S values + /// recId: the recovery ID, a.k.a. V value, 0 <= v < 4 + /// messageDigest: message digest (hash) to be signed + /// Throws on invalid data. + static PublicKey recoverRaw(const Data& signatureRS, byte recId, const Data& messageDigest); /// Recover public key from signature (SECP256k1Extended) - static PublicKey recover(const Data& signature, const Data& message); + /// signature: 65-byte signature (R, S, and V). V can have higher value bits, as used by Ethereum (for values over 27 the negated last bit is taken). + /// messageDigest: message digest (hash) to be signed + /// Throws on invalid data. + /// Naming is kept for backwards compatibility. + static PublicKey recover(const Data& signature, const Data& messageDigest); /// Check if this key makes a valid ED25519 key (it is on the curve) bool isValidED25519() const; diff --git a/src/Result.h b/src/Result.h index c7faede9896..1ce611525a1 100644 --- a/src/Result.h +++ b/src/Result.h @@ -42,7 +42,7 @@ struct Result { using Storage = typename std::aligned_storage::type; /// Wether the operation succeeded. - bool success_; + bool success_{false}; Storage storage_; public: @@ -73,6 +73,7 @@ struct Result { } else { new (&storage_) E(other.get()); } + return *this; } Result(Result&& other) { @@ -96,6 +97,7 @@ struct Result { } else { new (&storage_) E(std::move(other.get())); } + return *this; } ~Result() { @@ -150,7 +152,7 @@ struct Result { public: /// Initializes a success result with a payload. - Result(Types::Success payload) : success_(true), error_() {} + Result([[maybe_unused]] Types::Success payload) : success_(true), error_() {} /// Initializes a failure result. Result(Types::Failure error) : success_(false), error_(error.val) {} diff --git a/src/Ronin/Address.cpp b/src/Ronin/Address.cpp new file mode 100644 index 00000000000..74555fb553a --- /dev/null +++ b/src/Ronin/Address.cpp @@ -0,0 +1,55 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Address.h" +#include "Ethereum/Address.h" +#include "Ethereum/AddressChecksum.h" +#include "../Hash.h" +#include "../HexCoding.h" + +const std::string prefix = "ronin:"; + +namespace TW::Ronin { + +bool Address::isValid(const std::string& string) { + // check prefix + if (string.compare(0, prefix.length(), prefix) == 0) { + const auto suffix = string.substr(prefix.length()); + const auto data = parse_hex(suffix); + return Ethereum::Address::isValid(data); + } + // accept Ethereum format as well + if (Ethereum::Address::isValid(string)) { + return true; + } + return false; +} + +Address::Address(const std::string& string) { + // check prefix + if (string.compare(0, prefix.length(), prefix) == 0) { + const auto suffix = string.substr(prefix.length()); + const auto data = parse_hex(suffix); + std::copy(data.begin(), data.end(), bytes.begin()); + } else if (Ethereum::Address::isValid(string)) { + // accept Ethereum format as well + Ethereum::Address ethereumAddress(string); + bytes = ethereumAddress.bytes; + } else { + throw std::invalid_argument("Invalid address data"); + } +} + +// Normalized: with ronin prefix, checksummed hex address, no 0x prefix +std::string Address::string() const { + std::string address = Ethereum::checksumed(*this); + if (address.size() >= 2 && address.substr(0, 2) == "0x") { + address = address.substr(2); + } // skip 0x + return prefix + address; +} + +} // namespace TW::Ronin diff --git a/src/Ronin/Address.h b/src/Ronin/Address.h new file mode 100644 index 00000000000..ac28eb1372f --- /dev/null +++ b/src/Ronin/Address.h @@ -0,0 +1,31 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "../PublicKey.h" +#include "../Ethereum/Address.h" +#include +#include + +namespace TW::Ronin { + +class Address: public Ethereum::Address { + public: + /// Determines whether a string makes a valid address. + static bool isValid(const std::string& string); + + /// Initializes an address with a string representation. + explicit Address(const std::string& string); + + /// Initializes an address with a public key. + explicit Address(const PublicKey& publicKey): Ethereum::Address(publicKey) {} + + /// Returns a string representation of the address. + std::string string() const; +}; + +} // namespace TW::Ronin diff --git a/src/Ronin/Entry.cpp b/src/Ronin/Entry.cpp new file mode 100644 index 00000000000..d69ac356390 --- /dev/null +++ b/src/Ronin/Entry.cpp @@ -0,0 +1,42 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Entry.h" + +#include "Address.h" +#include "../Ethereum/Signer.h" + +using namespace TW; +using namespace std; + +namespace TW::Ronin { + +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { + return Address::isValid(address); +} + +string Entry::normalizeAddress([[maybe_unused]] TWCoinType coin, const string& address) const { + return Address(address).string(); +} + +string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { + return Address(publicKey).string(); +} + +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + const auto addr = Address(address); + return {addr.bytes.begin(), addr.bytes.end()}; +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + signTemplate(dataIn, dataOut); +} + +string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { + return Ethereum::Signer::signJSON(json, key); +} + +} // namespace TW::Ronin diff --git a/src/Ronin/Entry.h b/src/Ronin/Entry.h new file mode 100644 index 00000000000..9bb7f96aa40 --- /dev/null +++ b/src/Ronin/Entry.h @@ -0,0 +1,25 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "../CoinEntry.h" + +namespace TW::Ronin { + +/// Entry point for Ronin (EVM side chain) +class Entry final : public CoinEntry { +public: + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string normalizeAddress(TWCoinType coin, const std::string& address) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool supportsJSONSigning() const { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; +}; + +} // namespace TW::Ronin diff --git a/src/SS58Address.h b/src/SS58Address.h deleted file mode 100644 index b9ccfa627b6..00000000000 --- a/src/SS58Address.h +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "Base58.h" -#include "Data.h" -#include "PublicKey.h" - -#include -#include -#include - -const std::string SS58Prefix = "SS58PRE"; - -namespace TW { - -class SS58Address { - public: - /// Number of bytes in an address. - static const size_t size = 33; - - static const size_t checksumSize = 2; - - /// Address data consisting of a network byte followed by the public key. - std::array bytes; - - /// Determines whether a string makes a valid address - static bool isValid(const std::string& string, byte network) { - const auto decoded = Base58::bitcoin.decode(string); - if (decoded.size() != SS58Address::size + checksumSize) { - return false; - } - // check network - if (decoded[0] != network) { - return false; - } - auto checksum = computeChecksum(Data(decoded.begin(), decoded.end() - checksumSize)); - // compare checksum - if (!std::equal(decoded.end() - checksumSize, decoded.end(), checksum.begin())) { - return false; - } - return true; - } - - template - static Data computeChecksum(const T& data) { - auto prefix = Data(SS58Prefix.begin(), SS58Prefix.end()); - append(prefix, Data(data.begin(), data.end())); - auto hash = Hash::blake2b(prefix, 64); - auto checksum = Data(checksumSize); - std::copy(hash.begin(), hash.begin() + checksumSize, checksum.data()); - return checksum; - } - - SS58Address() = default; - - /// Initializes an address with a string representation. - SS58Address(const std::string& string, byte network) { - if (!isValid(string, network)) { - throw std::invalid_argument("Invalid address string"); - } - const auto decoded = Base58::bitcoin.decode(string); - std::copy(decoded.begin(), decoded.end() - checksumSize, bytes.begin()); - } - - /// Initializes an address with a public key and network. - SS58Address(const PublicKey& publicKey, byte network) { - if (publicKey.type != TWPublicKeyTypeED25519) { - throw std::invalid_argument("SS58Address expects an ed25519 public key."); - } - bytes[0] = network; - std::copy(publicKey.bytes.begin(), publicKey.bytes.end(), bytes.begin() + 1); - } - - /// Returns a string representation of the address. - std::string string() const { - auto result = Data(bytes.begin(), bytes.end()); - auto checksum = computeChecksum(bytes); - append(result, checksum); - return Base58::bitcoin.encode(result); - } - - /// Returns public key bytes - Data keyBytes() const { - return Data(bytes.begin() + 1, bytes.end()); - } -}; - -inline bool operator==(const SS58Address& lhs, const SS58Address& rhs) { - return lhs.bytes == rhs.bytes; -} - -} // namespace TW diff --git a/src/Solana/Address.cpp b/src/Solana/Address.cpp index c8a633eb853..3e4f77cface 100644 --- a/src/Solana/Address.cpp +++ b/src/Solana/Address.cpp @@ -5,8 +5,8 @@ // file LICENSE at the root of the source code distribution tree. #include "Address.h" -#include "Transaction.h" #include "Program.h" +#include "Transaction.h" #include "../Base58.h" #include "../Base58Address.h" #include "../Hash.h" @@ -16,7 +16,8 @@ #include using namespace TW; -using namespace TW::Solana; + +namespace TW::Solana { bool Address::isValid(const std::string& string) { const auto data = Base58::bitcoin.decode(string); @@ -58,3 +59,5 @@ Data Address::vector() const { Address Address::defaultTokenAddress(const Address& tokenMintAddress) { return TokenProgram::defaultTokenAddress(*this, tokenMintAddress); } + +} // namespace TW::Solana diff --git a/src/Solana/Entry.cpp b/src/Solana/Entry.cpp index 41c06ca31e1..765c9c1927f 100644 --- a/src/Solana/Entry.cpp +++ b/src/Solana/Entry.cpp @@ -9,23 +9,31 @@ #include "Address.h" #include "Signer.h" -using namespace TW::Solana; +using namespace TW; using namespace std; +namespace TW::Solana { + // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + return Address(address).vector(); +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { +string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { return Signer::signJSON(json, key); } + +} // namespace TW::Solana diff --git a/src/Solana/Entry.h b/src/Solana/Entry.h index 18f27424c69..b6bc8b4f1d4 100644 --- a/src/Solana/Entry.h +++ b/src/Solana/Entry.h @@ -12,14 +12,14 @@ namespace TW::Solana { /// Entry point for implementation of Solana coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeSolana}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual bool supportsJSONSigning() const { return true; } - virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool supportsJSONSigning() const { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; }; } // namespace TW::Solana diff --git a/src/Solana/Program.cpp b/src/Solana/Program.cpp index ae33d08bd3d..bac0340caa8 100644 --- a/src/Solana/Program.cpp +++ b/src/Solana/Program.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -7,18 +7,13 @@ #include "Program.h" #include "Address.h" #include "Transaction.h" -#include "../Base58.h" -#include "../Hash.h" -#include +#include -#include - -using namespace TW; -using namespace TW::Solana; +namespace TW::Solana { Address StakeProgram::addressFromValidatorSeed(const Address& fromAddress, const Address& validatorAddress, - const Address& programId) { + const Address& programId) { Data extended = fromAddress.vector(); std::string seed = validatorAddress.string(); Data vecSeed(seed.begin(), seed.end()); @@ -52,8 +47,7 @@ Address TokenProgram::defaultTokenAddress(const Address& mainAddress, const Addr std::vector seeds = { TW::data(mainAddress.bytes.data(), mainAddress.bytes.size()), TW::data(programId.bytes.data(), programId.bytes.size()), - TW::data(tokenMintAddress.bytes.data(), tokenMintAddress.bytes.size()) - }; + TW::data(tokenMintAddress.bytes.data(), tokenMintAddress.bytes.size())}; return findProgramAddress(seeds, Address(ASSOCIATED_TOKEN_PROGRAM_ID_ADDRESS)); } @@ -61,23 +55,20 @@ Address TokenProgram::defaultTokenAddress(const Address& mainAddress, const Addr * Based on solana code, find_program_address() * https://github.com/solana-labs/solana/blob/master/sdk/program/src/pubkey.rs#L193 */ -Address TokenProgram::findProgramAddress(const std::vector& seeds, const Address& programId) { +Address TokenProgram::findProgramAddress(const std::vector& seeds, [[maybe_unused]] const Address& programId) { Address result(Data(32)); // cycle through seeds for the rare case when result is not valid - for (uint8_t seed = 255; seed >= 0; --seed) { - std::vector seedsCopy; - for (auto& s: seeds) { - seedsCopy.push_back(s); - } - // add extra seed - seedsCopy.push_back({seed}); - Address address = createProgramAddress(seedsCopy, Address(ASSOCIATED_TOKEN_PROGRAM_ID_ADDRESS)); + auto bumpSeed = Data{std::numeric_limits::max()}; + for (std::uint8_t seed = 0; seed <= std::numeric_limits::max(); ++seed) { + std::vector seedsCopy = seeds; + seedsCopy.emplace_back(bumpSeed); + Address address = createProgramAddress(seedsCopy, programId); PublicKey publicKey = PublicKey(TW::data(address.bytes.data(), address.bytes.size()), TWPublicKeyTypeED25519); if (!publicKey.isValidED25519()) { result = address; break; } - // try next seed + bumpSeed[0] -= 1; } return result; } @@ -89,7 +80,7 @@ Address TokenProgram::findProgramAddress(const std::vector& seeds, con Address TokenProgram::createProgramAddress(const std::vector& seeds, const Address& programId) { // concatenate seeds Data hashInput; - for (auto& seed: seeds) { + for (auto& seed : seeds) { append(hashInput, seed); } // append programId @@ -99,3 +90,6 @@ Address TokenProgram::createProgramAddress(const std::vector& seeds, c Data hash = TW::Hash::sha256(hashInput.data(), hashInput.size()); return Address(hash); } + +} // namespace TW::Solana + diff --git a/src/Solana/Signer.cpp b/src/Solana/Signer.cpp index f999e893001..f5839bc0e8f 100644 --- a/src/Solana/Signer.cpp +++ b/src/Solana/Signer.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -7,16 +7,12 @@ #include "Signer.h" #include "Address.h" #include "Program.h" -#include "../Base58.h" -#include #include -#include #include -using namespace TW; -using namespace TW::Solana; +namespace TW::Solana { void Signer::sign(const std::vector& privateKeys, Transaction& transaction) { for (auto privateKey : privateKeys) { @@ -28,149 +24,146 @@ void Signer::sign(const std::vector& privateKeys, Transaction& trans } } +// Helper to convert protobuf-string-collection references to Address vector +std::vector
convertReferences(const google::protobuf::RepeatedPtrField& references) { + std::vector
ret; + for (auto i = 0; i < references.size(); ++i) { + if (Address::isValid(references[i])) { + ret.emplace_back(references[i]); + } + } + return ret; +} + Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto blockhash = Solana::Hash(input.recent_blockhash()); auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); Message message; - std::string stakePubkey; std::vector signerKeys; switch (input.transaction_type_case()) { - case Proto::SigningInput::TransactionTypeCase::kTransferTransaction: - { - auto protoMessage = input.transfer_transaction(); - message = Message::createTransfer( - /* from */ Address(key.getPublicKey(TWPublicKeyTypeED25519)), - /* to */ Address(protoMessage.recipient()), - /* value */ protoMessage.value(), - /* recent_blockhash */ blockhash); - signerKeys.push_back(key); - } - break; - - case Proto::SigningInput::TransactionTypeCase::kDelegateStakeTransaction: - { - auto protoMessage = input.delegate_stake_transaction(); - auto userAddress = Address(key.getPublicKey(TWPublicKeyTypeED25519)); - auto validatorAddress = Address(protoMessage.validator_pubkey()); - auto stakeProgramId = Address(STAKE_PROGRAM_ID_ADDRESS); - std::optional
stakeAddress; - if (protoMessage.stake_account().size() == 0) { - // no stake address specified, generate a new unique - stakeAddress = StakeProgram::addressFromRecentBlockhash(userAddress, blockhash, stakeProgramId); - } else { - // stake address specified, use it - stakeAddress = Address(protoMessage.stake_account()); - } - message = Message::createStake( - /* signer */ userAddress, - /* stakeAddress */ stakeAddress.value(), - /* voteAddress */ validatorAddress, - /* value */ protoMessage.value(), - /* recent_blockhash */ blockhash); - signerKeys.push_back(key); - } - break; - - case Proto::SigningInput::TransactionTypeCase::kDeactivateStakeTransaction: - { - auto protoMessage = input.deactivate_stake_transaction(); - auto userAddress = Address(key.getPublicKey(TWPublicKeyTypeED25519)); - auto stakeAddress = Address(protoMessage.stake_account()); - message = Message::createStakeDeactivate( - /* signer */ userAddress, - /* stakeAddress */ stakeAddress, - /* recent_blockhash */ blockhash); - signerKeys.push_back(key); - } - break; - - case Proto::SigningInput::TransactionTypeCase::kDeactivateAllStakeTransaction: - { - auto protoMessage = input.deactivate_all_stake_transaction(); - auto userAddress = Address(key.getPublicKey(TWPublicKeyTypeED25519)); - std::vector
addresses; - for (auto i = 0; i < protoMessage.stake_accounts_size(); ++i) { - addresses.emplace_back(Address(protoMessage.stake_accounts(i))); - } - message = Message::createStakeDeactivateAll(userAddress, addresses, blockhash); - signerKeys.push_back(key); - } - break; - - case Proto::SigningInput::TransactionTypeCase::kWithdrawTransaction: - { - auto protoMessage = input.withdraw_transaction(); - auto userAddress = Address(key.getPublicKey(TWPublicKeyTypeED25519)); - auto stakeAddress = Address(protoMessage.stake_account()); - message = Message::createStakeWithdraw( - /* signer */ userAddress, - /* stakeAddress */ stakeAddress, - /* value */ protoMessage.value(), - /* recent_blockhash */ blockhash); - signerKeys.push_back(key); - } - break; - - case Proto::SigningInput::TransactionTypeCase::kWithdrawAllTransaction: - { - auto protoMessage = input.withdraw_all_transaction(); - auto userAddress = Address(key.getPublicKey(TWPublicKeyTypeED25519)); - std::vector> stakes; - for (auto i = 0; i < protoMessage.stake_accounts_size(); ++i) { - stakes.push_back(std::make_pair( - Address(protoMessage.stake_accounts(i).stake_account()), - protoMessage.stake_accounts(i).value() - )); - } - message = Message::createStakeWithdrawAll(userAddress, stakes, blockhash); - signerKeys.push_back(key); - } - break; - - case Proto::SigningInput::TransactionTypeCase::kCreateTokenAccountTransaction: - { - auto protoMessage = input.create_token_account_transaction(); - auto userAddress = Address(key.getPublicKey(TWPublicKeyTypeED25519)); - auto mainAddress = Address(protoMessage.main_address()); - auto tokenMintAddress = Address(protoMessage.token_mint_address()); - auto tokenAddress = Address(protoMessage.token_address()); - message = Message::createTokenCreateAccount(userAddress, TokenInstruction::CreateTokenAccount, mainAddress, tokenMintAddress, tokenAddress, blockhash); - signerKeys.push_back(key); - } - break; - - case Proto::SigningInput::TransactionTypeCase::kTokenTransferTransaction: - { - auto protoMessage = input.token_transfer_transaction(); - auto userAddress = Address(key.getPublicKey(TWPublicKeyTypeED25519)); - auto tokenMintAddress = Address(protoMessage.token_mint_address()); - auto senderTokenAddress = Address(protoMessage.sender_token_address()); - auto recipientTokenAddress = Address(protoMessage.recipient_token_address()); - auto amount = protoMessage.amount(); - auto decimals = static_cast(protoMessage.decimals()); - message = Message::createTokenTransfer(userAddress, TokenInstruction::TokenTransfer, tokenMintAddress, senderTokenAddress, recipientTokenAddress, amount, decimals, blockhash); - signerKeys.push_back(key); - } - break; - - case Proto::SigningInput::TransactionTypeCase::kCreateAndTransferTokenTransaction: - { - auto protoMessage = input.create_and_transfer_token_transaction(); - auto userAddress = Address(key.getPublicKey(TWPublicKeyTypeED25519)); - auto recipientMainAddress = Address(protoMessage.recipient_main_address()); - auto tokenMintAddress = Address(protoMessage.token_mint_address()); - auto recipientTokenAddress = Address(protoMessage.recipient_token_address()); - auto senderTokenAddress = Address(protoMessage.sender_token_address()); - auto amount = protoMessage.amount(); - auto decimals = static_cast(protoMessage.decimals()); - message = Message::createTokenCreateAndTransfer(userAddress, recipientMainAddress, tokenMintAddress, recipientTokenAddress, senderTokenAddress, amount, decimals, blockhash); - signerKeys.push_back(key); - } - break; - - default: - assert(input.transaction_type_case() != Proto::SigningInput::TransactionTypeCase::TRANSACTION_TYPE_NOT_SET); + case Proto::SigningInput::TransactionTypeCase::kTransferTransaction: { + auto protoMessage = input.transfer_transaction(); + message = Message::createTransfer( + /* from */ Address(key.getPublicKey(TWPublicKeyTypeED25519)), + /* to */ Address(protoMessage.recipient()), + /* value */ protoMessage.value(), + /* recent_blockhash */ blockhash, + /* memo */ protoMessage.memo(), + convertReferences(protoMessage.references())); + signerKeys.push_back(key); + } break; + + case Proto::SigningInput::TransactionTypeCase::kDelegateStakeTransaction: { + auto protoMessage = input.delegate_stake_transaction(); + auto userAddress = Address(key.getPublicKey(TWPublicKeyTypeED25519)); + auto validatorAddress = Address(protoMessage.validator_pubkey()); + auto stakeProgramId = Address(STAKE_PROGRAM_ID_ADDRESS); + std::optional
stakeAddress; + if (protoMessage.stake_account().size() == 0) { + // no stake address specified, generate a new unique + stakeAddress = StakeProgram::addressFromRecentBlockhash(userAddress, blockhash, stakeProgramId); + } else { + // stake address specified, use it + stakeAddress = Address(protoMessage.stake_account()); + } + message = Message::createStake( + /* signer */ userAddress, + /* stakeAddress */ stakeAddress.value(), + /* voteAddress */ validatorAddress, + /* value */ protoMessage.value(), + /* recent_blockhash */ blockhash); + signerKeys.push_back(key); + } break; + + case Proto::SigningInput::TransactionTypeCase::kDeactivateStakeTransaction: { + auto protoMessage = input.deactivate_stake_transaction(); + auto userAddress = Address(key.getPublicKey(TWPublicKeyTypeED25519)); + auto stakeAddress = Address(protoMessage.stake_account()); + message = Message::createStakeDeactivate( + /* signer */ userAddress, + /* stakeAddress */ stakeAddress, + /* recent_blockhash */ blockhash); + signerKeys.push_back(key); + } break; + + case Proto::SigningInput::TransactionTypeCase::kDeactivateAllStakeTransaction: { + auto protoMessage = input.deactivate_all_stake_transaction(); + auto userAddress = Address(key.getPublicKey(TWPublicKeyTypeED25519)); + std::vector
addresses; + for (auto i = 0; i < protoMessage.stake_accounts_size(); ++i) { + addresses.emplace_back(Address(protoMessage.stake_accounts(i))); + } + message = Message::createStakeDeactivateAll(userAddress, addresses, blockhash); + signerKeys.push_back(key); + } break; + + case Proto::SigningInput::TransactionTypeCase::kWithdrawTransaction: { + auto protoMessage = input.withdraw_transaction(); + auto userAddress = Address(key.getPublicKey(TWPublicKeyTypeED25519)); + auto stakeAddress = Address(protoMessage.stake_account()); + message = Message::createStakeWithdraw( + /* signer */ userAddress, + /* stakeAddress */ stakeAddress, + /* value */ protoMessage.value(), + /* recent_blockhash */ blockhash); + signerKeys.push_back(key); + } break; + + case Proto::SigningInput::TransactionTypeCase::kWithdrawAllTransaction: { + auto protoMessage = input.withdraw_all_transaction(); + auto userAddress = Address(key.getPublicKey(TWPublicKeyTypeED25519)); + std::vector> stakes; + for (auto i = 0; i < protoMessage.stake_accounts_size(); ++i) { + stakes.push_back(std::make_pair( + Address(protoMessage.stake_accounts(i).stake_account()), + protoMessage.stake_accounts(i).value())); + } + message = Message::createStakeWithdrawAll(userAddress, stakes, blockhash); + signerKeys.push_back(key); + } break; + + case Proto::SigningInput::TransactionTypeCase::kCreateTokenAccountTransaction: { + auto protoMessage = input.create_token_account_transaction(); + auto userAddress = Address(key.getPublicKey(TWPublicKeyTypeED25519)); + auto mainAddress = Address(protoMessage.main_address()); + auto tokenMintAddress = Address(protoMessage.token_mint_address()); + auto tokenAddress = Address(protoMessage.token_address()); + message = Message::createTokenCreateAccount(userAddress, mainAddress, tokenMintAddress, tokenAddress, blockhash); + signerKeys.push_back(key); + } break; + + case Proto::SigningInput::TransactionTypeCase::kTokenTransferTransaction: { + auto protoMessage = input.token_transfer_transaction(); + auto userAddress = Address(key.getPublicKey(TWPublicKeyTypeED25519)); + auto tokenMintAddress = Address(protoMessage.token_mint_address()); + auto senderTokenAddress = Address(protoMessage.sender_token_address()); + auto recipientTokenAddress = Address(protoMessage.recipient_token_address()); + auto amount = protoMessage.amount(); + auto decimals = static_cast(protoMessage.decimals()); + const auto memo = protoMessage.memo(); + message = Message::createTokenTransfer(userAddress, tokenMintAddress, senderTokenAddress, recipientTokenAddress, amount, decimals, blockhash, + memo, convertReferences(protoMessage.references())); + signerKeys.push_back(key); + } break; + + case Proto::SigningInput::TransactionTypeCase::kCreateAndTransferTokenTransaction: { + auto protoMessage = input.create_and_transfer_token_transaction(); + auto userAddress = Address(key.getPublicKey(TWPublicKeyTypeED25519)); + auto recipientMainAddress = Address(protoMessage.recipient_main_address()); + auto tokenMintAddress = Address(protoMessage.token_mint_address()); + auto recipientTokenAddress = Address(protoMessage.recipient_token_address()); + auto senderTokenAddress = Address(protoMessage.sender_token_address()); + auto amount = protoMessage.amount(); + auto decimals = static_cast(protoMessage.decimals()); + const auto memo = protoMessage.memo(); + message = Message::createTokenCreateAndTransfer(userAddress, recipientMainAddress, tokenMintAddress, recipientTokenAddress, senderTokenAddress, amount, decimals, blockhash, + memo, convertReferences(protoMessage.references())); + signerKeys.push_back(key); + } break; + + default: + assert(input.transaction_type_case() != Proto::SigningInput::TransactionTypeCase::TRANSACTION_TYPE_NOT_SET); } auto transaction = Transaction(message); @@ -214,3 +207,5 @@ std::string Signer::signJSON(const std::string& json, const Data& key) { input.set_private_key(key.data(), key.size()); return Signer::sign(input).encoded(); } + +} // namespace TW::Solana diff --git a/src/Solana/Signer.h b/src/Solana/Signer.h index 8b86befe832..46bed571ffb 100644 --- a/src/Solana/Signer.h +++ b/src/Solana/Signer.h @@ -7,7 +7,7 @@ #pragma once #include "Transaction.h" -#include "../Data.h" +#include "Data.h" #include "../Hash.h" #include "../PrivateKey.h" #include "../proto/Solana.pb.h" diff --git a/src/Solana/Transaction.cpp b/src/Solana/Transaction.cpp index dd5dc5c0c05..ac345134dc6 100644 --- a/src/Solana/Transaction.cpp +++ b/src/Solana/Transaction.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -8,14 +8,10 @@ #include "Hash.h" #include "Signer.h" -#include "../BinaryCoding.h" -#include "../PublicKey.h" #include -using namespace TW; -using namespace TW::Solana; -using namespace std; +namespace TW::Solana { uint8_t CompiledInstruction::findAccount(const Address& address) { auto it = std::find(addresses.begin(), addresses.end(), address); @@ -54,31 +50,30 @@ void Message::addAccountKeys(const Address& account) { } void Message::compileAccounts() { - for (auto& instr: instructions) { - for (auto& address: instr.accounts) { + for (auto& instr : instructions) { + for (auto& address : instr.accounts) { addAccount(address); } } // add programIds (read-only, at end) - for (auto& instr: instructions) { + for (auto& instr : instructions) { addAccount(AccountMeta{instr.programId, false, true}); } header = MessageHeader{ (uint8_t)signedAccounts.size(), 0, - (uint8_t)readOnlyAccounts.size() - }; + (uint8_t)readOnlyAccounts.size()}; // merge the three buckets accountKeys.clear(); - for(auto& a: signedAccounts) { + for (auto& a : signedAccounts) { addAccountKeys(a); } - for(auto& a: unsignedAccounts) { + for (auto& a : unsignedAccounts) { addAccountKeys(a); } - for(auto& a: readOnlyAccounts) { + for (auto& a : readOnlyAccounts) { addAccountKeys(a); } @@ -87,7 +82,7 @@ void Message::compileAccounts() { void Message::compileInstructions() { compiledInstructions.clear(); - for (auto instruction: instructions) { + for (auto instruction : instructions) { compiledInstructions.emplace_back(CompiledInstruction(instruction, accountKeys)); } } @@ -145,3 +140,5 @@ uint8_t Transaction::getAccountIndex(Address publicKey) { bool Signature::operator==(const Signature& v) const { return bytes == v.bytes; } + +} // namespace TW::Solana diff --git a/src/Solana/Transaction.h b/src/Solana/Transaction.h index 701d90c245b..8ea3e6c5fca 100644 --- a/src/Solana/Transaction.h +++ b/src/Solana/Transaction.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,11 +9,10 @@ #include "Address.h" #include "../Base58.h" #include "../BinaryCoding.h" -#include "../Data.h" +#include "Data.h" #include #include -#include namespace TW::Solana { @@ -28,6 +27,7 @@ const std::string SYSVAR_CLOCK_ID_ADDRESS = "SysvarC1ock111111111111111111111111 const std::string STAKE_CONFIG_ID_ADDRESS = "StakeConfig11111111111111111111111111111111"; const std::string NULL_ID_ADDRESS = "11111111111111111111111111111111"; const std::string SYSVAR_STAKE_HISTORY_ID_ADDRESS = "SysvarStakeHistory1111111111111111111111111"; +const std::string MEMO_PROGRAM_ID_ADDRESS = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"; template Data shortVecLength(std::vector vec) { @@ -98,25 +98,19 @@ struct Instruction { Instruction(const Address& programId, const std::vector& accounts, const Data& data) : programId(programId), accounts(accounts), data(data) {} - // This constructor creates a default System Transfer instruction - Instruction(const std::vector& accounts, uint64_t value) : - programId(Address(SYSTEM_PROGRAM_ID_ADDRESS)), - accounts(accounts) - { - SystemInstruction type = Transfer; + // This creator creates a default System Transfer instruction + static Instruction createTransfer(const std::vector& accounts, uint64_t value) { + const SystemInstruction type = Transfer; auto data = Data(); encode32LE(static_cast(type), data); encode64LE(static_cast(value), data); - this->data = data; + + return Instruction(Address(SYSTEM_PROGRAM_ID_ADDRESS), accounts, data); } - // This constructor creates a System CreateAccountWithSeed instruction - Instruction(const std::vector& accounts, uint64_t value, uint64_t space, const Address& programId, - const Address& voteAddress, uint64_t seedLength, const Address& signer) : - programId(Address(SYSTEM_PROGRAM_ID_ADDRESS)), - accounts(accounts) - { - SystemInstruction type = CreateAccountWithSeed; + static Instruction createAccountWithSeed(const std::vector& accounts, uint64_t value, uint64_t space, const Address& programId, + const Address& voteAddress, uint64_t seedLength, const Address& signer) { + const SystemInstruction type = CreateAccountWithSeed; auto data = Data(); std::string seed = voteAddress.string(); Data vecSeed(seed.begin(), seed.end()); @@ -128,62 +122,62 @@ struct Instruction { encode64LE(static_cast(value), data); encode64LE(static_cast(space), data); append(data, programId.vector()); - this->data = data; + + return Instruction(Address(SYSTEM_PROGRAM_ID_ADDRESS), accounts, data); } - // This constructor creates an Initialize Stake instruction - Instruction(StakeInstruction type, const std::vector& accounts, const Address& signer) : - programId(Address(STAKE_PROGRAM_ID_ADDRESS)), - accounts(accounts) - { + // creates an Initialize Stake instruction + static Instruction createStakeInitialize(const std::vector& accounts, const Address& signer) { + const StakeInstruction type = Initialize; auto data = Data(); encode32LE(static_cast(type), data); append(data, signer.vector()); append(data, signer.vector()); auto lockup = Data(48); append(data, lockup); - this->data = data; + + return Instruction(Address(STAKE_PROGRAM_ID_ADDRESS), accounts, data); } - // This constructor creates a Withdraw Stake instruction - Instruction(StakeInstruction type, const std::vector& accounts, uint64_t value) : - programId(Address(STAKE_PROGRAM_ID_ADDRESS)), - accounts(accounts) - { + // creates a Withdraw Stake instruction + static Instruction createStakeWithdraw(const std::vector& accounts, uint64_t value) { + const StakeInstruction type = Withdraw; auto data = Data(); encode32LE(static_cast(type), data); encode64LE(static_cast(value), data); - this->data = data; + + return Instruction(Address(STAKE_PROGRAM_ID_ADDRESS), accounts, data); } - // This constructor creates a Stake instruction - Instruction(StakeInstruction type, const std::vector& accounts) : - programId(Address(STAKE_PROGRAM_ID_ADDRESS)), - accounts(accounts) - { + // creates a Stake instruction + static Instruction createStake(StakeInstruction type, const std::vector& accounts) { auto data = Data(); encode32LE(static_cast(type), data); - this->data = data; + + return Instruction(Address(STAKE_PROGRAM_ID_ADDRESS), accounts, data); } - // This constructor creates a createAccount token instruction. - Instruction(TokenInstruction type, const std::vector& accounts) : - programId(Address(ASSOCIATED_TOKEN_PROGRAM_ID_ADDRESS)), - accounts(accounts) - { - this->data = Data(); + // creates a createAccount token instruction. + static Instruction createTokenCreateAccount(const std::vector& accounts) { + auto data = Data(); + return Instruction(Address(ASSOCIATED_TOKEN_PROGRAM_ID_ADDRESS), accounts, data); } - // This constructor creates a transfer token instruction. - Instruction(TokenInstruction type, const std::vector& accounts, uint64_t value, uint8_t decimals) : - programId(Address(TOKEN_PROGRAM_ID_ADDRESS)), - accounts(accounts) - { + // creates a transfer token instruction. + static Instruction createTokenTransfer(const std::vector& accounts, uint64_t value, uint8_t decimals) { + const TokenInstruction type = TokenTransfer; auto data = Data(); data.push_back(static_cast(type)); encode64LE(value, data); data.push_back(static_cast(decimals)); - this->data = data; + + return Instruction(Address(TOKEN_PROGRAM_ID_ADDRESS), accounts, data); + } + + static Instruction createMemo(std::string memo) { + auto data = TW::data(memo); + std::vector accounts; // empty + return Instruction(Address(MEMO_PROGRAM_ID_ADDRESS), accounts, data); } }; @@ -290,14 +284,28 @@ class Message { // compile the instructions; replace instruction accounts with indices void compileInstructions(); + static void appendReferences(std::vector& accountMetas, const std::vector
& references) { + for (auto &&reference: references) { + accountMetas.emplace_back(reference, false, true); + } + } + // This constructor creates a default single-signer Transfer message - static Message createTransfer(const Address& from, const Address& to, uint64_t value, Hash recentBlockhash) { - auto instruction = Instruction(std::vector{ + static Message createTransfer(const Address& from, const Address& to, uint64_t value, Hash recentBlockhash, + std::string memo = "", std::vector
references = {} + ) { + std::vector instructions; + if (memo.length() > 0) { + // Optional memo. Order: before transfer, as per documentation. + instructions.push_back(Instruction::createMemo(memo)); + } + std::vector accountMetas = { AccountMeta(from, true, false), AccountMeta(to, false, false), - }, value); - std::vector instructions = {instruction}; - return Message(recentBlockhash, {instruction}); + }; + appendReferences(accountMetas, references); + instructions.push_back(Instruction::createTransfer(accountMetas, value)); + return Message(recentBlockhash, instructions); } // This constructor creates a create_account_with_seed_and_delegate_stake message @@ -309,22 +317,23 @@ class Message { auto sysvarStakeHistoryId = Address(SYSVAR_STAKE_HISTORY_ID_ADDRESS); auto stakeProgramId = Address(STAKE_PROGRAM_ID_ADDRESS); std::vector instructions; + instructions.reserve(3); // create_account_with_seed instruction Address seed = Address(data(recentBlockhash.bytes.data(), recentBlockhash.bytes.size())); - auto createAccountInstruction = Instruction(std::vector{ + auto createAccountInstruction = Instruction::createAccountWithSeed(std::vector{ AccountMeta(signer, true, true), AccountMeta(stakeAddress, false, false), AccountMeta(signer, true, true), }, value, 200, stakeProgramId, seed, 32, signer); instructions.push_back(createAccountInstruction); // initialize instruction - auto initializeInstruction = Instruction(Initialize, std::vector{ + auto initializeInstruction = Instruction::createStakeInitialize(std::vector{ AccountMeta(stakeAddress, false, false), AccountMeta(sysvarRentId, false, true) }, signer); instructions.push_back(initializeInstruction); // delegate_stake instruction - auto delegateInstruction = Instruction(DelegateStake, + auto delegateInstruction = Instruction::createStake(DelegateStake, std::vector{ AccountMeta(stakeAddress, false, false), // 0. `[WRITE]` Initialized stake account to be delegated AccountMeta(voteAddress, false, true), // 1. `[]` Vote account to which this stake will be delegated @@ -340,7 +349,7 @@ class Message { // This constructor creates a deactivate_stake message static Message createStakeDeactivate(const Address& signer, const Address& stakeAddress, Hash recentBlockhash) { auto sysvarClockId = Address(SYSVAR_CLOCK_ID_ADDRESS); - auto instruction = Instruction(Deactivate, std::vector{ + auto instruction = Instruction::createStake(Deactivate, std::vector{ AccountMeta(stakeAddress, false, false), // 0. `[WRITE]` Delegated stake account AccountMeta(sysvarClockId, false, true), // 1. `[]` Clock sysvar AccountMeta(signer, true, false), // 2. `[SIGNER]` Stake authority @@ -353,7 +362,7 @@ class Message { auto sysvarClockId = Address(SYSVAR_CLOCK_ID_ADDRESS); std::vector instructions; for(auto& address: stakeAddresses) { - auto instruction = Instruction(Deactivate, std::vector{ + auto instruction = Instruction::createStake(Deactivate, std::vector{ AccountMeta(address, false, false), // 0. `[WRITE]` Delegated stake account AccountMeta(sysvarClockId, false, true), // 1. `[]` Clock sysvar AccountMeta(signer, true, false), // 2. `[SIGNER]` Stake authority @@ -367,7 +376,7 @@ class Message { static Message createStakeWithdraw(const Address& signer, const Address& stakeAddress, uint64_t value, Hash recentBlockhash) { auto sysvarClockId = Address(SYSVAR_CLOCK_ID_ADDRESS); auto sysvarStakeHistoryId = Address(SYSVAR_STAKE_HISTORY_ID_ADDRESS); - auto instruction = Instruction(Withdraw, std::vector{ + auto instruction = Instruction::createStakeWithdraw(std::vector{ AccountMeta(stakeAddress, false, false), // 0. `[WRITE]` Stake account from which to withdraw AccountMeta(signer, false, false), // 1. `[WRITE]` Recipient account AccountMeta(sysvarClockId, false, true), // 2. `[]` Clock sysvar @@ -383,7 +392,7 @@ class Message { auto sysvarStakeHistoryId = Address(SYSVAR_STAKE_HISTORY_ID_ADDRESS); std::vector instructions; for(auto& stake: stakes) { - auto instruction = Instruction(Withdraw, std::vector{ + auto instruction = Instruction::createStakeWithdraw(std::vector{ AccountMeta(stake.first, false, false), // 0. `[WRITE]` Stake account from which to withdraw AccountMeta(signer, false, false), // 1. `[WRITE]` Recipient account AccountMeta(sysvarClockId, false, true), // 2. `[]` Clock sysvar @@ -397,12 +406,11 @@ class Message { // This constructor creates a createAccount token message // see create_associated_token_account() solana-program-library/associated-token-account/program/src/lib.rs - static Message createTokenCreateAccount(const Address& signer, TokenInstruction type, const Address& otherMainAccount, const Address& tokenMintAddress, const Address& tokenAddress, Hash recentBlockhash) { - assert(type == TokenInstruction::CreateTokenAccount); + static Message createTokenCreateAccount(const Address& signer, const Address& otherMainAccount, const Address& tokenMintAddress, const Address& tokenAddress, Hash recentBlockhash) { auto sysvarRentId = Address(SYSVAR_RENT_ID_ADDRESS); auto systemProgramId = Address(SYSTEM_PROGRAM_ID_ADDRESS); auto tokenProgramId = Address(TOKEN_PROGRAM_ID_ADDRESS); - auto instruction = Instruction(type, std::vector{ + auto instruction = Instruction::createTokenCreateAccount(std::vector{ AccountMeta(signer, true, false), // fundingAddress, AccountMeta(tokenAddress, false, false), AccountMeta(otherMainAccount, false, true), @@ -416,25 +424,37 @@ class Message { // This constructor creates a transfer token message. // see transfer_checked() solana-program-library/token/program/src/instruction.rs - static Message createTokenTransfer(const Address& signer, TokenInstruction type, const Address& tokenMintAddress, - const Address& senderTokenAddress, const Address& recipientTokenAddress, uint64_t amount, uint8_t decimals, Hash recentBlockhash) { - assert(type == TokenInstruction::TokenTransfer); - auto instruction = Instruction(type, std::vector{ + static Message createTokenTransfer(const Address& signer, const Address& tokenMintAddress, + const Address& senderTokenAddress, const Address& recipientTokenAddress, uint64_t amount, uint8_t decimals, Hash recentBlockhash, + std::string memo = "", std::vector
references = {} + ) { + std::vector instructions; + if (memo.length() > 0) { + // Optional memo. Order: before transfer, as per documentation. + instructions.push_back(Instruction::createMemo(memo)); + } + std::vector accountMetas = { AccountMeta(senderTokenAddress, false, false), AccountMeta(tokenMintAddress, false, true), AccountMeta(recipientTokenAddress, false, false), AccountMeta(signer, true, false), - }, amount, decimals); - return Message(recentBlockhash, {instruction}); + }; + appendReferences(accountMetas, references); + instructions.push_back(Instruction::createTokenTransfer(accountMetas, amount, decimals)); + return Message(recentBlockhash, instructions); } // This constructor creates a createAndTransferToken message, combining createAccount and transfer. static Message createTokenCreateAndTransfer(const Address& signer, const Address& recipientMainAddress, const Address& tokenMintAddress, - const Address& recipientTokenAddress, const Address& senderTokenAddress, uint64_t amount, uint8_t decimals, Hash recentBlockhash) { - auto sysvarRentId = Address(SYSVAR_RENT_ID_ADDRESS); - auto systemProgramId = Address(SYSTEM_PROGRAM_ID_ADDRESS); - auto tokenProgramId = Address(TOKEN_PROGRAM_ID_ADDRESS); - auto createInstruction = Instruction(TokenInstruction::CreateTokenAccount, std::vector{ + const Address& recipientTokenAddress, const Address& senderTokenAddress, uint64_t amount, uint8_t decimals, Hash recentBlockhash, + std::string memo = "", std::vector
references = {} + ) { + const auto sysvarRentId = Address(SYSVAR_RENT_ID_ADDRESS); + const auto systemProgramId = Address(SYSTEM_PROGRAM_ID_ADDRESS); + const auto tokenProgramId = Address(TOKEN_PROGRAM_ID_ADDRESS); + std::vector instructions; + instructions.reserve(3); + instructions.emplace_back(Instruction::createTokenCreateAccount(std::vector{ AccountMeta(signer, true, false), // fundingAddress, AccountMeta(recipientTokenAddress, false, false), AccountMeta(recipientMainAddress, false, true), @@ -442,14 +462,20 @@ class Message { AccountMeta(systemProgramId, false, true), AccountMeta(tokenProgramId, false, true), AccountMeta(sysvarRentId, false, true), - }); - auto transferInstruction = Instruction(TokenInstruction::TokenTransfer, std::vector{ + })); + if (memo.length() > 0) { + // Optional memo. Order: before transfer, as per documentation. + instructions.emplace_back(Instruction::createMemo(memo)); + } + std::vector accountMetas = { AccountMeta(senderTokenAddress, false, false), AccountMeta(tokenMintAddress, false, true), AccountMeta(recipientTokenAddress, false, false), AccountMeta(signer, true, false), - }, amount, decimals); - return Message(recentBlockhash, {createInstruction, transferInstruction}); + }; + appendReferences(accountMetas, references); + instructions.push_back(Instruction::createTokenTransfer(accountMetas, amount, decimals)); + return Message(recentBlockhash, instructions); } }; @@ -465,8 +491,8 @@ class Transaction { } // Default basic transfer transaction - Transaction(const Address& from, const Address& to, uint64_t value, Hash recentBlockhash) - : message(Message::createTransfer(from, to, value, recentBlockhash)) { + Transaction(const Address& from, const Address& to, uint64_t value, Hash recentBlockhash, std::string memo = "", std::vector
references = {}) + : message(Message::createTransfer(from, to, value, recentBlockhash, memo, references)) { this->signatures.resize(1, Signature(defaultSignature)); } @@ -480,8 +506,3 @@ class Transaction { }; } // namespace TW::Solana - -/// Wrapper for C interface. -struct TWSolanaTransaction { - TW::Solana::Transaction impl; -}; diff --git a/src/Stellar/Address.cpp b/src/Stellar/Address.cpp index c7197b89451..84b99a62bae 100644 --- a/src/Stellar/Address.cpp +++ b/src/Stellar/Address.cpp @@ -5,9 +5,9 @@ // file LICENSE at the root of the source code distribution tree. #include "Address.h" +#include "Crc.h" #include "../Base32.h" #include "../HexCoding.h" -#include "Crc.h" #include #include @@ -15,7 +15,7 @@ #include #include -using namespace TW::Stellar; +namespace TW::Stellar { bool Address::isValid(const std::string& string) { bool valid = false; @@ -82,3 +82,5 @@ std::string Address::string() const { auto out = Base32::encode(bytesAsData); return out; } + +} // namespace TW::Stellar diff --git a/src/Stellar/Address.h b/src/Stellar/Address.h index 6183aaeaace..6f42dd14926 100644 --- a/src/Stellar/Address.h +++ b/src/Stellar/Address.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include diff --git a/src/Stellar/Entry.cpp b/src/Stellar/Entry.cpp index 0e1beb09ca3..a2412dcb5dd 100644 --- a/src/Stellar/Entry.cpp +++ b/src/Stellar/Entry.cpp @@ -9,19 +9,20 @@ #include "Address.h" #include "Signer.h" -using namespace TW::Stellar; -using namespace std; +namespace TW::Stellar { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +} // namespace TW::Stellar diff --git a/src/Stellar/Entry.h b/src/Stellar/Entry.h index ed070efadf1..f4236c325df 100644 --- a/src/Stellar/Entry.h +++ b/src/Stellar/Entry.h @@ -12,12 +12,11 @@ namespace TW::Stellar { /// Entry point for implementation of Stellar coin, and Kin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeStellar, TWCoinTypeKin}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::Stellar diff --git a/src/Stellar/Signer.cpp b/src/Stellar/Signer.cpp index ba11047c747..5d48a71551b 100644 --- a/src/Stellar/Signer.cpp +++ b/src/Stellar/Signer.cpp @@ -4,8 +4,8 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "Base64.h" #include "Signer.h" +#include "Base64.h" #include "../BinaryCoding.h" #include "../Hash.h" #include "../HexCoding.h" @@ -14,8 +14,8 @@ #include using namespace TW; -using namespace TW::Stellar; +namespace TW::Stellar { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto signer = Signer(input); auto output = Proto::SigningOutput(); @@ -25,12 +25,12 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { std::string Signer::sign() const noexcept { - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - auto account = Address(input.account()); - auto encoded = encode(input); + auto key = PrivateKey(Data(_input.private_key().begin(), _input.private_key().end())); + auto account = Address(_input.account()); + auto encoded = encode(_input); auto encodedWithHeaders = Data(); - auto publicNetwork = input.passphrase(); // Header + auto publicNetwork = _input.passphrase(); // Header auto passphrase = Hash::sha256(publicNetwork); encodedWithHeaders.insert(encodedWithHeaders.end(), passphrase.begin(), passphrase.end()); auto transactionType = Data{0, 0, 0, 2}; // Header @@ -64,7 +64,7 @@ Data Signer::encode(const Proto::SigningInput& input) const { // Time bounds if (input.has_op_change_trust() && input.op_change_trust().valid_before() != 0) { encode32BE(1, data); - encode64BE(0, data); // from + encode64BE(0, data); // from encode64BE(input.op_change_trust().valid_before(), data); // to } else { encode32BE(0, data); // missing @@ -91,27 +91,51 @@ Data Signer::encode(const Proto::SigningInput& input) const { } // Operations - encode32BE(1, data); // Operation list size. Only 1 operation. - encode32BE(0, data); // Source equals account + encode32BE(1, data); // Operation list size. Only 1 operation. + encode32BE(0, data); // Source equals account encode32BE(operationType(input), data); // Operation type switch (input.operation_oneof_case()) { - case Proto::SigningInput::kOpCreateAccount: - default: - encodeAddress(Address(input.op_create_account().destination()), data); - encode64BE(input.op_create_account().amount(), data); - break; - - case Proto::SigningInput::kOpPayment: - encodeAddress(Address(input.op_payment().destination()), data); - encodeAsset(input.op_payment().asset(), data); - encode64BE(input.op_payment().amount(), data); - break; - - case Proto::SigningInput::kOpChangeTrust: - encodeAsset(input.op_change_trust().asset(), data); - encode64BE(0x7fffffffffffffff, data); // limit MAX - break; + case Proto::SigningInput::kOpCreateAccount: + default: + encodeAddress(Address(input.op_create_account().destination()), data); + encode64BE(input.op_create_account().amount(), data); + break; + + case Proto::SigningInput::kOpPayment: + encodeAddress(Address(input.op_payment().destination()), data); + encodeAsset(input.op_payment().asset(), data); + encode64BE(input.op_payment().amount(), data); + break; + + case Proto::SigningInput::kOpChangeTrust: + encodeAsset(input.op_change_trust().asset(), data); + encode64BE(0x7fffffffffffffff, data); // limit MAX + break; + + case Proto::SigningInput::kOpCreateClaimableBalance: { + const auto ClaimantTypeV0 = 0; + encodeAsset(input.op_create_claimable_balance().asset(), data); + encode64BE(input.op_create_claimable_balance().amount(), data); + auto nClaimants = input.op_create_claimable_balance().claimants_size(); + encode32BE((uint32_t)nClaimants, data); + for (auto i = 0; i < nClaimants; ++i) { + encode32BE((uint32_t)ClaimantTypeV0, data); + encodeAddress(Address(input.op_create_claimable_balance().claimants(i).account()), data); + encode32BE((uint32_t)input.op_create_claimable_balance().claimants(i).predicate(), data); + // Note: other predicates not supported, predicate-specific data would follow here + } + } break; + + case Proto::SigningInput::kOpClaimClaimableBalance: { + const auto ClaimableBalanceIdTypeClaimableBalanceIdTypeV0 = 0; + encode32BE((uint32_t)ClaimableBalanceIdTypeClaimableBalanceIdTypeV0, data); + const auto balanceId = input.op_claim_claimable_balance().balance_id(); + if (balanceId.size() != 32) { + return Data(); + } + data.insert(data.end(), balanceId.begin(), balanceId.end()); + } break; } encode32BE(0, data); // Ext @@ -120,13 +144,17 @@ Data Signer::encode(const Proto::SigningInput& input) const { uint32_t Signer::operationType(const Proto::SigningInput& input) { switch (input.operation_oneof_case()) { - case Proto::SigningInput::kOpCreateAccount: - default: - return 0; - case Proto::SigningInput::kOpPayment: - return 1; - case Proto::SigningInput::kOpChangeTrust: - return 6; + case Proto::SigningInput::kOpCreateAccount: + default: + return 0; + case Proto::SigningInput::kOpPayment: + return 1; + case Proto::SigningInput::kOpChangeTrust: + return 6; + case Proto::SigningInput::kOpCreateClaimableBalance: + return 14; + case Proto::SigningInput::kOpClaimClaimableBalance: + return 15; } } @@ -144,7 +172,7 @@ void Signer::encodeAsset(const Proto::Asset& asset, Data& data) { } encode32BE(assetType, data); if (assetType > 0) { - for (auto i = 0; i < 4; ++i) { + for (auto i = 0ul; i < 4; ++i) { if (alphaUse.length() > i) { data.push_back(alphaUse[i]); } else { @@ -165,3 +193,5 @@ void Signer::pad(Data& data) const { data.insert(data.end(), 0); } } + +} // namespace TW::Stellar diff --git a/src/Stellar/Signer.h b/src/Stellar/Signer.h index c38896000b2..7cf60a41094 100644 --- a/src/Stellar/Signer.h +++ b/src/Stellar/Signer.h @@ -6,7 +6,7 @@ #pragma once #include "Address.h" -#include "../Data.h" +#include "Data.h" #include "../Hash.h" #include "../PrivateKey.h" #include "../proto/Stellar.pb.h" @@ -20,9 +20,9 @@ class Signer { /// Signs a Proto::SigningInput transaction static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; public: - const Proto::SigningInput& input; + const Proto::SigningInput& _input; - Signer(const Proto::SigningInput& input) : input(input) {} + Signer(const Proto::SigningInput& input) : _input(input) {} /// Signs the given transaction. std::string sign() const noexcept; diff --git a/src/THORChain/Entry.cpp b/src/THORChain/Entry.cpp index 67faa8a23dc..02c1d4f97c0 100644 --- a/src/THORChain/Entry.cpp +++ b/src/THORChain/Entry.cpp @@ -9,18 +9,21 @@ #include "Signer.h" #include "../proto/Cosmos.pb.h" -using namespace TW::THORChain; using namespace std; +namespace TW::THORChain { + // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { auto input = Cosmos::Proto::SigningInput(); input.ParseFromArray(dataIn.data(), (int)dataIn.size()); auto serializedOut = Signer::sign(input).SerializeAsString(); dataOut.insert(dataOut.end(), serializedOut.begin(), serializedOut.end()); } -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { +string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { return Signer::signJSON(json, key); } + +} // namespace TW::THORChain diff --git a/src/THORChain/Entry.h b/src/THORChain/Entry.h index 5d5bd14c1de..a5650e5b94a 100644 --- a/src/THORChain/Entry.h +++ b/src/THORChain/Entry.h @@ -13,13 +13,10 @@ namespace TW::THORChain { /// Entry point for implementation of THORChain coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public Cosmos::Entry { +class Entry final : public Cosmos::Entry { public: - virtual const std::vector coinTypes() const { - return { TWCoinTypeTHORChain }; - } - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; }; } // namespace TW::THORChain diff --git a/src/THORChain/Signer.cpp b/src/THORChain/Signer.cpp index c6f768cc197..30ebb84217b 100644 --- a/src/THORChain/Signer.cpp +++ b/src/THORChain/Signer.cpp @@ -8,11 +8,12 @@ #include "../Cosmos/Signer.h" #include "../proto/Cosmos.pb.h" +#include #include using namespace TW; -using namespace TW::THORChain; +namespace TW::THORChain { const std::string TYPE_PREFIX_MSG_SEND = "thorchain/MsgSend"; Cosmos::Proto::SigningOutput Signer::sign(Cosmos::Proto::SigningInput& input) noexcept { @@ -21,7 +22,7 @@ Cosmos::Proto::SigningOutput Signer::sign(Cosmos::Proto::SigningInput& input) no input.mutable_messages(i)->mutable_send_coins_message()->set_type_prefix(TYPE_PREFIX_MSG_SEND); } } - return Cosmos::Signer::sign(input); + return Cosmos::Signer::sign(input, TWCoinTypeTHORChain); } std::string Signer::signJSON(const std::string& json, const Data& key) { @@ -31,3 +32,5 @@ std::string Signer::signJSON(const std::string& json, const Data& key) { auto output = Signer::sign(input); return output.json(); } + +} // namespace TW::THORChain diff --git a/src/THORChain/Signer.h b/src/THORChain/Signer.h index 5488f281291..e524abfb5ee 100644 --- a/src/THORChain/Signer.h +++ b/src/THORChain/Signer.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "../proto/Cosmos.pb.h" #include diff --git a/src/THORChain/Swap.cpp b/src/THORChain/Swap.cpp new file mode 100644 index 00000000000..17d47ebf51e --- /dev/null +++ b/src/THORChain/Swap.cpp @@ -0,0 +1,233 @@ +// Copyright © 2017-2021 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 "Swap.h" + +#include +#include "Coin.h" +#include "proto/THORChainSwap.pb.h" +// BTC +#include "Bitcoin/SigHashType.h" +#include "../proto/Bitcoin.pb.h" +// ETH +#include "Ethereum/Address.h" +#include "Ethereum/ABI/Function.h" +#include "Ethereum/ABI/ParamBase.h" +#include "Ethereum/ABI/ParamAddress.h" +#include "uint256.h" +#include "../proto/Ethereum.pb.h" +// BNB +#include "Binance/Address.h" +#include "../proto/Binance.pb.h" + +#include + +/* + * References: + * https://gitlab.com/thorchain/asgardex-common/asgardex-util + */ + +namespace TW::THORChainSwap { + +TWCoinType chainCoinType(Chain chain) { + switch (chain) { + case Chain::ETH: return TWCoinTypeEthereum; + case Chain::BNB: return TWCoinTypeBinance; + case Chain::BTC: return TWCoinTypeBitcoin; + case Chain::THOR: + default: + return TWCoinTypeTHORChain; + } +} + +std::string chainName(Chain chain) { + switch (chain) { + case Chain::ETH: return "ETH"; + case Chain::BNB: return "BNB"; + case Chain::BTC: return "BTC"; + case Chain::THOR: + default: + return "THOR"; + } +} + +std::string Swap::buildMemo(Chain toChain, const std::string& toSymbol, const std::string& toTokenId, const std::string& toAddress, uint64_t limit) { + std::string prefix = "SWAP"; + if (toChain == Chain::ETH) { + prefix = "="; + } + const auto toCoinToken = (!toTokenId.empty() && toTokenId != "0x0000000000000000000000000000000000000000") ? toTokenId : toSymbol; + return prefix + ":" + chainName(toChain) + "." + toCoinToken + ":" + toAddress + ":" + std::to_string(limit); +} + +bool validateAddress(Chain chain, const std::string& address) { + return TW::validateAddress(chainCoinType(chain), address); +} + +std::tuple Swap::build( + Chain fromChain, + Chain toChain, + const std::string& fromAddress, + const std::string& toSymbol, + const std::string& toTokenId, + const std::string& toAddress, + const std::string& vaultAddress, + const std::string& routerAddress, + const std::string& fromAmount, + const std::string& toAmountLimit +) { + if (!validateAddress(fromChain, fromAddress)) { + return std::make_tuple({}, static_cast(Proto::ErrorCode::Error_Invalid_from_address), "Invalid from address"); + } + if (!validateAddress(toChain, toAddress)) { + return std::make_tuple({}, static_cast(Proto::ErrorCode::Error_Invalid_to_address), "Invalid to address"); + } + + uint64_t fromAmountNum = std::atoll(fromAmount.c_str()); + uint64_t toAmountLimitNum = std::atoll(toAmountLimit.c_str()); + const auto memo = buildMemo(toChain, toSymbol, toTokenId, toAddress, toAmountLimitNum); + + switch (fromChain) { + case Chain::BTC: { + Data out; + auto res = buildBitcoin(toChain, toSymbol, toTokenId, fromAddress, toAddress, vaultAddress, fromAmountNum, memo, out); + return std::make_tuple(std::move(out), std::move(std::get<0>(res)), std::move(std::get<1>(res))); + } + + case Chain::ETH: { + Data out; + auto res = buildEthereum(toChain, toSymbol, toTokenId, fromAddress, toAddress, vaultAddress, routerAddress, fromAmountNum, memo, out); + return std::make_tuple(std::move(out), std::move(std::get<0>(res)), std::move(std::get<1>(res))); + } + + case Chain::BNB: { + Data out; + auto res = buildBinance(toChain, toSymbol, toTokenId, fromAddress, toAddress, vaultAddress, fromAmountNum, memo, out); + return std::make_tuple(std::move(out), std::move(std::get<0>(res)), std::move(std::get<1>(res))); + } + + case Chain::THOR: + default: + return std::make_tuple({}, static_cast(Proto::ErrorCode::Error_Unsupported_from_chain), "Unsupported from chain: " + std::to_string(toChain)); + } +} + +std::pair Swap::buildBitcoin([[maybe_unused]] Chain toChain, [[maybe_unused]] const std::string& toSymbol, [[maybe_unused]] const std::string& toTokenId, const std::string& fromAddress, [[maybe_unused]] const std::string& toAddress, const std::string& vaultAddress, uint64_t amount, const std::string& memo, Data& out) { + auto input = Bitcoin::Proto::SigningInput(); + + // Following fields must be set afterwards, before signing ... + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_byte_fee(1); + input.set_use_max_amount(false); + // private_key[] + // utxo[] + // scripts[] + // ... end + + input.set_amount(amount); + input.set_to_address(vaultAddress); + input.set_change_address(fromAddress); + input.set_coin_type(TWCoinTypeBitcoin); + input.set_output_op_return(memo); + + auto serialized = input.SerializeAsString(); + out.insert(out.end(), serialized.begin(), serialized.end()); + return std::make_pair(0, ""); +} + +Data ethAddressStringToData(const std::string& asString) { + Data asData(20); + if (asString.empty() || !Ethereum::Address::isValid(asString)) { + return asData; + } + auto address = Ethereum::Address(asString); + std::copy(address.bytes.begin(), address.bytes.end(), asData.data()); + return asData; +} + +std::pair Swap::buildEthereum([[maybe_unused]] Chain toChain, [[maybe_unused]] const std::string& toSymbol, const std::string& toTokenId, [[maybe_unused]] const std::string& fromAddress, [[maybe_unused]] const std::string& toAddress, const std::string& vaultAddress, const std::string& routerAddress, uint64_t amount, const std::string& memo, Data& out) { + auto input = Ethereum::Proto::SigningInput(); + + // some sanity check / address conversion + Data vaultAddressBin = ethAddressStringToData(vaultAddress); + if (!Ethereum::Address::isValid(vaultAddress) || vaultAddressBin.size() != Ethereum::Address::size) { + return std::make_pair(static_cast(Proto::ErrorCode::Error_Invalid_vault_address), "Invalid vault address: " + vaultAddress); + } + if (!Ethereum::Address::isValid(routerAddress)) { + return std::make_pair(static_cast(Proto::ErrorCode::Error_Invalid_router_address), "Invalid router address: " + routerAddress); + } + Data toAssetAddressBin = ethAddressStringToData(toTokenId); + + // Following fields must be set afterwards, before signing ... + const auto chainId = store(uint256_t(0)); + input.set_chain_id(chainId.data(), chainId.size()); + const auto nonce = store(uint256_t(0)); + input.set_nonce(nonce.data(), nonce.size()); + const auto gasPrice = store(uint256_t(0)); + input.set_gas_price(gasPrice.data(), gasPrice.size()); + const auto gasLimit = store(uint256_t(0)); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_private_key(""); + // ... end + + input.set_to_address(routerAddress); + auto& transfer = *input.mutable_transaction()->mutable_contract_generic(); + auto func = Ethereum::ABI::Function("deposit", std::vector>{ + std::make_shared(vaultAddressBin), + std::make_shared(toAssetAddressBin), + std::make_shared(uint256_t(amount)), + std::make_shared(memo) + }); + Data payload; + func.encode(payload); + transfer.set_data(payload.data(), payload.size()); + Data amountData = store(uint256_t(amount)); + transfer.set_amount(amountData.data(), amountData.size()); + + auto serialized = input.SerializeAsString(); + out.insert(out.end(), serialized.begin(), serialized.end()); + return std::make_pair(0, ""); +} + +std::pair Swap::buildBinance([[maybe_unused]] Chain toChain, [[maybe_unused]] const std::string& toSymbol, [[maybe_unused]] const std::string& toTokenId, const std::string& fromAddress, [[maybe_unused]] const std::string& toAddress, const std::string& vaultAddress, uint64_t amount, const std::string& memo, Data& out) { + auto input = Binance::Proto::SigningInput(); + + // Following fields must be set afterwards, before signing ... + input.set_chain_id(""); + input.set_account_number(0); + input.set_sequence(0); + input.set_source(0); + input.set_private_key(""); + // ... end + + input.set_memo(memo); + + auto& order = *input.mutable_send_order(); + + auto token = Binance::Proto::SendOrder::Token(); + token.set_denom("BNB"); + token.set_amount(amount); + { + Binance::Address fromAddressBin; + Binance::Address::decode(fromAddress, fromAddressBin); + auto input_ = order.add_inputs(); + input_->set_address(fromAddressBin.getKeyHash().data(), fromAddressBin.getKeyHash().size()); + *input_->add_coins() = token; + } + { + Binance::Address vaultAddressBin; + Binance::Address::decode(vaultAddress, vaultAddressBin); + auto output = order.add_outputs(); + output->set_address(vaultAddressBin.getKeyHash().data(), vaultAddressBin.getKeyHash().size()); + *output->add_coins() = token; + } + + auto serialized = input.SerializeAsString(); + out.insert(out.end(), serialized.begin(), serialized.end()); + return std::make_pair(0, ""); +} + +} // namespace TW diff --git a/src/THORChain/Swap.h b/src/THORChain/Swap.h new file mode 100644 index 00000000000..723b4ee83f2 --- /dev/null +++ b/src/THORChain/Swap.h @@ -0,0 +1,52 @@ +// Copyright © 2017-2021 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Data.h" + +#include +#include + +namespace TW::THORChainSwap { + +/// Supported blockchains +enum Chain { + THOR = 0, + BTC = 1, + ETH = 2, + BNB = 3, +}; + +/// Building THORChain cross-chain transactions +class Swap { +public: + /// Logic to build a native transaction on the source chain for a swap + /// Returns serialized SigningInput proto message, on the source chain, + /// and an optional error code + message + static std::tuple build( + Chain fromChain, + Chain toChain, + const std::string& fromAddress, // source address, on source chain, string format + const std::string& toSymbol, // destination coin symbol + const std::string& toTokenId, // destination token ID, on the destination chain, in case destination is a token, empty otherwise + const std::string& toAddress, // destination address, on destination chain, string format + const std::string& vaultAddress, // ThorChainSwap vault, on the source chain. Should be queried afresh, as it may change + const std::string& routerAddress, // ThorChain router, only in case of Ethereum source network + const std::string& fromAmount, // The source amount, as integer in the smallest native unit of the chain + const std::string& toAmountLimit // The minimum accepted destination amount. Actual destination amount will depend on current rates, limit amount can be used to prevent using very unfavorable rates. + ); + +protected: + static std::pair buildBitcoin(Chain toChain, const std::string& toSymbol, const std::string& toTokenId, const std::string& fromAddress, const std::string& toAddress, const std::string& vaultAddress, uint64_t amount, const std::string& memo, Data& out); + static std::pair buildEthereum(Chain toChain, const std::string& toSymbol, const std::string& toTokenId, const std::string& fromAddress, const std::string& toAddress, const std::string& vaultAddress, const std::string& routerAddress, uint64_t amount, const std::string& memo, Data& out); + static std::pair buildBinance(Chain toChain, const std::string& toSymbol, const std::string& toTokenId, const std::string& fromAddress, const std::string& toAddress, const std::string& vaultAddress, uint64_t amount, const std::string& memo, Data& out); + +public: + static std::string buildMemo(Chain toChain, const std::string& toSymbol, const std::string& toTokenId, const std::string& toAddress, uint64_t limit); +}; + +} // namespace TW diff --git a/src/THORChain/TWSwap.cpp b/src/THORChain/TWSwap.cpp new file mode 100644 index 00000000000..9f1341c50fe --- /dev/null +++ b/src/THORChain/TWSwap.cpp @@ -0,0 +1,92 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Data.h" +#include "Swap.h" +#include "proto/THORChainSwap.pb.h" +#include + +using namespace TW; + +TWData* _Nonnull TWTHORChainSwapBuildSwap(TWData* _Nonnull input) { + THORChainSwap::Proto::SwapInput inputProto; + THORChainSwap::Proto::SwapOutput outputProto; + if (!inputProto.ParseFromArray(TWDataBytes(input), static_cast(TWDataSize(input)))) { + // error + outputProto.mutable_error()->set_code(THORChainSwap::Proto::ErrorCode::Error_Input_proto_deserialization); + outputProto.mutable_error()->set_message("Could not deserialize input proto"); + auto outputData = TW::data(outputProto.SerializeAsString()); + return TWDataCreateWithBytes(outputData.data(), outputData.size()); + } + + const auto fromChain = inputProto.from_chain(); + const auto toChain = inputProto.to_asset().chain(); + auto res = THORChainSwap::Swap::build( + static_cast(static_cast(fromChain)), + static_cast(static_cast(toChain)), + inputProto.from_address(), + inputProto.to_asset().symbol(), + inputProto.to_asset().token_id(), + inputProto.to_address(), + inputProto.vault_address(), + inputProto.router_address(), + inputProto.from_amount(), + inputProto.to_amount_limit()); + + outputProto.set_from_chain(fromChain); + outputProto.set_to_chain(toChain); + if (std::get<1>(res) != 0) { + // error + outputProto.mutable_error()->set_code(static_cast(std::get<1>(res))); + outputProto.mutable_error()->set_message(std::get<2>(res)); + } else { + // no error + outputProto.mutable_error()->set_code(THORChainSwap::Proto::ErrorCode::OK); + outputProto.mutable_error()->set_message(""); + + const Data& txInput = std::get<0>(res); + switch (fromChain) { + case THORChainSwap::Proto::BTC: { + Bitcoin::Proto::SigningInput btcInput; + if (!btcInput.ParseFromArray(txInput.data(), static_cast(txInput.size()))) { + outputProto.mutable_error()->set_code(THORChainSwap::Proto::ErrorCode::Error_Input_proto_deserialization); + outputProto.mutable_error()->set_message("Could not deserialize BTC input"); + } else { + *outputProto.mutable_bitcoin() = btcInput; + } + } break; + + case THORChainSwap::Proto::ETH: { + Ethereum::Proto::SigningInput ethInput; + if (!ethInput.ParseFromArray(txInput.data(), static_cast(txInput.size()))) { + outputProto.mutable_error()->set_code(THORChainSwap::Proto::ErrorCode::Error_Input_proto_deserialization); + outputProto.mutable_error()->set_message("Could not deserialize ETH input"); + } else { + *outputProto.mutable_ethereum() = ethInput; + } + } break; + + case THORChainSwap::Proto::BNB: { + Binance::Proto::SigningInput bnbInput; + if (!bnbInput.ParseFromArray(txInput.data(), static_cast(txInput.size()))) { + outputProto.mutable_error()->set_code(THORChainSwap::Proto::ErrorCode::Error_Input_proto_deserialization); + outputProto.mutable_error()->set_message("Could not deserialize BNB input"); + } else { + *outputProto.mutable_binance() = bnbInput; + } + } break; + + default: + outputProto.mutable_error()->set_code(THORChainSwap::Proto::ErrorCode::Error_Unsupported_from_chain); + outputProto.mutable_error()->set_message(std::string("Unsupported from chain ") + std::to_string(fromChain)); + break; + } + } + + // serialize output + auto outputData = TW::data(outputProto.SerializeAsString()); + return TWDataCreateWithBytes(outputData.data(), outputData.size()); +} diff --git a/src/Tezos/Address.cpp b/src/Tezos/Address.cpp index 046bab28ce6..e351ce4d776 100644 --- a/src/Tezos/Address.cpp +++ b/src/Tezos/Address.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -15,13 +15,12 @@ #include -using namespace TW; -using namespace TW::Tezos; +namespace TW::Tezos { /// Address prefixes. -const std::array tz1Prefix{6, 161, 159}; -const std::array tz2Prefix{6, 161, 161}; -const std::array tz3Prefix{6, 161, 164}; +const std::array tz1Prefix{6, 161, 159}; +const std::array tz2Prefix{6, 161, 161}; +const std::array tz3Prefix{6, 161, 164}; bool Address::isValid(const std::string& string) { const auto decoded = Base58::bitcoin.decodeCheck(string); @@ -67,3 +66,5 @@ Data Address::forge() const { std::string s = string(); return forgePublicKeyHash(s); } + +} // namespace TW::Tezos diff --git a/src/Tezos/Address.h b/src/Tezos/Address.h index 88fb46bc79e..eb360abc6bd 100644 --- a/src/Tezos/Address.h +++ b/src/Tezos/Address.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -7,7 +7,7 @@ #pragma once #include "../Base58Address.h" -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include diff --git a/src/Tezos/BinaryCoding.cpp b/src/Tezos/BinaryCoding.cpp index 35f2d9cee2b..1f030be4cfd 100644 --- a/src/Tezos/BinaryCoding.cpp +++ b/src/Tezos/BinaryCoding.cpp @@ -1,11 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. #include "../Base58.h" -#include "../Data.h" +#include "Data.h" #include "../HexCoding.h" #include "../PublicKey.h" #include "../PrivateKey.h" @@ -13,9 +13,9 @@ #include #include -using namespace TW; +namespace TW::Tezos { -std::string base58ToHex(const std::string& string, size_t prefixLength, uint8_t* prefix) { +std::string base58ToHex(const std::string& string, size_t prefixLength) { const auto decoded = Base58::bitcoin.decodeCheck(string); if (decoded.size() < prefixLength) { return ""; @@ -40,11 +40,13 @@ PublicKey parsePublicKey(const std::string& publicKey) { PrivateKey parsePrivateKey(const std::string& privateKey) { const auto decoded = Base58::bitcoin.decodeCheck(privateKey); auto pk = Data(); - auto prefix_size = 4; + auto prefix_size = 4ul; if (decoded.size() != 32 + prefix_size) { throw std::invalid_argument("Invalid Public Key"); } append(pk, Data(decoded.begin() + prefix_size, decoded.end())); return PrivateKey(pk); -} \ No newline at end of file +} + +} // namespace TW::Tezos diff --git a/src/Tezos/BinaryCoding.h b/src/Tezos/BinaryCoding.h index 837a4e48b94..9bee6a3bd46 100644 --- a/src/Tezos/BinaryCoding.h +++ b/src/Tezos/BinaryCoding.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -6,14 +6,16 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include "../PrivateKey.h" #include -using namespace TW; +namespace TW::Tezos { PublicKey parsePublicKey(const std::string& publicKey); PrivateKey parsePrivateKey(const std::string& privateKey); -std::string base58ToHex(const std::string& data, size_t prefixLength, uint8_t* prefix); +std::string base58ToHex(const std::string& data, size_t prefixLength); + +} // namespace TW::Tezos diff --git a/src/Tezos/Entry.cpp b/src/Tezos/Entry.cpp index 2597237bb92..71aa8246a81 100644 --- a/src/Tezos/Entry.cpp +++ b/src/Tezos/Entry.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,23 +9,24 @@ #include "Address.h" #include "Signer.h" -using namespace TW::Tezos; -using namespace std; +namespace TW::Tezos { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { +std::string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { return Signer::signJSON(json, key); } + +} // namespace TW::Tezos diff --git a/src/Tezos/Entry.h b/src/Tezos/Entry.h index 2a705849f32..bea87ca0aa3 100644 --- a/src/Tezos/Entry.h +++ b/src/Tezos/Entry.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -12,14 +12,13 @@ namespace TW::Tezos { /// Entry point for implementation of Tezos coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeTezos}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual bool supportsJSONSigning() const { return true; } - virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool supportsJSONSigning() const { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; }; } // namespace TW::Tezos diff --git a/src/Tezos/Forging.cpp b/src/Tezos/Forging.cpp index b288b0935bf..83088b3f3c0 100644 --- a/src/Tezos/Forging.cpp +++ b/src/Tezos/Forging.cpp @@ -1,21 +1,29 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +#include "Forging.h" #include "Address.h" #include "BinaryCoding.h" -#include "../Base58.h" -#include "../Data.h" #include "../HexCoding.h" #include "../proto/Tezos.pb.h" - #include -using namespace TW; -using namespace TW::Tezos; -using namespace TW::Tezos::Proto; +namespace TW::Tezos { + +namespace { + +constexpr const char* gTezosContractAddressPrefix{"KT1"}; + +void encodePrefix(const std::string& address, Data& forged) { + const auto decoded = Base58::bitcoin.decodeCheck(address); + constexpr auto prefixSize{3}; + forged.insert(forged.end(), decoded.begin() + prefixSize, decoded.end()); +} + +} // namespace // Forge the given boolean into a hex encoded string. Data forgeBool(bool input) { @@ -23,6 +31,39 @@ Data forgeBool(bool input) { return Data{result}; } +Data forgeInt32(int value, int len) { + Data out(len); + for (int i = len - 1; i >= 0; i--, value >>= 8) { + out[i] = (value & 0xFF); + } + return out; +} + +Data forgeString(const std::string& value, std::size_t len) { + auto bytes = data(value); + auto result = forgeInt32(static_cast(bytes.size()), static_cast(len)); + append(result, bytes); + return result; +} + +Data forgeEntrypoint(const std::string& value) { + if (value == "default") + return Data{0x00}; + else if (value == "root") + return Data{0x01}; + else if (value == "do") + return Data{0x02}; + else if (value == "set_delegate") + return Data{0x03}; + else if (value == "remove_delegate") + return Data{0x04}; + else { + Data forged{0xff}; + append(forged, forgeString(value, 1)); + return forged; + } +} + // Forge the given public key hash into a hex encoded string. // Note: This function supports tz1, tz2 and tz3 addresses. Data forgePublicKeyHash(const std::string& publicKeyHash) { @@ -41,12 +82,31 @@ Data forgePublicKeyHash(const std::string& publicKeyHash) { default: throw std::invalid_argument("Invalid Prefix"); } - const auto decoded = Base58::bitcoin.decodeCheck(publicKeyHash); - const auto prefixSize = 3; - forged.insert(forged.end(), decoded.begin() + prefixSize, decoded.end()); + encodePrefix(publicKeyHash, forged); return forged; } +Data forgeAddress(const std::string& address) { + if (address.size() < 3) { + throw std::invalid_argument("Invalid address size"); + } + auto prefix = address.substr(0, 3); + + if (prefix == "tz1" || prefix == "tz2" || prefix == "tz3") { + Data forged{0x00}; + append(forged, forgePublicKeyHash(address)); + return forged; + } + + if (prefix == gTezosContractAddressPrefix) { + Data forged{0x01}; + encodePrefix(address, forged); + forged.emplace_back(0x00); + return forged; + } + throw std::invalid_argument("Invalid Prefix"); +} + // Forge the given public key into a hex encoded string. Data forgePublicKey(PublicKey publicKey) { std::array prefix = {13, 15, 37, 217}; @@ -55,7 +115,7 @@ Data forgePublicKey(PublicKey publicKey) { append(data, bytes); auto pk = Base58::bitcoin.encodeCheck(data); - auto decoded = "00" + base58ToHex(pk, 4, prefix.data()); + auto decoded = "00" + base58ToHex(pk, 4); return parse_hex(decoded); } @@ -63,15 +123,16 @@ Data forgePublicKey(PublicKey publicKey) { Data forgeZarith(uint64_t input) { Data forged = Data(); while (input >= 0x80) { - forged.push_back(static_cast((input & 0xff) | 0x80)); + forged.push_back(static_cast((input & 0xff) | 0x80)); input >>= 7; } - forged.push_back(static_cast(input)); + forged.push_back(static_cast(input)); return forged; } // Forge the given operation. -Data forgeOperation(const Operation& operation) { +Data forgeOperation(const Proto::Operation& operation) { + using namespace Proto; auto forged = Data(); auto source = Address(operation.source()); auto forgedSource = source.forge(); @@ -83,7 +144,7 @@ Data forgeOperation(const Operation& operation) { if (operation.kind() == Operation_OperationKind_REVEAL) { auto publicKey = PublicKey(data(operation.reveal_operation_data().public_key()), TWPublicKeyTypeED25519); auto forgedPublicKey = forgePublicKey(publicKey); - + forged.push_back(Operation_OperationKind_REVEAL); append(forged, forgedSource); append(forged, forgedFee); @@ -118,18 +179,110 @@ Data forgeOperation(const Operation& operation) { auto forgedAmount = forgeZarith(operation.transaction_operation_data().amount()); auto forgedDestination = Address(operation.transaction_operation_data().destination()).forge(); - forged.push_back(Operation_OperationKind_TRANSACTION); + forged.emplace_back(Operation_OperationKind_TRANSACTION); append(forged, forgedSource); append(forged, forgedFee); append(forged, forgedCounter); append(forged, forgedGasLimit); append(forged, forgedStorageLimit); append(forged, forgedAmount); - append(forged, forgeBool(false)); - append(forged, forgedDestination); - append(forged, forgeBool(false)); + if (!operation.transaction_operation_data().has_parameters()) { + append(forged, forgeBool(false)); + append(forged, forgedDestination); + append(forged, forgeBool(false)); + } else if (operation.transaction_operation_data().has_parameters()) { + append(forged, forgeAddress(operation.transaction_operation_data().destination())); + append(forged, forgeBool(true)); + auto& parameters = operation.transaction_operation_data().parameters(); + switch (parameters.parameters_case()) { + case OperationParameters::kFa12Parameters: + append(forged, forgeEntrypoint(parameters.fa12_parameters().entrypoint())); + append(forged, forgeArray(forgeMichelson(FA12ParameterToMichelson(parameters.fa12_parameters())))); + break; + case OperationParameters::kFa2Parameters: + append(forged, forgeEntrypoint(parameters.fa2_parameters().entrypoint())); + append(forged, forgeArray(forgeMichelson(FA2ParameterToMichelson(parameters.fa2_parameters())))); + break; + case OperationParameters::PARAMETERS_NOT_SET: + break; + } + } return forged; } throw std::invalid_argument("Invalid operation kind"); } + +Data forgePrim(const PrimValue& value) { + Data forged; + if (value.prim == "Pair") { + // https://tezos.gitlab.io/developer/encodings.html?highlight=pair#pairs + forged.reserve(2); + constexpr uint8_t nbArgs = 2; + // https://github.com/ecadlabs/taquito/blob/fd84d627171d24ce7ba81dd7b18763a95f16a99c/packages/taquito-local-forging/src/michelson/codec.ts#L195 + // https://github.com/baking-bad/netezos/blob/0bfd6db4e85ab1c99fb55503e476fe67cebd2dc5/Netezos/Forging/Local/LocalForge.Forgers.cs#L199 + const uint8_t preamble = static_cast(std::min(2 * nbArgs + static_cast(value.anots.size()) + 0x03, 9)); + forged.emplace_back(preamble); + forged.emplace_back(PrimType::Pair); + Data subForged; + for (auto&& cur : value.args) { + append(subForged, forgeMichelson(cur.value)); + } + append(forged, subForged); + } + return forged; +} + +Data forgeMichelson(const MichelsonValue::MichelsonVariant& value) { + auto visit_functor = [](const MichelsonValue::MichelsonVariant& value) -> Data { + if (std::holds_alternative(value)) { + return forgePrim(std::get(value)); + } else if (std::holds_alternative(value)) { + Data forged{1}; + append(forged, forgeString(std::get(value).string)); + return forged; + } else if (std::holds_alternative(value)) { + Data forged{0}; + auto res = int256_t(std::get(value)._int); + append(forged, forgeMichelInt(res)); + return forged; + } else if (std::holds_alternative(value)) { + return {}; + } else if (std::holds_alternative(value)) { + // array + Data forged{2}; + Data subForged; + auto array = std::get(value); + for (auto&& cur : array) { + std::visit([&subForged](auto&& arg) { append(subForged, forgeMichelson(arg)); }, cur); + } + append(forged, forgeArray(subForged)); + return forged; + } else { + throw std::invalid_argument("Invalid variant"); + } + }; + + return std::visit(visit_functor, value); +} + +Data forgeArray(const Data& data) { + auto forged = forgeInt32(static_cast(data.size())); + append(forged, data); + return forged; +} + +Data forgeMichelInt(const TW::int256_t& value) { + Data forged; + auto abs = boost::multiprecision::abs(value); + forged.emplace_back(static_cast(value.sign() < 0 ? (abs & 0x3f - 0x40) : (abs & 0x3f))); + abs >>= 6; + while (abs > 0) { + forged[forged.size() - 1] |= 0x80; + forged.emplace_back(static_cast(abs & 0x7F)); + abs >>= 7; + } + return forged; +} + +} // namespace TW::Tezos diff --git a/src/Tezos/Forging.h b/src/Tezos/Forging.h index 45f4624e282..721546db1e9 100644 --- a/src/Tezos/Forging.h +++ b/src/Tezos/Forging.h @@ -1,19 +1,36 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +#pragma once + +#include "Michelson.h" +#include "uint256.h" #include "../PublicKey.h" #include "../proto/Tezos.pb.h" #include +#include +#include using namespace TW; -using namespace TW::Tezos::Proto; + +namespace TW::Tezos { Data forgeBool(bool input); -Data forgeOperation(const Operation& operation); +Data forgeOperation(const Proto::Operation& operation); +Data forgeAddress(const std::string& address); +Data forgeArray(const Data& data); Data forgePublicKeyHash(const std::string& publicKeyHash); Data forgePublicKey(PublicKey publicKey); Data forgeZarith(uint64_t input); +Data forgeInt32(int value, int len = 4); +Data forgeString(const std::string& value, std::size_t len = 4); +Data forgeEntrypoint(const std::string& value); +Data forgeMichelson(const MichelsonValue::MichelsonVariant& value); +Data forgeMichelInt(const TW::int256_t& value); +Data forgePrim(const PrimValue& value); + +} // namespace TW::Tezos diff --git a/src/Tezos/Michelson.cpp b/src/Tezos/Michelson.cpp new file mode 100644 index 00000000000..0b573885ebd --- /dev/null +++ b/src/Tezos/Michelson.cpp @@ -0,0 +1,32 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Michelson.h" + +namespace TW::Tezos { + +MichelsonValue::MichelsonVariant FA12ParameterToMichelson(const Proto::FA12Parameters& data) { + MichelsonValue::MichelsonVariant address = StringValue{.string = data.from()}; + MichelsonValue::MichelsonVariant to = StringValue{.string = data.to()}; + MichelsonValue::MichelsonVariant amount = IntValue{._int = data.value()}; + auto primTransferInfos = PrimValue{.prim = "Pair", .args{{to}, {amount}}}; + return PrimValue{.prim = "Pair", .args{{address}, {primTransferInfos}}}; +} + +MichelsonValue::MichelsonVariant FA2ParameterToMichelson(const Proto::FA2Parameters& data) { + auto& txObj = *data.txs_object().begin(); + MichelsonValue::MichelsonVariant from = StringValue{.string = txObj.from()}; + auto& txTransferInfos = txObj.txs(0); + MichelsonValue::MichelsonVariant tokenId = IntValue{._int = txTransferInfos.token_id()}; + MichelsonValue::MichelsonVariant amount = IntValue{._int = txTransferInfos.amount()}; + auto primTransferInfos = PrimValue{.prim = "Pair", .args{{tokenId}, {amount}}}; + MichelsonValue::MichelsonVariant to = StringValue{.string = txTransferInfos.to()}; + MichelsonValue::MichelsonVariant txs = MichelsonValue::MichelsonArray{PrimValue{.prim = "Pair", .args{{to}, {primTransferInfos}}}}; + auto primTxs = PrimValue{.prim = "Pair", .args{{from}, {txs}}}; + return MichelsonValue::MichelsonArray{primTxs}; +} + +} // namespace TW::Tezos diff --git a/src/Tezos/Michelson.h b/src/Tezos/Michelson.h new file mode 100644 index 00000000000..1e6d944c59d --- /dev/null +++ b/src/Tezos/Michelson.h @@ -0,0 +1,56 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include +#include +#include + +#include "../proto/Tezos.pb.h" + +#pragma once + +namespace TW::Tezos { + +enum PrimType : std::uint8_t { + Pair = 7, +}; + +struct MichelsonValue; + +struct PrimValue { + std::string prim; + std::vector args; + std::vector anots; +}; + +struct BytesValue { + std::string bytes; +}; + +struct StringValue { + std::string string; +}; + +struct IntValue { + std::string _int; +}; + +struct MichelsonValue { + using MichelsonArray = std::vector>; + using MichelsonVariant = std::variant< + PrimValue, + BytesValue, + StringValue, + IntValue, + MichelsonArray>; + MichelsonVariant value; +}; + +MichelsonValue::MichelsonVariant FA12ParameterToMichelson(const Proto::FA12Parameters& data); +MichelsonValue::MichelsonVariant FA2ParameterToMichelson(const Proto::FA2Parameters& data); + +} // namespace TW::Tezos diff --git a/src/Tezos/OperationList.cpp b/src/Tezos/OperationList.cpp index ff7780ed385..fc2e7ed3897 100644 --- a/src/Tezos/OperationList.cpp +++ b/src/Tezos/OperationList.cpp @@ -1,19 +1,14 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. #include "OperationList.h" -#include "BinaryCoding.h" #include "Forging.h" -#include "HexCoding.h" #include "../Base58.h" -#include "../proto/Tezos.pb.h" -using namespace TW; -using namespace TW::Tezos; -using namespace TW::Tezos::Proto; +namespace TW::Tezos { Tezos::OperationList::OperationList(const std::string& str) { branch = str; @@ -53,3 +48,5 @@ Data Tezos::OperationList::forge(const PrivateKey& privateKey) const { return forged; } + +} // namespace TW::Tezos \ No newline at end of file diff --git a/src/Tezos/OperationList.h b/src/Tezos/OperationList.h index f7ddd5615ed..a6f3a4a9f70 100644 --- a/src/Tezos/OperationList.h +++ b/src/Tezos/OperationList.h @@ -1,15 +1,20 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + #pragma once -#include "../Data.h" +#include "Data.h" #include "proto/Tezos.pb.h" #include "../PrivateKey.h" #include -using namespace TW::Tezos; -using namespace TW::Tezos::Proto; - namespace TW::Tezos { +using TW::Tezos::Proto::Operation; + class OperationList { public: std::string branch; diff --git a/src/Tezos/Signer.cpp b/src/Tezos/Signer.cpp index b0732fd78c9..248df7fd115 100644 --- a/src/Tezos/Signer.cpp +++ b/src/Tezos/Signer.cpp @@ -1,12 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "OperationList.h" #include "Signer.h" -#include "../Hash.h" +#include "OperationList.h" #include "../HexCoding.h" #include @@ -15,12 +14,13 @@ #include using namespace TW; -using namespace TW::Tezos; + +namespace TW::Tezos { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto operationList = Tezos::OperationList(input.operation_list().branch()); for (Proto::Operation operation : input.operation_list().operations()) { - operationList.addOperation(operation); + operationList.addOperation(operation); } auto signer = Signer(); @@ -58,3 +58,5 @@ Data Signer::signData(const PrivateKey& privateKey, const Data& data) { append(signedData, signature); return signedData; } + +} // namespace TW::Tezos diff --git a/src/Tezos/Signer.h b/src/Tezos/Signer.h index 798d703ab21..a6e16cf4733 100644 --- a/src/Tezos/Signer.h +++ b/src/Tezos/Signer.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -7,7 +7,7 @@ #pragma once #include "OperationList.h" -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" #include "../proto/Tezos.pb.h" diff --git a/src/Theta/Entry.cpp b/src/Theta/Entry.cpp index 8e391ec086b..634a661a059 100644 --- a/src/Theta/Entry.cpp +++ b/src/Theta/Entry.cpp @@ -9,22 +9,10 @@ #include "Ethereum/Address.h" #include "Signer.h" -using namespace TW::Theta; -using namespace std; +namespace TW::Theta { -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { - return Ethereum::Address::isValid(address); -} - -string Entry::normalizeAddress(TWCoinType coin, const string& address) const { - // normalized with EIP55 checksum - return Ethereum::Address(address).string(); -} - -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { - return Ethereum::Address(publicKey).string(); -} - -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +} // namespace TW::Theta diff --git a/src/Theta/Entry.h b/src/Theta/Entry.h index 90d33acdf14..429190dee81 100644 --- a/src/Theta/Entry.h +++ b/src/Theta/Entry.h @@ -7,18 +7,15 @@ #pragma once #include "../CoinEntry.h" +#include "Ethereum/Entry.h" namespace TW::Theta { /// Entry point for Theta. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public Ethereum::Entry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeTheta}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string normalizeAddress(TWCoinType coin, const std::string& address) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::Theta diff --git a/src/Theta/Signer.cpp b/src/Theta/Signer.cpp index e2bc16b1188..c50c74f264e 100755 --- a/src/Theta/Signer.cpp +++ b/src/Theta/Signer.cpp @@ -9,9 +9,9 @@ #include "../Ethereum/RLP.h" #include "../Hash.h" -using namespace TW; -using namespace TW::Theta; -using RLP = Ethereum::RLP; +using RLP = TW::Ethereum::RLP; + +namespace TW::Theta { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto pkFrom = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); @@ -66,3 +66,5 @@ Data Signer::sign(const PrivateKey& privateKey, const Transaction& transaction) auto signature = privateKey.sign(hash, TWCurveSECP256k1); return signature; } + +} // namespace TW::Theta diff --git a/src/Theta/Signer.h b/src/Theta/Signer.h index 8b16c4957f5..e4dbe975fd4 100644 --- a/src/Theta/Signer.h +++ b/src/Theta/Signer.h @@ -9,7 +9,7 @@ #include #include "Transaction.h" -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" #include "../proto/Theta.pb.h" @@ -37,8 +37,3 @@ class Signer { }; } // namespace TW::Theta - -/// Wrapper for C interface. -struct TWThetaSigner { - TW::Theta::Signer impl; -}; diff --git a/src/Theta/Transaction.cpp b/src/Theta/Transaction.cpp index 7cfa42e3ad2..380f30084ea 100644 --- a/src/Theta/Transaction.cpp +++ b/src/Theta/Transaction.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -8,8 +8,8 @@ #include "../Ethereum/RLP.h" -using namespace TW; -using namespace TW::Theta; +namespace TW::Theta { + using RLP = Ethereum::RLP; Data encode(const Coins& coins) noexcept { @@ -52,15 +52,15 @@ Data encode(const std::vector& outputs) noexcept { } Transaction::Transaction(Ethereum::Address from, Ethereum::Address to, - uint256_t thetaAmount, uint256_t tfuelAmount, - uint64_t sequence, uint256_t feeAmount /* = 1000000000000*/) { + const uint256_t& thetaAmount, const uint256_t& tfuelAmount, + uint64_t sequence, const uint256_t& feeAmount /* = 1000000000000*/) { auto fee = Coins(0, feeAmount); auto coinsInput = Coins(thetaAmount, tfuelAmount + feeAmount); auto coinsOutput = Coins(thetaAmount, tfuelAmount); - auto input = TxInput(std::move(from), coinsInput, sequence); - auto output = TxOutput(std::move(to), coinsOutput); + auto input = TxInput(from, coinsInput, sequence); + auto output = TxOutput(to, coinsOutput); - this->fee = fee; + this->_fee = fee; this->inputs.push_back(input); this->outputs.push_back(output); } @@ -70,9 +70,9 @@ Data Transaction::encode() const noexcept { uint16_t txType = 2; // TxSend append(encoded, RLP::encode(txType)); auto encodedData = Data(); - append(encodedData, ::encode(fee)); - append(encodedData, ::encode(inputs)); - append(encodedData, ::encode(outputs)); + append(encodedData, Theta::encode(_fee)); + append(encodedData, Theta::encode(inputs)); + append(encodedData, Theta::encode(outputs)); append(encoded, RLP::encodeList(encodedData)); return encoded; } @@ -86,3 +86,5 @@ bool Transaction::setSignature(const Ethereum::Address& address, const Data& sig } return false; } + +} // namespace TW::Theta diff --git a/src/Theta/Transaction.h b/src/Theta/Transaction.h index 735c21ab1a8..20edd2d0de6 100644 --- a/src/Theta/Transaction.h +++ b/src/Theta/Transaction.h @@ -10,7 +10,7 @@ #include #include "Coins.h" -#include "../Data.h" +#include "Data.h" #include "../Ethereum/Address.h" namespace TW::Theta { @@ -39,17 +39,17 @@ class TxOutput { class Transaction { public: - Coins fee; + Coins _fee; std::vector inputs; std::vector outputs; Transaction() = default; Transaction(Coins fee, std::vector inputs, std::vector outputs) - : fee(std::move(fee)), inputs(std::move(inputs)), outputs(std::move(outputs)) {} + : _fee(std::move(fee)), inputs(std::move(inputs)), outputs(std::move(outputs)) {} Transaction(Ethereum::Address from, Ethereum::Address to, - uint256_t thetaAmount, uint256_t tfuelAmount, uint64_t sequence, - uint256_t feeAmount = 1000000000000); + const uint256_t& thetaAmount, const uint256_t& tfuelAmount, uint64_t sequence, + const uint256_t& feeAmount = 1000000000000); /// Encodes the transaction Data encode() const noexcept; diff --git a/src/TransactionCompiler.cpp b/src/TransactionCompiler.cpp new file mode 100644 index 00000000000..2f725ab6780 --- /dev/null +++ b/src/TransactionCompiler.cpp @@ -0,0 +1,38 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "TransactionCompiler.h" + +#include "Coin.h" + +using namespace TW; + + +Data TransactionCompiler::buildInput(TWCoinType coinType, const std::string& from, const std::string& to, const std::string& amount, const std::string& asset, const std::string& memo, const std::string& chainId) { + // parse amount + uint256_t amount256 { amount }; + return anyCoinBuildTransactionInput(coinType, from, to, amount256, asset, memo, chainId); +} + +Data TransactionCompiler::preImageHashes(TWCoinType coinType, const Data& txInputData) { + return anyCoinPreImageHashes(coinType, txInputData); +} + +Data TransactionCompiler::compileWithSignatures(TWCoinType coinType, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys) { + // input parameter conversion + const auto publicKeyType = ::publicKeyType(coinType); + std::vector pubs; + for (auto& p: publicKeys) { + if (!PublicKey::isValid(p, publicKeyType)) { + throw std::invalid_argument("Invalid public key"); + } + pubs.emplace_back(p, publicKeyType); + } + + Data txOutput; + anyCoinCompileWithSignatures(coinType, txInputData, signatures, pubs, txOutput); + return txOutput; +} diff --git a/src/TransactionCompiler.h b/src/TransactionCompiler.h new file mode 100644 index 00000000000..21b968717b6 --- /dev/null +++ b/src/TransactionCompiler.h @@ -0,0 +1,34 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include +#include "Data.h" +#include "CoinEntry.h" + +#include +#include + +namespace TW { + +/// Non-core transaction utility methods, like building a transaction using an external signature +class TransactionCompiler { +public: + /// Build a coin-specific SigningInput protobuf transaction input, from simple transaction parameters + static Data buildInput(TWCoinType coinType, const std::string& from, const std::string& to, const std::string& amount, const std::string& asset, const std::string& memo, const std::string& chainId); + + /// Obtain pre-signing hash of a transaction. + /// It will return a proto object named `PreSigningOutput` which will include hash. + /// We provide a default `PreSigningOutput` in TransactionCompiler.proto. + /// For some special coins, such as bitcoin, we will create a custom `PreSigningOutput` object in its proto file. + static Data preImageHashes(TWCoinType coinType, const Data& txInputData); + + /// Compile a complete transation with an external signature, put together from transaction input and provided public key and signature + static Data compileWithSignatures(TWCoinType coinType, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys); +}; + +} // namespace TW diff --git a/src/Tron/Address.cpp b/src/Tron/Address.cpp index 7fdac65b817..a67ea8fd1c2 100644 --- a/src/Tron/Address.cpp +++ b/src/Tron/Address.cpp @@ -12,7 +12,7 @@ #include #include -using namespace TW::Tron; +namespace TW::Tron { bool Address::isValid(const std::string& string) { const auto decoded = Base58::bitcoin.decodeCheck(string); @@ -36,3 +36,5 @@ Address::Address(const PublicKey& publicKey) { bytes[0] = prefix; std::copy(keyhash.end() - size + 1, keyhash.end(), bytes.begin() + 1); } + +} // namespace TW::Tron diff --git a/src/Tron/Address.h b/src/Tron/Address.h index 7577171a5b3..2c7a37128ee 100644 --- a/src/Tron/Address.h +++ b/src/Tron/Address.h @@ -7,7 +7,7 @@ #pragma once #include "../Base58Address.h" -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include diff --git a/src/Tron/Entry.cpp b/src/Tron/Entry.cpp index 0f69e92d865..056728c4bc6 100644 --- a/src/Tron/Entry.cpp +++ b/src/Tron/Entry.cpp @@ -9,19 +9,21 @@ #include "Address.h" #include "Signer.h" -using namespace TW::Tron; using namespace std; +namespace TW::Tron { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +} // namespace TW::Tron diff --git a/src/Tron/Entry.h b/src/Tron/Entry.h index 0e13d37855e..0d25c65da8b 100644 --- a/src/Tron/Entry.h +++ b/src/Tron/Entry.h @@ -12,12 +12,11 @@ namespace TW::Tron { /// Entry point for implementation of Tron coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeTron}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::Tron diff --git a/src/Tron/Serialization.cpp b/src/Tron/Serialization.cpp index a753e52e5d9..36bfe251f69 100644 --- a/src/Tron/Serialization.cpp +++ b/src/Tron/Serialization.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -10,17 +10,15 @@ #include #include -using namespace TW; -using namespace TW::Tron; -using namespace std; +namespace TW::Tron { using json = nlohmann::json; -string typeName(const protocol::Transaction::Contract::ContractType type) { +std::string typeName(const protocol::Transaction::Contract::ContractType type) { return protocol::Transaction::Contract::ContractType_Name(type); } -string typeUrl(const protocol::Transaction::Contract::ContractType type) { +std::string typeUrl(const protocol::Transaction::Contract::ContractType type) { std::ostringstream stringStream; stringStream << "type.googleapis.com/protocol." << typeName(type); return stringStream.str(); @@ -47,9 +45,9 @@ json valueJSON(const protocol::TransferAssetContract& contract) { json valueJSON(const protocol::VoteAssetContract& contract) { json valueJSON; - - vector vote_address; - for (const string& addr : contract.vote_address()) { + + std::vector vote_address; + for (const std::string& addr : contract.vote_address()) { vote_address.push_back(hex(addr)); } @@ -57,7 +55,7 @@ json valueJSON(const protocol::VoteAssetContract& contract) { valueJSON["vote_address"] = vote_address; valueJSON["support"] = contract.support(); valueJSON["count"] = contract.count(); - + return valueJSON; } @@ -72,7 +70,7 @@ json voteJSON(const protocol::VoteWitnessContract::Vote& vote) { json valueJSON(const protocol::VoteWitnessContract& contract) { json valueJSON; - vector votes; + std::vector votes; for (const protocol::VoteWitnessContract::Vote& vote : contract.votes()) { votes.push_back(voteJSON(vote)); } @@ -136,81 +134,81 @@ json valueJSON(const protocol::TriggerSmartContract& contract) { return valueJSON; } -json parameterJSON(const google::protobuf::Any ¶meter, const protocol::Transaction::Contract::ContractType type) { +json parameterJSON(const google::protobuf::Any& parameter, const protocol::Transaction::Contract::ContractType type) { json paramJSON; paramJSON["type_url"] = typeUrl(type); switch (type) { - case protocol::Transaction::Contract::TransferContract: { - protocol::TransferContract contract; - parameter.UnpackTo(&contract); - paramJSON["value"] = valueJSON(contract); - break; - } - case protocol::Transaction::Contract::TransferAssetContract: { - protocol::TransferAssetContract contract; - parameter.UnpackTo(&contract); - paramJSON["value"] = valueJSON(contract); - break; - } - case protocol::Transaction::Contract::VoteAssetContract: { - protocol::VoteAssetContract contract; - parameter.UnpackTo(&contract); - paramJSON["value"] = valueJSON(contract); - break; - } - case protocol::Transaction::Contract::VoteWitnessContract: { - protocol::VoteWitnessContract contract; - parameter.UnpackTo(&contract); - paramJSON["value"] = valueJSON(contract); - break; - } - case protocol::Transaction::Contract::FreezeBalanceContract: { - protocol::FreezeBalanceContract contract; - parameter.UnpackTo(&contract); - paramJSON["value"] = valueJSON(contract); - break; - } - case protocol::Transaction::Contract::UnfreezeBalanceContract: { - protocol::UnfreezeBalanceContract contract; - parameter.UnpackTo(&contract); - paramJSON["value"] = valueJSON(contract); - break; - } - case protocol::Transaction::Contract::WithdrawBalanceContract: { - protocol::WithdrawBalanceContract contract; - parameter.UnpackTo(&contract); - paramJSON["value"] = valueJSON(contract); - break; - } - case protocol::Transaction::Contract::UnfreezeAssetContract: { - protocol::UnfreezeAssetContract contract; - parameter.UnpackTo(&contract); - paramJSON["value"] = valueJSON(contract); - break; - } - case protocol::Transaction::Contract::TriggerSmartContract: { - protocol::TriggerSmartContract contract; - parameter.UnpackTo(&contract); - paramJSON["value"] = valueJSON(contract); - break; - } - case protocol::Transaction::Contract::AccountCreateContract: - default: - break; + case protocol::Transaction::Contract::TransferContract: { + protocol::TransferContract contract; + parameter.UnpackTo(&contract); + paramJSON["value"] = valueJSON(contract); + break; + } + case protocol::Transaction::Contract::TransferAssetContract: { + protocol::TransferAssetContract contract; + parameter.UnpackTo(&contract); + paramJSON["value"] = valueJSON(contract); + break; + } + case protocol::Transaction::Contract::VoteAssetContract: { + protocol::VoteAssetContract contract; + parameter.UnpackTo(&contract); + paramJSON["value"] = valueJSON(contract); + break; + } + case protocol::Transaction::Contract::VoteWitnessContract: { + protocol::VoteWitnessContract contract; + parameter.UnpackTo(&contract); + paramJSON["value"] = valueJSON(contract); + break; + } + case protocol::Transaction::Contract::FreezeBalanceContract: { + protocol::FreezeBalanceContract contract; + parameter.UnpackTo(&contract); + paramJSON["value"] = valueJSON(contract); + break; + } + case protocol::Transaction::Contract::UnfreezeBalanceContract: { + protocol::UnfreezeBalanceContract contract; + parameter.UnpackTo(&contract); + paramJSON["value"] = valueJSON(contract); + break; + } + case protocol::Transaction::Contract::WithdrawBalanceContract: { + protocol::WithdrawBalanceContract contract; + parameter.UnpackTo(&contract); + paramJSON["value"] = valueJSON(contract); + break; + } + case protocol::Transaction::Contract::UnfreezeAssetContract: { + protocol::UnfreezeAssetContract contract; + parameter.UnpackTo(&contract); + paramJSON["value"] = valueJSON(contract); + break; + } + case protocol::Transaction::Contract::TriggerSmartContract: { + protocol::TriggerSmartContract contract; + parameter.UnpackTo(&contract); + paramJSON["value"] = valueJSON(contract); + break; + } + case protocol::Transaction::Contract::AccountCreateContract: + default: + break; } return paramJSON; } -json contractJSON(const protocol::Transaction::Contract &contract) { +json contractJSON(const protocol::Transaction::Contract& contract) { json contractJSON; contractJSON["type"] = typeName(contract.type()); contractJSON["parameter"] = parameterJSON(contract.parameter(), contract.type()); return contractJSON; } -json raw_dataJSON(const protocol::Transaction::raw &raw) { +json raw_dataJSON(const protocol::Transaction::raw& raw) { json raw_dataJSON; raw_dataJSON["ref_block_bytes"] = hex(raw.ref_block_bytes()); @@ -223,16 +221,18 @@ json raw_dataJSON(const protocol::Transaction::raw &raw) { } raw_dataJSON["timestamp"] = raw.timestamp(); raw_dataJSON["expiration"] = raw.expiration(); - raw_dataJSON["contract"] = json::array({ contractJSON(raw.contract(0)) }); + raw_dataJSON["contract"] = json::array({contractJSON(raw.contract(0))}); return raw_dataJSON; } -json TW::Tron::transactionJSON(const protocol::Transaction& transaction, const TW::Data& txID, const TW::Data& signature) { +json transactionJSON(const protocol::Transaction& transaction, const TW::Data& txID, const TW::Data& signature) { json transactionJSON; transactionJSON["raw_data"] = raw_dataJSON(transaction.raw_data()); transactionJSON["txID"] = hex(txID); - transactionJSON["signature"] = json::array({ hex(signature) }); + transactionJSON["signature"] = json::array({hex(signature)}); return transactionJSON; } + +} // namespace TW::Tron diff --git a/src/Tron/Serialization.h b/src/Tron/Serialization.h index 13181c7ac47..7f3c074cfd5 100644 --- a/src/Tron/Serialization.h +++ b/src/Tron/Serialization.h @@ -7,7 +7,7 @@ #pragma once #include "./Protobuf/TronInternal.pb.h" -#include "../Data.h" +#include "Data.h" #include namespace TW::Tron { diff --git a/src/Tron/Signer.cpp b/src/Tron/Signer.cpp index 2680046135f..87ed70b30f5 100644 --- a/src/Tron/Signer.cpp +++ b/src/Tron/Signer.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -10,21 +10,16 @@ #include "../Base58.h" #include "../BinaryCoding.h" -#include "../Hash.h" #include "../HexCoding.h" #include "Serialization.h" #include #include -using namespace TW; -using namespace TW::Tron; -using namespace std::chrono; +namespace TW::Tron { const std::string TRANSFER_TOKEN_FUNCTION = "0xa9059cbb"; -size_t base58Capacity = 128; - /// Converts an external TransferContract to an internal one used for signing. protocol::TransferContract to_internal(const Proto::TransferContract& transfer) { auto internal = protocol::TransferContract(); @@ -106,7 +101,7 @@ protocol::VoteAssetContract to_internal(const Proto::VoteAssetContract& voteCont internal.set_owner_address(ownerAddress.data(), ownerAddress.size()); internal.set_support(voteContract.support()); internal.set_count(voteContract.count()); - for(int i = 0; i < voteContract.vote_address_size(); i++) { + for (int i = 0; i < voteContract.vote_address_size(); i++) { auto voteAddress = Base58::bitcoin.decodeCheck(voteContract.vote_address(i)); internal.add_vote_address(voteAddress.data(), voteAddress.size()); } @@ -120,7 +115,7 @@ protocol::VoteWitnessContract to_internal(const Proto::VoteWitnessContract& vote internal.set_owner_address(ownerAddress.data(), ownerAddress.size()); internal.set_support(voteContract.support()); - for(int i = 0; i < voteContract.votes_size(); i++) { + for (int i = 0; i < voteContract.votes_size(); i++) { auto voteAddress = Base58::bitcoin.decodeCheck(voteContract.votes(i).vote_address()); auto* vote = internal.add_votes(); @@ -293,15 +288,15 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { } // Get default timestamp and expiration - const uint64_t now = duration_cast< milliseconds >( - system_clock::now().time_since_epoch() - ).count(); + const uint64_t now = duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); const uint64_t timestamp = input.transaction().timestamp() == 0 - ? now - : input.transaction().timestamp(); + ? now + : input.transaction().timestamp(); const uint64_t expiration = input.transaction().expiration() == 0 - ? timestamp + 10 * 60 * 60 * 1000 // 10 hours - : input.transaction().expiration(); + ? timestamp + 10 * 60 * 60 * 1000 // 10 hours + : input.transaction().expiration(); internal.mutable_raw_data()->set_timestamp(timestamp); internal.mutable_raw_data()->set_expiration(expiration); @@ -325,3 +320,5 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { return output; } + +} // namespace TW::Tron diff --git a/src/Tron/Signer.h b/src/Tron/Signer.h index 2336c3c8fc2..33a06fff775 100644 --- a/src/Tron/Signer.h +++ b/src/Tron/Signer.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" #include "../proto/Tron.pb.h" diff --git a/src/VeChain/Clause.h b/src/VeChain/Clause.h index 9bbb1bc6435..71d56d7d785 100644 --- a/src/VeChain/Clause.h +++ b/src/VeChain/Clause.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "../Ethereum/Address.h" #include "../proto/VeChain.pb.h" #include "../uint256.h" diff --git a/src/VeChain/Entry.cpp b/src/VeChain/Entry.cpp index 96a0ef25dab..0a01f790321 100644 --- a/src/VeChain/Entry.cpp +++ b/src/VeChain/Entry.cpp @@ -9,22 +9,10 @@ #include "Ethereum/Address.h" #include "Signer.h" -using namespace TW::VeChain; -using namespace std; - -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { - return Ethereum::Address::isValid(address); -} - -string Entry::normalizeAddress(TWCoinType coin, const string& address) const { - // normalized with EIP55 checksum - return Ethereum::Address(address).string(); -} - -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { - return Ethereum::Address(publicKey).string(); -} - -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +namespace TW::VeChain { + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +} // namespace TW::VeChain diff --git a/src/VeChain/Entry.h b/src/VeChain/Entry.h index b856f4ebe28..48ec8b5db78 100644 --- a/src/VeChain/Entry.h +++ b/src/VeChain/Entry.h @@ -7,18 +7,15 @@ #pragma once #include "../CoinEntry.h" +#include "Ethereum/Entry.h" namespace TW::VeChain { /// Entry point for VeChain. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public Ethereum::Entry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeVeChain}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string normalizeAddress(TWCoinType coin, const std::string& address) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::VeChain diff --git a/src/VeChain/Signer.cpp b/src/VeChain/Signer.cpp index 3b12d7818bd..7cbfd00d333 100644 --- a/src/VeChain/Signer.cpp +++ b/src/VeChain/Signer.cpp @@ -9,7 +9,8 @@ #include "../Hash.h" using namespace TW; -using namespace TW::VeChain; + +namespace TW::VeChain { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); @@ -41,3 +42,5 @@ Data Signer::sign(const PrivateKey& privateKey, Transaction& transaction) noexce auto signature = privateKey.sign(hash, TWCurveSECP256k1); return Data(signature.begin(), signature.end()); } + +} // namespace TW::VeChain diff --git a/src/VeChain/Signer.h b/src/VeChain/Signer.h index 5b193a3385d..f0a2a92d22b 100644 --- a/src/VeChain/Signer.h +++ b/src/VeChain/Signer.h @@ -8,7 +8,7 @@ #include "Transaction.h" -#include "../Data.h" +#include "Data.h" #include "../Hash.h" #include "../PrivateKey.h" @@ -32,8 +32,3 @@ class Signer { }; } // namespace TW::VeChain - -/// Wrapper for C interface. -struct TWVeChainSigner { - TW::VeChain::Signer impl; -}; diff --git a/src/VeChain/Transaction.cpp b/src/VeChain/Transaction.cpp index 383531bf8a5..aeca93d352f 100644 --- a/src/VeChain/Transaction.cpp +++ b/src/VeChain/Transaction.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -8,8 +8,8 @@ #include "../Ethereum/RLP.h" -using namespace TW; -using namespace TW::VeChain; +namespace TW::VeChain { + using RLP = Ethereum::RLP; Data encode(const Clause& clause) noexcept { @@ -45,3 +45,5 @@ Data Transaction::encode() const noexcept { } return RLP::encodeList(encoded); } + +} // namespace TW::VeChain diff --git a/src/VeChain/Transaction.h b/src/VeChain/Transaction.h index ba905028550..c4229b795d3 100644 --- a/src/VeChain/Transaction.h +++ b/src/VeChain/Transaction.h @@ -7,7 +7,7 @@ #pragma once #include "Clause.h" -#include "../Data.h" +#include "Data.h" #include #include diff --git a/src/Wasm.h b/src/Wasm.h new file mode 100644 index 00000000000..afb9c67aa88 --- /dev/null +++ b/src/Wasm.h @@ -0,0 +1,19 @@ +// 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. + +#ifndef __USE_WASM +#define __USE_WASM +#endif // __USE_WASM + +#ifndef __USE_MISC +#ifdef __USE_WASM + +typedef unsigned long int ulong; +typedef unsigned short int ushort; +typedef unsigned int uint; + +#endif // __USE_WASM +#endif // __USE_MISC diff --git a/src/Waves/Address.cpp b/src/Waves/Address.cpp index 685f4327fb5..2f5ad758be7 100644 --- a/src/Waves/Address.cpp +++ b/src/Waves/Address.cpp @@ -7,7 +7,7 @@ #include "Address.h" #include "../Base58.h" -#include "../Data.h" +#include "Data.h" #include "../Hash.h" #include @@ -15,8 +15,7 @@ #include #include -using namespace TW; -using namespace TW::Waves; +namespace TW::Waves { template Data Address::secureHash(const T &data) { @@ -40,8 +39,6 @@ bool Address::isValid(const Data& decoded) { const auto data_checksum = Data(decoded.end() - 4, decoded.end()); const auto calculated_hash = secureHash(data); const auto calculated_checksum = Data(calculated_hash.begin(), calculated_hash.begin() + 4); - const auto h = hex(data); - const auto h2 = hex(calculated_hash); return std::memcmp(data_checksum.data(), calculated_checksum.data(), 4) == 0; } @@ -83,4 +80,6 @@ Address::Address(const PublicKey &publicKey) { std::string Address::string() const { return Base58::bitcoin.encode(bytes); -} \ No newline at end of file +} + +} // namespace TW::Waves diff --git a/src/Waves/Address.h b/src/Waves/Address.h index df518415334..fed1743c9de 100644 --- a/src/Waves/Address.h +++ b/src/Waves/Address.h @@ -7,7 +7,7 @@ #pragma once #include "../Base58Address.h" -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include diff --git a/src/Waves/Entry.cpp b/src/Waves/Entry.cpp index 6162485901d..9635e2d30cd 100644 --- a/src/Waves/Entry.cpp +++ b/src/Waves/Entry.cpp @@ -9,19 +9,22 @@ #include "Address.h" #include "Signer.h" -using namespace TW::Waves; using namespace std; +namespace TW::Waves { + // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +} // namespace TW::Waves diff --git a/src/Waves/Entry.h b/src/Waves/Entry.h index 8418548c2b6..ea114e04c45 100644 --- a/src/Waves/Entry.h +++ b/src/Waves/Entry.h @@ -12,12 +12,11 @@ namespace TW::Waves { /// Entry point for implementation of Waves coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeWaves}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::Waves diff --git a/src/Waves/Signer.cpp b/src/Waves/Signer.cpp index 701d47e869b..93716de50d4 100644 --- a/src/Waves/Signer.cpp +++ b/src/Waves/Signer.cpp @@ -9,7 +9,8 @@ #include "../Hash.h" using namespace TW; -using namespace TW::Waves; + +namespace TW::Waves { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); @@ -19,12 +20,12 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { Data signature = Signer::sign(privateKey, transaction); Proto::SigningOutput output = Proto::SigningOutput(); - output.set_signature(reinterpret_cast(signature.data()), signature.size()); + output.set_signature(reinterpret_cast(signature.data()), signature.size()); output.set_json(transaction.buildJson(signature).dump()); return output; } -Data Signer::sign(const PrivateKey &privateKey, Transaction &transaction) noexcept { +Data Signer::sign(const PrivateKey& privateKey, Transaction& transaction) noexcept { try { auto bytesToSign = transaction.serializeToSign(); auto signature = privateKey.sign(bytesToSign, TWCurveCurve25519); @@ -33,3 +34,5 @@ Data Signer::sign(const PrivateKey &privateKey, Transaction &transaction) noexce return Data(); } } + +} // namespace TW::Waves diff --git a/src/Waves/Signer.h b/src/Waves/Signer.h index 4540029e28c..f7504d61132 100644 --- a/src/Waves/Signer.h +++ b/src/Waves/Signer.h @@ -7,7 +7,7 @@ #pragma once #include "Transaction.h" -#include "../Data.h" +#include "Data.h" #include "../Hash.h" #include "../PrivateKey.h" #include "../proto/Waves.pb.h" diff --git a/src/Waves/Transaction.cpp b/src/Waves/Transaction.cpp index ee9d2a5af44..36ac5cb3447 100644 --- a/src/Waves/Transaction.cpp +++ b/src/Waves/Transaction.cpp @@ -11,7 +11,8 @@ #include "../HexCoding.h" using namespace TW; -using namespace TW::Waves; + +namespace TW::Waves { using json = nlohmann::json; @@ -20,7 +21,7 @@ const std::string Transaction::WAVES = "WAVES"; Data serializeTransfer(int64_t amount, std::string asset, int64_t fee, std::string fee_asset, Address to, const Data& attachment, int64_t timestamp, const Data& pub_key) { auto data = Data(); if (asset.empty()) { - asset = Transaction::WAVES; + asset = Transaction::WAVES; } if (fee_asset.empty()) { fee_asset = Transaction::WAVES; @@ -46,7 +47,7 @@ Data serializeTransfer(int64_t amount, std::string asset, int64_t fee, std::stri encode64BE(fee, data); append(data, Data(std::begin(to.bytes), std::end(to.bytes))); encodeDynamicLengthBytes(attachment, data); - + return data; } @@ -61,7 +62,7 @@ Data serializeLease(int64_t amount, int64_t fee, Address to, int64_t timestamp, encode64BE(amount, data); encode64BE(fee, data); encode64BE(timestamp, data); - + return data; } @@ -75,13 +76,13 @@ Data serializeCancelLease(const Data& leaseId, int64_t fee, int64_t timestamp, c encode64BE(fee, data); encode64BE(timestamp, data); append(data, leaseId); - + return data; } json jsonTransfer(const Data& signature, int64_t amount, const std::string& asset, int64_t fee, const std::string& fee_asset, Address to, const Data& attachment, int64_t timestamp, const Data& pub_key) { json jsonTx; - + jsonTx["type"] = TransactionType::transfer; jsonTx["version"] = TransactionVersion::V2; jsonTx["fee"] = fee; @@ -97,13 +98,13 @@ json jsonTransfer(const Data& signature, int64_t amount, const std::string& asse } jsonTx["amount"] = amount; jsonTx["attachment"] = Base58::bitcoin.encode(attachment); - + return jsonTx; } json jsonLease(const Data& signature, int64_t amount, int64_t fee, Address to, int64_t timestamp, const Data& pub_key) { json jsonTx; - + jsonTx["type"] = TransactionType::lease; jsonTx["version"] = TransactionVersion::V2; jsonTx["fee"] = fee; @@ -112,13 +113,13 @@ json jsonLease(const Data& signature, int64_t amount, int64_t fee, Address to, i jsonTx["proofs"] = json::array({Base58::bitcoin.encode(signature)}); jsonTx["recipient"] = Address(to).string(); jsonTx["amount"] = amount; - + return jsonTx; } json jsonCancelLease(const Data& signature, const Data& leaseId, int64_t fee, int64_t timestamp, const Data& pub_key) { json jsonTx; - + jsonTx["type"] = TransactionType::cancelLease; jsonTx["version"] = TransactionVersion::V2; jsonTx["fee"] = fee; @@ -127,7 +128,7 @@ json jsonCancelLease(const Data& signature, const Data& leaseId, int64_t fee, in jsonTx["chainId"] = 87; // mainnet jsonTx["timestamp"] = timestamp; jsonTx["proofs"] = json::array({Base58::bitcoin.encode(signature)}); - + return jsonTx; } @@ -154,49 +155,44 @@ Data Transaction::serializeToSign() const { auto leaseId = Base58::bitcoin.decode(message.lease_id()); return serializeCancelLease(leaseId, message.fee(), input.timestamp(), pub_key); } - + return Data(); } - - - - json Transaction::buildJson(const Data& signature) const { if (input.has_transfer_message()) { auto message = input.transfer_message(); auto attachment = Data(message.attachment().begin(), message.attachment().end()); return jsonTransfer( - signature, - message.amount(), - message.asset(), - message.fee(), - message.fee_asset(), - Address(message.to()), - attachment, - input.timestamp(), - pub_key); + signature, + message.amount(), + message.asset(), + message.fee(), + message.fee_asset(), + Address(message.to()), + attachment, + input.timestamp(), + pub_key); } else if (input.has_lease_message()) { auto message = input.lease_message(); return jsonLease( - signature, - message.amount(), - message.fee(), - Address(message.to()), - input.timestamp(), - pub_key); + signature, + message.amount(), + message.fee(), + Address(message.to()), + input.timestamp(), + pub_key); } else if (input.has_cancel_lease_message()) { auto message = input.cancel_lease_message(); auto leaseId = Base58::bitcoin.decode(message.lease_id()); return jsonCancelLease( - signature, - leaseId, - message.fee(), - input.timestamp(), - pub_key); + signature, + leaseId, + message.fee(), + input.timestamp(), + pub_key); } return nullptr; } - - +} // namespace TW::Waves diff --git a/src/Waves/Transaction.h b/src/Waves/Transaction.h index b22fd54a030..539af362566 100644 --- a/src/Waves/Transaction.h +++ b/src/Waves/Transaction.h @@ -7,7 +7,7 @@ #pragma once #include "Address.h" -#include "../Data.h" +#include "Data.h" #include "../proto/Waves.pb.h" #include diff --git a/src/Ripple/Address.cpp b/src/XRP/Address.cpp similarity index 92% rename from src/Ripple/Address.cpp rename to src/XRP/Address.cpp index 4f4a601f775..2ff0026deba 100644 --- a/src/Ripple/Address.cpp +++ b/src/XRP/Address.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -8,7 +8,7 @@ #include "../Base58.h" #include -using namespace TW::Ripple; +namespace TW::Ripple { bool Address::isValid(const std::string& string) { const auto decoded = Base58::ripple.decodeCheck(string); @@ -35,3 +35,5 @@ Address::Address(const PublicKey& publicKey) { std::string Address::string() const { return Base58::ripple.encodeCheck(bytes); } + +} // namespace TW::Ripple diff --git a/src/Ripple/Address.h b/src/XRP/Address.h similarity index 98% rename from src/Ripple/Address.h rename to src/XRP/Address.h index 5aa88a4c168..ffa8acbec2d 100644 --- a/src/Ripple/Address.h +++ b/src/XRP/Address.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include diff --git a/src/Ripple/BinaryCoding.h b/src/XRP/BinaryCoding.h similarity index 100% rename from src/Ripple/BinaryCoding.h rename to src/XRP/BinaryCoding.h diff --git a/src/Ripple/Entry.cpp b/src/XRP/Entry.cpp similarity index 56% rename from src/Ripple/Entry.cpp rename to src/XRP/Entry.cpp index a2948f7073e..b28a843c670 100644 --- a/src/Ripple/Entry.cpp +++ b/src/XRP/Entry.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -10,19 +10,20 @@ #include "XAddress.h" #include "Signer.h" -using namespace TW::Ripple; -using namespace std; +namespace TW::Ripple { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, TW::byte, TW::byte, const char*) const { return Address::isValid(address) || XAddress::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +} // namespace TW::Ripple diff --git a/src/Ripple/Entry.h b/src/XRP/Entry.h similarity index 54% rename from src/Ripple/Entry.h rename to src/XRP/Entry.h index adcd80e90fc..6736133286e 100644 --- a/src/Ripple/Entry.h +++ b/src/XRP/Entry.h @@ -12,12 +12,11 @@ namespace TW::Ripple { /// Entry point for implementation of Ripple (XRP) coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeXRP}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::Ripple diff --git a/src/Ripple/Signer.cpp b/src/XRP/Signer.cpp similarity index 62% rename from src/Ripple/Signer.cpp rename to src/XRP/Signer.cpp index 0be0b41f62f..52237c2fdba 100644 --- a/src/Ripple/Signer.cpp +++ b/src/XRP/Signer.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -6,30 +6,35 @@ #include "Signer.h" #include "../BinaryCoding.h" -#include "../Hash.h" +#include -using namespace TW; -using namespace TW::Ripple; +namespace TW::Ripple { static const int64_t fullyCanonical = 0x80000000; Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { + auto output = Proto::SigningOutput(); + + const int64_t tag = input.destination_tag(); + if (tag > std::numeric_limits::max() || tag < 0) { + output.set_error(Common::Proto::SigningError::Error_invalid_memo); + return output; + } + auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); auto transaction = Transaction( - /* amount */input.amount(), - /* fee */input.fee(), - /* flags */input.flags(), - /* sequence */input.sequence(), - /* last_ledger_sequence */input.last_ledger_sequence(), - /* account */Address(input.account()), - /* destination */input.destination(), - /* destination_tag*/input.destination_tag() - ); + /* amount */ input.amount(), + /* fee */ input.fee(), + /* flags */ input.flags(), + /* sequence */ input.sequence(), + /* last_ledger_sequence */ input.last_ledger_sequence(), + /* account */ Address(input.account()), + /* destination */ input.destination(), + /* destination_tag*/ tag); auto signer = Signer(); signer.sign(key, transaction); - auto output = Proto::SigningOutput(); auto encoded = transaction.serialize(); output.set_encoded(encoded.data(), encoded.size()); return output; @@ -44,5 +49,7 @@ void Signer::sign(const PrivateKey& privateKey, Transaction& transaction) const auto hash = Hash::sha512(unsignedTx); auto half = Data(hash.begin(), hash.begin() + 32); - transaction.signature = privateKey.signAsDER(half, TWCurveSECP256k1); + transaction.signature = privateKey.signAsDER(half); } + +} // namespace TW::Ripple diff --git a/src/Ripple/Signer.h b/src/XRP/Signer.h similarity index 97% rename from src/Ripple/Signer.h rename to src/XRP/Signer.h index 9674fdb5cd2..ab49851839b 100644 --- a/src/Ripple/Signer.h +++ b/src/XRP/Signer.h @@ -7,7 +7,7 @@ #pragma once #include "Transaction.h" -#include "../Data.h" +#include "Data.h" #include "../Hash.h" #include "../PrivateKey.h" diff --git a/src/Ripple/Transaction.cpp b/src/XRP/Transaction.cpp similarity index 89% rename from src/Ripple/Transaction.cpp rename to src/XRP/Transaction.cpp index ef2d595854f..00846a905c7 100644 --- a/src/Ripple/Transaction.cpp +++ b/src/XRP/Transaction.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -7,10 +7,10 @@ #include "BinaryCoding.h" #include "Transaction.h" #include "../BinaryCoding.h" -#include "../HexCoding.h" -using namespace TW; -using namespace TW::Ripple; +#include + +namespace TW::Ripple { const int NETWORK_PREFIX = 0x53545800; @@ -83,8 +83,8 @@ Data Transaction::serializeAmount(int64_t amount) { Data Transaction::serializeAddress(Address address) { auto data = Data(20); - if (!address.bytes.empty()) { - std::copy(&address.bytes[0] + 1, &address.bytes[0] + (address.bytes.size() < 21 ? address.bytes.size() : 21), &data[0]); - } + std::copy(&address.bytes[0] + 1, &address.bytes[0] + std::min(address.bytes.size(), size_t(21)), &data[0]); return data; } + +} // namespace TW::Ripple diff --git a/src/Ripple/Transaction.h b/src/XRP/Transaction.h similarity index 99% rename from src/Ripple/Transaction.h rename to src/XRP/Transaction.h index e718f319de4..a51f82eefab 100644 --- a/src/Ripple/Transaction.h +++ b/src/XRP/Transaction.h @@ -8,7 +8,7 @@ #include "Address.h" #include "XAddress.h" -#include "../Data.h" +#include "Data.h" #include "../proto/Ripple.pb.h" namespace TW::Ripple { diff --git a/src/Ripple/XAddress.cpp b/src/XRP/XAddress.cpp similarity index 84% rename from src/Ripple/XAddress.cpp rename to src/XRP/XAddress.cpp index b6f522d7319..0eb5e6978f8 100644 --- a/src/Ripple/XAddress.cpp +++ b/src/XRP/XAddress.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -7,12 +7,10 @@ #include "XAddress.h" #include "../Base58.h" -#include "../BinaryCoding.h" -#include "../Data.h" +#include "../BinaryCoding.h" #include -using namespace TW; -using namespace TW::Ripple; +namespace TW::Ripple { const Data prefixMainnet = {0x05, 0x44}; @@ -21,7 +19,7 @@ bool XAddress::isValid(const std::string& string) { if (decoded.size() != XAddress::size) { return false; } - if(!std::equal(decoded.begin(), decoded.begin() + 2, prefixMainnet.begin())) { + if (!std::equal(decoded.begin(), decoded.begin() + 2, prefixMainnet.begin())) { return false; } if (!(decoded[22] == byte(TagFlag::none) || decoded[22] == byte(TagFlag::classic))) { @@ -45,12 +43,13 @@ XAddress::XAddress(const std::string& string) { } } -XAddress::XAddress(const PublicKey& publicKey, const uint32_t destination): tag(destination) { +XAddress::XAddress(const PublicKey& publicKey, const uint32_t destination) + : tag(destination) { ecdsa_get_pubkeyhash(publicKey.bytes.data(), HASHER_SHA2_RIPEMD, bytes.data()); } std::string XAddress::string() const { - /// see https://github.com/ripple/ripple-address-codec/blob/master/src/index.ts + /// \see https://github.com/ripple/ripple-address-codec/blob/master/src/index.ts /// base58check(2 bytes prefix + 20 bytes keyhash + 1 byte flag + 4 bytes + 32bit tag + 4 bytes reserved) Data result; append(result, prefixMainnet); @@ -60,3 +59,5 @@ std::string XAddress::string() const { append(result, Data{0x00, 0x00, 0x00, 0x00}); return Base58::ripple.encodeCheck(result); } + +} // namespace TW::Ripple diff --git a/src/Ripple/XAddress.h b/src/XRP/XAddress.h similarity index 96% rename from src/Ripple/XAddress.h rename to src/XRP/XAddress.h index dc60e67a57d..1d1e38a5271 100644 --- a/src/Ripple/XAddress.h +++ b/src/XRP/XAddress.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include @@ -20,7 +20,7 @@ class XAddress { /// Number of bytes in a X-address. static const size_t size = 31; - /// Publick key hash length. + /// Public key hash length. static const size_t keyHashSize = 20; /// Address data consisting of public key hash diff --git a/src/XXHash64.h b/src/XXHash64.h deleted file mode 100644 index 6147ff42759..00000000000 --- a/src/XXHash64.h +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright © 2016 Stephan Brumme. All rights reserved, see http://create.stephan-brumme.com/disclaimer.html -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once -#include // for uint32_t and uint64_t - -/// XXHash (64 bit), based on Yann Collet's descriptions, see http://cyan4973.github.io/xxHash/ -/** How to use: - uint64_t myseed = 0; - XXHash64 myhash(myseed); - myhash.add(pointerToSomeBytes, numberOfBytes); - myhash.add(pointerToSomeMoreBytes, numberOfMoreBytes); // call add() as often as you like to ... - // and compute hash: - uint64_t result = myhash.hash(); - - // or all of the above in one single line: - uint64_t result2 = XXHash64::hash(mypointer, numBytes, myseed); - - Note: my code is NOT endian-aware ! -**/ -class XXHash64 -{ -public: - /// create new XXHash (64 bit) - /** @param seed your seed value, even zero is a valid seed **/ - explicit XXHash64(uint64_t seed) - { - state[0] = seed + Prime1 + Prime2; - state[1] = seed + Prime2; - state[2] = seed; - state[3] = seed - Prime1; - buffer[0] = 0; - bufferSize = 0; - totalLength = 0; - } - - /// add a chunk of bytes - /** @param input pointer to a continuous block of data - @param length number of bytes - @return false if parameters are invalid / zero **/ - bool add(const void* input, uint64_t length) - { - // no data ? - if (!input || length == 0) - return false; - - totalLength += length; - // byte-wise access - const unsigned char* data = (const unsigned char*)input; - - // unprocessed old data plus new data still fit in temporary buffer ? - if (bufferSize + length < MaxBufferSize) - { - // just add new data - while (length-- > 0) - buffer[bufferSize++] = *data++; - return true; - } - - // point beyond last byte - const unsigned char* stop = data + length; - const unsigned char* stopBlock = stop - MaxBufferSize; - - // some data left from previous update ? - if (bufferSize > 0) - { - // make sure temporary buffer is full (16 bytes) - while (bufferSize < MaxBufferSize) - buffer[bufferSize++] = *data++; - - // process these 32 bytes (4x8) - process(buffer, state[0], state[1], state[2], state[3]); - } - - // copying state to local variables helps optimizer A LOT - uint64_t s0 = state[0], s1 = state[1], s2 = state[2], s3 = state[3]; - // 32 bytes at once - while (data <= stopBlock) - { - // local variables s0..s3 instead of state[0]..state[3] are much faster - process(data, s0, s1, s2, s3); - data += 32; - } - // copy back - state[0] = s0; state[1] = s1; state[2] = s2; state[3] = s3; - - // copy remainder to temporary buffer - bufferSize = uint32_t(stop - data); - for (unsigned int i = 0; i < bufferSize; i++) - buffer[i] = data[i]; - - // done - return true; - } - - /// get current hash - /** @return 64 bit XXHash **/ - uint64_t hash() const - { - // fold 256 bit state into one single 64 bit value - uint64_t result; - if (totalLength >= MaxBufferSize) - { - result = rotateLeft(state[0], 1) + - rotateLeft(state[1], 7) + - rotateLeft(state[2], 12) + - rotateLeft(state[3], 18); - result = (result ^ processSingle(0, state[0])) * Prime1 + Prime4; - result = (result ^ processSingle(0, state[1])) * Prime1 + Prime4; - result = (result ^ processSingle(0, state[2])) * Prime1 + Prime4; - result = (result ^ processSingle(0, state[3])) * Prime1 + Prime4; - } - else - { - // internal state wasn't set in add(), therefore original seed is still stored in state2 - result = state[2] + Prime5; - } - - result += totalLength; - - // process remaining bytes in temporary buffer - const unsigned char* data = buffer; - // point beyond last byte - const unsigned char* stop = data + bufferSize; - - // at least 8 bytes left ? => eat 8 bytes per step - for (; data + 8 <= stop; data += 8) - result = rotateLeft(result ^ processSingle(0, *(uint64_t*)data), 27) * Prime1 + Prime4; - - // 4 bytes left ? => eat those - if (data + 4 <= stop) - { - result = rotateLeft(result ^ (*(uint32_t*)data) * Prime1, 23) * Prime2 + Prime3; - data += 4; - } - - // take care of remaining 0..3 bytes, eat 1 byte per step - while (data != stop) - result = rotateLeft(result ^ (*data++) * Prime5, 11) * Prime1; - - // mix bits - result ^= result >> 33; - result *= Prime2; - result ^= result >> 29; - result *= Prime3; - result ^= result >> 32; - return result; - } - - - /// combine constructor, add() and hash() in one static function (C style) - /** @param input pointer to a continuous block of data - @param length number of bytes - @param seed your seed value, e.g. zero is a valid seed - @return 64 bit XXHash **/ - static uint64_t hash(const void* input, uint64_t length, uint64_t seed) - { - XXHash64 hasher(seed); - hasher.add(input, length); - return hasher.hash(); - } - -private: - /// magic constants :-) - static const uint64_t Prime1 = 11400714785074694791ULL; - static const uint64_t Prime2 = 14029467366897019727ULL; - static const uint64_t Prime3 = 1609587929392839161ULL; - static const uint64_t Prime4 = 9650029242287828579ULL; - static const uint64_t Prime5 = 2870177450012600261ULL; - - /// temporarily store up to 31 bytes between multiple add() calls - static const uint64_t MaxBufferSize = 31+1; - - uint64_t state[4]; - unsigned char buffer[MaxBufferSize]; - uint32_t bufferSize; - uint64_t totalLength; - - /// rotate bits, should compile to a single CPU instruction (ROL) - static inline uint64_t rotateLeft(uint64_t x, unsigned char bits) - { - return (x << bits) | (x >> (64 - bits)); - } - - /// process a single 64 bit value - static inline uint64_t processSingle(uint64_t previous, uint64_t input) - { - return rotateLeft(previous + input * Prime2, 31) * Prime1; - } - - /// process a block of 4x4 bytes, this is the main part of the XXHash32 algorithm - static inline void process(const void* data, uint64_t& state0, uint64_t& state1, uint64_t& state2, uint64_t& state3) - { - const uint64_t* block = (const uint64_t*) data; - state0 = processSingle(state0, block[0]); - state1 = processSingle(state1, block[1]); - state2 = processSingle(state2, block[2]); - state3 = processSingle(state3, block[3]); - } -}; diff --git a/src/Zcash/Entry.cpp b/src/Zcash/Entry.cpp index cbab0176d74..ff5f1ac21ce 100644 --- a/src/Zcash/Entry.cpp +++ b/src/Zcash/Entry.cpp @@ -6,24 +6,30 @@ #include "Entry.h" -#include "TAddress.h" #include "Signer.h" +#include "TAddress.h" -using namespace TW::Zcash; -using namespace std; +namespace TW::Zcash { -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, [[maybe_unused]] const std::string& address, [[maybe_unused]] TW::byte p2pkh, [[maybe_unused]] TW::byte p2sh, [[maybe_unused]] const char* hrp) const { return TAddress::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, [[maybe_unused]] const char* hrp) const { return TAddress(publicKey, p2pkh).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + const auto addr = TAddress(address); + return {addr.bytes.begin() + 2, addr.bytes.end()}; +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } -void Entry::plan(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::plan([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { planTemplate(dataIn, dataOut); } + +} // namespace TW::Zcash diff --git a/src/Zcash/Entry.h b/src/Zcash/Entry.h index 503b13544d5..626f8105131 100644 --- a/src/Zcash/Entry.h +++ b/src/Zcash/Entry.h @@ -12,13 +12,13 @@ namespace TW::Zcash { /// Zcash entry dispatcher. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeZcash, TWCoinTypeZelcash}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::Zcash diff --git a/src/Zcash/Signer.cpp b/src/Zcash/Signer.cpp index 9387f58a8b7..37db9183ee8 100644 --- a/src/Zcash/Signer.cpp +++ b/src/Zcash/Signer.cpp @@ -11,8 +11,7 @@ #include "Transaction.h" #include "TransactionBuilder.h" -using namespace TW; -using namespace TW::Zcash; +namespace TW::Zcash { TransactionPlan Signer::plan(const SigningInput& input) noexcept { auto plan = Bitcoin::TransactionSigner::plan(input); @@ -38,3 +37,5 @@ SigningOutput Signer::sign(const SigningInput& input) noexcept { } return output; } + +} // namespace TW::Zcash diff --git a/src/Zcash/TAddress.h b/src/Zcash/TAddress.h index 6d6f5ac74a7..3452746b13f 100644 --- a/src/Zcash/TAddress.h +++ b/src/Zcash/TAddress.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -45,8 +45,3 @@ class TAddress : public TW::Base58Address<22> { }; } // namespace TW::Zcash - -/// Wrapper for C interface. -struct TWZcashTAddress { - TW::Zcash::TAddress impl; -}; diff --git a/src/Zcash/Transaction.cpp b/src/Zcash/Transaction.cpp index b023b1284f0..291c3384370 100644 --- a/src/Zcash/Transaction.cpp +++ b/src/Zcash/Transaction.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -8,26 +8,23 @@ #include "../Bitcoin/SigHashType.h" #include "../BinaryCoding.h" -#include "../Hash.h" -#include "../HexCoding.h" #include -using namespace TW; -using namespace TW::Zcash; +namespace TW::Zcash { -const auto sigHashPersonalization = Data({'Z','c','a','s','h','S','i','g','H','a','s','h'}); -const auto prevoutsHashPersonalization = Data({'Z','c','a','s','h','P','r','e','v','o','u','t','H','a','s','h'}); -const auto sequenceHashPersonalization = Data({'Z','c','a','s','h','S','e','q','u','e','n','c','H','a','s','h'}); -const auto outputsHashPersonalization = Data({'Z','c','a','s','h','O','u','t','p','u','t','s','H','a','s','h'}); -const auto joinsplitsHashPersonalization = Data({'Z','c','a','s','h','J','S','p','l','i','t','s','H','a','s','h'}); -const auto shieldedSpendHashPersonalization = Data({'Z','c','a','s','h','S','S','p','e','n','d','s','H','a','s','h'}); -const auto shieldedOutputsHashPersonalization = Data({'Z','c','a','s','h','S','O','u','t','p','u','t','H','a','s','h'}); +const auto sigHashPersonalization = Data({'Z', 'c', 'a', 's', 'h', 'S', 'i', 'g', 'H', 'a', 's', 'h'}); +const auto prevoutsHashPersonalization = Data({'Z', 'c', 'a', 's', 'h', 'P', 'r', 'e', 'v', 'o', 'u', 't', 'H', 'a', 's', 'h'}); +const auto sequenceHashPersonalization = Data({'Z', 'c', 'a', 's', 'h', 'S', 'e', 'q', 'u', 'e', 'n', 'c', 'H', 'a', 's', 'h'}); +const auto outputsHashPersonalization = Data({'Z', 'c', 'a', 's', 'h', 'O', 'u', 't', 'p', 'u', 't', 's', 'H', 'a', 's', 'h'}); +const auto joinsplitsHashPersonalization = Data({'Z', 'c', 'a', 's', 'h', 'J', 'S', 'p', 'l', 'i', 't', 's', 'H', 'a', 's', 'h'}); +const auto shieldedSpendHashPersonalization = Data({'Z', 'c', 'a', 's', 'h', 'S', 'S', 'p', 'e', 'n', 'd', 's', 'H', 'a', 's', 'h'}); +const auto shieldedOutputsHashPersonalization = Data({'Z', 'c', 'a', 's', 'h', 'S', 'O', 'u', 't', 'p', 'u', 't', 'H', 'a', 's', 'h'}); /// See https://github.com/zcash/zips/blob/master/zip-0205.rst#sapling-deployment BRANCH_ID section -const std::array Zcash::SaplingBranchID = {0xbb, 0x09, 0xb8, 0x76}; +const std::array SaplingBranchID = {0xbb, 0x09, 0xb8, 0x76}; /// See https://github.com/zcash/zips/blob/master/zip-0206.rst#blossom-deployment BRANCH_ID section -const std::array Zcash::BlossomBranchID = {0x60, 0x0e, 0xb4, 0x2b}; +const std::array BlossomBranchID = {0x60, 0x0e, 0xb4, 0x2b}; Data Transaction::getPreImage(const Bitcoin::Script& scriptCode, size_t index, enum TWBitcoinSigHashType hashType, uint64_t amount) const { @@ -36,7 +33,7 @@ Data Transaction::getPreImage(const Bitcoin::Script& scriptCode, size_t index, e auto data = Data{}; // header - encode32LE(version, data); + encode32LE(_version, data); // nVersionGroupId encode32LE(versionGroupId, data); @@ -152,7 +149,7 @@ Data Transaction::getShieldedOutputsHash() const { } void Transaction::encode(Data& data) const { - encode32LE(version, data); + encode32LE(_version, data); encode32LE(versionGroupId, data); // vin @@ -181,7 +178,7 @@ void Transaction::encode(Data& data) const { Data Transaction::getSignatureHash(const Bitcoin::Script& scriptCode, size_t index, enum TWBitcoinSigHashType hashType, uint64_t amount, - Bitcoin::SignatureVersion version) const { + [[maybe_unused]] Bitcoin::SignatureVersion version) const { Data personalization; personalization.reserve(16); std::copy(sigHashPersonalization.begin(), sigHashPersonalization.begin() + 12, @@ -194,7 +191,7 @@ Data Transaction::getSignatureHash(const Bitcoin::Script& scriptCode, size_t ind Bitcoin::Proto::Transaction Transaction::proto() const { auto protoTx = Bitcoin::Proto::Transaction(); - protoTx.set_version(version); + protoTx.set_version(_version); protoTx.set_locktime(lockTime); for (const auto& input : inputs) { @@ -214,3 +211,5 @@ Bitcoin::Proto::Transaction Transaction::proto() const { return protoTx; } + +} // namespace TW::Zcash diff --git a/src/Zcash/Transaction.h b/src/Zcash/Transaction.h index e42b77ef106..4086f3ddbea 100644 --- a/src/Zcash/Transaction.h +++ b/src/Zcash/Transaction.h @@ -23,7 +23,7 @@ extern const std::array BlossomBranchID; /// Only supports transparent transaction right now /// See also https://github.com/zcash/zips/blob/master/zip-0243.rst struct Transaction { - uint32_t version = 0x80000004; + uint32_t _version = 0x80000004; uint32_t versionGroupId = 0x892F2085; uint32_t lockTime = 0; uint32_t expiryHeight = 0; @@ -40,7 +40,7 @@ struct Transaction { Transaction(uint32_t version, uint32_t versionGroupId, uint32_t lockTime, uint32_t expiryHeight, uint64_t valueBalance, std::array branchId) - : version(version) + : _version(version) , versionGroupId(versionGroupId) , lockTime(lockTime) , expiryHeight(expiryHeight) diff --git a/src/Zilliqa/Address.cpp b/src/Zilliqa/Address.cpp index bf010554bd0..af0d14ac1bd 100644 --- a/src/Zilliqa/Address.cpp +++ b/src/Zilliqa/Address.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -8,6 +8,8 @@ #include -using namespace TW::Zilliqa; +namespace TW::Zilliqa { const std::string Address::hrp = HRP_ZILLIQA; + +} // namespace TW::Zilliqa diff --git a/src/Zilliqa/Address.h b/src/Zilliqa/Address.h index be7a7ef234f..8214e6aaebb 100644 --- a/src/Zilliqa/Address.h +++ b/src/Zilliqa/Address.h @@ -26,7 +26,7 @@ class Address : public Bech32Address { Address(const Data& keyHash) : Bech32Address(hrp, keyHash) {} /// Initializes an address with a public key. - Address(const PublicKey& publicKey) : Bech32Address(hrp, HASHER_SHA2, publicKey) {} + Address(const PublicKey& publicKey) : Bech32Address(hrp, Hash::HasherSha256, publicKey) {} std::string checksumed() const { return checksum(getKeyHash()); diff --git a/src/Zilliqa/AddressChecksum.cpp b/src/Zilliqa/AddressChecksum.cpp index 5332bee2363..bbd072f6268 100644 --- a/src/Zilliqa/AddressChecksum.cpp +++ b/src/Zilliqa/AddressChecksum.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -11,11 +11,10 @@ #include "../uint256.h" #include -using namespace TW; -using namespace TW::Zilliqa; +namespace TW::Zilliqa { -/// see https://github.com/Zilliqa/Zilliqa/blob/1c53b792c7ae44f7b77366536a7e2f73a3eade6a/src/libServer/AddressChecksum.h -std::string Zilliqa::checksum(const Data& bytes) { +/// \see https://github.com/Zilliqa/Zilliqa/blob/1c53b792c7ae44f7b77366536a7e2f73a3eade6a/src/libServer/AddressChecksum.h +std::string checksum(const Data& bytes) { const auto addressString = hex(bytes); const auto hash = hex(Hash::sha256(bytes)); @@ -23,7 +22,7 @@ std::string Zilliqa::checksum(const Data& bytes) { uint256_t v("0x" + hash); std::string string = ""; - for (auto i = 0; i < addressString.size(); i += 1) { + for (auto i = 0ul; i < addressString.size(); i += 1) { const auto a = addressString[i]; if (a >= '0' && a <= '9') { string.push_back(a); @@ -38,3 +37,5 @@ std::string Zilliqa::checksum(const Data& bytes) { return string; } + +} // namespace TW::Zilliqa diff --git a/src/Zilliqa/AddressChecksum.h b/src/Zilliqa/AddressChecksum.h index c2f32e63152..a91665deda1 100644 --- a/src/Zilliqa/AddressChecksum.h +++ b/src/Zilliqa/AddressChecksum.h @@ -6,7 +6,7 @@ #pragma once -#include "../Data.h" +#include "Data.h" #include namespace TW::Zilliqa { diff --git a/src/Zilliqa/Entry.cpp b/src/Zilliqa/Entry.cpp index c249ba827ba..9ab22662f0d 100644 --- a/src/Zilliqa/Entry.cpp +++ b/src/Zilliqa/Entry.cpp @@ -9,23 +9,33 @@ #include "Address.h" #include "Signer.h" -using namespace TW::Zilliqa; -using namespace std; +namespace TW::Zilliqa { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, byte, byte, const char*) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, byte, const char*) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + Address addr; + if (!Address::decode(address, addr)) { + return {}; + } + // data in Zilliqa is a checksummed string without 0x + return data(checksum(addr.getKeyHash())); +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const Data& dataIn, Data& dataOut) const { signTemplate(dataIn, dataOut); } -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { +std::string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { return Signer::signJSON(json, key); } + +} // namespace TW::Zilliqa diff --git a/src/Zilliqa/Entry.h b/src/Zilliqa/Entry.h index 05cc698c96f..fa1335f5838 100644 --- a/src/Zilliqa/Entry.h +++ b/src/Zilliqa/Entry.h @@ -12,14 +12,14 @@ namespace TW::Zilliqa { /// Entry point for implementation of Zilliqa coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeZilliqa}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual bool supportsJSONSigning() const { return true; } - virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool supportsJSONSigning() const { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; }; } // namespace TW::Zilliqa diff --git a/src/Zilliqa/Signer.cpp b/src/Zilliqa/Signer.cpp index 2de41566d73..521ffd5c556 100644 --- a/src/Zilliqa/Signer.cpp +++ b/src/Zilliqa/Signer.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -18,8 +18,8 @@ #include #include -using namespace TW; -using namespace TW::Zilliqa; +namespace TW::Zilliqa { + using ByteArray = ZilliqaMessage::ByteArray; static inline Data prependZero(Data& data) { @@ -95,7 +95,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { const auto preImage = Signer::getPreImage(input, address); const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); const auto pubKey = key.getPublicKey(TWPublicKeyTypeSECP256k1); - const auto signature = key.signSchnorr(preImage, TWCurveSECP256k1); + const auto signature = key.signZilliqa(preImage); const auto transaction = input.transaction(); // build json @@ -137,3 +137,5 @@ std::string Signer::signJSON(const std::string& json, const Data& key) { input.set_private_key(key.data(), key.size()); return hex(Signer::sign(input).json()); } + +} // namespace TW::Zilliqa diff --git a/src/Zilliqa/Signer.h b/src/Zilliqa/Signer.h index d4089e4e485..c1ce6f9cfe6 100644 --- a/src/Zilliqa/Signer.h +++ b/src/Zilliqa/Signer.h @@ -7,7 +7,7 @@ #pragma once #include "Address.h" -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" #include "../proto/Zilliqa.pb.h" diff --git a/src/algorithm/erase.h b/src/algorithm/erase.h new file mode 100644 index 00000000000..a01fe425ce2 --- /dev/null +++ b/src/algorithm/erase.h @@ -0,0 +1,33 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include +#include + +namespace TW { + +// C++17 Implementation of https://en.cppreference.com/w/cpp/container/vector/erase2 +template +constexpr typename std::vector::size_type erase(std::vector& c, + const U& value) { + auto it = std::remove(c.begin(), c.end(), value); + auto r = std::distance(it, c.end()); + c.erase(it, c.end()); + return r; +} + +template +constexpr typename std::vector::size_type erase_if(std::vector& c, + Functor&& pred) { + auto it = std::remove_if(c.begin(), c.end(), std::forward(pred)); + auto r = std::distance(it, c.end()); + c.erase(it, c.end()); + return r; +} + +} // namespace TW \ No newline at end of file diff --git a/src/algorithm/sort_copy.h b/src/algorithm/sort_copy.h new file mode 100644 index 00000000000..0b4dfeefe05 --- /dev/null +++ b/src/algorithm/sort_copy.h @@ -0,0 +1,27 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include + +namespace TW { + +template +inline Container sortCopy(const Container& container) noexcept { + Container result = container; + std::sort(begin(result), end(result)); + return result; +} + +template +inline Container sortCopy(const Container& container, Func&& func) noexcept { + Container result = container; + std::sort(begin(result), end(result), std::forward(func)); + return result; +} + +} // namespace TW \ No newline at end of file diff --git a/src/algorithm/to_array.h b/src/algorithm/to_array.h new file mode 100644 index 00000000000..06f302cd7e1 --- /dev/null +++ b/src/algorithm/to_array.h @@ -0,0 +1,21 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include +#include + +namespace TW { + +template +constexpr std::array to_array(Collection&& collection) { + std::array out{}; + std::copy(begin(collection), end(collection), out.begin()); + return out; +} + +} // namespace TW diff --git a/src/concepts/tw_concepts.h b/src/concepts/tw_concepts.h new file mode 100644 index 00000000000..57e2455a078 --- /dev/null +++ b/src/concepts/tw_concepts.h @@ -0,0 +1,19 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include + +namespace TW { + +template +concept integral = std::is_integral_v; + +template +concept floating_point = std::is_floating_point_v; + +} // namespace TW diff --git a/src/interface/TWAccount.cpp b/src/interface/TWAccount.cpp index cbe49c3f321..73a624738c7 100644 --- a/src/interface/TWAccount.cpp +++ b/src/interface/TWAccount.cpp @@ -10,30 +10,45 @@ using namespace TW; -struct TWAccount *_Nonnull TWAccountCreate(TWString *_Nonnull address, enum TWCoinType coin, TWString *_Nonnull derivationPath, TWString *_Nonnull extendedPublicKey) { +struct TWAccount* _Nonnull TWAccountCreate(TWString* _Nonnull address, enum TWCoinType coin, + enum TWDerivation derivation, + TWString* _Nonnull derivationPath, + TWString* _Nonnull publicKey, + TWString* _Nonnull extendedPublicKey) { auto& addressString = *reinterpret_cast(address); auto& derivationPathString = *reinterpret_cast(derivationPath); + auto& publicKeyString = *reinterpret_cast(publicKey); auto& extendedPublicKeyString = *reinterpret_cast(extendedPublicKey); const auto dp = DerivationPath(derivationPathString); - return new TWAccount{ Keystore::Account(addressString, coin, dp, extendedPublicKeyString) }; + return new TWAccount{ + Keystore::Account(addressString, coin, derivation, dp, publicKeyString, extendedPublicKeyString) + }; } -void TWAccountDelete(struct TWAccount *_Nonnull account) { +void TWAccountDelete(struct TWAccount* _Nonnull account) { delete account; } -TWString *_Nonnull TWAccountAddress(struct TWAccount *_Nonnull account) { +TWString* _Nonnull TWAccountAddress(struct TWAccount* _Nonnull account) { return TWStringCreateWithUTF8Bytes(account->impl.address.c_str()); } -TWString *_Nonnull TWAccountDerivationPath(struct TWAccount *_Nonnull account) { +enum TWDerivation TWAccountDerivation(struct TWAccount* _Nonnull account) { + return account->impl.derivation; +} + +TWString* _Nonnull TWAccountDerivationPath(struct TWAccount* _Nonnull account) { return TWStringCreateWithUTF8Bytes(account->impl.derivationPath.string().c_str()); } -TWString *_Nonnull TWAccountExtendedPublicKey(struct TWAccount *_Nonnull account) { +TWString* _Nonnull TWAccountPublicKey(struct TWAccount* _Nonnull account) { + return TWStringCreateWithUTF8Bytes(account->impl.publicKey.c_str()); +} + +TWString* _Nonnull TWAccountExtendedPublicKey(struct TWAccount* _Nonnull account) { return TWStringCreateWithUTF8Bytes(account->impl.extendedPublicKey.c_str()); } -enum TWCoinType TWAccountCoin(struct TWAccount *_Nonnull account) { +enum TWCoinType TWAccountCoin(struct TWAccount* _Nonnull account) { return account->impl.coin; } diff --git a/src/interface/TWAnyAddress.cpp b/src/interface/TWAnyAddress.cpp index 7361584ef85..6ad7a9fb9ce 100644 --- a/src/interface/TWAnyAddress.cpp +++ b/src/interface/TWAnyAddress.cpp @@ -7,35 +7,13 @@ #include #include #include -#include -#include "../Bitcoin/Address.h" -#include "../Bitcoin/CashAddress.h" -#include "../Bitcoin/SegwitAddress.h" -#include "../Cosmos/Address.h" -#include "../Decred/Address.h" -#include "../Kusama/Address.h" -#include "../Polkadot/Address.h" -#include "../Zcash/TAddress.h" -#include "../Zilliqa/Address.h" -#include "../Cardano/AddressV3.h" -#include "../NEO/Address.h" -#include "../Nano/Address.h" -#include "../Elrond/Address.h" -#include "../NEAR/Address.h" - -#include "../Coin.h" -#include "../HexCoding.h" - -using namespace TW; - -struct TWAnyAddress { - TWString* address; - enum TWCoinType coin; -}; +#include "Data.h" +#include "Coin.h" +#include "AnyAddress.h" bool TWAnyAddressEqual(struct TWAnyAddress* _Nonnull lhs, struct TWAnyAddress* _Nonnull rhs) { - return TWStringEqual(lhs->address, rhs->address) && lhs->coin == rhs->coin; + return *lhs->impl == *rhs->impl; } bool TWAnyAddressIsValid(TWString* _Nonnull string, enum TWCoinType coin) { @@ -43,183 +21,58 @@ bool TWAnyAddressIsValid(TWString* _Nonnull string, enum TWCoinType coin) { return TW::validateAddress(coin, address); } +bool TWAnyAddressIsValidBech32(TWString* _Nonnull string, enum TWCoinType coin, TWString* _Nonnull hrp) { + const auto& address = *reinterpret_cast(string); + const auto& hrpStr = *reinterpret_cast(hrp); + return TW::validateAddress(coin, address, hrpStr.c_str()); +} + struct TWAnyAddress* _Nullable TWAnyAddressCreateWithString(TWString* _Nonnull string, enum TWCoinType coin) { const auto& address = *reinterpret_cast(string); - auto normalized = TW::normalizeAddress(coin, address); - if (normalized.empty()) { return nullptr; } - return new TWAnyAddress{TWStringCreateWithUTF8Bytes(normalized.c_str()), coin}; + auto *impl = TW::AnyAddress::createAddress(address, coin); + if (impl == nullptr) { + return nullptr; + } + return new TWAnyAddress{impl}; +} + +struct TWAnyAddress* _Nullable TWAnyAddressCreateBech32(TWString* _Nonnull string, + enum TWCoinType coin, TWString* _Nonnull hrp) { + const auto& address = *reinterpret_cast(string); + const auto& hrpStr = *reinterpret_cast(hrp); + auto *impl = TW::AnyAddress::createAddress(address, coin, hrpStr); + if (impl == nullptr) { + return nullptr; + } + return new TWAnyAddress{impl}; } struct TWAnyAddress* _Nonnull TWAnyAddressCreateWithPublicKey( struct TWPublicKey* _Nonnull publicKey, enum TWCoinType coin) { - auto address = TW::deriveAddress(coin, publicKey->impl); - return new TWAnyAddress{TWStringCreateWithUTF8Bytes(address.c_str()), coin}; + return new TWAnyAddress{TW::AnyAddress::createAddress(publicKey->impl, coin)}; +} + +struct TWAnyAddress* _Nonnull TWAnyAddressCreateBech32WithPublicKey( + struct TWPublicKey* _Nonnull publicKey, enum TWCoinType coin, TWString* _Nonnull hrp) { + const auto& hrpStr = *reinterpret_cast(hrp); + return new TWAnyAddress{TW::AnyAddress::createAddress(publicKey->impl, coin, hrpStr)}; } void TWAnyAddressDelete(struct TWAnyAddress* _Nonnull address) { - TWStringDelete(address->address); + delete address->impl; delete address; } TWString* _Nonnull TWAnyAddressDescription(struct TWAnyAddress* _Nonnull address) { - return TWStringCreateWithUTF8Bytes(TWStringUTF8Bytes(address->address)); + return TWStringCreateWithUTF8Bytes(address->impl->address.c_str()); } enum TWCoinType TWAnyAddressCoin(struct TWAnyAddress* _Nonnull address) { - return address->coin; + return address->impl->coin; } TWData* _Nonnull TWAnyAddressData(struct TWAnyAddress* _Nonnull address) { - auto& string = *reinterpret_cast(address->address); - Data data; - switch (address->coin) { - case TWCoinTypeBinance: - case TWCoinTypeCosmos: - case TWCoinTypeKava: - case TWCoinTypeTerra: - case TWCoinTypeBandChain: - case TWCoinTypeTHORChain: - case TWCoinTypeBluzelle: - case TWCoinTypeIoTeX: - case TWCoinTypeCryptoOrg: - case TWCoinTypeHarmony: - { - Cosmos::Address addr; - if (!Cosmos::Address::decode(string, addr)) { - break; - } - data = addr.getKeyHash(); - break; - } - - case TWCoinTypeBitcoin: - case TWCoinTypeDigiByte: - case TWCoinTypeGroestlcoin: - case TWCoinTypeLitecoin: - case TWCoinTypeViacoin: { - auto decoded = Bitcoin::SegwitAddress::decode(string); - if (!std::get<2>(decoded)) { - break; - } - data = std::get<0>(decoded).witnessProgram; - break; - } - - case TWCoinTypeBitcoinCash: { - auto addr = Bitcoin::CashAddress(string); - data.resize(Bitcoin::Address::size); - size_t outlen = 0; - cash_data_to_addr(data.data(), &outlen, addr.bytes.data(), 34); - data = Data(data.begin() + 1, data.end()); - break; - } - - case TWCoinTypeDash: - case TWCoinTypeDogecoin: - case TWCoinTypeMonacoin: - case TWCoinTypeQtum: - case TWCoinTypeRavencoin: - case TWCoinTypeZcoin: { - auto addr = Bitcoin::Address(string); - data = Data(addr.bytes.begin() + 1, addr.bytes.end()); - break; - } - - case TWCoinTypeDecred: { - auto addr = Decred::Address(string); - data = Data(addr.bytes.begin() + 2, addr.bytes.end()); - break; - } - - case TWCoinTypeZcash: - case TWCoinTypeZelcash: { - auto addr = Zcash::TAddress(string); - data = Data(addr.bytes.begin() + 2, addr.bytes.end()); - break; - } - - case TWCoinTypeCallisto: - case TWCoinTypeEthereum: - case TWCoinTypeEthereumClassic: - case TWCoinTypeGoChain: - case TWCoinTypePOANetwork: - case TWCoinTypeThunderToken: - case TWCoinTypeTomoChain: - case TWCoinTypeVeChain: - case TWCoinTypeTheta: - case TWCoinTypeWanchain: - case TWCoinTypeAion: - case TWCoinTypeSmartChainLegacy: - case TWCoinTypeSmartChain: - case TWCoinTypePolygon: - case TWCoinTypeOptimism: - case TWCoinTypeArbitrum: - case TWCoinTypeECOChain: - case TWCoinTypeXDai: - case TWCoinTypeAvalancheCChain: - case TWCoinTypeFantom: - case TWCoinTypeCelo: - data = parse_hex(string); - break; - - case TWCoinTypeNano: { - auto addr = Nano::Address(string); - data = Data(addr.bytes.begin(), addr.bytes.end()); - break; - } - - case TWCoinTypeZilliqa: { - Zilliqa::Address addr; - if (!Zilliqa::Address::decode(string, addr)) { - break; - } - // data in Zilliqa is a checksummed string without 0x - auto str = Zilliqa::checksum(addr.getKeyHash()); - data = Data(str.begin(), str.end()); - break; - } - - case TWCoinTypeKusama: { - auto addr = Kusama::Address(string); - data = Data(addr.bytes.begin() + 1, addr.bytes.end()); - break; - } - - case TWCoinTypePolkadot: { - auto addr = Polkadot::Address(string); - data = Data(addr.bytes.begin() + 1, addr.bytes.end()); - break; - } - - case TWCoinTypeCardano: { - auto addr = Cardano::AddressV3(string); - data = addr.data(); - break; - } - - case TWCoinTypeNEO: { - auto addr = NEO::Address(string); - data = Data(addr.bytes.begin(), addr.bytes.end()); - break; - } - - case TWCoinTypeElrond: { - Elrond::Address addr; - if (Elrond::Address::decode(string, addr)) { - data = addr.getKeyHash(); - } - - break; - } - - case TWCoinTypeNEAR: { - auto addr = NEAR::Address(string); - data = Data(addr.bytes.begin(), addr.bytes.end()); - break; - } - - default: break; - } + auto data = address->impl->getData(); return TWDataCreateWithBytes(data.data(), data.size()); } diff --git a/src/interface/TWBase32.cpp b/src/interface/TWBase32.cpp new file mode 100644 index 00000000000..6d2e89c342c --- /dev/null +++ b/src/interface/TWBase32.cpp @@ -0,0 +1,42 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include + +#include "../Base32.h" + +#include + +using namespace TW; + +TWData* TWBase32DecodeWithAlphabet(TWString* _Nonnull string, TWString* _Nullable alphabet) { + Data decodedOut; + auto cppString = *reinterpret_cast(string); + const char* alphabetRaw = nullptr; + if (alphabet != nullptr) { + alphabetRaw = TWStringUTF8Bytes(alphabet); + } + auto result = Base32::decode(cppString, decodedOut, alphabetRaw); + return result ? TWDataCreateWithData(&decodedOut) : nullptr; +} + +TWData* _Nullable TWBase32Decode(TWString* _Nonnull string) { + return TWBase32DecodeWithAlphabet(string, nullptr); +} + +TWString* _Nonnull TWBase32EncodeWithAlphabet(TWData* _Nonnull data, TWString* _Nullable alphabet) { + auto cppData = *reinterpret_cast(data); + const char* alphabetRaw = nullptr; + if (alphabet != nullptr) { + alphabetRaw = TWStringUTF8Bytes(alphabet); + } + auto result = Base32::encode(cppData, alphabetRaw); + return TWStringCreateWithUTF8Bytes(result.c_str()); +} + +TWString* _Nonnull TWBase32Encode(TWData* _Nonnull data) { + return TWBase32EncodeWithAlphabet(data, nullptr); +} diff --git a/src/interface/TWBase64.cpp b/src/interface/TWBase64.cpp new file mode 100644 index 00000000000..c57a713e6e2 --- /dev/null +++ b/src/interface/TWBase64.cpp @@ -0,0 +1,37 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include + +#include "../Base64.h" + +#include + +using namespace TW; + +TWData* _Nullable TWBase64Decode(TWString* _Nonnull string) { + auto cppString = *reinterpret_cast(string); + Data decodedOut = Base64::decode(cppString); + return TWDataCreateWithData(&decodedOut); +} + +TWData* _Nullable TWBase64DecodeUrl(TWString* _Nonnull string) { + auto cppString = *reinterpret_cast(string); + Data decodedOut = Base64::decodeBase64Url(cppString); + return TWDataCreateWithData(&decodedOut); +} + +TWString *_Nonnull TWBase64Encode(TWData *_Nonnull data) { + auto cppData = *reinterpret_cast(data); + auto result = Base64::encode(cppData); + return TWStringCreateWithUTF8Bytes(result.c_str()); +} + +TWString *_Nonnull TWBase64EncodeUrl(TWData *_Nonnull data) { + auto cppData = *reinterpret_cast(data); + auto result = Base64::encodeBase64Url(cppData); + return TWStringCreateWithUTF8Bytes(result.c_str()); +} diff --git a/src/interface/TWBitcoinAddress.cpp b/src/interface/TWBitcoinAddress.cpp index 8df8de2a37f..cefd940e992 100644 --- a/src/interface/TWBitcoinAddress.cpp +++ b/src/interface/TWBitcoinAddress.cpp @@ -13,25 +13,23 @@ #include -using namespace TW::Bitcoin; - bool TWBitcoinAddressEqual(struct TWBitcoinAddress *_Nonnull lhs, struct TWBitcoinAddress *_Nonnull rhs) { return lhs->impl == rhs->impl; } bool TWBitcoinAddressIsValid(TWData *_Nonnull data) { - return TWDataSize(data) == Address::size; + return TWDataSize(data) == TW::Bitcoin::Address::size; } bool TWBitcoinAddressIsValidString(TWString *_Nonnull string) { auto& s = *reinterpret_cast(string); - return Address::isValid(s); + return TW::Bitcoin::Address::isValid(s); } struct TWBitcoinAddress *_Nullable TWBitcoinAddressCreateWithString(TWString *_Nonnull string) { auto& s = *reinterpret_cast(string); try { - return new TWBitcoinAddress{ Address(s) }; + return new TWBitcoinAddress{ TW::Bitcoin::Address(s) }; } catch (...) { return nullptr; } @@ -40,7 +38,7 @@ struct TWBitcoinAddress *_Nullable TWBitcoinAddressCreateWithString(TWString *_N struct TWBitcoinAddress *_Nullable TWBitcoinAddressCreateWithData(TWData *_Nonnull data) { const auto& d = *reinterpret_cast(data); try { - return new TWBitcoinAddress{ Address(d) }; + return new TWBitcoinAddress{ TW::Bitcoin::Address(d) }; } catch (...) { return nullptr; } @@ -48,7 +46,7 @@ struct TWBitcoinAddress *_Nullable TWBitcoinAddressCreateWithData(TWData *_Nonnu struct TWBitcoinAddress *_Nullable TWBitcoinAddressCreateWithPublicKey(struct TWPublicKey *_Nonnull publicKey, uint8_t prefix) { try { - return new TWBitcoinAddress{ Address(publicKey->impl, prefix) }; + return new TWBitcoinAddress{ TW::Bitcoin::Address(publicKey->impl, prefix) }; } catch (...) { return nullptr; } @@ -67,5 +65,5 @@ uint8_t TWBitcoinAddressPrefix(struct TWBitcoinAddress *_Nonnull address) { } TWData *_Nonnull TWBitcoinAddressKeyhash(struct TWBitcoinAddress *_Nonnull address) { - return TWDataCreateWithBytes(address->impl.bytes.data() + 1, Address::size - 1); + return TWDataCreateWithBytes(address->impl.bytes.data() + 1, TW::Bitcoin::Address::size - 1); } diff --git a/src/interface/TWBitcoinMessageSigner.cpp b/src/interface/TWBitcoinMessageSigner.cpp new file mode 100644 index 00000000000..d5b0985fbb1 --- /dev/null +++ b/src/interface/TWBitcoinMessageSigner.cpp @@ -0,0 +1,22 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include + +#include "Bitcoin/MessageSigner.h" + +TWString* _Nonnull TWBitcoinMessageSignerSignMessage(const struct TWPrivateKey* _Nonnull privateKey, TWString* _Nonnull address, TWString* _Nonnull message) { + try { + const auto signature = TW::Bitcoin::MessageSigner::signMessage(privateKey->impl, TWStringUTF8Bytes(address), TWStringUTF8Bytes(message), true); + return TWStringCreateWithUTF8Bytes(signature.c_str()); + } catch (...) { + return TWStringCreateWithUTF8Bytes(""); + } +} + +bool TWBitcoinMessageSignerVerifyMessage(TWString* _Nonnull address, TWString* _Nonnull message, TWString* _Nonnull signature) { + return TW::Bitcoin::MessageSigner::verifyMessage(TWStringUTF8Bytes(address), TWStringUTF8Bytes(message), TWStringUTF8Bytes(signature)); +} diff --git a/src/interface/TWBitcoinScript.cpp b/src/interface/TWBitcoinScript.cpp index 32601fc5238..8b685973143 100644 --- a/src/interface/TWBitcoinScript.cpp +++ b/src/interface/TWBitcoinScript.cpp @@ -11,8 +11,6 @@ #include -using namespace TW::Bitcoin; - struct TWBitcoinScript *_Nonnull TWBitcoinScriptCreate() { auto* script = new TWBitcoinScript{}; return script; @@ -122,38 +120,44 @@ TWData *TWBitcoinScriptEncode(const struct TWBitcoinScript *script) { struct TWBitcoinScript *TWBitcoinScriptBuildPayToPublicKey(TWData *pubkey) { auto* v = reinterpret_cast*>(pubkey); - auto script = Script::buildPayToPublicKey(*v); - return new TWBitcoinScript{ script }; + auto script = TW::Bitcoin::Script::buildPayToPublicKey(*v); + return new TWBitcoinScript{ script };//win + //return new TWBitcoinScript{ .impl = script }; } struct TWBitcoinScript *TWBitcoinScriptBuildPayToPublicKeyHash(TWData *hash) { auto* v = reinterpret_cast*>(hash); - auto script = Script::buildPayToPublicKeyHash(*v); - return new TWBitcoinScript{ script }; + auto script = TW::Bitcoin::Script::buildPayToPublicKeyHash(*v); + return new TWBitcoinScript{ script };//win + //return new TWBitcoinScript{ .impl = script }; } struct TWBitcoinScript *TWBitcoinScriptBuildPayToScriptHash(TWData *scriptHash) { auto* v = reinterpret_cast*>(scriptHash); - auto script = Script::buildPayToScriptHash(*v); - return new TWBitcoinScript{ script }; + auto script = TW::Bitcoin::Script::buildPayToScriptHash(*v); + return new TWBitcoinScript{ script };//win + //return new TWBitcoinScript{ .impl = script }; } struct TWBitcoinScript *TWBitcoinScriptBuildPayToWitnessPubkeyHash(TWData *hash) { auto* v = reinterpret_cast*>(hash); - auto script = Script::buildPayToWitnessPublicKeyHash(*v); - return new TWBitcoinScript{ script }; + auto script = TW::Bitcoin::Script::buildPayToWitnessPublicKeyHash(*v); + return new TWBitcoinScript{ script };//win + //return new TWBitcoinScript{ .impl = script }; } struct TWBitcoinScript *TWBitcoinScriptBuildPayToWitnessScriptHash(TWData *scriptHash) { auto* v = reinterpret_cast*>(scriptHash); - auto script = Script::buildPayToWitnessScriptHash(*v); - return new TWBitcoinScript{ script }; + auto script = TW::Bitcoin::Script::buildPayToWitnessScriptHash(*v); + return new TWBitcoinScript{ script };//win + //return new TWBitcoinScript{ .impl = script }; } struct TWBitcoinScript *_Nonnull TWBitcoinScriptLockScriptForAddress(TWString *_Nonnull address, enum TWCoinType coin) { auto* s = reinterpret_cast(address); - auto script = Script::lockScriptForAddress(*s, coin); - return new TWBitcoinScript{ script }; + auto script = TW::Bitcoin::Script::lockScriptForAddress(*s, coin); + //return new TWBitcoinScript{ .impl = script }; + return new TWBitcoinScript{ script };//win } uint32_t TWBitcoinScriptHashTypeForCoin(enum TWCoinType coinType) { diff --git a/src/interface/TWBitcoin.cpp b/src/interface/TWBitcoinSigHashType.cpp similarity index 100% rename from src/interface/TWBitcoin.cpp rename to src/interface/TWBitcoinSigHashType.cpp diff --git a/src/interface/TWCardano.cpp b/src/interface/TWCardano.cpp new file mode 100644 index 00000000000..9c1c43ed16d --- /dev/null +++ b/src/interface/TWCardano.cpp @@ -0,0 +1,31 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include + +#include "Cardano/Transaction.h" +#include "Cardano/AddressV3.h" +#include "proto/Cardano.pb.h" + +using namespace TW; + +uint64_t TWCardanoMinAdaAmount(TWData *_Nonnull tokenBundle) { + const Data* bundleData = static_cast(tokenBundle); + TW::Cardano::Proto::TokenBundle bundleProto; + if (bundleData && bundleProto.ParseFromArray(bundleData->data(), (int)bundleData->size())) { + return TW::Cardano::TokenBundle::fromProto(bundleProto).minAdaAmount(); + } + return 0; +} + +TWString *_Nonnull TWCardanoGetStakingAddress(TWString *_Nonnull baseAddress) { + const auto& address = *reinterpret_cast(baseAddress); + try { + return TWStringCreateWithUTF8Bytes(TW::Cardano::AddressV3(address).getStakingAddress().c_str()); + } catch (...) { + return TWStringCreateWithUTF8Bytes(""); + } +} diff --git a/src/interface/TWCoinType.cpp b/src/interface/TWCoinType.cpp index a99ecee7c75..2d58d5a7be5 100644 --- a/src/interface/TWCoinType.cpp +++ b/src/interface/TWCoinType.cpp @@ -39,6 +39,12 @@ TWString *_Nonnull TWCoinTypeDerivationPath(enum TWCoinType coin) { return TWStringCreateWithUTF8Bytes(string.c_str()); } +TWString* TWCoinTypeDerivationPathWithDerivation(enum TWCoinType coin, enum TWDerivation derivation) { + const auto path = TW::derivationPath(coin, derivation); + const auto string = path.string(); + return TWStringCreateWithUTF8Bytes(string.c_str()); +} + TWString *_Nonnull TWCoinTypeDeriveAddress(enum TWCoinType coin, struct TWPrivateKey *_Nonnull privateKey) { const auto string = TW::deriveAddress(coin, privateKey->impl); return TWStringCreateWithUTF8Bytes(string.c_str()); @@ -65,6 +71,14 @@ uint8_t TWCoinTypeStaticPrefix(enum TWCoinType coin) { return TW::staticPrefix(coin); } +TWString* _Nonnull TWCoinTypeChainId(enum TWCoinType coin) { + return TWStringCreateWithUTF8Bytes(TW::chainId(coin)); +} + uint32_t TWCoinTypeSlip44Id(enum TWCoinType coin) { return TW::slip44Id(coin); } + +enum TWPublicKeyType TWCoinTypePublicKeyType(enum TWCoinType coin) { + return TW::publicKeyType(coin); +} diff --git a/src/interface/TWData.cpp b/src/interface/TWData.cpp index da2b67fd14e..b08b5e7cb94 100644 --- a/src/interface/TWData.cpp +++ b/src/interface/TWData.cpp @@ -14,20 +14,20 @@ using namespace TW; TWData *_Nonnull TWDataCreateWithBytes(const uint8_t *_Nonnull bytes, size_t size) { - auto* data = new std::vector(); + auto* data = new Data(); data->reserve(size); std::copy(bytes, bytes + size, std::back_inserter(*data)); return data; } TWData *_Nonnull TWDataCreateWithSize(size_t size) { - auto* data = new std::vector(size, 0); + auto* data = new Data(size, 0); return data; } TWData *_Nonnull TWDataCreateWithData(TWData *_Nonnull data) { - auto* other = reinterpret_cast*>(data); - auto* copy = new std::vector(*other); + auto* other = reinterpret_cast(data); + auto* copy = new Data(*other); return copy; } @@ -40,69 +40,69 @@ TWData* TWDataCreateWithHexString(const TWString* hex) { } size_t TWDataSize(TWData *_Nonnull data) { - auto* v = reinterpret_cast*>(data); + auto* v = reinterpret_cast(data); return v->size(); } uint8_t *_Nonnull TWDataBytes(TWData *_Nonnull data) { - auto* v = const_cast*>(reinterpret_cast*>(data)); + auto* v = const_cast(reinterpret_cast(data)); return v->data(); } uint8_t TWDataGet(TWData *_Nonnull data, size_t index) { - auto* v = reinterpret_cast*>(data); + auto* v = reinterpret_cast(data); return (*v)[index]; } void TWDataSet(TWData *_Nonnull data, size_t index, uint8_t byte) { - auto* v = const_cast*>(reinterpret_cast*>(data)); + auto* v = const_cast(reinterpret_cast(data)); (*v)[index] = byte; } void TWDataCopyBytes(TWData *_Nonnull data, size_t start, size_t size, uint8_t *_Nonnull output) { - auto* v = reinterpret_cast*>(data); + auto* v = reinterpret_cast(data); std::copy(std::begin(*v) + start, std::begin(*v) + start + size, output); } void TWDataReplaceBytes(TWData *_Nonnull data, size_t start, size_t size, const uint8_t *_Nonnull bytes) { - auto* v = const_cast*>(reinterpret_cast*>(data)); + auto* v = const_cast(reinterpret_cast(data)); std::copy(bytes, bytes + size, std::begin(*v) + start); } void TWDataAppendBytes(TWData *_Nonnull data, const uint8_t *_Nonnull bytes, size_t size) { - auto* v = const_cast*>(reinterpret_cast*>(data)); - for (auto i = 0; i < size; i += 1) + auto* v = const_cast(reinterpret_cast(data)); + for (auto i = 0ul; i < size; i += 1) v->push_back(bytes[i]); } void TWDataAppendByte(TWData *_Nonnull data, uint8_t byte) { - auto* v = const_cast*>(reinterpret_cast*>(data)); + auto* v = const_cast(reinterpret_cast(data)); v->push_back(byte); } void TWDataAppendData(TWData *_Nonnull data, TWData *_Nonnull append) { - auto* v = const_cast*>(reinterpret_cast*>(data)); - auto* av = reinterpret_cast*>(append); + auto* v = const_cast(reinterpret_cast(data)); + auto* av = reinterpret_cast(append); std::copy(av->begin(), av->end(), std::back_inserter(*v)); } void TWDataReverse(TWData *_Nonnull data) { - auto* v = const_cast*>(reinterpret_cast*>(data)); + auto* v = const_cast(reinterpret_cast(data)); std::reverse(std::begin(*v), std::end(*v)); } void TWDataReset(TWData *_Nonnull data) { - auto* v = const_cast*>(reinterpret_cast*>(data)); + auto* v = const_cast(reinterpret_cast(data)); std::fill(std::begin(*v), std::end(*v), 0); } void TWDataDelete(TWData *_Nonnull data) { - auto* v = reinterpret_cast*>(data); + auto* v = reinterpret_cast(data); delete v; } bool TWDataEqual(TWData *_Nonnull lhs, TWData *_Nonnull rhs) { - auto* lv = reinterpret_cast*>(lhs); - auto* rv = reinterpret_cast*>(rhs); + auto* lv = reinterpret_cast(lhs); + auto* rv = reinterpret_cast(rhs); return *lv == *rv; } diff --git a/src/interface/TWDataVector.cpp b/src/interface/TWDataVector.cpp new file mode 100644 index 00000000000..bea89cc1dac --- /dev/null +++ b/src/interface/TWDataVector.cpp @@ -0,0 +1,54 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include + +#include "Data.h" + +#include +#include + +using namespace TW; + + +struct TWDataVector { + std::vector impl; +}; + + +struct TWDataVector *_Nonnull TWDataVectorCreate() { + auto* obj = new struct TWDataVector(); + assert(obj != nullptr); + return obj; +} + +struct TWDataVector *_Nonnull TWDataVectorCreateWithData(TWData *_Nonnull data) { + auto* obj = new struct TWDataVector(); + assert(obj != nullptr); + + TWDataVectorAdd(obj, data); + return obj; +} + +void TWDataVectorDelete(struct TWDataVector *_Nonnull dataVector) { + delete dataVector; +} + +void TWDataVectorAdd(struct TWDataVector *_Nonnull dataVector, TWData *_Nonnull data) { + dataVector->impl.push_back(TW::data(TWDataBytes(data), TWDataSize(data))); +} + +size_t TWDataVectorSize(const struct TWDataVector *_Nonnull dataVector) { + return dataVector->impl.size(); +} + +TWData *_Nullable TWDataVectorGet(const struct TWDataVector *_Nonnull dataVector, size_t index) { + if (index >= dataVector->impl.size()) { + return nullptr; + } + auto& elem = dataVector->impl[index]; + return TWDataCreateWithBytes(elem.data(), elem.size()); +} diff --git a/src/interface/TWDerivationPath.cpp b/src/interface/TWDerivationPath.cpp new file mode 100644 index 00000000000..f48bc051d77 --- /dev/null +++ b/src/interface/TWDerivationPath.cpp @@ -0,0 +1,65 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include +#include + +#include "../DerivationPath.h" + +using namespace TW; + +struct TWDerivationPath* _Nonnull TWDerivationPathCreate(enum TWPurpose purpose, uint32_t coin, uint32_t account, uint32_t change, uint32_t address) { + return new TWDerivationPath{DerivationPath(purpose, coin, account, change, address)}; +} + +struct TWDerivationPath* _Nullable TWDerivationPathCreateWithString(TWString* _Nonnull string) { + auto& str = *reinterpret_cast(string); + try { + return new TWDerivationPath{DerivationPath(str)}; + } catch (...) { + return nullptr; + } +} + +void TWDerivationPathDelete(struct TWDerivationPath* _Nonnull path) { + delete path; +} + +uint32_t TWDerivationPathIndicesCount(struct TWDerivationPath* _Nonnull path) { + return static_cast(path->impl.indices.size()); +} + +struct TWDerivationPathIndex* _Nullable TWDerivationPathIndexAt(struct TWDerivationPath* _Nonnull path, uint32_t index) { + if (index >= path->impl.indices.size()) { + return nullptr; + } + return new TWDerivationPathIndex{path->impl.indices[index]}; +} + +enum TWPurpose TWDerivationPathPurpose(struct TWDerivationPath* _Nonnull path) { + return path->impl.purpose(); +} + +uint32_t TWDerivationPathCoin(struct TWDerivationPath* _Nonnull path) { + return path->impl.coin(); +} + +uint32_t TWDerivationPathAccount(struct TWDerivationPath* _Nonnull path) { + return path->impl.account(); +} + +uint32_t TWDerivationPathChange(struct TWDerivationPath* _Nonnull path) { + return path->impl.change(); +} + +uint32_t TWDerivationPathAddress(struct TWDerivationPath* _Nonnull path) { + return path->impl.address(); +} + +TWString* _Nonnull TWDerivationPathDescription(struct TWDerivationPath* _Nonnull path) { + return TWStringCreateWithUTF8Bytes(path->impl.string().c_str()); +} diff --git a/src/interface/TWDerivationPathIndex.cpp b/src/interface/TWDerivationPathIndex.cpp new file mode 100644 index 00000000000..f443955191e --- /dev/null +++ b/src/interface/TWDerivationPathIndex.cpp @@ -0,0 +1,32 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include + +#include "../DerivationPath.h" + +using namespace TW; + +struct TWDerivationPathIndex* _Nonnull TWDerivationPathIndexCreate(uint32_t value, bool hardened) { + return new TWDerivationPathIndex{DerivationPathIndex(value, hardened)}; +} + +void TWDerivationPathIndexDelete(struct TWDerivationPathIndex* _Nonnull index) { + delete index; +} + +uint32_t TWDerivationPathIndexValue(struct TWDerivationPathIndex* _Nonnull index) { + return index->impl.value; +} + +bool TWDerivationPathIndexHardened(struct TWDerivationPathIndex* _Nonnull index) { + return index->impl.hardened; +} + +TWString* _Nonnull TWDerivationPathIndexDescription(struct TWDerivationPathIndex* _Nonnull index) { + return TWStringCreateWithUTF8Bytes(index->impl.string().c_str()); +} diff --git a/src/interface/TWEthereumAbi.cpp b/src/interface/TWEthereumAbi.cpp index 9156b9ea315..2ea781199eb 100644 --- a/src/interface/TWEthereumAbi.cpp +++ b/src/interface/TWEthereumAbi.cpp @@ -17,14 +17,12 @@ #include #include -using namespace TW::Ethereum::ABI; -using namespace TW::Ethereum; using namespace TW; - +namespace EthAbi = TW::Ethereum::ABI; TWData* _Nonnull TWEthereumAbiEncode(struct TWEthereumAbiFunction* _Nonnull func_in) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; Data encoded; function.encode(encoded); @@ -34,7 +32,7 @@ TWData* _Nonnull TWEthereumAbiEncode(struct TWEthereumAbiFunction* _Nonnull func bool TWEthereumAbiDecodeOutput(struct TWEthereumAbiFunction* _Nonnull func_in, TWData* _Nonnull encoded) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; assert(encoded != nullptr); Data encData = data(TWDataBytes(encoded), TWDataSize(encoded)); @@ -45,9 +43,9 @@ bool TWEthereumAbiDecodeOutput(struct TWEthereumAbiFunction* _Nonnull func_in, TWString* _Nullable TWEthereumAbiDecodeCall(TWData* _Nonnull callData, TWString* _Nonnull abiString) { const Data& call = *(reinterpret_cast(callData)); const auto& jsonString = *reinterpret_cast(abiString); - try { + try { auto abi = nlohmann::json::parse(jsonString); - auto string = decodeCall(call, abi); + auto string = EthAbi::decodeCall(call, abi); if (!string.has_value()) { return nullptr; } @@ -61,9 +59,7 @@ TWString* _Nullable TWEthereumAbiDecodeCall(TWData* _Nonnull callData, TWString* TWData* _Nonnull TWEthereumAbiEncodeTyped(TWString* _Nonnull messageJson) { Data data; try { - data = ParamStruct::hashStructJson(TWStringUTF8Bytes(messageJson)); - } catch (...) { - // return empty - } + data = EthAbi::ParamStruct::hashStructJson(TWStringUTF8Bytes(messageJson)); + } catch (...) {} // return empty return TWDataCreateWithBytes(data.data(), data.size()); } diff --git a/src/interface/TWEthereumAbiFunction.cpp b/src/interface/TWEthereumAbiFunction.cpp index 93b451c2a51..3951b949f5b 100644 --- a/src/interface/TWEthereumAbiFunction.cpp +++ b/src/interface/TWEthereumAbiFunction.cpp @@ -16,11 +16,10 @@ #include using namespace TW; -using namespace TW::Ethereum; -using namespace TW::Ethereum::ABI; +namespace EthAbi = TW::Ethereum::ABI; struct TWEthereumAbiFunction *_Nonnull TWEthereumAbiFunctionCreateWithString(TWString *_Nonnull name) { - auto func = Function(TWStringUTF8Bytes(name)); + auto func = EthAbi::Function(TWStringUTF8Bytes(name)); return new TWEthereumAbiFunction{ func }; } @@ -40,173 +39,173 @@ TWString *_Nonnull TWEthereumAbiFunctionGetType(struct TWEthereumAbiFunction *_N int TWEthereumAbiFunctionAddParamUInt8(struct TWEthereumAbiFunction *_Nonnull func_in, uint8_t val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; - auto param = std::make_shared(val); + auto param = std::make_shared(val); auto idx = function.addParam(param, isOutput); return idx; } int TWEthereumAbiFunctionAddParamUInt16(struct TWEthereumAbiFunction *_Nonnull func_in, uint16_t val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; - auto param = std::make_shared(val); + auto param = std::make_shared(val); auto idx = function.addParam(param, isOutput); return idx; } int TWEthereumAbiFunctionAddParamUInt32(struct TWEthereumAbiFunction *_Nonnull func_in, uint32_t val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; - auto param = std::make_shared(val); + auto param = std::make_shared(val); auto idx = function.addParam(param, isOutput); return idx; } int TWEthereumAbiFunctionAddParamUInt64(struct TWEthereumAbiFunction *_Nonnull func_in, uint64_t val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; - auto param = std::make_shared(val); + auto param = std::make_shared(val); auto idx = function.addParam(param, isOutput); return idx; } int TWEthereumAbiFunctionAddParamUInt256(struct TWEthereumAbiFunction *_Nonnull func_in, TWData *_Nonnull val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; uint256_t val2 = load(*static_cast(val)); - auto param = std::make_shared(val2); + auto param = std::make_shared(val2); auto idx = function.addParam(param, isOutput); return idx; } int TWEthereumAbiFunctionAddParamUIntN(struct TWEthereumAbiFunction *_Nonnull func_in, int bits, TWData *_Nonnull val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; uint256_t val2 = load(*static_cast(val)); - auto param = std::make_shared(bits, val2); + auto param = std::make_shared(bits, val2); auto idx = function.addParam(param, isOutput); return idx; } int TWEthereumAbiFunctionAddParamInt8(struct TWEthereumAbiFunction *_Nonnull func_in, int8_t val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; - auto param = std::make_shared(val); + auto param = std::make_shared(val); auto idx = function.addParam(param, isOutput); return idx; } int TWEthereumAbiFunctionAddParamInt16(struct TWEthereumAbiFunction *_Nonnull func_in, int16_t val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; - auto param = std::make_shared(val); + auto param = std::make_shared(val); auto idx = function.addParam(param, isOutput); return idx; } int TWEthereumAbiFunctionAddParamInt32(struct TWEthereumAbiFunction *_Nonnull func_in, int32_t val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; - auto param = std::make_shared(val); + auto param = std::make_shared(val); auto idx = function.addParam(param, isOutput); return idx; } int TWEthereumAbiFunctionAddParamInt64(struct TWEthereumAbiFunction *_Nonnull func_in, int64_t val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; - auto param = std::make_shared(val); + auto param = std::make_shared(val); auto idx = function.addParam(param, isOutput); return idx; } int TWEthereumAbiFunctionAddParamInt256(struct TWEthereumAbiFunction *_Nonnull func_in, TWData *_Nonnull val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; assert(val != nullptr); - int256_t val2 = ValueEncoder::int256FromUint256(load(*static_cast(val))); - auto param = std::make_shared(val2); + int256_t val2 = EthAbi::ValueEncoder::int256FromUint256(load(*static_cast(val))); + auto param = std::make_shared(val2); auto idx = function.addParam(param, isOutput); return idx; } int TWEthereumAbiFunctionAddParamIntN(struct TWEthereumAbiFunction *_Nonnull func_in, int bits, TWData *_Nonnull val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; assert(val != nullptr); - int256_t val2 = ValueEncoder::int256FromUint256(load(*static_cast(val))); - auto param = std::make_shared(bits, val2); + int256_t val2 = EthAbi::ValueEncoder::int256FromUint256(load(*static_cast(val))); + auto param = std::make_shared(bits, val2); auto idx = function.addParam(param, isOutput); return idx; } int TWEthereumAbiFunctionAddParamBool(struct TWEthereumAbiFunction *_Nonnull func_in, bool val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; - auto param = std::make_shared(val); + auto param = std::make_shared(val); auto idx = function.addParam(param, isOutput); return idx; } int TWEthereumAbiFunctionAddParamString(struct TWEthereumAbiFunction *_Nonnull func_in, TWString *_Nonnull val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; assert(val != nullptr); - auto param = std::make_shared(TWStringUTF8Bytes(val)); + auto param = std::make_shared(TWStringUTF8Bytes(val)); auto idx = function.addParam(param, isOutput); return idx; } int TWEthereumAbiFunctionAddParamAddress(struct TWEthereumAbiFunction *_Nonnull func_in, TWData *_Nonnull val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; assert(val != nullptr); Data data = TW::data(TWDataBytes(val), TWDataSize(val)); - auto param = std::make_shared(data); + auto param = std::make_shared(data); auto idx = function.addParam(param, isOutput); return idx; } int TWEthereumAbiFunctionAddParamBytes(struct TWEthereumAbiFunction *_Nonnull func_in, TWData *_Nonnull val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; Data data = TW::data(TWDataBytes(val), TWDataSize(val)); - auto param = std::make_shared(data); + auto param = std::make_shared(data); auto idx = function.addParam(param, isOutput); return idx; } int TWEthereumAbiFunctionAddParamBytesFix(struct TWEthereumAbiFunction *_Nonnull func_in, size_t count, TWData *_Nonnull val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; Data data = TW::data(TWDataBytes(val), TWDataSize(val)); - auto param = std::make_shared(count, data); + auto param = std::make_shared(count, data); auto idx = function.addParam(param, isOutput); return idx; } int TWEthereumAbiFunctionAddParamArray(struct TWEthereumAbiFunction *_Nonnull func_in, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; - auto param = std::make_shared(); + auto param = std::make_shared(); auto idx = function.addParam(param, isOutput); return idx; } @@ -215,13 +214,13 @@ int TWEthereumAbiFunctionAddParamArray(struct TWEthereumAbiFunction *_Nonnull fu uint8_t TWEthereumAbiFunctionGetParamUInt8(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; - std::shared_ptr param; + std::shared_ptr param; if (!function.getParam(idx, param, isOutput)) { return 0; } - auto param2 = std::dynamic_pointer_cast(param); + auto param2 = std::dynamic_pointer_cast(param); if (param2 == nullptr) { return 0; } @@ -230,13 +229,13 @@ uint8_t TWEthereumAbiFunctionGetParamUInt8(struct TWEthereumAbiFunction *_Nonnul uint64_t TWEthereumAbiFunctionGetParamUInt64(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; - std::shared_ptr param; + std::shared_ptr param; if (!function.getParam(idx, param, isOutput)) { return 0; } - auto param2 = std::dynamic_pointer_cast(param); + auto param2 = std::dynamic_pointer_cast(param); if (param2 == nullptr) { return 0; } @@ -245,15 +244,15 @@ uint64_t TWEthereumAbiFunctionGetParamUInt64(struct TWEthereumAbiFunction *_Nonn TWData *_Nonnull TWEthereumAbiFunctionGetParamUInt256(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; uint256_t val256 = 0; - std::shared_ptr param; + std::shared_ptr param; if (!function.getParam(idx, param, isOutput)) { TW::Data valData = TW::store(val256); return TWDataCreateWithData(&valData); } - auto param2 = std::dynamic_pointer_cast(param); + auto param2 = std::dynamic_pointer_cast(param); if (param2 == nullptr) { TW::Data valData = TW::store(val256); return TWDataCreateWithData(&valData); @@ -265,13 +264,13 @@ TWData *_Nonnull TWEthereumAbiFunctionGetParamUInt256(struct TWEthereumAbiFuncti bool TWEthereumAbiFunctionGetParamBool(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; - std::shared_ptr param; + std::shared_ptr param; if (!function.getParam(idx, param, isOutput)) { return false; } - auto param2 = std::dynamic_pointer_cast(param); + auto param2 = std::dynamic_pointer_cast(param); if (param2 == nullptr) { return false; } @@ -280,14 +279,14 @@ bool TWEthereumAbiFunctionGetParamBool(struct TWEthereumAbiFunction *_Nonnull fu TWString *_Nonnull TWEthereumAbiFunctionGetParamString(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; std::string valStr; - std::shared_ptr param; + std::shared_ptr param; if (!function.getParam(idx, param, isOutput)) { return TWStringCreateWithUTF8Bytes(valStr.c_str()); } - auto param2 = std::dynamic_pointer_cast(param); + auto param2 = std::dynamic_pointer_cast(param); if (param2 == nullptr) { return TWStringCreateWithUTF8Bytes(valStr.c_str()); } @@ -297,14 +296,14 @@ TWString *_Nonnull TWEthereumAbiFunctionGetParamString(struct TWEthereumAbiFunct TWData *_Nonnull TWEthereumAbiFunctionGetParamAddress(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; Data valData; - std::shared_ptr param; + std::shared_ptr param; if (!function.getParam(idx, param, isOutput)) { return TWDataCreateWithData(&valData); } - auto param2 = std::dynamic_pointer_cast(param); + auto param2 = std::dynamic_pointer_cast(param); if (param2 == nullptr) { return TWDataCreateWithData(&valData); } @@ -314,12 +313,12 @@ TWData *_Nonnull TWEthereumAbiFunctionGetParamAddress(struct TWEthereumAbiFuncti ///// AddInArrayParam -int addInArrayParam(Function& function, int arrayIdx, const std::shared_ptr& childParam) { - std::shared_ptr param; +int addInArrayParam(EthAbi::Function& function, int arrayIdx, const std::shared_ptr& childParam) { + std::shared_ptr param; if (!function.getInParam(arrayIdx, param)) { return -1; } - std::shared_ptr paramArr = std::dynamic_pointer_cast(param); + std::shared_ptr paramArr = std::dynamic_pointer_cast(param); if (paramArr == nullptr) { return -1; // not an array } @@ -328,130 +327,130 @@ int addInArrayParam(Function& function, int arrayIdx, const std::shared_ptrimpl; + EthAbi::Function& function = func_in->impl; - return addInArrayParam(function, arrayIdx, std::make_shared(val)); + return addInArrayParam(function, arrayIdx, std::make_shared(val)); } int TWEthereumAbiFunctionAddInArrayParamUInt16(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, uint16_t val) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; - return addInArrayParam(function, arrayIdx, std::make_shared(val)); + return addInArrayParam(function, arrayIdx, std::make_shared(val)); } int TWEthereumAbiFunctionAddInArrayParamUInt32(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, uint32_t val) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; - return addInArrayParam(function, arrayIdx, std::make_shared(val)); + return addInArrayParam(function, arrayIdx, std::make_shared(val)); } int TWEthereumAbiFunctionAddInArrayParamUInt64(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, uint64_t val) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; - return addInArrayParam(function, arrayIdx, std::make_shared(val)); + return addInArrayParam(function, arrayIdx, std::make_shared(val)); } int TWEthereumAbiFunctionAddInArrayParamUInt256(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, TWData *_Nonnull val) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; uint256_t val2 = load(*static_cast(val)); - return addInArrayParam(function, arrayIdx, std::make_shared(val2)); + return addInArrayParam(function, arrayIdx, std::make_shared(val2)); } int TWEthereumAbiFunctionAddInArrayParamUIntN(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int bits, TWData *_Nonnull val) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; uint256_t val2 = load(*static_cast(val)); - return addInArrayParam(function, arrayIdx, std::make_shared(bits, val2)); + return addInArrayParam(function, arrayIdx, std::make_shared(bits, val2)); } int TWEthereumAbiFunctionAddInArrayParamInt8(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int8_t val) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; - return addInArrayParam(function, arrayIdx, std::make_shared(val)); + return addInArrayParam(function, arrayIdx, std::make_shared(val)); } int TWEthereumAbiFunctionAddInArrayParamInt16(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int16_t val) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; - return addInArrayParam(function, arrayIdx, std::make_shared(val)); + return addInArrayParam(function, arrayIdx, std::make_shared(val)); } int TWEthereumAbiFunctionAddInArrayParamInt32(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int32_t val) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; - return addInArrayParam(function, arrayIdx, std::make_shared(val)); + return addInArrayParam(function, arrayIdx, std::make_shared(val)); } int TWEthereumAbiFunctionAddInArrayParamInt64(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int64_t val) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; - return addInArrayParam(function, arrayIdx, std::make_shared(val)); + return addInArrayParam(function, arrayIdx, std::make_shared(val)); } int TWEthereumAbiFunctionAddInArrayParamInt256(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, TWData *_Nonnull val) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; assert(val != nullptr); - int256_t val2 = ValueEncoder::int256FromUint256(load(*static_cast(val))); - return addInArrayParam(function, arrayIdx, std::make_shared(val2)); + int256_t val2 = EthAbi::ValueEncoder::int256FromUint256(load(*static_cast(val))); + return addInArrayParam(function, arrayIdx, std::make_shared(val2)); } int TWEthereumAbiFunctionAddInArrayParamIntN(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int bits, TWData *_Nonnull val) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; assert(val != nullptr); - int256_t val2 = ValueEncoder::int256FromUint256(load(*static_cast(val))); - return addInArrayParam(function, arrayIdx, std::make_shared(bits, val2)); + int256_t val2 = EthAbi::ValueEncoder::int256FromUint256(load(*static_cast(val))); + return addInArrayParam(function, arrayIdx, std::make_shared(bits, val2)); } int TWEthereumAbiFunctionAddInArrayParamBool(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, bool val) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; - return addInArrayParam(function, arrayIdx, std::make_shared(val)); + return addInArrayParam(function, arrayIdx, std::make_shared(val)); } int TWEthereumAbiFunctionAddInArrayParamString(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, TWString *_Nonnull val) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; assert(val != nullptr); - return addInArrayParam(function, arrayIdx, std::make_shared(TWStringUTF8Bytes(val))); + return addInArrayParam(function, arrayIdx, std::make_shared(TWStringUTF8Bytes(val))); } int TWEthereumAbiFunctionAddInArrayParamAddress(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, TWData *_Nonnull val) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; assert(val != nullptr); Data data = TW::data(TWDataBytes(val), TWDataSize(val)); - return addInArrayParam(function, arrayIdx, std::make_shared(data)); + return addInArrayParam(function, arrayIdx, std::make_shared(data)); } int TWEthereumAbiFunctionAddInArrayParamBytes(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, TWData *_Nonnull val) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; Data data = TW::data(TWDataBytes(val), TWDataSize(val)); - return addInArrayParam(function, arrayIdx, std::make_shared(data)); + return addInArrayParam(function, arrayIdx, std::make_shared(data)); } int TWEthereumAbiFunctionAddInArrayParamBytesFix(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, size_t count, TWData *_Nonnull val) { assert(func_in != nullptr); - Function& function = func_in->impl; + EthAbi::Function& function = func_in->impl; Data data = TW::data(TWDataBytes(val), TWDataSize(val)); - return addInArrayParam(function, arrayIdx, std::make_shared(count, data)); + return addInArrayParam(function, arrayIdx, std::make_shared(count, data)); } diff --git a/src/interface/TWEthereumAbiValue.cpp b/src/interface/TWEthereumAbiValue.cpp index 7a6843f23b0..a3dc02612af 100644 --- a/src/interface/TWEthereumAbiValue.cpp +++ b/src/interface/TWEthereumAbiValue.cpp @@ -10,63 +10,63 @@ #include #include -using namespace TW::Ethereum; using namespace TW; +namespace EthAbi = TW::Ethereum::ABI; TWData* _Nonnull TWEthereumAbiValueEncodeBool(bool value) { Data data; - ABI::ValueEncoder::encodeBool(value, data); + EthAbi::ValueEncoder::encodeBool(value, data); return TWDataCreateWithBytes(data.data(), data.size()); } TWData* _Nonnull TWEthereumAbiValueEncodeInt32(int32_t value) { Data data; - ABI::ValueEncoder::encodeInt32(value, data); + EthAbi::ValueEncoder::encodeInt32(value, data); return TWDataCreateWithBytes(data.data(), data.size()); } TWData* _Nonnull TWEthereumAbiValueEncodeUInt32(uint32_t value) { Data data; - ABI::ValueEncoder::encodeUInt32(value, data); + EthAbi::ValueEncoder::encodeUInt32(value, data); return TWDataCreateWithBytes(data.data(), data.size()); } TWData* _Nonnull TWEthereumAbiValueEncodeInt256(TWData* _Nonnull value) { Data data; int256_t value256 = static_cast(TW::load(*reinterpret_cast(value))); - ABI::ValueEncoder::encodeInt256(value256, data); + EthAbi::ValueEncoder::encodeInt256(value256, data); return TWDataCreateWithBytes(data.data(), data.size()); } TWData* _Nonnull TWEthereumAbiValueEncodeUInt256(TWData* _Nonnull value) { Data data; uint256_t value256 = TW::load(*reinterpret_cast(value)); - ABI::ValueEncoder::encodeUInt256(value256, data); + EthAbi::ValueEncoder::encodeUInt256(value256, data); return TWDataCreateWithBytes(data.data(), data.size()); } TWData* _Nonnull TWEthereumAbiValueEncodeAddress(TWData* _Nonnull value) { Data data; - ABI::ValueEncoder::encodeAddress(*reinterpret_cast(value), data); + EthAbi::ValueEncoder::encodeAddress(*reinterpret_cast(value), data); return TWDataCreateWithBytes(data.data(), data.size()); } TWData* _Nonnull TWEthereumAbiValueEncodeString(TWString* _Nonnull value) { Data data; - ABI::ValueEncoder::encodeString(TWStringUTF8Bytes(value), data); + EthAbi::ValueEncoder::encodeString(TWStringUTF8Bytes(value), data); return TWDataCreateWithBytes(data.data(), data.size()); } TWData* _Nonnull TWEthereumAbiValueEncodeBytes(TWData* _Nonnull value) { Data data; - ABI::ValueEncoder::encodeBytes(*reinterpret_cast(value), data); + EthAbi::ValueEncoder::encodeBytes(*reinterpret_cast(value), data); return TWDataCreateWithBytes(data.data(), data.size()); } TWData* _Nonnull TWEthereumAbiValueEncodeBytesDyn(TWData* _Nonnull value) { Data data; - ABI::ValueEncoder::encodeBytesDyn(*reinterpret_cast(value), data); + EthAbi::ValueEncoder::encodeBytesDyn(*reinterpret_cast(value), data); return TWDataCreateWithBytes(data.data(), data.size()); } diff --git a/src/interface/TWEthereumFee.cpp b/src/interface/TWEthereumFee.cpp deleted file mode 100644 index 12ec6f58955..00000000000 --- a/src/interface/TWEthereumFee.cpp +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include - -#include - -#include "Ethereum/Fee.h" -#include "Data.h" -#include "HexCoding.h" -#include "uint256.h" - -#include -#include - -using namespace TW; - -TWString* _Nullable TWEthereumFeeSuggest(TWString* _Nonnull feeHistory) { - const auto& json = *reinterpret_cast(feeHistory); - try { - const auto parsed = nlohmann::json::parse(json); - const auto fee = Ethereum::Fee::suggestFee(parsed); - return TWStringCreateWithUTF8Bytes(fee.dump().c_str()); - } - catch(...) { - return nullptr; - } -} diff --git a/src/interface/TWFIOAccount.cpp b/src/interface/TWFIOAccount.cpp index b600d82b493..4cdd419aa71 100644 --- a/src/interface/TWFIOAccount.cpp +++ b/src/interface/TWFIOAccount.cpp @@ -12,7 +12,6 @@ #include using namespace TW; -using namespace TW::FIO; struct TWFIOAccount { std::string description; @@ -20,11 +19,11 @@ struct TWFIOAccount { struct TWFIOAccount *_Nullable TWFIOAccountCreateWithString(TWString *_Nonnull string) { const auto& account = *reinterpret_cast(string); - if (Address::isValid(account)) { - const auto addr = Address(account); - return new TWFIOAccount{Actor::actor(addr)}; + if (FIO::Address::isValid(account)) { + const auto addr = FIO::Address(account); + return new TWFIOAccount{FIO::Actor::actor(addr)}; } - if (Actor::validate(account)) { + if (FIO::Actor::validate(account)) { return new TWFIOAccount{account}; } return nullptr; diff --git a/src/interface/TWGroestlcoinAddress.cpp b/src/interface/TWGroestlcoinAddress.cpp index 11d28b4780a..e825cc591f1 100644 --- a/src/interface/TWGroestlcoinAddress.cpp +++ b/src/interface/TWGroestlcoinAddress.cpp @@ -11,34 +11,32 @@ #include -using namespace TW::Groestlcoin; - -bool TWGroestlcoinAddressEqual(struct TWGroestlcoinAddress *_Nonnull lhs, struct TWGroestlcoinAddress *_Nonnull rhs) { +bool TWGroestlcoinAddressEqual(struct TWGroestlcoinAddress* _Nonnull lhs, struct TWGroestlcoinAddress* _Nonnull rhs) { return lhs->impl.bytes == rhs->impl.bytes; } -bool TWGroestlcoinAddressIsValidString(TWString *_Nonnull string) { +bool TWGroestlcoinAddressIsValidString(TWString* _Nonnull string) { auto& s = *reinterpret_cast(string); - return Address::isValid(s); + return TW::Groestlcoin::Address::isValid(s); } -struct TWGroestlcoinAddress *_Nullable TWGroestlcoinAddressCreateWithString(TWString *_Nonnull string) { +struct TWGroestlcoinAddress* _Nullable TWGroestlcoinAddressCreateWithString(TWString* _Nonnull string) { auto& s = *reinterpret_cast(string); - if (!Address::isValid(s)) { + if (!TW::Groestlcoin::Address::isValid(s)) { return nullptr; } - return new TWGroestlcoinAddress{ Address(s) }; + return new TWGroestlcoinAddress{TW::Groestlcoin::Address(s)}; } -struct TWGroestlcoinAddress *_Nonnull TWGroestlcoinAddressCreateWithPublicKey(struct TWPublicKey *_Nonnull publicKey, uint8_t prefix) { - return new TWGroestlcoinAddress{ Address(publicKey->impl, prefix) }; +struct TWGroestlcoinAddress* _Nonnull TWGroestlcoinAddressCreateWithPublicKey(struct TWPublicKey* _Nonnull publicKey, uint8_t prefix) { + return new TWGroestlcoinAddress{TW::Groestlcoin::Address(publicKey->impl, prefix)}; } -void TWGroestlcoinAddressDelete(struct TWGroestlcoinAddress *_Nonnull address) { +void TWGroestlcoinAddressDelete(struct TWGroestlcoinAddress* _Nonnull address) { delete address; } -TWString *_Nonnull TWGroestlcoinAddressDescription(struct TWGroestlcoinAddress *_Nonnull address) { +TWString* _Nonnull TWGroestlcoinAddressDescription(struct TWGroestlcoinAddress* _Nonnull address) { const auto str = address->impl.string(); return TWStringCreateWithUTF8Bytes(str.c_str()); } diff --git a/src/interface/TWHDWallet.cpp b/src/interface/TWHDWallet.cpp index 11859f798d5..18447aa54bd 100644 --- a/src/interface/TWHDWallet.cpp +++ b/src/interface/TWHDWallet.cpp @@ -89,6 +89,12 @@ struct TWPrivateKey *_Nonnull TWHDWalletGetDerivedKey(struct TWHDWallet *_Nonnul return new TWPrivateKey{ wallet->impl.getKey(coin, derivationPath) }; } +struct TWPrivateKey *_Nonnull TWHDWalletGetKeyByCurve(struct TWHDWallet *_Nonnull wallet, enum TWCurve curve, TWString *_Nonnull derivationPath) { + auto& s = *reinterpret_cast(derivationPath); + const auto path = DerivationPath(s); + return new TWPrivateKey{ wallet->impl.getKeyByCurve(curve, path)}; +} + TWString *_Nonnull TWHDWalletGetExtendedPrivateKey(struct TWHDWallet *wallet, TWPurpose purpose, TWCoinType coin, TWHDVersion version) { return new std::string(wallet->impl.getExtendedPrivateKey(purpose, coin, version)); } @@ -97,6 +103,22 @@ TWString *_Nonnull TWHDWalletGetExtendedPublicKey(struct TWHDWallet *wallet, TWP return new std::string(wallet->impl.getExtendedPublicKey(purpose, coin, version)); } +TWString *_Nonnull TWHDWalletGetExtendedPrivateKeyAccount(struct TWHDWallet *wallet, TWPurpose purpose, TWCoinType coin, TWDerivation derivation, TWHDVersion version, uint32_t account) { + return new std::string(wallet->impl.getExtendedPrivateKeyAccount(purpose, coin, derivation, version, account)); +} + +TWString *_Nonnull TWHDWalletGetExtendedPublicKeyAccount(struct TWHDWallet *wallet, TWPurpose purpose, TWCoinType coin, TWDerivation derivation, TWHDVersion version, uint32_t account) { + return new std::string(wallet->impl.getExtendedPublicKeyAccount(purpose, coin, derivation, version, account)); +} + +TWString *_Nonnull TWHDWalletGetExtendedPrivateKeyDerivation(struct TWHDWallet *wallet, TWPurpose purpose, TWCoinType coin, TWDerivation derivation, TWHDVersion version) { + return new std::string(wallet->impl.getExtendedPrivateKeyDerivation(purpose, coin, derivation, version)); +} + +TWString *_Nonnull TWHDWalletGetExtendedPublicKeyDerivation(struct TWHDWallet *wallet, TWPurpose purpose, TWCoinType coin, TWDerivation derivation, TWHDVersion version) { + return new std::string(wallet->impl.getExtendedPublicKeyDerivation(purpose, coin, derivation, version)); +} + TWPublicKey *TWHDWalletGetPublicKeyFromExtended(TWString *_Nonnull extended, enum TWCoinType coin, TWString *_Nonnull derivationPath) { const auto derivationPathObject = DerivationPath(*reinterpret_cast(derivationPath)); auto publicKey = HDWallet::getPublicKeyFromExtended(*reinterpret_cast(extended), coin, derivationPathObject); diff --git a/src/interface/TWHash.cpp b/src/interface/TWHash.cpp index 5b335f58677..fc442c13fcd 100644 --- a/src/interface/TWHash.cpp +++ b/src/interface/TWHash.cpp @@ -6,10 +6,9 @@ #include #include "../Hash.h" -#include "../Data.h" +#include "Data.h" #include "BinaryCoding.h" -#include "XXHash64.h" #include #include #include @@ -79,16 +78,6 @@ TWData* _Nonnull TWHashGroestl512(TWData* _Nonnull data) { return TWDataCreateWithBytes(result.data(), result.size()); } -TWData* _Nonnull TWHashXXHash64(TWData* _Nonnull data, uint64_t seed) { - const auto result = Hash::xxhash64(reinterpret_cast(TWDataBytes(data)), TWDataSize(data), seed); - return TWDataCreateWithBytes(result.data(), result.size()); -} - -TWData* _Nonnull TWHashTwoXXHash64Concat(TWData* _Nonnull data) { - const auto result = Hash::xxhash64concat(reinterpret_cast(TWDataBytes(data)), TWDataSize(data)); - return TWDataCreateWithBytes(result.data(), result.size()); -} - TWData* _Nonnull TWHashSHA256SHA256(TWData* _Nonnull data) { const auto result = Hash::sha256d(reinterpret_cast(TWDataBytes(data)), TWDataSize(data)); return TWDataCreateWithBytes(result.data(), result.size()); diff --git a/src/interface/TWNEARAccount.cpp b/src/interface/TWNEARAccount.cpp index 959fffcd049..84dca468ef0 100644 --- a/src/interface/TWNEARAccount.cpp +++ b/src/interface/TWNEARAccount.cpp @@ -13,7 +13,6 @@ #include using namespace TW; -using namespace TW::NEAR; struct TWNEARAccount { std::string description; @@ -21,11 +20,11 @@ struct TWNEARAccount { struct TWNEARAccount *_Nullable TWNEARAccountCreateWithString(TWString *_Nonnull string) { const auto& account = *reinterpret_cast(string); - if (Address::isValid(account)) { - const auto addr = Address(account); + if (TW::NEAR::Address::isValid(account)) { + const auto addr = TW::NEAR::Address(account); return new TWNEARAccount{addr.string()}; } - if (Account::isValid(account)) { + if (TW::NEAR::Account::isValid(account)) { return new TWNEARAccount{account}; } return nullptr; diff --git a/src/interface/TWNervosAddress.cpp b/src/interface/TWNervosAddress.cpp new file mode 100644 index 00000000000..963ddf763ff --- /dev/null +++ b/src/interface/TWNervosAddress.cpp @@ -0,0 +1,51 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "TrustWalletCore/TWData.h" +#include "../Base58.h" +#include "../Nervos/Address.h" + +#include + +#include + +bool TWNervosAddressEqual(struct TWNervosAddress *_Nonnull lhs, struct TWNervosAddress *_Nonnull rhs) { + return lhs->impl == rhs->impl; +} + +bool TWNervosAddressIsValidString(TWString *_Nonnull string) { + auto& s = *reinterpret_cast(string); + return TW::Nervos::Address::isValid(s); +} + +struct TWNervosAddress *_Nullable TWNervosAddressCreateWithString(TWString *_Nonnull string) { + auto& s = *reinterpret_cast(string); + try { + return new TWNervosAddress{ TW::Nervos::Address(s) }; + } catch (...) { + return nullptr; + } +} + +void TWNervosAddressDelete(struct TWNervosAddress *_Nonnull address) { + delete address; +} + +TWString *_Nonnull TWNervosAddressDescription(struct TWNervosAddress *_Nonnull address) { + return TWStringCreateWithUTF8Bytes(address->impl.string().c_str()); +} + +TWData *_Nonnull TWNervosAddressCodeHash(struct TWNervosAddress *_Nonnull address) { + return TWDataCreateWithBytes(address->impl.codeHash.data(), address->impl.codeHash.size()); +} + +TWString *_Nonnull TWNervosAddressHashType(struct TWNervosAddress *_Nonnull address) { + return TWStringCreateWithUTF8Bytes(address->impl.hashTypeString().c_str()); +} + +TWData *_Nonnull TWNervosAddressArgs(struct TWNervosAddress *_Nonnull address) { + return TWDataCreateWithBytes(address->impl.args.data(), address->impl.args.size()); +} diff --git a/src/interface/TWPBKDF2.cpp b/src/interface/TWPBKDF2.cpp new file mode 100644 index 00000000000..2ea0e4be9ec --- /dev/null +++ b/src/interface/TWPBKDF2.cpp @@ -0,0 +1,48 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include +#include + +#include "Data.h" + +using namespace TW; + +TWData* _Nullable TWPBKDF2HmacSha256(TWData* _Nonnull password, TWData* _Nonnull salt, + uint32_t iterations, uint32_t dkLen) { + + Data key(dkLen); + int passLen = static_cast(TWDataSize(password)); + int saltLen = static_cast(TWDataSize(salt)); + pbkdf2_hmac_sha256( + TWDataBytes(password), + passLen, + TWDataBytes(salt), + saltLen, + iterations, + key.data(), + dkLen + ); + return TWDataCreateWithData(&key); +} + +TWData* _Nullable TWPBKDF2HmacSha512(TWData* _Nonnull password, TWData* _Nonnull salt, + uint32_t iterations, uint32_t dkLen) { + Data key(dkLen); + int passLen = static_cast(TWDataSize(password)); + int saltLen = static_cast(TWDataSize(salt)); + pbkdf2_hmac_sha512( + TWDataBytes(password), + passLen, + TWDataBytes(salt), + saltLen, + iterations, + key.data(), + dkLen + ); + return TWDataCreateWithData(&key); +} diff --git a/src/interface/TWPrivateKey.cpp b/src/interface/TWPrivateKey.cpp index e6d2a846d52..ab91378d9ca 100644 --- a/src/interface/TWPrivateKey.cpp +++ b/src/interface/TWPrivateKey.cpp @@ -17,8 +17,8 @@ using namespace TW; struct TWPrivateKey *TWPrivateKeyCreate() { - Data bytes(PrivateKey::size); - random_buffer(bytes.data(), PrivateKey::size); + Data bytes(PrivateKey::_size); + random_buffer(bytes.data(), PrivateKey::_size); if (!PrivateKey::isValid(bytes)) { // Under no circumstance return an invalid private key. We'd rather // crash. This also captures cases where the random generator fails @@ -81,8 +81,8 @@ struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeyEd25519Blake2b(struct TWPri return new TWPublicKey{ pk->impl.getPublicKey(TWPublicKeyTypeED25519Blake2b) }; } -struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeyEd25519Extended(struct TWPrivateKey *_Nonnull pk) { - return new TWPublicKey{ pk->impl.getPublicKey(TWPublicKeyTypeED25519Extended) }; +struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeyEd25519Cardano(struct TWPrivateKey *_Nonnull pk) { + return new TWPublicKey{ pk->impl.getPublicKey(TWPublicKeyTypeED25519Cardano) }; } struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeyCurve25519(struct TWPrivateKey *_Nonnull pk) { @@ -108,9 +108,9 @@ TWData *TWPrivateKeySign(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull dige } } -TWData *TWPrivateKeySignAsDER(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull digest, enum TWCurve curve) { +TWData* TWPrivateKeySignAsDER(struct TWPrivateKey* pk, TWData* digest) { auto& d = *reinterpret_cast(digest); - auto result = pk->impl.signAsDER(d, curve); + auto result = pk->impl.signAsDER(d); if (result.empty()) { return nullptr; } else { @@ -118,9 +118,9 @@ TWData *TWPrivateKeySignAsDER(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull } } -TWData *TWPrivateKeySignSchnorr(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull message, enum TWCurve curve) { +TWData *TWPrivateKeySignZilliqaSchnorr(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull message) { const auto& msg = *reinterpret_cast(message); - auto result = pk->impl.signSchnorr(msg, curve); + auto result = pk->impl.signZilliqa(msg); if (result.empty()) { return nullptr; diff --git a/src/interface/TWPublicKey.cpp b/src/interface/TWPublicKey.cpp index 10d68e60a7f..61ada495ab1 100644 --- a/src/interface/TWPublicKey.cpp +++ b/src/interface/TWPublicKey.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -48,16 +48,22 @@ struct TWPublicKey *_Nonnull TWPublicKeyUncompressed(struct TWPublicKey *_Nonnul return new TWPublicKey{ pk->impl.extended() }; } -bool TWPublicKeyVerify(struct TWPublicKey *_Nonnull pk, TWData *signature, TWData *message) { +bool TWPublicKeyVerify(struct TWPublicKey *_Nonnull pk, TWData *_Nonnull signature, TWData *message) { const auto& s = *reinterpret_cast(signature); const auto& m = *reinterpret_cast(message); return pk->impl.verify(s, m); } -bool TWPublicKeyVerifySchnorr(struct TWPublicKey *_Nonnull pk, TWData *_Nonnull signature, TWData *_Nonnull message) { +bool TWPublicKeyVerifyAsDER(struct TWPublicKey *_Nonnull pk, TWData *_Nonnull signature, TWData *message) { const auto& s = *reinterpret_cast(signature); const auto& m = *reinterpret_cast(message); - return pk->impl.verifySchnorr(s, m); + return pk->impl.verifyAsDER(s, m); +} + +bool TWPublicKeyVerifyZilliqaSchnorr(struct TWPublicKey *_Nonnull pk, TWData *_Nonnull signature, TWData *_Nonnull message) { + const auto& s = *reinterpret_cast(signature); + const auto& m = *reinterpret_cast(message); + return pk->impl.verifyZilliqa(s, m); } enum TWPublicKeyType TWPublicKeyKeyType(struct TWPublicKey *_Nonnull publicKey) { diff --git a/src/interface/TWRippleXAddress.cpp b/src/interface/TWRippleXAddress.cpp index 464e88bafa1..f003e5614a0 100644 --- a/src/interface/TWRippleXAddress.cpp +++ b/src/interface/TWRippleXAddress.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../Ripple/XAddress.h" +#include "../XRP/XAddress.h" #include #include @@ -13,7 +13,6 @@ #include using namespace TW; -using namespace TW::Ripple; bool TWRippleXAddressEqual(struct TWRippleXAddress *_Nonnull lhs, struct TWRippleXAddress *_Nonnull rhs) { return lhs->impl == rhs->impl; @@ -21,13 +20,13 @@ bool TWRippleXAddressEqual(struct TWRippleXAddress *_Nonnull lhs, struct TWRippl bool TWRippleXAddressIsValidString(TWString *_Nonnull string) { auto* s = reinterpret_cast(string); - return XAddress::isValid(*s); + return Ripple::XAddress::isValid(*s); } struct TWRippleXAddress *_Nullable TWRippleXAddressCreateWithString(TWString *_Nonnull string) { auto* s = reinterpret_cast(string); try { - const auto address = XAddress(*s); + const auto address = Ripple::XAddress(*s); return new TWRippleXAddress{ std::move(address) }; } catch (...) { return nullptr; @@ -35,7 +34,7 @@ struct TWRippleXAddress *_Nullable TWRippleXAddressCreateWithString(TWString *_N } struct TWRippleXAddress *_Nonnull TWRippleXAddressCreateWithPublicKey(struct TWPublicKey *_Nonnull publicKey, const uint32_t tag) { - return new TWRippleXAddress{ XAddress(publicKey->impl, tag) }; + return new TWRippleXAddress{ Ripple::XAddress(publicKey->impl, tag) }; } void TWRippleXAddressDelete(struct TWRippleXAddress *_Nonnull address) { diff --git a/src/interface/TWSegwitAddress.cpp b/src/interface/TWSegwitAddress.cpp index f340d54a97f..59a41db467e 100644 --- a/src/interface/TWSegwitAddress.cpp +++ b/src/interface/TWSegwitAddress.cpp @@ -13,7 +13,6 @@ #include using namespace TW; -using namespace TW::Bitcoin; bool TWSegwitAddressEqual(struct TWSegwitAddress *_Nonnull lhs, struct TWSegwitAddress *_Nonnull rhs) { return lhs->impl == rhs->impl; @@ -21,12 +20,12 @@ bool TWSegwitAddressEqual(struct TWSegwitAddress *_Nonnull lhs, struct TWSegwitA bool TWSegwitAddressIsValidString(TWString *_Nonnull string) { auto* s = reinterpret_cast(string); - return SegwitAddress::isValid(*s); + return Bitcoin::SegwitAddress::isValid(*s); } struct TWSegwitAddress *_Nullable TWSegwitAddressCreateWithString(TWString *_Nonnull string) { auto* s = reinterpret_cast(string); - auto dec = SegwitAddress::decode(*s); + auto dec = Bitcoin::SegwitAddress::decode(*s); if (!std::get<2>(dec)) { return nullptr; } @@ -35,7 +34,7 @@ struct TWSegwitAddress *_Nullable TWSegwitAddressCreateWithString(TWString *_Non } struct TWSegwitAddress *_Nonnull TWSegwitAddressCreateWithPublicKey(enum TWHRP hrp, struct TWPublicKey *_Nonnull publicKey) { - const auto address = SegwitAddress(publicKey->impl, 0, stringForHRP(hrp)); + const auto address = Bitcoin::SegwitAddress(publicKey->impl, stringForHRP(hrp)); return new TWSegwitAddress{ std::move(address) }; } @@ -52,6 +51,10 @@ enum TWHRP TWSegwitAddressHRP(struct TWSegwitAddress *_Nonnull address) { return hrpForString(address->impl.hrp.c_str()); } +int TWSegwitAddressWitnessVersion(struct TWSegwitAddress *_Nonnull address) { + return address->impl.witnessVersion; +} + TWData *_Nonnull TWSegwitAddressWitnessProgram(struct TWSegwitAddress *_Nonnull address) { return TWDataCreateWithBytes(address->impl.witnessProgram.data(), address->impl.witnessProgram.size()); } diff --git a/src/interface/TWSolanaAddress.cpp b/src/interface/TWSolanaAddress.cpp index 023b9033ed8..df311698057 100644 --- a/src/interface/TWSolanaAddress.cpp +++ b/src/interface/TWSolanaAddress.cpp @@ -4,27 +4,26 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include #include "Solana/Address.h" +#include -using namespace TW::Solana; using namespace TW; struct TWSolanaAddress* _Nullable TWSolanaAddressCreateWithString(TWString* _Nonnull string) { auto& str = *reinterpret_cast(string); - return new TWSolanaAddress{Address(str)}; + return new TWSolanaAddress{Solana::Address(str)}; } void TWSolanaAddressDelete(struct TWSolanaAddress* _Nonnull address) { delete address; } -TWString *_Nullable TWSolanaAddressDefaultTokenAddress(struct TWSolanaAddress* _Nonnull address, TWString* _Nonnull tokenMintAddress) { +TWString* _Nullable TWSolanaAddressDefaultTokenAddress(struct TWSolanaAddress* _Nonnull address, TWString* _Nonnull tokenMintAddress) { try { if (address == nullptr || tokenMintAddress == nullptr) { return nullptr; } - Address tokenMint = Address(TWStringUTF8Bytes(tokenMintAddress)); + Solana::Address tokenMint = Solana::Address(TWStringUTF8Bytes(tokenMintAddress)); std::string defaultAddress = address->impl.defaultTokenAddress(tokenMint).string(); return TWStringCreateWithUTF8Bytes(defaultAddress.c_str()); } catch (...) { diff --git a/src/interface/TWStoredKey.cpp b/src/interface/TWStoredKey.cpp index cb4625a40c7..bccd49a9a03 100644 --- a/src/interface/TWStoredKey.cpp +++ b/src/interface/TWStoredKey.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -7,28 +7,32 @@ #include #include "../Coin.h" -#include "../Data.h" +#include "Data.h" #include "../HDWallet.h" #include "../Keystore/StoredKey.h" #include #include -using namespace TW::Keystore; +namespace KeyStore = TW::Keystore; struct TWStoredKey* _Nullable TWStoredKeyLoad(TWString* _Nonnull path) { try { const auto& pathString = *reinterpret_cast(path); - return new TWStoredKey{ StoredKey::load(pathString) }; + return new TWStoredKey{ KeyStore::StoredKey::load(pathString) }; } catch (...) { return nullptr; } } -struct TWStoredKey* _Nonnull TWStoredKeyCreate(TWString* _Nonnull name, TWData* _Nonnull password) { +struct TWStoredKey* _Nonnull TWStoredKeyCreateLevel(TWString* _Nonnull name, TWData* _Nonnull password, enum TWStoredKeyEncryptionLevel encryptionLevel) { const auto& nameString = *reinterpret_cast(name); const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); - return new TWStoredKey{ StoredKey::createWithMnemonicRandom(nameString, passwordData) }; + return new TWStoredKey{ KeyStore::StoredKey::createWithMnemonicRandom(nameString, passwordData, encryptionLevel) }; +} + +struct TWStoredKey* _Nonnull TWStoredKeyCreate(TWString* _Nonnull name, TWData* _Nonnull password) { + return TWStoredKeyCreateLevel(name, password, TWStoredKeyEncryptionLevelDefault); } struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKey(TWData* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin) { @@ -36,7 +40,7 @@ struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKey(TWData* _Nonnull priva const auto& privateKeyData = *reinterpret_cast(privateKey); const auto& nameString = *reinterpret_cast(name); const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); - return new TWStoredKey{ StoredKey::createWithPrivateKeyAddDefaultAddress(nameString, passwordData, coin, privateKeyData) }; + return new TWStoredKey{ KeyStore::StoredKey::createWithPrivateKeyAddDefaultAddress(nameString, passwordData, coin, privateKeyData) }; } catch (...) { return nullptr; } @@ -47,7 +51,7 @@ struct TWStoredKey* _Nullable TWStoredKeyImportHDWallet(TWString* _Nonnull mnemo const auto& mnemonicString = *reinterpret_cast(mnemonic); const auto& nameString = *reinterpret_cast(name); const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); - return new TWStoredKey{ StoredKey::createWithMnemonicAddDefaultAddress(nameString, passwordData, mnemonicString, coin) }; + return new TWStoredKey{ KeyStore::StoredKey::createWithMnemonicAddDefaultAddress(nameString, passwordData, mnemonicString, coin) }; } catch (...) { return nullptr; } @@ -57,7 +61,7 @@ struct TWStoredKey* _Nullable TWStoredKeyImportJSON(TWData* _Nonnull json) { try { const auto& d = *reinterpret_cast(json); const auto parsed = nlohmann::json::parse(d); - return new TWStoredKey{ StoredKey::createWithJson(nlohmann::json::parse(d)) }; + return new TWStoredKey{ KeyStore::StoredKey::createWithJson(nlohmann::json::parse(d)) }; } catch (...) { return nullptr; } @@ -79,7 +83,7 @@ TWString* _Nonnull TWStoredKeyName(struct TWStoredKey* _Nonnull key) { } bool TWStoredKeyIsMnemonic(struct TWStoredKey* _Nonnull key) { - return key->impl.type == StoredKeyType::mnemonicPhrase; + return key->impl.type == KeyStore::StoredKeyType::mnemonicPhrase; } size_t TWStoredKeyAccountCount(struct TWStoredKey* _Nonnull key) { @@ -104,15 +108,41 @@ struct TWAccount* _Nullable TWStoredKeyAccountForCoin(struct TWStoredKey* _Nonnu } } +struct TWAccount* _Nullable TWStoredKeyAccountForCoinDerivation(struct TWStoredKey* _Nonnull key, enum TWCoinType coin, TWDerivation derivation, struct TWHDWallet* _Nullable wallet) { + try { + if (wallet == nullptr) { + return nullptr; + } + const auto account = key->impl.account(coin, derivation, wallet->impl); + return new TWAccount{ account }; + } catch (...) { + return nullptr; + } +} + void TWStoredKeyRemoveAccountForCoin(struct TWStoredKey* _Nonnull key, enum TWCoinType coin) { key->impl.removeAccount(coin); } -void TWStoredKeyAddAccount(struct TWStoredKey* _Nonnull key, TWString* _Nonnull address, enum TWCoinType coin, TWString* _Nonnull derivationPath, TWString* _Nonnull extetndedPublicKey) { +void TWStoredKeyRemoveAccountForCoinDerivation(struct TWStoredKey* _Nonnull key, enum TWCoinType coin, enum TWDerivation derivation) { + key->impl.removeAccount(coin, derivation); +} + +void TWStoredKeyRemoveAccountForCoinDerivationPath(struct TWStoredKey* _Nonnull key, enum TWCoinType coin, TWString* _Nonnull derivationPath) { + const auto dp = TW::DerivationPath(*reinterpret_cast(derivationPath)); + key->impl.removeAccount(coin, dp); +} + +void TWStoredKeyAddAccountDerivation(struct TWStoredKey* _Nonnull key, TWString* _Nonnull address, enum TWCoinType coin, enum TWDerivation derivation, TWString* _Nonnull derivationPath, TWString* _Nonnull publicKey, TWString* _Nonnull extendedPublicKey) { const auto& addressString = *reinterpret_cast(address); - const auto& extetndedPublicKeyString = *reinterpret_cast(extetndedPublicKey); + const auto& publicKeyString = *reinterpret_cast(publicKey); + const auto& extendedPublicKeyString = *reinterpret_cast(extendedPublicKey); const auto dp = TW::DerivationPath(*reinterpret_cast(derivationPath)); - key->impl.addAccount(addressString, coin, dp, extetndedPublicKeyString); + key->impl.addAccount(addressString, coin, derivation, dp, publicKeyString, extendedPublicKeyString); +} + +void TWStoredKeyAddAccount(struct TWStoredKey* _Nonnull key, TWString* _Nonnull address, enum TWCoinType coin, TWString* _Nonnull derivationPath, TWString* _Nonnull publicKey, TWString* _Nonnull extendedPublicKey) { + return TWStoredKeyAddAccountDerivation(key, address, coin, TWDerivationDefault, derivationPath, publicKey, extendedPublicKey); } bool TWStoredKeyStore(struct TWStoredKey* _Nonnull key, TWString* _Nonnull path) { @@ -178,3 +208,11 @@ bool TWStoredKeyFixAddresses(struct TWStoredKey* _Nonnull key, TWData* _Nonnull return false; } } + +TWString* _Nullable TWStoredKeyEncryptionParameters(struct TWStoredKey* _Nonnull key) { + if (!key->impl.id) { + return nullptr; + } + const std::string params = key->impl.payload.json().dump(); + return TWStringCreateWithUTF8Bytes(params.c_str()); +} diff --git a/src/interface/TWTransactionCompiler.cpp b/src/interface/TWTransactionCompiler.cpp new file mode 100644 index 00000000000..bd8b6bcf051 --- /dev/null +++ b/src/interface/TWTransactionCompiler.cpp @@ -0,0 +1,69 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include + +#include "TransactionCompiler.h" +#include "Data.h" +#include "uint256.h" + +#include + +using namespace TW; + + +TWData *_Nonnull TWTransactionCompilerBuildInput(enum TWCoinType coinType, TWString *_Nonnull from, TWString *_Nonnull to, TWString *_Nonnull amount, TWString *_Nonnull asset, TWString *_Nonnull memo, TWString *_Nonnull chainId) { + Data result; + try { + result = TransactionCompiler::buildInput( + coinType, + std::string(TWStringUTF8Bytes(from)), + std::string(TWStringUTF8Bytes(to)), + std::string(TWStringUTF8Bytes(amount)), + std::string(TWStringUTF8Bytes(asset)), + std::string(TWStringUTF8Bytes(memo)), + std::string(TWStringUTF8Bytes(chainId)) + ); + } catch (...) {} // return empty + return TWDataCreateWithBytes(result.data(), result.size()); +} + +std::vector createFromTWDataVector(const struct TWDataVector* _Nonnull dataVector) { + std::vector ret; + const auto n = TWDataVectorSize(dataVector); + for (auto i = 0ul; i < n; ++i) { + auto elem = TWDataVectorGet(dataVector, i); + ret.push_back(*(static_cast(elem))); + TWDataDelete(elem); + } + return ret; +} + +TWData *_Nonnull TWTransactionCompilerPreImageHashes(enum TWCoinType coinType, TWData *_Nonnull txInputData) { + Data result; + try { + assert(txInputData != nullptr); + const Data inputData = data(TWDataBytes(txInputData), TWDataSize(txInputData)); + + result = TransactionCompiler::preImageHashes(coinType, inputData); + } catch (...) {} // return empty + return TWDataCreateWithBytes(result.data(), result.size()); +} + +TWData *_Nonnull TWTransactionCompilerCompileWithSignatures(enum TWCoinType coinType, TWData *_Nonnull txInputData, const struct TWDataVector *_Nonnull signatures, const struct TWDataVector *_Nonnull publicKeys) { + Data result; + try { + assert(txInputData != nullptr); + const Data inputData = data(TWDataBytes(txInputData), TWDataSize(txInputData)); + assert(signatures != nullptr); + const auto signaturesVec = createFromTWDataVector(signatures); + assert(publicKeys != nullptr); + const auto publicKeysVec = createFromTWDataVector(publicKeys); + + result = TransactionCompiler::compileWithSignatures(coinType, inputData, signaturesVec, publicKeysVec); + } catch (...) {} // return empty + return TWDataCreateWithBytes(result.data(), result.size()); +} diff --git a/src/memory/memzero_wrapper.h b/src/memory/memzero_wrapper.h new file mode 100644 index 00000000000..be8c7859e4d --- /dev/null +++ b/src/memory/memzero_wrapper.h @@ -0,0 +1,23 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include + +#include + +#include + +namespace TW { + +template +static inline void memzero(T* data, std::size_t len = sizeof(T)) noexcept { + static_assert(std::is_trivial_v, "type should be a pod"); + ::memzero(data, len); +} + +} // namespace TW diff --git a/src/operators/equality_comparable.h b/src/operators/equality_comparable.h new file mode 100644 index 00000000000..aa2fd02f34b --- /dev/null +++ b/src/operators/equality_comparable.h @@ -0,0 +1,23 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +namespace TW::operators::details { + +template +class empty_base {}; + +} // namespace TW::operators::details + +namespace TW { + +template > +struct equality_comparable : B { + friend bool operator!=(const T& x, const T& y) { return !(x == y); } +}; + +} // namespace TW diff --git a/src/proto/.clang-tidy b/src/proto/.clang-tidy new file mode 100644 index 00000000000..2c22f7387dd --- /dev/null +++ b/src/proto/.clang-tidy @@ -0,0 +1,6 @@ +--- +InheritParentConfig: false +Checks: '-*,misc-definitions-in-headers' +CheckOptions: + - { key: HeaderFileExtensions, value: "x" } +... diff --git a/src/proto/Aeternity.proto b/src/proto/Aeternity.proto index 8a20764369a..fc501db4859 100644 --- a/src/proto/Aeternity.proto +++ b/src/proto/Aeternity.proto @@ -11,8 +11,10 @@ message SigningInput { // Address of the recipient with "ak_" prefix string to_address = 2; + // Amount (uint256, serialized little endian) bytes amount = 3; + // Fee amount (uint256, serialized little endian) bytes fee = 4; // Message, optional @@ -21,15 +23,18 @@ message SigningInput { // Time to live until block height uint64 ttl = 6; + // Nonce (should be larger than in the last transaction of the account) uint64 nonce = 7; + // The secret private key used for signing (32 bytes). bytes private_key = 8; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signed and encoded transaction bytes, Base64 with checksum string encoded = 1; + // Signature, Base58 with checksum string signature = 2; } diff --git a/src/proto/Aion.proto b/src/proto/Aion.proto index 771e6774171..2b194fc0237 100644 --- a/src/proto/Aion.proto +++ b/src/proto/Aion.proto @@ -5,32 +5,32 @@ option java_package = "wallet.core.jni.proto"; // Input data necessary to create a signed transaction. message SigningInput { - // Nonce (256-bit number) + // Nonce (uint256, serialized little endian) bytes nonce = 1; - // Gas price (256-bit number) + // Gas price (uint256, serialized little endian) bytes gas_price = 2; - // Gas limit (256-bit number) + // Gas limit (uint256, serialized little endian) bytes gas_limit = 3; // Recipient's address. string to_address = 4; - // Amount to send in wei (256-bit number) + // Amount to send in wei (uint256, serialized little endian) bytes amount = 5; // Optional payload bytes payload = 6; - // Private key. + // The secret private key used for signing (32 bytes). bytes private_key = 7; // Timestamp uint64 timestamp = 8; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signed and encoded transaction bytes. bytes encoded = 1; diff --git a/src/proto/Algorand.proto b/src/proto/Algorand.proto index 937493e51a9..167fc0f10ee 100644 --- a/src/proto/Algorand.proto +++ b/src/proto/Algorand.proto @@ -3,18 +3,30 @@ syntax = "proto3"; package TW.Algorand.Proto; option java_package = "wallet.core.jni.proto"; +// Simple transfer message, transfer an amount to an address message Transfer { + // Destination address (string) string to_address = 1; + + // Amount uint64 amount = 2; } +// Asset Transfer message, with assetID message AssetTransfer { + // Destination address (string) string to_address = 1; + + // Amount uint64 amount = 2; + + // ID of the asset being transferred uint64 asset_id = 3; } +// Opt-in message for an asset message AssetOptIn { + // ID of the asset uint64 asset_id = 1; } @@ -22,19 +34,26 @@ message AssetOptIn { message SigningInput { // network / chain id string genesis_id = 1; + // network / chain hash bytes genesis_hash = 2; + // binary note data bytes note = 3; - // private key + + // The secret private key used for signing (32 bytes). bytes private_key = 4; + // network / first round uint64 first_round = 5; + // network / last round uint64 last_round = 6; - // fee + + // fee amount uint64 fee = 7; + // message payload oneof message_oneof { Transfer transfer = 10; AssetTransfer asset_transfer = 11; @@ -42,7 +61,7 @@ message SigningInput { } } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signed and encoded transaction bytes. bytes encoded = 1; diff --git a/src/proto/Aptos.proto b/src/proto/Aptos.proto new file mode 100644 index 00000000000..1972c1ea349 --- /dev/null +++ b/src/proto/Aptos.proto @@ -0,0 +1,153 @@ +// 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. + +syntax = "proto3"; + +package TW.Aptos.Proto; +option java_package = "wallet.core.jni.proto"; + +// Necessary fields to process a TransferMessage +message TransferMessage { + // Destination Account address (string) + string to = 1; + // Amount to be transferred (uint64) + uint64 amount = 2; +} + +// Necessary tag for type function argument +message StructTag { + // Address of the account + string account_address = 1; + // Module name + string module = 2; + // Identifier + string name = 3; +} + +// Necessary fields to process a TokenTransferMessage +message TokenTransferMessage { + // Destination Account address (string) + string to = 1; + // Amount to be transferred (uint64) + uint64 amount = 2; + // token function to call, e.g BTC: 0x43417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b9::coins::BTC + StructTag function = 3; +} + +// Necessary fields to process a ManagedTokensRegisterMessage +message ManagedTokensRegisterMessage { + // token function to register, e.g BTC: 0x43417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b9::coins::BTC + StructTag function = 1; +} + +// Necessary fields to process a CreateAccountMessage +message CreateAccountMessage { + // auth account address to create + string auth_key = 1; +} + +// Necessary fields to process an OfferNftMessage +message OfferNftMessage { + // Receiver address + string receiver = 1; + // Creator address + string creator = 2; + // Name of the collection + string collectionName = 3; + // Name of the NFT + string name = 4; + // Property version (should be often 0) + uint64 property_version = 5; + // Amount of NFT's to transfer (should be often 1) + uint64 amount = 6; +} + +// Necessary fields to process an CancelOfferNftMessage +message CancelOfferNftMessage { + // Receiver address + string receiver = 1; + // Creator address + string creator = 2; + // Name of the collection + string collectionName = 3; + // Name of the NFT + string name = 4; + // Property version (should be often 0) + uint64 property_version = 5; +} + +// Necessary fields to process an ClaimNftMessage +message ClaimNftMessage { + // Sender address + string sender = 1; + // Creator address + string creator = 2; + // Name of the collection + string collectionName = 3; + // Name of the NFT + string name = 4; + // Property version (should be often 0) + uint64 property_version = 5; +} + +message NftMessage { + oneof nft_transaction_payload { + OfferNftMessage offer_nft = 1; + CancelOfferNftMessage cancel_offer_nft = 2; + ClaimNftMessage claim_nft = 3; + } +} + +// Input data necessary to create a signed transaction. +message SigningInput { + // Sender Account address (string) + string sender = 1; + // Sequence number, incremented atomically for each tx present on the account, start at 0 (int64) + int64 sequence_number = 2; + // Max gas amount that the user is willing to pay (uint64) + uint64 max_gas_amount = 3; + // Gas unit price - queried through API (uint64) + uint64 gas_unit_price = 4; + // Expiration timestamp for the transaction, can't be in the past (uint64) + uint64 expiration_timestamp_secs = 5; + // Chain id 1 (mainnet) 32(devnet) (uint32 - casted in uint8_t later) + uint32 chain_id = 6; + // Private key to sign the transaction (bytes) + bytes private_key = 7; + // hex encoded function to sign, use it for smart contract approval (string) + string any_encoded = 8; + + oneof transaction_payload { + TransferMessage transfer = 9; + TokenTransferMessage token_transfer = 10; + CreateAccountMessage create_account = 11; + NftMessage nft_message = 12; + ManagedTokensRegisterMessage register_token = 13; + } +} + +// Information related to the signed transaction +message TransactionAuthenticator { + // Signature part of the signed transaction (bytes) + bytes signature = 1; + // Public key of the signer (bytes) + bytes public_key = 2; +} + +// Transaction signing output. +message SigningOutput { + /// The raw transaction (bytes) + bytes raw_txn = 1; + + /// Public key and signature to authenticate + TransactionAuthenticator authenticator = 2; + + /// Signed and encoded transaction bytes. + bytes encoded = 3; + + // Transaction json format for api broadcasting (string) + string json = 4; +} diff --git a/src/proto/Binance.proto b/src/proto/Binance.proto index 846307db7c6..00df9f21fd2 100644 --- a/src/proto/Binance.proto +++ b/src/proto/Binance.proto @@ -3,140 +3,260 @@ syntax = "proto3"; package TW.Binance.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + +// Transaction structure, used internally message Transaction { - // int64 SIZE-OF-ENCODED // varint encoded length of the structure after encoding - // 0xF0625DEE // prefix - repeated bytes msgs = 1; // array of size 1, containing the transaction message, which are one of the transaction type below - repeated bytes signatures = 2; // array of size 1, containing the standard signature structure of the transaction sender - string memo = 3; // a short sentence of remark for the transaction, only for `Transfer` transactions. - int64 source = 4; // an identifier for tools triggerring this transaction, set to zero if unwilling to disclose. - bytes data = 5; // reserved for future use + // array of size 1, containing the transaction message, which are one of the transaction type below + repeated bytes msgs = 1; + + // array of size 1, containing the standard signature structure of the transaction sender + repeated bytes signatures = 2; + + // a short sentence of remark for the transaction, only for `Transfer` transactions. + string memo = 3; + + // an identifier for tools triggering this transaction, set to zero if unwilling to disclose. + int64 source = 4; + + // reserved for future use + bytes data = 5; } +// Signature structure, used internally message Signature { - message PubKey { - // 0xEB5AE987 // prefix - // bytes // public key bytes - } - bytes pub_key = 1; // public key bytes of the signer address - bytes signature = 2; // signature bytes, please check chain access section for signature generation - int64 account_number = 3; // another identifier of signer, which can be read from chain by account REST API or RPC - int64 sequence = 4; // sequence number for the next transaction + // public key bytes of the signer address + bytes pub_key = 1; + + // signature bytes, please check chain access section for signature generation + bytes signature = 2; + + // another identifier of signer, which can be read from chain by account REST API or RPC + int64 account_number = 3; + + // sequence number for the next transaction + int64 sequence = 4; } +// Message for Trade order message TradeOrder { - // 0xCE6DC043 // prefix - bytes sender = 1; // originating address - string id = 2; // order id, optional - string symbol = 3; // symbol for trading pair in full name of the tokens - int64 ordertype = 4; // only accept 2 for now, meaning limit order - int64 side = 5; // 1 for buy and 2 fory sell - int64 price = 6; // price of the order, which is the real price multiplied by 1e8 (10^8) and rounded to integer - int64 quantity = 7; // quantity of the order, which is the real price multiplied by 1e8 (10^8) and rounded to integer - int64 timeinforce = 8; // 1 for Good Till Expire(GTE) order and 3 for Immediate Or Cancel (IOC) + // originating address + bytes sender = 1; + + // order id, optional + string id = 2; + + // symbol for trading pair in full name of the tokens + string symbol = 3; + + // only accept 2 for now, meaning limit order + int64 ordertype = 4; + + // 1 for buy and 2 for sell + int64 side = 5; + + // price of the order, which is the real price multiplied by 1e8 (10^8) and rounded to integer + int64 price = 6; + + // quantity of the order, which is the real price multiplied by 1e8 (10^8) and rounded to integer + int64 quantity = 7; + + // 1 for Good Till Expire(GTE) order and 3 for Immediate Or Cancel (IOC) + int64 timeinforce = 8; } +// Message for CancelTrade order message CancelTradeOrder { - // 0x166E681B // prefix - bytes sender = 1; // originating address - string symbol = 2; // symbol for trading pair in full name of the tokens - string refid = 3; // order id to cancel + // originating address + bytes sender = 1; + + // symbol for trading pair in full name of the tokens + string symbol = 2; + + // order id to cancel + string refid = 3; } +// Message for Send order message SendOrder { - // 0x2A2C87FA - // A symbol-amount pair. Could be moved out of SendOrder; kept here for backward compatibility. + // A token amount, symbol-amount pair. Could be moved out of SendOrder; kept here for backward compatibility. message Token { + // Token ID string denom = 1; + + // Amount int64 amount = 2; } + + // Transaction input message Input { + // source address bytes address = 1; + + // input coin amounts repeated Token coins = 2; } + + // Transaction output message Output { + // destination address bytes address = 1; + + // output coin amounts repeated Token coins = 2; } + + // Send inputs repeated Input inputs = 1; + + // Send outputs repeated Output outputs = 2; } +// Message for TokenIssue order message TokenIssueOrder { - // 0x17EFAB80 // prefix - bytes from = 1; // owner address - string name = 2; // token name - string symbol = 3; // token symbol, in full name with "-" suffix - int64 total_supply = 4; // total supply - bool mintable = 5; // mintable + // owner address + bytes from = 1; + + // token name + string name = 2; + + // token symbol, in full name with "-" suffix + string symbol = 3; + + // total supply + int64 total_supply = 4; + + // mintable + bool mintable = 5; } +// Message for TokenMint order message TokenMintOrder { - // 0x467E0829 // prefix - bytes from = 1; // owner address - string symbol = 2; // token symbol, in full name with "-" suffix - int64 amount = 3; // amount to mint + // owner address + bytes from = 1; + + // token symbol, in full name with "-" suffix + string symbol = 2; + + // amount to mint + int64 amount = 3; } +// Message for TokenBurn order message TokenBurnOrder { - // 0x7ED2D2A0 // prefix - bytes from = 1; // owner address - string symbol = 2; // token symbol, in full name with "-" suffix - int64 amount = 3; // amount to burn + // owner address + bytes from = 1; + + // token symbol, in full name with "-" suffix + string symbol = 2; + + // amount to burn + int64 amount = 3; } +// Message for TokenFreeze order message TokenFreezeOrder { - // 0xE774B32D // prefix - bytes from = 1; // owner address - string symbol = 2; // token symbol, in full name with "-" suffix - int64 amount = 3; // amount of token to freeze + // owner address + bytes from = 1; + + // token symbol, in full name with "-" suffix + string symbol = 2; + + // amount of token to freeze + int64 amount = 3; } +// Message for TokenUnfreeze order message TokenUnfreezeOrder { - // 0x6515FF0D // prefix - bytes from = 1; // owner address - string symbol = 2; // token symbol, in full name with "-" suffix - int64 amount = 3; // amount of token to unfreeze + // owner address + bytes from = 1; + + // token symbol, in full name with "-" suffix + string symbol = 2; + + // amount of token to unfreeze + int64 amount = 3; } +// Message for HashTimeLock order message HTLTOrder { - // 0xB33F9A24 // prefix - bytes from = 1; // signer address - bytes to = 2; // recipient address + // signer address + bytes from = 1; + + // recipient address + bytes to = 2; + + // source on other chain, optional string recipient_other_chain = 3; + + // recipient on other chain, optional string sender_other_chain = 4; - bytes random_number_hash = 5; //hash of a random number and timestamp, based on SHA256 + + // hash of a random number and timestamp, based on SHA256 + bytes random_number_hash = 5; + + // timestamp int64 timestamp = 6; + + // amounts repeated SendOrder.Token amount = 7; - string expected_income = 8; // expected gained token on the other chain + + // expected gained token on the other chain + string expected_income = 8; + + // period expressed in block heights int64 height_span = 9; + + // set for cross-chain send bool cross_chain = 10; } +// Message for Deposit HTLT order message DepositHTLTOrder { - // 0xB33F9A24 // prefix - bytes from = 1; // signer address + // signer address + bytes from = 1; + + // amounts repeated SendOrder.Token amount = 2; + + // swap ID bytes swap_id = 3; } +// Message for Claim HTLT order message ClaimHTLOrder { - // 0xC1665300 // prefix - bytes from = 1; // signer address + // signer address + bytes from = 1; + + // swap ID bytes swap_id = 2; + + // random number input bytes random_number = 3; } +// Message for Refund HTLT order message RefundHTLTOrder { - // 0x3454A27C // prefix - bytes from = 1; // signer address + // signer address + bytes from = 1; + + // swap ID bytes swap_id = 2; } +// Transfer message TransferOut { + // source address bytes from = 1; + + // recipient address bytes to = 2; + + // transfer amount SendOrder.Token amount = 3; + + // expiration time int64 expire_time = 4; } @@ -162,37 +282,73 @@ message SideChainUndelegate { string chain_id = 4; } +// Message for TimeLock order message TimeLockOrder { - bytes from_address = 1; // owner address + // owner address + bytes from_address = 1; + + // Description (optional) string description = 2; + // Array of symbol/amount pairs. see SDK https://github.com/binance-chain/javascript-sdk/blob/master/docs/api-docs/classes/tokenmanagement.md#timelock repeated SendOrder.Token amount = 3; + + // lock time int64 lock_time = 4; } +// Message for TimeRelock order message TimeRelockOrder { - bytes from_address = 1; // owner address - int64 id = 2; // order ID + // owner address + bytes from_address = 1; + + // order ID + int64 id = 2; + + // Description (optional) string description = 3; + // Array of symbol/amount pairs. repeated SendOrder.Token amount = 4; + + // lock time int64 lock_time = 5; } +// Message for TimeUnlock order message TimeUnlockOrder { - bytes from_address = 1; // owner address - int64 id = 2; // order ID + // owner address + bytes from_address = 1; + + // order ID + int64 id = 2; } -// Input data necessary to create a signed order. +// Input data necessary to create a signed transaction. message SigningInput { + // Chain ID string chain_id = 1; + + // Source account number int64 account_number = 2; + + // Sequence number (account specific) int64 sequence = 3; + + // Transaction source, see https://github.com/bnb-chain/BEPs/blob/master/BEP10.md + // Some important values: + // 0: Default source value (e.g. for Binance Chain Command Line, or SDKs) + // 1: Binance DEX Web Wallet + // 2: Trust Wallet int64 source = 4; + + // Optional memo string memo = 5; + + // The secret private key used for signing (32 bytes). bytes private_key = 6; + // Payload message oneof order_oneof { TradeOrder trade_order = 8; CancelTradeOrder cancel_trade_order = 9; @@ -216,8 +372,14 @@ message SigningInput { } } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signed and encoded transaction bytes. bytes encoded = 1; + + // OK (=0) or other codes in case of error + Common.Proto.SigningError error = 2; + + // error description in case of error + string error_message = 3; } diff --git a/src/proto/Bitcoin.proto b/src/proto/Bitcoin.proto index 50e32f12937..cb321e44a8a 100644 --- a/src/proto/Bitcoin.proto +++ b/src/proto/Bitcoin.proto @@ -5,6 +5,7 @@ option java_package = "wallet.core.jni.proto"; import "Common.proto"; +// A transaction, with its inputs and outputs message Transaction { // Transaction data format version. sint32 version = 1; @@ -15,7 +16,7 @@ message Transaction { // A list of 1 or more transaction inputs or sources for coins. repeated TransactionInput inputs = 3; - // A list of 1 or more transaction outputs or destinations for coins + // A list of 1 or more transaction outputs or destinations for coins. repeated TransactionOutput outputs = 4; } @@ -33,7 +34,7 @@ message TransactionInput { // Bitcoin transaction out-point reference. message OutPoint { - // The hash of the referenced transaction. + // The hash of the referenced transaction (network byte order, usually needs to be reversed). bytes hash = 1; // The index of the specific output in the transaction. @@ -74,31 +75,31 @@ message SigningInput { // If amount is equal or more than the available amount, also max amount will be used. int64 amount = 2; - // Transaction fee per byte. + // Transaction fee rate, satoshis per byte, used to compute required fee (when planning) int64 byte_fee = 3; - // Recipient's address. + // Recipient's address, as string. string to_address = 4; - // Change address. + // Change address, as string. string change_address = 5; - // Available private keys. + // The available secret private key or keys required for signing (32 bytes each). repeated bytes private_key = 6; // Available redeem scripts indexed by script hash. map scripts = 7; - // Available unspent transaction outputs. + // Available input unspent transaction outputs. repeated UnspentTransaction utxo = 8; - // If sending max amount. + // Set if sending max amount is requested. bool use_max_amount = 9; - // Coin type (forks). + // Coin type (used by forks). uint32 coin_type = 10; - // Optional transaction plan + // Optional transaction plan. If missing, plan will be computed. TransactionPlan plan = 11; // Optional lockTime, default value 0 means no time locking. @@ -107,6 +108,9 @@ message SigningInput { // value < 500000000 : Block number at which this transaction is unlocked // value >= 500000000 : UNIX timestamp at which this transaction is unlocked uint32 lock_time = 12; + + // Optional zero-amount, OP_RETURN output + bytes output_op_return = 13; } // Describes a preliminary transaction plan. @@ -114,16 +118,16 @@ message TransactionPlan { // Amount to be received at the other end. int64 amount = 1; - // Maximum available amount. + // Maximum available amount in all the input UTXOs. int64 available_amount = 2; // Estimated transaction fee. int64 fee = 3; - // Change. + // Remaining change int64 change = 4; - // Selected unspent transaction outputs. + // Selected unspent transaction outputs (subset of all input UTXOs) repeated UnspentTransaction utxos = 5; // Zcash branch id @@ -131,19 +135,47 @@ message TransactionPlan { // Optional error Common.Proto.SigningError error = 7; + + // Optional zero-amount, OP_RETURN output + bytes output_op_return = 8; }; -// Transaction signing output. +// Result containing the signed and encoded transaction. +// Note that the amount may be different than the requested amount to account for fees and available funds. message SigningOutput { - // Resulting transaction. Note that the amount may be different than the requested amount to account for fees and available funds. + // Resulting transaction. Transaction transaction = 1; // Signed and encoded transaction bytes. bytes encoded = 2; - // Transaction id + // Transaction ID (hash) string transaction_id = 3; // Optional error Common.Proto.SigningError error = 4; + + // error description + string error_message = 5; +} + +/// Pre-image hash to be used for signing +message HashPublicKey { + /// Pre-image data hash that will be used for signing + bytes data_hash = 1; + + /// public key hash used for signing + bytes public_key_hash = 2; +} + +/// Transaction pre-signing output +message PreSigningOutput { + /// hash, public key list + repeated HashPublicKey hash_public_keys = 1; + + /// error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 2; + + /// error description + string error_message = 3; } diff --git a/src/proto/Cardano.proto b/src/proto/Cardano.proto new file mode 100644 index 00000000000..ffeec086591 --- /dev/null +++ b/src/proto/Cardano.proto @@ -0,0 +1,202 @@ +syntax = "proto3"; + +package TW.Cardano.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Common.proto"; + +// A transaction output that can be used as input +message OutPoint { + // The transaction ID + bytes tx_hash = 1; + + // The index of this output within the transaction + uint64 output_index = 2; +} + +// Represents a token and an amount. Token is identified by PolicyID and name. +message TokenAmount { + // Policy ID of the token, as hex string (28x2 digits) + string policy_id = 1; + + // The name of the asset (within the policy) + string asset_name = 2; + + // The amount (uint256, serialized little endian) + bytes amount = 3; +} + +// One input for a transaction +message TxInput { + // The UTXO + OutPoint out_point = 1; + + // The owner address (string) + string address = 2; + + // ADA amount in the UTXO + uint64 amount = 3; + + // optional token amounts in the UTXO + repeated TokenAmount token_amount = 4; +} + +// One output for a transaction +message TxOutput { + // Destination address (string) + string address = 1; + + // ADA amount + uint64 amount = 2; + + // optional token amounts + repeated TokenAmount token_amount = 3; +} + +// Collection of tokens with amount +message TokenBundle { + repeated TokenAmount token = 1; +} + +// Message for simple Transfer tx +message Transfer { + // Destination address as string + string to_address = 1; + + // Change address + string change_address = 2; + + // Requested ADA amount to be transferred. Output can be different only in use_max_amount case. + // Note that Cardano has a minimum amount per UTXO, see TWCardanoMinAdaAmount. + uint64 amount = 3; + + // Optional token(s) to be transferred + // Currently only one token type to be transferred per transaction is supported + TokenBundle token_amount = 4; + + // Request max amount option. If set, tx will send all possible amounts from all inputs, including all tokens; there will be no change; requested amount and token_amount is disregarded in this case. + bool use_max_amount = 5; + + // Optional fee overriding. If left to 0, optimal fee will be calculated. If set, exactly this value will be used as fee. + // Use it with care, it may result in underfunded or wasteful fee. + uint64 force_fee = 6; +} + +// Register a staking key for the account, prerequisite for Staking. +// Note: staking messages are typically used with a 1-output-to-self transaction. +message RegisterStakingKey { + // Staking address (as string) + string staking_address = 1; + + // Amount deposited in this TX. Should be 2 ADA (2000000). If not set correctly, TX will be rejected. See also Delegate.deposit_amount. + uint64 deposit_amount = 2; +} + +// Deregister staking key. can be done when staking is stopped completely. The Staking deposit is returned at this time. +message DeregisterStakingKey { + // Staking address (as string) + string staking_address = 1; + + // Amount undeposited in this TX. Should be 2 ADA (2000000). If not set correctly, TX will be rejected. See also RegisterStakingKey.deposit_amount. + uint64 undeposit_amount = 2; +} + +// Delegate funds in this account to a specified staking pool. +message Delegate { + // Staking address (as string) + string staking_address = 1; + + // PoolID of staking pool + bytes pool_id = 2; + + // Amount deposited in this TX. Should be 0. If not set correctly, TX will be rejected. See also RegisterStakingKey.deposit_amount. + uint64 deposit_amount = 3; +} + +// Withdraw earned staking reward. +message Withdraw { + // Staking address (as string) + string staking_address = 1; + + // Withdrawal amount. Should match the real value of the earned reward. + uint64 withdraw_amount = 2; +} + +// Describes a preliminary transaction plan. +message TransactionPlan { + // total coins in the utxos + uint64 available_amount = 1; + + // coins in the output UTXO + uint64 amount = 2; + + // coin amount deducted as fee + uint64 fee = 3; + + // coins in the change UTXO + uint64 change = 4; + + // coins deposited (going to deposit) in this TX + uint64 deposit = 10; + + // coins undeposited (coming from deposit) in this TX + uint64 undeposit = 11; + + // total tokens in the utxos (optional) + repeated TokenAmount available_tokens = 5; + + // tokens in the output (optional) + repeated TokenAmount output_tokens = 6; + + // tokens in the change (optional) + repeated TokenAmount change_tokens = 7; + + // The selected UTXOs, subset ot the input UTXOs + repeated TxInput utxos = 8; + + // Optional error + Common.Proto.SigningError error = 9; +} + +// Input data necessary to create a signed transaction. +message SigningInput { + // Available input UTXOs + repeated TxInput utxos = 1; + + // Available private keys (double extended keys); every input UTXO address should be covered + // In case of Plan only, keys should be present, in correct number + repeated bytes private_key = 2; + + // Later this can be made oneof if more message types are supported + Transfer transfer_message = 3; + + // Optional, used in case of Staking Key registration (prerequisite for Staking) + RegisterStakingKey register_staking_key = 6; + + // Optional, used in case of (re)delegation + Delegate delegate = 7; + + // Optional, used in case of withdraw + Withdraw withdraw = 8; + + // Optional + DeregisterStakingKey deregister_staking_key = 9; + + // Time-to-live time of the TX + uint64 ttl = 4; + + // Optional plan, if missing it will be computed + TransactionPlan plan = 5; +} + +// Result containing the signed and encoded transaction. +message SigningOutput { + // Encoded transaction bytes + bytes encoded = 1; + + // TxID, derived from transaction data, also needed for submission + bytes tx_id = 2; + + // Optional error + Common.Proto.SigningError error = 3; +} diff --git a/src/proto/Common.proto b/src/proto/Common.proto index 5391a97abc0..cedf89feee4 100644 --- a/src/proto/Common.proto +++ b/src/proto/Common.proto @@ -3,25 +3,68 @@ syntax = "proto3"; package TW.Common.Proto; option java_package = "wallet.core.jni.proto"; +// Error codes, used in multiple blockchains. enum SigningError { - OK = 0; // OK - // chain-generic, generic + // This is the OK case, with value=0 + OK = 0; + + // Chain-generic codes: + // Generic error (used if there is no suitable specific error is adequate) Error_general = 1; + // Internal error, indicates some very unusual, unexpected case Error_internal = 2; - // chain-generic, input + + // Chain-generic codes, input related: + // Low balance: the sender balance is not enough to cover the send and other auxiliary amount such as fee, deposit, or minimal balance. Error_low_balance = 3; - Error_zero_amount_requested = 4; // Requested amount is zero + // Requested amount is zero, send of 0 makes no sense + Error_zero_amount_requested = 4; + // One required key is missing (too few or wrong keys are provided) Error_missing_private_key = 5; - // chain-generic, fee + // A private key provided is invalid (e.g. wrong size, usually should be 32 bytes) + Error_invalid_private_key = 15; + // A provided address (e.g. destination address) is invalid + Error_invalid_address = 16; + // A provided input UTXO is invalid + Error_invalid_utxo = 17; + // The amount of an input UTXO is invalid + Error_invalid_utxo_amount = 18; + + // Chain-generic, fee related: + // Wrong fee is given, probably it is too low to cover minimal fee for the transaction Error_wrong_fee = 6; - // chain-generic, signing + + // Chain-generic, signing related: + // General signing error Error_signing = 7; - Error_tx_too_big = 8; // [NEO] Transaction too big, fee in GAS needed or try send by parts - // UTXO-chain specific, inputs - Error_missing_input_utxos = 9; // No UTXOs provided [BTC] - Error_not_enough_utxos = 10; // Not enough non-dust input UTXOs to cover requested amount (dust UTXOs are filtered out) [BTC] - // UTXO-chain specific, script - Error_script_redeem = 11; // [BTC] Missing redeem script - Error_script_output = 12; // [BTC] Invalid output script - Error_script_witness_program = 13; // [BTC] Unrecognized witness program + // Resulting transaction is too large + // [NEO] Transaction too big, fee in GAS needed or try send by parts + Error_tx_too_big = 8; + + // UTXO-chain specific, input related: + // No input UTXOs provided [BTC] + Error_missing_input_utxos = 9; + // Not enough non-dust input UTXOs to cover requested amount (dust UTXOs are filtered out) [BTC] + Error_not_enough_utxos = 10; + + // UTXO-chain specific, script related: + // [BTC] Missing required redeem script + Error_script_redeem = 11; + // [BTC] Invalid required output script + Error_script_output = 12; + // [BTC] Unrecognized witness program + Error_script_witness_program = 13; + + // Invalid memo, e.g. [XRP] Invalid tag + Error_invalid_memo = 14; + // Some input field cannot be parsed + Error_input_parse = 19; + // Multi-input and multi-output transaction not supported + Error_no_support_n2n = 20; + // Incorrect count of signatures passed to compile + Error_signatures_count = 21; + // Incorrect input parameter + Error_invalid_params = 22; + // Invalid input token amount + Error_invalid_requested_token_amount = 23; } diff --git a/src/proto/Cosmos.proto b/src/proto/Cosmos.proto index fc4afa9bc56..97c8bb2e358 100644 --- a/src/proto/Cosmos.proto +++ b/src/proto/Cosmos.proto @@ -3,22 +3,41 @@ syntax = "proto3"; package TW.Cosmos.Proto; option java_package = "wallet.core.jni.proto"; +// A denomination and an amount message Amount { + // name of the denomination string denom = 1; - int64 amount = 2; + + // amount, number as string + string amount = 2; } +// Fee incl. gas message Fee { + // Fee amount(s) repeated Amount amounts = 1; + + // Gas price uint64 gas = 2; } +// Block height, a revision and block height tuple. +// A height can be compared against another Height for the purposes of updating and freezing clients +message Height { + // the revision that the client is currently on + uint64 revision_number = 1; + // the height within the given revision + uint64 revision_height = 2; +} + +// Transaction broadcast mode enum BroadcastMode { BLOCK = 0; // Wait for the tx to pass/fail CheckTx, DeliverTx, and be committed in a block SYNC = 1; // Wait for the tx to pass/fail CheckTx ASYNC = 2; // Don't wait for pass/fail CheckTx; send and return tx immediately } +// A transaction payload message message Message { // cosmos-sdk/MsgSend message Send { @@ -28,6 +47,22 @@ message Message { string type_prefix = 4; } + // cosmos-sdk/MsgTransfer, IBC transfer + message Transfer { + // IBC port, e.g. "transfer" + string source_port = 1; + // IBC connection channel, e.g. "channel-141", see apis /ibc/applications/transfer/v1beta1/denom_traces (connections) or /node_info (own channel) + string source_channel = 2; + Amount token = 3; + string sender = 4; + string receiver = 5; + // Timeout block height. Either timeout height or timestamp should be set. + // Recommendation is to set height, to rev. 1 and block current + 1000 (see api /blocks/latest) + Height timeout_height = 6; + // Timeout timestamp (in nanoseconds) relative to the current block timestamp. Either timeout height or timestamp should be set. + uint64 timeout_timestamp = 7; + } + // cosmos-sdk/MsgDelegate to stake message Delegate { string delegator_address = 1; @@ -60,40 +95,276 @@ message Message { string type_prefix = 3; } + // transfer within wasm/MsgExecuteContract, used by Terra Classic + message WasmTerraExecuteContractTransfer { + // sender address + string sender_address = 1; + + // token contract address + string contract_address = 2; + + // size is uint128, as bigint + bytes amount = 3; + + string recipient_address = 4; + } + + // send within wasm/MsgExecuteContract, used by Terra Classic + message WasmTerraExecuteContractSend { + // sender address + string sender_address = 1; + + // token contract address + string contract_address = 2; + + // size is uint128, as bigint + bytes amount = 3; + + string recipient_contract_address = 4; + + // execute_msg to be executed in the context of recipient contract + string msg = 5; + + // used in case you are sending native tokens along with this message + repeated string coin = 6; + } + + // thorchain/MsgSend + message THORChainSend { + bytes from_address = 1; + bytes to_address = 2; + repeated Amount amounts = 3; + } + + // execute within wasm/MsgExecuteContract, used by Terra Classic + message WasmTerraExecuteContractGeneric { + // sender address + string sender_address = 1; + + // token contract address + string contract_address = 2; + + // execute_msg to be executed in the context of recipient contract + string execute_msg = 3; + + // used in case you are sending native tokens along with this message + // Gap in field numbering is intentional + repeated Amount coins = 5; + } + + // transfer within wasm/MsgExecuteContract + message WasmExecuteContractTransfer { + // sender address + string sender_address = 1; + + // token contract address + string contract_address = 2; + + // size is uint128, as bigint + bytes amount = 3; + + string recipient_address = 4; + } + + // send within wasm/MsgExecuteContract + message WasmExecuteContractSend { + // sender address + string sender_address = 1; + + // token contract address + string contract_address = 2; + + // size is uint128, as bigint + bytes amount = 3; + + string recipient_contract_address = 4; + + // execute_msg to be executed in the context of recipient contract + string msg = 5; + + // used in case you are sending native tokens along with this message + repeated string coin = 6; + } + + // execute within wasm/MsgExecuteContract + message WasmExecuteContractGeneric { + // sender address + string sender_address = 1; + + // token contract address + string contract_address = 2; + + // execute_msg to be executed in the context of recipient contract + string execute_msg = 3; + + // used in case you are sending native tokens along with this message + // Gap in field numbering is intentional + repeated Amount coins = 5; + } + message RawJSON { string type = 1; string value = 2; } + // For signing an already serialized transaction. Account number and chain ID must be set outside. + message SignDirect { + // The prepared serialized TxBody + bytes body_bytes = 1; + // The prepared serialized AuthInfo + bytes auth_info_bytes = 2; + } + + // StakeAuthorization defines authorization for delegate/undelegate/redelegate. + // + // Since: cosmos-sdk 0.43 + message StakeAuthorization { + // max_tokens specifies the maximum amount of tokens can be delegate to a validator. If it is + // empty, there is no spend limit and any amount of coins can be delegated. + Amount max_tokens = 1; + // validators is the oneof that represents either allow_list or deny_list + oneof validators { + // allow_list specifies list of validator addresses to whom grantee can delegate tokens on behalf of granter's + // account. + Validators allow_list = 2; + // deny_list specifies list of validator addresses to whom grantee can not delegate tokens. + Validators deny_list = 3; + } + // Validators defines list of validator addresses. + message Validators { + repeated string address = 1; + } + // authorization_type defines one of AuthorizationType. + AuthorizationType authorization_type = 4; + } + + // AuthorizationType defines the type of staking module authorization type + // + // Since: cosmos-sdk 0.43 + enum AuthorizationType { + // AUTHORIZATION_TYPE_UNSPECIFIED specifies an unknown authorization type + UNSPECIFIED = 0; + // AUTHORIZATION_TYPE_DELEGATE defines an authorization type for Msg/Delegate + DELEGATE = 1; + // AUTHORIZATION_TYPE_UNDELEGATE defines an authorization type for Msg/Undelegate + UNDELEGATE = 2; + // AUTHORIZATION_TYPE_REDELEGATE defines an authorization type for Msg/BeginRedelegate + REDELEGATE = 3; + } + + + // cosmos-sdk/MsgGrant + message AuthGrant { + string granter = 1; + string grantee = 2; + oneof grant_type { + StakeAuthorization grant_stake = 3; + } + int64 expiration = 4; + } + + // cosmos-sdk/MsgRevoke + message AuthRevoke { + string granter = 1; + string grantee = 2; + string msg_type_url = 3; + } + + // VoteOption enumerates the valid vote options for a given governance proposal. + enum VoteOption { + //_UNSPECIFIED defines a no-op vote option. + _UNSPECIFIED = 0; + // YES defines a yes vote option. + YES = 1; + // ABSTAIN defines an abstain vote option. + ABSTAIN = 2; + // NO defines a no vote option. + NO = 3; + // NO_WITH_VETO defines a no with veto vote option. + NO_WITH_VETO = 4; + } + + // cosmos-sdk/MsgVote defines a message to cast a vote. + message MsgVote { + uint64 proposal_id = 1; + string voter = 2; + VoteOption option = 3; + } + + // The payload message oneof message_oneof { Send send_coins_message = 1; - Delegate stake_message = 2; - Undelegate unstake_message = 3; - BeginRedelegate restake_message = 4; - WithdrawDelegationReward withdraw_stake_reward_message = 5; - RawJSON raw_json_message = 6; + Transfer transfer_tokens_message = 2; + Delegate stake_message = 3; + Undelegate unstake_message = 4; + BeginRedelegate restake_message = 5; + WithdrawDelegationReward withdraw_stake_reward_message = 6; + RawJSON raw_json_message = 7; + WasmTerraExecuteContractTransfer wasm_terra_execute_contract_transfer_message = 8; + WasmTerraExecuteContractSend wasm_terra_execute_contract_send_message = 9; + THORChainSend thorchain_send_message = 10; + WasmTerraExecuteContractGeneric wasm_terra_execute_contract_generic = 11; + WasmExecuteContractTransfer wasm_execute_contract_transfer_message = 12; + WasmExecuteContractSend wasm_execute_contract_send_message = 13; + WasmExecuteContractGeneric wasm_execute_contract_generic = 14; + SignDirect sign_direct_message = 15; + AuthGrant auth_grant = 16; + AuthRevoke auth_revoke = 17; + MsgVote msg_vote = 18; } } -// Input data necessary to create a signed order. +// Options for transaction encoding: JSON (Amino, older) or Protobuf. +enum SigningMode { + JSON = 0; // JSON format, Pre-Stargate + Protobuf = 1; // Protobuf-serialized (binary), Stargate +} + +// Input data necessary to create a signed transaction. message SigningInput { - uint64 account_number = 1; - string chain_id = 2; - Fee fee = 3; - string memo = 4; - uint64 sequence = 5; + // Specify if protobuf (a.k.a. Stargate) or earlier JSON serialization is used + SigningMode signing_mode = 1; + + // Source account number + uint64 account_number = 2; - bytes private_key = 6; + // Chain ID (string) + string chain_id = 3; - repeated Message messages = 7; + // Transaction fee + Fee fee = 4; - BroadcastMode mode = 8; + // Optional memo + string memo = 5; + + // Sequence number (account specific) + uint64 sequence = 6; + + // The secret private key used for signing (32 bytes). + bytes private_key = 7; + + // Payload message(s) + repeated Message messages = 8; + + // Broadcast mode (included in output, relevant when broadcasting) + BroadcastMode mode = 9; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signature bytes signature = 1; - // Signed transaction in JSON. + + // Signed transaction in JSON (pre-Stargate case) string json = 2; + + // Signed transaction containing protobuf encoded, Base64-encoded form (Stargate case), + // wrapped in a ready-to-broadcast json. + string serialized = 3; + + // Set in case of error + string error = 4; + + // signatures array json string + string signature_json = 5; } diff --git a/src/proto/Decred.proto b/src/proto/Decred.proto index d8355f5e6b5..fe9a8ff5663 100644 --- a/src/proto/Decred.proto +++ b/src/proto/Decred.proto @@ -6,11 +6,12 @@ option java_package = "wallet.core.jni.proto"; import "Bitcoin.proto"; import "Common.proto"; +// A transfer transaction message Transaction { - /// Serialization format + // Serialization format uint32 serializeType = 1; - /// Transaction data format version + // Transaction data format version uint32 version = 2; // A list of 1 or more transaction inputs or sources for coins. @@ -19,10 +20,10 @@ message Transaction { // A list of 1 or more transaction outputs or destinations for coins repeated TransactionOutput outputs = 4; - /// The time when a transaction can be spent (usually zero, in which case it has no effect). + // The time when a transaction can be spent (usually zero, in which case it has no effect). uint32 lockTime = 5; - /// The block height at which the transaction expires and is no longer valid. + // The block height at which the transaction expires and is no longer valid. uint32 expiry = 6; } @@ -34,8 +35,13 @@ message TransactionInput { // Transaction version as defined by the sender. uint32 sequence = 2; + // The amount of the input int64 valueIn = 3; + + // Creation block height uint32 blockHeight = 4; + + // Index within the block uint32 blockIndex = 5; // Computational script for confirming transaction authorization. @@ -47,14 +53,14 @@ message TransactionOutput { // Transaction amount. int64 value = 1; - /// Transaction output version. + // Transaction output version. uint32 version = 2; // Usually contains the public key as a Decred script setting up conditions to claim this output. bytes script = 3; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Resulting transaction. Note that the amount may be different than the requested amount to account for fees and available funds. Transaction transaction = 1; diff --git a/src/proto/EOS.proto b/src/proto/EOS.proto index d6a09f94f41..7f32c0a75fd 100644 --- a/src/proto/EOS.proto +++ b/src/proto/EOS.proto @@ -13,17 +13,22 @@ enum KeyType { // Values for an Asset object. message Asset { + // Total amount int64 amount = 1; + + // Number of decimals defined uint32 decimals = 2; + + // Asset symbol string symbol = 3; } // Input data necessary to create a signed transaction. message SigningInput { - // Chain id (256-bit number) + // Chain id (uint256, serialized little endian) bytes chain_id = 1; - // Reference Block Id (256-bits) + // Reference Block Id (uint256, serialized little endian) bytes reference_block_id = 2; // Timestamp on the reference block @@ -44,14 +49,14 @@ message SigningInput { // Asset details and amount Asset asset = 8; - // Sender's private key's raw bytes + // Sender's secret private key used for signing (32 bytes). bytes private_key = 9; // Type of the private key KeyType private_key_type = 10; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // JSON of the packed transaction. string json_encoded = 1; diff --git a/src/proto/Elrond.proto b/src/proto/Elrond.proto index 36cb5aaf2c2..9951055fde1 100644 --- a/src/proto/Elrond.proto +++ b/src/proto/Elrond.proto @@ -9,29 +9,104 @@ syntax = "proto3"; package TW.Elrond.Proto; option java_package = "wallet.core.jni.proto"; -// A transaction, typical balance transfer -message TransactionMessage { - uint64 nonce = 1; +// Generic action. Using one of the more specific actions (e.g. transfers, see below) is recommended. +message GenericAction { + // Accounts involved + Accounts accounts = 1; + + // amount string value = 2; - string receiver = 3; - string sender = 4; - uint64 gas_price = 5; - uint64 gas_limit = 6; - string data = 7; - string chain_id = 8; - uint32 version = 9; + + // additional data + string data = 3; + + // transaction version + uint32 version = 4; + + // Currently, the "options" field should be ignored (not set) by applications using TW Core. + // In the future, TW Core will handle specific transaction options + // (such as the "SignedWithHash" flag, as seen in https://github.com/ElrondNetwork/elrond-go-core/blob/main/core/versioning/txVersionChecker.go) + // when building and signing transactions. + uint32 options = 5; +} + +// EGLD transfer (move balance). +message EGLDTransfer { + // Accounts involved + Accounts accounts = 1; + + // Transfer amount (string) + string amount = 2; +} + +// ESDT transfer (transfer regular ESDTs - fungible tokens). +message ESDTTransfer { + // Accounts involved + Accounts accounts = 1; + + // Token ID + string token_identifier = 2; + + // Transfer token amount (string) + string amount = 3; +} + +// ESDTNFT transfer (transfer NFTs, SFTs and Meta ESDTs). +message ESDTNFTTransfer { + // Accounts involved + Accounts accounts = 1; + + // tokens + string token_collection = 2; + + // nonce of the token + uint64 token_nonce = 3; + + // transfer amount + string amount = 4; +} + +// Transaction sender & receiver etc. +message Accounts { + // Nonce of the sender + uint64 sender_nonce = 1; + + // Sender address + string sender = 2; + + // Sender username + string sender_username = 3; + + // Receiver address + string receiver = 4; + + string receiver_username = 5; } // Input data necessary to create a signed transaction. message SigningInput { - bytes private_key = 1; + // The secret private key used for signing (32 bytes). + bytes private_key = 1; + + // Chain identifier, string + string chain_id = 2; + + // Gas price + uint64 gas_price = 3; + + // Limit for the gas used + uint64 gas_limit = 4; + // transfer payload oneof message_oneof { - TransactionMessage transaction = 2; + GenericAction generic_action = 5; + EGLDTransfer egld_transfer = 6; + ESDTTransfer esdt_transfer = 7; + ESDTNFTTransfer esdtnft_transfer = 8; } } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { string encoded = 1; string signature = 2; diff --git a/src/proto/Ethereum.proto b/src/proto/Ethereum.proto index 8a91287f89a..ce3ab148556 100644 --- a/src/proto/Ethereum.proto +++ b/src/proto/Ethereum.proto @@ -3,11 +3,13 @@ syntax = "proto3"; package TW.Ethereum.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // Transaction (transfer, smart contract call, ...) message Transaction { // Native coin transfer transaction message Transfer { - // Amount to send in wei (256-bit number) + // Amount to send in wei (uint256, serialized little endian) bytes amount = 1; // Optional payload data @@ -16,40 +18,46 @@ message Transaction { // ERC20 token transfer transaction message ERC20Transfer { + // destination address (string) string to = 1; - // Amount to send (256-bit number) + // Amount to send (uint256, serialized little endian) bytes amount = 2; } // ERC20 approve transaction message ERC20Approve { + // Target of the approval string spender = 1; - // Amount to send (256-bit number) + // Amount to send (uint256, serialized little endian) bytes amount = 2; } // ERC721 NFT transfer transaction message ERC721Transfer { + // Source address string from = 1; + // Destination address string to = 2; - // ID of the token (256-bit number) + // ID of the token (uint256, serialized little endian) bytes token_id = 3; } // ERC1155 NFT transfer transaction message ERC1155Transfer { + // Source address string from = 1; + // Destination address string to = 2; - // ID of the token (256-bit number) + // ID of the token (uint256, serialized little endian) bytes token_id = 3; - // The amount of tokens being transferred + // The amount of tokens being transferred (uint256, serialized little endian) bytes value = 4; bytes data = 5; @@ -57,13 +65,14 @@ message Transaction { // Generic smart contract transaction message ContractGeneric { - // Amount to send in wei (256-bit number) + // Amount to send in wei (uint256, serialized little endian) bytes amount = 1; // Contract call payload data bytes data = 2; } + // Payload transfer oneof transaction_oneof { Transfer transfer = 1; ERC20Transfer erc20_transfer = 2; @@ -74,57 +83,69 @@ message Transaction { } } +// Transaction type enum TransactionMode { - Legacy = 0; // Legacy transaction, pre-EIP2718/EIP1559; for fee gasPrice/gasLimit is used - Enveloped = 1; // Enveloped transaction EIP2718 (with type 0x2), fee is according to EIP1559 (base fee, inclusion fee, ...) + // Legacy transaction, pre-EIP2718/EIP1559; for fee gasPrice/gasLimit is used + Legacy = 0; + + // Enveloped transaction EIP2718 (with type 0x2), fee is according to EIP1559 (base fee, inclusion fee, ...) + Enveloped = 1; } // Input data necessary to create a signed transaction. // Legacy and EIP2718/EIP1559 transactions supported, see TransactionMode. message SigningInput { - // Chain identifier (256-bit number) + // Chain identifier (uint256, serialized little endian) bytes chain_id = 1; - // Nonce (256-bit number) + // Nonce (uint256, serialized little endian) bytes nonce = 2; // Transaction version selector: Legacy or enveloped, has impact on fee structure. // Default is Legacy (value 0) TransactionMode tx_mode = 3; - // Gas price (256-bit number) + // Gas price (uint256, serialized little endian) // Relevant for legacy transactions only (disregarded for enveloped/EIP1559) bytes gas_price = 4; - // Gas limit (256-bit number) + // Gas limit (uint256, serialized little endian) bytes gas_limit = 5; - // Maxinmum optional inclusion fee (aka tip) (256-bit number) + // Maximum optional inclusion fee (aka tip) (uint256, serialized little endian) // Relevant for enveloped/EIP1559 transactions only, tx_mode=Enveloped, (disregarded for legacy) bytes max_inclusion_fee_per_gas = 6; - // Maxinmum fee (256-bit number) + // Maximum fee (uint256, serialized little endian) // Relevant for enveloped/EIP1559 transactions only, tx_mode=Enveloped, (disregarded for legacy) bytes max_fee_per_gas = 7; // Recipient's address. string to_address = 8; - // Private key. + // The secret private key used for signing (32 bytes). bytes private_key = 9; + // The payload transaction Transaction transaction = 10; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signed and encoded transaction bytes. bytes encoded = 1; + // The V, R, S components of the resulting signature, (each uint256, serialized little endian) bytes v = 2; bytes r = 3; bytes s = 4; // The payload part, supplied in the input or assembled from input parameters bytes data = 5; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 6; + + // error code description + string error_message = 7; } diff --git a/src/proto/Everscale.proto b/src/proto/Everscale.proto new file mode 100644 index 00000000000..54ed4bb825c --- /dev/null +++ b/src/proto/Everscale.proto @@ -0,0 +1,60 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +syntax = "proto3"; + +package TW.Everscale.Proto; +option java_package = "wallet.core.jni.proto"; + + +// Message option +enum MessageBehavior { + // Sends a message with the specified amount. The sender pays a fee from the account balance + SimpleTransfer = 0; + + // Sends the entire account balance along with the message + SendAllBalance = 1; +} + +// Transfer message +message Transfer { + // If set to true, then the message will be returned if there is an error on the recipient's side. + bool bounce = 1; + + // Affect the attached amount and fees + MessageBehavior behavior = 2; + + // Amount to send in nano EVER + uint64 amount = 3; + + // Expiration UNIX timestamp + uint32 expired_at = 4; + + // Recipient address + string to = 5; + + // Account state if there is any + oneof account_state_oneof { + // Just contract data + string encoded_contract_data = 6; + } +} + +// Input data necessary to create a signed transaction. +message SigningInput { + // The payload transfer + oneof action_oneof { + Transfer transfer = 1; + } + + // The secret private key used for signing (32 bytes). + bytes private_key = 2; +} + +// Result containing the signed and encoded transaction. +message SigningOutput { + string encoded = 1; +} diff --git a/src/proto/FIO.proto b/src/proto/FIO.proto index 5579cde39dd..e946dcf3fa7 100644 --- a/src/proto/FIO.proto +++ b/src/proto/FIO.proto @@ -52,6 +52,7 @@ message Action { } // Acion for adding public chain addresses to a FIO name; add_pub_address + // Note: actor is not needed, computed from private key message AddPubAddress { // The FIO name already registered to the owner. Ex.: "alice@trust" string fio_address = 1; @@ -60,12 +61,11 @@ message Action { repeated PublicAddress public_addresses = 2; // Max fee to spend, can be obtained using get_fee API. - uint64 fee = 3; - - // Note: actor is not needed, computed from private key + uint64 fee = 3; } - // Action for transfering FIO coins; transfer_tokens_pub_key + // Action for transferring FIO coins; transfer_tokens_pub_key + // Note: actor is not needed, computed from private key message Transfer { // FIO address of the payee. Ex.: "FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf" string payee_public_key = 1; @@ -75,11 +75,10 @@ message Action { // Max fee to spend, can be obtained using get_fee API. uint64 fee = 3; - - // Note: actor is not needed, computed from private key } // Action for renewing a FIO name; renew_fio_address + // Note: actor is not needed, computed from private key message RenewFioAddress { // The FIO name to be renewed. Ex.: "alice@trust" string fio_address = 1; @@ -89,11 +88,10 @@ message Action { // Max fee to spend, can be obtained using get_fee API. uint64 fee = 3; - - // Note: actor is not needed, computed from owner_fio_public_key } // Action for creating a new payment request; new_funds_request + // Note: actor is not needed, computed from private key message NewFundsRequest { // The FIO name of the requested payer. Ex.: "alice@trust" string payer_fio_name = 1; @@ -109,10 +107,9 @@ message Action { // Max fee to spend, can be obtained using get_fee API. uint64 fee = 5; - - // Note: actor is not needed, computed from private key } + // Payload message oneof message_oneof { RegisterFioAddress register_fio_address_message = 1; AddPubAddress add_pub_address_message = 2; @@ -134,7 +131,7 @@ message ChainParams { uint64 ref_block_prefix = 3; } -// Transaction signing input +// Input data necessary to create a signed transaction. message SigningInput { // Expiry for this message, in unix time. Can be 0, then it is taken from current time with default expiry uint32 expiry = 1; @@ -142,7 +139,7 @@ message SigningInput { // Current parameters of the FIO blockchain ChainParams chain_params = 2; - // The private key matching the address, needed for signing + // The secret private key matching the address, used for signing (32 bytes). bytes private_key = 3; // The FIO name of the originating wallet (project-wide constant) @@ -152,7 +149,7 @@ message SigningInput { Action action = 5; } -// Transaction signing output +// Result containing the signed and encoded transaction. message SigningOutput { // Signed transaction in JSON string json = 1; diff --git a/src/proto/Filecoin.proto b/src/proto/Filecoin.proto index 0c39da82c72..58ef6a156c0 100644 --- a/src/proto/Filecoin.proto +++ b/src/proto/Filecoin.proto @@ -5,7 +5,7 @@ option java_package = "wallet.core.jni.proto"; // Input data necessary to create a signed transaction. message SigningInput { - // Private key of sender account. + // The secret private key of the sender account, used for signing (32 bytes). bytes private_key = 1; // Recipient's address. @@ -14,20 +14,21 @@ message SigningInput { // Transaction nonce. uint64 nonce = 3; - // Transfer value. + // Transfer value (uint256, serialized little endian) bytes value = 4; // Gas limit. int64 gas_limit = 5; - // Gas fee cap. + // Gas fee cap (uint256, serialized little endian) bytes gas_fee_cap = 6; - // Gas premium. + // Gas premium (uint256, serialized little endian) bytes gas_premium = 7; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { + // Resulting transaction, in JSON. string json = 1; } diff --git a/src/proto/Harmony.proto b/src/proto/Harmony.proto index 72762c24acd..86647fad3bd 100644 --- a/src/proto/Harmony.proto +++ b/src/proto/Harmony.proto @@ -5,54 +5,58 @@ option java_package = "wallet.core.jni.proto"; // Input data necessary to create a signed transaction. message SigningInput { - // Chain identifier (256-bit number) + // Chain identifier (uint256, serialized little endian) bytes chain_id = 1; - // Private key. + // The secret private key used for signing (32 bytes). bytes private_key = 2; + // The payload message oneof message_oneof { TransactionMessage transaction_message = 3; StakingMessage staking_message = 4; } } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signed and encoded transaction bytes. bytes encoded = 1; + // THE V,R,S components of the signature bytes v = 2; bytes r = 3; bytes s = 4; } +// A Transfer message message TransactionMessage { - // Nonce (256-bit number) + // Nonce (uint256, serialized little endian) bytes nonce = 1; - // Gas price (256-bit number) + // Gas price (uint256, serialized little endian) bytes gas_price = 2; - // Gas limit (256-bit number) + // Gas limit (uint256, serialized little endian) bytes gas_limit = 3; // Recipient's address. string to_address = 4; - // Amount to send in wei (256-bit number) + // Amount to send in wei (uint256, serialized little endian) bytes amount = 5; // Optional payload bytes payload = 6; - // From shard ID (256-bit number) + // From shard ID (uint256, serialized little endian) bytes from_shard_id = 7; - // To Shard ID (256-bit number) + // To Shard ID (uint256, serialized little endian) bytes to_shard_id = 8; } +// A Staking message. message StakingMessage { // StakeMsg oneof stake_msg { @@ -63,16 +67,17 @@ message StakingMessage { DirectiveCollectRewards collect_rewards = 5; } - // Nonce (256-bit number) + // Nonce (uint256, serialized little endian) bytes nonce = 6; - // Gas price (256-bit number) + // Gas price (uint256, serialized little endian) bytes gas_price = 7; - // Gas limit (256-bit number) + // Gas limit (uint256, serialized little endian) bytes gas_limit = 8; } +// Description for a validator message Description { string name = 1; string identity = 2; @@ -81,21 +86,38 @@ message Description { string details = 5; } +// A variable precision number message Decimal { + // The 'raw' value bytes value = 1; + + // The precision (number of decimals) bytes precision = 2; } +// Represents validator commission rule message CommissionRate { + // The rate Decimal rate = 1; + + // Maximum rate Decimal max_rate = 2; + + // Maximum of rate change Decimal max_change_rate = 3; } +// Create Validator directive message DirectiveCreateValidator { + // Address of validator string validator_address = 1; + + // Description, name etc. Description description = 2; + + // Rates CommissionRate commission_rates = 3; + bytes min_self_delegation = 4; bytes max_total_delegation = 5; repeated bytes slot_pub_keys = 6; @@ -103,7 +125,10 @@ message DirectiveCreateValidator { bytes amount = 8; } + +// Edit Validator directive message DirectiveEditValidator { + // Validator address string validator_address = 1; Description description = 2; Decimal commission_rate = 3; @@ -115,18 +140,33 @@ message DirectiveEditValidator { bytes active = 9; } +// Delegate directive message DirectiveDelegate { + // Delegator address string delegator_address = 1; + + // Validator address string validator_address = 2; + + // Delegate amount (uint256, serialized little endian) bytes amount = 3; } +// Undelegate directive message DirectiveUndelegate { + // Delegator address string delegator_address = 1; + + // Validator address string validator_address = 2; + + // Undelegate amount (uint256, serialized little endian) bytes amount = 3; } + +// Collect reward message DirectiveCollectRewards { + // Delegator address string delegator_address = 1; -} \ No newline at end of file +} diff --git a/src/proto/Icon.proto b/src/proto/Icon.proto index 8319b026d7c..fc39efc3ee8 100644 --- a/src/proto/Icon.proto +++ b/src/proto/Icon.proto @@ -11,7 +11,7 @@ message SigningInput { // Recipient address. string to_address = 2; - // Transfer amount. + // Transfer amount (uint256, serialized little endian) bytes value = 3; // The amount of step to send with the transaction. @@ -26,11 +26,11 @@ message SigningInput { // Network identifier bytes network_id = 7; - // Private key. + // The secret private key used for signing (32 bytes). bytes private_key = 8; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // JSON-encoded transaction parameters. string encoded = 1; diff --git a/src/proto/IoTeX.proto b/src/proto/IoTeX.proto index 1df337cf627..275dd40d8ab 100644 --- a/src/proto/IoTeX.proto +++ b/src/proto/IoTeX.proto @@ -3,96 +3,159 @@ syntax = "proto3"; package TW.IoTeX.Proto; option java_package = "wallet.core.jni.proto"; +// A transfer message Transfer { + // Amount (as string) string amount = 1; + + // Destination address string recipient = 2; + + // Payload data bytes payload = 3; } +// A Staking message message Staking { - // create stake - message Create { - string candidateName = 1; - string stakedAmount = 2; - uint32 stakedDuration = 3; - bool autoStake = 4; - bytes payload = 5; - } - - // unstake or withdraw - message Reclaim { - uint64 bucketIndex = 1; - bytes payload = 2; - } - - // add the amount of bucket - message AddDeposit { - uint64 bucketIndex = 1; - string amount = 2; - bytes payload = 3; - } - - // restake the duration and autoStake flag of bucket - message Restake { - uint64 bucketIndex = 1; - uint32 stakedDuration = 2; - bool autoStake = 3; - bytes payload = 4; - } - - // move the bucket to vote for another candidate or transfer the ownership of bucket to another voters - message ChangeCandidate { - uint64 bucketIndex = 1; - string candidateName = 2; - bytes payload = 3; - } - - message TransferOwnership { - uint64 bucketIndex = 1; - string voterAddress = 2; - bytes payload = 3; - } - - message CandidateBasicInfo { - string name = 1; - string operatorAddress = 2; - string rewardAddress = 3; - } - - message CandidateRegister { - CandidateBasicInfo candidate = 1; - string stakedAmount = 2; - uint32 stakedDuration = 3; - bool autoStake = 4; - string ownerAddress = 5; // if ownerAddress is absent, owner of candidate is the sender - bytes payload = 6; - } - oneof message { - Create stakeCreate = 1; - Reclaim stakeUnstake = 2; - Reclaim stakeWithdraw = 3; - AddDeposit stakeAddDeposit = 4; - Restake stakeRestake = 5; - ChangeCandidate stakeChangeCandidate = 6; - TransferOwnership stakeTransferOwnership = 7; - CandidateRegister candidateRegister = 8; - CandidateBasicInfo candidateUpdate = 9; - } + // create stake + message Create { + // validator name + string candidateName = 1; + + // amount to be staked + string stakedAmount = 2; + + // duration + uint32 stakedDuration = 3; + + // auto-restake + bool autoStake = 4; + + // payload data + bytes payload = 5; + } + + // unstake or withdraw + message Reclaim { + // index to claim + uint64 bucketIndex = 1; + + // payload data + bytes payload = 2; + } + + // add the amount of bucket + message AddDeposit { + // index + uint64 bucketIndex = 1; + + // amount to add + string amount = 2; + + // payload data + bytes payload = 3; + } + + // restake the duration and autoStake flag of bucket + message Restake { + // index + uint64 bucketIndex = 1; + + // stake duration + uint32 stakedDuration = 2; + + // auto re-stake + bool autoStake = 3; + + // payload data + bytes payload = 4; + } + + // move the bucket to vote for another candidate or transfer the ownership of bucket to another voters + message ChangeCandidate { + // index + uint64 bucketIndex = 1; + + // validator name + string candidateName = 2; + + // payload data + bytes payload = 3; + } + + // transfer ownserhip of stake + message TransferOwnership { + // index + uint64 bucketIndex = 1; + + // address of voter + string voterAddress = 2; + + // payload data + bytes payload = 3; + } + + // Candidate (validator) info + message CandidateBasicInfo { + string name = 1; + string operatorAddress = 2; + string rewardAddress = 3; + } + + // Register a Candidate + message CandidateRegister { + CandidateBasicInfo candidate = 1; + string stakedAmount = 2; + uint32 stakedDuration = 3; + bool autoStake = 4; + string ownerAddress = 5; // if ownerAddress is absent, owner of candidate is the sender + bytes payload = 6; + } + + // the payload message + oneof message { + Create stakeCreate = 1; + Reclaim stakeUnstake = 2; + Reclaim stakeWithdraw = 3; + AddDeposit stakeAddDeposit = 4; + Restake stakeRestake = 5; + ChangeCandidate stakeChangeCandidate = 6; + TransferOwnership stakeTransferOwnership = 7; + CandidateRegister candidateRegister = 8; + CandidateBasicInfo candidateUpdate = 9; + } } +// Arbitrary contract call message ContractCall { + // amount string amount = 1; + + // contract address string contract = 2; + + // payload data bytes data = 3; } -// transaction signing input +// Input data necessary to create a signed transaction. message SigningInput { + // Transaction version uint32 version = 1; + + // Nonce (should be larger than in the last transaction of the account) uint64 nonce = 2; + + // Limit for the gas used uint64 gasLimit = 3; + + // Gas price string gasPrice = 4; + + // The secret private key used for signing (32 bytes). bytes privateKey = 5; + + // Payload transfer oneof action { Transfer transfer = 10; ContractCall call = 12; @@ -106,10 +169,10 @@ message SigningInput { Staking.TransferOwnership stakeTransferOwnership = 46; Staking.CandidateRegister candidateRegister = 47; Staking.CandidateBasicInfo candidateUpdate = 48; - } + } } -// transaction signing output +// Result containing the signed and encoded transaction. message SigningOutput { // Signed and encoded Action bytes bytes encoded = 1; @@ -118,29 +181,47 @@ message SigningOutput { bytes hash = 2; } +// An Action structure +// Used internally message ActionCore { - uint32 version = 1; - uint64 nonce = 2; - uint64 gasLimit = 3; - string gasPrice = 4; - oneof action { - Transfer transfer = 10; - ContractCall execution = 12; - // Native staking - Staking.Create stakeCreate = 40; - Staking.Reclaim stakeUnstake = 41; - Staking.Reclaim stakeWithdraw = 42; - Staking.AddDeposit stakeAddDeposit = 43; - Staking.Restake stakeRestake = 44; - Staking.ChangeCandidate stakeChangeCandidate = 45; - Staking.TransferOwnership stakeTransferOwnership = 46; - Staking.CandidateRegister candidateRegister = 47; - Staking.CandidateBasicInfo candidateUpdate = 48; - } + // version number + uint32 version = 1; + + // Nonce (should be larger than in the last transaction of the account) + uint64 nonce = 2; + + // Gas limit + uint64 gasLimit = 3; + + // Gas price + string gasPrice = 4; + + // action payload + oneof action { + Transfer transfer = 10; + ContractCall execution = 12; + // Native staking + Staking.Create stakeCreate = 40; + Staking.Reclaim stakeUnstake = 41; + Staking.Reclaim stakeWithdraw = 42; + Staking.AddDeposit stakeAddDeposit = 43; + Staking.Restake stakeRestake = 44; + Staking.ChangeCandidate stakeChangeCandidate = 45; + Staking.TransferOwnership stakeTransferOwnership = 46; + Staking.CandidateRegister candidateRegister = 47; + Staking.CandidateBasicInfo candidateUpdate = 48; + } } +// Signed Action +// Used internally message Action { - ActionCore core = 1; - bytes senderPubKey = 2; - bytes signature = 3; -} \ No newline at end of file + // Action details + ActionCore core = 1; + + // public key + bytes senderPubKey = 2; + + // the signature + bytes signature = 3; +} diff --git a/src/proto/NEAR.proto b/src/proto/NEAR.proto index 7bd8b2e2781..a55a9079f94 100644 --- a/src/proto/NEAR.proto +++ b/src/proto/NEAR.proto @@ -3,63 +3,97 @@ syntax = "proto3"; package TW.NEAR.Proto; option java_package = "wallet.core.jni.proto"; +// Public key with type message PublicKey { + // Key type uint32 key_type = 1; + + // The public key data bytes data = 2; } +// Permissions for a function call message FunctionCallPermission { - bytes allowance = 1; // uint128 / little endian byte order + // uint128 / little endian byte order + bytes allowance = 1; + string receiver_id = 2; + + repeated string method_names = 3; } +// Full access message FullAccessPermission { } +// Access key: nonce + permission message AccessKey { + // Nonce uint64 nonce = 1; + + // Permission oneof permission { FunctionCallPermission function_call = 2; FullAccessPermission full_access = 3; } } +// Create Account message CreateAccount { } +// Deploying a contract message DeployContract { bytes code = 1; } +// A method/function call message FunctionCall { - bytes method_name = 1; + // Method/function name + string method_name = 1; + + // input arguments bytes args = 2; + + // gas uint64 gas = 3; - bytes deposit = 4; // uint128 / little endian byte order + + // uint128 / little endian byte order + bytes deposit = 4; } +// Transfer message Transfer { - bytes deposit = 1; // uint128 / little endian byte order + // amount; uint128 / little endian byte order + bytes deposit = 1; } +// Stake message Stake { - bytes stake = 1; // uint128 / little endian byte order - string public_key = 2; + // amount; uint128 / little endian byte order + bytes stake = 1; + + // owner public key + PublicKey public_key = 2; } +// Add a key message AddKey { PublicKey public_key = 1; AccessKey access_key = 2; } +// Delete a key message DeleteKey { PublicKey public_key = 1; } +// Delete account message DeleteAccount { string beneficiary_id = 1; } +// Represents an action message Action { oneof payload { CreateAccount create_account = 1; @@ -75,17 +109,30 @@ message Action { // Input data necessary to create a signed order. message SigningInput { + // ID of the sender string signer_id = 1; + + // Nonce (should be larger than in the last transaction of the account) uint64 nonce = 2; + + // ID of the receiver string receiver_id = 3; + + // Recent block hash bytes block_hash = 4; + + // Payload action(s) repeated Action actions = 5; + // The secret private key used for signing (32 bytes). bytes private_key = 6; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signed transaction blob bytes signed_transaction = 1; + + // Hash of the transaction + bytes hash = 2; } diff --git a/src/proto/NEO.proto b/src/proto/NEO.proto index 0093145f29f..096327cbc51 100644 --- a/src/proto/NEO.proto +++ b/src/proto/NEO.proto @@ -5,35 +5,61 @@ option java_package = "wallet.core.jni.proto"; import "Common.proto"; +// Input for a transaction (output of a prev tx) message TransactionInput { + // Previous tx hash bytes prev_hash = 1; + + // Output index fixed32 prev_index = 2; // unspent value of UTXO int64 value = 3; + // Asset string asset_id = 4; } +// Output of a transaction message TransactionOutput { + // Asset string asset_id = 1; + + // Amount (as string) sint64 amount = 2; + + // destination address string to_address = 3; + + // change address string change_address = 4; } // Input data necessary to create a signed transaction. message SigningInput { + // Available transaction inputs repeated TransactionInput inputs = 1; + + // Transaction outputs repeated TransactionOutput outputs = 2; + + // The secret private key used for signing (32 bytes). bytes private_key = 3; + + // Fee int64 fee = 4; + + // Asset ID for gas string gas_asset_id = 5; + + // Address for the change string gas_change_address = 6; + + // Optional transaction plan (if missing it will be computed) TransactionPlan plan = 7; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signed and encoded transaction bytes. bytes encoded = 1; @@ -50,9 +76,16 @@ message TransactionOutputPlan { // Maximum available amount. int64 available_amount = 2; + // Amount that is left as change int64 change = 3; + + // Asset string asset_id = 4; + + // Destination address string to_address = 5; + + // Address for the change string change_address = 6; }; @@ -69,4 +102,4 @@ message TransactionPlan { // Optional error Common.Proto.SigningError error = 4; -}; \ No newline at end of file +}; diff --git a/src/proto/NULS.proto b/src/proto/NULS.proto index 2bcfd9d7a2e..51ed1806919 100644 --- a/src/proto/NULS.proto +++ b/src/proto/NULS.proto @@ -3,65 +3,122 @@ syntax = "proto3"; package TW.NULS.Proto; option java_package = "wallet.core.jni.proto"; +// Transaction from address message TransactionCoinFrom { + // Source address string from_address = 1; + + // Chain ID uint32 assets_chainid = 2; + + // ID of the asset uint32 assets_id = 3; - //tranaction out amount (256-bit number) + + // transaction out amount (256-bit number) bytes id_amount = 4; - //8 bytes + + // Nonce, 8 bytes bytes nonce = 5; - //lock status: 1 locked; 0 unlocked + + // lock status: 1 locked; 0 unlocked uint32 locked = 6; } +// Transaction to a destination message TransactionCoinTo { + // destination address string to_address = 1; + + // Chain ID uint32 assets_chainid = 2; + + // ID of the asset uint32 assets_id = 3; - // tranaction amount (256-bit number) + + // transaction amount (uint256, serialized little endian) bytes id_amount = 4; + + // lock time uint32 lock_time = 5; } +// A signature message Signature { + // Length of public key data uint32 pkey_len = 1; + + // The public key bytes public_key = 2; + + // The length of the signature uint32 sig_len = 3; + + // The signature data bytes signature = 4; } +// A transaction message Transaction { + // transaction type uint32 type = 1; + + // Timestamp of the transaction uint32 timestamp = 2; + + // Optional string remark string remark = 3; + + // The raw data bytes tx_data = 4; - //CoinFrom + + // CoinFrom TransactionCoinFrom input = 5; - //CoinTo + + // CoinTo TransactionCoinTo output = 6; + // Signature Signature tx_sigs = 7; + + // Tx hash uint32 hash = 8; } // Input data necessary to create a signed order. message SigningInput { + // The secret private key used for signing (32 bytes). bytes private_key = 1; + + // Source address string from = 2; + + // Destination address string to = 3; + + // Transfer amount (uint256, serialized little endian) bytes amount = 4; + + // Chain ID uint32 chain_id = 5; + + // Asset ID uint32 idassets_id = 6; - //The last 8 bytes of latest transaction hash + + // The last 8 bytes of latest transaction hash bytes nonce = 7; + + // Optional memo remark string remark = 8; + // Account balance bytes balance = 9; + // time, accurate to the second uint32 timestamp = 10; } +// Result containing the signed and encoded transaction. message SigningOutput { + // Encoded transaction bytes encoded = 1; -} \ No newline at end of file +} diff --git a/src/proto/Nano.proto b/src/proto/Nano.proto index 954c7930e28..9bd7a7a26bd 100644 --- a/src/proto/Nano.proto +++ b/src/proto/Nano.proto @@ -5,12 +5,13 @@ option java_package = "wallet.core.jni.proto"; // Input data necessary to create a signed transaction. message SigningInput { - // Private key + // The secret private key used for signing (32 bytes). bytes private_key = 1; // Optional parent block hash bytes parent_block = 2; + // Receive/Send reference oneof link_oneof { // Hash of a block to receive from bytes link_block = 3; @@ -28,12 +29,14 @@ message SigningInput { string work = 7; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signature bytes signature = 1; + // Block hash bytes block_hash = 2; - // Json representation of the block + + // JSON representation of the block string json = 3; } diff --git a/src/proto/Nebulas.proto b/src/proto/Nebulas.proto index fc8e7e9acf9..ea915410d59 100644 --- a/src/proto/Nebulas.proto +++ b/src/proto/Nebulas.proto @@ -8,42 +8,47 @@ message SigningInput { // sender's address. string from_address = 1; - // Chain identifier (256-bit number) + // Chain identifier (uint256, serialized little endian) bytes chain_id = 2; - // Nonce (256-bit number) + // Nonce (uint256, serialized little endian) bytes nonce = 3; - // Gas price (256-bit number) + // Gas price (uint256, serialized little endian) bytes gas_price = 4; - // Gas limit (256-bit number) + // Gas limit (uint256, serialized little endian) bytes gas_limit = 5; // Recipient's address. string to_address = 6; - // Amount to send in wei, 1 NAS = 10^18 Wei (256-bit number) + // Amount to send in wei, 1 NAS = 10^18 Wei (uint256, serialized little endian) bytes amount = 7; - // Timestamp to create transaction (256-bit number) + // Timestamp to create transaction (uint256, serialized little endian) bytes timestamp = 8; // Optional payload string payload = 9; - // Private key. + // The secret private key used for signing (32 bytes). bytes private_key = 10; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { + // Algorithm code uint32 algorithm = 1; + + // The signature bytes signature = 2; + + // Encoded transaction string raw = 3; } -// +// Generic data message Data { string type = 1; bytes payload = 2; @@ -51,17 +56,39 @@ message Data { // Raw transaction data message RawTransaction { + // tx hash bytes hash = 1; + + // source address bytes from = 2; + + // destination address bytes to = 3; + + // amount (uint256, serialized little endian) bytes value = 4; + + // Nonce (should be larger than in the last transaction of the account) uint64 nonce = 5; + + // transaction timestamp int64 timestamp = 6; + + // generic data Data data = 7; + + // chain ID (4 bytes) uint32 chain_id = 8; + + // gas price (uint256, serialized little endian) bytes gas_price = 9; + + // gas limit (uint256, serialized little endian) bytes gas_limit = 10; + // algorithm uint32 alg = 11; + + // signature bytes sign = 12; -} \ No newline at end of file +} diff --git a/src/proto/Nervos.proto b/src/proto/Nervos.proto new file mode 100644 index 00000000000..07cc53060d0 --- /dev/null +++ b/src/proto/Nervos.proto @@ -0,0 +1,208 @@ +// Copyright © 2017-2021 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.Nervos.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Common.proto"; + +// Nervos transaction plan +message TransactionPlan { + // A list of cell deps. + repeated CellDep cell_deps = 1; + + // A list of header deps. + repeated bytes header_deps = 2; + + // A list of 1 or more selected cells for this transaction + repeated Cell selected_cells = 3; + + // A list of 1 or more outputs by this transaction + repeated CellOutput outputs = 4; + + // A list of outputs data. + repeated bytes outputs_data = 5; + + // Optional error + Common.Proto.SigningError error = 6; +} + +// Nervos cell dep. +message CellDep { + // Prevents the transaction to be mined before an absolute or relative time + string dep_type = 1; + + // Reference to the previous transaction's output. + OutPoint out_point = 2; +} + +// Nervos transaction out-point reference. +message OutPoint { + // The hash of the referenced transaction. + bytes tx_hash = 1; + + // The index of the specific output in the transaction. + uint32 index = 2; +} + +// Nervos cell output. +message CellOutput { + // Transaction amount. + uint64 capacity = 1; + + // Lock script + Script lock = 2; + + // Type script + Script type = 3; +} + +// Nervos script +message Script { + // Code hash + bytes code_hash = 1; + + // Hash type + string hash_type = 2; + + // args + bytes args = 3; +} + +// Transfer of native asset +message NativeTransfer { + // Recipient's address. + string to_address = 1; + + // Change address. + string change_address = 2; + + // Amount to send. + uint64 amount = 3; + + // If sending max amount. + bool use_max_amount = 4; +} + +// Token transfer (SUDT) +message SudtTransfer { + // Recipient's address. + string to_address = 1; + + // Change address. + string change_address = 2; + + // SUDT (Simple User Defined Token) address + bytes sudt_address = 3; + + // Amount to send. + string amount = 4; + + // If sending max amount. + bool use_max_amount = 5; +} + +// Deposit +message DaoDeposit { + // Recipient's address. + string to_address = 1; + + // Change address. + string change_address = 2; + + // Amount to deposit. + uint64 amount = 3; +} + +message DaoWithdrawPhase1 { + // Deposit cell + Cell deposit_cell = 1; + + // Change address. + string change_address = 2; +} + +message DaoWithdrawPhase2 { + // Deposit cell + Cell deposit_cell = 1; + + // Withdrawing cell + Cell withdrawing_cell = 2; + + // Amount + uint64 amount = 3; +} + +// Input data necessary to create a signed transaction. +message SigningInput { + // Transaction fee per byte. + uint64 byte_fee = 1; + + // The available secret private keys used for signing (32 bytes each). + repeated bytes private_key = 2; + + // Available unspent cell outputs. + repeated Cell cell = 3; + + // Optional transaction plan + TransactionPlan plan = 4; + + // The payload transfer + oneof operation_oneof { + NativeTransfer native_transfer = 5; + SudtTransfer sudt_transfer = 6; + DaoDeposit dao_deposit = 7; + DaoWithdrawPhase1 dao_withdraw_phase1 = 8; + DaoWithdrawPhase2 dao_withdraw_phase2 = 9; + } +} + +// An unspent cell output, that can serve as input to a transaction +message Cell { + // The unspent output + OutPoint out_point = 1; + + // Amount of the cell + uint64 capacity = 2; + + // Lock script + Script lock = 3; + + // Type script + Script type = 4; + + // Data + bytes data = 5; + + // Optional block number + uint64 block_number = 6; + + // Optional block hash + bytes block_hash = 7; + + // Optional since the cell is available to spend + uint64 since = 8; + + // Optional input type data to be included in witness + bytes input_type = 9; + + // Optional output type data to be included in witness + bytes output_type = 10; +} + +// Result containing the signed and encoded transaction. +message SigningOutput { + // Resulting transaction. Note that the amount may be different than the requested amount to account for fees and available funds. + string transaction_json = 1; + + // Transaction id + string transaction_id = 2; + + // Optional error + Common.Proto.SigningError error = 3; +} diff --git a/src/proto/Nimiq.proto b/src/proto/Nimiq.proto index 6c96981278d..76d00dd903e 100644 --- a/src/proto/Nimiq.proto +++ b/src/proto/Nimiq.proto @@ -5,18 +5,24 @@ option java_package = "wallet.core.jni.proto"; // Input data necessary to create a signed transaction. message SigningInput { + // The secret private key used for signing (32 bytes). bytes private_key = 1; + // Destination address string destination = 2; + // Amount of the transfer uint64 value = 3; + // Fee amount uint64 fee = 4; + // Validity start, in block height uint32 validity_start_height = 5; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { + // The encoded transaction bytes encoded = 1; } diff --git a/src/proto/Oasis.proto b/src/proto/Oasis.proto index 6b327044b58..316f6e295df 100644 --- a/src/proto/Oasis.proto +++ b/src/proto/Oasis.proto @@ -9,28 +9,39 @@ syntax = "proto3"; package TW.Oasis.Proto; option java_package = "wallet.core.jni.proto"; +// Transfer message TransferMessage { + // destination address string to = 1; + + // Gas price uint64 gas_price = 2; // Amount values strings prefixed by zero. e.g. "\u000010000000" string gas_amount = 3; + // Amount values strings prefixed by zero string amount = 4; + // Nonce (should be larger than in the last transaction of the account) uint64 nonce = 5; + + // Context, see https://docs.oasis.dev/oasis-core/common-functionality/crypto#domain-separation string context = 6; } +// Input data necessary to create a signed transaction. message SigningInput { + // The secret private key used for signing (32 bytes). bytes private_key = 1; + // Transfer payload oneof message { TransferMessage transfer = 2; } } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signed and encoded transaction bytes. bytes encoded = 1; diff --git a/src/proto/Ontology.proto b/src/proto/Ontology.proto index 28992aacfcf..9496537dd18 100644 --- a/src/proto/Ontology.proto +++ b/src/proto/Ontology.proto @@ -5,32 +5,39 @@ option java_package = "wallet.core.jni.proto"; // Input data necessary to create a signed transaction. message SigningInput { - + // Contract ID, e.g. "ONT" string contract = 1; + // Method, e.g. "transfer" string method = 2; + // The secret private key used for signing (32 bytes). bytes owner_private_key = 3; // base58 encode address string (160-bit number) string to_address = 4; + // Transfer amount uint64 amount = 5; + // Private key of the payer bytes payer_private_key = 6; + // Gas price uint64 gas_price = 7; + // Limit for gas used uint64 gas_limit = 8; // base58 encode address string (160-bit number) string query_address = 9; + // Nonce (should be larger than in the last transaction of the account) uint32 nonce = 10; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signed and encoded transaction bytes. bytes encoded = 1; diff --git a/src/proto/Polkadot.proto b/src/proto/Polkadot.proto index a0f07d1b0c6..178a26485de 100644 --- a/src/proto/Polkadot.proto +++ b/src/proto/Polkadot.proto @@ -3,17 +3,20 @@ syntax = "proto3"; package TW.Polkadot.Proto; option java_package = "wallet.core.jni.proto"; +// Known networks enum Network { POLKADOT = 0; KUSAMA = 2; } +// Destination options for reward enum RewardDestination { STAKED = 0; STASH = 1; CONTROLLER = 2; } +// An era, a period defined by a starting block and length message Era { // recent block number (called phase in polkadot code), should match block hash uint64 block_number = 1; @@ -22,48 +25,84 @@ message Era { uint64 period = 2; } +// Balance transfer transaction message Balance { + // transfer message Transfer { + // destination address string to_address = 1; - bytes value = 2; // big integer + + // amount (uint256, serialized little endian) + bytes value = 2; } oneof message_oneof { Transfer transfer = 1; } } +// Staking transaction message Staking { + // Bond to a controller message Bond { + // controller ID string controller = 1; + + // amount (uint256, serialized little endian) bytes value = 2; + + // destination for rewards RewardDestination reward_destination = 3; } + // Bond to a controller, with nominators message BondAndNominate { + // controller ID string controller = 1; + + // amount (uint256, serialized little endian) bytes value = 2; + + // destination for rewards RewardDestination reward_destination = 3; + + // list of nominators repeated string nominators = 4; } + // Bond extra amount message BondExtra { + // amount (uint256, serialized little endian) bytes value = 1; } + // Unbond message Unbond { + // amount (uint256, serialized little endian) bytes value = 1; } + // Withdraw unbonded amounts message WithdrawUnbonded { int32 slashing_spans = 1; } + // Nominate message Nominate { + // list of nominators repeated string nominators = 1; } - message Chill {} + // Chill and unbound + message ChillAndUnbond { + // amount (uint256, serialized little endian) + bytes value = 1; + } + + // Chill + message Chill { + } + // Payload messsage oneof message_oneof { Bond bond = 1; BondAndNominate bond_and_nominate = 2; @@ -72,6 +111,7 @@ message Staking { WithdrawUnbonded withdraw_unbonded = 5; Nominate nominate = 6; Chill chill = 7; + ChillAndUnbond chill_and_unbond = 8; } } @@ -80,28 +120,38 @@ message SigningInput { // Recent block hash, or genesis hash if era is not set bytes block_hash = 1; + // Genesis block hash (identifies the chain) bytes genesis_hash = 2; // Current account nonce uint64 nonce = 3; + // Specification version, e.g. 26. uint32 spec_version = 4; + + // Transaction version, e.g. 5. uint32 transaction_version = 5; - bytes tip = 6; // big integer + + // Optional tip to pay, big integer + bytes tip = 6; // Optional time validity limit, recommended, for replay-protection. Empty means Immortal. Era era = 7; + // The secret private key used for signing (32 bytes). bytes private_key = 8; + + // Network type Network network = 9; + // Payload message oneof message_oneof { Balance balance_call = 10; Staking staking_call = 11; } } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signed and encoded transaction bytes. bytes encoded = 1; diff --git a/src/proto/Ripple.proto b/src/proto/Ripple.proto index 8d0be3c317e..ffbb863741c 100644 --- a/src/proto/Ripple.proto +++ b/src/proto/Ripple.proto @@ -3,28 +3,43 @@ syntax = "proto3"; package TW.Ripple.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // Input data necessary to create a signed transaction. message SigningInput { + // Transfer amount int64 amount = 1; + // Transfer fee int64 fee = 2; + // Account sequence number int32 sequence = 3; + // Ledger sequence number int32 last_ledger_sequence = 4; + // Source account string account = 5; + // Target account string destination = 6; + // A Destination Tag int64 destination_tag = 7; + // Transaction flags, optional int64 flags = 8; + // The secret private key used for signing (32 bytes). bytes private_key = 9; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { + // Encoded transaction bytes encoded = 1; + + // Optional error + Common.Proto.SigningError error = 2; } diff --git a/src/proto/Solana.proto b/src/proto/Solana.proto index 80eaad6c1ef..0f7280b5da3 100644 --- a/src/proto/Solana.proto +++ b/src/proto/Solana.proto @@ -3,39 +3,62 @@ syntax = "proto3"; package TW.Solana.Proto; option java_package = "wallet.core.jni.proto"; +// Transfer transaction message Transfer { + // destination address string recipient = 1; + + // amount uint64 value = 2; + + // optional memo + string memo = 3; + + // optional referenced public keys + repeated string references = 4; } // Create and initialize a stake account, and delegate amount to it. // Recommendation behavior is to not specify a stake account, and a new unique account will be created each time. // Optionally a stake account pubkey can be specified, but it should not exist on chain. message DelegateStake { + // Validator's public key string validator_pubkey = 1; + + // delegation amount uint64 value = 2; + + // staking account string stake_account = 3; } // Deactivate staking on stake account message DeactivateStake { + // staking account string stake_account = 1; } // Deactivate staking on multiple stake account message DeactivateAllStake { + // staking accounts repeated string stake_accounts = 1; } // Withdraw amount from stake account message WithdrawStake { + // staking account string stake_account = 1; + + // withdrawal amount uint64 value = 2; } -// Techinical structure to group a staking account and an amount +// Technical structure to group a staking account and an amount message StakeAccountValue { + // staking account string stake_account = 1; + + // amount uint64 value = 2; } @@ -48,35 +71,74 @@ message WithdrawAllStake { message CreateTokenAccount { // main account -- can be same as signer, or other main account (if done on some other account's behalf) string main_address = 1; + + // Token minting address string token_mint_address = 2; + + // Token address string token_address = 3; } // Transfer tokens message TokenTransfer { + // Mint address of the token string token_mint_address = 1; + + // Source address string sender_token_address = 2; + + // Destination address string recipient_token_address = 3; + + // Amount uint64 amount = 4; - uint32 decimals = 5; // Note: 8-bit value + + // Note: 8-bit value + uint32 decimals = 5; + + // optional memo§ + string memo = 6; + + // optional referenced public keys + repeated string references = 7; } // CreateTokenAccount and TokenTransfer combined message CreateAndTransferToken { // main account -- can be same as signer, or other main account (if done on some other account's behalf) string recipient_main_address = 1; + + // Mint address of the token string token_mint_address = 2; + // Token address for the recipient, will be created first string recipient_token_address = 3; + + // Sender's token address string sender_token_address = 4; + + // amount uint64 amount = 5; - uint32 decimals = 6; // Note: 8-bit value + + // Note: 8-bit value + uint32 decimals = 6; + + // optional + string memo = 7; + + // optional referenced public keys + repeated string references = 8; } // Input data necessary to create a signed transaction. message SigningInput { + // The secret private key used for signing (32 bytes). bytes private_key = 1; + + // Relatively recent block hash string recent_blockhash = 2; + + // Payload message oneof transaction_type { Transfer transfer_transaction = 3; DelegateStake delegate_stake_transaction = 4; @@ -90,7 +152,8 @@ message SigningInput { } } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { + // The encoded transaction string encoded = 1; } diff --git a/src/proto/Stellar.proto b/src/proto/Stellar.proto index 1698cf084bd..44a67e3672f 100644 --- a/src/proto/Stellar.proto +++ b/src/proto/Stellar.proto @@ -3,24 +3,28 @@ syntax = "proto3"; package TW.Stellar.Proto; option java_package = "wallet.core.jni.proto"; +// Represents an asset +// Note: alphanum12 currently not supported message Asset { // Optional in case of non-native asset; the asset issuer address string issuer = 1; // Optional in case of non-native asset; the asset alphanum4 code. string alphanum4 = 2; - - // Note: alphanum12 currently not supported } +// Create a new account message OperationCreateAccount { + // address string destination = 1; // Amount (*10^7) int64 amount = 2; } +// Perform payment message OperationPayment { + // Destination address string destination = 1; // Optional, can be left empty for native asset @@ -30,47 +34,95 @@ message OperationPayment { int64 amount = 3; } +// Change trust message OperationChangeTrust { + // The asset Asset asset = 1; // Validity (time bound to), unix time. Set to (now() + 2 * 365 * 86400) for 2 years; set to 0 for missing. int64 valid_before = 2; } +// A predicate (used in claim) +// Rest of predicates not currently supported +// See https://github.com/stellar/stellar-protocol/blob/master/core/cap-0023.md +enum ClaimPredicate { + Predicate_unconditional = 0; +} + +// Claimant: account & predicate +message Claimant { + // Claimant account + string account = 1; + + // predicate + ClaimPredicate predicate = 2; +} + +// Create a claimable balance (2-phase transfer) +message OperationCreateClaimableBalance { + // Optional, can be left empty for native asset + Asset asset = 1; + + // Amount (*10^7) + int64 amount = 2; + + // One or more claimants + repeated Claimant claimants = 3; +} + +// Claim a claimable balance +message OperationClaimClaimableBalance { + // 32-byte balance ID hash + bytes balance_id = 1; +} + +// Empty memo (placeholder) message MemoVoid { } +// Memo with text message MemoText { string text = 1; } +// Memo with an ID message MemoId { int64 id = 1; } +// Memo with a hash message MemoHash { bytes hash = 1; } // Input data necessary to create a signed transaction. message SigningInput { + // Transaction fee int32 fee = 1; + // Account sequence int64 sequence = 2; + // Source account string account = 3; - // Private key. + // The secret private key used for signing (32 bytes). bytes private_key = 4; + // Wellknown passphrase, specific to the chain string passphrase = 5; + // Payload message oneof operation_oneof { OperationCreateAccount op_create_account = 6; OperationPayment op_payment = 7; OperationChangeTrust op_change_trust = 8; + OperationCreateClaimableBalance op_create_claimable_balance = 14; + OperationClaimClaimableBalance op_claim_claimable_balance = 15; } + // Memo oneof memo_type_oneof { MemoVoid memo_void = 9; MemoText memo_text = 10; @@ -80,7 +132,7 @@ message SigningInput { } } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signature. string signature = 1; diff --git a/src/proto/THORChainSwap.proto b/src/proto/THORChainSwap.proto new file mode 100644 index 00000000000..7f3a20396c0 --- /dev/null +++ b/src/proto/THORChainSwap.proto @@ -0,0 +1,97 @@ +syntax = "proto3"; + +package TW.THORChainSwap.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Bitcoin.proto"; +import "Ethereum.proto"; +import "Binance.proto"; + +// Supported blockchains +enum Chain { + THOR = 0; + BTC = 1; + ETH = 2; + BNB = 3; +} + +// Predefined error codes +enum ErrorCode { + // OK + OK = 0; + Error_general = 1; + Error_Input_proto_deserialization = 2; + Error_Unsupported_from_chain = 13; + Error_Unsupported_to_chain = 14; + Error_Invalid_from_address = 15; + Error_Invalid_to_address = 16; + Error_Invalid_vault_address = 21; + Error_Invalid_router_address = 22; +} + +// An error code + a free text +message Error { + // code of the error + ErrorCode code = 1; + + // optional error message + string message = 2; +} + +// Represents an asset. Examples: BNB.BNB, RUNE.RUNE, BNB.RUNE-67C +message Asset { + // Chain ID + Chain chain = 1; + + // Symbol + string symbol = 2; + + // The ID of the token (blockchain-specific format) + string token_id = 3; +} + +// Input for a swap between source and destination chains; for creating a TX on the source chain. +message SwapInput { + // Source chain + Chain from_chain = 1; + + // Source address, on source chain + string from_address = 2; + + // Destination chain+asset, on destination chain + Asset to_asset = 3; + + // Destination address, on destination chain + string to_address = 4; + + // ThorChainSwap vault, on the source chain. Should be queried afresh, as it may change + string vault_address = 5; + + // ThorChain router, only in case of Ethereum source network + string router_address = 6; + + // The source amount, integer as string, in the smallest native unit of the chain + string from_amount = 7; + + // The minimum accepted destination amount. Actual destination amount will depend on current rates, limit amount can be used to prevent using very unfavorable rates. + string to_amount_limit = 8; +} + +// Result of the swap, a SigningInput struct for the specific chain +message SwapOutput { + // Source chain + Chain from_chain = 1; + + // Destination chain + Chain to_chain = 2; + + // Error code, filled in case of error, OK/empty on success + Error error = 3; + + // Prepared unsigned transaction input, on the source chain, to THOR. Some fields must be completed, and it has to be signed. + oneof signing_input_oneof { + Bitcoin.Proto.SigningInput bitcoin = 4; + Ethereum.Proto.SigningInput ethereum = 5; + Binance.Proto.SigningInput binance = 6; + } +} diff --git a/src/proto/Tezos.proto b/src/proto/Tezos.proto index c09f70bd674..7a56c4d2ab5 100644 --- a/src/proto/Tezos.proto +++ b/src/proto/Tezos.proto @@ -6,32 +6,49 @@ option java_package = "wallet.core.jni.proto"; // Input data necessary to create a signed Tezos transaction. // Next field: 3 message SigningInput { + // One or more operations OperationList operation_list = 1; + + // The secret private key used for signing (32 bytes). bytes private_key = 2; } -// Transaction signing output. +// Result containing the signed and encoded transaction. // Next field: 2 message SigningOutput { + // The encoded transaction bytes encoded = 1; } // A list of operations and a branch. // Next field: 3 message OperationList { + // branch string branch = 1; + + // list of operations repeated Operation operations = 2; } // An operation that can be applied to the Tezos blockchain. // Next field: 12 message Operation { + // counter int64 counter = 1; + + // source account string source = 2; + + // fee int64 fee = 3; + + // gas limit int64 gas_limit = 4; + + // storage limit int64 storage_limit = 5; + // Operation types enum OperationKind { // Note: Proto3 semantics require a zero value. ENDORSEMENT = 0; @@ -40,6 +57,7 @@ message Operation { TRANSACTION = 108; DELEGATION = 110; } + // Operation type OperationKind kind = 7; // Operation specific data depending on the type of the operation. @@ -50,11 +68,43 @@ message Operation { } } +message FA12Parameters { + string entrypoint = 1; + string from = 2; + string to = 3; + string value = 4; +} + +message Txs { + string to = 1; + string token_id = 2; + string amount = 3; +} + +message TxObject { + string from = 1; + repeated Txs txs = 2; +} + +message FA2Parameters { + string entrypoint = 1; + repeated TxObject txs_object = 2; +} + +// Generic operation parameters +message OperationParameters { + oneof parameters { + FA12Parameters fa12_parameters = 1; + FA2Parameters fa2_parameters = 2; + } +} + // Transaction operation specific data. // Next field: 3 message TransactionOperationData { string destination = 1; int64 amount = 2; + OperationParameters parameters = 3; } // Reveal operation specific data. @@ -67,4 +117,4 @@ message RevealOperationData { // Next field: 2 message DelegationOperationData { string delegate = 1; -} \ No newline at end of file +} diff --git a/src/proto/Theta.proto b/src/proto/Theta.proto index f779c0b5b0d..4647d89ba83 100644 --- a/src/proto/Theta.proto +++ b/src/proto/Theta.proto @@ -11,23 +11,23 @@ message SigningInput { /// Recipient address string to_address = 2; - /// Theta token amount to send in wei (256-bit number) + /// Theta token amount to send in wei (uint256, serialized little endian) bytes theta_amount = 3; - /// TFuel token amount to send in wei (256-bit number) + /// TFuel token amount to send in wei (uint256, serialized little endian) bytes tfuel_amount = 4; /// Sequence number of the transaction for the sender address uint64 sequence = 5; - /// Fee amount in TFuel wei for the transaction (256-bit number) + /// Fee amount in TFuel wei for the transaction (uint256, serialized little endian) bytes fee = 6; - /// Private key + /// The secret private key used for signing (32 bytes). bytes private_key = 7; } -/// Transaction signing output +// Result containing the signed and encoded transaction. message SigningOutput { /// Signed and encoded transaction bytes bytes encoded = 1; diff --git a/src/proto/TransactionCompiler.proto b/src/proto/TransactionCompiler.proto new file mode 100644 index 00000000000..5afcd7f51ba --- /dev/null +++ b/src/proto/TransactionCompiler.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +package TW.TxCompiler.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Common.proto"; + +/// Transaction pre-signing output +message PreSigningOutput { + /// Pre-image data hash that will be used for signing + bytes data_hash = 1; + + /// Pre-image data + bytes data = 2; + + /// error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 3; + + /// error code description + string error_message = 4; +} diff --git a/src/proto/Tron.proto b/src/proto/Tron.proto index 3a6b635b169..7ed7ce37723 100644 --- a/src/proto/Tron.proto +++ b/src/proto/Tron.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package TW.Tron.Proto; option java_package = "wallet.core.jni.proto"; +// A transfer transaction message TransferContract { // Sender address. string owner_address = 1; @@ -14,6 +15,7 @@ message TransferContract { int64 amount = 3; } +// Asset transfer message TransferAssetContract { // Asset name. string asset_name = 1; @@ -28,6 +30,7 @@ message TransferAssetContract { int64 amount = 4; } +// TRC20 token transfer message TransferTRC20Contract { // Contract name. string contract_address = 1; @@ -38,79 +41,125 @@ message TransferTRC20Contract { // Recipient address. string to_address = 3; - // Amount to send, uint256, big-endian. + // Amount to send, (uint256, serialized big endian) bytes amount = 4; } +// Freeze balance message FreezeBalanceContract { // Sender address. string owner_address = 1; + // Frozen balance. Minimum 1 int64 frozen_balance = 2; + // Frozen duration int64 frozen_duration = 3; + // Resource type: BANDWIDTH | ENERGY string resource = 10; + // Receiver address string receiver_address = 15; } +// Unfreeze balance message UnfreezeBalanceContract { // Sender address string owner_address = 1; + // Resource type: BANDWIDTH | ENERGY string resource = 10; + // Receiver address string receiver_address = 15; } +// Unfreeze asset message UnfreezeAssetContract { // Sender address string owner_address = 1; } +// Vote asset message VoteAssetContract { // Sender address string owner_address = 1; + // Vote addresses repeated string vote_address = 2; + bool support = 3; + int32 count = 5; } +// Vote witness message VoteWitnessContract { + // A vote message Vote { + // address string vote_address = 1; + + // vote count int64 vote_count = 2; } + + // Owner string owner_address = 1; + + // The votes repeated Vote votes = 2; + bool support = 3; } +// Withdraw balance message WithdrawBalanceContract { // Sender address string owner_address = 1; } +// Smart contract call message TriggerSmartContract { + // Owner string owner_address = 1; + + // Contract address string contract_address = 2; + + // amount int64 call_value = 3; + + // call data bytes data = 4; + + // token value int64 call_token_value = 5; + + // ID of the token int64 token_id = 6; } +// Info from block header message BlockHeader { + // creation timestamp int64 timestamp = 1; + + // root bytes tx_trie_root = 2; + + // hash of the parent bytes parent_hash = 3; + int64 number = 7; + bytes witness_address = 9; + int32 version = 10; } +// Transaction message Transaction { // Transaction timestamp in milliseconds. int64 timestamp = 1; @@ -139,15 +188,16 @@ message Transaction { } } +// Input data necessary to create a signed transaction. message SigningInput { // Transaction. Transaction transaction = 1; - // Private key. + // The secret private key used for signing (32 bytes). bytes private_key = 2; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Transaction identifier. bytes id = 1; @@ -158,5 +208,6 @@ message SigningOutput { bytes ref_block_bytes = 3; bytes ref_block_hash = 4; + // Result in JSON string json = 5; } diff --git a/src/proto/VeChain.proto b/src/proto/VeChain.proto index c89d9f0bfc5..2b2b7e151c2 100644 --- a/src/proto/VeChain.proto +++ b/src/proto/VeChain.proto @@ -3,11 +3,12 @@ syntax = "proto3"; package TW.VeChain.Proto; option java_package = "wallet.core.jni.proto"; +// A clause, between a sender and destination message Clause { /// Recipient address. string to = 1; - /// Transaction amount. + /// Transaction amount (uint256, serialized little endian) bytes value = 2; /// Payload data. @@ -43,11 +44,11 @@ message SigningInput { /// Number set by user. uint64 nonce = 8; - // Private key. + /// The secret private key used for signing (32 bytes). bytes private_key = 9; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signed and encoded transaction bytes. bytes encoded = 1; diff --git a/src/proto/Waves.proto b/src/proto/Waves.proto index 011fcf1138b..a2acc2ef599 100644 --- a/src/proto/Waves.proto +++ b/src/proto/Waves.proto @@ -3,29 +3,45 @@ syntax = "proto3"; package TW.Waves.Proto; option java_package = "wallet.core.jni.proto"; -//Transfer transaction +// Transfer transaction message TransferMessage { + // amount int64 amount = 1; + + // asset ID string asset = 2; + // minimum 0.001 Waves (100000 Wavelets) for now int64 fee = 3; + + // asset of the fee string fee_asset = 4; + + // destination address string to = 5; + // any 140 bytes payload, will be displayed to the client as utf-8 string bytes attachment = 6; } -//Lease transaction +// Lease transaction message LeaseMessage { + // amount int64 amount = 1; + + // destination string to = 2; + // minimum 0.001 Waves (100000 Wavelets) for now int64 fee = 3; } -//Lease transaction +// Cancel Lease transaction message CancelLeaseMessage { + // Lease ID to cancel string lease_id = 1; + + // Fee used int64 fee = 2; } @@ -34,7 +50,11 @@ message CancelLeaseMessage { message SigningInput { // in millis int64 timestamp = 1; + + // The secret private key used for signing (32 bytes). bytes private_key = 2; + + // Payload message oneof message_oneof { TransferMessage transfer_message = 3; LeaseMessage lease_message = 4; @@ -42,9 +62,12 @@ message SigningInput { } } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { + // signature data bytes signature = 1; + + // transaction in JSON format string json = 2; } diff --git a/src/proto/Zilliqa.proto b/src/proto/Zilliqa.proto index ea5d3eff4c9..03e5e2167fa 100644 --- a/src/proto/Zilliqa.proto +++ b/src/proto/Zilliqa.proto @@ -3,14 +3,17 @@ syntax = "proto3"; package TW.Zilliqa.Proto; option java_package = "wallet.core.jni.proto"; +// Generic transaction message Transaction { + // Transfer transaction message Transfer { - // Amount to send (256-bit number) + // Amount to send (uint256, serialized little endian) bytes amount = 1; } + // Generic contract call message Raw { - // Amount to send (256-bit number) + // Amount to send (uint256, serialized little endian) bytes amount = 1; // Smart contract code @@ -43,13 +46,14 @@ message SigningInput { // GasLimit uint64 gas_limit = 5; - // Private Key + // The secret private key used for signing (32 bytes). bytes private_key = 6; + // The payload transaction Transaction transaction = 7; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signed signature bytes. bytes signature = 1; diff --git a/src/uint256.h b/src/uint256.h index 2c61d2edbcc..46159abb2cd 100644 --- a/src/uint256.h +++ b/src/uint256.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -55,12 +55,18 @@ inline uint256_t load(const std::string& data) { return result; } -/// Stores a `uint256_t` as a collection of bytes. -inline Data store(const uint256_t& v) { +/// Stores a `uint256_t` as a collection of bytes, with optional padding (typically to 32 bytes). +/// If minLen is given (non-zero), and result is shorter, it is padded (with zeroes, on the left, big endian) +inline Data store(const uint256_t& v, byte minLen = 0) { using boost::multiprecision::cpp_int; Data bytes; bytes.reserve(32); export_bits(v, std::back_inserter(bytes), 8); + if (minLen && bytes.size() < minLen) { + Data padded(minLen - bytes.size()); + append(padded, bytes); + return padded; + } return bytes; } diff --git a/.swiftlint.yml b/swift/.swiftlint.yml similarity index 100% rename from .swiftlint.yml rename to swift/.swiftlint.yml diff --git a/swift/Gemfile b/swift/Gemfile index cdd3a6b3491..b734015f820 100644 --- a/swift/Gemfile +++ b/swift/Gemfile @@ -1,6 +1,10 @@ +# Autogenerated by fastlane +# +# Ensure this file is checked in to source control! + source "https://rubygems.org" -gem "fastlane" +gem 'fastlane' plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/swift/Gemfile.lock b/swift/Gemfile.lock index f4f39eb55c2..6005b99e515 100644 --- a/swift/Gemfile.lock +++ b/swift/Gemfile.lock @@ -1,65 +1,80 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.3) - addressable (2.7.0) + CFPropertyList (3.0.5) + rexml + addressable (2.8.0) public_suffix (>= 2.0.2, < 5.0) artifactory (3.0.15) atomos (0.1.3) - aws-eventstream (1.1.1) - aws-partitions (1.446.0) - aws-sdk-core (3.114.0) + aws-eventstream (1.2.0) + aws-partitions (1.600.0) + aws-sdk-core (3.131.1) aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.239.0) + aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) - jmespath (~> 1.0) - aws-sdk-kms (1.43.0) - aws-sdk-core (~> 3, >= 3.112.0) + jmespath (~> 1, >= 1.6.1) + aws-sdk-kms (1.57.0) + aws-sdk-core (~> 3, >= 3.127.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.93.1) - aws-sdk-core (~> 3, >= 3.112.0) + aws-sdk-s3 (1.114.0) + aws-sdk-core (~> 3, >= 3.127.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.1) - aws-sigv4 (1.2.3) + aws-sigv4 (~> 1.4) + aws-sigv4 (1.5.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) - claide (1.0.3) + claide (1.1.0) colored (1.2) colored2 (3.1.2) - commander-fastlane (4.4.6) - highline (~> 1.7.2) + commander (4.6.0) + highline (~> 2.0.0) declarative (0.0.20) - digest-crc (0.6.3) + digest-crc (0.6.4) rake (>= 12.0.0, < 14.0.0) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) dotenv (2.7.6) - emoji_regex (3.2.2) - excon (0.80.1) - faraday (1.4.1) + emoji_regex (3.2.3) + excon (0.92.3) + faraday (1.10.0) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) faraday-net_http (~> 1.0) - faraday-net_http_persistent (~> 1.1) - multipart-post (>= 1.2, < 3) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) ruby2_keywords (>= 0.0.4) faraday-cookie_jar (0.0.7) faraday (>= 0.8.0) http-cookie (~> 1.0.0) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.0) faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.0.4) + multipart-post (~> 2) faraday-net_http (1.0.1) - faraday-net_http_persistent (1.1.0) - faraday_middleware (1.0.0) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.3) + faraday_middleware (1.2.0) faraday (~> 1.0) - fastimage (2.2.3) - fastlane (2.181.0) + fastimage (2.2.6) + fastlane (2.206.2) CFPropertyList (>= 2.3, < 4.0.0) - addressable (>= 2.3, < 3.0.0) + addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) aws-sdk-s3 (~> 1.0) babosa (>= 1.0.3, < 2.0.0) bundler (>= 1.12.0, < 3.0.0) colored - commander-fastlane (>= 4.4.6, < 5.0.0) + commander (~> 4.6) dotenv (>= 2.1.1, < 3.0.0) emoji_regex (>= 0.1, < 4.0) excon (>= 0.71.0, < 1.0.0) @@ -68,19 +83,20 @@ GEM faraday_middleware (~> 1.0) fastimage (>= 2.1.0, < 3.0.0) gh_inspector (>= 1.1.2, < 2.0.0) - google-api-client (>= 0.37.0, < 0.39.0) - google-cloud-storage (>= 1.15.0, < 2.0.0) - highline (>= 1.7.2, < 2.0.0) + google-apis-androidpublisher_v3 (~> 0.3) + google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-storage (~> 1.31) + highline (~> 2.0) json (< 3.0.0) jwt (>= 2.1.0, < 3) mini_magick (>= 4.9.4, < 5.0.0) multipart-post (~> 2.0.0) naturally (~> 2.2) + optparse (~> 0.1.1) plist (>= 3.1.0, < 4.0.0) rubyzip (>= 2.0.0, < 3.0.0) security (= 0.1.3) simctl (~> 1.6.3) - slack-notifier (>= 2.0.0, < 3.0.0) terminal-notifier (>= 2.0.0, < 3.0.0) terminal-table (>= 1.4.5, < 2.0.0) tty-screen (>= 0.6.3, < 1.0.0) @@ -91,90 +107,85 @@ GEM xcpretty-travis-formatter (>= 0.0.3) fastlane-plugin-create_xcframework (1.1.2) gh_inspector (1.1.3) - google-api-client (0.38.0) - addressable (~> 2.5, >= 2.5.1) - googleauth (~> 0.9) - httpclient (>= 2.8.1, < 3.0) - mini_mime (~> 1.0) - representable (~> 3.0) - retriable (>= 2.0, < 4.0) - signet (~> 0.12) - google-apis-core (0.3.0) + google-apis-androidpublisher_v3 (0.22.0) + google-apis-core (>= 0.5, < 2.a) + google-apis-core (0.6.0) addressable (~> 2.5, >= 2.5.1) - googleauth (~> 0.14) - httpclient (>= 2.8.1, < 3.0) + googleauth (>= 0.16.2, < 2.a) + httpclient (>= 2.8.1, < 3.a) mini_mime (~> 1.0) representable (~> 3.0) - retriable (>= 2.0, < 4.0) + retriable (>= 2.0, < 4.a) rexml - signet (~> 0.14) webrick - google-apis-iamcredentials_v1 (0.3.0) - google-apis-core (~> 0.1) - google-apis-storage_v1 (0.3.0) - google-apis-core (~> 0.1) + google-apis-iamcredentials_v1 (0.11.0) + google-apis-core (>= 0.5, < 2.a) + google-apis-playcustomapp_v1 (0.8.0) + google-apis-core (>= 0.5, < 2.a) + google-apis-storage_v1 (0.15.0) + google-apis-core (>= 0.5, < 2.a) google-cloud-core (1.6.0) google-cloud-env (~> 1.0) google-cloud-errors (~> 1.0) - google-cloud-env (1.5.0) - faraday (>= 0.17.3, < 2.0) - google-cloud-errors (1.1.0) - google-cloud-storage (1.31.0) - addressable (~> 2.5) + google-cloud-env (1.6.0) + faraday (>= 0.17.3, < 3.0) + google-cloud-errors (1.2.0) + google-cloud-storage (1.36.2) + addressable (~> 2.8) digest-crc (~> 0.4) google-apis-iamcredentials_v1 (~> 0.1) google-apis-storage_v1 (~> 0.1) - google-cloud-core (~> 1.2) - googleauth (~> 0.9) + google-cloud-core (~> 1.6) + googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (0.16.1) - faraday (>= 0.17.3, < 2.0) + googleauth (1.1.3) + faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) - signet (~> 0.14) - highline (1.7.10) - http-cookie (1.0.3) + signet (>= 0.16, < 2.a) + highline (2.0.3) + http-cookie (1.0.5) domain_name (~> 0.5) httpclient (2.8.3) - jmespath (1.4.0) - json (2.5.1) - jwt (2.2.3) + jmespath (1.6.1) + json (2.6.2) + jwt (2.4.1) memoist (0.16.2) mini_magick (4.11.0) - mini_mime (1.1.0) + mini_mime (1.1.2) multi_json (1.15.0) multipart-post (2.0.0) nanaimo (0.3.0) naturally (2.2.1) - os (1.1.1) + optparse (0.1.1) + os (1.1.4) plist (3.6.0) - public_suffix (4.0.6) - rake (13.0.3) - representable (3.1.1) + public_suffix (4.0.7) + rake (13.0.6) + representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) rexml (3.2.5) rouge (2.0.7) - ruby2_keywords (0.0.4) - rubyzip (2.3.0) + ruby2_keywords (0.0.5) + rubyzip (2.3.2) security (0.1.3) - signet (0.15.0) - addressable (~> 2.3) - faraday (>= 0.17.3, < 2.0) + signet (0.16.1) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.0) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) simctl (1.6.8) CFPropertyList naturally - slack-notifier (2.3.2) terminal-notifier (2.0.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) - trailblazer-option (0.1.1) + trailblazer-option (0.1.2) tty-cursor (0.7.1) tty-screen (0.8.1) tty-spinner (0.9.3) @@ -182,16 +193,17 @@ GEM uber (0.1.0) unf (0.1.4) unf_ext - unf_ext (0.0.7.7) - unicode-display_width (1.7.0) + unf_ext (0.0.8.2) + unicode-display_width (1.8.0) webrick (1.7.0) word_wrap (1.0.0) - xcodeproj (1.19.0) + xcodeproj (1.21.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) nanaimo (~> 0.3.0) + rexml (~> 3.2.4) xcpretty (0.3.0) rouge (~> 2.0.7) xcpretty-travis-formatter (1.0.1) diff --git a/swift/Podfile b/swift/Podfile index 028e65b3612..ab704127b08 100644 --- a/swift/Podfile +++ b/swift/Podfile @@ -10,7 +10,6 @@ target 'WalletCore' do use_frameworks! pod 'SwiftProtobuf' - pod 'SwiftLint' target 'WalletCoreTests' end diff --git a/swift/Podfile.lock b/swift/Podfile.lock index 3448645476f..a8504fac8bd 100644 --- a/swift/Podfile.lock +++ b/swift/Podfile.lock @@ -1,20 +1,16 @@ PODS: - - SwiftLint (0.41.0) - SwiftProtobuf (1.13.0) DEPENDENCIES: - - SwiftLint - SwiftProtobuf SPEC REPOS: trunk: - - SwiftLint - SwiftProtobuf SPEC CHECKSUMS: - SwiftLint: c585ebd615d9520d7fbdbe151f527977b0534f1e SwiftProtobuf: fd4693388a96c8c2df35d3b063272b0e7c499d00 -PODFILE CHECKSUM: 79a99a0b6ec3019db772eb07bcd6c08f70cd2fa3 +PODFILE CHECKSUM: aac2324ba35cdd5631cb37618cd483887bab9cfd -COCOAPODS: 1.11.2 +COCOAPODS: 1.11.3 diff --git a/swift/Sources/AnySigner.swift b/swift/Sources/AnySigner.swift index 9a6f5862cd6..5abf730b95d 100644 --- a/swift/Sources/AnySigner.swift +++ b/swift/Sources/AnySigner.swift @@ -10,7 +10,15 @@ import SwiftProtobuf public typealias SigningInput = Message public typealias SigningOutput = Message +/// Represents a signer to sign transactions for any blockchain. public final class AnySigner { + + /// Signs a transaction by SigningInput message and coin type + /// + /// - Parameters: + /// - input: The generic SigningInput SwiftProtobuf message + /// - coin: CoinType + /// - Returns: The generic SigningOutput SwiftProtobuf message public static func sign(input: SigningInput, coin: CoinType) -> SigningOutput { do { let outputData = nativeSign(data: try input.serializedData(), coin: coin) @@ -20,6 +28,12 @@ public final class AnySigner { } } + /// Signs a transaction by serialized data of a SigningInput and coin type + /// + /// - Parameters: + /// - data: The serialized data of a SigningInput + /// - coin: CoinType + /// - Returns: The serialized data of a SigningOutput public static func nativeSign(data: Data, coin: CoinType) -> Data { let inputData = TWDataCreateWithNSData(data) defer { @@ -28,10 +42,18 @@ public final class AnySigner { return TWDataNSData(TWAnySignerSign(inputData, TWCoinType(rawValue: coin.rawValue))) } + /// Check if AnySigner supports signing JSON representation of SigningInput for a given coin. public static func supportsJSON(coin: CoinType) -> Bool { return TWAnySignerSupportsJSON(TWCoinType(rawValue: coin.rawValue)) } + /// Signs a transaction specified by the JSON representation of a SigningInput, coin type and a private key + /// + /// - Parameters: + /// - json: JSON representation of a SigningInput + /// - key: The private key data + /// - coin: CoinType + /// - Returns: The JSON representation of a SigningOutput. public static func signJSON(_ json: String, key: Data, coin: CoinType) -> String { let jsonString = TWStringCreateWithNSString(json) let keyData = TWDataCreateWithNSData(key) @@ -41,6 +63,12 @@ public final class AnySigner { return TWStringNSString(TWAnySignerSignJSON(jsonString, keyData, TWCoinType(rawValue: coin.rawValue))) } + /// Plans a transaction (for UTXO chains only). + /// + /// - Parameters: + /// - input: The generic SigningInput SwiftProtobuf message + /// - coin: CoinType + /// - Returns: TransactionPlan SwiftProtobuf message public static func plan(input: SigningInput, coin: CoinType) -> TransactionPlan { do { let outputData = nativePlan(data: try input.serializedData(), coin: coin) @@ -50,6 +78,12 @@ public final class AnySigner { } } + /// Plans a transaction (for UTXO chains only). + /// + /// - Parameters: + /// - input: The serialized data of a SigningInput + /// - coin: CoinType + /// - Returns: The serialized data of a TransactionPlan public static func nativePlan(data: Data, coin: CoinType) -> Data { let inputData = TWDataCreateWithNSData(data) defer { diff --git a/swift/Sources/DerivationPath.Index.swift b/swift/Sources/DerivationPath.Index.swift deleted file mode 100644 index 09a43cc45c3..00000000000 --- a/swift/Sources/DerivationPath.Index.swift +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -import Foundation - -extension DerivationPath { - /// Derivation path index. - public struct Index: Codable, Hashable, CustomStringConvertible { - /// Index value. - public var value: UInt32 - - /// Whether the index is hardened. - public var hardened: Bool - - /// The derivation index. - public var derivationIndex: UInt32 { - if hardened { - return UInt32(value) | 0x80000000 - } else { - return UInt32(value) - } - } - - public init(_ value: UInt32, hardened: Bool = true) { - self.value = value - self.hardened = hardened - } - - public func hash(into hasher: inout Hasher) { - hasher.combine(value) - hasher.combine(hardened) - } - - public static func == (lhs: Index, rhs: Index) -> Bool { - return lhs.value == rhs.value && lhs.hardened == rhs.hardened - } - - public var description: String { - if hardened { - return "\(value)'" - } else { - return value.description - } - } - } -} diff --git a/swift/Sources/DerivationPath.swift b/swift/Sources/DerivationPath.swift deleted file mode 100644 index c9f694bb6b4..00000000000 --- a/swift/Sources/DerivationPath.swift +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -import Foundation - -/// Represents a hierarchical determinisic derivation path. -public struct DerivationPath: Codable, Hashable, CustomStringConvertible { - var indexCount = 5 - - /// List of indices in the derivation path. - public private(set) var indices = [Index]() - - /// Address purpose, each coin will have a different value. - public var purpose: Purpose { - get { - return Purpose(rawValue: indices[0].value)! - } - set { - indices[0] = Index(newValue.rawValue, hardened: true) - } - } - - /// Coin type distinguishes between main net, test net, and forks. - public var coinType: UInt32 { - get { - return indices[1].value - } - set { - indices[1] = Index(newValue, hardened: true) - } - } - - /// Account number. - public var account: UInt32 { - get { - return indices[2].value - } - set { - indices[2] = Index(newValue, hardened: true) - } - } - - /// Change or private addresses will set this to 1. - public var change: UInt32 { - get { - return indices[3].value - } - set { - indices[3] = Index(newValue, hardened: false) - } - } - - /// Address number - public var address: UInt32 { - get { - return indices[4].value - } - set { - indices[4] = Index(newValue, hardened: false) - } - } - - init(indices: [Index]) { - precondition(indices.count == indexCount, "Not enough indices") - self.indices = indices - } - - /// Creates a `DerivationPath` by components. - public init(purpose: Purpose, coin: UInt32, account: UInt32 = 0, change: UInt32 = 0, address: UInt32 = 0) { - self.indices = [Index](repeating: Index(0), count: indexCount) - self.purpose = purpose - self.coinType = coin - self.account = account - self.change = change - self.address = address - } - - /// Creates a derivation path with a string description like `m/10/0/2'/3` - public init?(_ string: String) { - let components = string.split(separator: "/") - for component in components { - if component == "m" { - continue - } - if component.hasSuffix("'") { - guard let index = UInt32(component.dropLast()) else { - return nil - } - indices.append(Index(index, hardened: true)) - } else { - guard let index = UInt32(component) else { - return nil - } - indices.append(Index(index, hardened: false)) - } - } - guard indices.count == indexCount else { - return nil - } - } - - /// String representation. - public var description: String { - return "m/" + indices.map({ $0.description }).joined(separator: "/") - } - - public func hash(into hasher: inout Hasher) { - indices.forEach { hasher.combine($0) } - } - - public static func == (lhs: DerivationPath, rhs: DerivationPath) -> Bool { - return lhs.indices == rhs.indices - } -} diff --git a/swift/Sources/Dummy.cpp b/swift/Sources/Dummy.cpp index 3cb855c4329..e433c2b6009 100644 --- a/swift/Sources/Dummy.cpp +++ b/swift/Sources/Dummy.cpp @@ -4,4 +4,4 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -// Dummy C++ file to foce inclusion of the C++ STL when compiling. +// Dummy C++ file to force inclusion of the C++ STL when compiling. diff --git a/swift/Sources/Extensions/Account+Codable.swift b/swift/Sources/Extensions/Account+Codable.swift new file mode 100644 index 00000000000..39b48c3d0c5 --- /dev/null +++ b/swift/Sources/Extensions/Account+Codable.swift @@ -0,0 +1,69 @@ +// 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 Foundation + +extension Account: Equatable { + public static func == (lhs: Account, rhs: Account) -> Bool { + return lhs.coin == rhs.coin && + lhs.address == rhs.address && + lhs.derivation == rhs.derivation && + lhs.derivationPath == rhs.derivationPath && + lhs.publicKey == rhs.publicKey && + lhs.extendedPublicKey == rhs.extendedPublicKey + } +} + +extension Account: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(coin) + hasher.combine(address) + hasher.combine(derivation) + hasher.combine(derivationPath) + hasher.combine(publicKey) + hasher.combine(extendedPublicKey) + } +} + +extension Account: Codable { + private enum CodingKeys: String, CodingKey { + case coin + case address + case derivation + case derivationPath + case publicKey + case extendedPublicKey + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(coin.rawValue, forKey: .coin) + try container.encode(address, forKey: .address) + try container.encode(derivation.rawValue, forKey: .derivation) + try container.encode(derivationPath, forKey: .derivationPath) + try container.encode(publicKey, forKey: .publicKey) + try container.encode(extendedPublicKey, forKey: .extendedPublicKey) + } + + public convenience init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let rawCoin = try container.decode(UInt32.self, forKey: .coin) + let address = try container.decode(String.self, forKey: .address) + let rawDerivation = try container.decode(UInt32.self, forKey: .derivation) + let derivationPath = try container.decode(String.self, forKey: .derivationPath) + let publicKey = try container.decode(String.self, forKey: .publicKey) + let extendedPublicKey = try container.decode(String.self, forKey: .extendedPublicKey) + + self.init( + address: address, + coin: CoinType(rawValue: rawCoin)!, + derivation: Derivation(rawValue: rawDerivation)!, + derivationPath: derivationPath, + publicKey: publicKey, + extendedPublicKey: extendedPublicKey + ) + } +} diff --git a/swift/Sources/Extensions/AddressProtocol.swift b/swift/Sources/Extensions/AddressProtocol.swift index 273e71cea0f..1660d724252 100644 --- a/swift/Sources/Extensions/AddressProtocol.swift +++ b/swift/Sources/Extensions/AddressProtocol.swift @@ -6,6 +6,7 @@ import Foundation +/// Generic Address protocol for AnyAddress / SegwitAddress / SolanaAddress public protocol Address: CustomStringConvertible {} extension AnyAddress: Equatable {} diff --git a/swift/Sources/Extensions/DerivationPath+Extension.swift b/swift/Sources/Extensions/DerivationPath+Extension.swift new file mode 100644 index 00000000000..09678360e85 --- /dev/null +++ b/swift/Sources/Extensions/DerivationPath+Extension.swift @@ -0,0 +1,119 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +import Foundation + +extension DerivationPath: Equatable, Hashable, CustomStringConvertible { + + public typealias Index = DerivationPathIndex + + public static func == (lhs: DerivationPath, rhs: DerivationPath) -> Bool { + return lhs.description == rhs.description + } + + public var coinType: UInt32 { + coin + } + + public var indices: [Index] { + var result = [Index]() + for i in 0.. DerivationPathIndex? { + return self.indexAt(index: UInt32(index)) + } + + public func hash(into hasher: inout Hasher) { + let count = indicesCount() + for i in 0.. Bool { + return lhs.value == rhs.value && lhs.hardened == rhs.hardened + } + + public convenience init(_ value: UInt32, hardened: Bool) { + self.init(value: value, hardened: hardened) + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(value) + hasher.combine(hardened) + } +} + +extension DerivationPathIndex: Codable { + private enum CodingKeys: String, CodingKey { + case value + case hardened + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(value, forKey: .value) + try container.encode(hardened, forKey: .hardened) + } + + public convenience init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let value = try container.decode(UInt32.self, forKey: .value) + let hardened = try container.decode(Bool.self, forKey: .hardened) + self.init(value: value, hardened: hardened) + } +} diff --git a/swift/Sources/TWCardano.swift b/swift/Sources/TWCardano.swift new file mode 100644 index 00000000000..1eb456be7ce --- /dev/null +++ b/swift/Sources/TWCardano.swift @@ -0,0 +1,15 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +import Foundation + +public func CardanoMinAdaAmount(tokenBundle: Data) -> UInt64 { + let tokenBundleData = TWDataCreateWithNSData(tokenBundle) + defer { + TWDataDelete(tokenBundleData) + } + return TWCardanoMinAdaAmount(tokenBundleData) +} diff --git a/swift/Sources/Types/UniversalAssetID.swift b/swift/Sources/Types/UniversalAssetID.swift index acd4e1e0082..9cd4f59945d 100644 --- a/swift/Sources/Types/UniversalAssetID.swift +++ b/swift/Sources/Types/UniversalAssetID.swift @@ -23,7 +23,7 @@ public struct UniversalAssetID: CustomStringConvertible, Equatable, Hashable { return [prefix, suffix].joined(separator: "_") } - public init(coin: CoinType, token: String) { + public init(coin: CoinType, token: String = "") { self.coin = coin self.token = token } diff --git a/swift/Sources/Wallet.swift b/swift/Sources/Wallet.swift index 036d2a22d7e..04749b5d564 100644 --- a/swift/Sources/Wallet.swift +++ b/swift/Sources/Wallet.swift @@ -43,6 +43,22 @@ public final class Wallet: Hashable, Equatable { return account } + /// Returns the account for a specific coin and derivation. + /// + /// - Parameters: + /// - password: wallet encryption password + /// - coin: coin type + /// - derivation: derivation, a specific or default + /// - Returns: the account + /// - Throws: `KeyStore.Error.invalidPassword` if the password is incorrect. + public func getAccount(password: String, coin: CoinType, derivation: Derivation) throws -> Account { + let wallet = key.wallet(password: Data(password.utf8)) + guard let account = key.accountForCoinDerivation(coin: coin, derivation: derivation, wallet: wallet) else { + throw KeyStore.Error.invalidPassword + } + return account + } + /// Returns the accounts for a specific coins. /// /// - Parameters: diff --git a/swift/Tests/Addresses/JunoAddressTests.swift b/swift/Tests/Addresses/JunoAddressTests.swift new file mode 100644 index 00000000000..02935c98c3a --- /dev/null +++ b/swift/Tests/Addresses/JunoAddressTests.swift @@ -0,0 +1,25 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +import WalletCore +import XCTest + +class JunoAddressTests: XCTestCase { + func testAnyAddressValidation() { + let addr = AnyAddress(string: "juno1gckvjxau7k56f8wg8c8xj80khyp83y8x8eqc94", coin: .cosmos, hrp: "juno")!; + XCTAssertTrue(AnyAddress.isValidBech32(string: addr.description, coin: .cosmos, hrp: "juno")); + XCTAssertFalse(AnyAddress.isValidBech32(string: addr.description, coin: .bitcoin, hrp: "juno")); + XCTAssertFalse(AnyAddress.isValid(string: addr.description, coin: .bitcoin)); + XCTAssertFalse(AnyAddress.isValid(string: addr.description, coin: .cosmos)); + } + + func testAnyAddressFromPubkey() { + let data = Data(hexString: "02753f5c275e1847ba4d2fd3df36ad00af2e165650b35fe3991e9c9c46f68b12bc")!; + let pubkey = PublicKey(data: data, type: .secp256k1)!; + let anyAddr = AnyAddress(publicKey: pubkey, coin: .cosmos, hrp: "juno"); + XCTAssertEqual(anyAddr.description, "juno1cj2vfjec3c3luf9fx9vddnglhh9gawmncn4k5n"); + } +} diff --git a/swift/Tests/Base32Tests.swift b/swift/Tests/Base32Tests.swift new file mode 100644 index 00000000000..41be00de149 --- /dev/null +++ b/swift/Tests/Base32Tests.swift @@ -0,0 +1,38 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +import XCTest +import WalletCore + +class Base32Tests: XCTestCase { + func testEncode() { + let encoded = Base32.encode(data: Data.init(bytes: "HelloWorld", count: 10)); + XCTAssertEqual(encoded, "JBSWY3DPK5XXE3DE"); + } + + func testEncodeWithAlphabet() { + let encoded = Base32.encodeWithAlphabet(data: Data.init(bytes: "7uoq6tp427uzv7fztkbsnn64iwotfrristwpryy", count: 39), alphabet: "abcdefghijklmnopqrstuvwxyz234567"); + XCTAssertEqual(encoded, "g52w64jworydimrxov5hmn3gpj2gwyttnzxdmndjo5xxiztsojuxg5dxobzhs6i"); + } + + func testDecode() { + guard let decoded = Base32.decode(string: "JBSWY3DPK5XXE3DE") else { + return XCTFail(); + } + let toCompare = String(data: decoded, encoding:.utf8); + + XCTAssertEqual(toCompare, "HelloWorld"); + } + + func testDecodeWithAlphabet() { + guard let decoded = Base32.decodeWithAlphabet(string: "g52w64jworydimrxov5hmn3gpj2gwyttnzxdmndjo5xxiztsojuxg5dxobzhs6i", alphabet:"abcdefghijklmnopqrstuvwxyz234567") else { + return XCTFail(); + } + let toCompare = String(data: decoded, encoding:.utf8); + + XCTAssertEqual(toCompare, "7uoq6tp427uzv7fztkbsnn64iwotfrristwpryy"); + } +} diff --git a/swift/Tests/Base64Tests.swift b/swift/Tests/Base64Tests.swift new file mode 100644 index 00000000000..b4fdc17c1cf --- /dev/null +++ b/swift/Tests/Base64Tests.swift @@ -0,0 +1,38 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +import XCTest +import WalletCore + +class Base64Tests: XCTestCase { + func testEncode() { + let encoded = Base64.encode(data: Data.init(bytes: "HelloWorld", count: 10)); + XCTAssertEqual(encoded, "SGVsbG9Xb3JsZA=="); + } + + func testDecode() { + guard let decoded = Base64.decode(string: "SGVsbG9Xb3JsZA==") else { + return XCTFail(); + } + let toCompare = String(data: decoded, encoding:.utf8); + + XCTAssertEqual(toCompare, "HelloWorld"); + } + + func testUrlEncode() { + let encoded = Base64.encodeUrl(data: Data.init(bytes: "+\\?ab", count: 5)); + XCTAssertEqual(encoded, "K1w_YWI="); + } + + func testUrlDecode() { + guard let decoded = Base64.decodeUrl(string: "K1w_YWI=") else { + return XCTFail(); + } + let toCompare = String(data: decoded, encoding:.utf8); + + XCTAssertEqual(toCompare, "+\\?ab"); + } +} diff --git a/swift/Tests/Blockchains/AlgorandTests.swift b/swift/Tests/Blockchains/AlgorandTests.swift index e20fa3441ea..0df5b148700 100644 --- a/swift/Tests/Blockchains/AlgorandTests.swift +++ b/swift/Tests/Blockchains/AlgorandTests.swift @@ -20,6 +20,7 @@ class AlgorandTests: XCTestCase { } func testSign() { + let round: UInt64 = 1937767 let transaction = AlgorandTransfer.with { $0.toAddress = "CRLADAHJZEW2GFY2UPEHENLOGCUOU74WYSTUXQLVLJUJFHEUZOHYZNWYR4" $0.amount = 1000000000000 @@ -29,8 +30,8 @@ class AlgorandTests: XCTestCase { $0.genesisHash = Data(base64Encoded: "wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=")! $0.note = "hello".data(using: .utf8)! $0.privateKey = Data(hexString: "d5b43d706ef0cb641081d45a2ec213b5d8281f439f2425d1af54e2afdaabf55b")! - $0.firstRound = 1937767 - $0.lastRound = 1938767 + $0.firstRound = round + $0.lastRound = round + 1000 $0.fee = 263000 $0.transfer = transaction } @@ -39,6 +40,34 @@ class AlgorandTests: XCTestCase { XCTAssertEqual(output.encoded.hexString, "82a3736967c440baa00062adcdcb5875e4435cdc6885d26bfe5308ab17983c0fda790b7103051fcb111554e5badfc0ac7edf7e1223a434342a9eeed5cdb047690827325051560ba374786e8aa3616d74cf000000e8d4a51000a3666565ce00040358a26676ce001d9167a367656eac6d61696e6e65742d76312e30a26768c420c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adfa26c76ce001d954fa46e6f7465c40568656c6c6fa3726376c42014560180e9c92da3171aa3c872356e30a8ea7f96c4a74bc1755a68929c94cb8fa3736e64c42061bf060efc02e2887dfffc8ed85268c8c091c013eedf315bc50794d02a8791ada474797065a3706179") } + func testSignVoteTx() { + // manual vote tx is 0 amount + note + + let round: UInt64 = 18426344 + let transaction = AlgorandTransfer.with { + $0.toAddress = "57QZ4S7YHTWPRAM3DQ2MLNSVLAQB7DTK4D7SUNRIEFMRGOU7DMYFGF55BY" + $0.amount = 0 + } + let note = """ + af/gov1:j{"com":1000000} + """ + let input = AlgorandSigningInput.with { + $0.genesisID = "mainnet-v1.0" + $0.genesisHash = Data(base64Encoded: "wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=")! + $0.note = note.data(using: .utf8)! + $0.privateKey = Data(hexString: "d5b43d706ef0cb641081d45a2ec213b5d8281f439f2425d1af54e2afdaabf55b")! + $0.firstRound = round + $0.lastRound = round + 1000 + $0.fee = 1000 + $0.transfer = transaction + } + let output: AlgorandSigningOutput = AnySigner.sign(input: input, coin: .algorand) + + // real key is 1p, posted by: echo '' | xxd -r -p | curl -X POST --data-binary @- + // https://algoexplorer.io/tx/OHYNQA7X5LHUKWEM6ZMUT6RCVOZUELXSYELV7CHQFQBDI3XEM4NQ + XCTAssertEqual(output.encoded.hexString, "82a3736967c440aad1e2d80fbdfc4dc5def13e1dc9f39c9261df9d5c6664478b951d28c3a688c4a261894d8c9bd686f5b2355f2edd54fd611eeaba8a871cc05af728a18598ed04a374786e89a3666565cd03e8a26676ce011929e8a367656eac6d61696e6e65742d76312e30a26768c420c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adfa26c76ce01192dd0a46e6f7465c41861662f676f76313a6a7b22636f6d223a313030303030307da3726376c420efe19e4bf83cecf8819b1c34c5b65558201f8e6ae0ff2a36282159133a9f1b30a3736e64c42061bf060efc02e2887dfffc8ed85268c8c091c013eedf315bc50794d02a8791ada474797065a3706179") + } + func testSignJSON() { let json = """ {"genesisId":"mainnet-v1.0","genesisHash":"wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=","note":"aGVsbG8=","firstRound":"1937767","lastRound":"1938767","fee":"263000","transfer":{"toAddress":"CRLADAHJZEW2GFY2UPEHENLOGCUOU74WYSTUXQLVLJUJFHEUZOHYZNWYR4","amount":"1000000000000"}} diff --git a/swift/Tests/Blockchains/AptosTests.swift b/swift/Tests/Blockchains/AptosTests.swift new file mode 100644 index 00000000000..3c54581dafe --- /dev/null +++ b/swift/Tests/Blockchains/AptosTests.swift @@ -0,0 +1,48 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +import WalletCore +import XCTest + +class AptosTests: XCTestCase { + func testAddress() { + let anyAddress = AnyAddress(string: "0x6af7d07b8a541913dfa87a9f99628faa255c70241ef9ebd9b82a7e715ee13108", coin: .aptos) + + XCTAssertEqual(anyAddress?.description, "0x6af7d07b8a541913dfa87a9f99628faa255c70241ef9ebd9b82a7e715ee13108") + XCTAssertEqual(anyAddress?.coin, .aptos) + + let invalid = "MQqpqMQgCBuiPkoXfgZZsJvuzCeI1zc00z6vHJj4" + XCTAssertNil(Data(hexString: invalid)) + XCTAssertNil(AnyAddress(string: invalid, coin: .aptos)) + XCTAssertFalse(AnyAddress.isValid(string: invalid, coin: .aptos)) + } + + func testSign() { + // Successfully broadcasted https://explorer.aptoslabs.com/txn/0xb4d62afd3862116e060dd6ad9848ccb50c2bc177799819f1d29c059ae2042467?network=devnet + let privateKeyData = Data(hexString: "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")! + let transferMsg = AptosTransferMessage.with { + $0.to = "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30" + $0.amount = 1000 + } + let input = AptosSigningInput.with { + $0.chainID = 33 + $0.sender = "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30" + $0.expirationTimestampSecs = 3664390082 + $0.gasUnitPrice = 100 + $0.maxGasAmount = 3296766 + $0.sequenceNumber = 99 + $0.transfer = transferMsg + $0.privateKey = privateKeyData + } + let output: AptosSigningOutput = AnySigner.sign(input: input, coin: .aptos) + let expectedRawTx = "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada0000000021" + let expectedSignature = "5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01" + let expectedSignedTx = "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c405707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01" + XCTAssertEqual(output.rawTxn.hexString, expectedRawTx) + XCTAssertEqual(output.authenticator.signature.hexString, expectedSignature) + XCTAssertEqual(output.encoded.hexString, expectedSignedTx) + } +} diff --git a/swift/Tests/Blockchains/AvalancheTests.swift b/swift/Tests/Blockchains/AvalancheTests.swift index 5f6d6bb595c..6e7b1a81c1d 100644 --- a/swift/Tests/Blockchains/AvalancheTests.swift +++ b/swift/Tests/Blockchains/AvalancheTests.swift @@ -18,4 +18,20 @@ class AvalancheTests: XCTestCase { XCTAssertEqual(address.description, addressETH.description) XCTAssertEqual(address.data.hexString, addressETH.data.hexString) } + + func testXPub() { + let wallet = HDWallet(mnemonic: "liquid spider narrow follow black west cabbage intact stadium resource gentle raccoon", passphrase: "")! + + let xpub1 = wallet.getExtendedPublicKey(purpose: .bip44, coin: .ethereum, version: .xpub) + let xpub2 = wallet.getExtendedPublicKey(purpose: .bip44, coin: .avalancheCChain, version: .xpub) + + XCTAssertEqual(xpub1, xpub2) + XCTAssertEqual(xpub2, "xpub6Bmo35QfCNvffj8tZsTVRvFxA5ULv2aHDV8dDCa7q6SzkMLJffxWRNE5vSJ4hANoKpmSp3p7Nbcp1vEz5F785HV4Aq2A6jWwHoyWp3EFgYp") + + let xpri1 = wallet.getExtendedPrivateKey(purpose: .bip44, coin: .ethereum, version: .xprv) + let xpri2 = wallet.getExtendedPrivateKey(purpose: .bip44, coin: .smartChain, version: .xprv) + + XCTAssertEqual(xpri1, xpri2) + XCTAssertEqual(xpri2, "xprv9xnSdZsmN1NNTF4RTqvV4nKDc3drWZrRrGD2QpAWGkv1sZ1A88eFsZuc59vKRr4mxJELH7C18ymaHENodYzEbeLq1JmPUAy3CmpA2inVCwo") + } } diff --git a/swift/Tests/Blockchains/BandChainTests.swift b/swift/Tests/Blockchains/BandChainTests.swift index 78e62e3f314..03200907d37 100644 --- a/swift/Tests/Blockchains/BandChainTests.swift +++ b/swift/Tests/Blockchains/BandChainTests.swift @@ -27,7 +27,7 @@ class BandChainTests: XCTestCase { $0.fromAddress = fromAddress $0.toAddress = "band1pnndgfwsrff86263xzpc5cd3t6yfvgjyqc87jh" $0.amounts = [CosmosAmount.with { - $0.amount = 1000000 + $0.amount = "1000000" $0.denom = "uband" }] } @@ -39,7 +39,7 @@ class BandChainTests: XCTestCase { let fee = CosmosFee.with { $0.gas = 200000 $0.amounts = [CosmosAmount.with { - $0.amount = 100 + $0.amount = "100" $0.denom = "uband" }] } @@ -107,7 +107,7 @@ class BandChainTests: XCTestCase { $0.delegatorAddress = "band13nzgys7y9c693u0pq089an4pq6q87hf9kqgkrz" $0.validatorAddress = "bandvaloper13fwr8rmugu2mfuurfx4sfmyv05haw9sujnqzd8" $0.amount = CosmosAmount.with { - $0.amount = 1000000 + $0.amount = "1000000" $0.denom = "uband" } } @@ -119,7 +119,7 @@ class BandChainTests: XCTestCase { let fee = CosmosFee.with { $0.gas = 200000 $0.amounts = [CosmosAmount.with { - $0.amount = 100 + $0.amount = "100" $0.denom = "uband" }] } @@ -191,7 +191,7 @@ class BandChainTests: XCTestCase { let fee = CosmosFee.with { $0.amounts = [CosmosAmount.with { - $0.amount = 100 + $0.amount = "100" $0.denom = "uband" }] $0.gas = 200000 @@ -253,7 +253,7 @@ class BandChainTests: XCTestCase { $0.delegatorAddress = "band13tug898kgtwprg7fevzzqgh45draa3cyffw3kp" $0.validatorAddress = "bandvaloper1jp633fleakzv4uxxvl707j9u2jj6j5x2rg7glv" $0.amount = CosmosAmount.with { - $0.amount = 500000 + $0.amount = "500000" $0.denom = "uband" } } @@ -265,7 +265,7 @@ class BandChainTests: XCTestCase { let fee = CosmosFee.with { $0.gas = 200000 $0.amounts = [CosmosAmount.with { - $0.amount = 100 + $0.amount = "100" $0.denom = "uband" }] } @@ -331,7 +331,7 @@ class BandChainTests: XCTestCase { $0.validatorSrcAddress = "bandvaloper1hln9scsl9yqup8nxyum06rmggql5m5zqwxmt3p" $0.validatorDstAddress = "bandvaloper1hydxm5h8v6tty2x623az65x3r39tl3paxyxtr0" $0.amount = CosmosAmount.with { - $0.amount = 500000 + $0.amount = "500000" $0.denom = "uband" } } @@ -343,7 +343,7 @@ class BandChainTests: XCTestCase { let fee = CosmosFee.with { $0.gas = 200000 $0.amounts = [CosmosAmount.with { - $0.amount = 100 + $0.amount = "100" $0.denom = "uband" }] } diff --git a/swift/Tests/Blockchains/BitcoinTests.swift b/swift/Tests/Blockchains/BitcoinTests.swift index e4b7255fc76..4ace92e08e5 100644 --- a/swift/Tests/Blockchains/BitcoinTests.swift +++ b/swift/Tests/Blockchains/BitcoinTests.swift @@ -174,4 +174,13 @@ class BitcoinTransactionSignerTests: XCTestCase { XCTAssertEqual(output.error, .ok) XCTAssertEqual(output.encoded.hexString, "01000000026c90312e53a3411347a197bfd637c2583d617dd2317262a70e1b5245d2f1e36a000000008a47304402201a631068ea5ddea19467ef7c932a0f3b04f366ca2beaf70e18958e47456124980220614816c449e39cf6acc6625e1cf3100db1db7c0b755bdbb6804d4fa3c4b735d10141041b3937fac1f14074447cde9d3a324ed292d2865ed0d7a7da26cb43558ce4db4ef33c47e820e53031ae16bb0c39205def059a5ca8e1d617650eabc72c5206a81dffffffff13bf27945c669cf3c1d70cf3048f4ab14f1ab6acf06d10d425e8288217a81efd000000008a473044022051d381d8f48a9a4866ca4109f12647922514604a4733e8da8aac046e19275f700220797c3ebf20df7d2a9fed283f9d0ad14cbd656cafb5ec70a2b1c85646ea7485190141041b3937fac1f14074447cde9d3a324ed292d2865ed0d7a7da26cb43558ce4db4ef33c47e820e53031ae16bb0c39205def059a5ca8e1d617650eabc72c5206a81dffffffff0194590000000000001976a914a0c0a50f986924e65ae9bd18eafae448f83117ed88ac00000000") } + + func testBitcoinMessageSigner() { + let verifyResult = BitcoinMessageSigner.verifyMessage( + address: "1B8Qea79tsxmn4dTiKKRVvsJpHwL2fMQnr", + message: "test signature", + signature: "H+3L5IbSVcejp4S2VwLXCxLEMQAWDvKbE8lQyq0ocdvyM1aoEudkzN/S/qLI3vnNOFY6V13BXWSFrPr3OjGa5Dk=" + ) + XCTAssertTrue(verifyResult) + } } diff --git a/swift/Tests/Blockchains/BluzelleTests.swift b/swift/Tests/Blockchains/BluzelleTests.swift index 02891cc58f0..fde2d9cc4a4 100644 --- a/swift/Tests/Blockchains/BluzelleTests.swift +++ b/swift/Tests/Blockchains/BluzelleTests.swift @@ -94,7 +94,7 @@ class BluzelleSignerTests: XCTestCase { $0.fromAddress = myAddress $0.toAddress = "bluzelle1xccvees6ev4wm2r49rc6ptulsdxa8x8jfpmund" $0.amounts = [CosmosAmount.with { - $0.amount = 1 + $0.amount = "1" $0.denom = "ubnt" }] } @@ -106,7 +106,7 @@ class BluzelleSignerTests: XCTestCase { let fee = CosmosFee.with { $0.gas = 500000 $0.amounts = [CosmosAmount.with { - $0.amount = 1000 + $0.amount = "1000" $0.denom = "ubnt" }] } diff --git a/swift/Tests/Blockchains/CardanoTests.swift b/swift/Tests/Blockchains/CardanoTests.swift index 8e7d5ed1b86..32561ab157d 100644 --- a/swift/Tests/Blockchains/CardanoTests.swift +++ b/swift/Tests/Blockchains/CardanoTests.swift @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,12 +9,216 @@ import XCTest class CardanoTests: XCTestCase { func testAddress() { - let key = PrivateKey(data: Data(hexString: "b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71effbf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4")!)! - let pubkey = key.getPublicKeyEd25519Extended() + let key = PrivateKey(data: Data(hexString: "e8c8c5b2df13f3abed4e6b1609c808e08ff959d7e6fc3d849e3f2880550b574437aa559095324d78459b9bb2da069da32337e1cc5da78f48e1bd084670107f3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26fae0d152bb611cb9ff34e945e4ff627e6fba81da687a601a879759cd76530b5744424db69a75edd4780a5fbc05d1a3c84ac4166ff8e424808481dd8e77627ce5f5bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276")!)! + let pubkey = key.getPublicKeyEd25519Cardano() let address = AnyAddress(publicKey: pubkey, coin: .cardano) - let addressFromString = AnyAddress(string: "addr1s3tl64970vuthz2j0qkz7kd2ya5j3fxuhdnv333vu38e6c37e4dq80ek4raf7hs3adag2tzpuxz7895a2x8xde5f8jqa8lrjyuqfj5k50pm668", coin: .cardano)! + let addressFromString = AnyAddress(string: "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq", coin: .cardano)! - XCTAssertEqual(pubkey.data.hexString, "57fd54be7b38bb8952782c2f59aa276928a4dcbb66c8c62ce44f9d623ecd5a03bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4") + XCTAssertEqual(pubkey.data.hexString, "fafa7eb4146220db67156a03a5f7a79c666df83eb31abbfbe77c85e06d40da3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26faf4b8d5201961e68f2e177ba594101f513ee70fe70a41324e8ea8eb787ffda6f4bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276") XCTAssertEqual(address.description, addressFromString.description) } + + func testDeriveAddressWallet() { + let wallet = HDWallet(mnemonic: "cost dash dress stove morning robust group affair stomach vacant route volume yellow salute laugh", passphrase: "")! + let privateKey = wallet.getKeyForCoin(coin: .cardano) + XCTAssertEqual(privateKey.data.hexString, "e8c8c5b2df13f3abed4e6b1609c808e08ff959d7e6fc3d849e3f2880550b574437aa559095324d78459b9bb2da069da32337e1cc5da78f48e1bd084670107f3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26fae0d152bb611cb9ff34e945e4ff627e6fba81da687a601a879759cd76530b5744424db69a75edd4780a5fbc05d1a3c84ac4166ff8e424808481dd8e77627ce5f5bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276") + let address = CoinType.cardano.deriveAddress(privateKey: privateKey) + XCTAssertEqual(address, "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq") + } + + func testSignTransfer1() { + var input = CardanoSigningInput.with { + $0.transferMessage.toAddress = "addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5" + $0.transferMessage.changeAddress = "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23" + $0.transferMessage.amount = 7000000 + $0.ttl = 53333333 + } + input.privateKey.append(Data(hexString: "089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e")!) + + let utxo1 = CardanoTxInput.with { + $0.outPoint.txHash = Data(hexString: "f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767")! + $0.outPoint.outputIndex = 1 + $0.address = "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23" + $0.amount = 1500000 + } + input.utxos.append(utxo1) + + let utxo2 = CardanoTxInput.with { + $0.outPoint.txHash = Data(hexString: "554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af0")! + $0.outPoint.outputIndex = 0 + $0.address = "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23" + $0.amount = 6500000 + } + input.utxos.append(utxo2) + + // Sign + let output: CardanoSigningOutput = AnySigner.sign(input: input, coin: .cardano) + XCTAssertEqual(output.error, TW_Common_Proto_SigningError.ok) + + let encoded = output.encoded + XCTAssertEqual(encoded.hexString, + "83a40082825820554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af000825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd51a006acfc082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a000ca96c021a000298d4031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058407cf591599852b5f5e007fdc241062405c47e519266c0d884b0767c1d4f5eacce00db035998e53ed10ca4ba5ce4aac8693798089717ce6cf4415f345cc764200ef6") + + let txid = output.txID + XCTAssertEqual(txid.hexString, "9b5b15e133cd73ccaa85307d2986aebc846505118a2eb4e6111e6b4b67d1f389") + } + + func testSignTransferToken1() throws { + let toToken = CardanoTokenAmount.with { + $0.policyID = "9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77" + $0.assetName = "SUNDAE" + $0.amount = Data(hexString: "01312d00")! // 20000000 + } + var toTokenBundle = CardanoTokenBundle(); + toTokenBundle.token.append(toToken) + + // check min ADA amount, set it + let inputTokenAmountSerialized = try toTokenBundle.serializedData() + let minAmount = CardanoMinAdaAmount(tokenBundle: inputTokenAmountSerialized) + XCTAssertEqual(minAmount, 1444443) + + var input = CardanoSigningInput.with { + $0.transferMessage.toAddress = "addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5" + $0.transferMessage.changeAddress = "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq" + $0.transferMessage.amount = minAmount + $0.transferMessage.useMaxAmount = false + $0.transferMessage.tokenAmount = toTokenBundle + $0.ttl = 53333333 + } + input.privateKey.append(Data(hexString: "089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e")!) + + var utxo1 = CardanoTxInput.with { + $0.outPoint.txHash = Data(hexString: "f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767")! + $0.outPoint.outputIndex = 1 + $0.address = "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23" + $0.amount = 8051373 + } + let token3 = CardanoTokenAmount.with { + $0.policyID = "9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77" + $0.assetName = "CUBY" + $0.amount = Data(hexString: "2dc6c0")! // 3000000 + } + utxo1.tokenAmount.append(token3) + input.utxos.append(utxo1) + + var utxo2 = CardanoTxInput.with { + $0.outPoint.txHash = Data(hexString: "f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767")! + $0.outPoint.outputIndex = 2 + $0.address = "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23" + $0.amount = 2000000 + } + let token1 = CardanoTokenAmount.with { + $0.policyID = "9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77" + $0.assetName = "SUNDAE" + $0.amount = Data(hexString: "04d3e8d9")! // 80996569 + } + utxo2.tokenAmount.append(token1) + let token2 = CardanoTokenAmount.with { + $0.policyID = "9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77" + $0.assetName = "CUBY" + $0.amount = Data(hexString: "1e8480")! // 2000000 + } + utxo2.tokenAmount.append(token2) + input.utxos.append(utxo2) + + // Sign + let output: CardanoSigningOutput = AnySigner.sign(input: input, coin: .cardano) + XCTAssertEqual(output.error, TW_Common_Proto_SigningError.ok) + + let encoded = output.encoded + XCTAssertEqual(encoded.hexString, + "83a40082825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76702018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd5821a00160a5ba1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a14653554e4441451a01312d00825839018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468821a0080aac9a1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a244435542591a004c4b404653554e4441451a03a2bbd9021a0002aa09031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840d90dcfbd190cbe59c42094e59eeb49b3de9d80a85b786cc311f932c5c9302d1c8c6c577b22aa70ff7955c139c700ea918f8cb425c3ba43a27980e1d238e4e908f6") + + let txid = output.txID + XCTAssertEqual(txid.hexString, "201c537693b005b64a0f0528e366ec67a84be0119ed4363b547f141f2a7770c2") + } + + func testGetStakingAddress() throws { + let stakingAddress = Cardano.getStakingAddress(baseAddress: "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23") + XCTAssertEqual(stakingAddress, "stake1u80jysjtdzqt88jt4jx93h5lumfr67d273r4vwyasfa2pxcwxllmx") + } + + func testSignStakingRegisterAndDelegate() throws { + let ownAddress = "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23" + let stakingAddress = Cardano.getStakingAddress(baseAddress: ownAddress) + let poolIdNufi = "7d7ac07a2f2a25b7a4db868a40720621c4939cf6aefbb9a11464f1a6" + + var input = CardanoSigningInput.with { + $0.transferMessage.toAddress = ownAddress + $0.transferMessage.changeAddress = ownAddress + $0.transferMessage.amount = 4000000 // not relevant as we use MaxAmount + $0.transferMessage.useMaxAmount = true + $0.ttl = 69885081 + // Register staking key, 2 ADA desposit + $0.registerStakingKey.stakingAddress = stakingAddress + $0.registerStakingKey.depositAmount = 2000000 + // Delegate + $0.delegate.stakingAddress = stakingAddress + $0.delegate.poolID = Data(hexString: poolIdNufi)! + $0.delegate.depositAmount = 0 + } + input.privateKey.append(Data(hexString: "089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e")!) + + let utxo1 = CardanoTxInput.with { + $0.outPoint.txHash = Data(hexString: "9b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e")! + $0.outPoint.outputIndex = 0 + $0.address = ownAddress + $0.amount = 4000000 + } + let utxo2 = CardanoTxInput.with { + $0.outPoint.txHash = Data(hexString: "9b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e")! + $0.outPoint.outputIndex = 1 + $0.address = ownAddress + $0.amount = 26651312 + } + input.utxos.append(utxo1) + input.utxos.append(utxo2) + + // Sign + let output: CardanoSigningOutput = AnySigner.sign(input: input, coin: .cardano) + XCTAssertEqual(output.error, TW_Common_Proto_SigningError.ok) + + let encoded = output.encoded + XCTAssertEqual(encoded.hexString, + "83a500828258209b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e008258209b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e01018182583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a01b27ef5021a0002b03b031a042a5c99048282008200581cdf22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b83028200581cdf22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b581c7d7ac07a2f2a25b7a4db868a40720621c4939cf6aefbb9a11464f1a6a100828258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840677c901704be027d9a1734e8aa06f0700009476fa252baaae0de280331746a320a61456d842d948ea5c0e204fc36f3bd04c88ca7ee3d657d5a38014243c37c07825820e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e0693258401fa21bdc62b85ca217bf08cbacdeba2fadaf33dc09ee3af9cc25b40f24822a1a42cfbc03585cc31a370ef75aaec4d25db6edcf329e40a4e725ec8718c94f220af6") + + let txid = output.txID + XCTAssertEqual(txid.hexString, "96a781fd6481b6a7fd3926da110265e8c44b53947b81daa84da5b148825d02aa") + } + + func testSignStakingWithdraw() throws { + let ownAddress = "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23" + let stakingAddress = Cardano.getStakingAddress(baseAddress: "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23") + + var input = CardanoSigningInput.with { + $0.transferMessage.toAddress = ownAddress + $0.transferMessage.changeAddress = ownAddress + $0.transferMessage.amount = 6000000 // not relevant as we use MaxAmount + $0.transferMessage.useMaxAmount = true + $0.ttl = 71678326 + // Withdraw available amount + $0.withdraw.stakingAddress = stakingAddress + $0.withdraw.withdrawAmount = 3468 + } + input.privateKey.append(Data(hexString: "089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e")!) + + let utxo1 = CardanoTxInput.with { + $0.outPoint.txHash = Data(hexString: "7dfd2c579794314b1f84efc9db932a098e440ccefb874945591f1d4e85a9152a")! + $0.outPoint.outputIndex = 0 + $0.address = ownAddress + $0.amount = 6305913 + } + input.utxos.append(utxo1) + + // Sign + let output: CardanoSigningOutput = AnySigner.sign(input: input, coin: .cardano) + XCTAssertEqual(output.error, TW_Common_Proto_SigningError.ok) + + let encoded = output.encoded + XCTAssertEqual(encoded.hexString, + "83a500818258207dfd2c579794314b1f84efc9db932a098e440ccefb874945591f1d4e85a9152a00018182583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a005da6ff021a00029f06031a0445b97605a1581de1df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b190d8ca100828258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058401ebaca2876fd17122404912a2558a98109cdf0f990a938d2917fa2c3b8c4e55e18a2cbabfa82fff03fa0d7ab8b88ca01ed18e42af3bfc4cda7f423a3aa30c00b825820e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e069325840777f04fa8f083fe562aecf78898aaaaac36e2cc6ca962f6ffb01e84a421cae1860496db79b2c5fb2879524c3d5121060b9ea1e693336230c6e5338e14c4c3303f6") + + let txid = output.txID + XCTAssertEqual(txid.hexString, "6dcf3956232953fc25b8355fb1ded1e912b5802090fd21434d789087d6329683") + } } diff --git a/swift/Tests/Blockchains/CosmosTests.swift b/swift/Tests/Blockchains/CosmosTests.swift index 21a7ab0dd4c..3d940fe09d5 100644 --- a/swift/Tests/Blockchains/CosmosTests.swift +++ b/swift/Tests/Blockchains/CosmosTests.swift @@ -34,7 +34,7 @@ class CosmosSignerTests: XCTestCase { $0.fromAddress = fromAddress.description $0.toAddress = "cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573" $0.amounts = [CosmosAmount.with { - $0.amount = 1 + $0.amount = "1" $0.denom = "muon" }] } @@ -46,12 +46,13 @@ class CosmosSignerTests: XCTestCase { let fee = CosmosFee.with { $0.gas = 200000 $0.amounts = [CosmosAmount.with { - $0.amount = 200 + $0.amount = "200" $0.denom = "muon" }] } let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; $0.accountNumber = 1037 $0.chainID = "gaia-13003" $0.memo = "" @@ -63,50 +64,80 @@ class CosmosSignerTests: XCTestCase { let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .cosmos) - let expectedJSON: String = -""" -{ - "mode": "block", - "tx": { - "fee": { - "amount": [ - { - "amount": "200", - "denom": "muon" - } - ], - "gas": "200000" - }, - "memo": "", - "msg": [ - { - "type": "cosmos-sdk/MsgSend", - "value": { - "amount": [ - { - "amount": "1", - "denom": "muon" + XCTAssertJSONEqual(output.serialized, "{\"tx_bytes\": \"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H\", \"mode\": \"BROADCAST_MODE_BLOCK\"}") + XCTAssertEqual(output.error, "") + } + + func testAuthCompounding() { + let authMessage = CosmosMessage.AuthGrant.with { + $0.granter = "cosmos13k0q0l7lg2kr32kvt7ly236ppldy8v9dzwh3gd" + $0.grantee = "cosmos1fs7lu28hx5m9akm7rp0c2422cn8r2f7gurujhf" + $0.grantStake = CosmosMessage.StakeAuthorization.with { + $0.allowList.address = ["cosmosvaloper1gjtvly9lel6zskvwtvlg5vhwpu9c9waw7sxzwx"] + $0.authorizationType = CosmosMessage.AuthorizationType.delegate } - ], - "from_address": "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02", - "to_address": "cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573" + $0.expiration = 1692309600 } - } - ], - "signatures": [ - { - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F" - }, - "signature": "/D74mdIGyIB3/sQvIboLTfS9P9EV/fYGrgHZE2/vNj9X6eM6e57G3atljNB+PABnRw3pTk51uXmhCFop8O/ZJg==" - } - ] - } -} -""" + let message = CosmosMessage.with { + $0.authGrant = authMessage + } + let fee = CosmosFee.with { + $0.gas = 96681 + $0.amounts = [CosmosAmount.with { + $0.amount = "2418" + $0.denom = "uatom" + }] + } + + let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; + $0.accountNumber = 1290826 + $0.chainID = "cosmoshub-4" + $0.memo = "" + $0.sequence = 5 + $0.messages = [message] + $0.fee = fee + $0.privateKey = PrivateKey(data: Data(hexString: "c7764249cdf77f8f1d840fa8af431579e5e41cf1af937e1e23afa22f3f4f0ccc")!)!.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .cosmos) - XCTAssertJSONEqual(expectedJSON, output.json) + XCTAssertJSONEqual(output.serialized, "{\"tx_bytes\": \"CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczEzazBxMGw3bGcya3IzMmt2dDdseTIzNnBwbGR5OHY5ZHp3aDNnZBItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjoSNgo0Y29zbW9zdmFsb3BlcjFnanR2bHk5bGVsNnpza3Z3dHZsZzV2aHdwdTljOXdhdzdzeHp3eCABEgYI4LD6pgYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAUSEwoNCgV1YXRvbRIEMjQxOBCp8wUaQIFyfuijGKf87Hz61ZqxasfLI1PZnNge4RDq/tRyB/tZI6p80iGRqHecoV6+84EQkc9GTlNRQOSlApRCsivT9XI=\", \"mode\": \"BROADCAST_MODE_BLOCK\"}") + XCTAssertEqual(output.error, "") + } + + func testRevokeAuthCompounding() { + let revokeAuthMessage = CosmosMessage.AuthRevoke.with { + $0.granter = "cosmos13k0q0l7lg2kr32kvt7ly236ppldy8v9dzwh3gd" + $0.grantee = "cosmos1fs7lu28hx5m9akm7rp0c2422cn8r2f7gurujhf" + $0.msgTypeURL = "/cosmos.staking.v1beta1.MsgDelegate" + } + let message = CosmosMessage.with { + $0.authRevoke = revokeAuthMessage + } + let fee = CosmosFee.with { + $0.gas = 87735 + $0.amounts = [CosmosAmount.with { + $0.amount = "2194" + $0.denom = "uatom" + }] + } + + let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; + $0.accountNumber = 1290826 + $0.chainID = "cosmoshub-4" + $0.memo = "" + $0.sequence = 4 + $0.messages = [message] + $0.fee = fee + $0.privateKey = PrivateKey(data: Data(hexString: "c7764249cdf77f8f1d840fa8af431579e5e41cf1af937e1e23afa22f3f4f0ccc")!)!.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .cosmos) + + XCTAssertJSONEqual(output.serialized, "{\"tx_bytes\": \"CqoBCqcBCh8vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnUmV2b2tlEoMBCi1jb3Ntb3MxM2swcTBsN2xnMmtyMzJrdnQ3bHkyMzZwcGxkeTh2OWR6d2gzZ2QSLWNvc21vczFmczdsdTI4aHg1bTlha203cnAwYzI0MjJjbjhyMmY3Z3VydWpoZhojL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuTXNnRGVsZWdhdGUSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAQSEwoNCgV1YXRvbRIEMjE5NBC3rQUaQI7K+W7MMBoD6FbFZxRBqs9VTjErztjWTy57+fvrLaTCIZ+eBs7CuaKqfUZdSN8otjubSHVTQID3k9DpPAX0yDo=\", \"mode\": \"BROADCAST_MODE_BLOCK\"}") + XCTAssertEqual(output.error, "") } func testStaking() { @@ -114,7 +145,7 @@ class CosmosSignerTests: XCTestCase { $0.delegatorAddress = "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02" $0.validatorAddress = "cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp" $0.amount = CosmosAmount.with { - $0.amount = 10 + $0.amount = "10" $0.denom = "muon" } } @@ -126,12 +157,13 @@ class CosmosSignerTests: XCTestCase { let fee = CosmosFee.with { $0.gas = 101721 $0.amounts = [CosmosAmount.with { - $0.amount = 1018 + $0.amount = "1018" $0.denom = "muon" }] } let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; $0.accountNumber = 1037 $0.chainID = "gaia-13003" $0.memo = "" @@ -143,47 +175,8 @@ class CosmosSignerTests: XCTestCase { let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .cosmos) - let expectedJSON = """ -{ - "mode": "block", - "tx": { - "fee": { - "amount": [ - { - "amount": "1018", - "denom": "muon" - } - ], - "gas": "101721" - }, - "memo": "", - "msg": [ - { - "type": "cosmos-sdk/MsgDelegate", - "value": { - "amount": { - "amount": "10", - "denom": "muon" - }, - "delegator_address": "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02", - "validator_address": "cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp" - } - } - ], - "signatures": [ - { - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F" - }, - "signature": "wIvfbCsLRCjzeXXoXTKfHLGXRbAAmUp0O134HVfVc6pfdVNJvvzISMHRUHgYcjsSiFlLyR32heia/yLgMDtIYQ==" - } - ] - } -} - -""" - XCTAssertJSONEqual(expectedJSON, output.json) + XCTAssertJSONEqual(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CpsBCpgBCiMvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dEZWxlZ2F0ZRJxCi1jb3Ntb3MxaHNrNmpyeXlxamZocDVkaGM1NXRjOWp0Y2t5Z3gwZXBoNmRkMDISNGNvc21vc3ZhbG9wZXIxemt1cHI4M2hyemtuM3VwNWVsa3R6Y3EzdHVmdDhueHNtd2RxZ3AaCgoEbXVvbhICMTASZgpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAcSEgoMCgRtdW9uEgQxMDE4ENmaBhpA8O9Jm/kL6Za2I3poDs5vpMowYJgNvYCJBRU/vxAjs0lNZYsq40qpTbwOTbORjJA5UjQ6auc40v6uCFT4q4z+uA==\"}") + XCTAssertEqual(output.error, "") } func testWithdraw() { @@ -209,12 +202,13 @@ class CosmosSignerTests: XCTestCase { let fee = CosmosFee.with { $0.amounts = [CosmosAmount.with { $0.denom = "uatom" - $0.amount = 1 + $0.amount = "1" }] $0.gas = 220000 } let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; $0.fee = fee $0.accountNumber = 8698 $0.chainID = "cosmoshub-2" @@ -226,54 +220,56 @@ class CosmosSignerTests: XCTestCase { let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .cosmos) - let expectedJSON = """ - { - "mode": "block", - "tx": { - "fee": { - "amount": [ - { - "amount": "1", - "denom": "uatom" - } - ], - "gas": "220000" - }, - "memo": "", - "msg": [ - { - "type": "cosmos-sdk/MsgWithdrawDelegationReward", - "value": { - "delegator_address": "cosmos100rhxclqasy6vnrcervgh99alx5xw7lkfp4u54", - "validator_address": "cosmosvaloper1ey69r37gfxvxg62sh4r0ktpuc46pzjrm873ae8" - } - },{ - "type": "cosmos-sdk/MsgWithdrawDelegationReward", - "value": { - "delegator_address": "cosmos100rhxclqasy6vnrcervgh99alx5xw7lkfp4u54", - "validator_address": "cosmosvaloper1sjllsnramtg3ewxqwwrwjxfgc4n4ef9u2lcnj0" - } - },{ - "type": "cosmos-sdk/MsgWithdrawDelegationReward", - "value": { - "delegator_address": "cosmos100rhxclqasy6vnrcervgh99alx5xw7lkfp4u54", - "validator_address": "cosmosvaloper1648ynlpdw7fqa2axt0w2yp3fk542junl7rsvq6" - } - } - ], - "signatures": [ - { - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F" - }, - "signature": "2k5bSnfWxaauXHBNJTKmf4CpLiCWLg7UAC/q2SVhZNkU+n0DdLBSTdmYhKYmmtpl/Njm4YrcxE0WLb/hVccQ+g==" - } - ] - } + XCTAssertJSONEqual(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CukDCqABCjcvY29zbW9zLmRpc3RyaWJ1dGlvbi52MWJldGExLk1zZ1dpdGhkcmF3RGVsZWdhdG9yUmV3YXJkEmUKLWNvc21vczEwMHJoeGNscWFzeTZ2bnJjZXJ2Z2g5OWFseDV4dzdsa2ZwNHU1NBI0Y29zbW9zdmFsb3BlcjFleTY5cjM3Z2Z4dnhnNjJzaDRyMGt0cHVjNDZwempybTg3M2FlOAqgAQo3L2Nvc21vcy5kaXN0cmlidXRpb24udjFiZXRhMS5Nc2dXaXRoZHJhd0RlbGVnYXRvclJld2FyZBJlCi1jb3Ntb3MxMDByaHhjbHFhc3k2dm5yY2VydmdoOTlhbHg1eHc3bGtmcDR1NTQSNGNvc21vc3ZhbG9wZXIxc2psbHNucmFtdGczZXd4cXd3cndqeGZnYzRuNGVmOXUybGNuajAKoAEKNy9jb3Ntb3MuZGlzdHJpYnV0aW9uLnYxYmV0YTEuTXNnV2l0aGRyYXdEZWxlZ2F0b3JSZXdhcmQSZQotY29zbW9zMTAwcmh4Y2xxYXN5NnZucmNlcnZnaDk5YWx4NXh3N2xrZnA0dTU0EjRjb3Ntb3N2YWxvcGVyMTY0OHlubHBkdzdmcWEyYXh0MHcyeXAzZms1NDJqdW5sN3JzdnE2EmUKUQpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQJXKG7D830zVXu7qgALJ3RKyQI6qZZ8rnWhgdH/kfqdxRIECgIIARi+AhIQCgoKBXVhdG9tEgExEOC2DRpAXLgJ+8xEMUn7nkFj3ukg2V65Vh5ob7HKeCaNpMM6OPQrpW2r6askfssIFcOd8ThiBEz65bJz81Fmb5MtDTGv4g==\"}") + XCTAssertEqual(output.error, "") + } + + func testIbcTransfer() { + let privateKey = PrivateKey(data: Data(hexString: "8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af")!)! + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .cosmos) + + let transferMessage = CosmosMessage.Transfer.with { + $0.sourcePort = "transfer" + $0.sourceChannel = "channel-141" + $0.sender = fromAddress.description + $0.receiver = "osmo18s0hdnsllgcclweu9aymw4ngktr2k0rkvn7jmn" + $0.token = CosmosAmount.with { + $0.amount = "100000" + $0.denom = "uatom" + } + $0.timeoutHeight = CosmosHeight.with { + $0.revisionNumber = 1 + $0.revisionHeight = 8800000 + } + } + + let message = CosmosMessage.with { + $0.transferTokensMessage = transferMessage + } + + let fee = CosmosFee.with { + $0.gas = 500000 + $0.amounts = [CosmosAmount.with { + $0.amount = "12500" + $0.denom = "uatom" + }] } - """ - XCTAssertJSONEqual(expectedJSON, output.json) + let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; + $0.accountNumber = 546179 + $0.chainID = "cosmoshub-4" + $0.sequence = 2 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .cosmos) + + // https://www.mintscan.io/cosmos/txs/817101F3D96314AD028733248B28BAFAD535024D7D2C8875D3FE31DC159F096B + XCTAssertJSONEqual(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"Cr4BCrsBCikvaWJjLmFwcGxpY2F0aW9ucy50cmFuc2Zlci52MS5Nc2dUcmFuc2ZlchKNAQoIdHJhbnNmZXISC2NoYW5uZWwtMTQxGg8KBXVhdG9tEgYxMDAwMDAiLWNvc21vczFta3k2OWNuOGVrdHd5MDg0NXZlYzl1cHNkcGhrdHh0MDNna3dseCorb3NtbzE4czBoZG5zbGxnY2Nsd2V1OWF5bXc0bmdrdHIyazBya3ZuN2ptbjIHCAEQgI6ZBBJoClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEC7O9c5DejAsZ/lUaN5LMfNukR9GfX5qUrQcHhPh1WNkkSBAoCCAEYAhIUCg4KBXVhdG9tEgUxMjUwMBCgwh4aQK0HIWdFMk+C6Gi1KG/vELe1ffcc1aEWUIqz2t/ZhwqNNHxUUSp27wteiugHEMVTEIOBhs84t2gIcT/nD/1yKOU=\"}") + XCTAssertEqual(output.error, "") } } diff --git a/swift/Tests/Blockchains/CronosTests.swift b/swift/Tests/Blockchains/CronosTests.swift new file mode 100644 index 00000000000..b28a89849bb --- /dev/null +++ b/swift/Tests/Blockchains/CronosTests.swift @@ -0,0 +1,17 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +import XCTest +import WalletCore + +class CronosTests: XCTestCase { + + func testAddress() { + let address = AnyAddress(string: "0xEC49280228b0D05Aa8e8b756503254e1eE7835ab", coin: .cronosChain)! + + XCTAssertEqual(address.data.hexString, "ec49280228b0d05aa8e8b756503254e1ee7835ab") + } +} diff --git a/swift/Tests/Blockchains/CryptoorgTests.swift b/swift/Tests/Blockchains/CryptoorgTests.swift index 04ffd6443f6..7926940b6b5 100644 --- a/swift/Tests/Blockchains/CryptoorgTests.swift +++ b/swift/Tests/Blockchains/CryptoorgTests.swift @@ -28,7 +28,7 @@ class CryptoorgTests: XCTestCase { $0.fromAddress = fromAddress.description $0.toAddress = "cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus" $0.amounts = [CosmosAmount.with { - $0.amount = 100000000 + $0.amount = "50000000" $0.denom = "basecro" }] } @@ -40,16 +40,17 @@ class CryptoorgTests: XCTestCase { let fee = CosmosFee.with { $0.gas = 200000 $0.amounts = [CosmosAmount.with { - $0.amount = 5000 + $0.amount = "5000" $0.denom = "basecro" }] } let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; $0.accountNumber = 125798 $0.chainID = "crypto-org-chain-mainnet-1" $0.memo = "" - $0.sequence = 0 + $0.sequence = 2 $0.messages = [message] $0.fee = fee $0.privateKey = privateKey.data @@ -57,49 +58,8 @@ class CryptoorgTests: XCTestCase { let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .cryptoOrg) - let expectedJSON: String = -""" -{ - "mode": "block", - "tx": { - "fee": { - "amount": [ - { - "amount": "5000", - "denom": "basecro" - } - ], - "gas": "200000" - }, - "memo": "", - "msg": [ - { - "type": "cosmos-sdk/MsgSend", - "value": { - "amount": [ - { - "amount": "100000000", - "denom": "basecro" - } - ], - "from_address": "cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0", - "to_address": "cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus" - } - } - ], - "signatures": [ - { - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A4gxsGFiPn6L5Z2IjHEISkXI0IkwfL9exV3GLB171Wvj" - }, - "signature": "5+5rSFFg0FE9cTklQWQHNktBDJsz7UCnMSgF0t0+gYcrIhEWUyTtibXaHZQbKAAaciJ1BkHXYREjU55VswByVg==" - } - ] - } -} -""" - - XCTAssertJSONEqual(expectedJSON, output.json) + // https://crypto.org/explorer/tx/BCB213B0A121F0CF11BECCF52475F1C8328D6070F3CFDA9E14C42E6DB30E847E + XCTAssertJSONEqual(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CpABCo0BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm0KKmNybzFjdHd0Y3dwZ2tza3k5ODhkaHRoNmpzbHh2ZXVtZ3UwZDQ1emdmMBIqY3JvMXhwYWh5NmM3d2xkeGFjdjZsZDk5aDQzNW1odmZuc3VwMjR2Y3VzGhMKB2Jhc2Vjcm8SCDUwMDAwMDAwEmkKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQOIMbBhYj5+i+WdiIxxCEpFyNCJMHy/XsVdxiwde9Vr4xIECgIIARgCEhUKDwoHYmFzZWNybxIENTAwMBDAmgwaQAcxK9xk6r69gmz+1UWaCnYxNuXPXZdp59YcqKPJE5d6fp+IICTBOwd2rs8MiApcf8kNSrbZ6oECxcGQAdxF0SI=\"}") + XCTAssertEqual(output.error, "") } } diff --git a/swift/Tests/Blockchains/Data b/swift/Tests/Blockchains/Data index f50c5d874d3..154e08de1f2 120000 --- a/swift/Tests/Blockchains/Data +++ b/swift/Tests/Blockchains/Data @@ -1 +1 @@ -../../../tests/Ethereum/Data \ No newline at end of file +../../../tests/chains/Ethereum/Data \ No newline at end of file diff --git a/swift/Tests/Blockchains/ECashTests.swift b/swift/Tests/Blockchains/ECashTests.swift new file mode 100644 index 00000000000..7826b92e44f --- /dev/null +++ b/swift/Tests/Blockchains/ECashTests.swift @@ -0,0 +1,91 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +import WalletCore +import XCTest + +class ECashTests: XCTestCase { + + func testExtendedKeys() { + let wallet = HDWallet.test + + let xprv = wallet.getExtendedPrivateKey(purpose: .bip44, coin: .ecash, version: .xprv) + let xpub = wallet.getExtendedPublicKey(purpose: .bip44, coin: .ecash, version: .xpub) + + XCTAssertEqual(xprv, "xprv9xjBcTizebJaV61xMkuMJ89vis7saMmwFgTYeF83KwinEksJ4frk7wB4mDiKiwXDCbJmgmh6Bp1FkF8SopNZhbF3B5wyX32cuDVFZtuUDvB") + XCTAssertEqual(xpub, "xpub6BiY1yFtUxrsha6RTnSMfG6fGtxMypVncuP9SdXetHFm7ZCScDAzfjVYcW32bkNCGJ5DTqawAHSTbJdTBL8wVxqUDGpxnRtukrhhBoS7Wy7") + } + + func testDeriveFromXPub() { + let xpub = "xpub6BiY1yFtUxrsha6RTnSMfG6fGtxMypVncuP9SdXetHFm7ZCScDAzfjVYcW32bkNCGJ5DTqawAHSTbJdTBL8wVxqUDGpxnRtukrhhBoS7Wy7" + + let coin = CoinType.ecash + let xpubAddr2 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: coin, + derivationPath: DerivationPath(purpose: .bip44, coin: coin.slip44Id, account: 0, change: 0, address: 2).description + )! + + let xpubAddr9 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: coin, + derivationPath: DerivationPath(purpose: .bip44, coin: coin.slip44Id, account: 0, change: 0, address: 9).description + )! + + XCTAssertEqual(coin.deriveAddressFromPublicKey(publicKey: xpubAddr2), "ecash:qpttymfhuq3v8tasfv7drlglhq6ne6zxquqltu3dcj") + XCTAssertEqual(coin.deriveAddressFromPublicKey(publicKey: xpubAddr9), "ecash:qqjraw2s5pwqwzql4znjpvp4vtvy3c9gmugq62r2j7") + } + + func testAddress() { + XCTAssertEqual( + "ecash:prm3srpqu4kmx00370m4wt5qr3cp7sekmc0adf8na6", + AnyAddress(string: "ecash:prm3srpqu4kmx00370m4wt5qr3cp7sekmc0adf8na6", coin: .ecash)?.description + ) + XCTAssertEqual( + "ecash:prm3srpqu4kmx00370m4wt5qr3cp7sekmc0adf8na6", + AnyAddress(string: "prm3srpqu4kmx00370m4wt5qr3cp7sekmc0adf8na6", coin: .ecash)?.description + ) + } + + func testLockScript() { + let address = AnyAddress(string: "pzukqjmcyzrkh3gsqzdcy3e3d39cqxhl3gkypy0vjg", coin: .ecash)! + let script = BitcoinScript.lockScriptForAddress(address: address.description, coin: .ecash) + XCTAssertEqual(script.data.hexString, "a914b9604b7820876bc510009b8247316c4b801aff8a87") + + let address2 = AnyAddress(string: "qphr8l8ns8wd99a8653ctfe5qcrxaumz5qck55dsl3", coin: .ecash)! + let script2 = BitcoinScript.lockScriptForAddress(address: address2.description, coin: .ecash) + XCTAssertEqual(script2.data.hexString, "76a9146e33fcf381dcd297a7d52385a73406066ef362a088ac") + } + + func testSign() throws { + let utxoTxId = "050d00e2e18ef13969606f1ceee290d3f49bd940684ce39898159352952b8ce2" + let privateKey = PrivateKey(data: Data(hexString: "7fdafb9db5bc501f2096e7d13d331dc7a75d9594af3d251313ba8b6200f4e384")!)! + let address = CoinType.ecash.deriveAddress(privateKey: privateKey) + let utxo = BitcoinUnspentTransaction.with { + $0.outPoint.hash = Data.reverse(hexString: utxoTxId) // reverse of UTXO tx id, Bitcoin internal expects network byte order + $0.outPoint.index = 2 // outpoint index of this this UTXO + $0.outPoint.sequence = UINT32_MAX + $0.amount = 5151 // value of this UTXO + $0.script = BitcoinScript.lockScriptForAddress(address: address, coin: .ecash).data // Build lock script from address or public key hash + } + + let input = BitcoinSigningInput.with { + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .ecash) + $0.amount = 600 + $0.byteFee = 1 + $0.toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx" + $0.changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU" + $0.utxo = [utxo] + $0.privateKey = [privateKey.data] + } + + let output: BitcoinSigningOutput = AnySigner.sign(input: input, coin: .ecash) + XCTAssertEqual(output.error, TW_Common_Proto_SigningError.ok) + + XCTAssertEqual(output.transactionID, "96ee20002b34e468f9d3c5ee54f6a8ddaa61c118889c4f35395c2cd93ba5bbb4") + XCTAssertEqual(output.encoded.hexString, "0100000001e28c2b955293159898e34c6840d99bf4d390e2ee1c6f606939f18ee1e2000d05020000006b483045022100b70d158b43cbcded60e6977e93f9a84966bc0cec6f2dfd1463d1223a90563f0d02207548d081069de570a494d0967ba388ff02641d91cadb060587ead95a98d4e3534121038eab72ec78e639d02758e7860cdec018b49498c307791f785aa3019622f4ea5bffffffff0258020000000000001976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ace5100000000000001976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac00000000") + } +} diff --git a/swift/Tests/Blockchains/ElrondTests.swift b/swift/Tests/Blockchains/ElrondTests.swift index d9d5b1d746a..06718ed42a3 100644 --- a/swift/Tests/Blockchains/ElrondTests.swift +++ b/swift/Tests/Blockchains/ElrondTests.swift @@ -9,10 +9,10 @@ import XCTest class ElrondTests: XCTestCase { - let aliceBech32 = "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz" - let aliceSeedHex = "1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf" - let alicePubKeyHex = "fd691bb5e85d102687d81079dffce842d4dc328276d2d4c60d8fd1c3433c3293" - let bobBech32 = "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r" + let aliceBech32 = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + let alicePubKeyHex = "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1" + let aliceSeedHex = "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9" + let bobBech32 = "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx" func testAddress() { let key = PrivateKey(data: Data(hexString: aliceSeedHex)!)! @@ -24,28 +24,106 @@ class ElrondTests: XCTestCase { XCTAssertEqual(address.description, addressFromString.description) } - func testSign() { + func testSignGenericAction() { let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! let input = ElrondSigningInput.with { - $0.transaction = ElrondTransactionMessage.with { - $0.nonce = 0 + $0.genericAction = ElrondGenericAction.with { + $0.accounts = ElrondAccounts.with { + $0.senderNonce = 7 + $0.sender = aliceBech32 + $0.receiver = bobBech32 + } $0.value = "0" - $0.sender = aliceBech32 - $0.receiver = bobBech32 - $0.gasPrice = 1000000000 - $0.gasLimit = 50000 $0.data = "foo" - $0.chainID = "1" $0.version = 1 } + $0.gasPrice = 1000000000 + $0.gasLimit = 50000 + $0.chainID = "1" + $0.privateKey = privateKey.data + } + + let output: ElrondSigningOutput = AnySigner.sign(input: input, coin: .elrond) + let expectedSignature = "e8647dae8b16e034d518a1a860c6a6c38d16192d0f1362833e62424f424e5da660770dff45f4b951d9cc58bfb9d14559c977d443449bfc4b8783ff9c84065700" + let expectedEncoded = #"{"nonce":7,"value":"0","receiver":"\#(bobBech32)","sender":"\#(aliceBech32)","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"\#(expectedSignature)"}"# + + XCTAssertEqual(output.signature, expectedSignature) + XCTAssertEqual(output.encoded, expectedEncoded) + } + + func testSignEGLDTransfer() { + let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! + + let input = ElrondSigningInput.with { + $0.egldTransfer = ElrondEGLDTransfer.with { + $0.accounts = ElrondAccounts.with { + $0.senderNonce = 7 + $0.sender = aliceBech32 + $0.receiver = bobBech32 + } + $0.amount = "1000000000000000000" + } + $0.chainID = "1" + $0.privateKey = privateKey.data + } + + let output: ElrondSigningOutput = AnySigner.sign(input: input, coin: .elrond) + let expectedSignature = "7e1c4c63b88ea72dcf7855a54463b1a424eb357ac3feb4345221e512ce07c7a50afb6d7aec6f480b554e32cf2037082f3bc17263d1394af1f3ef240be53c930b" + let expectedEncoded = #"{"nonce":7,"value":"1000000000000000000","receiver":"\#(bobBech32)","sender":"\#(aliceBech32)","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":1,"signature":"\#(expectedSignature)"}"# + + XCTAssertEqual(output.signature, expectedSignature) + XCTAssertEqual(output.encoded, expectedEncoded) + } + + func testSignESDTTransfer() { + let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! + + let input = ElrondSigningInput.with { + $0.esdtTransfer = ElrondESDTTransfer.with { + $0.accounts = ElrondAccounts.with { + $0.senderNonce = 7 + $0.sender = aliceBech32 + $0.receiver = bobBech32 + } + $0.amount = "10000000000000" + $0.tokenIdentifier = "MYTOKEN-1234" + } + + $0.privateKey = privateKey.data + } + + let output: ElrondSigningOutput = AnySigner.sign(input: input, coin: .elrond) + let expectedSignature = "9add6d9ac3f1a1fddb07b934e8a73cad3b8c232bdf29d723c1b38ad619905f03e864299d06eb3fe3bbb48a9f1d9b7f14e21dc5eaffe0c87f5718ad0c4198bb0c" + let expectedData = "RVNEVFRyYW5zZmVyQDRkNTk1NDRmNGI0NTRlMmQzMTMyMzMzNEAwOTE4NGU3MmEwMDA=" + let expectedEncoded = #"{"nonce":7,"value":"0","receiver":"\#(bobBech32)","sender":"\#(aliceBech32)","gasPrice":1000000000,"gasLimit":425000,"data":"\#(expectedData)","chainID":"1","version":1,"signature":"\#(expectedSignature)"}"# + + XCTAssertEqual(output.signature, expectedSignature) + XCTAssertEqual(output.encoded, expectedEncoded) + } + + func testSignESDTNFTTransfer() { + let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! + + let input = ElrondSigningInput.with { + $0.esdtnftTransfer = ElrondESDTNFTTransfer.with { + $0.accounts = ElrondAccounts.with { + $0.senderNonce = 7 + $0.sender = aliceBech32 + $0.receiver = bobBech32 + } + $0.tokenCollection = "LKMEX-aab910" + $0.tokenNonce = 4 + $0.amount = "184300000000000000" + } $0.privateKey = privateKey.data } let output: ElrondSigningOutput = AnySigner.sign(input: input, coin: .elrond) - let expectedSignature = "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00" - let expectedEncoded = #"{"nonce":0,"value":"0","receiver":"\#(bobBech32)","sender":"\#(aliceBech32)","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"\#(expectedSignature)"}"# + let expectedSignature = "cc935685d5b31525e059a16a832cba98dee751983a5a93de4198f6553a2c55f5f1e0b4300fe9077376fa754546da0b0f6697e66462101a209aafd0fc775ab60a" + let expectedData = "RVNEVE5GVFRyYW5zZmVyQDRjNGI0ZDQ1NTgyZDYxNjE2MjM5MzEzMEAwNEAwMjhlYzNkZmEwMWFjMDAwQDgwNDlkNjM5ZTVhNjk4MGQxY2QyMzkyYWJjY2U0MTAyOWNkYTc0YTE1NjM1MjNhMjAyZjA5NjQxY2MyNjE4Zjg=" + let expectedEncoded = #"{"nonce":7,"value":"0","receiver":"\#(aliceBech32)","sender":"\#(aliceBech32)","gasPrice":1000000000,"gasLimit":937500,"data":"\#(expectedData)","chainID":"1","version":1,"signature":"\#(expectedSignature)"}"# XCTAssertEqual(output.signature, expectedSignature) XCTAssertEqual(output.encoded, expectedEncoded) diff --git a/swift/Tests/Blockchains/EthereumAbiTests.swift b/swift/Tests/Blockchains/EthereumAbiTests.swift index 3edc89d55c2..a6a18430e5b 100644 --- a/swift/Tests/Blockchains/EthereumAbiTests.swift +++ b/swift/Tests/Blockchains/EthereumAbiTests.swift @@ -213,4 +213,12 @@ class EthereumAbiTests: XCTestCase { let hash = EthereumAbi.encodeTyped(messageJson: message) XCTAssertEqual(hash.hexString, "a85c2e2b118698e88db68a8105b794a8cc7cec074e89ef991cb4f5f533819cc2") } + + func testEncodeSeaportMessage() throws { + let url = Bundle(for: EthereumAbiTests.self).url(forResource: "seaport_712", withExtension: "json")! + let json = try String(contentsOf: url) + let hash = EthereumAbi.encodeTyped(messageJson: json) + + XCTAssertEqual(hash.hexString, "54140d99a864932cbc40fd8a2d1d1706c3923a79c183a3b151e929ac468064db") + } } diff --git a/swift/Tests/Blockchains/EthereumFeeTests.swift b/swift/Tests/Blockchains/EthereumFeeTests.swift deleted file mode 100644 index 09e5a830931..00000000000 --- a/swift/Tests/Blockchains/EthereumFeeTests.swift +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright © 2017-2021 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 XCTest -import WalletCore - -class EthereumFeeTests: XCTestCase { - - func testBaseFeeOnly() throws { - let url = Bundle(for: EthereumFeeTests.self).url(forResource: "eth_feeHistory", withExtension: "json")! - let data = try String(contentsOf: url) - - guard let result = EthereumFee.suggest(feeHistory: data) else { - XCTFail("fail to suggest fee") - return - } - - let expected = """ - { - "baseFee": "80885125076", - "maxPriorityFee": "2000000000" - } - """ - - XCTAssertJSONEqual(result, expected) - } - - func testBaseFeeAndPriority() throws { - let url = Bundle(for: EthereumFeeTests.self).url(forResource: "eth_feeHistory2", withExtension: "json")! - let data = try String(contentsOf: url) - - guard let result = EthereumFee.suggest(feeHistory: data) else { - XCTFail("fail to suggest fee") - return - } - - let expected = """ - { - "baseFee": "87408740685", - "maxPriorityFee": "1500000000" - } - """ - - XCTAssertJSONEqual(result, expected) - } - - func testHexPadding() throws { - let url = Bundle(for: EthereumFeeTests.self).url(forResource: "eth_feeHistory3", withExtension: "json")! - let data = try String(contentsOf: url) - - guard let result = EthereumFee.suggest(feeHistory: data) else { - XCTFail("fail to suggest fee") - return - } - - let expected = """ - { - "baseFee": "44208904215", - "maxPriorityFee": "1500000000" - } - """ - - XCTAssertJSONEqual(result, expected) - } -} diff --git a/swift/Tests/Blockchains/EthereumTests.swift b/swift/Tests/Blockchains/EthereumTests.swift index ae65e1ce74e..b16ba0bf7db 100644 --- a/swift/Tests/Blockchains/EthereumTests.swift +++ b/swift/Tests/Blockchains/EthereumTests.swift @@ -21,7 +21,7 @@ class EthereumTests: XCTestCase { XCTAssertFalse(AnyAddress.isValid(string: invalid, coin: .ethereum)) } - func testSigner() { + func testSigner() throws { let input = EthereumSigningInput.with { $0.chainID = Data(hexString: "01")! $0.nonce = Data(hexString: "09")! @@ -39,6 +39,7 @@ class EthereumTests: XCTestCase { let output: EthereumSigningOutput = AnySigner.sign(input: input, coin: .ethereum) + XCTAssertEqual(try input.serializedData().hexString, "0a0101120109220504a817c8002a025208422a3078333533353335333533353335333533353335333533353335333533353335333533353335333533354a204646464646464646464646464646464646464646464646464646464646464646520c0a0a0a080de0b6b3a7640000") XCTAssertEqual(output.encoded.hexString, "f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83") } diff --git a/swift/Tests/Blockchains/EverscaleTests.swift b/swift/Tests/Blockchains/EverscaleTests.swift new file mode 100644 index 00000000000..d3ce3f32293 --- /dev/null +++ b/swift/Tests/Blockchains/EverscaleTests.swift @@ -0,0 +1,54 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +import WalletCore +import XCTest + +class EverscaleTests: XCTestCase { + func testAddressFromPrivateKey() { + let privateKey = PrivateKey(data: Data(hexString: "15d126cb1a84acdbcd1d9c3f6975968c2beb18cc43c95849d4b0226e1c8552aa")!)! + let publicKey = privateKey.getPublicKeyEd25519() + let address = AnyAddress(publicKey: publicKey, coin: .everscale) + XCTAssertEqual(address.description, "0:0c39661089f86ec5926ea7d4ee4223d634ba4ed6dcc2e80c7b6a8e6d59f79b04") + } + + func testAddressFromPublicKey() { + let publicKey = PublicKey(data: Data(hexString: "a0303f8fc89a3c2124f5dc6f3ab9a9cb246b7d1e24897eaf5e63eeee20085db0")!, type: PublicKeyType.ed25519)! + let address = AnyAddress(publicKey: publicKey, coin: .everscale) + XCTAssertEqual(address.description, "0:0c39661089f86ec5926ea7d4ee4223d634ba4ed6dcc2e80c7b6a8e6d59f79b04") + } + + func testAddressFromString() { + let addressString = "0:0c39661089f86ec5926ea7d4ee4223d634ba4ed6dcc2e80c7b6a8e6d59f79b04" + let address = AnyAddress(string: addressString, coin: .everscale) + XCTAssertEqual(address!.description, "0:0c39661089f86ec5926ea7d4ee4223d634ba4ed6dcc2e80c7b6a8e6d59f79b04") + } + + func testSign() throws { + let privateKeyData = Data(hexString: "542bd4288352f1c6b270046f153d406aec054a0a06000ab9b36b5c6dd3050ad4")! + + let transfer = EverscaleTransfer.with { + $0.bounce = false + $0.behavior = EverscaleMessageBehavior.simpleTransfer + $0.amount = 100000000 + $0.expiredAt = 1680770631 + $0.to = "0:db18a67f4626f15ac0537a18445937f685f9b30184f0d7b28be4bdeb92d2fd90" + $0.encodedContractData = "te6ccgEBAQEAKgAAUAAAAAFLqS2KOWKN+7Y5OSiKhKisiw6t/h2ovvR3WbapyAtrdctwupw=" + } + + let input = EverscaleSigningInput.with { + $0.transfer = transfer + $0.privateKey = privateKeyData + } + + let output: EverscaleSigningOutput = AnySigner.sign(input: input, coin: .everscale) + + // Link to the message: https://everscan.io/messages/73807b0a3ca2d8564c023dccd5b9da222a270f68338c6fc2c064dda376a2c59d + let expectedString = "te6ccgICAAIAAQAAAKoAAAHfiAG+Ilaz1wTyTEauoymMGl6o+NGqhszIlHS8BXAmXniYrAImASIQKH2jIwoA65IGC6aua4gAA4fFo/Nuxgb3sIRELhZnSXIS7IsE2E4D+8hk3EWGVZX+ICqlN/ka9DvXduhaXUlsUyF0MjgAAAAIHAABAGhCAG2MUz+jE3itYCm9DCIsm/tC/NmAwnhr2UXyXvXJaX7IIC+vCAAAAAAAAAAAAAAAAAAA" + + XCTAssertEqual(output.encoded, expectedString) + } +} diff --git a/swift/Tests/Blockchains/EvmosTests.swift b/swift/Tests/Blockchains/EvmosTests.swift new file mode 100644 index 00000000000..ae10bdc08aa --- /dev/null +++ b/swift/Tests/Blockchains/EvmosTests.swift @@ -0,0 +1,76 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +import XCTest +import WalletCore + +class EvmosTests: XCTestCase { + + func testAddressData() throws { + let wallet = HDWallet(strength: 256, passphrase: "")! + let nativeEvmos = wallet.getAddressForCoin(coin: .nativeEvmos) + let evmos = wallet.getAddressForCoin(coin: .evmos) + + let addr1 = AnyAddress(string: nativeEvmos, coin: .nativeEvmos) + let addr2 = AnyAddress(string: evmos, coin: .evmos) + + XCTAssertEqual(addr1?.data.hexString, addr2?.data.hexString) + } + + func testSigningNativeTransfer() { + + let wallet = HDWallet(mnemonic: "glue blanket noodle name bring castle degree vibrant great joy usual mother pyramid cat balance swear diagram green split goat token day arm shoe", passphrase: "")! + let privateKey = wallet.getKeyForCoin(coin: .nativeEvmos) + let publicKey = privateKey.getPublicKeySecp256k1(compressed: false) + + XCTAssertEqual(publicKey.data.hexString, "049475c9fa23ec693667baa76c4da69b49cccfdf058c4dcb27ba67cfbc9082d9ed9074786560aa698b19bb9729526b1c75934f3d4a78f7be719e4386b749b36310") + + let fromAddress = AnyAddress(publicKey: publicKey, coin: .nativeEvmos) + + print(publicKey.compressed.data.hexString) + + XCTAssertEqual(fromAddress.description, "evmos1rk39dk3wff5nps7emuhv3ntkn3nsz6z2erqfr0") + + let sendCoinsMessage = CosmosMessage.Send.with { + $0.fromAddress = fromAddress.description + $0.toAddress = "evmos10k9lrrruap9nu96mxwwye2f6a5wazeh33kq67z" // 1p + $0.amounts = [CosmosAmount.with { + $0.amount = "200000000000000" // 0.0002 Evmos + $0.denom = "aevmos" + }] + } + + let message = CosmosMessage.with { + $0.sendCoinsMessage = sendCoinsMessage + } + + let fee = CosmosFee.with { + $0.gas = 140000 + $0.amounts = [CosmosAmount.with { + $0.amount = "1400000000000000" + $0.denom = "aevmos" + }] + } + + let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; + $0.accountNumber = 1982243 + $0.chainID = "evmos_9001-2" + $0.sequence = 0 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .nativeEvmos) + // https://www.mintscan.io/evmos/txs/B05D2047086B158665EC552879270AEF40AEAAFEE7D275B63E9674E3CC4C4E55 + let expected = """ + {"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CpoBCpcBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEncKLGV2bW9zMXJrMzlkazN3ZmY1bnBzN2VtdWh2M250a24zbnN6NnoyZXJxZnIwEixldm1vczEwazlscnJydWFwOW51OTZteHd3eWUyZjZhNXdhemVoMzNrcTY3ehoZCgZhZXZtb3MSDzIwMDAwMDAwMDAwMDAwMBJ7ClcKTwooL2V0aGVybWludC5jcnlwdG8udjEuZXRoc2VjcDI1NmsxLlB1YktleRIjCiEClHXJ+iPsaTZnuqdsTaabSczP3wWMTcsnumfPvJCC2e0SBAoCCAESIAoaCgZhZXZtb3MSEDE0MDAwMDAwMDAwMDAwMDAQ4MUIGkAz9vh1EutbLrLZmRA4eK72bA6bhfMX0YnhtRl5jeaL3AYmk0qdrwG9XzzleBsZ++IokJIk47cgOOyvEjl92Jhj"} + """ + XCTAssertJSONEqual(output.serialized, expected) + XCTAssertEqual(output.error, "") + } +} diff --git a/swift/Tests/Blockchains/KavaTests.swift b/swift/Tests/Blockchains/KavaTests.swift index b9ed2095a30..f9a5f8957a5 100644 --- a/swift/Tests/Blockchains/KavaTests.swift +++ b/swift/Tests/Blockchains/KavaTests.swift @@ -29,7 +29,7 @@ class KavaTests: XCTestCase { $0.fromAddress = fromAddress $0.toAddress = "kava1hdp298kaz0eezpgl6scsykxljrje3667hmlv0h" $0.amounts = [CosmosAmount.with { - $0.amount = 1000000 + $0.amount = "1000000" $0.denom = "ukava" }] } @@ -41,7 +41,7 @@ class KavaTests: XCTestCase { let fee = CosmosFee.with { $0.gas = 200000 $0.amounts = [CosmosAmount.with { - $0.amount = 100 + $0.amount = "100" $0.denom = "ukava" }] } @@ -101,7 +101,7 @@ class KavaTests: XCTestCase { $0.delegatorAddress = "kava1jf9aaj9myrzsnmpdr7twecnaftzmku2mdpy2a7" $0.validatorAddress = "kavavaloper17498ffqdj49zca4jm7mdf3eevq7uhcsgjvm0uk" $0.amount = CosmosAmount.with { - $0.amount = 1000000 + $0.amount = "1000000" $0.denom = "ukava" } } @@ -113,7 +113,7 @@ class KavaTests: XCTestCase { let fee = CosmosFee.with { $0.gas = 200000 $0.amounts = [CosmosAmount.with { - $0.amount = 100 + $0.amount = "100" $0.denom = "ukava" }] } @@ -179,7 +179,7 @@ class KavaTests: XCTestCase { let fee = CosmosFee.with { $0.amounts = [CosmosAmount.with { - $0.amount = 100 + $0.amount = "100" $0.denom = "ukava" }] $0.gas = 200000 @@ -235,7 +235,7 @@ class KavaTests: XCTestCase { $0.delegatorAddress = "kava1jf9aaj9myrzsnmpdr7twecnaftzmku2mdpy2a7" $0.validatorAddress = "kavavaloper17498ffqdj49zca4jm7mdf3eevq7uhcsgjvm0uk" $0.amount = CosmosAmount.with { - $0.amount = 500000 + $0.amount = "500000" $0.denom = "ukava" } } @@ -247,7 +247,7 @@ class KavaTests: XCTestCase { let fee = CosmosFee.with { $0.gas = 200000 $0.amounts = [CosmosAmount.with { - $0.amount = 100 + $0.amount = "100" $0.denom = "ukava" }] } @@ -307,7 +307,7 @@ class KavaTests: XCTestCase { $0.validatorSrcAddress = "kavavaloper17498ffqdj49zca4jm7mdf3eevq7uhcsgjvm0uk" $0.validatorDstAddress = "kavavaloper14fkp35j5nkvtztmxmsxh88jks6p3w8u7p76zs9" $0.amount = CosmosAmount.with { - $0.amount = 500000 + $0.amount = "500000" $0.denom = "ukava" } } @@ -319,7 +319,7 @@ class KavaTests: XCTestCase { let fee = CosmosFee.with { $0.gas = 200000 $0.amounts = [CosmosAmount.with { - $0.amount = 100 + $0.amount = "100" $0.denom = "ukava" }] } diff --git a/swift/Tests/Blockchains/KuCoinCommunityChainTests.swift b/swift/Tests/Blockchains/KuCoinCommunityChainTests.swift new file mode 100644 index 00000000000..df42dfc52fb --- /dev/null +++ b/swift/Tests/Blockchains/KuCoinCommunityChainTests.swift @@ -0,0 +1,22 @@ +// Copyright © 2017-2021 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 KuCoinCommunityChainTests: XCTestCase { + + func testAddress() { + let key = PrivateKey(data: Data(hexString: "33b85056aabab539bcb68540735ecf054e38bc58b29b751530e2b54ecb4ca564")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: false) + let address = AnyAddress(publicKey: pubkey, coin: .kuCoinCommunityChain) + let addressFromString = AnyAddress(string: "0xE5cA667d795685E9915E5F4b4254ca832eEB398B", coin: .kuCoinCommunityChain)! + + XCTAssertEqual(pubkey.data.hexString, "0413bde18e3329af54d51a24f424fe09a8d7d42c324c07e10e53a6e139cbee80e6288142dec2ed46f7b81dccbb28d6168cdc7b208928730cbeeb911f8db6a707bb") + XCTAssertEqual(address.description, addressFromString.description) + } + +} diff --git a/swift/Tests/Blockchains/NervosTests.swift b/swift/Tests/Blockchains/NervosTests.swift new file mode 100644 index 00000000000..e250869c9ad --- /dev/null +++ b/swift/Tests/Blockchains/NervosTests.swift @@ -0,0 +1,123 @@ +// 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 XCTest +import WalletCore + +class NervosTests: XCTestCase { + + func testDerive() throws { + let wallet = HDWallet(mnemonic: "disorder wolf eager ladder fence renew dynamic idea metal camera bread obscure", passphrase: "")! + let address = wallet.getAddressForCoin(coin: .nervos) + + XCTAssertEqual(address, "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqga4k4agxexsd3zdq0wvrlyumfz7n5r7fsjxtnw8") + } + + func testAddress() throws { + + let key = PrivateKey(data: Data(hexString: "8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: true) + let address = AnyAddress(publicKey: pubkey, coin: .nervos) + let addressFromString = AnyAddress(string: "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8furras980hksatlslfaktks7epf25", coin: .nervos)! + + XCTAssertEqual(address.description, addressFromString.description) + } + + func testSign() throws { + + let string = "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8furras980hksatlslfaktks7epf25" + let address = NervosAddress(string: string)! + let lockScript = NervosScript.with { + $0.codeHash = address.codeHash + $0.hashType = address.hashType + $0.args = address.args + } + + let input = NervosSigningInput.with { + $0.nativeTransfer = NervosNativeTransfer.with { + $0.toAddress = "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02wectaumxn0664yw2jd53lqk4mxg3" + $0.changeAddress = "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqds6ed78yze6eyfyvd537z66ur22c9mmrgz82ama" + $0.amount = 10000000000 + } + $0.byteFee = 1 + $0.cell = [ + NervosCell.with { + $0.capacity = 100000000000 + $0.outPoint = NervosOutPoint.with { + $0.txHash = Data(hexString: "71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3")! + $0.index = 1 + } + $0.lock = lockScript + }, + NervosCell.with { + $0.capacity = 20000000000 + $0.outPoint = NervosOutPoint.with { + $0.txHash = Data(hexString: "71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3")! + $0.index = 0 + } + $0.lock = lockScript + } + ] + $0.privateKey = [ + Data(hexString: "8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb")! + ] + } + + let output: NervosSigningOutput = AnySigner.sign(input: input, coin: .nervos) + let json = """ + { + "cell_deps": [ + { + "dep_type": "dep_group", + "out_point": { + "index": "0x0", + "tx_hash": "0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c" + } + } + ], + "header_deps": [], + "inputs": [ + { + "previous_output": { + "index": "0x0", + "tx_hash": "0x71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3" + }, + "since": "0x0" + } + ], + "outputs": [ + { + "capacity": "0x2540be400", + "lock": { + "args": "0xab201f55b02f53b385f79b34dfad548e549b48fc", + "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + "hash_type": "type" + }, + "type": null + }, + { + "capacity": "0x2540be230", + "lock": { + "args": "0xb0d65be39059d6489231b48f85ad706a560bbd8d", + "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + "hash_type": "type" + }, + "type": null + } + ], + "outputs_data": ["0x", "0x"], + "version": "0x0", + "witnesses": [ + "0x55000000100000005500000055000000410000002a9ef2ad7829e5ea0c7a32735d29a0cb2ec20434f6fd5bf6e29cda56b28e08140156191cbbf80313d3c9cae4b74607acce7b28eb21d52ef058ed8491cdde70b700" + ] + } + """ + + XCTAssertEqual(output.transactionID, "0xf2c32afde7e72011985583873bc16c0a3c01fc01fc161eb4b914fcf84c53cdf8") + XCTAssertEqual(output.error, CommonSigningError.ok) + XCTAssertJSONEqual(output.transactionJson, json) + } +} diff --git a/swift/Tests/Blockchains/OasisTests.swift b/swift/Tests/Blockchains/OasisTests.swift index fc9f4df1bd7..03ad91930f7 100644 --- a/swift/Tests/Blockchains/OasisTests.swift +++ b/swift/Tests/Blockchains/OasisTests.swift @@ -36,6 +36,6 @@ class OasisTests: XCTestCase { let output: OasisSigningOutput = AnySigner.sign(input: input, coin: .oasis) - XCTAssertEqual(output.encoded.hexString, "a273756e747275737465645f7261775f76616c7565585ea4656e6f6e636500666d6574686f64707374616b696e672e5472616e7366657263666565a2636761730066616d6f756e74410064626f6479a262746f5500c73cc001463434915ba3f39751beb7c0905b45eb66616d6f756e744400989680697369676e6174757265a26a7075626c69635f6b6579582093d8f8a455f50527976a8aa87ebde38d5606efa86cb985d3fb466aff37000e3b697369676e61747572655840e331ce731ed819106586152b13cd98ecf3248a880bdc71174ee3d83f6d5f3f8ee8fc34c19b22032f2f1e3e06d382720125d7a517fba9295c813228cc2b63170b") + XCTAssertEqual(output.encoded.hexString, "a2697369676e6174757265a2697369676e617475726558406e51c18c9b2015c9b49414b3307336597f51ff331873d214ce2db81c9651a34d99529ccaa294a39ccd01c6b0bc2c2239d87c624e5ba4840cf99ac8f9283e240c6a7075626c69635f6b6579582093d8f8a455f50527976a8aa87ebde38d5606efa86cb985d3fb466aff37000e3b73756e747275737465645f7261775f76616c7565585ea463666565a2636761730066616d6f756e74410064626f6479a262746f5500c73cc001463434915ba3f39751beb7c0905b45eb66616d6f756e744400989680656e6f6e636500666d6574686f64707374616b696e672e5472616e73666572") } } diff --git a/swift/Tests/Blockchains/OsmosisTests.swift b/swift/Tests/Blockchains/OsmosisTests.swift new file mode 100644 index 00000000000..bd7303dbe51 --- /dev/null +++ b/swift/Tests/Blockchains/OsmosisTests.swift @@ -0,0 +1,63 @@ +// Copyright © 2017-2021 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 OsmosisTests: XCTestCase { + func testAddress() { + let key = PrivateKey(data: Data(hexString: "8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: true) + let address = AnyAddress(publicKey: pubkey, coin: .osmosis) + let addressFromString = AnyAddress(string: "osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5", coin: .osmosis)! + + XCTAssertEqual(pubkey.data.hexString, "02ecef5ce437a302c67f95468de4b31f36e911f467d7e6a52b41c1e13e1d563649") + XCTAssertEqual(address.description, addressFromString.description) + } + + func testSigningTransaction() { + let privateKey = PrivateKey(data: Data(hexString: "8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af")!)! + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .osmosis) + + let sendCoinsMessage = CosmosMessage.Send.with { + $0.fromAddress = fromAddress.description + $0.toAddress = "osmo18s0hdnsllgcclweu9aymw4ngktr2k0rkvn7jmn" + $0.amounts = [CosmosAmount.with { + $0.amount = "99800" + $0.denom = "uosmo" + }] + } + + let message = CosmosMessage.with { + $0.sendCoinsMessage = sendCoinsMessage + } + + let fee = CosmosFee.with { + $0.gas = 200000 + $0.amounts = [CosmosAmount.with { + $0.amount = "200" + $0.denom = "uosmo" + }] + } + + let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; + $0.accountNumber = 124703 + $0.chainID = "osmosis-1" + $0.memo = "" + $0.sequence = 0 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .osmosis) + + XCTAssertJSONEqual(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"Co0BCooBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmoKK29zbW8xbWt5Njljbjhla3R3eTA4NDV2ZWM5dXBzZHBoa3R4dDBlbjk3ZjUSK29zbW8xOHMwaGRuc2xsZ2NjbHdldTlheW13NG5na3RyMmswcmt2bjdqbW4aDgoFdW9zbW8SBTk5ODAwEmQKTgpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQLs71zkN6MCxn+VRo3ksx826RH0Z9fmpStBweE+HVY2SRIECgIIARISCgwKBXVvc21vEgMyMDAQwJoMGkAMY//Md5GRUR4lVZhk558hFS3kii9QZYoYKfg4+ac/xgNeyoiEweVDhcmEvlH1orVwjLUOnYs4ly2a/yIurYVj\"}") + XCTAssertEqual(output.error, "") + } +} diff --git a/swift/Tests/Blockchains/PolkadotTests.swift b/swift/Tests/Blockchains/PolkadotTests.swift index f13dce7b122..aae9c432a70 100644 --- a/swift/Tests/Blockchains/PolkadotTests.swift +++ b/swift/Tests/Blockchains/PolkadotTests.swift @@ -137,4 +137,30 @@ class PolkadotTests: XCTestCase { // https://polkadot.subscan.io/extrinsic/4999416-1 XCTAssertEqual("0x" + output.encoded.hexString, "0xb501840036092fac541e0e5feda19e537c679b487566d7101141c203ac8322c27e5f076a00c8268c2dfd4074f41d225e12e62e5975ff8debf0f828d31ddbfed6f7593e067fb860298eb12f50294f7ba0f82795809c84fc5cce6fcb36cde4cb1c07edbbb60900140007010300943577") } + + func testChillAndUnbond() { + // real key in 1p test + let key = PrivateKey(data: Data(hexString: "298fcced2b497ed48367261d8340f647b3fca2d9415d57c2e3c5ef90482a2266")!)! + + let input = PolkadotSigningInput.with { + $0.genesisHash = genesisHash + $0.blockHash = Data(hexString: "0x35ba668bb19453e8da6334cadcef2a27c8d4141bfc8b49e78e853c3d73e1ecd0")! + $0.era = PolkadotEra.with { + $0.blockNumber = 10541373 + $0.period = 64 + } + $0.nonce = 6 + $0.specVersion = 9200 + $0.network = .polkadot + $0.transactionVersion = 12 + $0.privateKey = key.data + $0.stakingCall.chillAndUnbond = PolkadotStaking.ChillAndUnbond.with { + $0.value = Data(hexString: "0x1766444D00")! // 10.05 DOT + } + } + let output: PolkadotSigningOutput = AnySigner.sign(input: input, coin: .polkadot) + + // https://polkadot.subscan.io/extrinsic/10541383-2 + XCTAssertEqual("0x" + output.encoded.hexString, "0xd10184008361bd08ddca5fda28b5e2aa84dc2621de566e23e089e555a42194c3eaf2da7900c891ba102db672e378945d74cf7f399226a76b43cab502436971599255451597fc2599902e4b62c7ce85ecc3f653c693fef3232be620984b5bb5bcecbbd7b209d50318001a02080706070207004d446617") + } } diff --git a/swift/Tests/Blockchains/RoninTests.swift b/swift/Tests/Blockchains/RoninTests.swift index b5bf4e35a6c..2cb54acb389 100644 --- a/swift/Tests/Blockchains/RoninTests.swift +++ b/swift/Tests/Blockchains/RoninTests.swift @@ -22,11 +22,11 @@ class RoninTests: XCTestCase { $0.nonce = Data(hexString: "0x02")! $0.gasPrice = Data() $0.gasLimit = Data(hexString: "0xca98")! // 51,864 - $0.toAddress = "0x97a9107c1793bc407d6f527b77e7fff4d812bece" // AXS + $0.toAddress = "ronin:97a9107c1793bc407d6f527b77e7fff4d812bece" // AXS $0.privateKey = keyData $0.transaction = EthereumTransaction.with { $0.erc20Transfer = EthereumTransaction.ERC20Transfer.with { - $0.to = "0x47331175b23C2f067204B506CA1501c26731C990" + $0.to = "ronin:47331175b23c2f067204b506ca1501c26731c990" $0.amount = Data(hexString: "0x016345785d8a0000")! // 0.1 AXS } } diff --git a/swift/Tests/Blockchains/SmartBitcoinCashTests.swift b/swift/Tests/Blockchains/SmartBitcoinCashTests.swift new file mode 100644 index 00000000000..131f13d14b6 --- /dev/null +++ b/swift/Tests/Blockchains/SmartBitcoinCashTests.swift @@ -0,0 +1,22 @@ +// Copyright © 2017-2021 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 SmartBitcoinCashTests: XCTestCase { + + func testAddress() { + let key = PrivateKey(data: Data(hexString: "ab4accc9310d90a61fc354d8f353bca4a2b3c0590685d3eb82d0216af3badddc")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: false) + let address = AnyAddress(publicKey: pubkey, coin: .smartBitcoinCash) + let addressFromString = AnyAddress(string: "0xA3Dcd899C0f3832DFDFed9479a9d828c6A4EB2A7", coin: .smartBitcoinCash)! + + XCTAssertEqual(pubkey.data.hexString, "0448a9ffac8022f1c7eb5253746e24d11d9b6b2737c0aecd48335feabb95a179916b1f3a97bed6740a85a2d11c663d38566acfb08af48a47ce0c835c65c9b23d0d") + XCTAssertEqual(address.description, addressFromString.description) + } + +} diff --git a/swift/Tests/Blockchains/THORChainSwapTests.swift b/swift/Tests/Blockchains/THORChainSwapTests.swift new file mode 100644 index 00000000000..cb51dd73711 --- /dev/null +++ b/swift/Tests/Blockchains/THORChainSwapTests.swift @@ -0,0 +1,99 @@ +// Copyright © 2017-2021 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 XCTest +import WalletCore + +class THORSwapTests: XCTestCase { + + func testSignerEthBnb() throws { + // prepare swap input + let input = THORChainSwapSwapInput.with { + $0.fromChain = .eth + $0.fromAddress = "0xb9f5771c27664bf2282d98e09d7f50cec7cb01a7" + $0.toAsset = THORChainSwapAsset.with { + $0.chain = .bnb + $0.symbol = "BNB" + $0.tokenID = "" + } + $0.toAddress = "bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx" + $0.vaultAddress = "0x1091c4De6a3cF09CdA00AbDAeD42c7c3B69C83EC" + $0.routerAddress = "0x42A5Ed456650a09Dc10EBc6361A7480fDd61f27B" + $0.fromAmount = "50000000000000000" + $0.toAmountLimit = "600003" + } + + // serialize input + let inputSerialized = try input.serializedData() + XCTAssertEqual(inputSerialized.hexString, "0802122a3078623966353737316332373636346266323238326439386530396437663530636563376362303161371a0708031203424e42222a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372782a2a307831303931633444653661336346303943644130304162444165443432633763334236394338334543322a3078343241354564343536363530613039446331304542633633363141373438306644643631663237423a1135303030303030303030303030303030304206363030303033") + + // invoke swap + let outputData = THORChainSwap.buildSwap(input: inputSerialized) + XCTAssertEqual(outputData.count, 311) + + // parse result in proto + let outputProto = try THORChainSwapSwapOutput(serializedData: outputData) + XCTAssertEqual(outputProto.fromChain, TW_THORChainSwap_Proto_Chain.eth) + XCTAssertEqual(outputProto.toChain, TW_THORChainSwap_Proto_Chain.bnb) + XCTAssertEqual(outputProto.error.code, TW_THORChainSwap_Proto_ErrorCode.ok) + var txInput = outputProto.ethereum + + // set few fields before signing + txInput.chainID = Data(hexString: "01")! + txInput.nonce = Data(hexString: "03")! + txInput.gasPrice = Data(hexString: "06FC23AC00")! + txInput.gasLimit = Data(hexString: "013880")! + txInput.privateKey = Data(hexString: "4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904")! + + // sign and encode resulting input + let output: EthereumSigningOutput = AnySigner.sign(input: txInput, coin: .ethereum) + + XCTAssertEqual(output.encoded.hexString, "f90151038506fc23ac00830138809442a5ed456650a09dc10ebc6361a7480fdd61f27b87b1a2bc2ec50000b8e41fece7b40000000000000000000000001091c4de6a3cf09cda00abdaed42c7c3b69c83ec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b1a2bc2ec500000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000003e535741503a424e422e424e423a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a363030303033000025a06ae104be3201baca38315352f81fac70ca4dd47339981914e64e91149813e780a066a3f0b2c44ddf5a96a38481274f623f552a593d723237d6742185f4885c0064") + } + + func testSignerBnbBtc() throws { + // prepare swap input + let input = THORChainSwapSwapInput.with { + $0.fromChain = .bnb + $0.fromAddress = "bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx" + $0.toAsset = THORChainSwapAsset.with { + $0.chain = .btc + $0.symbol = "BTC" + $0.tokenID = "" + } + $0.toAddress = "bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8" + $0.vaultAddress = "bnb1n9esxuw8ca7ts8l6w66kdh800s09msvul6vlse" + $0.routerAddress = "" + $0.fromAmount = "10000000" + $0.toAmountLimit = "10000000" + } + + // serialize input + let inputSerialized = try input.serializedData() + XCTAssertEqual(inputSerialized.hexString, "0803122a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372781a0708011203425443222a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070382a2a626e62316e396573787577386361377473386c367736366b64683830307330396d7376756c36766c73653a08313030303030303042083130303030303030") + + // invoke swap + let outputData = THORChainSwap.buildSwap(input: inputSerialized) + XCTAssertEqual(outputData.count, 149) + + // parse result in proto + let outputProto = try THORChainSwapSwapOutput(serializedData: outputData) + XCTAssertEqual(outputProto.fromChain, TW_THORChainSwap_Proto_Chain.bnb) + XCTAssertEqual(outputProto.toChain, TW_THORChainSwap_Proto_Chain.btc) + XCTAssertEqual(outputProto.error.code, TW_THORChainSwap_Proto_ErrorCode.ok) + var txInput = outputProto.binance + + // set few fields before signing + + txInput.chainID = "Binance-Chain-Nile" + txInput.privateKey = Data(hexString: "bcf8b072560dda05122c99390def2c385ec400e1a93df0657a85cf6b57a715da")! + + // sign and encode resulting input + let output: BinanceSigningOutput = AnySigner.sign(input: txInput, coin: .binance) + + XCTAssertEqual(output.encoded.hexString, "8002f0625dee0a4c2a2c87fa0a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e421080ade20412220a1499730371c7c77cb81ffa76b566dcef7c1e5dc19c120a0a03424e421080ade204126a0a26eb5ae9872103ea4b4bc12dc6f36a28d2c9775e01eef44def32cc70fb54f0e4177b659dbc0e1912404836ee8659caa86771281d3f104424d95977bdedf644ec8585f1674796fde525669a6d446f72da89ee90fb0e064473b0a2159a79630e081592c52948d03d67071a40535741503a4254432e4254433a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070383a3130303030303030") + } +} diff --git a/swift/Tests/Blockchains/THORChainTests.swift b/swift/Tests/Blockchains/THORChainTests.swift index 75aeb4511b6..45d9cb3ac80 100644 --- a/swift/Tests/Blockchains/THORChainTests.swift +++ b/swift/Tests/Blockchains/THORChainTests.swift @@ -24,7 +24,7 @@ class THORChainSignerTests: XCTestCase { let privateKey = PrivateKey(data: Data(hexString: "7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e")!)! - func testSigningTransaction() { + func testJsonModeSigning() { let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) let fromAddress = AnyAddress(publicKey: publicKey, coin: .thorchain) @@ -32,7 +32,7 @@ class THORChainSignerTests: XCTestCase { $0.fromAddress = fromAddress.description $0.toAddress = "thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn" $0.amounts = [CosmosAmount.with { - $0.amount = 10000000 + $0.amount = "10000000" $0.denom = "rune" }] } @@ -44,7 +44,7 @@ class THORChainSignerTests: XCTestCase { let fee = CosmosFee.with { $0.gas = 200000 $0.amounts = [CosmosAmount.with { - $0.amount = 2000000 + $0.amount = "2000000" $0.denom = "rune" }] } @@ -61,49 +61,94 @@ class THORChainSignerTests: XCTestCase { let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .thorchain) - let expectedJSON: String = -""" -{ - "mode": "block", - "tx": { - "fee": { - "amount": [ - { - "amount": "2000000", - "denom": "rune" - } - ], - "gas": "200000" - }, - "memo": "", - "msg": [ - { - "type": "thorchain/MsgSend", - "value": { + let expectedJSON: String = """ + { + "mode": "block", + "tx": { + "fee": { "amount": [ { - "amount": "10000000", + "amount": "2000000", "denom": "rune" } ], - "from_address": "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r", - "to_address": "thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn" - } - } - ], - "signatures": [ - { - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A+2Zfjls9CkvX85aQrukFZnM1dluMTFUp8nqcEneMXx3" + "gas": "200000" }, - "signature": "ZPhcYubhAd6iz/pBrtLfSJaK04ISnEo+jBFvFFzoToMJA9NGhhCFmsmXMQ1AtoJ6C1aylvUnck93A7ork8ZzEQ==" + "memo": "", + "msg": [ + { + "type": "thorchain/MsgSend", + "value": { + "amount": [ + { + "amount": "10000000", + "denom": "rune" + } + ], + "from_address": "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r", + "to_address": "thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn" + } + } + ], + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A+2Zfjls9CkvX85aQrukFZnM1dluMTFUp8nqcEneMXx3" + }, + "signature": "ZPhcYubhAd6iz/pBrtLfSJaK04ISnEo+jBFvFFzoToMJA9NGhhCFmsmXMQ1AtoJ6C1aylvUnck93A7ork8ZzEQ==" + } + ] } - ] - } -} -""" + } + """ XCTAssertJSONEqual(expectedJSON, output.json) } + + func testProtobufModeSigning() { + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .thorchain) + + let sendCoinsMessage = CosmosMessage.THORChainSend.with { + $0.fromAddress = fromAddress.data + $0.toAddress = AnyAddress(string: "thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn", coin: .thorchain)!.data + $0.amounts = [CosmosAmount.with { + $0.amount = "38000000" + $0.denom = "rune" + }] + } + + let message = CosmosMessage.with { + $0.thorchainSendMessage = sendCoinsMessage + } + + let fee = CosmosFee.with { + $0.gas = 2500000 + $0.amounts = [CosmosAmount.with { + $0.amount = "200" + $0.denom = "rune" + }] + } + + let input = CosmosSigningInput.with { + $0.accountNumber = 593 + $0.chainID = "thorchain-mainnet-v1" + $0.sequence = 21 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + $0.signingMode = .protobuf + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .thorchain) + let expectedJSON = """ + { + "mode": "BROADCAST_MODE_BLOCK", + "tx_bytes": "ClIKUAoOL3R5cGVzLk1zZ1NlbmQSPgoUFSLnZ9tusZcIsAOAKb+9YHvJvQ4SFMqGRZ+wBVHH30JUDF54aRksgzrbGhAKBHJ1bmUSCDM4MDAwMDAwEmYKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQPtmX45bPQpL1/OWkK7pBWZzNXZbjExVKfJ6nBJ3jF8dxIECgIIARgVEhIKCwoEcnVuZRIDMjAwEKDLmAEaQKZtS3ATa26OOGvqdKm14ZbHeNfkPtIajXi5MkZ5XaX2SWOeX+YnCPZ9TxF9Jj5cVIo71m55xq4hVL3yDbRe89g=" + } + """ + + XCTAssertJSONEqual(expectedJSON, output.serialized) + } } diff --git a/swift/Tests/Blockchains/TerraClassicTests.swift b/swift/Tests/Blockchains/TerraClassicTests.swift new file mode 100644 index 00000000000..bb1e8c81e21 --- /dev/null +++ b/swift/Tests/Blockchains/TerraClassicTests.swift @@ -0,0 +1,605 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +import XCTest +import WalletCore + +class TerraClassicTests: XCTestCase { + + let privateKey = PrivateKey(data: Data(hexString: "1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6")!)! + + func testAddress() { + let address = CoinType.terra.deriveAddress(privateKey: privateKey) + + XCTAssertEqual(address, "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe") + XCTAssertTrue(CoinType.terra.validate(address: "terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms")) + XCTAssertTrue(CoinType.terra.validate(address: "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk")) + XCTAssertFalse(CoinType.terra.validate(address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02")) + } + + func testRawJSON() { + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .terra).description + + let message = CosmosMessage.with { + $0.rawJsonMessage = CosmosMessage.RawJSON.with { + $0.type = "bank/MsgSend" + $0.value = """ + { + "amount": [{ + "amount": "1000000", + "denom": "uluna" + }], + "from_address": "\(fromAddress)", + "to_address": "terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms" + } + """ + } + } + + let fee = CosmosFee.with { + $0.gas = 200000 + $0.amounts = [CosmosAmount.with { + $0.amount = "3000" + $0.denom = "uluna" + }] + } + + let input = CosmosSigningInput.with { + $0.accountNumber = 158 + $0.chainID = "soju-0013" + $0.memo = "" + $0.sequence = 0 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) + + let expectedJSON: String = +""" +{ + "mode": "block", + "tx": { + "msg": [{ + "type": "bank/MsgSend", + "value": { + "from_address": "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe", + "to_address": "terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms", + "amount": [{ + "denom": "uluna", + "amount": "1000000" + }] + } + }], + "fee": { + "amount": [{ + "denom": "uluna", + "amount": "3000" + }], + "gas": "200000" + }, + "signatures": [{ + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" + }, + "signature": "KPdiVsKpY12JG/VKEJVa/FpMKclxlS0qNNG6VOAypj10R5vY5UX5IgRJET1zNYnH0wvcXxfNXV+s8jtwN2UXiQ==" + }], + "memo": "" + } +} +""" + XCTAssertJSONEqual(expectedJSON, output.json) + } + + func testSigningTransaction() { + // https://finder.terra.money/soju-0013/tx/1403B07F2D218BCE961CB92D83377A924FEDB54C1F0B62E25C8B93B63470EBF7 + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .terra).description + + let message = CosmosMessage.with { + $0.sendCoinsMessage = CosmosMessage.Send.with { + $0.fromAddress = fromAddress + $0.toAddress = "terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms" + $0.amounts = [CosmosAmount.with { + $0.amount = "1000000" + $0.denom = "uluna" + }] + $0.typePrefix = "bank/MsgSend" + } + } + + let fee = CosmosFee.with { + $0.gas = 200000 + $0.amounts = [CosmosAmount.with { + $0.amount = "3000" + $0.denom = "uluna" + }] + } + + let input = CosmosSigningInput.with { + $0.accountNumber = 158 + $0.chainID = "soju-0013" + $0.memo = "" + $0.sequence = 0 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) + + let expectedJSON: String = +""" +{ + "mode": "block", + "tx": { + "msg": [{ + "type": "bank/MsgSend", + "value": { + "from_address": "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe", + "to_address": "terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms", + "amount": [{ + "denom": "uluna", + "amount": "1000000" + }] + } + }], + "fee": { + "amount": [{ + "denom": "uluna", + "amount": "3000" + }], + "gas": "200000" + }, + "signatures": [{ + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" + }, + "signature": "KPdiVsKpY12JG/VKEJVa/FpMKclxlS0qNNG6VOAypj10R5vY5UX5IgRJET1zNYnH0wvcXxfNXV+s8jtwN2UXiQ==" + }], + "memo": "" + } +} +""" + XCTAssertJSONEqual(expectedJSON, output.json) + } + + func testStaking() { + // https://finder.terra.money/soju-0013/tx/4C0A6690ECB601ACB42D3ECAF4C24C0555B5E32E45B09C3B1607B144CD191F87 + let stakeMessage = CosmosMessage.Delegate.with { + $0.delegatorAddress = "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe" + $0.validatorAddress = "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" + $0.amount = CosmosAmount.with { + $0.amount = "1000000" + $0.denom = "uluna" + } + $0.typePrefix = "staking/MsgDelegate" + } + + let message = CosmosMessage.with { + $0.stakeMessage = stakeMessage + } + + let fee = CosmosFee.with { + $0.gas = 200000 + $0.amounts = [CosmosAmount.with { + $0.amount = "3000" + $0.denom = "uluna" + }] + } + + let input = CosmosSigningInput.with { + $0.accountNumber = 158 + $0.chainID = "soju-0013" + $0.memo = "" + $0.sequence = 1 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) + + let expectedJSON = """ +{ + "mode": "block", + "tx": { + "fee": { + "amount": [{ + "amount": "3000", + "denom": "uluna" + }], + "gas": "200000" + }, + "memo": "", + "msg": [{ + "type": "staking/MsgDelegate", + "value": { + "amount": { + "amount": "1000000", + "denom": "uluna" + }, + "delegator_address": "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe", + "validator_address": "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" + } + }], + "signatures": [{ + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" + }, + "signature": "F8UJxbkqa0j6dYTk8PymrudBKI3WYhZImRxMFCw0ecFCmPGgNTg7yfpKZo6K6JtnoJaP7bQ4db5e4wnhMCJyAQ==" + }] + } +} +""" + XCTAssertJSONEqual(expectedJSON, output.json) + } + + func testWithdraw() { + // https://finder.terra.money/soju-0013/tx/AE0E4F2B254449950A3A7F41FABCE0B3C846D70F809380313CE3BB323E490BBD + let withdrawMessage = CosmosMessage.WithdrawDelegationReward.with { + $0.delegatorAddress = "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe" + $0.validatorAddress = "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" + $0.typePrefix = "distribution/MsgWithdrawDelegationReward" + } + + let message = CosmosMessage.with { + $0.withdrawStakeRewardMessage = withdrawMessage + } + + let fee = CosmosFee.with { + $0.amounts = [CosmosAmount.with { + $0.amount = "3000" + $0.denom = "uluna" + }] + $0.gas = 200000 + } + + let input = CosmosSigningInput.with { + $0.accountNumber = 158 + $0.chainID = "soju-0013" + $0.memo = "" + $0.sequence = 2 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) + + let expectedJSON = """ +{ + "mode": "block", + "tx": { + "fee": { + "amount": [{ + "amount": "3000", + "denom": "uluna" + }], + "gas": "200000" + }, + "memo": "", + "msg": [{ + "type": "distribution/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe", + "validator_address": "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" + } + }], + "signatures": [{ + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" + }, + "signature": "Kfwi1uJplzLucXDyQZsJI9v8lMFJFUBLD46+MpwBwYwPJgqPRzSOfyjRpmNou0G/Qe1hbsGEgqb85FQpsgLz+g==" + }] + } +} +""" + XCTAssertJSONEqual(expectedJSON, output.json) + } + + func testUndelegate() { + // https://finder.terra.money/soju-0013/tx/FCF50C180303AECA97F916D0CE0E0937BA4C4D2F6777FFF2AA0D52A9DAF9CCBA + let unstakeMessage = CosmosMessage.Undelegate.with { + $0.delegatorAddress = "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe" + $0.validatorAddress = "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" + $0.amount = CosmosAmount.with { + $0.amount = "500000" + $0.denom = "uluna" + } + $0.typePrefix = "staking/MsgUndelegate" + } + + let message = CosmosMessage.with { + $0.unstakeMessage = unstakeMessage + } + + let fee = CosmosFee.with { + $0.gas = 200000 + $0.amounts = [CosmosAmount.with { + $0.amount = "3000" + $0.denom = "uluna" + }] + } + + let input = CosmosSigningInput.with { + $0.accountNumber = 158 + $0.chainID = "soju-0013" + $0.memo = "" + $0.sequence = 3 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) + + let expectedJSON = """ +{ + "mode": "block", + "tx": { + "fee": { + "amount": [{ + "amount": "3000", + "denom": "uluna" + }], + "gas": "200000" + }, + "memo": "", + "msg": [{ + "type": "staking/MsgUndelegate", + "value": { + "amount": { + "amount": "500000", + "denom": "uluna" + }, + "delegator_address": "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe", + "validator_address": "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" + } + }], + "signatures": [{ + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" + }, + "signature": "THf/RxsBr2EhHE2OMHLXfv+qSP9ORbvHgo4OSOS2P95xxGH73wW+t1zIl9cGlIVvcoChwaCg5/iEuvbgXUWpNw==" + }] + } +} +""" + XCTAssertJSONEqual(expectedJSON, output.json) + } + + func testRedlegate() { + // https://finder.terra.money/soju-0013/tx/36CE381BDF72AD7407EEE3859E3349F83B723BE9AD407E9D8C38DEE0C4434D29 + let restakeMessage = CosmosMessage.BeginRedelegate.with { + $0.delegatorAddress = "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe" + $0.validatorSrcAddress = "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" + $0.validatorDstAddress = "terravaloper1rhrptnx87ufpv62c7ngt9yqlz2hr77xr9nkcr9" + $0.amount = CosmosAmount.with { + $0.amount = "500000" + $0.denom = "uluna" + } + $0.typePrefix = "staking/MsgBeginRedelegate" + } + + let message = CosmosMessage.with { + $0.restakeMessage = restakeMessage + } + + let fee = CosmosFee.with { + $0.gas = 200000 + $0.amounts = [CosmosAmount.with { + $0.amount = "3000" + $0.denom = "uluna" + }] + } + + let input = CosmosSigningInput.with { + $0.accountNumber = 158 + $0.chainID = "soju-0013" + $0.memo = "" + $0.sequence = 4 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) + + let expectedJSON = """ +{ + "mode": "block", + "tx": { + "fee": { + "amount": [{ + "amount": "3000", + "denom": "uluna" + }], + "gas": "200000" + }, + "memo": "", + "msg": [{ + "type": "staking/MsgBeginRedelegate", + "value": { + "amount": { + "amount": "500000", + "denom": "uluna" + }, + "delegator_address": "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe", + "validator_dst_address": "terravaloper1rhrptnx87ufpv62c7ngt9yqlz2hr77xr9nkcr9", + "validator_src_address": "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" + } + }], + "signatures": [{ + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" + }, + "signature": "HyEpSz48dkebmBFvwh5xDiiZD0jUdOvzTD3ACMw0rOQ9F3JhK2cPaEx6/ZmYNIrdsPqMNkUnHcDYD1o4IztoEg==" + }] + } +} +""" + XCTAssertJSONEqual(expectedJSON, output.json) + } + + func testSigningWasmTerraTransferTxProtobuf() { + let privateKey = PrivateKey(data: Data(hexString: "cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616")!)! + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .terra) + + let wasmTransferMessage = CosmosMessage.WasmTerraExecuteContractTransfer.with { + $0.senderAddress = fromAddress.description + $0.contractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76" // ANC + $0.amount = Data(hexString: "03D090")! // 250000 + $0.recipientAddress = "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp" + } + + let message = CosmosMessage.with { + $0.wasmTerraExecuteContractTransferMessage = wasmTransferMessage + } + + let fee = CosmosFee.with { + $0.gas = 200000 + $0.amounts = [CosmosAmount.with { + $0.amount = "3000" + $0.denom = "uluna" + }] + } + + let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; + $0.accountNumber = 3407705 + $0.chainID = "columbus-5" + $0.memo = "" + $0.sequence = 3 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) + + XCTAssertJSONEqual(output.serialized, +""" +{ + "tx_bytes": "CucBCuQBCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBK5AQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2Glt7InRyYW5zZmVyIjp7ImFtb3VudCI6IjI1MDAwMCIsInJlY2lwaWVudCI6InRlcnJhMWpsZ2FxeTludm4yaGY1dDJzcmE5eWN6OHM3N3duZjlsMGttZ2NwIn19EmcKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQNwZjrHsPmJKW/rXOWfukpQ1+lOHOJW3/IlFFnKLNmsABIECgIIARgDEhMKDQoFdWx1bmESBDMwMDAQwJoMGkAaprIEMLPH2HmFdwFGoaipb2GIyhXt6ombz+WMnG2mORBI6gFt0M+IymYgzZz6w1SW52R922yafDnn7yXfutRw", + "mode": "BROADCAST_MODE_BLOCK" +} +""" + ) + XCTAssertEqual(output.error, "") + } + + func testSigningWasmTerraGenericProtobuf() { + let privateKey = PrivateKey(data: Data(hexString: "cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616")!)! + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .terra) + + let wasmGenericMessage = CosmosMessage.WasmTerraExecuteContractGeneric.with { + $0.senderAddress = fromAddress.description + $0.contractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76" // ANC + $0.executeMsg = """ + {"transfer": { "amount": "250000", "recipient": "terra1d7048csap4wzcv5zm7z6tdqem2agyp9647vdyj" } } + """ + } + + let message = CosmosMessage.with { + $0.wasmTerraExecuteContractGeneric = wasmGenericMessage + } + + let fee = CosmosFee.with { + $0.gas = 200000 + $0.amounts = [CosmosAmount.with { + $0.amount = "3000" + $0.denom = "uluna" + }] + } + + let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; + $0.accountNumber = 3407705 + $0.chainID = "columbus-5" + $0.memo = "" + $0.sequence = 7 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) + + XCTAssertJSONEqual(output.serialized, + """ + { + "tx_bytes": "Cu4BCusBCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBLAAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2GmJ7InRyYW5zZmVyIjogeyAiYW1vdW50IjogIjI1MDAwMCIsICJyZWNpcGllbnQiOiAidGVycmExZDcwNDhjc2FwNHd6Y3Y1em03ejZ0ZHFlbTJhZ3lwOTY0N3ZkeWoiIH0gfRJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYBxITCg0KBXVsdW5hEgQzMDAwEMCaDBpAkPsS7xlSng2LMc9KiD1soN5NLaDcUh8I9okPmsdJN3le1B7yxRGNB4aQfhaRl/8Z0r5vitRT0AWuxDasd8wcFw==", + "mode": "BROADCAST_MODE_BLOCK" + } + """ + ) + XCTAssertEqual(output.error, "") + } + + func testSigningWasmTerraGenericWithCoins() { + let privateKey = PrivateKey(data: Data(hexString: "cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616")!)! + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .terra) + + let wasmGenericMessage = CosmosMessage.WasmTerraExecuteContractGeneric.with { + $0.senderAddress = fromAddress.description + $0.contractAddress = "terra1sepfj7s0aeg5967uxnfk4thzlerrsktkpelm5s" // ANC Market + $0.executeMsg = """ + { "deposit_stable": {} } + """ + $0.coins = [CosmosAmount.with { + $0.amount = "1000" + $0.denom = "uusd" + }] + } + + let message = CosmosMessage.with { + $0.wasmTerraExecuteContractGeneric = wasmGenericMessage + } + + let fee = CosmosFee.with { + $0.gas = 600000 + $0.amounts = [CosmosAmount.with { + $0.amount = "7000" + $0.denom = "uluna" + }] + } + + let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; + $0.accountNumber = 3407705 + $0.chainID = "columbus-5" + $0.memo = "" + $0.sequence = 9 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) + + XCTAssertJSONEqual(output.serialized, + """ + { + "tx_bytes": "CrIBCq8BCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBKEAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMXNlcGZqN3MwYWVnNTk2N3V4bmZrNHRoemxlcnJza3RrcGVsbTVzGhh7ICJkZXBvc2l0X3N0YWJsZSI6IHt9IH0qDAoEdXVzZBIEMTAwMBJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYCRITCg0KBXVsdW5hEgQ3MDAwEMDPJBpAGyi7f1ioY8XV6pjFq1s86Om4++CIUnd3rLHif2iopCcYvX0mLkTlQ6NUERg8nWTYgXcj6fOTO/ptgPuAtv0NWg==", + "mode": "BROADCAST_MODE_BLOCK" + } + """ + ) + XCTAssertEqual(output.error, "") + } + } diff --git a/swift/Tests/Blockchains/TerraTests.swift b/swift/Tests/Blockchains/TerraTests.swift index daa53853b13..02a264beadf 100644 --- a/swift/Tests/Blockchains/TerraTests.swift +++ b/swift/Tests/Blockchains/TerraTests.swift @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,178 +9,124 @@ import WalletCore class TerraTests: XCTestCase { - let privateKey = PrivateKey(data: Data(hexString: "1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6")!)! + let privateKey80e8 = PrivateKey(data: Data(hexString: "80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005")!)! // terra1hsk6jryyqjfhp5dhc55tc9jtckygx0ep37hdd2 + let privateKeycf08 = PrivateKey(data: Data(hexString: "cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616")!)! // terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf + let privateKey1037 = PrivateKey(data: Data(hexString: "1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6")!)! func testAddress() { - let address = CoinType.terra.deriveAddress(privateKey: privateKey) + let address = CoinType.terraV2.deriveAddress(privateKey: privateKey1037) XCTAssertEqual(address, "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe") - XCTAssertTrue(CoinType.terra.validate(address: "terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms")) - XCTAssertTrue(CoinType.terra.validate(address: "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk")) - XCTAssertFalse(CoinType.terra.validate(address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02")) + XCTAssertTrue(CoinType.terraV2.validate(address: "terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms")) + XCTAssertTrue(CoinType.terraV2.validate(address: "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk")) + XCTAssertFalse(CoinType.terraV2.validate(address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02")) } - func testRawJSON() { - let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) - let fromAddress = AnyAddress(publicKey: publicKey, coin: .terra).description + func testSignSendTx() { + let publicKey = privateKey80e8.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .terraV2).description + XCTAssertEqual(fromAddress, "terra1hsk6jryyqjfhp5dhc55tc9jtckygx0ep37hdd2") let message = CosmosMessage.with { - $0.rawJsonMessage = CosmosMessage.RawJSON.with { - $0.type = "bank/MsgSend" - $0.value = """ - { - "amount": [{ - "amount": "1000000", - "denom": "uluna" - }], - "from_address": "\(fromAddress)", - "to_address": "terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms" - } - """ + $0.sendCoinsMessage = CosmosMessage.Send.with { + $0.fromAddress = fromAddress + $0.toAddress = "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp" + $0.amounts = [CosmosAmount.with { + $0.amount = "1000000" + $0.denom = "uluna" + }] } } let fee = CosmosFee.with { $0.gas = 200000 $0.amounts = [CosmosAmount.with { - $0.amount = 3000 + $0.amount = "30000" $0.denom = "uluna" }] } let input = CosmosSigningInput.with { - $0.accountNumber = 158 - $0.chainID = "soju-0013" + $0.signingMode = .protobuf; + $0.accountNumber = 1037 + $0.chainID = "phoenix-1" $0.memo = "" - $0.sequence = 0 + $0.sequence = 1 $0.messages = [message] $0.fee = fee - $0.privateKey = privateKey.data + $0.privateKey = privateKey80e8.data } - let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terraV2) let expectedJSON: String = -""" -{ - "mode": "block", - "tx": { - "msg": [{ - "type": "bank/MsgSend", - "value": { - "from_address": "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe", - "to_address": "terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms", - "amount": [{ - "denom": "uluna", - "amount": "1000000" - }] - } - }], - "fee": { - "amount": [{ - "denom": "uluna", - "amount": "3000" - }], - "gas": "200000" - }, - "signatures": [{ - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" - }, - "signature": "KPdiVsKpY12JG/VKEJVa/FpMKclxlS0qNNG6VOAypj10R5vY5UX5IgRJET1zNYnH0wvcXxfNXV+s8jtwN2UXiQ==" - }], - "memo": "" - } -} -""" - XCTAssertJSONEqual(expectedJSON, output.json) + """ + { + "tx_bytes": "CpEBCo4BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm4KLHRlcnJhMWhzazZqcnl5cWpmaHA1ZGhjNTV0YzlqdGNreWd4MGVwMzdoZGQyEix0ZXJyYTFqbGdhcXk5bnZuMmhmNXQyc3JhOXljejhzNzd3bmY5bDBrbWdjcBoQCgV1bHVuYRIHMTAwMDAwMBJoClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECVyhuw/N9M1V7u6oACyd0SskCOqmWfK51oYHR/5H6ncUSBAoCCAEYARIUCg4KBXVsdW5hEgUzMDAwMBDAmgwaQPh0C3rjzdixIUiyPx3FlWAxzbKILNAcSRVeQnaTl1vsI5DEfYa2oYlUBLqyilcMCcU/iaJLhex30No2ak0Zn1Q=", + "mode": "BROADCAST_MODE_BLOCK" + } + """ + XCTAssertJSONEqual(expectedJSON, output.serialized) + XCTAssertEqual(output.error, "") } - func testSigningTransaction() { - // https://finder.terra.money/soju-0013/tx/1403B07F2D218BCE961CB92D83377A924FEDB54C1F0B62E25C8B93B63470EBF7 - let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) - let fromAddress = AnyAddress(publicKey: publicKey, coin: .terra).description + func testSignWasmTransferTx() { + let publicKey = privateKeycf08.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .terraV2).description + XCTAssertEqual(fromAddress, "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf") + + let wasmTransferMessage = CosmosMessage.WasmExecuteContractTransfer.with { + $0.senderAddress = fromAddress.description + $0.contractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76" + $0.amount = Data(hexString: "03D090")! // 250000 + $0.recipientAddress = "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp" + } let message = CosmosMessage.with { - $0.sendCoinsMessage = CosmosMessage.Send.with { - $0.fromAddress = fromAddress - $0.toAddress = "terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms" - $0.amounts = [CosmosAmount.with { - $0.amount = 1000000 - $0.denom = "uluna" - }] - $0.typePrefix = "bank/MsgSend" - } + $0.wasmExecuteContractTransferMessage = wasmTransferMessage } let fee = CosmosFee.with { $0.gas = 200000 $0.amounts = [CosmosAmount.with { - $0.amount = 3000 + $0.amount = "3000" $0.denom = "uluna" }] } let input = CosmosSigningInput.with { - $0.accountNumber = 158 - $0.chainID = "soju-0013" + $0.signingMode = .protobuf; + $0.accountNumber = 3407705 + $0.chainID = "phoenix-1" $0.memo = "" - $0.sequence = 0 + $0.sequence = 3 $0.messages = [message] $0.fee = fee - $0.privateKey = privateKey.data + $0.privateKey = privateKeycf08.data } - let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terraV2) - let expectedJSON: String = -""" -{ - "mode": "block", - "tx": { - "msg": [{ - "type": "bank/MsgSend", - "value": { - "from_address": "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe", - "to_address": "terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms", - "amount": [{ - "denom": "uluna", - "amount": "1000000" - }] - } - }], - "fee": { - "amount": [{ - "denom": "uluna", - "amount": "3000" - }], - "gas": "200000" - }, - "signatures": [{ - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" - }, - "signature": "KPdiVsKpY12JG/VKEJVa/FpMKclxlS0qNNG6VOAypj10R5vY5UX5IgRJET1zNYnH0wvcXxfNXV+s8jtwN2UXiQ==" - }], - "memo": "" - } -} -""" - XCTAssertJSONEqual(expectedJSON, output.json) + XCTAssertJSONEqual(output.serialized, + """ + { + "tx_bytes": "CuUBCuIBCiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QSuQEKLHRlcnJhMTh3dWtwODRkcTIyN3d1NG1naDBqbTZuOW5sbmo2cnM4MnBwOXdmEix0ZXJyYTE0ejU2bDBmcDJsc2Y4Nnp5M2h0eTJ6NDdlemtobnRodHI5eXE3NhpbeyJ0cmFuc2ZlciI6eyJhbW91bnQiOiIyNTAwMDAiLCJyZWNpcGllbnQiOiJ0ZXJyYTFqbGdhcXk5bnZuMmhmNXQyc3JhOXljejhzNzd3bmY5bDBrbWdjcCJ9fRJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYAxITCg0KBXVsdW5hEgQzMDAwEMCaDBpAiBGbQaj+jsXE6/FssD3fC77QOxpli9GqsPea+KoNyMIEgVj89Hii+oU1bAEQS4qV0SaE2V6RNy24uCcFTIRbcQ==", + "mode": "BROADCAST_MODE_BLOCK" + } + """ + ) + XCTAssertEqual(output.error, "") } - func testStaking() { - // https://finder.terra.money/soju-0013/tx/4C0A6690ECB601ACB42D3ECAF4C24C0555B5E32E45B09C3B1607B144CD191F87 + func testSignStakeTx() throws { + let stakeMessage = CosmosMessage.Delegate.with { - $0.delegatorAddress = "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe" - $0.validatorAddress = "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" + $0.delegatorAddress = "terra1ncfyexz3nrrdru37ahqpp4wen48v7p5nany478" + $0.validatorAddress = "terravaloper1ekq8xuypdxtf3nfmffmydnhny59pjuy0p8wpn7" $0.amount = CosmosAmount.with { - $0.amount = 1000000 + $0.amount = "1000000" // 5 Luna $0.denom = "uluna" } - $0.typePrefix = "staking/MsgDelegate" } let message = CosmosMessage.with { @@ -188,268 +134,84 @@ class TerraTests: XCTestCase { } let fee = CosmosFee.with { - $0.gas = 200000 + $0.gas = 254682 $0.amounts = [CosmosAmount.with { - $0.amount = 3000 + $0.amount = "38203" $0.denom = "uluna" }] } let input = CosmosSigningInput.with { - $0.accountNumber = 158 - $0.chainID = "soju-0013" - $0.memo = "" - $0.sequence = 1 + $0.signingMode = .protobuf; + $0.accountNumber = 127185 + $0.chainID = "phoenix-1" + $0.sequence = 6 $0.messages = [message] $0.fee = fee - $0.privateKey = privateKey.data - } - - let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) - - let expectedJSON = """ -{ - "mode": "block", - "tx": { - "fee": { - "amount": [{ - "amount": "3000", - "denom": "uluna" - }], - "gas": "200000" - }, - "memo": "", - "msg": [{ - "type": "staking/MsgDelegate", - "value": { - "amount": { - "amount": "1000000", - "denom": "uluna" - }, - "delegator_address": "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe", - "validator_address": "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" - } - }], - "signatures": [{ - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" - }, - "signature": "F8UJxbkqa0j6dYTk8PymrudBKI3WYhZImRxMFCw0ecFCmPGgNTg7yfpKZo6K6JtnoJaP7bQ4db5e4wnhMCJyAQ==" - }] - } -} -""" - XCTAssertJSONEqual(expectedJSON, output.json) - } - - func testWithdraw() { - // https://finder.terra.money/soju-0013/tx/AE0E4F2B254449950A3A7F41FABCE0B3C846D70F809380313CE3BB323E490BBD - let withdrawMessage = CosmosMessage.WithdrawDelegationReward.with { - $0.delegatorAddress = "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe" - $0.validatorAddress = "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" - $0.typePrefix = "distribution/MsgWithdrawDelegationReward" + $0.privateKey = privateKey1037.data // real key is terra: define... } - let message = CosmosMessage.with { - $0.withdrawStakeRewardMessage = withdrawMessage + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terraV2) + let expected = """ + { + "mode": "BROADCAST_MODE_BLOCK", + "tx_bytes": "Cp8BCpwBCiMvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dEZWxlZ2F0ZRJ1Cix0ZXJyYTFuY2Z5ZXh6M25ycmRydTM3YWhxcHA0d2VuNDh2N3A1bmFueTQ3OBIzdGVycmF2YWxvcGVyMWVrcTh4dXlwZHh0ZjNuZm1mZm15ZG5obnk1OXBqdXkwcDh3cG43GhAKBXVsdW5hEgcxMDAwMDAwEmgKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQNd8YVWZSHWp4AjGe4G4aKOl7d3Lftf3RPKbwV1UYlo5BIECgIIARgGEhQKDgoFdWx1bmESBTM4MjAzENrFDxpAamKyAvWhWCv0nUKz7yiYETpkZETflDvfe1vmuFIy31g+s0u1cgLNo+7jBRXRuzYJXukigtwoLUrxY/C8rowiJw==" } + """ - let fee = CosmosFee.with { - $0.amounts = [CosmosAmount.with { - $0.amount = 3000 - $0.denom = "uluna" - }] - $0.gas = 200000 - } + // https://finder.terra.money/mainnet/tx/4441c65f24783b8f59b20b1b80ee43f1f4f6ff827597d87b6bbc94982b45be0c + XCTAssertJSONEqual(output.serialized, expected) + XCTAssertEqual(output.error, "") + } - let input = CosmosSigningInput.with { - $0.accountNumber = 158 - $0.chainID = "soju-0013" - $0.memo = "" - $0.sequence = 2 - $0.messages = [message] - $0.fee = fee - $0.privateKey = privateKey.data - } + func testSignClaimRewards() throws { + let delegator = "terra1ncfyexz3nrrdru37ahqpp4wen48v7p5nany478" + let validators = [ + "terravaloper1ekq8xuypdxtf3nfmffmydnhny59pjuy0p8wpn7" + ] - let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) - - let expectedJSON = """ -{ - "mode": "block", - "tx": { - "fee": { - "amount": [{ - "amount": "3000", - "denom": "uluna" - }], - "gas": "200000" - }, - "memo": "", - "msg": [{ - "type": "distribution/MsgWithdrawDelegationReward", - "value": { - "delegator_address": "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe", - "validator_address": "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" + let withdrawals = validators.map { validator in + CosmosMessage.WithdrawDelegationReward.with { + $0.delegatorAddress = delegator + $0.validatorAddress = validator } - }], - "signatures": [{ - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" - }, - "signature": "Kfwi1uJplzLucXDyQZsJI9v8lMFJFUBLD46+MpwBwYwPJgqPRzSOfyjRpmNou0G/Qe1hbsGEgqb85FQpsgLz+g==" - }] - } -} -""" - XCTAssertJSONEqual(expectedJSON, output.json) - } - - func testUndelegate() { - // https://finder.terra.money/soju-0013/tx/FCF50C180303AECA97F916D0CE0E0937BA4C4D2F6777FFF2AA0D52A9DAF9CCBA - let unstakeMessage = CosmosMessage.Undelegate.with { - $0.delegatorAddress = "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe" - $0.validatorAddress = "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" - $0.amount = CosmosAmount.with { - $0.amount = 500000 - $0.denom = "uluna" + }.map { withdraw in + CosmosMessage.with { + $0.withdrawStakeRewardMessage = withdraw } - $0.typePrefix = "staking/MsgUndelegate" - } - - let message = CosmosMessage.with { - $0.unstakeMessage = unstakeMessage } let fee = CosmosFee.with { - $0.gas = 200000 $0.amounts = [CosmosAmount.with { - $0.amount = 3000 + $0.amount = "29513" $0.denom = "uluna" }] + $0.gas = 196749 } let input = CosmosSigningInput.with { - $0.accountNumber = 158 - $0.chainID = "soju-0013" - $0.memo = "" - $0.sequence = 3 - $0.messages = [message] + $0.signingMode = .protobuf; $0.fee = fee - $0.privateKey = privateKey.data - } - - let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) - - let expectedJSON = """ -{ - "mode": "block", - "tx": { - "fee": { - "amount": [{ - "amount": "3000", - "denom": "uluna" - }], - "gas": "200000" - }, - "memo": "", - "msg": [{ - "type": "staking/MsgUndelegate", - "value": { - "amount": { - "amount": "500000", - "denom": "uluna" - }, - "delegator_address": "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe", - "validator_address": "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" - } - }], - "signatures": [{ - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" - }, - "signature": "THf/RxsBr2EhHE2OMHLXfv+qSP9ORbvHgo4OSOS2P95xxGH73wW+t1zIl9cGlIVvcoChwaCg5/iEuvbgXUWpNw==" - }] - } -} -""" - XCTAssertJSONEqual(expectedJSON, output.json) - } - - func testRedlegate() { - // https://finder.terra.money/soju-0013/tx/36CE381BDF72AD7407EEE3859E3349F83B723BE9AD407E9D8C38DEE0C4434D29 - let restakeMessage = CosmosMessage.BeginRedelegate.with { - $0.delegatorAddress = "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe" - $0.validatorSrcAddress = "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" - $0.validatorDstAddress = "terravaloper1rhrptnx87ufpv62c7ngt9yqlz2hr77xr9nkcr9" - $0.amount = CosmosAmount.with { - $0.amount = 500000 - $0.denom = "uluna" - } - $0.typePrefix = "staking/MsgBeginRedelegate" - } - - let message = CosmosMessage.with { - $0.restakeMessage = restakeMessage + $0.accountNumber = 127185 + $0.chainID = "phoenix-1" + $0.sequence = 5 + $0.messages = withdrawals + $0.fee = fee + $0.privateKey = privateKey1037.data // real key is terra: define... } - let fee = CosmosFee.with { - $0.gas = 200000 - $0.amounts = [CosmosAmount.with { - $0.amount = 3000 - $0.denom = "uluna" - }] - } + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terraV2) - let input = CosmosSigningInput.with { - $0.accountNumber = 158 - $0.chainID = "soju-0013" - $0.memo = "" - $0.sequence = 4 - $0.messages = [message] - $0.fee = fee - $0.privateKey = privateKey.data + let expected = """ + { + "mode": "BROADCAST_MODE_BLOCK", + "tx_bytes": "CqEBCp4BCjcvY29zbW9zLmRpc3RyaWJ1dGlvbi52MWJldGExLk1zZ1dpdGhkcmF3RGVsZWdhdG9yUmV3YXJkEmMKLHRlcnJhMW5jZnlleHozbnJyZHJ1MzdhaHFwcDR3ZW40OHY3cDVuYW55NDc4EjN0ZXJyYXZhbG9wZXIxZWtxOHh1eXBkeHRmM25mbWZmbXlkbmhueTU5cGp1eTBwOHdwbjcSaApQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjkEgQKAggBGAUSFAoOCgV1bHVuYRIFMjk1MTMQjYEMGkA/bh2va6RRZvkSLnej84dJgSSvbgcHgYDbkRt8wDge03W747BZcuBcg/U5EuE7zBqSJrKUTZl7oUCp//rYlJKV" } + """ - let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) - - let expectedJSON = """ -{ - "mode": "block", - "tx": { - "fee": { - "amount": [{ - "amount": "3000", - "denom": "uluna" - }], - "gas": "200000" - }, - "memo": "", - "msg": [{ - "type": "staking/MsgBeginRedelegate", - "value": { - "amount": { - "amount": "500000", - "denom": "uluna" - }, - "delegator_address": "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe", - "validator_dst_address": "terravaloper1rhrptnx87ufpv62c7ngt9yqlz2hr77xr9nkcr9", - "validator_src_address": "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" - } - }], - "signatures": [{ - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" - }, - "signature": "HyEpSz48dkebmBFvwh5xDiiZD0jUdOvzTD3ACMw0rOQ9F3JhK2cPaEx6/ZmYNIrdsPqMNkUnHcDYD1o4IztoEg==" - }] - } -} -""" - XCTAssertJSONEqual(expectedJSON, output.json) + // https://finder.terra.money/mainnet/tx/0e62170ed5407992251d7e161f23c3467e1bea54c7f601953953bdabc7f0c30c + XCTAssertJSONEqual(output.serialized, expected) + XCTAssertEqual(output.error, "") } + } diff --git a/swift/Tests/Blockchains/TezosTests.swift b/swift/Tests/Blockchains/TezosTests.swift index a9416b97322..67a26a6c971 100644 --- a/swift/Tests/Blockchains/TezosTests.swift +++ b/swift/Tests/Blockchains/TezosTests.swift @@ -26,6 +26,93 @@ class TezosTests: XCTestCase { XCTAssertEqual(address.description, "tz1cG2jx3W4bZFeVGBjsTxUAG8tdpTXtE8PT") } + + public func testSigningFA12() { + let privateKeyData = Data(hexString: "363265a0b3f06661001cab8b4f3ca8fd97ae70608184979cf7300836f57ec2d6")! + + let branch = "BL8euoCWqNCny9AR3AKjnpi38haYMxjei1ZqNHuXMn19JSQnoWp" + var operationList = TezosOperationList() + operationList.branch = branch + + let transactionOperationData = TezosTransactionOperationData.with { + $0.amount = 0 + $0.destination = "KT1EwXFWoG9bYebmF4pYw72aGjwEnBWefgW5" + $0.parameters.fa12Parameters.entrypoint = "transfer"; + $0.parameters.fa12Parameters.from = "tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"; + $0.parameters.fa12Parameters.to = "tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"; + $0.parameters.fa12Parameters.value = "123"; + } + + let transactionOperation = TezosOperation.with { + $0.source = "tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP" + $0.fee = 100000 + $0.counter = 2993172 + $0.gasLimit = 100000 + $0.storageLimit = 0 + $0.kind = .transaction + $0.transactionOperationData = transactionOperationData + } + + operationList.operations = [ transactionOperation ] + + let input = TezosSigningInput.with { + $0.operationList = operationList + $0.privateKey = privateKeyData + } + + let output: TezosSigningOutput = AnySigner.sign(input: input, coin: .tezos) + let expected = "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016c00fe2ce0cccc0214af521ad60c140c5589b4039247a08d0694d8b601a08d0600000145bd8a65cc48159d8ea60a55df735b7c5ad45f0e00ffff087472616e736665720000005907070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555007070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555000bb012914d768155fba2df319a81136e8e3e573b9cadb1676834490c90212615d271da029b6b0531e290e9063bcdb40bea43627af048b18e036f02be2b6b22fc8b307" + + XCTAssertEqual(output.encoded.hexString, expected) + } + + public func testSigningFA2() { + let privateKeyData = Data(hexString: "363265a0b3f06661001cab8b4f3ca8fd97ae70608184979cf7300836f57ec2d6")! + + let branch = "BKvEAX9HXfJZWYfTQbR1C7B3ADoKY6a1aKVRF7qQqvc9hS8Rr3m" + var operationList = TezosOperationList() + operationList.branch = branch + + let transferInfos = TezosTxs.with{ + $0.to = "tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP" + $0.tokenID = "0" + $0.amount = "10" + } + + let transactionOperationData = TezosTransactionOperationData.with { + $0.amount = 0 + $0.destination = "KT1DYk1XDzHredJq1EyNkDindiWDqZyekXGj" + $0.parameters.fa2Parameters.entrypoint = "transfer"; + $0.parameters.fa2Parameters.txsObject = [TezosTxObject.with{ + $0.from = "tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP" + $0.txs = [transferInfos] + }] + } + + + + let transactionOperation = TezosOperation.with { + $0.source = "tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP" + $0.fee = 100000 + $0.counter = 2993173 + $0.gasLimit = 100000 + $0.storageLimit = 0 + $0.kind = .transaction + $0.transactionOperationData = transactionOperationData + } + + operationList.operations = [ transactionOperation ] + + let input = TezosSigningInput.with { + $0.operationList = operationList + $0.privateKey = privateKeyData + } + + let output: TezosSigningOutput = AnySigner.sign(input: input, coin: .tezos) + let expected = "1b1f9345dc9f77bd24b09034d1d2f9a28f02ac837f49db54b8d68341f53dc4b76c00fe2ce0cccc0214af521ad60c140c5589b4039247a08d0695d8b601a08d0600000136767f88850bae28bfb9f46b73c5e87ede4de12700ffff087472616e7366657200000066020000006107070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b5550020000003107070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555007070000000a552d24710d6c59383286700c6c2917b25a6c1fa8b587e593c289dd47704278796792f1e522c1623845ec991e292b0935445e6994850bd03f035a006c5ed93806" + + XCTAssertEqual(output.encoded.hexString, expected) + } public func testSigning() { let privateKeyData = Data(hexString: "c6377a4cc490dc913fc3f0d9cf67d293a32df4547c46cb7e9e33c3b7b97c64d8")! diff --git a/swift/Tests/Blockchains/ZcoinTests.swift b/swift/Tests/Blockchains/ZcoinTests.swift index 0aabb50b9a2..7014d4e274d 100644 --- a/swift/Tests/Blockchains/ZcoinTests.swift +++ b/swift/Tests/Blockchains/ZcoinTests.swift @@ -8,11 +8,11 @@ import XCTest import WalletCore class ZcoinTests: XCTestCase { - let zcoin = CoinType.zcoin + let coin = CoinType.firo func testValidAddresses() { - XCTAssertTrue(zcoin.validate(address: "a4YtT82mWWxHZhLmdx7e5aroW92dqJoRs3")) - XCTAssertTrue(zcoin.validate(address: "4CFa4fnAQvFz4VpikGNzQ9XfCDXMmdk6sh")) + XCTAssertTrue(coin.validate(address: "a4YtT82mWWxHZhLmdx7e5aroW92dqJoRs3")) + XCTAssertTrue(coin.validate(address: "4CFa4fnAQvFz4VpikGNzQ9XfCDXMmdk6sh")) } func testInvalidAddresses() { @@ -23,7 +23,7 @@ class ZcoinTests: XCTestCase { "Xm1iDLBP5tdxTxc6t7uJBCVjC4L2A5vB2J", "TKjdnbJxP4yHeLTHZ86DGnFFY6QhTjuBv2", ] { - XCTAssertFalse(zcoin.validate(address: addr)) + XCTAssertFalse(coin.validate(address: addr)) } } } diff --git a/swift/Tests/CoinAddressDerivationTests.swift b/swift/Tests/CoinAddressDerivationTests.swift index de4c865949b..5dd2b6d4ef4 100644 --- a/swift/Tests/CoinAddressDerivationTests.swift +++ b/swift/Tests/CoinAddressDerivationTests.swift @@ -47,7 +47,7 @@ class CoinAddressDerivationTests: XCTestCase { let expectedResult = "0x3E6FFC80745E6669135a76F4A7ce6BCF02436e04" assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .cardano: - let expectedResult = "addr1snpa4z7ntyfszv7ckquprdw75w4qjqh0qmya9jtkpxxlzxghlqyvv7l0yjamh8fxraw06p3ua8sj2g2gv98v4849s43t9g2999kquuu5egnprk" + let expectedResult = "addr1qyr8jjfnypp95eq74aqzn7ss687ehxclgj7mu6gratmg3mul2040vt35dypp042awzsjk5xm3zr3zm5qh7454uwdv08s84ray2" assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .cosmos: let expectedResult = "cosmos142j9u5eaduzd7faumygud6ruhdwme98qsy2ekn" @@ -74,14 +74,31 @@ class CoinAddressDerivationTests: XCTestCase { .smartChain, .polygon, .optimism, + .zksync, .arbitrum, .ecochain, .avalancheCChain, .xdai, .fantom, - .ronin: + .celo, + .cronosChain, + .smartBitcoinCash, + .kuCoinCommunityChain, + .boba, + .metis, + .aurora, + .evmos, + .moonriver, + .moonbeam, + .kavaEvm, + .klaytn, + .meter, + .okxchain: let expectedResult = "0x8f348F300873Fd5DA36950B2aC75a26584584feE" assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .ronin: + let expectedResult = "ronin:8f348F300873Fd5DA36950B2aC75a26584584feE" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .ethereumClassic: let expectedResult = "0x078bA3228F3E6C08bEEac9A005de0b7e7089aD1c" assertCoinDerivation(coin, expectedResult, derivedAddress, address) @@ -124,6 +141,9 @@ class CoinAddressDerivationTests: XCTestCase { case .neo: let expectedResult = "AT6w7PJvwPcSqHvtbNBY2aHPDv12eW5Uuf" assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .nervos: + let expectedResult = "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02wectaumxn0664yw2jd53lqk4mxg3"; + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .nimiq: let expectedResult = "NQ76 7AVR EHDA N05U X7SY XB14 XJU7 8ERV GM6H" assertCoinDerivation(coin, expectedResult, derivedAddress, address) @@ -160,7 +180,8 @@ class CoinAddressDerivationTests: XCTestCase { case .stellar: let expectedResult = "GA3H6I4C5XUBYGVB66KXR27JV5KS3APSTKRUWOIXZ5MVWZKVTLXWKZ2P" assertCoinDerivation(coin, expectedResult, derivedAddress, address) - case .terra: + case .terra, + .terraV2: let expectedResult = "terra1rh402g98t7sly8trzqw5cyracntlep6qe3smug" assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .tezos: @@ -196,7 +217,7 @@ class CoinAddressDerivationTests: XCTestCase { case .zcash: let expectedResult = "t1YYnByMzdGhQv3W3rnjHMrJs6HH4Y231gy" assertCoinDerivation(coin, expectedResult, derivedAddress, address) - case .zcoin: + case .firo: let expectedResult = "aEd5XFChyXobvEics2ppAqgK3Bgusjxtik" assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .zelcash: @@ -220,8 +241,20 @@ class CoinAddressDerivationTests: XCTestCase { case .cryptoOrg: let expectedResult = "cro16fdf785ejm00jf9a24d23pzqzjh2h05klxjwu8" assertCoinDerivation(coin, expectedResult, derivedAddress, address) - case .celo: - let expectedResult = "0xea1ac53e7Ccb5b47cdE341C118615Ef1862e3CF5" + case .osmosis: + let expectedResult = "osmo142j9u5eaduzd7faumygud6ruhdwme98qclefqp" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .ecash: + let expectedResult = "ecash:qpelrdn7a0hcucjlf9ascz3lkxv7r3rffgzn6x5377" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .nativeEvmos: + let expectedResult = "evmos13u6g7vqgw074mgmf2ze2cadzvkz9snlwstd20d" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .everscale: + let expectedResult = "0:0c39661089f86ec5926ea7d4ee4223d634ba4ed6dcc2e80c7b6a8e6d59f79b04"; + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .aptos: + let expectedResult = "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"; assertCoinDerivation(coin, expectedResult, derivedAddress, address) @unknown default: fatalError() diff --git a/swift/Tests/CoinTypeTests.swift b/swift/Tests/CoinTypeTests.swift index 5b49e9325d5..353b85cf729 100644 --- a/swift/Tests/CoinTypeTests.swift +++ b/swift/Tests/CoinTypeTests.swift @@ -30,4 +30,10 @@ class CoinTypeTests: XCTestCase { XCTAssertEqual(CoinType.avalancheCChain.rawValue, 10009000) XCTAssertEqual(CoinType.xdai.rawValue, 10000100) } + + func testCoinDerivation() { + XCTAssertEqual(CoinType.bitcoin.derivationPath(), "m/84'/0'/0'/0/0") + XCTAssertEqual(CoinType.bitcoin.derivationPathWithDerivation(derivation: Derivation.bitcoinLegacy), "m/44'/0'/0'/0/0") + XCTAssertEqual(CoinType.solana.derivationPathWithDerivation(derivation: Derivation.solanaSolana), "m/44'/501'/0'/0'") + } } diff --git a/swift/Tests/DerivationPathTests.swift b/swift/Tests/DerivationPathTests.swift index e24ab38190a..3867d434847 100644 --- a/swift/Tests/DerivationPathTests.swift +++ b/swift/Tests/DerivationPathTests.swift @@ -10,6 +10,7 @@ import XCTest class DerivationPathTests: XCTestCase { func testInitWithIndices() { let path = DerivationPath(purpose: .bip44, coin: CoinType.ethereum.slip44Id, account: 0, change: 0, address: 0) + XCTAssertEqual(path.indices[0], DerivationPath.Index(44, hardened: true)) XCTAssertEqual(path.indices[1], DerivationPath.Index(60, hardened: true)) XCTAssertEqual(path.indices[2], DerivationPath.Index(0, hardened: true)) @@ -18,20 +19,20 @@ class DerivationPathTests: XCTestCase { } func testInitWithString() { - let path = DerivationPath("m/44'/60'/0'/0/0") - - XCTAssertNotNil(path) - XCTAssertEqual(path?.indices[0], DerivationPath.Index(44, hardened: true)) - XCTAssertEqual(path?.indices[1], DerivationPath.Index(60, hardened: true)) - XCTAssertEqual(path?.indices[2], DerivationPath.Index(0, hardened: true)) - XCTAssertEqual(path?.indices[3], DerivationPath.Index(0, hardened: false)) - XCTAssertEqual(path?.indices[4], DerivationPath.Index(0, hardened: false)) - - XCTAssertEqual(path?.purpose, Purpose(rawValue: 44)!) - XCTAssertEqual(path?.coinType, 60) - XCTAssertEqual(path?.account, 0) - XCTAssertEqual(path?.change, 0) - XCTAssertEqual(path?.address, 0) + let path = DerivationPath(string: "m/44'/60'/0'/0/0")! + let indices = path.indices + + XCTAssertEqual(indices[0], DerivationPath.Index(value: 44, hardened: true)) + XCTAssertEqual(indices[1], DerivationPath.Index(value: 60, hardened: true)) + XCTAssertEqual(indices[2], DerivationPath.Index(value: 0, hardened: true)) + XCTAssertEqual(indices[3], DerivationPath.Index(value: 0, hardened: false)) + XCTAssertEqual(indices[4], DerivationPath.Index(value: 0, hardened: false)) + + XCTAssertEqual(path.purpose, Purpose(rawValue: 44)!) + XCTAssertEqual(path.coinType, 60) + XCTAssertEqual(path.account, 0) + XCTAssertEqual(path.change, 0) + XCTAssertEqual(path.address, 0) } func testInitInvalid() { @@ -41,12 +42,14 @@ class DerivationPathTests: XCTestCase { func testDescription() { let path = DerivationPath("m/44'/60'/0'/0/0") + XCTAssertEqual(path?.description, "m/44'/60'/0'/0/0") } func testEqual() { let path1 = DerivationPath("m/44'/60'/0'/0/0") let path2 = DerivationPath("44'/60'/0'/0/0") + XCTAssertNotNil(path1) XCTAssertNotNil(path2) XCTAssertEqual(path1, path2) diff --git a/swift/Tests/HDWalletTests.swift b/swift/Tests/HDWalletTests.swift index 37d268ec84c..e6e5b8b6b46 100644 --- a/swift/Tests/HDWalletTests.swift +++ b/swift/Tests/HDWalletTests.swift @@ -152,7 +152,7 @@ class HDWalletTests: XCTestCase { } func testDeriveZcoin() { - let zcoin = CoinType.zcoin + let zcoin = CoinType.firo let wallet = HDWallet.test let key = wallet.getKeyForCoin(coin: zcoin) let address = zcoin.deriveAddress(privateKey: key) @@ -386,10 +386,10 @@ class HDWalletTests: XCTestCase { XCTAssertEqual("RHQmrg7nNFnRUwg2mH7GafhRY3ZaF6FB2x", address) } - func testDeriveTerra() { - let coin = CoinType.terra + func testDeriveTerraV2() { + let coin = CoinType.terraV2 let key = HDWallet.test.getKeyForCoin(coin: coin) - let address = CoinType.terra.deriveAddress(privateKey: key) + let address = CoinType.terraV2.deriveAddress(privateKey: key) XCTAssertEqual(address, "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe") } diff --git a/swift/Tests/HashTests.swift b/swift/Tests/HashTests.swift index b85f3325d8c..fc192dceebe 100644 --- a/swift/Tests/HashTests.swift +++ b/swift/Tests/HashTests.swift @@ -15,11 +15,4 @@ class HashTests: XCTestCase { XCTAssertEqual(hashed.hexString, "e30d87cfa2a75db545eac4d61baf970366a8357c7f72fa95b52d0accb698f13a") } - - func testTwoXXhash64() { - let message = "ReservedBalance".data(using: .utf8)! - let hashed = Hash.twoXXHash64Concat(data: message) - - XCTAssertEqual(hashed.hexString, "3c22813def93ef32c365b55cb92f10f9") - } } diff --git a/swift/Tests/Keystore/KeyStoreTests.swift b/swift/Tests/Keystore/KeyStoreTests.swift index 7c205b766cd..3ae5529e79e 100755 --- a/swift/Tests/Keystore/KeyStoreTests.swift +++ b/swift/Tests/Keystore/KeyStoreTests.swift @@ -217,27 +217,33 @@ class KeyStoreTests: XCTestCase { "address": "bc1q4zehq85jqx9zzgzvzn9t64yjy66nunn3vehuv6", "coin": 0, "derivationPath": "m/84'/0'/0'/0/0", - "extendedPublicKey": "zpub6qMRMrwcEYaqjf8wSpNqtBfUee6MqpQjrZNKfj5a48EUFUx2yUmfkDJMdHwWvkg8SjdS3ua6dy9ofMrzrytTfdyy2pXg344yFwm2Ta9cm6Q" + "extendedPublicKey": "zpub6qMRMrwcEYaqjf8wSpNqtBfUee6MqpQjrZNKfj5a48EUFUx2yUmfkDJMdHwWvkg8SjdS3ua6dy9ofMrzrytTfdyy2pXg344yFwm2Ta9cm6Q", + "publicKey": "0334c47fa4eafef196f62eb53192a39bc36c5823ad4bd23db503170b9d3dbe80fd" }, { "address": "0x33F44330cc4253cCd4ce4224186DB9baCe2190ea", "coin": 60, - "derivationPath": "m/44'/60'/0'/0/0" + "derivationPath": "m/44'/60'/0'/0/0", + "publicKey": "04906ab3a756b952c1f2ad41daf0c82cc12fb155cd73919b904ffb2630866abfe3feae7169c3e322465d119f4b20465b2a98f8bcb9e19bf22d84ba04e277c1c6ee" }, { "address": "bnb1njuczq3hgvupu2vnczrjz7rc8x4uxlmhjyq95z", "coin": 714, - "derivationPath": "m/44'/714'/0'/0/0" + "derivationPath": "m/44'/714'/0'/0/0", + "publicKey": "03397cf6ee9ddfee746dc750e9b1abd9824ff8fec3e29bb09b3b2c330a88b605b8" }, { "address": "0x5dEc7A9299360aEb44c83B8F730F2BF5Dd1688bC", "coin": 10000714, - "derivationPath": "m/44'/714'/0'/0/0" + "derivationPath": "m/44'/714'/0'/0/0", + "publicKey": "04397cf6ee9ddfee746dc750e9b1abd9824ff8fec3e29bb09b3b2c330a88b605b81e46b99afe5dd84a5420b9e54f04b26aeb034f12849145a8163255875af1aef7" }, { "address": "0x33F44330cc4253cCd4ce4224186DB9baCe2190ea", "coin": 20000714, - "derivationPath": "m/44'/60'/0'/0/0" + "derivationPath": "m/44'/60'/0'/0/0", + "publicKey": "04906ab3a756b952c1f2ad41daf0c82cc12fb155cd73919b904ffb2630866abfe3feae7169c3e322465d119f4b20465b2a98f8bcb9e19bf22d84ba04e277c1c6ee" }, { "address": "838f8aeba6bb083b5b6e22030fb051eaf1a8b6cd692d4ad533cba60c77e6b8f2", "coin": 397, - "derivationPath": "m/44'/397'/0'" + "derivationPath": "m/44'/397'/0'", + "publicKey": "838f8aeba6bb083b5b6e22030fb051eaf1a8b6cd692d4ad533cba60c77e6b8f2" }], "crypto": { "cipher": "aes-128-ctr", @@ -264,6 +270,13 @@ class KeyStoreTests: XCTestCase { let password = "e28ddf66cec05c1fc09939a00628b230459202b2493fccac288038ef37815723" let keyStore = try KeyStore(keyDirectory: keyDirectory) + + // Fill public key if needed + let btcAccount = try keyStore.bnbWallet.getAccount(password: password, coin: .bitcoin) + XCTAssertEqual(btcAccount.publicKey, "0334c47fa4eafef196f62eb53192a39bc36c5823ad4bd23db503170b9d3dbe80fd") + + // Fix all empty + _ = keyStore.bnbWallet.key.fixAddresses(password: Data(password.utf8)) _ = try keyStore.addAccounts(wallet: keyStore.bnbWallet, coins: [.smartChainLegacy, .smartChain], password: password) // simulate migration code @@ -339,6 +352,7 @@ class KeyStoreTests: XCTestCase { for account in accounts { XCTAssertFalse(account.address.isEmpty) + XCTAssertFalse(account.publicKey.isEmpty) } XCTAssertEqual(coins.count, wallet.accounts.count) @@ -385,6 +399,31 @@ class KeyStoreTests: XCTestCase { } } + func testCreateMultiAccount() throws { + let mnemonic = "team engine square letter hero song dizzy scrub tornado fabric divert saddle" + let password = "password" + let keyStore = try KeyStore(keyDirectory: keyDirectory) + let wallet = try keyStore.import(mnemonic: mnemonic, name: "name", encryptPassword: password, coins: [.bitcoin, .solana]) + + _ = try keyStore.addAccounts(wallet: wallet, coins: [.bitcoin, .solana], password: password) + + let btc1 = try wallet.getAccount(password: password, coin: .bitcoin, derivation: .default) + XCTAssertEqual(btc1.address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny") + XCTAssertEqual(btc1.extendedPublicKey, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn") + + let btc2 = try wallet.getAccount(password: password, coin: .bitcoin, derivation: .bitcoinLegacy) + XCTAssertEqual(btc2.address, "1NyRyFewhZcWMa9XCj3bBxSXPXyoSg8dKz") + XCTAssertEqual(btc2.extendedPublicKey, "xpub6CR52eaUuVb4kXAVyHC2i5ZuqJ37oWNPZFtjXaazFPXZD45DwWBYEBLdrF7fmCR9pgBuCA9Q57zZfyJjDUBDNtWkhWuGHNYKLgDHpqrHsxV") + + let solana1 = try wallet.getAccount(password: password, coin: .solana, derivation: .default) + XCTAssertEqual(solana1.address, "HiipoCKL8hX2RVmJTz3vaLy34hS2zLhWWMkUWtw85TmZ") + XCTAssertEqual(solana1.derivationPath, "m/44'/501'/0'") + + let solana2 = try wallet.getAccount(password: password, coin: .solana, derivation: .solanaSolana) + XCTAssertEqual(solana2.address, "CgWJeEWkiYqosy1ba7a3wn9HAQuHyK48xs3LM4SSDc1C") + XCTAssertEqual(solana2.derivationPath, "m/44'/501'/0'/0'") + } + func createTempDirURL() throws -> URL { let dir = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("keystore") try? fileManager.removeItem(at: dir) diff --git a/swift/Tests/Keystore/KeystoreKeyTests.swift b/swift/Tests/Keystore/KeystoreKeyTests.swift index dafa208495b..b33659ceaef 100755 --- a/swift/Tests/Keystore/KeystoreKeyTests.swift +++ b/swift/Tests/Keystore/KeystoreKeyTests.swift @@ -119,4 +119,25 @@ class KeystoreKeyTests: XCTestCase { let data = keystore.decryptPrivateKey(password: password) XCTAssertEqual(data?.hexString, "4357b2f9a6150ba969bc52f01c98cce5313595fe49f2d08303759c73e5c7a46c") } + + struct KdfParams: Decodable { + let dklen: Int + let n: Int + } + + struct EncryptionParameters: Decodable { + let kdf: String + let kdfparams: KdfParams + } + + func testEncryptionParameters() { + let url = Bundle(for: type(of: self)).url(forResource: "key", withExtension: "json")! + let key = StoredKey.load(path: url.path)! + + let paramsData = key.encryptionParameters!.data(using: .utf8)! + let params = try! JSONDecoder().decode(EncryptionParameters.self, from: paramsData) + + XCTAssertEqual(params.kdf, "scrypt"); + XCTAssertEqual(params.kdfparams.n, 262144); + } } diff --git a/swift/Tests/PBKDF2Tests.swift b/swift/Tests/PBKDF2Tests.swift new file mode 100644 index 00000000000..c5ccbadc1af --- /dev/null +++ b/swift/Tests/PBKDF2Tests.swift @@ -0,0 +1,42 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +import XCTest +import WalletCore + +class PBKDF2Tests: XCTestCase { + + let password = "password".data(using: .utf8)! + let salt = "salt".data(using: .utf8)! + let salt2 = Data(base64Encoded: "kNHS+Mx//slRsmLF9396HQ==")! + + func testSha256Hmac() { + + let key = PBKDF2.hmacSha256(password: password, salt: salt, iterations: 1, dkLen: 20)! + + XCTAssertEqual(key.hexString, "120fb6cffcf8b32c43e7225256c4f837a86548c9") + + let key2 = PBKDF2.hmacSha256(password: password, salt: salt, iterations: 4096, dkLen: 20)! + + XCTAssertEqual(key2.hexString, "c5e478d59288c841aa530db6845c4c8d962893a0") + + let key3 = PBKDF2.hmacSha256(password: password, salt: salt2, iterations: 100, dkLen: 32)! + XCTAssertEqual(key3.hexString, "9cf33ebd3542c691fac6f61609a8d13355a0adf4d15eed77cc9d13f792b77c3a") + } + + func testSha512Hmac() { + let key = PBKDF2.hmacSha512(password: password, salt: salt, iterations: 1, dkLen: 20)! + + XCTAssertEqual(key.hexString, "867f70cf1ade02cff3752599a3a53dc4af34c7a6") + + let key2 = PBKDF2.hmacSha512(password: password, salt: salt, iterations: 4096, dkLen: 20)! + + XCTAssertEqual(key2.hexString, "d197b1b33db0143e018b12f3d1d1479e6cdebdcc") + + let key3 = PBKDF2.hmacSha512(password: password, salt: salt2, iterations: 100, dkLen: 32)! + XCTAssertEqual(key3.hexString, "6a9a209f35be9212118ea055e11b545451b53b686608a6362d59ddf31a2b3ce0") + } +} diff --git a/swift/Tests/PrivateKeyTests.swift b/swift/Tests/PrivateKeyTests.swift index 6ecdf044344..ae363969f17 100644 --- a/swift/Tests/PrivateKeyTests.swift +++ b/swift/Tests/PrivateKeyTests.swift @@ -79,8 +79,8 @@ class PrivateKeyTests: XCTestCase { let message = "hello schnorr".data(using: .utf8)! - let sig = privateKey.signSchnorr(message: message, curve: .secp256k1)! - let verified = publicKey.verifySchnorr(signature: sig, message: message) + let sig = privateKey.signZilliqaSchnorr(message: message)! + let verified = publicKey.verifyZilliqaSchnorr(signature: sig, message: message) XCTAssertEqual(sig.hexString, "d166b1ae7892c5ef541461dc12a50214d0681b63d8037cda29a3fe6af8bb973e4ea94624d85bc0010bdc1b38d05198328fae21254adc2bf5feaf2804d54dba55") XCTAssertTrue(verified) diff --git a/swift/Tests/TransactionCompilerTests.swift b/swift/Tests/TransactionCompilerTests.swift new file mode 100644 index 00000000000..b5e147a65f5 --- /dev/null +++ b/swift/Tests/TransactionCompilerTests.swift @@ -0,0 +1,176 @@ +// 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 XCTest +import WalletCore + +class TransactionCompilerTests: XCTestCase { + override func setUp() { + continueAfterFailure = false + } + + func testBitcoinCompileWithSignatures() throws { + // Test external signining with a Bitcoin transaction with 3 input UTXOs, all used, but only using 2 public keys. + // Three signatures are neeeded. This illustrates that order of UTXOs/hashes is not always the same. + + let revUtxoHash0 = Data(hexString: "07c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa8")! + let revUtxoHash1 = Data(hexString: "d6892a5aa54e3b8fe430efd23f49a8950733aaa9d7c915d9989179f48dd1905e")! + let revUtxoHash2 = Data(hexString: "6021efcf7555f90627364339fc921139dd40a06ccb2cb2a2a4f8f4ea7a2dc74d")! + let inPubKey0 = Data(hexString: "024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382")! + let inPubKey1 = Data(hexString: "0217142f69535e4dad0dc7060df645c55a174cc1bfa5b9eb2e59aad2ae96072dfc")! + let inPubKeyHash0 = Data(hexString: "bd92088bb7e82d611a9b94fbb74a0908152b784f")! + let inPubKeyHash1 = Data(hexString: "6641abedacf9483b793afe1718689cc9420bbb1c")! + + // Test data: Input UTXO infos + struct UtxoInfo { + let revUtxoHash: Data + let publicKey: Data + let amount: Int64 + let index: UInt32 + } + let utxoInfos: [UtxoInfo] = [ + // first + UtxoInfo(revUtxoHash: revUtxoHash0, publicKey: inPubKey0, amount: 600000, index: 0), + // second UTXO, with same pubkey + UtxoInfo(revUtxoHash: revUtxoHash1, publicKey: inPubKey0, amount: 500000, index: 1), + // third UTXO, with different pubkey + UtxoInfo(revUtxoHash: revUtxoHash2, publicKey: inPubKey1, amount: 400000, index: 0), + ] + + // Signature infos, indexed by pubkeyhash+hash + struct SignatureInfo { + let signature: Data + let publicKey: Data + } + let signatureInfos: [String: SignatureInfo] = [ + inPubKeyHash0.hexString + "+" + "a296bead4172007be69b21971a790e076388666c162a9505698415f1b003ebd7": + SignatureInfo(signature: Data(hexString: "304402201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b34902200a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f2a40")!, publicKey: inPubKey0), + inPubKeyHash1.hexString + "+" + "505f527f00e15fcc5a2d2416c9970beb57dfdfaca99e572a01f143b24dd8fab6": + SignatureInfo(signature: Data(hexString: "3044022041294880caa09bb1b653775310fcdd1458da6b8e7d7fae34e37966414fe115820220646397c9d2513edc5974ecc336e9b287de0cdf071c366f3b3dc3ff309213e4e4")!, publicKey: inPubKey1), + inPubKeyHash0.hexString + "+" + "60ed6e9371e5ddc72fd88e46a12cb2f68516ebd307c0fd31b1b55cf767272101": + SignatureInfo(signature: Data(hexString: "30440220764e3d5b3971c4b3e70b23fb700a7462a6fe519d9830e863a1f8388c402ad0b102207e777f7972c636961f92375a2774af3b7a2a04190251bbcb31d19c70927952dc")!, publicKey: inPubKey0), + ] + + let coin = CoinType.bitcoin + let ownAddress = "bc1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z00ppggv" + + // Setup input for Plan + var signingInput = BitcoinSigningInput.with { + $0.coinType = coin.rawValue + $0.hashType = BitcoinSigHashType.all.rawValue + $0.amount = 1200000 + $0.useMaxAmount = false + $0.byteFee = 1 + $0.toAddress = "bc1q2dsdlq3343vk29runkgv4yc292hmq53jedfjmp" + $0.changeAddress = ownAddress + } + + // process UTXOs + var count: UInt32 = 0 + for u in utxoInfos { + let publicKey = PublicKey(data: u.publicKey, type: PublicKeyType.secp256k1) + let address = SegwitAddress(hrp: .bitcoin, publicKey: publicKey!) + if (count == 0) { XCTAssertEqual(address.description, ownAddress) } + if (count == 1) { XCTAssertEqual(address.description, ownAddress) } + if (count == 2) { XCTAssertEqual(address.description, "bc1qveq6hmdvl9yrk7f6lct3s6yue9pqhwcuxedggg") } + + let utxoScript = BitcoinScript.lockScriptForAddress(address: address.description, coin: coin) + if (count == 0) { XCTAssertEqual(utxoScript.data.hexString, "0014bd92088bb7e82d611a9b94fbb74a0908152b784f") } + if (count == 1) { XCTAssertEqual(utxoScript.data.hexString, "0014bd92088bb7e82d611a9b94fbb74a0908152b784f") } + if (count == 2) { XCTAssertEqual(utxoScript.data.hexString, "00146641abedacf9483b793afe1718689cc9420bbb1c") } + + let keyHash: Data = utxoScript.matchPayToWitnessPublicKeyHash()! + if (count == 0) { XCTAssertEqual(keyHash.hexString, inPubKeyHash0.hexString) } + if (count == 1) { XCTAssertEqual(keyHash.hexString, inPubKeyHash0.hexString) } + if (count == 2) { XCTAssertEqual(keyHash.hexString, inPubKeyHash1.hexString) } + + let redeemScript = BitcoinScript.buildPayToPublicKeyHash(hash: keyHash) + if (count == 0) { XCTAssertEqual(redeemScript.data.hexString, "76a914bd92088bb7e82d611a9b94fbb74a0908152b784f88ac") } + if (count == 1) { XCTAssertEqual(redeemScript.data.hexString, "76a914bd92088bb7e82d611a9b94fbb74a0908152b784f88ac") } + if (count == 2) { XCTAssertEqual(redeemScript.data.hexString, "76a9146641abedacf9483b793afe1718689cc9420bbb1c88ac") } + signingInput.scripts[keyHash.hexString] = redeemScript.data + + let outPoint = BitcoinOutPoint.with { + $0.hash = u.revUtxoHash + $0.index = u.index + $0.sequence = UInt32.max + } + let utxo = BitcoinUnspentTransaction.with { + $0.script = utxoScript.data + $0.amount = u.amount + $0.outPoint = outPoint + } + signingInput.utxo.append(utxo) + + count += 1 + } + XCTAssertEqual(count, 3) + XCTAssertEqual(signingInput.utxo.count, 3) + + // Plan + let plan: BitcoinTransactionPlan = AnySigner.plan(input: signingInput, coin: coin) + + // At this point plan can be checked, assume it is accepted unmodified + XCTAssertEqual(plan.amount, 1200000) + XCTAssertEqual(plan.fee, 277) + XCTAssertEqual(plan.change, 299723) + XCTAssertEqual(plan.utxos.count, 3) + // Note that UTXOs happen to be in reverse order compared to the input + XCTAssertEqual(plan.utxos[0].outPoint.hash.hexString, revUtxoHash2.hexString) + XCTAssertEqual(plan.utxos[1].outPoint.hash.hexString, revUtxoHash1.hexString) + XCTAssertEqual(plan.utxos[2].outPoint.hash.hexString, revUtxoHash0.hexString) + + // Extend input with accepted plan + signingInput.plan = plan + + // Serialize input + let txInputData = try signingInput.serializedData() + XCTAssertEqual(txInputData.count, 692) + + /// Step 2: Obtain preimage hashes + let preImageHashes = TransactionCompiler.preImageHashes(coinType: coin, txInputData: txInputData) + let preSigningOutput: BitcoinPreSigningOutput = try BitcoinPreSigningOutput(serializedData: preImageHashes) + + XCTAssertEqual(preSigningOutput.error, CommonSigningError.ok) + XCTAssertEqual(preSigningOutput.hashPublicKeys[0].dataHash.hexString, "505f527f00e15fcc5a2d2416c9970beb57dfdfaca99e572a01f143b24dd8fab6") + XCTAssertEqual(preSigningOutput.hashPublicKeys[1].dataHash.hexString, "a296bead4172007be69b21971a790e076388666c162a9505698415f1b003ebd7") + XCTAssertEqual(preSigningOutput.hashPublicKeys[2].dataHash.hexString, "60ed6e9371e5ddc72fd88e46a12cb2f68516ebd307c0fd31b1b55cf767272101") + XCTAssertEqual(preSigningOutput.hashPublicKeys[0].publicKeyHash.hexString, inPubKeyHash1.hexString) + XCTAssertEqual(preSigningOutput.hashPublicKeys[1].publicKeyHash.hexString, inPubKeyHash0.hexString) + XCTAssertEqual(preSigningOutput.hashPublicKeys[2].publicKeyHash.hexString, inPubKeyHash0.hexString) + + // Simulate signatures, normally they are obtained from external source, e.g. a signature server. + var signatureVec = DataVector() + var pubkeyVec = DataVector() + for h in preSigningOutput.hashPublicKeys { + let preImageHash = h.dataHash + let pubkeyHash = h.publicKeyHash + + let key: String = pubkeyHash.hexString + "+" + preImageHash.hexString + XCTAssertTrue(signatureInfos.contains { $0.key == key }) + let sigInfo: SignatureInfo = signatureInfos[key]! + let publicKeyData = sigInfo.publicKey + let publicKey = PublicKey(data: publicKeyData, type: PublicKeyType.secp256k1) + let signature = sigInfo.signature + + signatureVec.add(data: signature) + pubkeyVec.add(data: publicKeyData) + + // Verify signature (pubkey & hash & signature) + publicKey?.verifyAsDER(signature: signature, message: preImageHash) + } + + /// Step 3: Compile transaction info + let compileWithSignatures = TransactionCompiler.compileWithSignatures(coinType: coin, txInputData: txInputData, signatures: signatureVec, publicKeys: pubkeyVec) + + let ExpectedTx = "010000000001036021efcf7555f90627364339fc921139dd40a06ccb2cb2a2a4f8f4ea7a2dc74d0000000000ffffffffd6892a5aa54e3b8fe430efd23f49a8950733aaa9d7c915d9989179f48dd1905e0100000000ffffffff07c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa80000000000ffffffff02804f1200000000001600145360df8231ac5965147c9d90ca930a2aafb05232cb92040000000000160014bd92088bb7e82d611a9b94fbb74a0908152b784f02473044022041294880caa09bb1b653775310fcdd1458da6b8e7d7fae34e37966414fe115820220646397c9d2513edc5974ecc336e9b287de0cdf071c366f3b3dc3ff309213e4e401210217142f69535e4dad0dc7060df645c55a174cc1bfa5b9eb2e59aad2ae96072dfc0247304402201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b34902200a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f2a400121024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382024730440220764e3d5b3971c4b3e70b23fb700a7462a6fe519d9830e863a1f8388c402ad0b102207e777f7972c636961f92375a2774af3b7a2a04190251bbcb31d19c70927952dc0121024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb49338200000000" + + XCTAssertEqual(compileWithSignatures.count, 786) + let output: BitcoinSigningOutput = try BitcoinSigningOutput(serializedData: compileWithSignatures) + XCTAssertEqual(output.encoded.count, 518) + XCTAssertEqual(output.encoded.hexString, ExpectedTx) + } +} diff --git a/swift/Tests/UniversalAssetIDTests.swift b/swift/Tests/UniversalAssetIDTests.swift index 51498a08c2c..73ad5ac8358 100644 --- a/swift/Tests/UniversalAssetIDTests.swift +++ b/swift/Tests/UniversalAssetIDTests.swift @@ -29,7 +29,7 @@ class UniversalAssetIDTests: XCTestCase { XCTAssertEqual(busd.description, "c714_tBUSD-BD1") } - func testEqutable() { + func testEquatable() { XCTAssertEqual( UniversalAssetID(coin: .ethereum), diff --git a/swift/common-xcframework.yml b/swift/common-xcframework.yml index c9531eb76a0..fead1284c2c 100644 --- a/swift/common-xcframework.yml +++ b/swift/common-xcframework.yml @@ -11,7 +11,7 @@ settings: ENABLE_BITCODE: YES HEADER_SEARCH_PATHS: $(SRCROOT)/wallet-core ${SRCROOT}/trezor-crypto/crypto SYSTEM_HEADER_SEARCH_PATHS: ${SRCROOT}/include ${SRCROOT}/../build/local/include ${SRCROOT}/trezor-crypto/include $(SRCROOT)/protobuf /usr/local/include /opt/homebrew/include - CLANG_CXX_LANGUAGE_STANDARD: c++17 + CLANG_CXX_LANGUAGE_STANDARD: c++20 GCC_WARN_64_TO_32_BIT_CONVERSION: NO targets: @@ -109,7 +109,8 @@ targets: - trezor-crypto/crypto/groestl.c - trezor-crypto/crypto/hmac_drbg.c - trezor-crypto/crypto/rfc6979.c - - trezor-crypto/crypto/schnorr.c + - trezor-crypto/crypto/zilliqa.c + - trezor-crypto/crypto/cardano.c - trezor-crypto/crypto/shamir.c - trezor-crypto/crypto/sodium/private/fe_25_5/fe.c - trezor-crypto/crypto/sodium/private/ed25519_ref10.c @@ -141,11 +142,15 @@ targets: - protobuf/google/protobuf/extension_set_heavy.cc - protobuf/google/protobuf/field_mask.pb.cc - protobuf/google/protobuf/generated_enum_util.cc + - protobuf/google/protobuf/generated_message_bases.cc - protobuf/google/protobuf/generated_message_reflection.cc - protobuf/google/protobuf/generated_message_table_driven.cc - protobuf/google/protobuf/generated_message_table_driven_lite.cc + - protobuf/google/protobuf/generated_message_tctable_full.cc + - protobuf/google/protobuf/generated_message_tctable_lite.cc - protobuf/google/protobuf/generated_message_util.cc - protobuf/google/protobuf/implicit_weak_message.cc + - protobuf/google/protobuf/inlined_string_field.cc - protobuf/google/protobuf/io/coded_stream.cc - protobuf/google/protobuf/io/gzip_stream.cc - protobuf/google/protobuf/io/io_win32.cc @@ -162,6 +167,7 @@ targets: - protobuf/google/protobuf/parse_context.cc - protobuf/google/protobuf/reflection_ops.cc - protobuf/google/protobuf/repeated_field.cc + - protobuf/google/protobuf/repeated_ptr_field.cc - protobuf/google/protobuf/service.cc - protobuf/google/protobuf/source_context.pb.cc - protobuf/google/protobuf/struct.pb.cc @@ -195,7 +201,6 @@ targets: - protobuf/google/protobuf/util/internal/protostream_objectsource.cc - protobuf/google/protobuf/util/internal/protostream_objectwriter.cc - protobuf/google/protobuf/util/internal/type_info.cc - - protobuf/google/protobuf/util/internal/type_info_test_helper.cc - protobuf/google/protobuf/util/internal/utility.cc - protobuf/google/protobuf/util/json_util.cc - protobuf/google/protobuf/util/message_differencer.cc diff --git a/swift/cpp.xcconfig.in b/swift/cpp.xcconfig.in index 36b166aceb6..41c80610459 100644 --- a/swift/cpp.xcconfig.in +++ b/swift/cpp.xcconfig.in @@ -5,4 +5,4 @@ // file LICENSE at the root of the source code distribution tree. HEADER_SEARCH_PATHS = $(SRCROOT)/../src @Boost_INCLUDE_DIRS@ -SYSTEM_HEADER_SEARCH_PATHS = $(SRCROOT)/../src/build/local/include $(SRCROOT)/../build/local/src/protobuf/protobuf-3.14.0/src +SYSTEM_HEADER_SEARCH_PATHS = $(SRCROOT)/../src/build/local/include $(SRCROOT)/../build/local/src/protobuf/protobuf-3.19.2/src diff --git a/swift/project.yml b/swift/project.yml index 9c2ce918cb2..3d17eb60784 100644 --- a/swift/project.yml +++ b/swift/project.yml @@ -6,7 +6,7 @@ settings: base: HEADER_SEARCH_PATHS: $(SRCROOT)/wallet-core ${SRCROOT}/trezor-crypto/crypto SYSTEM_HEADER_SEARCH_PATHS: ${SRCROOT}/include ${SRCROOT}/../build/local/include ${SRCROOT}/trezor-crypto/include $(SRCROOT)/protobuf /usr/local/include /opt/homebrew/include - CLANG_CXX_LANGUAGE_STANDARD: c++17 + CLANG_CXX_LANGUAGE_STANDARD: c++20 SWIFT_VERSION: 5.1 IPHONEOS_DEPLOYMENT_TARGET: 13.0 configs: @@ -27,6 +27,7 @@ targets: excludes: - ".vscode" - "proto/*.proto" + - "Cosmos/Protobuf/*.proto" - "Tron/Protobuf/*.proto" - "Zilliqa/Protobuf/*.proto" - Sources @@ -43,14 +44,22 @@ targets: settings: SKIP_INSTALL: false SUPPORTS_MACCATALYST: true + DEBUG_INFORMATION_FORMAT: dwarf-with-dsym BUILD_LIBRARY_FOR_DISTRIBUTION: true INFOPLIST_FILE: 'Info.plist' CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION: YES_ERROR CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER: $(inherited) false OTHER_CFLAGS: $(inherited) -Wno-comma -DNDEBUG postCompileScripts: - - script: ${PODS_ROOT}/SwiftLint/swiftlint --config ../.swiftlint.yml + - script: | + export PATH=/opt/homebrew/bin:$PATH; + if which swiftlint >/dev/null; then + swiftlint + else + echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint" + fi name: SwiftLint + shell: /bin/bash WalletCoreTests: type: bundle.unit-test @@ -117,7 +126,8 @@ targets: - trezor-crypto/crypto/groestl.c - trezor-crypto/crypto/hmac_drbg.c - trezor-crypto/crypto/rfc6979.c - - trezor-crypto/crypto/schnorr.c + - trezor-crypto/crypto/zilliqa.c + - trezor-crypto/crypto/cardano.c - trezor-crypto/crypto/shamir.c - trezor-crypto/crypto/sodium/private/fe_25_5/fe.c - trezor-crypto/crypto/sodium/private/ed25519_ref10.c @@ -149,11 +159,15 @@ targets: - protobuf/google/protobuf/extension_set_heavy.cc - protobuf/google/protobuf/field_mask.pb.cc - protobuf/google/protobuf/generated_enum_util.cc + - protobuf/google/protobuf/generated_message_bases.cc - protobuf/google/protobuf/generated_message_reflection.cc - protobuf/google/protobuf/generated_message_table_driven.cc - protobuf/google/protobuf/generated_message_table_driven_lite.cc + - protobuf/google/protobuf/generated_message_tctable_full.cc + - protobuf/google/protobuf/generated_message_tctable_lite.cc - protobuf/google/protobuf/generated_message_util.cc - protobuf/google/protobuf/implicit_weak_message.cc + - protobuf/google/protobuf/inlined_string_field.cc - protobuf/google/protobuf/io/coded_stream.cc - protobuf/google/protobuf/io/gzip_stream.cc - protobuf/google/protobuf/io/io_win32.cc @@ -170,6 +184,7 @@ targets: - protobuf/google/protobuf/parse_context.cc - protobuf/google/protobuf/reflection_ops.cc - protobuf/google/protobuf/repeated_field.cc + - protobuf/google/protobuf/repeated_ptr_field.cc - protobuf/google/protobuf/service.cc - protobuf/google/protobuf/source_context.pb.cc - protobuf/google/protobuf/struct.pb.cc @@ -203,7 +218,6 @@ targets: - protobuf/google/protobuf/util/internal/protostream_objectsource.cc - protobuf/google/protobuf/util/internal/protostream_objectwriter.cc - protobuf/google/protobuf/util/internal/type_info.cc - - protobuf/google/protobuf/util/internal/type_info_test_helper.cc - protobuf/google/protobuf/util/internal/utility.cc - protobuf/google/protobuf/util/json_util.cc - protobuf/google/protobuf/util/message_differencer.cc diff --git a/swift/protobuf b/swift/protobuf index 3d4e7cf4303..a96b2043a74 120000 --- a/swift/protobuf +++ b/swift/protobuf @@ -1 +1 @@ -../build/local/src/protobuf/protobuf-3.14.0/src \ No newline at end of file +../build/local/src/protobuf/protobuf-3.19.2/src \ No newline at end of file diff --git a/swift/protobuf.patch b/swift/protobuf.patch deleted file mode 100644 index 05c920be396..00000000000 --- a/swift/protobuf.patch +++ /dev/null @@ -1,30 +0,0 @@ ---- common.cc 2021-04-23 10:54:11.000000000 +0800 -+++ common.patch.cc 2021-04-23 11:03:01.000000000 +0800 -@@ -38,17 +38,17 @@ - #include - #include - --#ifdef _WIN32 --#ifndef WIN32_LEAN_AND_MEAN --#define WIN32_LEAN_AND_MEAN // We only need minimal includes --#endif --#include --#define snprintf _snprintf // see comment in strutil.cc --#elif defined(HAVE_PTHREAD) -+// #ifdef _WIN32 -+// #ifndef WIN32_LEAN_AND_MEAN -+// #define WIN32_LEAN_AND_MEAN // We only need minimal includes -+// #endif -+// #include -+// #define snprintf _snprintf // see comment in strutil.cc -+// #elif defined(HAVE_PTHREAD) - #include --#else --#error "No suitable threading library available." --#endif -+// #else -+// #error "No suitable threading library available." -+// #endif - #if defined(__ANDROID__) - #include - #endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 28a0132cf92..fbeed4ace8a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,3 +1,9 @@ +# 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. + enable_testing() # Prevent overriding the parent project's compiler/linker @@ -9,7 +15,7 @@ if(WIN32) else() # Add googletest directly to our build. This defines # the gtest and gtest_main targets. - add_subdirectory(${CMAKE_SOURCE_DIR}/build/local/src/gtest/googletest-release-1.10.0 + add_subdirectory(${CMAKE_SOURCE_DIR}/build/local/src/gtest/googletest-release-1.11.0 ${CMAKE_CURRENT_BINARY_DIR}/googletest-build EXCLUDE_FROM_ALL) set(GTEST_LIBRARIES gtest) @@ -21,17 +27,27 @@ endif() ##include_directories(${Protobuf_INCLUDE_DIRS}) # Test executable -file(GLOB_RECURSE test_sources *.cpp **/*.cpp) +file(GLOB_RECURSE test_sources *.cpp **/*.cpp **/*.cc) +if("${CMAKE_CXX_COMPILER_ID}" MATCHES "MSVC") + list(FILTER test_sources EXCLUDE REGEX ".*CMakeCXXCompilerId\\.cpp$") +endif() + add_executable(tests ${test_sources}) target_link_libraries(tests ${GTEST_MAIN_LIBRARIES} TrezorCrypto TrustWalletCore walletconsolelib ${Protobuf_LIBRARIES} Boost::boost) target_include_directories(tests PRIVATE ${CMAKE_SOURCE_DIR}/src) +target_include_directories(tests PRIVATE ${CMAKE_SOURCE_DIR}/tests/common) + if(NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "MSVC")) target_compile_options(tests PRIVATE "-Wall") endif() +if (NOT ANDROID AND TW_UNITY_BUILD) + set_target_properties(tests PROPERTIES UNITY_BUILD ON) +endif() + set_target_properties(tests PROPERTIES - CXX_STANDARD 17 + CXX_STANDARD 20 CXX_STANDARD_REQUIRED ON ) diff --git a/tests/Cardano/AddressTests.cpp b/tests/Cardano/AddressTests.cpp deleted file mode 100644 index d798fb66f90..00000000000 --- a/tests/Cardano/AddressTests.cpp +++ /dev/null @@ -1,377 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Cardano/AddressV3.h" - -#include "HDWallet.h" -#include "HexCoding.h" -#include "PrivateKey.h" - -#include - -#include - -using namespace TW; -using namespace TW::Cardano; -using namespace std; - - -TEST(CardanoAddress, Validation) { - // valid V3 address - ASSERT_TRUE(AddressV3::isValid("addr1s3hdtrqgs47l7ue5srga8wmk9dzw279x9e7lxadalt6z0fk64nnn2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59j5lempe")); - ASSERT_TRUE(AddressV3::isValid("addr1s3xuxwfetyfe7q9u3rfn6je9stlvcgmj8rezd87qjjegdtxm3y3f2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59jz29g8j")); - ASSERT_TRUE(AddressV3::isValid("ca1qvqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0jqxuzx4s")); - ASSERT_TRUE(AddressV3::isValid("ca1qsqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0jq2f29vkz6t30xqcnyve5x5mrwwpe8ganc0f78aqyzsjrg3z5v36gguhxny")); - - // valid V2 address - ASSERT_TRUE(AddressV3::isValid("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvx")); - ASSERT_TRUE(AddressV3::isValid("Ae2tdPwUPEZ6RUCnjGHFqi59k5WZLiv3HoCCNGCW8SYc5H9srdTzn1bec4W")); - - // valid V1 address - ASSERT_TRUE(AddressV3::isValid("DdzFFzCqrhssmYoG5Eca1bKZFdGS8d6iag1mU4wbLeYcSPVvBNF2wRG8yhjzQqErbg63N6KJA4DHqha113tjKDpGEwS5x1dT2KfLSbSJ")); - ASSERT_TRUE(AddressV3::isValid("DdzFFzCqrht7HGoJ87gznLktJGywK1LbAJT2sbd4txmgS7FcYLMQFhawb18ojS9Hx55mrbsHPr7PTraKh14TSQbGBPJHbDZ9QVh6Z6Di")); - - // invalid checksum V3 - ASSERT_FALSE(AddressV3::isValid("PREFIX1qvqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0jqxuzx4s")); - // invalid checksum V2 - ASSERT_FALSE(AddressV3::isValid("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvm")); - // random - ASSERT_FALSE(AddressV3::isValid("hasoiusaodiuhsaijnnsajnsaiussai")); - // empty - ASSERT_FALSE(AddressV3::isValid("")); -} - -TEST(CardanoAddress, FromStringV2) { - { - auto address = AddressV3("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvx"); - ASSERT_EQ(address.string(), "Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvx"); - } - { - auto address = AddressV3("DdzFFzCqrhssmYoG5Eca1bKZFdGS8d6iag1mU4wbLeYcSPVvBNF2wRG8yhjzQqErbg63N6KJA4DHqha113tjKDpGEwS5x1dT2KfLSbSJ"); - ASSERT_EQ(address.string(), "DdzFFzCqrhssmYoG5Eca1bKZFdGS8d6iag1mU4wbLeYcSPVvBNF2wRG8yhjzQqErbg63N6KJA4DHqha113tjKDpGEwS5x1dT2KfLSbSJ"); - } -} - -TEST(CardanoAddress, FromStringV3) { - { - // single addr - auto address = AddressV3("ca1qvqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0jqxuzx4s"); - EXPECT_EQ(address.string("ca"), "ca1qvqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0jqxuzx4s"); - EXPECT_EQ(address.string("ca"), "ca1qvqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0jqxuzx4s"); - EXPECT_EQ(AddressV3::Discrim_Production, address.discrimination); - EXPECT_EQ(AddressV3::Kind_Single, address.kind); - EXPECT_EQ("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", hex(address.key1)); - EXPECT_EQ("", hex(address.groupKey)); - } - { - // group addr - auto address = AddressV3("addr1s3xuxwfetyfe7q9u3rfn6je9stlvcgmj8rezd87qjjegdtxm3y3f2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59jz29g8j"); - EXPECT_EQ(address.string(), "addr1s3xuxwfetyfe7q9u3rfn6je9stlvcgmj8rezd87qjjegdtxm3y3f2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59jz29g8j"); - EXPECT_EQ(AddressV3::Discrim_Test, address.discrimination); - EXPECT_EQ(AddressV3::Kind_Group, address.kind); - EXPECT_EQ("4dc3393959139f00bc88d33d4b2582fecc237238f2269fc094b286acdb892295", hex(address.key1)); - EXPECT_EQ("6d0b99fdb1cc851fbc8dbcb7d6710fde28df46360c259d482db679c24c4e50b2", hex(address.groupKey)); - } -} - -TEST(CardanoAddress, MnemonicToAddressV2) { - { - // Test from cardano-crypto.js; Test wallet - auto mnemonic = "cost dash dress stove morning robust group affair stomach vacant route volume yellow salute laugh"; - auto wallet = HDWallet(mnemonic, ""); - - PrivateKey masterPrivKey = wallet.getMasterKey(TWCurve::TWCurveED25519Extended); - PrivateKey masterPrivKeyExt = wallet.getMasterKeyExtension(TWCurve::TWCurveED25519Extended); - ASSERT_EQ("a018cd746e128a0be0782b228c275473205445c33b9000a33dd5668b430b5744", hex(masterPrivKey.bytes)); - ASSERT_EQ("26877cfe435fddda02409b839b7386f3738f10a30b95a225f4b720ee71d2505b", hex(masterPrivKeyExt.bytes)); - - { - string addr = wallet.deriveAddress(TWCoinType::TWCoinTypeCardano); - EXPECT_EQ("addr1sna05l45z33zpkm8z44q8f0h57wxvm0c86e34wlmua7gtcrdgrdrzy8ny3walyfjanhe33nsyuh088qr5gepqaen6jsa9r94xvvd7fh6jc3e6x", addr); - } - { - PrivateKey privKey0 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/44'/1815'/0'/0/0")); - PublicKey pubKey0 = privKey0.getPublicKey(TWPublicKeyTypeED25519Extended); - auto addr0 = AddressV2(pubKey0); - EXPECT_EQ("Ae2tdPwUPEZ6RUCnjGHFqi59k5WZLiv3HoCCNGCW8SYc5H9srdTzn1bec4W", addr0.string()); - } - { - PrivateKey privKey1 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/44'/1815'/0'/0/1")); - PublicKey pubKey1 = privKey1.getPublicKey(TWPublicKeyTypeED25519Extended); - auto addr1 = AddressV2(pubKey1); - EXPECT_EQ("Ae2tdPwUPEZ7dnds6ZyhQdmgkrDFFPSDh8jG9RAhswcXt1bRauNw5jczjpV", addr1.string()); - } - { - PrivateKey privKey1 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/44'/1815'/0'/0/2")); - PublicKey pubKey1 = privKey1.getPublicKey(TWPublicKeyTypeED25519Extended); - auto addr1 = AddressV2(pubKey1); - EXPECT_EQ("Ae2tdPwUPEZ8LAVy21zj4BF97iWxKCmPv12W6a18zLX3V7rZDFFVgqUBkKw", addr1.string()); - } - { - PrivateKey privKey0 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/1852'/1815'/0'/0/0")); - PublicKey pubKey0 = privKey0.getPublicKey(TWPublicKeyTypeED25519Extended); - auto addr0 = AddressV3(pubKey0); - EXPECT_EQ("addr1sna05l45z33zpkm8z44q8f0h57wxvm0c86e34wlmua7gtcrdgrdrzy8ny3walyfjanhe33nsyuh088qr5gepqaen6jsa9r94xvvd7fh6jc3e6x", addr0.string()); - } - { - PrivateKey privKey1 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/1852'/1815'/0'/0/1")); - PublicKey pubKey1 = privKey1.getPublicKey(TWPublicKeyTypeED25519Extended); - auto addr1 = AddressV3(pubKey1); - EXPECT_EQ("addr1sjkw630aatyg273m9cpgezvs2unf6xrtw0z7udhguh7ednkhf9p0jduldrg5qsnaz99e3sl4f8t56w0hs0zhql9lacr63mx693ppjw2r5nwehs", addr1.string()); - } - { - PrivateKey privKey1 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/1852'/1815'/0'/0/2")); - PublicKey pubKey1 = privKey1.getPublicKey(TWPublicKeyTypeED25519Extended); - auto addr1 = AddressV3(pubKey1); - EXPECT_EQ("addr1sng939f9el5mnsj4l30kk2f02ea63rwhny5pa69masam4xtsmp5naq6lks0p7pzkn35z7juyd7hhk3zc8p9dc736pu4nzhyy6fusxapa9v5h5c", addr1.string()); - } - } - { - auto mnemonicPlay1 = "youth away raise north opinion slice dash bus soldier dizzy bitter increase saddle live champion"; - auto wallet = HDWallet(mnemonicPlay1, ""); - string addr = wallet.deriveAddress(TWCoinType::TWCoinTypeCardano); - EXPECT_EQ("addr1ssf3e4w2g8gpqlewnt0a4t9kwvdwhxyaaqu7tmru20zgakwf2mdu3jamu779gr3085lykk7r0q8t6lf6p2vfj7u9ma2s7a748vn0se2gxym6da", addr); - } - { - auto mnemonicPlay2 = "return custom two home gain guilt kangaroo supply market current curtain tomorrow heavy blue robot"; - auto wallet = HDWallet(mnemonicPlay2, ""); - string addr = wallet.deriveAddress(TWCoinType::TWCoinTypeCardano); - EXPECT_EQ("addr1sn8hmvmhxw6926mgz3fn5qp205wmu42adg8uehnce3nfr4umecm3mfqmxy4jyl5xewag3kq52vulqpgt386atv3v5upz9j0rl4cc42m707gewk", addr); - } - { - auto mnemonicALDemo = "civil void tool perfect avocado sweet immense fluid arrow aerobic boil flash"; - auto wallet = HDWallet(mnemonicALDemo, ""); - string addr = wallet.deriveAddress(TWCoinType::TWCoinTypeCardano); - EXPECT_EQ("addr1sn32yvavqtnzpqggxf9aa0yypng80gr3anfpwppz8dhztx4cevzp5v6nf40c6d8v6z70fcd76634sdlyfpfpk6d3ya84czk83jlze676vmpf37", addr); - } - { - // V2 Tested against AdaLite - auto mnemonicPlay1 = "youth away raise north opinion slice dash bus soldier dizzy bitter increase saddle live champion"; - auto wallet = HDWallet(mnemonicPlay1, ""); - PrivateKey privateKey = wallet.getKey(TWCoinTypeCardano, DerivationPath(TWPurposeBIP44, TWCoinTypeCardano, DerivationPathIndex(0, true).derivationIndex(), 0, 0)); - PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Extended); - string addr = AddressV2(publicKey).string(); - EXPECT_EQ("Ae2tdPwUPEZJYT9g1JgQWtLveUHavyRxQGi6hVzoQjct7yyCLGgk3pCyx7h", addr); - } - { - // V2 Tested against AdaLite - auto mnemonicPlay2 = "return custom two home gain guilt kangaroo supply market current curtain tomorrow heavy blue robot"; - auto wallet = HDWallet(mnemonicPlay2, ""); - PrivateKey privateKey = wallet.getKey(TWCoinTypeCardano, DerivationPath(TWPurposeBIP44, TWCoinTypeCardano, DerivationPathIndex(0, true).derivationIndex(), 0, 0)); - PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Extended); - string addr = AddressV2(publicKey).string(); - EXPECT_EQ("Ae2tdPwUPEZLtJx7LA2XZ3zzwonH9x9ieX3dMzaTBD3TfXuKczjMSjTecr1", addr); - } - { - // V2 AdaLite Demo phrase, 12-word. AdaLite uses V1 for it, in V2 it produces different addresses. - // In AdaLite V1 addr0 is DdzFFzCqrht7HGoJ87gznLktJGywK1LbAJT2sbd4txmgS7FcYLMQFhawb18ojS9Hx55mrbsHPr7PTraKh14TSQbGBPJHbDZ9QVh6Z6Di - auto mnemonicALDemo = "civil void tool perfect avocado sweet immense fluid arrow aerobic boil flash"; - auto wallet = HDWallet(mnemonicALDemo, ""); - PrivateKey privateKey = wallet.getKey(TWCoinTypeCardano, DerivationPath(TWPurposeBIP44, TWCoinTypeCardano, DerivationPathIndex(0, true).derivationIndex(), 0, 0)); - PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Extended); - string addr = AddressV2(publicKey).string(); - EXPECT_EQ("Ae2tdPwUPEZJbLcD8iLgN7hVGvq66WdR4zocntRekSP97Ds3MvCfmEDjJYu", addr); - } -} - -TEST(CardanoAddress, KeyHashV2) { - auto xpub = parse_hex("e6f04522f875c1563682ca876ddb04c2e2e3ae718e3ff9f11c03dd9f9dccf69869272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000"); - auto hash = AddressV2::keyHash(xpub); - ASSERT_EQ("a1eda96a9952a56c983d9f49117f935af325e8a6c9d38496e945faa8", hex(hash)); -} - -TEST(CardanoAddress, FromPublicKeyInternalV3) { - // tests from chain-lib - { - auto address = AddressV3::createSingle(AddressV3::Discrim_Production, parse_hex("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20")); - auto bech = address.string("ca"); - EXPECT_EQ("ca1qvqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0jqxuzx4s", bech); - EXPECT_EQ("amaqeayeaudaocajbifqydiob4ibceqtcqkrmfyydenbwha5dypsa", address.stringBase32()); - } - { - auto address = AddressV3::createGroup(AddressV3::Discrim_Production, - parse_hex("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"), - parse_hex("292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748") - ); - auto bech = address.string("ca"); - EXPECT_EQ("ca1qsqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0jq2f29vkz6t30xqcnyve5x5mrwwpe8ganc0f78aqyzsjrg3z5v36gguhxny", bech); - EXPECT_EQ("aqaqeayeaudaocajbifqydiob4ibceqtcqkrmfyydenbwha5dypsakjkfmwc2lrpgaytemzugu3doobzhi5typj6h5aecqsdircumr2i", address.stringBase32()); - } - { - auto address = AddressV3::createGroup(AddressV3::Discrim_Test, - parse_hex("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"), - parse_hex("292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748") - ); - auto bech = address.string("ca"); - EXPECT_EQ("ca1ssqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0jq2f29vkz6t30xqcnyve5x5mrwwpe8ganc0f78aqyzsjrg3z5v36gjdetkp", bech); - EXPECT_EQ("qqaqeayeaudaocajbifqydiob4ibceqtcqkrmfyydenbwha5dypsakjkfmwc2lrpgaytemzugu3doobzhi5typj6h5aecqsdircumr2i", address.stringBase32()); - } - { - auto address = AddressV3::createAccount(AddressV3::Discrim_Test, parse_hex("292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748")); - auto bech = address.string("ca"); - EXPECT_EQ("ca1s55j52ev95hz7vp3xgengdfkxuurjw3m8s7nu06qg9pyx3z9ger5samu4rv", bech); - EXPECT_EQ("quusukzmfuxc6mbrgiztinjwg44dsor3hq6t4p2aifbegrcfizduq", address.stringBase32()); - } - // end of chain-lib - { - auto address = AddressV3::createGroup(AddressV3::Discrim_Test, - parse_hex("4dc3393959139f00bc88d33d4b2582fecc237238f2269fc094b286acdb892295"), - parse_hex("6d0b99fdb1cc851fbc8dbcb7d6710fde28df46360c259d482db679c24c4e50b2") - ); - auto bech = address.string("addr"); - EXPECT_EQ("addr1s3xuxwfetyfe7q9u3rfn6je9stlvcgmj8rezd87qjjegdtxm3y3f2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59jz29g8j", bech); - } -} - -TEST(CardanoAddress, FromPublicKeyV2) { - { - // caradano-crypto.js test - auto publicKey = PublicKey(parse_hex("e6f04522f875c1563682ca876ddb04c2e2e3ae718e3ff9f11c03dd9f9dccf69869272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000"), TWPublicKeyTypeED25519Extended); - auto address = AddressV2(publicKey); - ASSERT_EQ(address.string(), "Ae2tdPwUPEZCxt4UV1Uj2AMMRvg5pYPypqZowVptz3GYpK4pkcvn3EjkuNH"); - } - { - // Adalite test account addr0 - auto publicKey = PublicKey(parse_hex("57fd54be7b38bb8952782c2f59aa276928a4dcbb66c8c62ce44f9d623ecd5a03bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4"), TWPublicKeyTypeED25519Extended); - auto address = AddressV2(publicKey); - ASSERT_EQ(address.string(), "Ae2tdPwUPEZ6RUCnjGHFqi59k5WZLiv3HoCCNGCW8SYc5H9srdTzn1bec4W"); - } - { - // Adalite test account addr1 - auto publicKey = PublicKey(parse_hex("25af99056d600f7956312406bdd1cd791975bb1ae91c9d034fc65f326195fcdb247ee97ec351c0820dd12de4ca500232f73a35fe6f86778745bcd57f34d1048d"), TWPublicKeyTypeED25519Extended); - auto address = AddressV2(publicKey); - ASSERT_EQ(address.string(), "Ae2tdPwUPEZ7dnds6ZyhQdmgkrDFFPSDh8jG9RAhswcXt1bRauNw5jczjpV"); - } - { - // Play1 addr0 - auto publicKey = PublicKey(parse_hex("7cee0f30b9d536a786547dd77b35679b6830e945ffde768eb4f2a061b9dba016e513fa1290da1d22e83a41f17eed72d4489483b561fff36b9555ffdb91c430e2"), TWPublicKeyTypeED25519Extended); - auto address = AddressV2(publicKey); - ASSERT_EQ(address.string(), "Ae2tdPwUPEZJYT9g1JgQWtLveUHavyRxQGi6hVzoQjct7yyCLGgk3pCyx7h"); - } -} - -TEST(CardanoAddress, FromPrivateKeyV2) { - { - // mnemonic Test, addr0 - auto privateKey = PrivateKey( - parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), - parse_hex("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff"), - parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4") - ); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Extended); - ASSERT_EQ(hex(publicKey.bytes), "57fd54be7b38bb8952782c2f59aa276928a4dcbb66c8c62ce44f9d623ecd5a03bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4"); - auto address = AddressV2(publicKey); - ASSERT_EQ(address.string(), "Ae2tdPwUPEZ6RUCnjGHFqi59k5WZLiv3HoCCNGCW8SYc5H9srdTzn1bec4W"); - } - { - // mnemonic Play1, addr0 - auto privateKey = PrivateKey( - parse_hex("a089c9423100960440ccd5b7adbd202d1ab1993a7bb30fc88b287d94016df247"), - parse_hex("da86a87f08fb15de1431a6c0ccd5ebf51c3bee81f7eaf714801bbbe4d903154a"), - parse_hex("e513fa1290da1d22e83a41f17eed72d4489483b561fff36b9555ffdb91c430e2") - ); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Extended); - ASSERT_EQ(hex(publicKey.bytes), "7cee0f30b9d536a786547dd77b35679b6830e945ffde768eb4f2a061b9dba016e513fa1290da1d22e83a41f17eed72d4489483b561fff36b9555ffdb91c430e2"); - auto address = AddressV2(publicKey); - ASSERT_EQ(address.string(), "Ae2tdPwUPEZJYT9g1JgQWtLveUHavyRxQGi6hVzoQjct7yyCLGgk3pCyx7h"); - } - { - // from cardano-crypto.js test - auto privateKey = PrivateKey( - parse_hex("d809b1b4b4c74734037f76aace501730a3fe2fca30b5102df99ad3f7c0103e48"), - parse_hex("d54cde47e9041b31f3e6873d700d83f7a937bea746dadfa2c5b0a6a92502356c"), - parse_hex("69272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000") - ); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Extended); - ASSERT_EQ(hex(publicKey.bytes), "e6f04522f875c1563682ca876ddb04c2e2e3ae718e3ff9f11c03dd9f9dccf69869272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000"); - auto address = AddressV2(publicKey); - ASSERT_EQ(address.string(), "Ae2tdPwUPEZCxt4UV1Uj2AMMRvg5pYPypqZowVptz3GYpK4pkcvn3EjkuNH"); - } -} - -TEST(CardanoAddress, PrivateKeyExtended) { - // check extended key lengths, private key 3x32 bytes, public key 64 bytes - auto privateKeyExt = PrivateKey( - parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), - parse_hex("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff"), - parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4") - ); - auto publicKeyExt = privateKeyExt.getPublicKey(TWPublicKeyTypeED25519Extended); - ASSERT_EQ(64, publicKeyExt.bytes.size()); - - // Non-extended: both are 32 bytes. - auto privateKeyNonext = PrivateKey( - parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744") - ); - auto publicKeyNonext = privateKeyNonext.getPublicKey(TWPublicKeyTypeED25519); - ASSERT_EQ(32, publicKeyNonext.bytes.size()); -} - -TEST(CardanoAddress, FromStringNegativeInvalidString) { - try { - auto address = AddressV3("__INVALID_ADDRESS__"); - } catch (...) { - return; - } - FAIL() << "Expected exception!"; -} - -TEST(CardanoAddress, FromStringNegativeBadChecksumV2) { - try { - auto address = AddressV3("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvm"); - } catch (...) { - return; - } - FAIL() << "Expected exception!"; -} - -TEST(CardanoAddress, DataV3) { - // group addr - auto address = AddressV3("addr1s3xuxwfetyfe7q9u3rfn6je9stlvcgmj8rezd87qjjegdtxm3y3f2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59jz29g8j"); - EXPECT_EQ("4dc3393959139f00bc88d33d4b2582fecc237238f2269fc094b286acdb892295", hex(address.key1)); - EXPECT_EQ("6d0b99fdb1cc851fbc8dbcb7d6710fde28df46360c259d482db679c24c4e50b2", hex(address.groupKey)); - Data data = address.data(); - EXPECT_EQ( - "0104" - "20" "4dc3393959139f00bc88d33d4b2582fecc237238f2269fc094b286acdb892295" - "20" "6d0b99fdb1cc851fbc8dbcb7d6710fde28df46360c259d482db679c24c4e50b2", - hex(data) - ); -} - -TEST(CardanoAddress, FromDataV3) { - Data data = parse_hex("0104204dc3393959139f00bc88d33d4b2582fecc237238f2269fc094b286acdb892295206d0b99fdb1cc851fbc8dbcb7d6710fde28df46360c259d482db679c24c4e50b2"); - auto address = AddressV3(data); - EXPECT_EQ(address.string(), "addr1s3xuxwfetyfe7q9u3rfn6je9stlvcgmj8rezd87qjjegdtxm3y3f2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59jz29g8j"); - EXPECT_EQ("6d0b99fdb1cc851fbc8dbcb7d6710fde28df46360c259d482db679c24c4e50b2", hex(address.groupKey)); -} - -TEST(CardanoAddress, CopyConstructorLegacy) { - AddressV3 address1 = AddressV3("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvx"); - EXPECT_TRUE(address1.legacyAddressV2.has_value()); - AddressV3 address2 = AddressV3(address1); - EXPECT_TRUE(address2.legacyAddressV2.has_value()); - EXPECT_TRUE(*(address2.legacyAddressV2) == *(address1.legacyAddressV2)); - // if it was not a deep copy, double freeing would occur -} - -TEST(CardanoAddress, AssignmentOperatorLegacy) { - AddressV3 addr1leg = AddressV3("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvx"); - EXPECT_TRUE(addr1leg.legacyAddressV2.has_value()); - AddressV3 addr2nonleg = AddressV3("addr1s3hdtrqgs47l7ue5srga8wmk9dzw279x9e7lxadalt6z0fk64nnn2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59j5lempe"); - EXPECT_FALSE(addr2nonleg.legacyAddressV2.has_value()); - AddressV3 addr3leg = AddressV3("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvx"); - EXPECT_TRUE(addr3leg.legacyAddressV2.has_value()); - - AddressV3 address = addr1leg; - EXPECT_TRUE(address.legacyAddressV2.has_value()); - EXPECT_TRUE(*address.legacyAddressV2 == *addr1leg.legacyAddressV2); - address = addr2nonleg; - EXPECT_FALSE(address.legacyAddressV2.has_value()); - address = addr3leg; - EXPECT_TRUE(address.legacyAddressV2.has_value()); - EXPECT_TRUE(*address.legacyAddressV2 == *addr3leg.legacyAddressV2); -} diff --git a/tests/Cardano/TWCardanoAddressTests.cpp b/tests/Cardano/TWCardanoAddressTests.cpp deleted file mode 100644 index f920bffeb33..00000000000 --- a/tests/Cardano/TWCardanoAddressTests.cpp +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include -#include -#include -#include "../interface/TWTestUtilities.h" -#include "PrivateKey.h" - -#include - -TEST(TWCardano, Address) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71effbf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4").get())); - ASSERT_NE(nullptr, privateKey.get()); - auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519Extended(privateKey.get())); - ASSERT_NE(nullptr, publicKey.get()); - ASSERT_EQ(64, publicKey.get()->impl.bytes.size()); - auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeCardano)); - auto addressString = WRAPS(TWAnyAddressDescription(address.get())); - assertStringsEqual(addressString, "addr1s3tl64970vuthz2j0qkz7kd2ya5j3fxuhdnv333vu38e6c37e4dq80ek4raf7hs3adag2tzpuxz7895a2x8xde5f8jqa8lrjyuqfj5k50pm668"); - - auto address2 = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(STRING("addr1s3tl64970vuthz2j0qkz7kd2ya5j3fxuhdnv333vu38e6c37e4dq80ek4raf7hs3adag2tzpuxz7895a2x8xde5f8jqa8lrjyuqfj5k50pm668").get(), TWCoinTypeCardano)); - ASSERT_NE(nullptr, address2.get()); - auto address2String = WRAPS(TWAnyAddressDescription(address2.get())); - assertStringsEqual(address2String, "addr1s3tl64970vuthz2j0qkz7kd2ya5j3fxuhdnv333vu38e6c37e4dq80ek4raf7hs3adag2tzpuxz7895a2x8xde5f8jqa8lrjyuqfj5k50pm668"); - - ASSERT_TRUE(TWAnyAddressEqual(address.get(), address2.get())); -} - -TEST(TWCardano, SigningNotImplemented) { - // not implemented, returns empty data - auto result = WRAPD(TWAnySignerSign(WRAPD(TWDataCreateWithSize(0)).get(), TWCoinType::TWCoinTypeCardano)); - EXPECT_EQ(TWDataSize(result.get()), 0); -} diff --git a/tests/CoinAddressDerivationTests.cpp b/tests/CoinAddressDerivationTests.cpp deleted file mode 100644 index d8dbdd586ef..00000000000 --- a/tests/CoinAddressDerivationTests.cpp +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Coin.h" -#include "HexCoding.h" - -#include - -#include -#include - -namespace TW { - -TEST(Coin, DeriveAddress) { - auto dummyKeyData = parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646"); - const auto privateKey = PrivateKey(dummyKeyData); - const auto privateKeyExt = PrivateKey(dummyKeyData, dummyKeyData, dummyKeyData); - - EXPECT_EQ(TW::deriveAddress(TWCoinTypeAion, privateKey), "0xa0010b0ea04ba4d76ca6e5e9900bacf19bc4402eaec7e36ea7ddd8eed48f60f3"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeBinance, privateKey), "bnb1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0mlq0d0"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeBitcoin, privateKey), "bc1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z00ppggv"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeBitcoinCash, privateKey), "bitcoincash:qz7eyzytkl5z6cg6nw20hd62pyyp22mcfuardfd2vn"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeCallisto, privateKey), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeCosmos, privateKey), "cosmos1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0emlrvp"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeDash, privateKey), "XsyCV5yojxF4y3bYeEiVYqarvRgsWFELZL"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeDecred, privateKey), "Dsp4u8xxTHSZU2ELWTQLQP77xJhgeWrTsGK"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeDigiByte, privateKey), "dgb1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z0c69ssz"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeEthereum, privateKey), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeEthereumClassic, privateKey), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeGoChain, privateKey), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeGroestlcoin, privateKey), "grs1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z0jsaf3d"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeICON, privateKey), "hx4728fc65c31728f0d3538b8783b5394b31a136b9"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeIoTeX, privateKey), "io1nk9x9ajk4rgkzhqjjn7hr6w0k0jg2kj0zgdt6h"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeLitecoin, privateKey), "ltc1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z0tamvsu"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeViacoin, privateKey), "via1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z09y9mn2"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeNimiq, privateKey), "NQ74 D40G N3M0 9EJD ET56 UPLR 02VC X6DU 8G1E"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeOntology, privateKey), "AeicEjZyiXKgUeSBbYQHxsU1X3V5Buori5"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypePOANetwork, privateKey), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeXRP, privateKey), "rJHMeqKu8Ep7Fazx8MQG6JunaafBXz93YQ"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeStellar, privateKey), "GDXJHJHWN6GRNOAZXON6XH74ZX6NYFAS5B7642RSJQVJTIPA4ZYUQLEB"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeTezos, privateKey), "tz1gcEWswVU6dxfNQWbhTgaZrUrNUFwrsT4z"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeThunderToken, privateKey), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeTomoChain, privateKey), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeTron, privateKey), "TQLCsShbQNXMTVCjprY64qZmEA4rBarpQp"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeVeChain, privateKey), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeWanchain, privateKey), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeZcash, privateKey), "t1b9xfAk3kZp5Qk3rinDPq7zzLkJGHTChDS"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeZcoin, privateKey), "aHzpPjmY132KseS4nkiQTbDahTEXqesY89"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeNano, privateKey), "nano_1qepdf4k95dhb5gsmhmq3iddqsxiafwkihunm7irn48jdiwdtnn6pe93k3f6"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeKin, privateKey), "GDXJHJHWN6GRNOAZXON6XH74ZX6NYFAS5B7642RSJQVJTIPA4ZYUQLEB"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeTheta, privateKey), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeQtum, privateKey), "QdtLm8ccxhuJFF5zCgikpaghbM3thdaGsW"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeNULS, privateKey), "NULSd6HgfXT3m5JBGxeCZXHRQbb82FKgZGT8o"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeEOS, privateKey), "EOS5TrYnZP1RkDSUMzBY4GanCy6AP68kCMdkAb5EACkAwkdgRLShz"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeIoTeX, privateKey), "io1nk9x9ajk4rgkzhqjjn7hr6w0k0jg2kj0zgdt6h"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeDogecoin, privateKey), "DNRTC6GZ5evmM7BZWwPqF54fyDqUqULMyu"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeZilliqa, privateKey), "zil1j2cvtd7j9n7fnxfv2r3neucjw8tp4xz9sp07v4"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeRavencoin, privateKey), "RSZYjMDCP4q3t7NAFXPPnqEGrMZn971pdB"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeAeternity, privateKey), "ak_2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5FeVfcw"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeTerra, privateKey), "terra1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0ll9rwp"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeNebulas, privateKey), "n1XTciu9ZRYt3ni7SxNBmivk9Y6XpP6VrhT"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeMonacoin, privateKey), "MRBWtGEKHGCHhmyJ1L4CwaWQZJzM5DnVcs"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeFIO, privateKey), "FIO5TrYnZP1RkDSUMzBY4GanCy6AP68kCMdkAb5EACkAwkdgRLShz"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeAlgorand, privateKey), "52J2J5TPRULLQGN3TPVZ77GN7TOBIEXIP7XGUMSMFKM2DYHGOFEOGBP2T4"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeKusama, privateKey), "Hy8mqcexg5FMwMYnQvzrUvD723qMxDjMRU9HdNCnTsMAypY"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypePolkadot, privateKey), "16PpFrXrC6Ko3pYcyMAx6gPMp3mFFaxgyYMt4G5brkgNcSz8"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeKava, privateKey), "kava1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z09wt76x"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeCardano, privateKeyExt), "addr1sn0sqpku8yfj2dazh7czyspcd5flzkzu6x9wqt0lsn4wfvds9sg3s3jxgeryv3jxgeryv3jxgeryv3jxgeryv3jxgeryv3jxgeryv3jxdnpy03"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeNEO, privateKeyExt), "AeicEjZyiXKgUeSBbYQHxsU1X3V5Buori5"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeFilecoin, privateKey), "f1qsx7qwiojh5duxbxhbqgnlyx5hmpcf7mcz5oxsy"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeNEAR, privateKey), "ee93a4f66f8d16b819bb9beb9ffccdfcdc1412e87fee6a324c2a99a1e0e67148"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeSolana, privateKey), "H4JcMPicKkHcxxDjkyyrLoQj7Kcibd9t815ak4UvTr9M"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeElrond, privateKey), "erd1a6f6fan035ttsxdmn04ellxdlnwpgyhg0lhx5vjv92v6rc8xw9yq83344f"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeOasis, privateKey), "oasis1qzw4h3wmyjtrttduqqrs8udggyy2emwdzqmuzwg4"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeTHORChain, privateKey), "thor1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0luxce7"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeAvalancheCChain, privateKey), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeXDai, privateKey), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeCelo, privateKey), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); -} - -int countThreadReady = 0; -std::mutex countThreadReadyMutex; - -void useCoinFromThread() { - const int tryCount = 20; - for (int i = 0; i < tryCount; ++i) { - // perform some operations - TW::validateAddress(TWCoinTypeZilliqa, "zil1j8xae6lggm8y63m3y2r7aefu797ze7mhzulnqg"); - TW::validateAddress(TWCoinTypeEthereum, "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); - const auto coinTypes = TW::getCoinTypes(); - } - countThreadReadyMutex.lock(); - ++countThreadReady; - countThreadReadyMutex.unlock(); -} - -TEST(Coin, InitMultithread) { - const int numThread = 20; - countThreadReady = 0; - std::thread thread[numThread]; - // execute in threads - for (int i = 0; i < numThread; ++i) { - thread[i] = std::thread(useCoinFromThread); - } - // wait for completion - for (int i = 0; i < numThread; ++i) { - thread[i].join(); - } - // check that all completed OK - ASSERT_EQ(countThreadReady, numThread); -} - -TEST(Coin, SupportedCoins) { - const auto coinTypes = TW::getCoinTypes(); - for (auto c: coinTypes) { - const auto similarTypes = TW::getSimilarCoinTypes(c); - // For all coins, supported coins should include this coin as well - EXPECT_TRUE(std::find(similarTypes.begin(), similarTypes.end(), c) != similarTypes.end()); - } -} - -} // namespace TW diff --git a/tests/Cosmos/SignerTests.cpp b/tests/Cosmos/SignerTests.cpp deleted file mode 100644 index f43ff55238e..00000000000 --- a/tests/Cosmos/SignerTests.cpp +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Coin.h" -#include "HexCoding.h" -#include "Base64.h" -#include "proto/Cosmos.pb.h" -#include "Cosmos/Address.h" -#include "Cosmos/Signer.h" - -#include -#include - -using namespace TW; -using namespace TW::Cosmos; - -TEST(CosmosSigner, SignTx) { - auto input = Proto::SigningInput(); - input.set_account_number(1037); - input.set_chain_id("gaia-13003"); - input.set_memo(""); - input.set_sequence(8); - - auto fromAddress = Address("cosmos", parse_hex("BC2DA90C84049370D1B7C528BC164BC588833F21")); - auto toAddress = Address("cosmos", parse_hex("12E8FE8B81ECC1F4F774EA6EC8DF267138B9F2D9")); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_send_coins_message(); - message.set_from_address(fromAddress.string()); - message.set_to_address(toAddress.string()); - auto amountOfTx = message.add_amounts(); - amountOfTx->set_denom("muon"); - amountOfTx->set_amount(1); - - auto& fee = *input.mutable_fee(); - fee.set_gas(200000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("muon"); - amountOfFee->set_amount(200); - - std::string json; - google::protobuf::util::MessageToJsonString(input, &json); - - ASSERT_EQ(R"({"accountNumber":"1037","chainId":"gaia-13003","fee":{"amounts":[{"denom":"muon","amount":"200"}],"gas":"200000"},"sequence":"8","messages":[{"sendCoinsMessage":{"fromAddress":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","toAddress":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573","amounts":[{"denom":"muon","amount":"1"}]}}]})", json); - - auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input); - - ASSERT_EQ(R"({"mode":"block","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"muon"}],"from_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","to_address":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"/D74mdIGyIB3/sQvIboLTfS9P9EV/fYGrgHZE2/vNj9X6eM6e57G3atljNB+PABnRw3pTk51uXmhCFop8O/ZJg=="}]}})", output.json()); - ASSERT_EQ(hex(output.signature()), "fc3ef899d206c88077fec42f21ba0b4df4bd3fd115fdf606ae01d9136fef363f57e9e33a7b9ec6ddab658cd07e3c0067470de94e4e75b979a1085a29f0efd926"); - - /* - the sample tx on testnet - https://hubble.figment.network/chains/gaia-13003/blocks/142933/transactions/3A9206598C3D2E75A5EC074FD33EA53EB18EC729357F0965971C1C51F812AEA3?format=json - */ -} - -TEST(CosmosSigner, SignTxWithMode) { - auto input = Proto::SigningInput(); - input.set_account_number(1037); - input.set_chain_id("gaia-13003"); - input.set_memo(""); - input.set_sequence(8); - input.set_mode(Proto::BroadcastMode::ASYNC); - - auto fromAddress = Address("cosmos", parse_hex("BC2DA90C84049370D1B7C528BC164BC588833F21")); - auto toAddress = Address("cosmos", parse_hex("12E8FE8B81ECC1F4F774EA6EC8DF267138B9F2D9")); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_send_coins_message(); - message.set_from_address(fromAddress.string()); - message.set_to_address(toAddress.string()); - auto amountOfTx = message.add_amounts(); - amountOfTx->set_denom("muon"); - amountOfTx->set_amount(1); - - auto& fee = *input.mutable_fee(); - fee.set_gas(200000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("muon"); - amountOfFee->set_amount(200); - - auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); - input.set_private_key(privateKey.data(), privateKey.size()); - - { - auto output = Signer::sign(input); - ASSERT_EQ(R"({"mode":"async","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"muon"}],"from_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","to_address":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"/D74mdIGyIB3/sQvIboLTfS9P9EV/fYGrgHZE2/vNj9X6eM6e57G3atljNB+PABnRw3pTk51uXmhCFop8O/ZJg=="}]}})", output.json()); - } - input.set_mode(Proto::BroadcastMode::SYNC); - { - auto output = Signer::sign(input); - ASSERT_EQ(R"({"mode":"sync","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"muon"}],"from_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","to_address":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"/D74mdIGyIB3/sQvIboLTfS9P9EV/fYGrgHZE2/vNj9X6eM6e57G3atljNB+PABnRw3pTk51uXmhCFop8O/ZJg=="}]}})", output.json()); - } -} diff --git a/tests/Cosmos/StakingTests.cpp b/tests/Cosmos/StakingTests.cpp deleted file mode 100644 index d7745b4b10c..00000000000 --- a/tests/Cosmos/StakingTests.cpp +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Coin.h" -#include "HexCoding.h" -#include "Base64.h" -#include "proto/Cosmos.pb.h" -#include "Cosmos/Address.h" -#include "Cosmos/Signer.h" - -#include - -using namespace TW; -using namespace TW::Cosmos; - -TEST(CosmosStaking, Staking) { - auto input = Proto::SigningInput(); - input.set_account_number(1037); - input.set_chain_id("gaia-13003"); - input.set_memo(""); - input.set_sequence(7); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_stake_message(); - message.set_delegator_address("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); - message.set_validator_address("cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"); - auto& amountOfTx = *message.mutable_amount(); - amountOfTx.set_denom("muon"); - amountOfTx.set_amount(10); - - auto& fee = *input.mutable_fee(); - fee.set_gas(101721); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("muon"); - amountOfFee->set_amount(1018); - - auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input); - - ASSERT_EQ(output.json(), "{\"mode\":\"block\",\"tx\":{\"fee\":{\"amount\":[{\"amount\":\"1018\",\"denom\":\"muon\"}],\"gas\":\"101721\"},\"memo\":\"\",\"msg\":[{\"type\":\"cosmos-sdk/MsgDelegate\",\"value\":{\"amount\":{\"amount\":\"10\",\"denom\":\"muon\"},\"delegator_address\":\"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02\",\"validator_address\":\"cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp\"}}],\"signatures\":[{\"pub_key\":{\"type\":\"tendermint/PubKeySecp256k1\",\"value\":\"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F\"},\"signature\":\"wIvfbCsLRCjzeXXoXTKfHLGXRbAAmUp0O134HVfVc6pfdVNJvvzISMHRUHgYcjsSiFlLyR32heia/yLgMDtIYQ==\"}]}}"); - ASSERT_EQ(hex(output.signature()), "c08bdf6c2b0b4428f37975e85d329f1cb19745b000994a743b5df81d57d573aa5f755349befcc848c1d1507818723b1288594bc91df685e89aff22e0303b4861"); -} - -TEST(CosmosStaking, Unstaking) { - auto input = Proto::SigningInput(); - input.set_account_number(1037); - input.set_chain_id("gaia-13003"); - input.set_memo(""); - input.set_sequence(7); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_unstake_message(); - message.set_delegator_address("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); - message.set_validator_address("cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"); - auto& amountOfTx = *message.mutable_amount(); - amountOfTx.set_denom("muon"); - amountOfTx.set_amount(10); - - auto& fee = *input.mutable_fee(); - fee.set_gas(101721); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("muon"); - amountOfFee->set_amount(1018); - - auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input); - - ASSERT_EQ(output.json(), "{\"mode\":\"block\",\"tx\":{\"fee\":{\"amount\":[{\"amount\":\"1018\",\"denom\":\"muon\"}],\"gas\":\"101721\"},\"memo\":\"\",\"msg\":[{\"type\":\"cosmos-sdk/MsgUndelegate\",\"value\":{\"amount\":{\"amount\":\"10\",\"denom\":\"muon\"},\"delegator_address\":\"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02\",\"validator_address\":\"cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp\"}}],\"signatures\":[{\"pub_key\":{\"type\":\"tendermint/PubKeySecp256k1\",\"value\":\"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F\"},\"signature\":\"j4WpUVohGIHa6/s0bCvuyjq1wtQGqbOtQCz92qPQjisTN44Tz++Ozx1lAP6F0M4+eTA03XerqQ8hZCeAfL/3nw==\"}]}}"); - ASSERT_EQ(hex(output.signature()), "8f85a9515a211881daebfb346c2beeca3ab5c2d406a9b3ad402cfddaa3d08e2b13378e13cfef8ecf1d6500fe85d0ce3e793034dd77aba90f216427807cbff79f"); -} - -TEST(CosmosStaking, Restaking) { - auto input = Proto::SigningInput(); - input.set_account_number(1037); - input.set_chain_id("gaia-13003"); - input.set_memo(""); - input.set_sequence(7); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_restake_message(); - message.set_delegator_address("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); - message.set_validator_dst_address("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); - message.set_validator_src_address("cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"); - - auto& amountOfTx = *message.mutable_amount(); - amountOfTx.set_denom("muon"); - amountOfTx.set_amount(10); - - auto& fee = *input.mutable_fee(); - fee.set_gas(101721); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("muon"); - amountOfFee->set_amount(1018); - - auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input); - - ASSERT_EQ(output.json(), "{\"mode\":\"block\",\"tx\":{\"fee\":{\"amount\":[{\"amount\":\"1018\",\"denom\":\"muon\"}],\"gas\":\"101721\"},\"memo\":\"\",\"msg\":[{\"type\":\"cosmos-sdk/MsgBeginRedelegate\",\"value\":{\"amount\":{\"amount\":\"10\",\"denom\":\"muon\"},\"delegator_address\":\"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02\",\"validator_dst_address\":\"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02\",\"validator_src_address\":\"cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp\"}}],\"signatures\":[{\"pub_key\":{\"type\":\"tendermint/PubKeySecp256k1\",\"value\":\"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F\"},\"signature\":\"5k03Yb0loovvzagMCg4gjQJP2woriZVRcOZaXF1FSros6B1X4B8MEm3lpZwrWBJMEJVgyYA9ZaF6FLVI3WxQ2w==\"}]}}"); - ASSERT_EQ(hex(output.signature()), "e64d3761bd25a28befcda80c0a0e208d024fdb0a2b89955170e65a5c5d454aba2ce81d57e01f0c126de5a59c2b58124c109560c9803d65a17a14b548dd6c50db"); -} - -TEST(CosmosStaking, Withdraw) { - auto input = Proto::SigningInput(); - input.set_account_number(1037); - input.set_chain_id("gaia-13003"); - input.set_memo(""); - input.set_sequence(7); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_withdraw_stake_reward_message(); - message.set_delegator_address("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); - message.set_validator_address("cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"); - - auto& fee = *input.mutable_fee(); - fee.set_gas(101721); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("muon"); - amountOfFee->set_amount(1018); - - auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input); - - ASSERT_EQ( output.json(), "{\"mode\":\"block\",\"tx\":{\"fee\":{\"amount\":[{\"amount\":\"1018\",\"denom\":\"muon\"}],\"gas\":\"101721\"},\"memo\":\"\",\"msg\":[{\"type\":\"cosmos-sdk/MsgWithdrawDelegationReward\",\"value\":{\"delegator_address\":\"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02\",\"validator_address\":\"cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp\"}}],\"signatures\":[{\"pub_key\":{\"type\":\"tendermint/PubKeySecp256k1\",\"value\":\"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F\"},\"signature\":\"VG8NZzVvavlM+1qyK5dOSZwzEj8sLCkvTw5kh44Oco9GQxBf13FVC+s/I3HwiICqo4+o8jNMEDp3nx2C0tuY1g==\"}]}}"); - ASSERT_EQ(hex(output.signature()), "546f0d67356f6af94cfb5ab22b974e499c33123f2c2c292f4f0e64878e0e728f4643105fd771550beb3f2371f08880aaa38fa8f2334c103a779f1d82d2db98d6"); -} - -TEST(CosmosStaking, WithdrawAllRaw) { - auto input = Proto::SigningInput(); - input.set_account_number(1037); - input.set_chain_id("gaia-13003"); - input.set_memo(""); - input.set_sequence(7); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_raw_json_message(); - message.set_type("cosmos-sdk/MsgWithdrawDelegationRewardsAll"); - message.set_value("{\"delegator_address\":\"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02\"}"); - auto& fee = *input.mutable_fee(); - fee.set_gas(101721); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("muon"); - amountOfFee->set_amount(1018); - - auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input); - - ASSERT_EQ(output.json(), "{\"mode\":\"block\",\"tx\":{\"fee\":{\"amount\":[{\"amount\":\"1018\",\"denom\":\"muon\"}],\"gas\":\"101721\"},\"memo\":\"\",\"msg\":[{\"type\":\"cosmos-sdk/MsgWithdrawDelegationRewardsAll\",\"value\":{\"delegator_address\":\"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02\"}}],\"signatures\":[{\"pub_key\":{\"type\":\"tendermint/PubKeySecp256k1\",\"value\":\"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F\"},\"signature\":\"ImvsgnfbjebxzeBCUPeOcMoOJWMV3IhWM1apV20WiS4K11iA50fe0uXr4Xf/RTxUDXTm56cne/OjOr77BG99Aw==\"}]}}"); - ASSERT_EQ(hex(output.signature()), "226bec8277db8de6f1cde04250f78e70ca0e256315dc88563356a9576d16892e0ad75880e747ded2e5ebe177ff453c540d74e6e7a7277bf3a33abefb046f7d03"); -} diff --git a/tests/CryptoOrg/TWAnySignerTests.cpp b/tests/CryptoOrg/TWAnySignerTests.cpp deleted file mode 100644 index 694b34936f0..00000000000 --- a/tests/CryptoOrg/TWAnySignerTests.cpp +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include "proto/Cosmos.pb.h" -#include "HexCoding.h" - -#include "../interface/TWTestUtilities.h" -#include - -using namespace TW; - -TEST(TWAnySignerCryptoorg, SignTx_DDCCE4) { - auto input = Cosmos::Proto::SigningInput(); - input.set_account_number(125798); - input.set_sequence(0); - input.set_chain_id("crypto-org-chain-mainnet-1"); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_send_coins_message(); - message.set_from_address("cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0"); - message.set_to_address("cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus"); - auto amountOfTx = message.add_amounts(); - amountOfTx->set_denom("basecro"); - amountOfTx->set_amount(100000000); - - auto& fee = *input.mutable_fee(); - fee.set_gas(200000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("basecro"); - amountOfFee->set_amount(5000); - - auto privateKey = parse_hex("200e439e39cf1aad465ee3de6166247f914cbc0f823fc2dd48bf16dcd556f39d"); - input.set_private_key(privateKey.data(), privateKey.size()); - - Cosmos::Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeCryptoOrg); - - assertJSONEqual(output.json(), R"( - { - "mode": "block", - "tx": { - "fee": { - "amount": [ - { - "amount": "5000", - "denom": "basecro" - } - ], - "gas": "200000" - }, - "memo": "", - "msg": [ - { - "type": "cosmos-sdk/MsgSend", - "value": { - "amount": [ - { - "amount": "100000000", - "denom": "basecro" - } - ], - "from_address": "cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0", - "to_address": "cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus" - } - } - ], - "signatures": [ - { - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A4gxsGFiPn6L5Z2IjHEISkXI0IkwfL9exV3GLB171Wvj" - }, - "signature": "5+5rSFFg0FE9cTklQWQHNktBDJsz7UCnMSgF0t0+gYcrIhEWUyTtibXaHZQbKAAaciJ1BkHXYREjU55VswByVg==" - } - ] - } - } - )"); - - /// https://crypto.org/explorer/tx/DDCCE4052040B05914CADEFE78C0A75BE363AE39504E7EF6B2EDB8A9072AD44B -} diff --git a/tests/Elrond/SignerTests.cpp b/tests/Elrond/SignerTests.cpp deleted file mode 100644 index 39a019caf65..00000000000 --- a/tests/Elrond/SignerTests.cpp +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include "boost/format.hpp" - -#include "HexCoding.h" -#include "PrivateKey.h" -#include "PublicKey.h" -#include "Elrond/Signer.h" -#include "Elrond/Address.h" -#include "TestAccounts.h" - -using namespace TW; -using namespace TW::Elrond; - - -TEST(ElrondSigner, Sign) { - auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - input.mutable_transaction()->set_nonce(0); - input.mutable_transaction()->set_value("0"); - input.mutable_transaction()->set_sender(ALICE_BECH32); - input.mutable_transaction()->set_receiver(BOB_BECH32); - input.mutable_transaction()->set_gas_price(1000000000); - input.mutable_transaction()->set_gas_limit(50000); - input.mutable_transaction()->set_data("foo"); - input.mutable_transaction()->set_chain_id("1"); - input.mutable_transaction()->set_version(1); - - auto output = Signer::sign(input); - auto signature = output.signature(); - auto encoded = output.encoded(); - auto expectedSignature = "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00"; - auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); - - ASSERT_EQ(expectedSignature, signature); - ASSERT_EQ(expectedEncoded, encoded); -} - -TEST(ElrondSigner, SignJSON) { - // Shuffle some fields, assume arbitrary order in the input - auto input = (boost::format(R"({"transaction" : {"data":"foo","value":"0","nonce":0,"receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"chainId":"1","version":1}})") % BOB_BECH32 % ALICE_BECH32).str(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); - - auto encoded = Signer::signJSON(input, privateKey.bytes); - auto expectedSignature = "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00"; - auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); - - ASSERT_EQ(expectedEncoded, encoded); -} - -TEST(ElrondSigner, SignWithoutData) { - auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - input.mutable_transaction()->set_nonce(0); - input.mutable_transaction()->set_value("0"); - input.mutable_transaction()->set_sender(ALICE_BECH32); - input.mutable_transaction()->set_receiver(BOB_BECH32); - input.mutable_transaction()->set_gas_price(1000000000); - input.mutable_transaction()->set_gas_limit(50000); - input.mutable_transaction()->set_data(""); - input.mutable_transaction()->set_chain_id("1"); - input.mutable_transaction()->set_version(1); - - auto output = Signer::sign(input); - auto signature = output.signature(); - auto encoded = output.encoded(); - auto expectedSignature = "3079d37bfbdbe66fbb4c4b186144f9d9ad5b4b08fbcd6083be0688cf1171123109dfdefdbabf91425c757ca109b6db6d674cb9aeebb19a1a51333565abb53109"; - auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":1,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); - - ASSERT_EQ(expectedSignature, signature); - ASSERT_EQ(expectedEncoded, encoded); -} - -TEST(ElrondSigner, SignJSONWithoutData) { - // Shuffle some fields, assume arbitrary order in the input - auto input = (boost::format(R"({"transaction" : {"value":"0","nonce":0,"receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"chainId":"1","version":1}})") % BOB_BECH32 % ALICE_BECH32).str(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); - - auto encoded = Signer::signJSON(input, privateKey.bytes); - auto expectedSignature = "3079d37bfbdbe66fbb4c4b186144f9d9ad5b4b08fbcd6083be0688cf1171123109dfdefdbabf91425c757ca109b6db6d674cb9aeebb19a1a51333565abb53109"; - auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":1,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); - - ASSERT_EQ(expectedEncoded, encoded); -} diff --git a/tests/Elrond/TestAccounts.h b/tests/Elrond/TestAccounts.h deleted file mode 100644 index 55060002abd..00000000000 --- a/tests/Elrond/TestAccounts.h +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -const auto ALICE_BECH32 = "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz"; -const auto ALICE_SEED_HEX = "1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf"; -const auto ALICE_PUBKEY_HEX = "fd691bb5e85d102687d81079dffce842d4dc328276d2d4c60d8fd1c3433c3293"; -const auto BOB_BECH32 = "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r"; -const auto BOB_SEED_HEX = "e3a3a3d1ac40d42d8fd4c569a9749b65a1250dd3d10b6f4e438297662ea4850e"; -const auto BOB_PUBKEY_HEX = "c70cf50b238372fffaf7b7c5723b06b57859d424a2da621bcc1b2f317543aa36"; -const auto CAROL_BECH32 = "erd19nu5t7hszckwah5nlcadmk5rlchtugzplznskffpwecygcu0520s9tnyy0"; -const auto CAROL_SEED_HEX = "a926316cc3daf8ff25ba3e417797e6dfd51f62ae735ab07148234732f7314052"; -const auto CAROL_PUBKEY_HEX = "2cf945faf0162ceede93fe3addda83fe2ebe2041f8a70b2521767044638fa29f"; diff --git a/tests/Ethereum/FeeTests.cpp b/tests/Ethereum/FeeTests.cpp deleted file mode 100644 index 643cbb12d4f..00000000000 --- a/tests/Ethereum/FeeTests.cpp +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright © 2017-2021 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 "Ethereum/Fee.h" -#include "uint256.h" -#include "../interface/TWTestUtilities.h" - -#include -#include - -using namespace TW::Ethereum; -using namespace TW; - -extern std::string TESTS_ROOT; - -static nlohmann::json load_json(std::string path) { - std::ifstream stream(path); - nlohmann::json json; - stream >> json; - return json; -} - -TEST(EthereumFee, suggestBaseFeeAndTip) { - const auto path = TESTS_ROOT + "/Ethereum/Data/eth_feeHistory3.json"; - const auto history = load_json(path); - - auto fee = Fee::suggestFee(history).dump(); - auto expected = R"|( - { - "baseFee": "44208904215", - "maxPriorityFee": "1500000000" - } - )|"; - assertJSONEqual(fee, expected); -} - -TEST(EthereumFee, suggestHighBaseFee) { - const auto path = TESTS_ROOT + "/Ethereum/Data/eth_feeHistory4.json"; - const auto history = load_json(path); - - auto fee = Fee::suggestFee(history).dump(); - auto expected = R"|( - { - "baseFee": "1098508176069", - "maxPriorityFee": "23492129063" - } - )|"; - assertJSONEqual(fee, expected); -} diff --git a/tests/Keystore/StoredKeyTests.cpp b/tests/Keystore/StoredKeyTests.cpp deleted file mode 100644 index 84266df0ccd..00000000000 --- a/tests/Keystore/StoredKeyTests.cpp +++ /dev/null @@ -1,386 +0,0 @@ -// Copyright © 2017-2021 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 "Keystore/StoredKey.h" - -#include "Coin.h" -#include "HexCoding.h" -#include "Data.h" -#include "PrivateKey.h" -#include "Mnemonic.h" - -#include -#include - -extern std::string TESTS_ROOT; - -namespace TW::Keystore { - -using namespace std; - -const auto passwordString = "password"; -const auto password = TW::data(string(passwordString)); -const auto mnemonic = "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; -const TWCoinType coinTypeBc = TWCoinTypeBitcoin; -const TWCoinType coinTypeBnb = TWCoinTypeBinance; -const TWCoinType coinTypeBsc = TWCoinTypeSmartChain; -const TWCoinType coinTypeEth = TWCoinTypeEthereum; -const TWCoinType coinTypeBscLegacy = TWCoinTypeSmartChainLegacy; - -TEST(StoredKey, CreateWithMnemonic) { - auto key = StoredKey::createWithMnemonic("name", password, mnemonic); - EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); - const Data& mnemo2Data = key.payload.decrypt(password); - EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(mnemonic)); - EXPECT_EQ(key.accounts.size(), 0); - EXPECT_EQ(key.wallet(password).getMnemonic(), string(mnemonic)); - - const auto json = key.json(); - EXPECT_EQ(json["name"], "name"); - EXPECT_EQ(json["type"], "mnemonic"); - EXPECT_EQ(json["version"], 3); -} - -TEST(StoredKey, CreateWithMnemonicInvalid) { - try { - auto key = StoredKey::createWithMnemonic("name", password, "_THIS_IS_NOT_A_VALID_MNEMONIC_"); - } catch (std::invalid_argument&) { - // expedcted exception OK - return; - } - FAIL() << "Missing excpected excpetion"; -} - -TEST(StoredKey, CreateWithMnemonicRandom) { - const auto key = StoredKey::createWithMnemonicRandom("name", password); - EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); - // random mnemonic: check only length and validity - const Data& mnemo2Data = key.payload.decrypt(password); - EXPECT_TRUE(mnemo2Data.size() >= 36); - EXPECT_TRUE(Mnemonic::isValid(string(mnemo2Data.begin(), mnemo2Data.end()))); - EXPECT_EQ(key.accounts.size(), 0); -} - -TEST(StoredKey, CreateWithMnemonicAddDefaultAddress) { - auto key = StoredKey::createWithMnemonicAddDefaultAddress("name", password, mnemonic, coinTypeBc); - EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); - const Data& mnemo2Data = key.payload.decrypt(password); - EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(mnemonic)); - EXPECT_EQ(key.accounts.size(), 1); - EXPECT_EQ(key.accounts[0].coin, coinTypeBc); - EXPECT_EQ(key.accounts[0].address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); - EXPECT_EQ(hex(key.privateKey(coinTypeBc, password).bytes), "d2568511baea8dc347f14c4e0479eb8ebe29eb5f664ed796e755896250ffd11f"); -} - -TEST(StoredKey, CreateWithPrivateKeyAddDefaultAddress) { - const auto privateKey = parse_hex("3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"); - auto key = StoredKey::createWithPrivateKeyAddDefaultAddress("name", password, coinTypeBc, privateKey); - EXPECT_EQ(key.type, StoredKeyType::privateKey); - EXPECT_EQ(key.accounts.size(), 1); - EXPECT_EQ(key.accounts[0].coin, coinTypeBc); - EXPECT_EQ(key.accounts[0].address, "bc1q375sq4kl2nv0mlmup3vm8znn4eqwu7mt6hkwhr"); - EXPECT_EQ(hex(key.privateKey(coinTypeBc, password).bytes), hex(privateKey)); - - const auto json = key.json(); - EXPECT_EQ(json["name"], "name"); - EXPECT_EQ(json["type"], "private-key"); - EXPECT_EQ(json["version"], 3); -} - -TEST(StoredKey, CreateWithPrivateKeyAddDefaultAddressInvalid) { - try { - const auto privateKeyInvalid = parse_hex("0001020304"); - auto key = StoredKey::createWithPrivateKeyAddDefaultAddress("name", password, coinTypeBc, privateKeyInvalid); - } catch (std::invalid_argument&) { - // expected exception ok - return; - } - FAIL() << "Missing expected exception"; -} - -TEST(StoredKey, AccountGetCreate) { - auto key = StoredKey::createWithMnemonic("name", password, mnemonic); - EXPECT_EQ(key.accounts.size(), 0); - - // not exists - EXPECT_FALSE(key.account(coinTypeBc).has_value()); - EXPECT_EQ(key.accounts.size(), 0); - - auto wallet = key.wallet(password); - // not exists, wallet null, not create - EXPECT_FALSE(key.account(coinTypeBc, nullptr).has_value()); - EXPECT_EQ(key.accounts.size(), 0); - - // not exists, wallet nonnull, create - std::optional acc3 = key.account(coinTypeBc, &wallet); - EXPECT_TRUE(acc3.has_value()); - EXPECT_EQ(acc3->coin, coinTypeBc); - EXPECT_EQ(key.accounts.size(), 1); - - // exists - std::optional acc4 = key.account(coinTypeBc); - EXPECT_TRUE(acc4.has_value()); - EXPECT_EQ(acc4->coin, coinTypeBc); - EXPECT_EQ(key.accounts.size(), 1); - - // exists, wallet nonnull, not create - std::optional acc5 = key.account(coinTypeBc, &wallet); - EXPECT_TRUE(acc5.has_value()); - EXPECT_EQ(acc5->coin, coinTypeBc); - EXPECT_EQ(key.accounts.size(), 1); - - // exists, wallet null, not create - std::optional acc6 = key.account(coinTypeBc, nullptr); - EXPECT_TRUE(acc6.has_value()); - EXPECT_EQ(acc6->coin, coinTypeBc); - EXPECT_EQ(key.accounts.size(), 1); -} - -TEST(StoredKey, AccountGetDoesntChange) { - auto key = StoredKey::createWithMnemonic("name", password, mnemonic); - auto wallet = key.wallet(password); - EXPECT_EQ(key.accounts.size(), 0); - - vector coins = {coinTypeBc, coinTypeEth, coinTypeBnb}; - // retrieve multiple accounts, which will be created - vector accounts; - for (auto coin: coins) { - std::optional account = key.account(coin, &wallet); - accounts.push_back(*account); - - // check - ASSERT_TRUE(account.has_value()); - EXPECT_EQ(account->coin, coin); - } - - // Check again; make sure returned references don't change - for (auto i = 0; i < accounts.size(); ++i) { - // check - EXPECT_EQ(accounts[i].coin, coins[i]); - } -} - -TEST(StoredKey, AddRemoveAccount) { - auto key = StoredKey::createWithMnemonic("name", password, mnemonic); - EXPECT_EQ(key.accounts.size(), 0); - - { - const auto derivationPath = DerivationPath("m/84'/0'/0'/0/0"); - key.addAccount("bc1qaucw06s3agez8tyyk4zj9kt0q2934e3mcewdpf", coinTypeBc, derivationPath, "zpub6rxtad3SPT1C5GUDjPiKQ5oJN5DBeMbdUR7LrdYt12VbU7TBSpGUkdLvfVYGuj1N5edkDoZ3bu1fdN1HprQYfCBdsSH5CaAAygHGsanwtTe"); - EXPECT_EQ(key.accounts.size(), 1); - } - { - const auto derivationPath = DerivationPath("m/714'/0'/0'/0/0"); - key.addAccount("bnb1utrnnjym7ustgw7pgyvtmnxay4qmt3ahh276nu", coinTypeBnb, derivationPath, ""); - key.addAccount("0x23b02dC8f67eD6cF8DCa47935791954286ffe7c9", coinTypeBsc, derivationPath, ""); - EXPECT_EQ(key.accounts.size(), 3); - } - { - const auto derivationPath = DerivationPath("m/60'/0'/0'/0/0"); - key.addAccount("0xC0d97f61A84A0708225F15d54978D628Fe2C5E62", coinTypeEth, derivationPath, ""); - key.addAccount("0xC0d97f61A84A0708225F15d54978D628Fe2C5E62", coinTypeBscLegacy, derivationPath, ""); - EXPECT_EQ(key.accounts.size(), 5); - } - - key.removeAccount(coinTypeBc); - key.removeAccount(coinTypeBnb); - key.removeAccount(coinTypeBsc); - key.removeAccount(coinTypeEth); - key.removeAccount(coinTypeBscLegacy); - EXPECT_EQ(key.accounts.size(), 0); -} - -TEST(StoredKey, FixAddress) { - { - auto key = StoredKey::createWithMnemonic("name", password, mnemonic); - key.fixAddresses(password); - } - { - const auto privateKey = parse_hex("3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"); - auto key = StoredKey::createWithPrivateKeyAddDefaultAddress("name", password, coinTypeBc, privateKey); - key.fixAddresses(password); - } -} - -TEST(StoredKey, WalletInvalid) { - const auto privateKey = parse_hex("3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"); - auto key = StoredKey::createWithPrivateKeyAddDefaultAddress("name", password, coinTypeBc, privateKey); - try { - auto wallet = key.wallet(password); - } catch (std::invalid_argument&) { - // expected exception ok - return; - } - FAIL() << "Missing expected exception"; -} - -TEST(StoredKey, LoadNonexistent) { - ASSERT_THROW(StoredKey::load(TESTS_ROOT + "/Keystore/Data/nonexistent.json"), invalid_argument); -} - -TEST(StoredKey, LoadLegacyPrivateKey) { - const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/legacy-private-key.json"); - EXPECT_EQ(key.type, StoredKeyType::privateKey); - EXPECT_EQ(key.id, "3051ca7d-3d36-4a4a-acc2-09e9083732b0"); - EXPECT_EQ(key.accounts[0].coin, TWCoinTypeEthereum); - EXPECT_EQ(hex(key.payload.decrypt(TW::data("testpassword"))), "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"); -} - -TEST(StoredKey, LoadLivepeerKey) { - const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/livepeer.json"); - EXPECT_EQ(key.type, StoredKeyType::privateKey); - EXPECT_EQ(key.id, "70ea3601-ee21-4e94-a7e4-66255a987d22"); - EXPECT_EQ(key.accounts[0].coin, TWCoinTypeEthereum); - EXPECT_EQ(hex(key.payload.decrypt(TW::data("Radchenko"))), "09b4379d9a41a71d94ee36357bccb4d77b45e7fd9307e2c0f673dd54c0558c73"); -} - -TEST(StoredKey, LoadPBKDF2Key) { - const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/pbkdf2.json"); - EXPECT_EQ(key.type, StoredKeyType::privateKey); - EXPECT_EQ(key.id, "3198bc9c-6672-5ab3-d995-4942343ae5b6"); - - const auto& payload = key.payload; - ASSERT_TRUE(payload.kdfParams.which() == 1); - EXPECT_EQ(boost::get(payload.kdfParams).desiredKeyLength, 32); - EXPECT_EQ(boost::get(payload.kdfParams).iterations, 262144); - EXPECT_EQ(hex(boost::get(payload.kdfParams).salt), "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"); - - EXPECT_EQ(hex(payload.decrypt(TW::data("testpassword"))), "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"); -} - -TEST(StoredKey, LoadLegacyMnemonic) { - const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/legacy-mnemonic.json"); - EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); - EXPECT_EQ(key.id, "629aad29-0b22-488e-a0e7-b4219d4f311c"); - - const auto data = key.payload.decrypt(password); - const auto mnemonic = string(reinterpret_cast(data.data())); - EXPECT_EQ(mnemonic, "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn back"); - - EXPECT_EQ(key.accounts[0].coin, TWCoinTypeEthereum); - EXPECT_EQ(key.accounts[0].derivationPath.string(), "m/44'/60'/0'/0/0"); - EXPECT_EQ(key.accounts[0].address, ""); - EXPECT_EQ(key.accounts[1].coin, coinTypeBc); - EXPECT_EQ(key.accounts[1].derivationPath.string(), "m/84'/0'/0'/0/0"); - EXPECT_EQ(key.accounts[1].address, ""); - EXPECT_EQ(key.accounts[1].extendedPublicKey, "zpub6r97AegwVxVbJeuDAWP5KQgX5y4Q6KyFUrsFQRn8yzSXrnmpwg1ZKHSWwECR1Kiqgr4h93WN5kdS48KC6hVFniuZHqVFXjULZZkCwurqyPn"); -} - -TEST(StoredKey, LoadFromWeb3j) { - const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/web3j.json"); - EXPECT_EQ(key.type, StoredKeyType::privateKey); - EXPECT_EQ(key.id, "86066d8c-8dba-4d81-afd4-934e2a2b72a2"); - const auto password = parse_hex("2d6eefbfbd4622efbfbdefbfbd516718efbfbdefbfbdefbfbdefbfbd59efbfbd30efbfbdefbfbd3a4348efbfbd2aefbfbdefbfbd49efbfbd27efbfbd0638efbfbdefbfbdefbfbd4cefbfbd6befbfbdefbfbd6defbfbdefbfbd63efbfbd5aefbfbd61262b70efbfbdefbfbdefbfbdefbfbdefbfbdc7aa373163417cefbfbdefbfbdefbfbd44efbfbdefbfbd1d10efbfbdefbfbdefbfbd61dc9e5b124befbfbd11efbfbdefbfbd2fefbfbdefbfbd3d7c574868efbfbdefbfbdefbfbd37043b7b5c1a436471592f02efbfbd18efbfbdefbfbd2befbfbdefbfbd7218efbfbd6a68efbfbdcb8e5f3328773ec48174efbfbd67efbfbdefbfbdefbfbdefbfbdefbfbd2a31efbfbd7f60efbfbdd884efbfbd57efbfbd25efbfbd590459efbfbd37efbfbd2bdca20fefbfbdefbfbdefbfbdefbfbd39450113efbfbdefbfbdefbfbd454671efbfbdefbfbdd49fefbfbd47efbfbdefbfbdefbfbdefbfbd00efbfbdefbfbdefbfbdefbfbd05203f4c17712defbfbd7bd1bbdc967902efbfbdc98a77efbfbd707a36efbfbd12efbfbdefbfbd57c78cefbfbdefbfbdefbfbd10efbfbdefbfbdefbfbde1a1bb08efbfbdefbfbd26efbfbdefbfbd58efbfbdefbfbdc4b1efbfbd295fefbfbd0eefbfbdefbfbdefbfbd0e6eefbfbd"); - const auto data = key.payload.decrypt(password); - EXPECT_EQ(hex(data), "043c5429c7872502531708ec0d821c711691402caf37ef7ba78a8c506f10653b"); -} - -TEST(StoredKey, ReadWallet) { - const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/key.json"); - - EXPECT_EQ(key.type, StoredKeyType::privateKey); - EXPECT_EQ(key.id, "e13b209c-3b2f-4327-bab0-3bef2e51630d"); - EXPECT_EQ(key.name, "Test Account"); - - const auto header = key.payload; - - EXPECT_EQ(header.cipher, "aes-128-ctr"); - EXPECT_EQ(hex(header.encrypted), "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c"); - EXPECT_EQ(hex(header.mac), "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097"); - EXPECT_EQ(hex(header.cipherParams.iv), "83dbcc02d8ccb40e466191a123791e0e"); - - ASSERT_TRUE(header.kdfParams.which() == 0); - EXPECT_EQ(boost::get(header.kdfParams).desiredKeyLength, 32); - EXPECT_EQ(boost::get(header.kdfParams).n, 262144); - EXPECT_EQ(boost::get(header.kdfParams).p, 8); - EXPECT_EQ(boost::get(header.kdfParams).r, 1); - EXPECT_EQ(hex(boost::get(header.kdfParams).salt), "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19"); -} - -TEST(StoredKey, ReadMyEtherWallet) { - ASSERT_NO_THROW(StoredKey::load(TESTS_ROOT + "/Keystore/Data/myetherwallet.uu")); -} - -TEST(StoredKey, InvalidPassword) { - const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/key.json"); - - ASSERT_THROW(key.payload.decrypt(password), DecryptionError); -} - -TEST(StoredKey, EmptyAccounts) { - const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/empty-accounts.json"); - - ASSERT_NO_THROW(key.payload.decrypt(TW::data("testpassword"))); -} - -TEST(StoredKey, Decrypt) { - const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/key.json"); - const auto privateKey = key.payload.decrypt(TW::data("testpassword")); - - EXPECT_EQ(hex(privateKey), "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"); -} - -TEST(StoredKey, CreateWallet) { - const auto privateKey = parse_hex("3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"); - const auto key = StoredKey::createWithPrivateKey("name", password, privateKey); - const auto decrypted = key.payload.decrypt(password); - - EXPECT_EQ(hex(decrypted), hex(privateKey)); -} - -TEST(StoredKey, CreateAccounts) { - string mnemonicPhrase = "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; - auto key = StoredKey::createWithMnemonic("name", password, mnemonicPhrase); - const auto wallet = key.wallet(password); - - EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->address, "0x494f60cb6Ac2c8F5E1393aD9FdBdF4Ad589507F7"); - EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->extendedPublicKey, ""); - - EXPECT_EQ(key.account(coinTypeBc, &wallet)->address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); - EXPECT_EQ(key.account(coinTypeBc, &wallet)->extendedPublicKey, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); -} - -TEST(StoredKey, DecodingEthereumAddress) { - const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/key.json"); - - EXPECT_EQ(key.accounts[0].address, "0x008AeEda4D805471dF9b2A5B0f38A0C3bCBA786b"); -} - -TEST(StoredKey, DecodingBitcoinAddress) { - const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/key_bitcoin.json"); - - EXPECT_EQ(key.accounts[0].address, "3PWazDi9n1Hfyq9gXFxDxzADNL8RNYyK2y"); -} - -TEST(StoredKey, RemoveAccount) { - auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/legacy-mnemonic.json"); - EXPECT_EQ(key.accounts.size(), 2); - key.removeAccount(TWCoinTypeEthereum); - EXPECT_EQ(key.accounts.size(), 1); - EXPECT_EQ(key.accounts[0].coin, coinTypeBc); -} - -TEST(StoredKey, MissingAddress) { - auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/missing-address.json"); - EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); - - const auto wallet = key.wallet(password); - EXPECT_EQ(wallet.getMnemonic(), "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"); - EXPECT_TRUE(Mnemonic::isValid(wallet.getMnemonic())); - - key.fixAddresses(password); - - EXPECT_EQ(key.account(TWCoinTypeEthereum, nullptr)->address, "0xA3Dcd899C0f3832DFDFed9479a9d828c6A4EB2A7"); - EXPECT_EQ(key.account(coinTypeBc, nullptr)->address, "bc1qpsp72plnsqe6e2dvtsetxtww2cz36ztmfxghpd"); -} - -TEST(StoredKey, EtherWalletAddressNo0x) { - auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/ethereum-wallet-address-no-0x.json"); - key.fixAddresses(TW::data("15748c4e3dca6ae2110535576ab0c398cb79d985707c68ee6c9f9df9d421dd53")); - EXPECT_EQ(key.account(TWCoinTypeEthereum, nullptr)->address, "0xAc1ec44E4f0ca7D172B7803f6836De87Fb72b309"); -} - -} // namespace TW::Keystore diff --git a/tests/NEAR/SerializationTests.cpp b/tests/NEAR/SerializationTests.cpp deleted file mode 100644 index 9d637de3293..00000000000 --- a/tests/NEAR/SerializationTests.cpp +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "Base58.h" -#include "proto/NEAR.pb.h" -#include "NEAR/Serialization.h" - -#include -#include - -namespace TW::NEAR { - -TEST(NEARSerialization, SerializeTransferTransaction) { - auto publicKey = Base58::bitcoin.decode("Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC"); - - auto input = Proto::SigningInput(); - input.set_signer_id("test.near"); - input.set_nonce(1); - input.set_receiver_id("whatever.near"); - - input.add_actions(); - auto& transfer = *input.mutable_actions(0)->mutable_transfer(); - Data deposit(16, 0); - deposit[0] = 1; - transfer.set_deposit(deposit.data(), deposit.size()); - - auto blockHash = Base58::bitcoin.decode("244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM"); - input.set_block_hash(blockHash.data(), blockHash.size()); - - auto privateKey = Base58::bitcoin.decode("3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv"); - input.set_private_key(privateKey.data(), 32); - - auto serialized = transactionData(input); - auto serializedHex = hex(serialized); - - ASSERT_EQ(serializedHex, "09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef6010000000301000000000000000000000000000000"); -} - -} diff --git a/tests/NEAR/TWAnySignerTests.cpp b/tests/NEAR/TWAnySignerTests.cpp deleted file mode 100644 index 49326b7cb4a..00000000000 --- a/tests/NEAR/TWAnySignerTests.cpp +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "proto/NEAR.pb.h" -#include "../interface/TWTestUtilities.h" -#include -#include - -namespace TW::NEAR { - -TEST(TWAnySignerNEAR, Sign) { - - auto privateKey = parse_hex("8737b99bf16fba78e1e753e23ba00c4b5423ac9c45d9b9caae9a519434786568"); - auto blockHash = parse_hex("0fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef6"); - // uint128_t / little endian byte order - auto deposit = parse_hex("01000000000000000000000000000000"); - - Proto::SigningInput input; - input.set_signer_id("test.near"); - input.set_nonce(1); - input.set_receiver_id("whatever.near"); - input.set_private_key(privateKey.data(), privateKey.size()); - input.set_block_hash(blockHash.data(), blockHash.size()); - - auto& action = *input.add_actions(); - auto& transfer = *action.mutable_transfer(); - transfer.set_deposit(deposit.data(), deposit.size()); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeNEAR); - - ASSERT_EQ(hex(output.signed_transaction()), "09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef601000000030100000000000000000000000000000000969a83332186ee9755e4839325525806e189a3d2d2bb4b4760e94443e97e1c4f22deeef0059a8e9713100eda6e19144da7e8a0ef7e539b20708ba1d8d021bd01"); -} - -} // namespace TW::NEAR diff --git a/tests/THORChain/SignerTests.cpp b/tests/THORChain/SignerTests.cpp deleted file mode 100644 index 5cb58be271d..00000000000 --- a/tests/THORChain/SignerTests.cpp +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright © 2017-2021 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 "proto/Cosmos.pb.h" -#include "THORChain/Signer.h" -#include "HexCoding.h" - -#include -#include - -using namespace TW; - -TEST(THORChainSigner, SignTx) { - auto input = Cosmos::Proto::SigningInput(); - input.set_memo("memo1234"); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_send_coins_message(); - message.set_from_address("thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r"); - message.set_to_address("thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn"); - auto amountOfTx = message.add_amounts(); - amountOfTx->set_denom("rune"); - amountOfTx->set_amount(50000000); - - auto& fee = *input.mutable_fee(); - fee.set_gas(2000000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("rune"); - amountOfFee->set_amount(200); - - std::string json; - google::protobuf::util::MessageToJsonString(input, &json); - - EXPECT_EQ(R"({"fee":{"amounts":[{"denom":"rune","amount":"200"}],"gas":"2000000"},"memo":"memo1234","messages":[{"sendCoinsMessage":{"fromAddress":"thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r","toAddress":"thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn","amounts":[{"denom":"rune","amount":"50000000"}]}}]})", json); - - auto privateKey = parse_hex("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = THORChain::Signer::sign(input); - - EXPECT_EQ(R"({"mode":"block","tx":{"fee":{"amount":[{"amount":"200","denom":"rune"}],"gas":"2000000"},"memo":"memo1234","msg":[{"type":"thorchain/MsgSend","value":{"amount":[{"amount":"50000000","denom":"rune"}],"from_address":"thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r","to_address":"thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A+2Zfjls9CkvX85aQrukFZnM1dluMTFUp8nqcEneMXx3"},"signature":"12AaNC0v51Rhz8rBf7V7rpI6oksREWrjzba3RK1v1NNlqZq62sG0aXWvStp9zZXe07Pp2FviFBAx+uqWsO30NQ=="}]}})", output.json()); - EXPECT_EQ(hex(output.signature()), "d7601a342d2fe75461cfcac17fb57bae923aa24b11116ae3cdb6b744ad6fd4d365a99abadac1b46975af4ada7dcd95ded3b3e9d85be2141031faea96b0edf435"); -} - -TEST(THORChainSigner, SignJson) { - auto inputJson = R"({"fee":{"amounts":[{"denom":"rune","amount":"200"}],"gas":"2000000"},"memo":"memo1234","messages":[{"sendCoinsMessage":{"fromAddress":"thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r","toAddress":"thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn","amounts":[{"denom":"rune","amount":"50000000"}]}}]})"; - auto privateKey = parse_hex("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e"); - - auto outputJson = THORChain::Signer::signJSON(inputJson, privateKey); - - EXPECT_EQ(R"({"mode":"block","tx":{"fee":{"amount":[{"amount":"200","denom":"rune"}],"gas":"2000000"},"memo":"memo1234","msg":[{"type":"thorchain/MsgSend","value":{"amount":[{"amount":"50000000","denom":"rune"}],"from_address":"thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r","to_address":"thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A+2Zfjls9CkvX85aQrukFZnM1dluMTFUp8nqcEneMXx3"},"signature":"12AaNC0v51Rhz8rBf7V7rpI6oksREWrjzba3RK1v1NNlqZq62sG0aXWvStp9zZXe07Pp2FviFBAx+uqWsO30NQ=="}]}})", outputJson); -} diff --git a/tests/Tezos/TWAnySignerTests.cpp b/tests/Tezos/TWAnySignerTests.cpp deleted file mode 100644 index 4f2627ed2d7..00000000000 --- a/tests/Tezos/TWAnySignerTests.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "proto/Tezos.pb.h" -#include "../interface/TWTestUtilities.h" -#include - -#include - -using namespace TW; -using namespace TW::Tezos; - -TEST(TWAnySignerTezos, Sign) { - auto key = parse_hex("2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6f"); - auto revealKey = parse_hex("311f002e899cdd9a52d96cb8be18ea2bbab867c505da2b44ce10906f511cff95"); - - Proto::SigningInput input; - input.set_private_key(key.data(), key.size()); - auto& operations = *input.mutable_operation_list(); - operations.set_branch("BL8euoCWqNCny9AR3AKjnpi38haYMxjei1ZqNHuXMn19JSQnoWp"); - - auto& reveal = *operations.add_operations(); - auto& revealData = *reveal.mutable_reveal_operation_data(); - revealData.set_public_key(revealKey.data(), revealKey.size()); - reveal.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); - reveal.set_fee(1272); - reveal.set_counter(30738); - reveal.set_gas_limit(10100); - reveal.set_storage_limit(257); - reveal.set_kind(Proto::Operation::REVEAL); - - auto& transaction = *operations.add_operations(); - auto& txData = *transaction.mutable_transaction_operation_data(); - txData.set_amount(1); - txData.set_destination("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); - transaction.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); - transaction.set_fee(1272); - transaction.set_counter(30739); - transaction.set_gas_limit(10100); - transaction.set_storage_limit(257); - transaction.set_kind(Proto::Operation::TRANSACTION); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeTezos); - - EXPECT_EQ(hex(output.encoded()), "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016b0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80992f001f44e810200311f002e899cdd9a52d96cb8be18ea2bbab867c505da2b44ce10906f511cff956c0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80993f001f44e810201000081faa75f741ef614b0e35fcc8c90dfa3b0b95721000217034271b815e5f0c0a881342838ce49d7b48cdf507c72b1568c69a10db70c98774cdad1a74df760763e25f760ff13afcbbf3a1f2c833a0beeb9576a579c05"); -} - -TEST(TWAnySignerTezos, SignJSON) { - auto json = STRING(R"({"operationList": {"branch": "BL8euoCWqNCny9AR3AKjnpi38haYMxjei1ZqNHuXMn19JSQnoWp","operations": [{"source": "tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW","fee": 1272,"counter": 30738,"gasLimit": 10100,"storageLimit": 257,"kind": 107,"revealOperationData": {"publicKey": "QpqYbIBypAofOj4qtaWBm7Gy+2mZPFAEg3gVudxVkj4="}},{"source": "tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW","fee": 1272,"counter": 30739,"gasLimit": 10100,"storageLimit": 257,"kind": 108,"transactionOperationData": {"destination": "tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW","amount": 1}}]}})"); - auto key = DATA("2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6f"); - auto result = WRAPS(TWAnySignerSignJSON(json.get(), key.get(), TWCoinTypeTezos)); - - ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeTezos)); - assertStringsEqual(result, "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016b0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80992f001f44e810200429a986c8072a40a1f3a3e2ab5a5819bb1b2fb69993c5004837815b9dc55923e6c0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80993f001f44e810201000081faa75f741ef614b0e35fcc8c90dfa3b0b957210001b86398d5b9be737dca8e4106ea18d70e69b75e92f892fb283546a99152b8d7794b919c0fbf1c31de386069a60014491c0e7505adef5781cead1cfe6608030b"); -} diff --git a/tests/Aeternity/AddressTests.cpp b/tests/chains/Aeternity/AddressTests.cpp similarity index 80% rename from tests/Aeternity/AddressTests.cpp rename to tests/chains/Aeternity/AddressTests.cpp index 40c99814889..c08866c05e7 100644 --- a/tests/Aeternity/AddressTests.cpp +++ b/tests/chains/Aeternity/AddressTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -7,13 +7,11 @@ #include #include #include -#include -using namespace TW; -using namespace TW::Aeternity; +namespace TW::Aeternity::tests { TEST(AeternityAddress, FromPublicKey) { - auto publicKey = PublicKey(parse_hex("ee93a4f66f8d16b819bb9beb9ffccdfcdc1412e87fee6a324c2a99a1e0e67148"),TWPublicKeyTypeED25519); + auto publicKey = PublicKey(parse_hex("ee93a4f66f8d16b819bb9beb9ffccdfcdc1412e87fee6a324c2a99a1e0e67148"), TWPublicKeyTypeED25519); auto address = Address(publicKey); ASSERT_EQ(address.string(), "ak_2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5FeVfcw"); } @@ -21,4 +19,6 @@ TEST(AeternityAddress, FromPublicKey) { TEST(AeternityAddress, FromString) { auto address = Address("ak_2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5FeVfcw"); ASSERT_EQ(address.string(), "ak_2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5FeVfcw"); -} \ No newline at end of file +} + +} // namespace TW::Aeternity::tests diff --git a/tests/Aeternity/SignerTests.cpp b/tests/chains/Aeternity/SignerTests.cpp similarity index 97% rename from tests/Aeternity/SignerTests.cpp rename to tests/chains/Aeternity/SignerTests.cpp index aab6d85abfb..55f9246fd3b 100644 --- a/tests/Aeternity/SignerTests.cpp +++ b/tests/chains/Aeternity/SignerTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -12,8 +12,7 @@ #include #include "uint256.h" -using namespace TW; -using namespace TW::Aeternity; +namespace TW::Aeternity::tests { TEST(AeternitySigner, Sign) { std::string sender_id = "ak_2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5FeVfcw"; @@ -82,3 +81,5 @@ TEST(AeternitySigner, SignTxWithZeroNonce) { EXPECT_EQ(result.signature(), "sg_MaJc4ptSUhq5kH6mArszDAvu4f7PejyuhmgM6U8GEr8bRUTaSFbdFPx4C6FEYA5v5Lgwu9EToaWnHgR2xkqZ9JjHnaBpA"); EXPECT_EQ(result.encoded(), "tx_+LULAfhCuECdQsgcE8bp+9CANdasxkt5gxfjBSI1ztyPl1aNJbm+MwUvE7Lu/qvAkHijfe+Eui2zrqhZRYc5mblRa+oLOIIEuG34awwBoQHuk6T2b40WuBm7m+uf/M383BQS6H/uajJMKpmh4OZxSKEBHxOjsIvwAUAGYqaLadh194A87EwIZH9u1dhMeJe9UKOILsSS9IArwACGEjCc5UAAgwG7qwCPWmVybyBub25jZSB0ZXN0piWfFA=="); } + +} // namespace TW::Aeternity::tests diff --git a/tests/Aeternity/TWAeternityAddressTests.cpp b/tests/chains/Aeternity/TWAeternityAddressTests.cpp similarity index 96% rename from tests/Aeternity/TWAeternityAddressTests.cpp rename to tests/chains/Aeternity/TWAeternityAddressTests.cpp index 51376f5cf7a..b801091634c 100644 --- a/tests/Aeternity/TWAeternityAddressTests.cpp +++ b/tests/chains/Aeternity/TWAeternityAddressTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Aeternity/TWAnySignerTests.cpp b/tests/chains/Aeternity/TWAnySignerTests.cpp similarity index 90% rename from tests/Aeternity/TWAnySignerTests.cpp rename to tests/chains/Aeternity/TWAnySignerTests.cpp index 01b4fcf80be..9a1f716447f 100644 --- a/tests/Aeternity/TWAnySignerTests.cpp +++ b/tests/chains/Aeternity/TWAnySignerTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,15 +9,14 @@ #include "proto/Aeternity.pb.h" #include -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include -using namespace TW; -using namespace TW::Aeternity; +namespace TW::Aeternity::tests { TEST(TWAnySignerAeternity, Sign) { auto privateKey = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); - + Proto::SigningInput input; input.set_from_address("ak_2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5FeVfcw"); input.set_to_address("ak_Egp9yVdpxmvAfQ7vsXGvpnyfNq71msbdUpkMNYGTeTe8kPL3v"); @@ -35,3 +34,5 @@ TEST(TWAnySignerAeternity, Sign) { ASSERT_EQ(output.encoded(), "tx_+KkLAfhCuEDZ2XDV5OuHv1iuLn66sFLBUwnzp1K8JW1Zz+fEgmuEh6HEvNu0R112M3IYkVzvTSnT0pJ3TWhVOumgJ+IWwW8HuGH4XwwBoQHuk6T2b40WuBm7m+uf/M383BQS6H/uajJMKpmh4OZxSKEBHxOjsIvwAUAGYqaLadh194A87EwIZH9u1dhMeJe9UKMKhhIwnOVAAIMBQ0Uxi0hlbGxvIFdvcmxkDZqNSg=="); } + +} // namespace TW::Aeternity::tests diff --git a/tests/Aeternity/TWCoinTypeTests.cpp b/tests/chains/Aeternity/TWCoinTypeTests.cpp similarity index 97% rename from tests/Aeternity/TWCoinTypeTests.cpp rename to tests/chains/Aeternity/TWCoinTypeTests.cpp index 7db96db7589..7e9f16603ca 100644 --- a/tests/Aeternity/TWCoinTypeTests.cpp +++ b/tests/chains/Aeternity/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Aeternity/TransactionTests.cpp b/tests/chains/Aeternity/TransactionTests.cpp similarity index 89% rename from tests/Aeternity/TransactionTests.cpp rename to tests/chains/Aeternity/TransactionTests.cpp index 025eb2dc056..fa50abb449f 100644 --- a/tests/Aeternity/TransactionTests.cpp +++ b/tests/chains/Aeternity/TransactionTests.cpp @@ -1,19 +1,17 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "Aeternity/Address.cpp" -#include "Aeternity/Transaction.cpp" #include "HexCoding.h" #include "PrivateKey.h" -#include "../interface/TWTestUtilities.h" - -#include "HexCoding.h" +#include "TestUtilities.h" #include #include +namespace TW::Aeternity::tests { + TEST(AeternityTransaction, EncodeRlp) { std::string sender_id = "ak_2a1j2Mk9YSmC1gioUq4PWRm3bsv887MbuRVwyv4KaUGoR1eiKi"; std::string recipient_id = "ak_Egp9yVdpxmvAfQ7vsXGvpnyfNq71msbdUpkMNYGTeTe8kPL3v"; @@ -25,7 +23,7 @@ TEST(AeternityTransaction, EncodeRlp) { auto tx = Transaction(sender_id, recipient_id, amount, fee, payload, ttl, nonce); auto encodedTx = tx.encode(); - auto encodedTxHex = hex(encodedTx); + auto encodedTxHex = TW::hex(encodedTx); ASSERT_EQ(encodedTxHex, "f85f0c01a101cea7ade470c9f99d9d4e400880a86f1d49bb444b62f11a9ebb64bbcfeb73fef3a1011f13a3b08bf001400662a68b69d875f7803cec4c08647f6ed5d84c7897bd50a30a8612309ce5400083014345318b48656c6c6f20576f726c64"); } @@ -41,7 +39,7 @@ TEST(AeternityTransaction, EncodeRlpWithZeroAmount) { auto tx = Transaction(sender_id, recipient_id, amount, fee, payload, ttl, nonce); auto encodedTx = tx.encode(); - auto encodedTxHex = hex(encodedTx); + auto encodedTxHex = TW::hex(encodedTx); ASSERT_EQ(encodedTxHex, "f85f0c01a101cea7ade470c9f99d9d4e400880a86f1d49bb444b62f11a9ebb64bbcfeb73fef3a1011f13a3b08bf001400662a68b69d875f7803cec4c08647f6ed5d84c7897bd50a3008612309ce5400083014345318b48656c6c6f20576f726c64"); } @@ -57,8 +55,9 @@ TEST(AeternityTransaction, EncodeRlpWithZeroTtl) { auto tx = Transaction(sender_id, recipient_id, amount, fee, payload, ttl, nonce); auto encodedTx = tx.encode(); - auto encodedTxHex = hex(encodedTx); + auto encodedTxHex = TW::hex(encodedTx); ASSERT_EQ(encodedTxHex, "f85c0c01a101cea7ade470c9f99d9d4e400880a86f1d49bb444b62f11a9ebb64bbcfeb73fef3a1011f13a3b08bf001400662a68b69d875f7803cec4c08647f6ed5d84c7897bd50a30a8612309ce5400000318b48656c6c6f20576f726c64"); } +} // namespace TW::Aeternity::tests diff --git a/tests/Aion/AddressTests.cpp b/tests/chains/Aion/AddressTests.cpp similarity index 95% rename from tests/Aion/AddressTests.cpp rename to tests/chains/Aion/AddressTests.cpp index 5942e0ce0b6..05f37edb64e 100644 --- a/tests/Aion/AddressTests.cpp +++ b/tests/chains/Aion/AddressTests.cpp @@ -10,7 +10,8 @@ #include using namespace TW; -using namespace TW::Aion; + +namespace TW::Aion::tests { TEST(AionAddress, FromPublicKey) { auto publicKey = PublicKey(parse_hex("01a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a7"), TWPublicKeyTypeED25519); @@ -31,3 +32,5 @@ TEST(AionAddress, isValid) { ASSERT_TRUE(Address::isValid(validAddress)); ASSERT_FALSE(Address::isValid(invalidAddress)); } + +} // namespace TW::Aion::tests diff --git a/tests/Aion/RLPTests.cpp b/tests/chains/Aion/RLPTests.cpp similarity index 91% rename from tests/Aion/RLPTests.cpp rename to tests/chains/Aion/RLPTests.cpp index 977fbdbda65..86df3146ace 100644 --- a/tests/Aion/RLPTests.cpp +++ b/tests/chains/Aion/RLPTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -6,11 +6,10 @@ #include "Aion/RLP.h" #include "HexCoding.h" - #include -using namespace TW; -using namespace TW::Aion; +namespace TW::Aion::tests { + using boost::multiprecision::uint128_t; TEST(AionRLP, EncodeLong) { @@ -24,3 +23,5 @@ TEST(AionRLP, EncodeLong) { EXPECT_EQ(hex(RLP::encodeLong(uint128_t(4295000060L))), "880000000100007ffc"); EXPECT_EQ(hex(RLP::encodeLong(uint128_t(72057594037927935L))), "8800ffffffffffffff"); } + +} // namespace TW::Aion::tests diff --git a/tests/Aion/SignerTests.cpp b/tests/chains/Aion/SignerTests.cpp similarity index 95% rename from tests/Aion/SignerTests.cpp rename to tests/chains/Aion/SignerTests.cpp index b8a7970f248..34d44504db7 100644 --- a/tests/Aion/SignerTests.cpp +++ b/tests/chains/Aion/SignerTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -10,8 +10,7 @@ #include -using namespace TW; -using namespace TW::Aion; +namespace TW::Aion::tests { TEST(AionSigner, Sign) { auto address = Aion::Address("0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e"); @@ -38,3 +37,5 @@ TEST(AionSigner, SignWithData) { // Raw transaction EXPECT_EQ(hex(transaction.encode()), "f8a109a0a082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e8227108641494f4e000085242019b04d8252088800000004a817c80001b860a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a736fc2642c2d62900204779aa274dba3b8712eff7a8464aa78ea52b09ece20679fe3f5edf94c84a7e0c5f93213be891bc279af927086f455167f5bc73d3046c0d"); } + +} // namespace TW::Aion::tests diff --git a/tests/Aion/TWAnySignerTests.cpp b/tests/chains/Aion/TWAnySignerTests.cpp similarity index 92% rename from tests/Aion/TWAnySignerTests.cpp rename to tests/chains/Aion/TWAnySignerTests.cpp index 1f30f761d03..5b7f3417014 100644 --- a/tests/Aion/TWAnySignerTests.cpp +++ b/tests/chains/Aion/TWAnySignerTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,15 +9,14 @@ #include "proto/Aion.pb.h" #include -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include -using namespace TW; -using namespace TW::Aion; +namespace TW::Aion::tests { TEST(TWAnySignerAion, Sign) { auto privateKey = parse_hex("db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9"); - + Proto::SigningInput input; input.set_to_address("0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e"); auto amount = store(uint256_t(10000)); @@ -36,3 +35,5 @@ TEST(TWAnySignerAion, Sign) { ASSERT_EQ(hex(output.encoded()), "f89b09a0a082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e8227108085242019b04d8252088800000004a817c80001b860a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a7d3d3386742c2716031b79950cef5fcb49c079a5cab095c8b08915e126b9741389924ba2d5c00036a3b39c2a8562fa0800f1a13a566ce6e027274ce63a41dec07"); } + +} // namespace TW::Aion::tests diff --git a/tests/Aion/TWCoinTypeTests.cpp b/tests/chains/Aion/TWCoinTypeTests.cpp similarity index 97% rename from tests/Aion/TWCoinTypeTests.cpp rename to tests/chains/Aion/TWCoinTypeTests.cpp index e87d983d8d8..9d217c0654f 100644 --- a/tests/Aion/TWCoinTypeTests.cpp +++ b/tests/chains/Aion/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Aion/TransactionTests.cpp b/tests/chains/Aion/TransactionTests.cpp similarity index 93% rename from tests/Aion/TransactionTests.cpp rename to tests/chains/Aion/TransactionTests.cpp index ad6b8546c2b..9b9ff818758 100644 --- a/tests/Aion/TransactionTests.cpp +++ b/tests/chains/Aion/TransactionTests.cpp @@ -1,17 +1,14 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. #include "Aion/Transaction.h" - #include "HexCoding.h" - #include -using namespace TW; -using namespace TW::Aion; +namespace TW::Aion::tests { TEST(AionTransaction, Encode) { auto address = Aion::Address("0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e"); @@ -25,3 +22,5 @@ TEST(AionTransaction, EncodeWithSignature) { transaction.signature = parse_hex("a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a7d3d3386742c2716031b79950cef5fcb49c079a5cab095c8b08915e126b9741389924ba2d5c00036a3b39c2a8562fa0800f1a13a566ce6e027274ce63a41dec07"); ASSERT_EQ(hex(transaction.encode()), "f89b09a0a082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e8227108085242019b04d8252088800000004a817c80001b860a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a7d3d3386742c2716031b79950cef5fcb49c079a5cab095c8b08915e126b9741389924ba2d5c00036a3b39c2a8562fa0800f1a13a566ce6e027274ce63a41dec07"); } + +} // namespace TW::Aion::tests diff --git a/tests/Algorand/AddressTests.cpp b/tests/chains/Algorand/AddressTests.cpp similarity index 96% rename from tests/Algorand/AddressTests.cpp rename to tests/chains/Algorand/AddressTests.cpp index 0ed495d060e..6edff289604 100644 --- a/tests/Algorand/AddressTests.cpp +++ b/tests/chains/Algorand/AddressTests.cpp @@ -4,15 +4,16 @@ // 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 "Algorand/Address.h" -#include "PublicKey.h" +#include "HexCoding.h" #include "PrivateKey.h" +#include "PublicKey.h" #include #include using namespace TW; -using namespace TW::Algorand; + +namespace TW::Algorand::tests { TEST(AlgorandAddress, Validation) { // empty address @@ -43,3 +44,5 @@ TEST(AlgorandAddress, FromString) { auto address = Address("PITDOF57RHOVLT37KM7DCXDCETLDL3OA5CBAN7LQ44Z36LGFC27IJ2IQ64"); ASSERT_EQ(address.string(), "PITDOF57RHOVLT37KM7DCXDCETLDL3OA5CBAN7LQ44Z36LGFC27IJ2IQ64"); } + +} // namespace TW::Algorand::tests diff --git a/tests/Algorand/SignerTests.cpp b/tests/chains/Algorand/SignerTests.cpp similarity index 96% rename from tests/Algorand/SignerTests.cpp rename to tests/chains/Algorand/SignerTests.cpp index f9cfcecb2bf..d3efef06418 100644 --- a/tests/Algorand/SignerTests.cpp +++ b/tests/chains/Algorand/SignerTests.cpp @@ -5,17 +5,18 @@ // file LICENSE at the root of the source code distribution tree. #include "Algorand/Address.h" -#include "Algorand/Signer.h" #include "Algorand/BinaryCoding.h" -#include "HexCoding.h" +#include "Algorand/Signer.h" #include "Base64.h" +#include "HexCoding.h" #include "PrivateKey.h" #include "PublicKey.h" #include #include using namespace TW; -using namespace TW::Algorand; + +namespace TW::Algorand::tests { TEST(AlgorandSigner, EncodeNumbers) { auto tests = { @@ -39,9 +40,7 @@ TEST(AlgorandSigner, EncodeStrings) { std::make_tuple("It's like JSON. but fast and small.", "d92349742773206c696b65204a534f4e2e20627574206661737420616e6420736d616c6c2e"), std::make_tuple( "MessagePack is an efficient binary serialization format. It lets you exchange data among multiple languages like JSON. But it's faster and smaller. Small integers are encoded into a single byte, and typical short strings require only one extra byte in addition to the strings themselves.", - "da011f4d6573736167655061636b20697320616e20656666696369656e742062696e6172792073657269616c697a6174696f6e20666f726d61742e204974206c65747320796f752065786368616e6765206461746120616d6f6e67206d756c7469706c65206c616e677561676573206c696b65204a534f4e2e2042757420697427732066617374657220616e6420736d616c6c65722e20536d616c6c20696e7465676572732061726520656e636f64656420696e746f20612073696e676c6520627974652c20616e64207479706963616c2073686f727420737472696e67732072657175697265206f6e6c79206f6e65206578747261206279746520696e206164646974696f6e20746f2074686520737472696e6773207468656d73656c7665732e" - ) - }; + "da011f4d6573736167655061636b20697320616e20656666696369656e742062696e6172792073657269616c697a6174696f6e20666f726d61742e204974206c65747320796f752065786368616e6765206461746120616d6f6e67206d756c7469706c65206c616e677561676573206c696b65204a534f4e2e2042757420697427732066617374657220616e6420736d616c6c65722e20536d616c6c20696e7465676572732061726520656e636f64656420696e746f20612073696e676c6520627974652c20616e64207479706963616c2073686f727420737472696e67732072657175697265206f6e6c79206f6e65206578747261206279746520696e206164646974696f6e20746f2074686520737472696e6773207468656d73656c7665732e")}; for (auto& test : tests) { Data data; @@ -75,8 +74,7 @@ TEST(AlgorandSigner, Sign) { /* note */ note, /* type */ "pay", /* genesis id*/ genesisId, - /* genesis hash*/ genesisHash - ); + /* genesis hash*/ genesisHash); auto serialized = transaction.serialize(); auto signature = Signer::sign(key, transaction); @@ -101,14 +99,13 @@ TEST(AlgorandSigner, SignAsset) { /* to */ to, /* fee */ 2340, /* amount */ 1000000, - /* asset id */13379146, + /* asset id */ 13379146, /* first round */ 15775683, /* last round */ 15776683, /* note */ note, /* type */ "axfer", /* genesis id*/ genesisId, - /* genesis hash*/ genesisHash - ); + /* genesis hash*/ genesisHash); auto serialized = transaction.serialize(); auto signature = Signer::sign(key, transaction); @@ -130,14 +127,13 @@ TEST(AlgorandSigner, SignAssetOptIn) { auto transaction = OptInAssetTransaction( /* from */ address, /* fee */ 2340, - /* asset id */13379146, + /* asset id */ 13379146, /* first round */ 15775553, /* last round */ 15776553, /* note */ note, /* type */ "axfer", /* genesis id*/ genesisId, - /* genesis hash*/ genesisHash - ); + /* genesis hash*/ genesisHash); auto serialized = transaction.serialize(); auto signature = Signer::sign(key, transaction); @@ -151,7 +147,7 @@ TEST(AlgorandSigner, SignAssetOptIn) { TEST(AlgorandSigner, ProtoSignerOptIn) { // https://testnet.algoexplorer.io/tx/47LE2QS4B5N6IFHXOUN2MJUTCOQCHNY6AB3AJYECK4IM2VYKJDKQ auto optIn = new Proto::AssetOptIn(); - optIn -> set_asset_id(13379146); + optIn->set_asset_id(13379146); auto privateKey = parse_hex("5a6a3cfe5ff4cc44c19381d15a0d16de2a76ee5c9b9d83b232e38cb5a2c84b04"); @@ -175,9 +171,9 @@ TEST(AlgorandSigner, ProtoSignerOptIn) { TEST(AlgorandSigner, ProtoSignerAssetTransaction) { // https://testnet.algoexplorer.io/tx/NJ62HYO2LC222AVLIN2GW5LKIWKLGC7NZLIQ3DUL2RDVRYO2UW7A auto transaction = new Proto::AssetTransfer(); - transaction -> set_asset_id(13379146); - transaction -> set_amount(1000000); - transaction -> set_to_address("GJIWJSX2EU5RC32LKTDDXWLA2YICBHKE35RV2ZPASXZYKWUWXFLKNFSS4U"); + transaction->set_asset_id(13379146); + transaction->set_amount(1000000); + transaction->set_to_address("GJIWJSX2EU5RC32LKTDDXWLA2YICBHKE35RV2ZPASXZYKWUWXFLKNFSS4U"); auto privateKey = parse_hex("5a6a3cfe5ff4cc44c19381d15a0d16de2a76ee5c9b9d83b232e38cb5a2c84b04"); @@ -197,3 +193,5 @@ TEST(AlgorandSigner, ProtoSignerAssetTransaction) { ASSERT_EQ(hex(encoded), "82a3736967c440412720eff99a17280a437bdb8eeba7404b855d6433fffd5dde7f7966c1f9ae531a1af39e18b8a58b4a6c6acb709cca92f8a18c36d8328be9520c915311027005a374786e8aa461616d74ce000f4240a461726376c420325164cafa253b116f4b54c63bd960d610209d44df635d65e095f3855a96b956a3666565cd0924a26676ce00f0b7c3a367656eac746573746e65742d76312e30a26768c4204863b518a4b3c84ec810f22d4f1081cb0f71f059a7ac20dec62f7f70e5093a22a26c76ce00f0bbaba3736e64c42082872d60c338cb928006070e02ec0942addcb79e7fbd01c76458aea526899bd3a474797065a56178666572a478616964ce00cc264a"); } + +} // namespace TW::Algorand::tests diff --git a/tests/Algorand/TWAnySignerTests.cpp b/tests/chains/Algorand/TWAnySignerTests.cpp similarity index 96% rename from tests/Algorand/TWAnySignerTests.cpp rename to tests/chains/Algorand/TWAnySignerTests.cpp index 63723876d9e..3890bafcfeb 100644 --- a/tests/Algorand/TWAnySignerTests.cpp +++ b/tests/chains/Algorand/TWAnySignerTests.cpp @@ -4,22 +4,23 @@ // 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 "Base64.h" +#include "HexCoding.h" #include "proto/Algorand.pb.h" #include -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include using namespace TW; -using namespace TW::Algorand; + +namespace TW::Algorand::tests { TEST(TWAnySignerAlgorand, Sign) { auto privateKey = parse_hex("d5b43d706ef0cb641081d45a2ec213b5d8281f439f2425d1af54e2afdaabf55b"); auto note = parse_hex("68656c6c6f"); auto genesisHash = Base64::decode("wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8="); - + Proto::SigningInput input; auto& transaction = *input.mutable_transfer(); transaction.set_to_address("CRLADAHJZEW2GFY2UPEHENLOGCUOU74WYSTUXQLVLJUJFHEUZOHYZNWYR4"); @@ -47,3 +48,5 @@ TEST(TWAnySignerAlgorand, SignJSON) { ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeAlgorand)); assertStringsEqual(result, "82a3736967c440baa00062adcdcb5875e4435cdc6885d26bfe5308ab17983c0fda790b7103051fcb111554e5badfc0ac7edf7e1223a434342a9eeed5cdb047690827325051560ba374786e8aa3616d74cf000000e8d4a51000a3666565ce00040358a26676ce001d9167a367656eac6d61696e6e65742d76312e30a26768c420c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adfa26c76ce001d954fa46e6f7465c40568656c6c6fa3726376c42014560180e9c92da3171aa3c872356e30a8ea7f96c4a74bc1755a68929c94cb8fa3736e64c42061bf060efc02e2887dfffc8ed85268c8c091c013eedf315bc50794d02a8791ada474797065a3706179"); } + +} // namespace TW::Algorand::tests diff --git a/tests/Algorand/TWCoinTypeTests.cpp b/tests/chains/Algorand/TWCoinTypeTests.cpp similarity index 97% rename from tests/Algorand/TWCoinTypeTests.cpp rename to tests/chains/Algorand/TWCoinTypeTests.cpp index f95721fa727..26d33c3670f 100644 --- a/tests/Algorand/TWCoinTypeTests.cpp +++ b/tests/chains/Algorand/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/chains/Aptos/AddressTests.cpp b/tests/chains/Aptos/AddressTests.cpp new file mode 100644 index 00000000000..92f156d077e --- /dev/null +++ b/tests/chains/Aptos/AddressTests.cpp @@ -0,0 +1,54 @@ +// Copyright © 2017-2022 Trust Wallet. +// Author: Clement Doumergue +// +// 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 "Aptos/Address.h" +#include "PublicKey.h" +#include "PrivateKey.h" +#include +#include + +namespace TW::Aptos::tests { + +TEST(AptosAddress, Valid) { + ASSERT_TRUE(Address::isValid("0x1")); + ASSERT_TRUE(Address::isValid(gAddressOne.string())); + ASSERT_TRUE(Address::isValid(gAddressZero.string())); + ASSERT_TRUE(Address::isValid("0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b")); + ASSERT_TRUE(Address::isValid("eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b")); + ASSERT_TRUE(Address::isValid("19aadeca9388e009d136245b9a67423f3eee242b03142849eb4f81a4a409e59c")); +} + +TEST(AptosAddress, Invalid) { + ASSERT_FALSE(Address::isValid("Seff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b")); // Invalid hex character + ASSERT_FALSE(Address::isValid("eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175bb")); // Invalid length: too long + ASSERT_FALSE(Address::isValid("eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175")); // Invalid length: too short +} + +TEST(AptosAddress, FromPrivateKey) { + auto privateKey = PrivateKey(parse_hex("088baa019f081d6eab8dff5c447f9ce2f83c1babf3d03686299eaf6a1e89156e")); + auto address = Address(privateKey.getPublicKey(TWPublicKeyTypeED25519)); + ASSERT_EQ(address.string(), "0xe9c4d0b6fe32a5cc8ebd1e9ad5b54a0276a57f2d081dcb5e30342319963626c3"); +} + +TEST(AptosAddress, FromPublicKey) { + auto publicKey = PublicKey(parse_hex("ad0e293a56c9fc648d1872a00521d97e6b65724519a2676c2c47cb95d131cf5a"), TWPublicKeyTypeED25519); + auto address = Address(publicKey); + ASSERT_EQ(address.string(), "0xe9c4d0b6fe32a5cc8ebd1e9ad5b54a0276a57f2d081dcb5e30342319963626c3"); +} + +TEST(AptosAddress, FromString) { + auto address = Address("eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b"); + ASSERT_EQ(address.string(), "0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b"); +} + +TEST(AptosAddress, ShortString) { + ASSERT_EQ(gAddressOne.string(), "0x0000000000000000000000000000000000000000000000000000000000000001"); + ASSERT_EQ(gAddressOne.shortString(), "1"); +} + +} // namespace TW::Aptos::tests diff --git a/tests/chains/Aptos/MoveTypesTests.cpp b/tests/chains/Aptos/MoveTypesTests.cpp new file mode 100644 index 00000000000..45c7ec321e7 --- /dev/null +++ b/tests/chains/Aptos/MoveTypesTests.cpp @@ -0,0 +1,60 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include +#include + +namespace TW::Aptos::tests { + +TEST(AptosMoveTypes, ModuleId) { + ModuleId module(gAddressOne, "coin"); + ASSERT_EQ(module.address(), gAddressOne); + ASSERT_EQ(module.name(), "coin"); + ASSERT_EQ(hex(module.accessVector()), "00000000000000000000000000000000000000000000000000000000000000000104636f696e"); + ASSERT_EQ(module.string(), "0x0000000000000000000000000000000000000000000000000000000000000001::coin"); + ASSERT_EQ(module.shortString(), "0x1::coin"); +} + +/* +TEST(AptosMoveTypes, StructTag) { + auto functorTest = [](T value, const std::string expectedHex) { + TypeTag t{.tags = value}; + StructTag st(gAddressOne, "abc", "abc", std::vector{{t}}); + ASSERT_EQ(st.moduleID().name(), "abc"); + ASSERT_EQ(st.moduleID().address(), gAddressOne); + ASSERT_EQ(hex(st.serialize()), expectedHex); + }; + functorTest(Bool{}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630100"); + functorTest(U8{}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630101"); + functorTest(U64{}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630102"); + functorTest(U128{}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630103"); + functorTest(TAddress{}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630104"); + functorTest(TSigner{}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630105"); + functorTest(Vector{.tags = std::vector{{TypeTag{.tags = U8{}}}}}, "0100000000000000000000000000000000000000000000000000000000000000010361626303616263010601"); + StructTag stInner(gAddressOne, "foo", "bar", std::vector{{U8{}}}); + functorTest(TStructTag{stInner}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630107000000000000000000000000000000000000000000000000000000000000000103666f6f036261720101"); +} +*/ +TEST(AptosMoveTypes, TypeTagDisplay) { + auto functorTest = [](const TypeTag &value, const std::string& expected) { + ASSERT_EQ(TypeTagToString(value), expected); + }; + functorTest(TypeTag{.tags = Bool{}}, "bool"); + functorTest(TypeTag{.tags = U8{}}, "u8"); + functorTest(TypeTag{.tags = U64{}}, "u64"); + functorTest(TypeTag{.tags = U128{}}, "u128"); + functorTest(TypeTag{.tags = TAddress{}}, "address"); + functorTest(TypeTag{.tags = TSigner{}}, "signer"); + TypeTag t{.tags = TypeTag::TypeTagVariant(Vector{.tags = {{U8{}}}})}; + functorTest(t, "vector"); + StructTag st(gAddressOne, "foo", "bar", std::vector{{U8{}}}); + TypeTag anotherT{.tags = TypeTag::TypeTagVariant(st)}; + functorTest(anotherT, "0x1::foo::bar"); + functorTest(gTransferTag, "0x1::aptos_coin::AptosCoin"); +} + +} // namespace TW::Aptos::tests diff --git a/tests/chains/Aptos/SignerTests.cpp b/tests/chains/Aptos/SignerTests.cpp new file mode 100644 index 00000000000..c47c05bc3e5 --- /dev/null +++ b/tests/chains/Aptos/SignerTests.cpp @@ -0,0 +1,379 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Aptos/Address.h" +#include "Aptos/Signer.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TestUtilities.h" +#include + +#include + +namespace TW::Aptos::tests { + +TEST(AptosSigner, ClaimNftTxSign) { + // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x60b51e15140ec0b7650334e948fb447ce3cb13ae63492260461ebfa9d02e85c4?network=testnet + Proto::SigningInput input; + input.set_sender("0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); + input.set_sequence_number(19); + auto& tf = *input.mutable_nft_message()->mutable_claim_nft(); + tf.set_sender("0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee"); + tf.set_creator("0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac"); + tf.set_collectionname("Topaz Troopers"); + tf.set_name("Topaz Trooper #20068"); + tf.set_property_version(0); + input.set_max_gas_amount(3296766); + input.set_gas_unit_price(100); + input.set_expiration_timestamp_secs(3664390082); + input.set_chain_id(2); + auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + auto result = Signer::sign(input); + ASSERT_EQ(hex(result.raw_txn()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3013000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572730c636c61696d5f736372697074000520783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000fe4d3200000000006400000000000000c2276ada0000000002"); + ASSERT_EQ(hex(result.authenticator().signature()), "ede1ffb5f8f663741c2ca9597af44af81c98f7a910261bb4125f758fd0c0ebbf5bacb34f1196ad45153177729eb6d478676b364ab747da17602713f65ca2dd0a"); + ASSERT_EQ(hex(result.encoded()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3013000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572730c636c61696d5f736372697074000520783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000fe4d3200000000006400000000000000c2276ada00000000020020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40ede1ffb5f8f663741c2ca9597af44af81c98f7a910261bb4125f758fd0c0ebbf5bacb34f1196ad45153177729eb6d478676b364ab747da17602713f65ca2dd0a"); + nlohmann::json expectedJson = R"( + { + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": [ + "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", + "0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac", + "Topaz Troopers", "Topaz Trooper #20068", "0"], + "function": "0x3::token_transfers::claim_script", + "type": "entry_function_payload", + "type_arguments": [] + }, + "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "19", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0xede1ffb5f8f663741c2ca9597af44af81c98f7a910261bb4125f758fd0c0ebbf5bacb34f1196ad45153177729eb6d478676b364ab747da17602713f65ca2dd0a", + "type": "ed25519_signature" + } + } + )"_json; + nlohmann::json parsedJson = nlohmann::json::parse(result.json()); + assertJSONEqual(expectedJson, parsedJson); +} + +TEST(AptosSigner, NftOfferTxSign) { + // Successfully broadcasted https://explorer.aptoslabs.com/txn/0x514e473618bd3cb89a2b110b7c473db9a2e10532f98eb42d02d86fb31c00525d?network=testnet + Proto::SigningInput input; + input.set_sender("0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee"); + input.set_sequence_number(1); + auto& tf = *input.mutable_nft_message()->mutable_offer_nft(); + tf.set_receiver("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); + tf.set_creator("0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac"); + tf.set_collectionname("Topaz Troopers"); + tf.set_name("Topaz Trooper #20068"); + tf.set_property_version(0); + tf.set_amount(1); + input.set_max_gas_amount(3296766); + input.set_gas_unit_price(100); + input.set_expiration_timestamp_secs(3664390082); + input.set_chain_id(2); + auto privateKey = PrivateKey(parse_hex("7bebb6d543d17f6fe4e685cfab239fa37896edd594ff859f1df32f244fb707e2")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + auto result = Signer::sign(input); + ASSERT_EQ(hex(result.raw_txn()), "783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee01000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572730c6f666665725f73637269707400062007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000080100000000000000fe4d3200000000006400000000000000c2276ada0000000002"); + ASSERT_EQ(hex(result.authenticator().signature()), "af5c7357a83c69e3f425beb23eaf232f8bb36dea3b7cad4a7ab8d735cee999c8ec5285005adf69dc85a6c34b042dd0308fe92b76dad5d6ac88c7b9259902c10f"); + ASSERT_EQ(hex(result.encoded()), "783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee01000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572730c6f666665725f73637269707400062007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000080100000000000000fe4d3200000000006400000000000000c2276ada00000000020020d1d99b67e37b483161a0fa369c46f34a3be4863c20e20fc7cdc669c0826a411340af5c7357a83c69e3f425beb23eaf232f8bb36dea3b7cad4a7ab8d735cee999c8ec5285005adf69dc85a6c34b042dd0308fe92b76dad5d6ac88c7b9259902c10f"); + nlohmann::json expectedJson = R"( + { + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": [ + "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac", + "Topaz Troopers", "Topaz Trooper #20068", "0", "1"], + "function": "0x3::token_transfers::offer_script", + "type": "entry_function_payload", + "type_arguments": [] + }, + "sender": "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", + "sequence_number": "1", + "signature": { + "public_key": "0xd1d99b67e37b483161a0fa369c46f34a3be4863c20e20fc7cdc669c0826a4113", + "signature": "0xaf5c7357a83c69e3f425beb23eaf232f8bb36dea3b7cad4a7ab8d735cee999c8ec5285005adf69dc85a6c34b042dd0308fe92b76dad5d6ac88c7b9259902c10f", + "type": "ed25519_signature" + } + } + )"_json; + nlohmann::json parsedJson = nlohmann::json::parse(result.json()); + assertJSONEqual(expectedJson, parsedJson); +} + +TEST(AptosSigner, CancelNftOfferTxSign) { + // Successfully broadcasted https://explorer.aptoslabs.com/txn/0x0b8c64e6847c368e4c6bd2cce0e9eab378971b0ef2e3bc40cbd292910a80201d?network=testnet + Proto::SigningInput input; + input.set_sender("0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); + input.set_sequence_number(21); + auto& tf = *input.mutable_nft_message()->mutable_cancel_offer_nft(); + tf.set_receiver("0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee"); + tf.set_creator("0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac"); + tf.set_collectionname("Topaz Troopers"); + tf.set_name("Topaz Trooper #20068"); + tf.set_property_version(0); + input.set_max_gas_amount(3296766); + input.set_gas_unit_price(100); + input.set_expiration_timestamp_secs(3664390082); + input.set_chain_id(2); + auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + auto result = Signer::sign(input); + ASSERT_EQ(hex(result.raw_txn()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3015000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572731363616e63656c5f6f666665725f736372697074000520783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000fe4d3200000000006400000000000000c2276ada0000000002"); + ASSERT_EQ(hex(result.authenticator().signature()), "826722d374e276f618123e77da3ac024c89a3f97db9e09e19aa8ed06c3cdfc57d4a21c7890137f9a7c0447cc303447ba10ca5b1908e889071e0a68f48c0f260a"); + ASSERT_EQ(hex(result.encoded()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3015000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572731363616e63656c5f6f666665725f736372697074000520783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000fe4d3200000000006400000000000000c2276ada00000000020020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40826722d374e276f618123e77da3ac024c89a3f97db9e09e19aa8ed06c3cdfc57d4a21c7890137f9a7c0447cc303447ba10ca5b1908e889071e0a68f48c0f260a"); + nlohmann::json expectedJson = R"( + { + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": [ + "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", + "0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac", + "Topaz Troopers", "Topaz Trooper #20068", "0"], + "function": "0x3::token_transfers::cancel_offer_script", + "type": "entry_function_payload", + "type_arguments": [] + }, + "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "21", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x826722d374e276f618123e77da3ac024c89a3f97db9e09e19aa8ed06c3cdfc57d4a21c7890137f9a7c0447cc303447ba10ca5b1908e889071e0a68f48c0f260a", + "type": "ed25519_signature" + } + } + )"_json; + nlohmann::json parsedJson = nlohmann::json::parse(result.json()); + assertJSONEqual(expectedJson, parsedJson); +} + +TEST(AptosSigner, TxSign) { + // Successfully broadcasted https://explorer.aptoslabs.com/txn/0xb4d62afd3862116e060dd6ad9848ccb50c2bc177799819f1d29c059ae2042467?network=devnet + Proto::SigningInput input; + input.set_sender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); + input.set_sequence_number(99); + auto& tf = *input.mutable_transfer(); + tf.set_to("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); + tf.set_amount(1000); + input.set_max_gas_amount(3296766); + input.set_gas_unit_price(100); + input.set_expiration_timestamp_secs(3664390082); + input.set_chain_id(33); + auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + auto result = Signer::sign(input); + ASSERT_EQ(hex(result.raw_txn()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada0000000021"); + ASSERT_EQ(hex(result.authenticator().signature()), "5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01"); + ASSERT_EQ(hex(result.encoded()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c405707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01"); + nlohmann::json expectedJson = R"( + { + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": ["0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30","1000"], + "function": "0x1::aptos_account::transfer", + "type": "entry_function_payload", + "type_arguments": [] + }, + "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "99", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01", + "type": "ed25519_signature" + } + } + )"_json; + nlohmann::json parsedJson = nlohmann::json::parse(result.json()); + assertJSONEqual(expectedJson, parsedJson); +} + +TEST(AptosSigner, CreateAccount) { + // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x477141736de6b0936a6c3734e4d6fd018c7d21f1f28f99028ef0bc6881168602?network=Devnet + Proto::SigningInput input; + input.set_sender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); + input.set_sequence_number(0); + auto& tf = *input.mutable_create_account(); + tf.set_auth_key("0x3aa1672641a4e17b3d913b4c0301e805755a80b12756fc729c5878f12344d30e"); + input.set_max_gas_amount(3296766); + input.set_gas_unit_price(100); + input.set_expiration_timestamp_secs(3664390082); + input.set_chain_id(33); + auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + auto result = Signer::sign(input); + ASSERT_EQ(hex(result.raw_txn()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3000000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e740e6372656174655f6163636f756e740001203aa1672641a4e17b3d913b4c0301e805755a80b12756fc729c5878f12344d30efe4d3200000000006400000000000000c2276ada0000000021"); + ASSERT_EQ(hex(result.authenticator().signature()), "fcba3dfbec76721454ef414955f09f159660a13886b4edd8c579e3c779c29073afe7b25efa3fef9b21c2efb1cf16b4247fc0e5c8f63fdcd1c8d87f5d59f44501"); + ASSERT_EQ(hex(result.encoded()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3000000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e740e6372656174655f6163636f756e740001203aa1672641a4e17b3d913b4c0301e805755a80b12756fc729c5878f12344d30efe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40fcba3dfbec76721454ef414955f09f159660a13886b4edd8c579e3c779c29073afe7b25efa3fef9b21c2efb1cf16b4247fc0e5c8f63fdcd1c8d87f5d59f44501"); + nlohmann::json expectedJson = R"( + { + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": ["0x3aa1672641a4e17b3d913b4c0301e805755a80b12756fc729c5878f12344d30e"], + "function": "0x1::aptos_account::create_account", + "type": "entry_function_payload", + "type_arguments": [] + }, + "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "0", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0xfcba3dfbec76721454ef414955f09f159660a13886b4edd8c579e3c779c29073afe7b25efa3fef9b21c2efb1cf16b4247fc0e5c8f63fdcd1c8d87f5d59f44501", + "type": "ed25519_signature" + } + } + )"_json; + nlohmann::json parsedJson = nlohmann::json::parse(result.json()); + assertJSONEqual(expectedJson, parsedJson); +} + +TEST(AptosSigner, BlindSign) { + // successfully broadcasted https://explorer.aptoslabs.com/txn/0xd95857a9e644528708778a3a0a6e13986751944fca30eaac98853c1655de0422?network=Devnet + // encoded submission with: + // curl --location --request POST 'https://fullnode.devnet.aptoslabs.com/v1/transactions/encode_submission' \ + //--header 'Content-Type: application/json' \ + //--header 'Cookie: AWSALB=0zI2zWypvEr0I3sGM6vnyHSxYO1D0aaMXfyA/2VwhA291aJJ80Yz67Fur50sXPFBI8dKKID4p8DShj1KkEXPY/NGAylpOj1EG2M2Qjuu1B38Q5C+dZW2CHT+IAZ5; AWSALBCORS=0zI2zWypvEr0I3sGM6vnyHSxYO1D0aaMXfyA/2VwhA291aJJ80Yz67Fur50sXPFBI8dKKID4p8DShj1KkEXPY/NGAylpOj1EG2M2Qjuu1B38Q5C+dZW2CHT+IAZ5' \ + //--data-raw '{ + // "expiration_timestamp_secs": "3664390082", + // "gas_unit_price": "100", + // "max_gas_amount": "3296766", + // "payload": { + // "function": "0x4633134869a61c41ad42eaca028d71c5b8b4109ffd69e1aa99c35a621b298837::pool::swap_y_to_x", + // "type_arguments": [ + // "0xdeae46f81671e76f444e2ce5a299d9e1ea06a8fa26e81dfd49aa7fa5a5a60e01::devnet_coins::DevnetUSDT", + // "0x1::aptos_coin::AptosCoin" + // ], + // "arguments": [ + // "100000000", + // "0" + // ], + // "type": "entry_function_payload" + // }, + // "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + // "sequence_number": "2" + //}' + Proto::SigningInput input; + input.set_any_encoded("0xb5e97db07fa0bd0e5598aa3643a9bc6f6693bddc1a9fec9e674a461eaa00b19307968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f300200000000000000024633134869a61c41ad42eaca028d71c5b8b4109ffd69e1aa99c35a621b29883704706f6f6c0b737761705f795f746f5f780207deae46f81671e76f444e2ce5a299d9e1ea06a8fa26e81dfd49aa7fa5a5a60e010c6465766e65745f636f696e730a4465766e657455534454000700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00020800e1f50500000000080000000000000000fe4d3200000000006400000000000000c2276ada0000000021"); + auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + auto result = Signer::sign(input); + ASSERT_EQ(hex(result.raw_txn()), "b5e97db07fa0bd0e5598aa3643a9bc6f6693bddc1a9fec9e674a461eaa00b19307968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f300200000000000000024633134869a61c41ad42eaca028d71c5b8b4109ffd69e1aa99c35a621b29883704706f6f6c0b737761705f795f746f5f780207deae46f81671e76f444e2ce5a299d9e1ea06a8fa26e81dfd49aa7fa5a5a60e010c6465766e65745f636f696e730a4465766e657455534454000700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00020800e1f50500000000080000000000000000fe4d3200000000006400000000000000c2276ada0000000021"); + ASSERT_EQ(hex(result.authenticator().signature()), "9e81026fdd43986f4d5588afdab875cd18b64dc15b3489fcc00ed46fc361915b27e23e0cefe6d23698ee76a562915fe85e99185dbc1dd29ba720f7fad144af0b"); + ASSERT_EQ(hex(result.encoded()), "b5e97db07fa0bd0e5598aa3643a9bc6f6693bddc1a9fec9e674a461eaa00b19307968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f300200000000000000024633134869a61c41ad42eaca028d71c5b8b4109ffd69e1aa99c35a621b29883704706f6f6c0b737761705f795f746f5f780207deae46f81671e76f444e2ce5a299d9e1ea06a8fa26e81dfd49aa7fa5a5a60e010c6465766e65745f636f696e730a4465766e657455534454000700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00020800e1f50500000000080000000000000000fe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c409e81026fdd43986f4d5588afdab875cd18b64dc15b3489fcc00ed46fc361915b27e23e0cefe6d23698ee76a562915fe85e99185dbc1dd29ba720f7fad144af0b"); + nlohmann::json expectedJson = R"( + { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x9e81026fdd43986f4d5588afdab875cd18b64dc15b3489fcc00ed46fc361915b27e23e0cefe6d23698ee76a562915fe85e99185dbc1dd29ba720f7fad144af0b", + "type": "ed25519_signature" + } + )"_json; + nlohmann::json parsedJson = nlohmann::json::parse(result.json()); + assertJSONEqual(expectedJson, parsedJson); +} + +TEST(AptosSigner, TokenRegisterTxSign) { + // Successfully broadcasted https://explorer.aptoslabs.com/txn/0xe591252daed785641bfbbcf72a5d17864568cf32e04c0cc9129f3a13834d0e8e?network=testnet + Proto::SigningInput input; + input.set_sender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); + input.set_sequence_number(23); + auto& tf = *input.mutable_register_token(); + tf.mutable_function()->set_account_address("0xe4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379"); + tf.mutable_function()->set_module("move_coin"); + tf.mutable_function()->set_name("MoveCoin"); + input.set_max_gas_amount(2000000); + input.set_gas_unit_price(100); + input.set_expiration_timestamp_secs(3664390082); + input.set_chain_id(2); + auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + auto result = Signer::sign(input); + ASSERT_EQ(hex(result.raw_txn()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3017000000000000000200000000000000000000000000000000000000000000000000000000000000010c6d616e616765645f636f696e0872656769737465720107e4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379096d6f76655f636f696e084d6f7665436f696e000080841e00000000006400000000000000c2276ada0000000002"); + ASSERT_EQ(hex(result.authenticator().signature()), "e230b49f552fb85356dbec9df13f0dc56228eb7a9c29a8af3a99f4ae95b86c72bdcaa4ff1e9beb0bd81c298b967b9d97449856ec8bc672a08e2efef345c37100"); + ASSERT_EQ(hex(result.encoded()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3017000000000000000200000000000000000000000000000000000000000000000000000000000000010c6d616e616765645f636f696e0872656769737465720107e4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379096d6f76655f636f696e084d6f7665436f696e000080841e00000000006400000000000000c2276ada00000000020020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40e230b49f552fb85356dbec9df13f0dc56228eb7a9c29a8af3a99f4ae95b86c72bdcaa4ff1e9beb0bd81c298b967b9d97449856ec8bc672a08e2efef345c37100"); + nlohmann::json expectedJson = R"( + { + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "2000000", + "payload": { + "arguments": [], + "function": "0x1::managed_coin::register", + "type": "entry_function_payload", + "type_arguments": ["0xe4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379::move_coin::MoveCoin"] + }, + "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "23", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0xe230b49f552fb85356dbec9df13f0dc56228eb7a9c29a8af3a99f4ae95b86c72bdcaa4ff1e9beb0bd81c298b967b9d97449856ec8bc672a08e2efef345c37100", + "type": "ed25519_signature" + } + } + )"_json; + nlohmann::json parsedJson = nlohmann::json::parse(result.json()); + assertJSONEqual(expectedJson, parsedJson); +} + +TEST(AptosSigner, TokenTxSign) { + // Successfully broadcasted https://explorer.aptoslabs.com/txn/0xb5b383a5c7f99b2edb3bed9533f8169a89051b149d65876a82f4c0b9bf78a15b?network=Devnet + Proto::SigningInput input; + input.set_sender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); + input.set_sequence_number(24); + auto& tf = *input.mutable_token_transfer(); + tf.set_to("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); + tf.set_amount(100000); + tf.mutable_function()->set_account_address("0x43417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b9"); + tf.mutable_function()->set_module("coins"); + tf.mutable_function()->set_name("BTC"); + input.set_max_gas_amount(3296766); + input.set_gas_unit_price(100); + input.set_expiration_timestamp_secs(3664390082); + input.set_chain_id(32); + auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + auto result = Signer::sign(input); + ASSERT_EQ(hex(result.raw_txn()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30180000000000000002000000000000000000000000000000000000000000000000000000000000000104636f696e087472616e73666572010743417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b905636f696e730342544300022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008a086010000000000fe4d3200000000006400000000000000c2276ada0000000020"); + ASSERT_EQ(hex(result.authenticator().signature()), "7643ec8aae6198bd13ca6ea2962265859cba5a228e7d181131f6c022700dd02a7a04dc0345ad99a0289e5ab80b130b3864e6404079980bc226f1a13aee7d280a"); + ASSERT_EQ(hex(result.encoded()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30180000000000000002000000000000000000000000000000000000000000000000000000000000000104636f696e087472616e73666572010743417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b905636f696e730342544300022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008a086010000000000fe4d3200000000006400000000000000c2276ada00000000200020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c407643ec8aae6198bd13ca6ea2962265859cba5a228e7d181131f6c022700dd02a7a04dc0345ad99a0289e5ab80b130b3864e6404079980bc226f1a13aee7d280a"); + nlohmann::json expectedJson = R"( + { + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": ["0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30","100000"], + "function": "0x1::coin::transfer", + "type": "entry_function_payload", + "type_arguments": ["0x43417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b9::coins::BTC"] + }, + "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "24", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x7643ec8aae6198bd13ca6ea2962265859cba5a228e7d181131f6c022700dd02a7a04dc0345ad99a0289e5ab80b130b3864e6404079980bc226f1a13aee7d280a", + "type": "ed25519_signature" + } + } + )"_json; + nlohmann::json parsedJson = nlohmann::json::parse(result.json()); + assertJSONEqual(expectedJson, parsedJson); +} + +} // namespace TW::Aptos::tests diff --git a/tests/chains/Aptos/TWAnySignerTests.cpp b/tests/chains/Aptos/TWAnySignerTests.cpp new file mode 100644 index 00000000000..e97b0977d9f --- /dev/null +++ b/tests/chains/Aptos/TWAnySignerTests.cpp @@ -0,0 +1,63 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Aptos/Address.h" +#include "Aptos/Signer.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include +#include +#include "TestUtilities.h" + +#include + +namespace TW::Aptos::tests { + +TEST(TWAnySignerAptos, TxSign) { + // Successfully broadcasted https://explorer.aptoslabs.com/txn/0xb4d62afd3862116e060dd6ad9848ccb50c2bc177799819f1d29c059ae2042467?network=devnet + Proto::SigningInput input; + input.set_sender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); + input.set_sequence_number(99); + auto& tf = *input.mutable_transfer(); + tf.set_to("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); + tf.set_amount(1000); + input.set_max_gas_amount(3296766); + input.set_gas_unit_price(100); + input.set_expiration_timestamp_secs(3664390082); + input.set_chain_id(33); + auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeAptos); + ASSERT_EQ(hex(output.raw_txn()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada0000000021"); + ASSERT_EQ(hex(output.authenticator().signature()), "5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01"); + ASSERT_EQ(hex(output.encoded()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c405707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01"); + nlohmann::json expectedJson = R"( + { + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": ["0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30","1000"], + "function": "0x1::aptos_account::transfer", + "type": "entry_function_payload", + "type_arguments": [] + }, + "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "99", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01", + "type": "ed25519_signature" + } + } + )"_json; + nlohmann::json parsedJson = nlohmann::json::parse(output.json()); + assertJSONEqual(expectedJson, parsedJson); +} + +} // namespace TW::Aptos::tests diff --git a/tests/chains/Aptos/TWAptosAddressTests.cpp b/tests/chains/Aptos/TWAptosAddressTests.cpp new file mode 100644 index 00000000000..e03407f5c56 --- /dev/null +++ b/tests/chains/Aptos/TWAptosAddressTests.cpp @@ -0,0 +1,34 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "HexCoding.h" +#include "TestUtilities.h" +#include +#include + +#include + +using namespace TW; + +namespace TW::Aptos::tests { + +TEST(TWAptosAddress, HDWallet) { + auto mnemonic = + "shoot island position soft burden budget tooth cruel issue economy destroy above"; + auto passphrase = ""; + + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(STRING(mnemonic).get(), STRING(passphrase).get())); + + auto privateKey = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), TWCoinTypeAptos, WRAPS(TWCoinTypeDerivationPath(TWCoinTypeAptos)).get())); + + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519(privateKey.get())); + auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeAptos)); + auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + + assertStringsEqual(addressStr, "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); +} + +} // namespace TW::Aptos::tests diff --git a/tests/chains/Aptos/TWCoinTypeTests.cpp b/tests/chains/Aptos/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..af10a478945 --- /dev/null +++ b/tests/chains/Aptos/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWAptosCoinType, TWCoinType) { + const auto coin = TWCoinTypeAptos; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("91424546")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x6af7d07b8a541913dfa87a9f99628faa255c70241ef9ebd9b82a7e715ee13108")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "aptos"); + assertStringsEqual(name, "Aptos"); + assertStringsEqual(symbol, "APT"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 8); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainAptos); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(txUrl, "https://explorer.aptoslabs.com/txn/91424546"); + assertStringsEqual(accUrl, "https://explorer.aptoslabs.com/account/0x6af7d07b8a541913dfa87a9f99628faa255c70241ef9ebd9b82a7e715ee13108"); +} diff --git a/tests/chains/Aptos/TransactionPayloadTests.cpp b/tests/chains/Aptos/TransactionPayloadTests.cpp new file mode 100644 index 00000000000..29fd5e3712a --- /dev/null +++ b/tests/chains/Aptos/TransactionPayloadTests.cpp @@ -0,0 +1,32 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include +#include + +namespace TW::Aptos::tests { + +TEST(AptosTransactionPayload, PayLoadBasis) { + ModuleId module(gAddressOne, "coin"); + std::uint64_t amount{1000}; + Address to("0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b"); + BCS::Serializer serializer; + serializer << to; + std::vector args; + args.emplace_back(serializer.bytes); + serializer.clear(); + serializer << amount; + args.emplace_back(serializer.bytes); + TransactionPayload payload = EntryFunction(module, "transfer", {gTransferTag}, args); + ASSERT_EQ(std::get(payload).module().name(), "coin"); + ASSERT_EQ(std::get(payload).module().shortString(), "0x1::coin"); + serializer.clear(); + serializer << payload; + ASSERT_EQ(hex(serializer.bytes), "02000000000000000000000000000000000000000000000000000000000000000104636f696e087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e000220eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b08e803000000000000"); +} + +} // namespace TW::Aptos::tests diff --git a/tests/Arbitrum/TWCoinTypeTests.cpp b/tests/chains/Arbitrum/TWCoinTypeTests.cpp similarity index 94% rename from tests/Arbitrum/TWCoinTypeTests.cpp rename to tests/chains/Arbitrum/TWCoinTypeTests.cpp index d0220106ec0..bab96f946ff 100644 --- a/tests/Arbitrum/TWCoinTypeTests.cpp +++ b/tests/chains/Arbitrum/TWCoinTypeTests.cpp @@ -5,7 +5,7 @@ // file LICENSE at the root of the source code distribution tree. // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -22,7 +22,7 @@ TEST(TWArbitrumCoinType, TWCoinType) { ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeArbitrum), 18); ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeArbitrum)); - assertStringsEqual(symbol, "ARETH"); + assertStringsEqual(symbol, "ETH"); assertStringsEqual(txUrl, "https://arbiscan.io/tx/0xa1e319be22c08155e5904aa211fb87df5f4ade48de79c6578b1cf3dfda9e498c"); assertStringsEqual(accUrl, "https://arbiscan.io/address/0xecf9ffa7f51e1194f89c25ad8c484f6bfd04e1ac"); assertStringsEqual(id, "arbitrum"); diff --git a/tests/chains/Aurora/TWCoinTypeTests.cpp b/tests/chains/Aurora/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..b612ce508ce --- /dev/null +++ b/tests/chains/Aurora/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// Copyright © 2017-2021 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(TWAuroraCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeAurora)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x99deebdb70f8027037abb3d3d0f3c7523daee857d85e9056d2671593ff2f2f28")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeAurora, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x8707cdE20dd43E3dB1F74c28fcd509ef38B0bA51")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeAurora, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeAurora)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeAurora)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeAurora), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeAurora)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeAurora)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeAurora)); + assertStringsEqual(symbol, "ETH"); + assertStringsEqual(txUrl, "https://aurorascan.dev/tx/0x99deebdb70f8027037abb3d3d0f3c7523daee857d85e9056d2671593ff2f2f28"); + assertStringsEqual(accUrl, "https://aurorascan.dev/address/0x8707cdE20dd43E3dB1F74c28fcd509ef38B0bA51"); + assertStringsEqual(id, "aurora"); + assertStringsEqual(name, "Aurora"); +} diff --git a/tests/Avalanche/TWCoinTypeTests.cpp b/tests/chains/Avalanche/TWCoinTypeTests.cpp similarity index 84% rename from tests/Avalanche/TWCoinTypeTests.cpp rename to tests/chains/Avalanche/TWCoinTypeTests.cpp index 49220a3a60b..8bf54001455 100644 --- a/tests/Avalanche/TWCoinTypeTests.cpp +++ b/tests/chains/Avalanche/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -27,8 +27,8 @@ TEST(TWAvalancheCoinType, TWCoinTypeCChain) { ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeAvalancheCChain)); ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeAvalancheCChain)); assertStringsEqual(symbol, "AVAX"); - assertStringsEqual(txUrl, "https://cchain.explorer.avax.network/tx/0x9243890b844219accefd8798271052f5a056453ec18984a56e81c92921330d54"); - assertStringsEqual(accUrl, "https://cchain.explorer.avax.network/address/0xa664325f36Ec33E66323fe2620AF3f2294b2Ef3A"); + assertStringsEqual(txUrl, "https://snowtrace.io/tx/0x9243890b844219accefd8798271052f5a056453ec18984a56e81c92921330d54"); + assertStringsEqual(accUrl, "https://snowtrace.io/address/0xa664325f36Ec33E66323fe2620AF3f2294b2Ef3A"); assertStringsEqual(id, "avalanchec"); assertStringsEqual(name, "Avalanche C-Chain"); } diff --git a/tests/BandChain/TWCoinTypeTests.cpp b/tests/chains/BandChain/TWCoinTypeTests.cpp similarity index 97% rename from tests/BandChain/TWCoinTypeTests.cpp rename to tests/chains/BandChain/TWCoinTypeTests.cpp index 4fdd7e8f90b..f6ad4c690c0 100644 --- a/tests/BandChain/TWCoinTypeTests.cpp +++ b/tests/chains/BandChain/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Binance/SignerTests.cpp b/tests/chains/Binance/SignerTests.cpp similarity index 100% rename from tests/Binance/SignerTests.cpp rename to tests/chains/Binance/SignerTests.cpp diff --git a/tests/Binance/TWAnySignerTests.cpp b/tests/chains/Binance/TWAnySignerTests.cpp similarity index 72% rename from tests/Binance/TWAnySignerTests.cpp rename to tests/chains/Binance/TWAnySignerTests.cpp index 8cb091000c9..84193da844a 100644 --- a/tests/Binance/TWAnySignerTests.cpp +++ b/tests/chains/Binance/TWAnySignerTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -8,17 +8,17 @@ #include "Binance/Address.h" #include "proto/Binance.pb.h" #include +#include "Coin.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include -using namespace TW; -using namespace TW::Binance; +namespace TW::Binance { Proto::SigningOutput SignTest() { auto input = Proto::SigningInput(); - input.set_chain_id("Binance-Chain-Nile"); + input.set_chain_id("Binance-Chain-Tigris"); input.set_account_number(0); input.set_sequence(0); input.set_source(0); @@ -28,15 +28,19 @@ Proto::SigningOutput SignTest() { auto& order = *input.mutable_send_order(); - // bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2 - auto fromKeyhash = parse_hex("40c2979694bbc961023d1d27be6fc4d21a9febe6"); - // bnb1hlly02l6ahjsgxw9wlcswnlwdhg4xhx38yxpd5 - auto toKeyhash = parse_hex("bffe47abfaede50419c577f1074fee6dd1535cd1"); + Address fromAddress; + EXPECT_TRUE(Address::decode("bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", fromAddress)); + EXPECT_EQ(hex(fromAddress.getKeyHash()), "40c2979694bbc961023d1d27be6fc4d21a9febe6"); + auto fromKeyhash = fromAddress.getKeyHash(); + Address toAddress; + EXPECT_TRUE(Address::decode("bnb1hlly02l6ahjsgxw9wlcswnlwdhg4xhx38yxpd5", toAddress)); + EXPECT_EQ(hex(toAddress.getKeyHash()), "bffe47abfaede50419c577f1074fee6dd1535cd1"); + auto toKeyhash = toAddress.getKeyHash(); { - auto input = order.add_inputs(); - input->set_address(fromKeyhash.data(), fromKeyhash.size()); - auto inputCoin = input->add_coins(); + auto inputOrder = order.add_inputs(); + inputOrder->set_address(fromKeyhash.data(), fromKeyhash.size()); + auto inputCoin = inputOrder->add_coins(); inputCoin->set_denom("BNB"); inputCoin->set_amount(1); } @@ -56,7 +60,7 @@ Proto::SigningOutput SignTest() { TEST(TWAnySignerBinance, Sign) { Proto::SigningOutput output = SignTest(); - ASSERT_EQ(hex(output.encoded()), "b801f0625dee0a462a2c87fa0a1f0a1440c2979694bbc961023d1d27be6fc4d21a9febe612070a03424e421001121f0a14bffe47abfaede50419c577f1074fee6dd1535cd112070a03424e421001126a0a26eb5ae98721026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e50212401b1181faec30b60a2ddaa2804c253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5366bb8b041781a6771248550d04094c3d7a504f9e8310679"); + ASSERT_EQ(hex(output.encoded()), "b801f0625dee0a462a2c87fa0a1f0a1440c2979694bbc961023d1d27be6fc4d21a9febe612070a03424e421001121f0a14bffe47abfaede50419c577f1074fee6dd1535cd112070a03424e421001126a0a26eb5ae98721026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e50212409073e581e1ea4fdf11242fe30a732f96d20799c638354bcf7a242161ac015b9321fbbed93e85b0ef9b5de58fba74dff54ecb1e379ef26e1023be8996003f4899"); } TEST(TWAnySignerBinance, SignJSON) { @@ -72,7 +76,7 @@ TEST(TWAnySignerBinance, MultithreadedSigning) { auto f = [](int n) { for (int i = 0; i < n; i++) { Proto::SigningOutput output = SignTest(); - ASSERT_EQ(hex(output.encoded()), "b801f0625dee0a462a2c87fa0a1f0a1440c2979694bbc961023d1d27be6fc4d21a9febe612070a03424e421001121f0a14bffe47abfaede50419c577f1074fee6dd1535cd112070a03424e421001126a0a26eb5ae98721026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e50212401b1181faec30b60a2ddaa2804c253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5366bb8b041781a6771248550d04094c3d7a504f9e8310679"); + ASSERT_EQ(hex(output.encoded()), "b801f0625dee0a462a2c87fa0a1f0a1440c2979694bbc961023d1d27be6fc4d21a9febe612070a03424e421001121f0a14bffe47abfaede50419c577f1074fee6dd1535cd112070a03424e421001126a0a26eb5ae98721026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e50212409073e581e1ea4fdf11242fe30a732f96d20799c638354bcf7a242161ac015b9321fbbed93e85b0ef9b5de58fba74dff54ecb1e379ef26e1023be8996003f4899"); } }; @@ -85,3 +89,5 @@ TEST(TWAnySignerBinance, MultithreadedSigning) { th2.join(); th3.join(); } + +} // namespace TW::Binance diff --git a/tests/Binance/TWCoinTypeTests.cpp b/tests/chains/Binance/TWCoinTypeTests.cpp similarity index 89% rename from tests/Binance/TWCoinTypeTests.cpp rename to tests/chains/Binance/TWCoinTypeTests.cpp index aaa9c3f0b9e..2059c040c6b 100644 --- a/tests/Binance/TWCoinTypeTests.cpp +++ b/tests/chains/Binance/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -21,6 +21,7 @@ TEST(TWBinanceCoinType, TWCoinType) { auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeBinance, accId.get())); auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeBinance)); auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeBinance)); + const auto chainId = WRAPS(TWCoinTypeChainId(TWCoinTypeBinance)); ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeBinance), 8); ASSERT_EQ(TWBlockchainBinance, TWCoinTypeBlockchain(TWCoinTypeBinance)); @@ -30,5 +31,6 @@ TEST(TWBinanceCoinType, TWCoinType) { assertStringsEqual(txUrl, "https://explorer.binance.org/tx/A93625C9F9ABEA1A8E31585B30BBB16C34FAE0D172EB5B6B2F834AF077BF06BB"); assertStringsEqual(accUrl, "https://explorer.binance.org/address/bnb1u7jm0cll5h3224y0tapwn6gf6pr49ytewx4gsz"); assertStringsEqual(id, "binance"); - assertStringsEqual(name, "BNB"); + assertStringsEqual(name, "BNB Beacon Chain"); + assertStringsEqual(chainId, "Binance-Chain-Tigris"); } diff --git a/tests/BinanceSmartChain/SignerTests.cpp b/tests/chains/BinanceSmartChain/SignerTests.cpp similarity index 84% rename from tests/BinanceSmartChain/SignerTests.cpp rename to tests/chains/BinanceSmartChain/SignerTests.cpp index b3721eb7eec..6303410de22 100644 --- a/tests/BinanceSmartChain/SignerTests.cpp +++ b/tests/chains/BinanceSmartChain/SignerTests.cpp @@ -1,34 +1,28 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. #include -#include #include "Ethereum/Signer.h" #include "Ethereum/Transaction.h" #include "Ethereum/Address.h" -#include "Ethereum/RLP.h" #include "Ethereum/ABI.h" #include "proto/Ethereum.pb.h" #include "HexCoding.h" #include "uint256.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include namespace TW::Binance { -using namespace TW::Ethereum; -using namespace TW::Ethereum::ABI; - - TEST(BinanceSmartChain, SignNativeTransfer) { // https://explorer.binance.org/smart-testnet/tx/0x6da28164f7b3bc255d749c3ae562e2a742be54c12bf1858b014cc2fe5700684e auto toAddress = parse_hex("0x31BE00EB1fc8e14A696DBC72f746ec3e95f49683"); - auto transaction = TransactionNonTyped::buildNativeTransfer( + auto transaction = Ethereum::TransactionNonTyped::buildNativeTransfer( /* nonce: */ 0, /* gasPrice: */ 20000000000, /* gasLimit: */ 21000, @@ -39,7 +33,7 @@ TEST(BinanceSmartChain, SignNativeTransfer) { // addr: 0xB9F5771C27664bF2282D98E09D7F50cEc7cB01a7 mnemonic: isolate dismiss ... cruel note auto privateKey = PrivateKey(parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904")); uint256_t chainID = 97; - auto signature = Signer::sign(privateKey, chainID, transaction); + auto signature = Ethereum::Signer::sign(privateKey, chainID, transaction); auto encoded = transaction->encoded(signature, chainID); ASSERT_EQ(hex(encoded), "f86c808504a817c8008252089431be00eb1fc8e14a696dbc72f746ec3e95f49683872386f26fc100008081e5a057806b486844c5d0b7b5ce34b289f4e8776aa1fe24a3311cef5053995c51050ca07697aa0695de27da817625df0e7e4c64b0ab22d9df30aec92299a7b380be8db7"); @@ -47,15 +41,15 @@ TEST(BinanceSmartChain, SignNativeTransfer) { TEST(BinanceSmartChain, SignTokenTransfer) { auto toAddress = parse_hex("0x31BE00EB1fc8e14A696DBC72f746ec3e95f49683"); - auto func = Function("transfer", std::vector>{ - std::make_shared(toAddress), - std::make_shared(uint256_t(10000000000000000)) + auto func = Ethereum::ABI::Function("transfer", std::vector>{ + std::make_shared(toAddress), + std::make_shared(uint256_t(10000000000000000)) }); Data payloadFunction; func.encode(payloadFunction); EXPECT_EQ(hex(payloadFunction), "a9059cbb00000000000000000000000031be00eb1fc8e14a696dbc72f746ec3e95f49683000000000000000000000000000000000000000000000000002386f26fc10000"); - auto input = Proto::SigningInput(); + auto input = Ethereum::Proto::SigningInput(); auto chainId = store(uint256_t(97)); auto nonce = store(uint256_t(30)); auto gasPrice = store(uint256_t(20000000000)); @@ -76,7 +70,7 @@ TEST(BinanceSmartChain, SignTokenTransfer) { const std::string expected = "f8ab1e8504a817c800830f424094ed24fc36d5ee211ea25a80239fb8c4cfd80f12ee80b844a9059cbb00000000000000000000000031be00eb1fc8e14a696dbc72f746ec3e95f49683000000000000000000000000000000000000000000000000002386f26fc1000081e6a0aa9d5e9a947e96f728fe5d3e6467000cd31a693c00270c33ec64b4abddc29516a00bf1d5646139b2bcca1ad64e6e79f45b7d1255de603b5a3765cbd9544ae148d0"; - Proto::SigningOutput output; + Ethereum::Proto::SigningOutput output; ANY_SIGN(input, TWCoinTypeSmartChain); EXPECT_EQ(hex(output.encoded()), expected); diff --git a/tests/BinanceSmartChain/TWAnyAddressTests.cpp b/tests/chains/BinanceSmartChain/TWAnyAddressTests.cpp similarity index 96% rename from tests/BinanceSmartChain/TWAnyAddressTests.cpp rename to tests/chains/BinanceSmartChain/TWAnyAddressTests.cpp index e50537d7b8c..f7769d5c198 100644 --- a/tests/BinanceSmartChain/TWAnyAddressTests.cpp +++ b/tests/chains/BinanceSmartChain/TWAnyAddressTests.cpp @@ -7,7 +7,7 @@ #include #include "HexCoding.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include using namespace TW; diff --git a/tests/BinanceSmartChain/TWCoinTypeTests.cpp b/tests/chains/BinanceSmartChain/TWCoinTypeTests.cpp similarity index 96% rename from tests/BinanceSmartChain/TWCoinTypeTests.cpp rename to tests/chains/BinanceSmartChain/TWCoinTypeTests.cpp index 8cca95a5cf4..f48a6b1e56e 100644 --- a/tests/BinanceSmartChain/TWCoinTypeTests.cpp +++ b/tests/chains/BinanceSmartChain/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -31,7 +31,7 @@ TEST(TWBinanceSmartChainCoinType, TWCoinType) { assertStringsEqual(txUrl, "https://bscscan.com/tx/0xb9ae2e808fe8e57171f303ad8f6e3fd17d949b0bfc7b4db6e8e30a71cc517d7e"); assertStringsEqual(accUrl, "https://bscscan.com/address/0x35552c16704d214347f29Fa77f77DA6d75d7C752"); assertStringsEqual(id, "smartchain"); - assertStringsEqual(name, "Smart Chain"); + assertStringsEqual(name, "BNB Smart Chain"); } TEST(TWBinanceSmartChainLegacyCoinType, TWCoinType) { diff --git a/tests/Bitcoin/BitcoinAddressTests.cpp b/tests/chains/Bitcoin/BitcoinAddressTests.cpp similarity index 91% rename from tests/Bitcoin/BitcoinAddressTests.cpp rename to tests/chains/Bitcoin/BitcoinAddressTests.cpp index 6b8dcba3fc5..dd769f6a29e 100644 --- a/tests/Bitcoin/BitcoinAddressTests.cpp +++ b/tests/chains/Bitcoin/BitcoinAddressTests.cpp @@ -5,16 +5,17 @@ // file LICENSE at the root of the source code distribution tree. #include "Bitcoin/Address.h" -#include -#include "PublicKey.h" #include "Bitcoin/Script.h" #include "HexCoding.h" +#include "PublicKey.h" +#include -#include #include +#include using namespace TW; -using namespace TW::Bitcoin; + +namespace TW::Bitcoin::tests { const char* TestPubKey1 = "039d645d2ce630c2a9a6dbe0cbd0a8fcb7b70241cb8a48424f25593290af2494b9"; const char* TestP2phkAddr1 = "12dNaXQtN5Asn2YFwT1cvciCrJa525fAe4"; @@ -50,20 +51,21 @@ TEST(BitcoinAddress, P2SH_CreateFromString) { TEST(BitcoinAddress, P2WPKH_Nested_P2SH) { // P2SH address cannot be created directly from pubkey, script is built const auto publicKey = PublicKey(parse_hex(TestPubKey1), TWPublicKeyTypeSECP256k1); - + const auto pubKeyHash = publicKey.hash({}); EXPECT_EQ(hex(pubKeyHash), "11d91ce1cc681f95583da3f4a6841c174be950c7"); - - const auto script = Script::buildPayToWitnessProgram(pubKeyHash); - EXPECT_EQ(hex(script.bytes), "0014" "11d91ce1cc681f95583da3f4a6841c174be950c7"); - + + const auto script = Script::buildPayToV0WitnessProgram(pubKeyHash); + EXPECT_EQ(hex(script.bytes), "0014" + "11d91ce1cc681f95583da3f4a6841c174be950c7"); + const auto scriptHash = Hash::sha256ripemd(script.bytes.data(), script.bytes.size()); EXPECT_EQ(hex(scriptHash), "ee1e69460b59027d9df0a79ca2c92aa382a25fb7"); - + Data addressData = {TWCoinTypeP2shPrefix(TWCoinTypeBitcoin)}; TW::append(addressData, scriptHash); EXPECT_EQ(hex(addressData), TestP2shData1); - + const auto address = Address(addressData); EXPECT_EQ(address.string(), TestP2shAddr1); EXPECT_EQ(hex(address.bytes), TestP2shData1); @@ -74,3 +76,5 @@ TEST(BitcoinAddress, P2SH_CreateFromData) { EXPECT_EQ(address.string(), TestP2shAddr1); EXPECT_EQ(hex(address.bytes), TestP2shData1); } + +} // namespace TW::Bitcoin::tests diff --git a/tests/Bitcoin/BitcoinScriptTests.cpp b/tests/chains/Bitcoin/BitcoinScriptTests.cpp similarity index 92% rename from tests/Bitcoin/BitcoinScriptTests.cpp rename to tests/chains/Bitcoin/BitcoinScriptTests.cpp index 56109ac2f47..66c50b9701e 100644 --- a/tests/Bitcoin/BitcoinScriptTests.cpp +++ b/tests/chains/Bitcoin/BitcoinScriptTests.cpp @@ -6,13 +6,13 @@ #include "Bitcoin/Script.h" #include "Bitcoin/SignatureBuilder.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include "HexCoding.h" #include using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin::tests { const Script PayToScriptHash(parse_hex("a914" "4733f37cf4db86fbc2efed2500b4f4e49f312023" "87")); const Script PayToWitnessScriptHash(parse_hex("0020" "ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db")); @@ -61,7 +61,7 @@ TEST(BitcoinScript, PayToScriptHash) { EXPECT_EQ(hex(script.bytes), hex(PayToScriptHash.bytes)); EXPECT_EQ(PayToScriptHash.isPayToScriptHash(), true); - EXPECT_EQ(PayToScriptHash.bytes.size(), 23); + EXPECT_EQ(PayToScriptHash.bytes.size(), 23ul); EXPECT_EQ(PayToWitnessScriptHash.isPayToScriptHash(), false); EXPECT_EQ(PayToWitnessPublicKeyHash.isPayToScriptHash(), false); @@ -85,7 +85,7 @@ TEST(BitcoinScript, PayToWitnessScriptHash) { EXPECT_EQ(hex(script.bytes), hex(PayToWitnessScriptHash.bytes)); EXPECT_EQ(PayToWitnessScriptHash.isPayToWitnessScriptHash(), true); - EXPECT_EQ(PayToWitnessScriptHash.bytes.size(), 34); + EXPECT_EQ(PayToWitnessScriptHash.bytes.size(), 34ul); EXPECT_EQ(PayToScriptHash.isPayToWitnessScriptHash(), false); EXPECT_EQ(PayToWitnessPublicKeyHash.isPayToWitnessScriptHash(), false); @@ -109,7 +109,7 @@ TEST(BitcoinScript, PayToWitnessPublicKeyHash) { EXPECT_EQ(hex(script.bytes), hex(PayToWitnessPublicKeyHash.bytes)); EXPECT_EQ(PayToWitnessPublicKeyHash.isPayToWitnessPublicKeyHash(), true); - EXPECT_EQ(PayToWitnessPublicKeyHash.bytes.size(), 22); + EXPECT_EQ(PayToWitnessPublicKeyHash.bytes.size(), 22ul); EXPECT_EQ(PayToScriptHash.isPayToWitnessPublicKeyHash(), false); EXPECT_EQ(PayToWitnessScriptHash.isPayToWitnessPublicKeyHash(), false); @@ -160,21 +160,21 @@ TEST(BitcoinScript, GetScriptOp) { { size_t index = 0; uint8_t opcode; Data operand; EXPECT_EQ(Script(parse_hex("4f")).getScriptOp(index, opcode, operand), true); - EXPECT_EQ(index, 1); + EXPECT_EQ(index, 1ul); EXPECT_EQ(opcode, 0x4f); EXPECT_EQ(hex(operand), ""); } { size_t index = 0; uint8_t opcode; Data operand; EXPECT_EQ(Script(parse_hex("05" "0102030405")).getScriptOp(index, opcode, operand), true); - EXPECT_EQ(index, 6); + EXPECT_EQ(index, 6ul); EXPECT_EQ(opcode, 0x05); EXPECT_EQ(hex(operand), "0102030405"); } { // OP_PUSHDATA1 size_t index = 0; uint8_t opcode; Data operand; EXPECT_EQ(Script(parse_hex("4c" "05" "0102030405")).getScriptOp(index, opcode, operand), true); - EXPECT_EQ(index, 7); + EXPECT_EQ(index, 7ul); EXPECT_EQ(opcode, 0x4c); EXPECT_EQ(hex(operand), "0102030405"); } @@ -189,7 +189,7 @@ TEST(BitcoinScript, GetScriptOp) { { // OP_PUSHDATA2 size_t index = 0; uint8_t opcode; Data operand; EXPECT_EQ(Script(parse_hex("4d" "0500" "0102030405")).getScriptOp(index, opcode, operand), true); - EXPECT_EQ(index, 8); + EXPECT_EQ(index, 8ul); EXPECT_EQ(opcode, 0x4d); EXPECT_EQ(hex(operand), "0102030405"); } @@ -204,7 +204,7 @@ TEST(BitcoinScript, GetScriptOp) { { // OP_PUSHDATA4 size_t index = 0; uint8_t opcode; Data operand; EXPECT_EQ(Script(parse_hex("4e" "05000000" "0102030405")).getScriptOp(index, opcode, operand), true); - EXPECT_EQ(index, 10); + EXPECT_EQ(index, 10ul); EXPECT_EQ(opcode, 0x4e); EXPECT_EQ(hex(operand), "0102030405"); } @@ -239,7 +239,7 @@ TEST(BitcoinScript, MatchMultiSig) { // valid one key EXPECT_EQ(Script(parse_hex("51" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "51" "ae")).matchMultisig(keys, required), true); EXPECT_EQ(required, 1); - ASSERT_EQ(keys.size(), 1); + ASSERT_EQ(keys.size(), 1ul); EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); EXPECT_EQ(Script(parse_hex("51" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "21" "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1" "51" "ae")).matchMultisig(keys, required), false); @@ -248,7 +248,7 @@ TEST(BitcoinScript, MatchMultiSig) { // valid two keys EXPECT_EQ(Script(parse_hex("52" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "21" "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1" "52" "ae")).matchMultisig(keys, required), true); EXPECT_EQ(required, 2); - ASSERT_EQ(keys.size(), 2); + ASSERT_EQ(keys.size(), 2ul); EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); EXPECT_EQ(hex(keys[1]), "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); @@ -261,7 +261,7 @@ TEST(BitcoinScript, MatchMultiSig) { // valid one key, OP_PUSHDATA1 EXPECT_EQ(Script(parse_hex("514c" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "51" "ae")).matchMultisig(keys, required), true); EXPECT_EQ(required, 1); - ASSERT_EQ(keys.size(), 1); + ASSERT_EQ(keys.size(), 1ul); EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); // OP_PUSHDATA2 @@ -273,7 +273,7 @@ TEST(BitcoinScript, MatchMultiSig) { // valid one key, OP_PUSHDATA2 EXPECT_EQ(Script(parse_hex("514d" "2100" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "51" "ae")).matchMultisig(keys, required), true); EXPECT_EQ(required, 1); - ASSERT_EQ(keys.size(), 1); + ASSERT_EQ(keys.size(), 1ul); EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); // OP_PUSHDATA4 @@ -286,7 +286,7 @@ TEST(BitcoinScript, MatchMultiSig) { // valid one key, OP_PUSHDATA2 EXPECT_EQ(Script(parse_hex("514e" "21000000" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "51" "ae")).matchMultisig(keys, required), true); EXPECT_EQ(required, 1); - ASSERT_EQ(keys.size(), 1); + ASSERT_EQ(keys.size(), 1ul); EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); // valid three keys, mixed @@ -296,12 +296,31 @@ TEST(BitcoinScript, MatchMultiSig) { "4e" "21000000" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "53" "ae")).matchMultisig(keys, required), true); EXPECT_EQ(required, 3); - ASSERT_EQ(keys.size(), 3); + ASSERT_EQ(keys.size(), 3ul); EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); EXPECT_EQ(hex(keys[1]), "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); EXPECT_EQ(hex(keys[2]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); } +TEST(BitcoinScript, OpReturn) { + { + Data data = parse_hex("00010203"); + Script script = Script::buildOpReturnScript(data); + EXPECT_EQ(hex(script.bytes), "6a0400010203"); + } + { + Data data = parse_hex("535741503a54484f522e52554e453a74686f72317470657263616d6b6b7865633071306a6b366c74646e6c7176737732396775617038776d636c3a"); + Script script = Script::buildOpReturnScript(data); + EXPECT_EQ(hex(script.bytes), "6a3b535741503a54484f522e52554e453a74686f72317470657263616d6b6b7865633071306a6b366c74646e6c7176737732396775617038776d636c3a"); + } + { + // too long, truncated + Data data = Data(70); + Script script = Script::buildOpReturnScript(data); + EXPECT_EQ(hex(script.bytes), "6a4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + } +} + TEST(BitcoinTransactionSigner, PushAllEmpty) { { std::vector input = {}; @@ -346,4 +365,5 @@ TEST(BitcoinTransactionSigner, PushAllEmpty) { Data res = SignatureBuilder::pushAll(input); EXPECT_EQ(hex(res), hex(expected)); } -} \ No newline at end of file +} +} // namespace TW::Bitcoin::tests diff --git a/tests/Bitcoin/FeeCalculatorTests.cpp b/tests/chains/Bitcoin/FeeCalculatorTests.cpp similarity index 89% rename from tests/Bitcoin/FeeCalculatorTests.cpp rename to tests/chains/Bitcoin/FeeCalculatorTests.cpp index 755ea13efa0..93ea4013e77 100644 --- a/tests/Bitcoin/FeeCalculatorTests.cpp +++ b/tests/chains/Bitcoin/FeeCalculatorTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -8,9 +8,7 @@ #include -using namespace TW; -using namespace TW::Bitcoin; - +namespace TW::Bitcoin { TEST(BitcoinFeeCalculator, ConstantFeeCalculator) { const auto feeCalculator = ConstantFeeCalculator(33); @@ -32,7 +30,7 @@ TEST(BitcoinFeeCalculator, LinearFeeCalculator) { } TEST(BitcoinFeeCalculator, BitcoinCalculate) { - FeeCalculator& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + const FeeCalculator& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 174); EXPECT_EQ(feeCalculator.calculate(1, 1, 1), 143); EXPECT_EQ(feeCalculator.calculate(0, 2, 1), 72); @@ -42,7 +40,7 @@ TEST(BitcoinFeeCalculator, BitcoinCalculate) { } TEST(BitcoinFeeCalculator, SegwitCalculate) { - FeeCalculator& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + const FeeCalculator& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 174); EXPECT_EQ(feeCalculator.calculate(1, 1, 1), 143); EXPECT_EQ(feeCalculator.calculate(0, 2, 1), 72); @@ -69,9 +67,11 @@ TEST(BitcoinFeeCalculator, DefaultCalculateSingleInput) { } TEST(BitcoinFeeCalculator, DecredCalculate) { - FeeCalculator& feeCalculator = getFeeCalculator(TWCoinTypeDecred); + const FeeCalculator& feeCalculator = getFeeCalculator(TWCoinTypeDecred); EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 254); EXPECT_EQ(feeCalculator.calculate(0, 0, 1), 12); EXPECT_EQ(feeCalculator.calculate(1, 2, 10), 2540); EXPECT_EQ(feeCalculator.calculateSingleInput(1), 166); } + +} // namespace TW::Bitcoin diff --git a/tests/Bitcoin/InputSelectorTests.cpp b/tests/chains/Bitcoin/InputSelectorTests.cpp similarity index 96% rename from tests/Bitcoin/InputSelectorTests.cpp rename to tests/chains/Bitcoin/InputSelectorTests.cpp index ad773a72465..1f66fedeec5 100644 --- a/tests/Bitcoin/InputSelectorTests.cpp +++ b/tests/chains/Bitcoin/InputSelectorTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -7,17 +7,11 @@ #include #include "TxComparisonHelper.h" -#include "Bitcoin/OutPoint.h" -#include "Bitcoin/Script.h" #include "Bitcoin/InputSelector.h" -#include "proto/Bitcoin.pb.h" #include -#include - -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { TEST(BitcoinInputSelector, SelectUnspents1) { auto utxos = buildTestUTXOs({4000, 2000, 6000, 1000, 11000, 12000}); @@ -193,7 +187,7 @@ TEST(BitcoinInputSelector, SelectThreeNoDust) { // 100'000 would fit with dust; instead two UTXOs are selected not to leave dust EXPECT_TRUE(verifySelectedUTXOs(selected, {75'000, 100'000})); - + EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 174); const auto dustLimit = 102; @@ -432,7 +426,7 @@ TEST(BitcoinInputSelector, ManyUtxos_900) { valueSum += val; } const uint64_t requestedAmount = valueSum / 8; - EXPECT_EQ(requestedAmount, 5'068'125); + EXPECT_EQ(requestedAmount, 5'068'125ul); auto utxos = buildTestUTXOs(values); auto selector = InputSelector(utxos); @@ -446,8 +440,8 @@ TEST(BitcoinInputSelector, ManyUtxos_900) { subset.push_back(val); subsetSum += val; } - EXPECT_EQ(subset.size(), 59); - EXPECT_EQ(subsetSum, 5'138'900); + EXPECT_EQ(subset.size(), 59ul); + EXPECT_EQ(subsetSum, 5'138'900ul); EXPECT_TRUE(verifySelectedUTXOs(selected, subset)); } @@ -462,7 +456,7 @@ TEST(BitcoinInputSelector, ManyUtxos_5000_simple) { valueSum += val; } const uint64_t requestedAmount = valueSum / 20; - EXPECT_EQ(requestedAmount, 62'512'500); + EXPECT_EQ(requestedAmount, 62'512'500ul); auto utxos = buildTestUTXOs(values); auto selector = InputSelector(utxos); @@ -471,13 +465,13 @@ TEST(BitcoinInputSelector, ManyUtxos_5000_simple) { // expected result: 1205 utxos, with the smaller amounts (except the very small dust ones) std::vector subset; uint64_t subsetSum = 0; - for (int i = 10; i < 1205+10; ++i) { + for (int i = 10; i < 1205 + 10; ++i) { const uint64_t val = (i + 1) * 100; subset.push_back(val); subsetSum += val; } - EXPECT_EQ(subset.size(), 1205); - EXPECT_EQ(subsetSum, 73'866'500); + EXPECT_EQ(subset.size(), 1205ul); + EXPECT_EQ(subsetSum, 73'866'500ul); EXPECT_TRUE(verifySelectedUTXOs(selected, subset)); } @@ -485,11 +479,9 @@ TEST(BitcoinInputSelector, ManyUtxos_MaxAmount_5000) { const auto n = 5000; const auto byteFee = 10; std::vector values; - uint64_t valueSum = 0; for (int i = 0; i < n; ++i) { const uint64_t val = (i + 1) * 100; values.push_back(val); - valueSum += val; } auto utxos = buildTestUTXOs(values); @@ -499,12 +491,14 @@ TEST(BitcoinInputSelector, ManyUtxos_MaxAmount_5000) { // expected result: 4990 utxos (none of which is dust) std::vector subset; uint64_t subsetSum = 0; - for (int i = 10; i < 4990+10; ++i) { + for (int i = 10; i < 4990 + 10; ++i) { const uint64_t val = (i + 1) * 100; subset.push_back(val); subsetSum += val; } - EXPECT_EQ(subset.size(), 4990); - EXPECT_EQ(subsetSum, 1'250'244'500); + EXPECT_EQ(subset.size(), 4990ul); + EXPECT_EQ(subsetSum, 1'250'244'500ul); EXPECT_TRUE(verifySelectedUTXOs(selected, subset)); } + +} // namespace TW::Bitcoin diff --git a/tests/chains/Bitcoin/MessageSignerTests.cpp b/tests/chains/Bitcoin/MessageSignerTests.cpp new file mode 100644 index 00000000000..df617721300 --- /dev/null +++ b/tests/chains/Bitcoin/MessageSignerTests.cpp @@ -0,0 +1,167 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Bitcoin/MessageSigner.h" +#include +#include "Bitcoin/Address.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "Base64.h" +#include "Coin.h" +#include "Data.h" +#include "TestUtilities.h" + +#include +#include + +#include +#include + +namespace TW::Bitcoin::MessageSignerTests { + +const auto gPrivateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + +TEST(BitcoinMessageSigner, VerifyMessage) { + EXPECT_TRUE(MessageSigner::verifyMessage( + "1B8Qea79tsxmn4dTiKKRVvsJpHwL2fMQnr", + "test signature", + "H+3L5IbSVcejp4S2VwLXCxLEMQAWDvKbE8lQyq0ocdvyM1aoEudkzN/S/qLI3vnNOFY6V13BXWSFrPr3OjGa5Dk=" + )); + EXPECT_TRUE(MessageSigner::verifyMessage( + "1HZwkjkeaoZfTSaJxDw6aKkxp45agDiEzN", + "This is an example of a signed message.", + "G39Qf0XrZHICWbz3r5gOkcgTRw3vM4leGjiR3refr/K1OezcKmmXaLn4zc8ji2rjbBUIMrIhH/jc5Z2qEEz7qVk=" + )); + EXPECT_TRUE(MessageSigner::verifyMessage( + "1H8X4u6CVZRTLLNbUQTKAnc5vCkqWMpwfF", + "compressed key", + "IKUI9v2xbHogJe8HKXI2M5KEhMKaW6fjNxtyEy27Mf+3/e1ht4jZoc85e4F8stPsxt4Xcg8Yr42S28O6L/Qx9fE=" + )); + EXPECT_TRUE(MessageSigner::verifyMessage( + "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X", + "test signature", + "ILH5K7JQLaRGaKGXXH5mYM6FIIy9IWyY4JUPI+PHYY4WaupxUbg+zy0bhBCrDuehy9x4WidwjkRR1GSLnWvOXBo=" + )); + EXPECT_TRUE(MessageSigner::verifyMessage( + "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X", + "another text", + "H7vrF2C+TlFiHyegAw3QLv6SK0myuEEXUOgfx0+Qio1YVDuSa6p/OHpoQVlUt3F8QJdbdZN9M1h/fYEAnEz16V0=" + )); + EXPECT_TRUE(MessageSigner::verifyMessage( + "1E4T9JZ3mq6cdgiRJEWzHqDXb9t322fE6d", + "test signature", + "HLH5K7JQLaRGaKGXXH5mYM6FIIy9IWyY4JUPI+PHYY4WaupxUbg+zy0bhBCrDuehy9x4WidwjkRR1GSLnWvOXBo=" + )); +} + +TEST(BitcoinMessageSigner, SignAndVerify) { + const auto pubKey = gPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(pubKey.bytes), "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); + const auto address = Address(pubKey, TW::p2pkhPrefix(TWCoinTypeBitcoin)).string(); + EXPECT_EQ(address, "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X"); + + { + const auto msg = "test signature"; + const auto signature = MessageSigner::signMessage(gPrivateKey, address, msg); + EXPECT_EQ(signature, "ILH5K7JQLaRGaKGXXH5mYM6FIIy9IWyY4JUPI+PHYY4WaupxUbg+zy0bhBCrDuehy9x4WidwjkRR1GSLnWvOXBo="); + + EXPECT_TRUE(MessageSigner::verifyMessage(address, msg, signature)); + } + { + const auto msg = "another text"; + const auto signature = MessageSigner::signMessage(gPrivateKey, address, msg); + EXPECT_EQ(signature, "H7vrF2C+TlFiHyegAw3QLv6SK0myuEEXUOgfx0+Qio1YVDuSa6p/OHpoQVlUt3F8QJdbdZN9M1h/fYEAnEz16V0="); + + EXPECT_TRUE(MessageSigner::verifyMessage(address, msg, signature)); + } + + // uncompressed + const auto pubKeyUncomp = gPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + const auto keyHash = pubKeyUncomp.hash(Data{TW::p2pkhPrefix(TWCoinTypeBitcoin)}, Hash::HasherSha256ripemd); + const auto addressUncomp = Address(keyHash).string(); + EXPECT_EQ(addressUncomp, "1E4T9JZ3mq6cdgiRJEWzHqDXb9t322fE6d"); + { + const auto msg = "test signature"; + const auto signature = MessageSigner::signMessage(gPrivateKey, addressUncomp, msg, false); + EXPECT_EQ(signature, "HLH5K7JQLaRGaKGXXH5mYM6FIIy9IWyY4JUPI+PHYY4WaupxUbg+zy0bhBCrDuehy9x4WidwjkRR1GSLnWvOXBo="); + + EXPECT_TRUE(MessageSigner::verifyMessage(addressUncomp, msg, signature)); + } +} + +TEST(BitcoinMessageSigner, SignNegative) { + const auto address = Address(gPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1), TW::p2pkhPrefix(TWCoinTypeBitcoin)).string(); + EXPECT_EQ(address, "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X"); + const auto msg = "test signature"; + // Use invalid address + EXPECT_EXCEPTION(MessageSigner::signMessage(gPrivateKey, "__THIS_IS_NOT_A_VALID_ADDRESS__", msg), "Address is not valid (legacy) address"); + // Use invalid address, not legacy + EXPECT_EXCEPTION(MessageSigner::signMessage(gPrivateKey, "bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8", msg), "Address is not valid (legacy) address"); + // Use valid, but not matching key + EXPECT_EXCEPTION(MessageSigner::signMessage(gPrivateKey, "1B8Qea79tsxmn4dTiKKRVvsJpHwL2fMQnr", msg), "Address does not match key"); +} + +TEST(BitcoinMessageSigner, VerifyNegative) { + const auto sig = parse_hex("1fedcbe486d255c7a3a784b65702d70b12c43100160ef29b13c950caad2871dbf23356a812e764ccdfd2fea2c8def9cd38563a575dc15d6485acfaf73a319ae439"); + // Baseline positive + EXPECT_TRUE(MessageSigner::verifyMessage( + "1B8Qea79tsxmn4dTiKKRVvsJpHwL2fMQnr", + "test signature", + sig + )); + + // Provide non-matching address + EXPECT_FALSE(MessageSigner::verifyMessage( + "1HZwkjkeaoZfTSaJxDw6aKkxp45agDiEzN", + "test signature", + sig + )); + // Signature too short + EXPECT_EXCEPTION(MessageSigner::verifyMessage( + "1HZwkjkeaoZfTSaJxDw6aKkxp45agDiEzN", + "test signature", + parse_hex("1fedcbe486d255c7a3a784b65702d70b12c43100160ef29b13c950caad2871dbf23356a812e764ccdfd2fea2c8def9cd38563a575dc15d6485acfaf73a319ae4") + ), "signature too short"); + // Invalid address + EXPECT_EXCEPTION(MessageSigner::verifyMessage( + "__THIS_IS_NOT_A_VALID_ADDRESS__", + "test signature", + parse_hex("1fedcbe486d255c7a3a784b65702d70b12c43100160ef29b13c950caad2871dbf23356a812e764ccdfd2fea2c8def9cd38563a575dc15d6485acfaf73a319ae4") + ), "Input address invalid"); + EXPECT_EXCEPTION(MessageSigner::verifyMessage( + "bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8", + "test signature", + parse_hex("1fedcbe486d255c7a3a784b65702d70b12c43100160ef29b13c950caad2871dbf23356a812e764ccdfd2fea2c8def9cd38563a575dc15d6485acfaf73a319ae4") + ), "Input address invalid"); +} + +TEST(BitcoinMessageSigner, MessageToHash) { + EXPECT_EQ(hex(MessageSigner::messageToHash("Hello, world!")), "02d6c0643e40b0db549cbbd7eb47dcab71a59d7017199ebde6b272f28fbbf95f"); + EXPECT_EQ(hex(MessageSigner::messageToHash("test signature")), "8e81cc5bca9862d8b7f22be1f7cb762b49121cf4e1611c27906a041f9a9eb21f"); +} + +TEST(TWBitcoinMessageSigner, VerifyMessage) { + EXPECT_TRUE(TWBitcoinMessageSignerVerifyMessage( + STRING("1B8Qea79tsxmn4dTiKKRVvsJpHwL2fMQnr").get(), + STRING("test signature").get(), + STRING("H+3L5IbSVcejp4S2VwLXCxLEMQAWDvKbE8lQyq0ocdvyM1aoEudkzN/S/qLI3vnNOFY6V13BXWSFrPr3OjGa5Dk=").get() + )); +} + +TEST(TWBitcoinMessageSigner, SignAndVerify) { + const auto privKeyData = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get())); + const auto address = STRING("19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X"); + const auto message = STRING("test signature"); + + const auto signature = WRAPS(TWBitcoinMessageSignerSignMessage(privateKey.get(), address.get(), message.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(signature.get())), "ILH5K7JQLaRGaKGXXH5mYM6FIIy9IWyY4JUPI+PHYY4WaupxUbg+zy0bhBCrDuehy9x4WidwjkRR1GSLnWvOXBo="); + + EXPECT_TRUE(TWBitcoinMessageSignerVerifyMessage(address.get(), message.get(), signature.get())); +} + +} // namespace TW::Bitcoin diff --git a/tests/Bitcoin/SegwitAddressTests.cpp b/tests/chains/Bitcoin/SegwitAddressTests.cpp similarity index 71% rename from tests/Bitcoin/SegwitAddressTests.cpp rename to tests/chains/Bitcoin/SegwitAddressTests.cpp index 256f5b59981..3180eef8337 100644 --- a/tests/Bitcoin/SegwitAddressTests.cpp +++ b/tests/chains/Bitcoin/SegwitAddressTests.cpp @@ -6,6 +6,7 @@ #include "Bech32.h" #include "Bitcoin/SegwitAddress.h" +#include "HDWallet.h" #include "HexCoding.h" #include @@ -14,7 +15,7 @@ #include using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin::tests { static const std::string valid_checksum[] = { "A12UEL5L", @@ -54,6 +55,9 @@ static const std::vector valid_address = { //{"BC1SW50QA3JX3S", "6002751e"}, //{"bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", "5210751e76e8199196d454941c45d1b3a323"}, {"tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"}, + {"bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8", "00140cb9f5c6b62c03249367bc20a90dd2425e6926af"}, + {"bc1qm9jzmujvdqjj6y28hptk859zs3yyv78hpqqjfj", "0014d9642df24c68252d1147b85763d0a284484678f7"}, + {"bc1ptmsk7c2yut2xah4pgflpygh2s7fh0cpfkrza9cjj29awapv53mrslgd5cf", "51205ee16f6144e2d46edea1427e1222ea879377e029b0c5d2e252517aee85948ec7"}, // Taproot /// test vectors from BIP350 {"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y", "5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"}, @@ -125,7 +129,7 @@ bool case_insensitive_equal(const std::string& s1, const std::string& s2) { } TEST(SegwitAddress, ValidChecksum) { - for (auto i = 0; i < sizeof(valid_checksum) / sizeof(valid_checksum[0]); ++i) { + for (auto i = 0ul; i < sizeof(valid_checksum) / sizeof(valid_checksum[0]); ++i) { auto dec = Bech32::decode(valid_checksum[i]); ASSERT_FALSE(std::get<0>(dec).empty()); @@ -137,7 +141,7 @@ TEST(SegwitAddress, ValidChecksum) { } TEST(SegwitAddress, InvalidChecksum) { - for (auto i = 0; i < sizeof(invalid_checksum) / sizeof(invalid_checksum[0]); ++i) { + for (auto i = 0ul; i < sizeof(invalid_checksum) / sizeof(invalid_checksum[0]); ++i) { auto dec = Bech32::decode(invalid_checksum[i]); EXPECT_TRUE(std::get<0>(dec).empty() && std::get<1>(dec).empty()); } @@ -148,7 +152,7 @@ TEST(SegwitAddress, ValidAddress) { auto dec = SegwitAddress::decode(td.address); EXPECT_TRUE(std::get<2>(dec)) << "Valid address could not be decoded " << td.address; EXPECT_TRUE(std::get<0>(dec).witnessProgram.size() > 0) << "Empty decoded address data for " << td.address; - EXPECT_EQ(std::get<1>(dec).length(), 2); // hrp + EXPECT_EQ(std::get<1>(dec).length(), 2ul); // hrp // recode std::string recode = std::get<0>(dec).string(); @@ -161,14 +165,14 @@ TEST(SegwitAddress, ValidAddress) { } TEST(SegwitAddress, InvalidAddress) { - for (auto i = 0; i < invalid_address.size(); ++i) { + for (auto i = 0ul; i < invalid_address.size(); ++i) { auto dec = SegwitAddress::decode(invalid_address[i]); EXPECT_FALSE(std::get<2>(dec)) << "Invalid address reported as valid: " << invalid_address[i]; } } TEST(SegwitAddress, InvalidAddressEncoding) { - for (auto i = 0; i < sizeof(invalid_address_enc) / sizeof(invalid_address_enc[0]); ++i) { + for (auto i = 0ul; i < sizeof(invalid_address_enc) / sizeof(invalid_address_enc[0]); ++i) { auto address = SegwitAddress(invalid_address_enc[i].hrp, invalid_address_enc[i].version, Data(invalid_address_enc[i].program_length, 0)); std::string code = address.string(); EXPECT_TRUE(code.empty()); @@ -192,3 +196,58 @@ TEST(SegwitAddress, fromRaw) { EXPECT_FALSE(addr.second); } } + +TEST(SegwitAddress, Equals) { + const auto dec1 = SegwitAddress::decode("bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8"); + EXPECT_TRUE(std::get<2>(dec1)); + const auto addr1 = std::get<0>(dec1); + const auto dec2 = SegwitAddress::decode("bc1qm9jzmujvdqjj6y28hptk859zs3yyv78hpqqjfj"); + EXPECT_TRUE(std::get<2>(dec2)); + const auto addr2 = std::get<0>(dec2); + + ASSERT_TRUE(addr1 == addr1); + ASSERT_FALSE(addr1 == addr2); +} + +TEST(SegwitAddress, TestnetAddress) { + const auto mnemonic1 = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; + const auto passphrase = ""; + const auto coin = TWCoinTypeBitcoin; + HDWallet wallet = HDWallet(mnemonic1, passphrase); + + // default + { + const auto privKey = wallet.getKey(coin, TWDerivationDefault); + const auto pubKey = privKey.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(pubKey.bytes), "02df9ef2a7a5552765178b181e1e1afdefc7849985c7dfe9647706dd4fa40df6ac"); + EXPECT_EQ(SegwitAddress(pubKey, "bc").string(), "bc1qpsp72plnsqe6e2dvtsetxtww2cz36ztmfxghpd"); + } + + // testnet: different derivation path and hrp + { + const auto privKey = wallet.getKey(coin, TW::DerivationPath("m/84'/1'/0'/0/0")); + const auto pubKey = privKey.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(pubKey.bytes), "03eb1a535b59f03894b99319f850c784cf4164f4de07620695c5cf0dc5c1ab2a54"); + EXPECT_EQ(SegwitAddress::createTestnetFromPublicKey(pubKey).string(), "tb1qq8p994ak933c39d2jaj8n4sg598tnkhnyk5sg5"); + // alternative with custom hrp + EXPECT_EQ(SegwitAddress(pubKey, "tb").string(), "tb1qq8p994ak933c39d2jaj8n4sg598tnkhnyk5sg5"); + } + + EXPECT_TRUE(SegwitAddress::isValid("tb1qq8p994ak933c39d2jaj8n4sg598tnkhnyk5sg5")); +} + +TEST(SegwitAddress, SegwitDerivationHDWallet) { + const auto mnemonic1 = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; + const auto passphrase = ""; + const auto coin = TWCoinTypeBitcoin; + HDWallet wallet = HDWallet(mnemonic1, passphrase); + + // addresses with different derivations + EXPECT_EQ(wallet.deriveAddress(coin), "bc1qpsp72plnsqe6e2dvtsetxtww2cz36ztmfxghpd"); + EXPECT_EQ(wallet.deriveAddress(coin, TWDerivationDefault), "bc1qpsp72plnsqe6e2dvtsetxtww2cz36ztmfxghpd"); + EXPECT_EQ(wallet.deriveAddress(coin, TWDerivationBitcoinSegwit), "bc1qpsp72plnsqe6e2dvtsetxtww2cz36ztmfxghpd"); + EXPECT_EQ(wallet.deriveAddress(coin, TWDerivationBitcoinLegacy), "1GVb4mfQrvymPLz7zeZ3LnQ8sFv3NedZXe"); + EXPECT_EQ(wallet.deriveAddress(coin, TWDerivationBitcoinTestnet), "tb1qq8p994ak933c39d2jaj8n4sg598tnkhnyk5sg5"); +} + +} // namespace TW::Bitcoin::tests diff --git a/tests/Bitcoin/TWBitcoinAddressTests.cpp b/tests/chains/Bitcoin/TWBitcoinAddressTests.cpp similarity index 98% rename from tests/Bitcoin/TWBitcoinAddressTests.cpp rename to tests/chains/Bitcoin/TWBitcoinAddressTests.cpp index c2e81b6d33a..13c96dc5a5c 100644 --- a/tests/Bitcoin/TWBitcoinAddressTests.cpp +++ b/tests/chains/Bitcoin/TWBitcoinAddressTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Bitcoin/TWBitcoinScriptTests.cpp b/tests/chains/Bitcoin/TWBitcoinScriptTests.cpp similarity index 92% rename from tests/Bitcoin/TWBitcoinScriptTests.cpp rename to tests/chains/Bitcoin/TWBitcoinScriptTests.cpp index 0d8cae2c41a..2f9a3e1ad29 100644 --- a/tests/Bitcoin/TWBitcoinScriptTests.cpp +++ b/tests/chains/Bitcoin/TWBitcoinScriptTests.cpp @@ -4,34 +4,38 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include #include +namespace TW::Bitcoin::TWScriptTests { + +// clang-format off const auto PayToScriptHash = WRAP(TWBitcoinScript, TWBitcoinScriptCreateWithData(DATA("a914" "4733f37cf4db86fbc2efed2500b4f4e49f312023" "87").get())); const auto PayToWitnessScriptHash = WRAP(TWBitcoinScript, TWBitcoinScriptCreateWithData(DATA("0020" "ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db").get())); const auto PayToWitnessPublicKeyHash = WRAP(TWBitcoinScript, TWBitcoinScriptCreateWithData(DATA("0014" "79091972186c449eb1ded22b78e40d009bdf0089").get())); const auto PayToPublicKeySecp256k1 = WRAP(TWBitcoinScript, TWBitcoinScriptCreateWithData(DATA("21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "ac").get())); const auto PayToPublicKeyHash = WRAP(TWBitcoinScript, TWBitcoinScriptCreateWithData(DATA("76a914" "79091972186c449eb1ded22b78e40d009bdf0089" "88ac").get())); +// clang-format on TEST(TWBitcoinScript, Create) { auto data = DATA("a9144733f37cf4db86fbc2efed2500b4f4e49f31202387"); { auto script = WRAP(TWBitcoinScript, TWBitcoinScriptCreateWithData(data.get())); ASSERT_TRUE(script.get() != nullptr); - ASSERT_EQ(TWBitcoinScriptSize(script.get()), 23); + ASSERT_EQ(TWBitcoinScriptSize(script.get()), 23ul); } { auto script = WRAP(TWBitcoinScript, TWBitcoinScriptCreateWithBytes(TWDataBytes(data.get()), TWDataSize(data.get()))); ASSERT_TRUE(script.get() != nullptr); - ASSERT_EQ(TWBitcoinScriptSize(script.get()), 23); + ASSERT_EQ(TWBitcoinScriptSize(script.get()), 23ul); auto scriptCopy = WRAP(TWBitcoinScript, TWBitcoinScriptCreateCopy(script.get())); ASSERT_TRUE(scriptCopy.get() != nullptr); - ASSERT_EQ(TWBitcoinScriptSize(scriptCopy.get()), 23); + ASSERT_EQ(TWBitcoinScriptSize(scriptCopy.get()), 23ul); } } @@ -117,7 +121,9 @@ TEST(TWBitcoinScript, BuildPayToPublicKey) { const auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildPayToPublicKey(pubkey.get())); ASSERT_TRUE(script.get() != nullptr); const auto hex = WRAPS(TWStringCreateWithHexData(WRAPD(TWBitcoinScriptData(script.get())).get())); - ASSERT_STREQ(TWStringUTF8Bytes(hex.get()), "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "ac"); + ASSERT_STREQ(TWStringUTF8Bytes(hex.get()), "21" + "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" + "ac"); } TEST(TWBitcoinScript, BuildPayToWitnessPubkeyHash) { @@ -125,7 +131,8 @@ TEST(TWBitcoinScript, BuildPayToWitnessPubkeyHash) { const auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildPayToWitnessPubkeyHash(hash.get())); ASSERT_TRUE(script.get() != nullptr); const auto hex = WRAPS(TWStringCreateWithHexData(WRAPD(TWBitcoinScriptData(script.get())).get())); - ASSERT_STREQ(TWStringUTF8Bytes(hex.get()), "0014" "79091972186c449eb1ded22b78e40d009bdf0089"); + ASSERT_STREQ(TWStringUTF8Bytes(hex.get()), "0014" + "79091972186c449eb1ded22b78e40d009bdf0089"); } TEST(TWBitcoinScript, BuildPayToWitnessScriptHash) { @@ -133,7 +140,8 @@ TEST(TWBitcoinScript, BuildPayToWitnessScriptHash) { const auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildPayToWitnessScriptHash(hash.get())); ASSERT_TRUE(script.get() != nullptr); const auto hex = WRAPS(TWStringCreateWithHexData(WRAPD(TWBitcoinScriptData(script.get())).get())); - ASSERT_STREQ(TWStringUTF8Bytes(hex.get()), "0020" "ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db"); + ASSERT_STREQ(TWStringUTF8Bytes(hex.get()), "0020" + "ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db"); } TEST(TWBitcoinScript, ScriptHash) { @@ -216,6 +224,7 @@ TEST(TWBitcoinSigHashType, HashTypeForCoin) { EXPECT_EQ(TWBitcoinScriptHashTypeForCoin(TWCoinTypeLitecoin), (uint32_t)TWBitcoinSigHashTypeAll); EXPECT_EQ(TWBitcoinScriptHashTypeForCoin(TWCoinTypeZcash), (uint32_t)TWBitcoinSigHashTypeAll); EXPECT_EQ(TWBitcoinScriptHashTypeForCoin(TWCoinTypeBitcoinCash), (uint32_t)TWBitcoinSigHashTypeAll | (uint32_t)TWBitcoinSigHashTypeFork); + EXPECT_EQ(TWBitcoinScriptHashTypeForCoin(TWCoinTypeECash), (uint32_t)TWBitcoinSigHashTypeAll | (uint32_t)TWBitcoinSigHashTypeFork); EXPECT_EQ(TWBitcoinScriptHashTypeForCoin(TWCoinTypeBitcoinGold), (uint32_t)TWBitcoinSigHashTypeAll | (uint32_t)TWBitcoinSigHashTypeForkBTG); } @@ -232,3 +241,5 @@ TEST(TWBitcoinSigHashType, IsNone) { EXPECT_FALSE(TWBitcoinSigHashTypeIsNone(TWBitcoinSigHashTypeAll)); EXPECT_FALSE(TWBitcoinSigHashTypeIsNone(TWBitcoinSigHashTypeFork)); } + +} // namespace TW::Bitcoin::TWScriptTests diff --git a/tests/Bitcoin/TWBitcoinSigningTests.cpp b/tests/chains/Bitcoin/TWBitcoinSigningTests.cpp similarity index 84% rename from tests/Bitcoin/TWBitcoinSigningTests.cpp rename to tests/chains/Bitcoin/TWBitcoinSigningTests.cpp index c2ac12d4869..f2c26b2691c 100644 --- a/tests/Bitcoin/TWBitcoinSigningTests.cpp +++ b/tests/chains/Bitcoin/TWBitcoinSigningTests.cpp @@ -1,37 +1,34 @@ -// Copyright © 2017-2021 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +#include "Base58.h" #include "Bitcoin/Address.h" #include "Bitcoin/OutPoint.h" #include "Bitcoin/Script.h" +#include "Bitcoin/SegwitAddress.h" +#include "Bitcoin/SigHashType.h" #include "Bitcoin/Transaction.h" #include "Bitcoin/TransactionBuilder.h" #include "Bitcoin/TransactionSigner.h" -#include "Bitcoin/SigHashType.h" -#include "Base58.h" #include "Hash.h" #include "HexCoding.h" #include "PrivateKey.h" -#include "proto/Bitcoin.pb.h" #include "TxComparisonHelper.h" -#include "../interface/TWTestUtilities.h" +#include "proto/Bitcoin.pb.h" -#include #include #include -#include -#include #include -#include #include +#include -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { +// clang-format off SigningInput buildInputP2PKH(bool omitKey = false) { auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); @@ -73,7 +70,8 @@ SigningInput buildInputP2PKH(bool omitKey = false) { input.utxos.push_back(utxo0); UTXO utxo1; - utxo1.script = Script(parse_hex("0014" "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); + utxo1.script = Script(parse_hex("0014" + "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); utxo1.amount = 600'000'000; utxo1.outPoint = OutPoint(hash1, 1, UINT32_MAX); input.utxos.push_back(utxo1); @@ -146,7 +144,7 @@ TEST(BitcoinSigning, EncodeP2WPKH) { Data unsignedData; unsignedTx.encode(unsignedData, Transaction::SegwitFormatMode::Segwit); - ASSERT_EQ(unsignedData.size(), 164); + ASSERT_EQ(unsignedData.size(), 164ul); ASSERT_EQ(hex(unsignedData), "01000000" // version "0001" // marker & flag @@ -201,17 +199,17 @@ TEST(BitcoinSigning, SignP2WPKH_Bip143) { input.utxos.push_back(utxo0); UTXO utxo1; - auto utxo1Script = Script::buildPayToWitnessProgram(utxoPubkeyHash1); + auto utxo1Script = Script::buildPayToV0WitnessProgram(utxoPubkeyHash1); utxo1.script = utxo1Script; utxo1.amount = 600000000; // 0x23C34600 0046c323 utxo1.outPoint = OutPoint(hash1, 1, UINT32_MAX); - input.utxos.push_back(utxo1); + input.utxos.push_back(utxo1); // Set plan to force both UTXOs and exact output amounts TransactionPlan plan; plan.amount = amount; plan.availableAmount = 600000000 + 1000000; - plan.fee = 265210000; // very large, the amounts specified (in1, out0, out1) are not consistent/realistic + plan.fee = 265210000; // very large, the amounts specified (in1, out0, out1) are not consistent/realistic plan.change = 223450000; // 0x0d519390 plan.branchId = {0}; plan.utxos.push_back(utxo0); @@ -274,7 +272,8 @@ SigningInput buildInputP2WPKH(int64_t amount, TWBitcoinSigHashType hashType, int assert(hex(utxoPubkeyHash1) == "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); input.privateKeys.push_back(utxoKey1); - auto scriptPub1 = Script(parse_hex("0014" "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); + auto scriptPub1 = Script(parse_hex("0014" + "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); Data scriptHash; scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); auto scriptHashHex = hex(scriptHash); @@ -290,7 +289,8 @@ SigningInput buildInputP2WPKH(int64_t amount, TWBitcoinSigHashType hashType, int input.utxos.push_back(utxo0); UTXO utxo1; - utxo1.script = Script(parse_hex("0014" "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); + utxo1.script = Script(parse_hex("0014" + "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); utxo1.amount = utxo1Amount; utxo1.outPoint = OutPoint(hash1, 1, UINT32_MAX); input.utxos.push_back(utxo1); @@ -316,7 +316,7 @@ TEST(BitcoinSigning, SignP2WPKH) { Data serialized; signedTx.encode(serialized); EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{195, 192, 193})); - EXPECT_EQ(serialized.size(), 192); + EXPECT_EQ(serialized.size(), 192ul); EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction "01000000" // version @@ -330,11 +330,11 @@ TEST(BitcoinSigning, SignP2WPKH) { { // Non-segwit encoded, for comparison - Data serialized; - signedTx.encode(serialized, Transaction::SegwitFormatMode::NonSegwit); + Data serialized_; + signedTx.encode(serialized_, Transaction::SegwitFormatMode::NonSegwit); EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{195, 192, 193})); - EXPECT_EQ(serialized.size(), 192); - ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + EXPECT_EQ(serialized_.size(), 192ul); + ASSERT_EQ(hex(serialized_), // printed using prettyPrintTransaction "01000000" // version "01" // inputs "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49" "483045022100c327babdd370f0fc5b24cf920736446bf7d9c5660e4a5f7df432386fd652fe280220269c4fc3690c1c248e50c8bf2435c20b4ef00f308b403575f4437f862a91c53a01" "ffffffff" @@ -500,7 +500,7 @@ SigningInput buildInputP2WSH(enum TWBitcoinSigHashType hashType, bool omitScript auto hash0 = parse_hex("0001000000000000000000000000000000000000000000000000000000000000"); utxo0.outPoint = OutPoint(hash0, 0, UINT32_MAX); input.utxos.push_back(utxo0); - + return input; } @@ -624,7 +624,7 @@ TEST(BitcoinSigning, SignP2WSH_HashAnyoneCanPay) { Data serialized; signedTx.encode(serialized); - EXPECT_EQ(serialized.size(), 231); + EXPECT_EQ(serialized.size(), 231ul); EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{231, 119, 147})); EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction @@ -976,7 +976,7 @@ TEST(BitcoinSigning, SignP2SH_P2WSH) { "47" "3044022044e3b59b06931d46f857c82fa1d53d89b116a40a581527eac35c5eb5b7f0785302207d0f8b5d063ffc6749fb4e133db7916162b540c70dee40ec0b21e142d8843b3a00" "cf" "56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae" "00000000" // nLockTime - ; + ; Data serialized; signedTx.encode(serialized); @@ -1106,7 +1106,7 @@ TEST(BitcoinSigning, Plan_10input_MaxAmount) { input.toAddress = "bc1qauwlpmzamwlf9tah6z4w0t8sunh6pnyyjgk0ne"; input.changeAddress = ownAddress; - // Plan. + // Plan. // Estimated size: witness size: 10 * (1 + 1 + 72 + 1 + 33) + 2 = 1082; base 451; raw 451 + 1082 = 1533; vsize 451 + 1082/4 --> 722 // Actual size: witness size: 1078; base 451; raw 451 + 1078 = 1529; vsize 451 + 1078/4 --> 721 auto plan = TransactionBuilder::plan(input); @@ -1128,7 +1128,7 @@ TEST(BitcoinSigning, Plan_10input_MaxAmount) { EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{1529, 451, 721})); EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); - ASSERT_EQ(serialized.size(), 1529); + ASSERT_EQ(serialized.size(), 1529ul); } TEST(BitcoinSigning, Sign_LitecoinReal_a85f) { @@ -1295,7 +1295,7 @@ TEST(BitcoinSigning, Sign_ManyUtxos_400) { input.utxos.push_back(utxo); utxoSum += utxo.amount; } - EXPECT_EQ(utxoSum, 1'202'000); + EXPECT_EQ(utxoSum, 1'202'000ul); input.coinType = TWCoinTypeBitcoin; input.hashType = hashTypeForCoin(TWCoinTypeBitcoin); @@ -1316,8 +1316,8 @@ TEST(BitcoinSigning, Sign_ManyUtxos_400) { subset.push_back(val); subsetSum += val; } - EXPECT_EQ(subset.size(), 66); - EXPECT_EQ(subsetSum, 308'550); + EXPECT_EQ(subset.size(), 66ul); + EXPECT_EQ(subsetSum, 308'550ul); EXPECT_TRUE(verifyPlan(plan, subset, 300'000, 4'561)); // Extend input with keys, reuse plan, Sign @@ -1334,7 +1334,7 @@ TEST(BitcoinSigning, Sign_ManyUtxos_400) { Data serialized; signedTx.encode(serialized); - EXPECT_EQ(serialized.size(), 9871); + EXPECT_EQ(serialized.size(), 9871ul); } TEST(BitcoinSigning, Sign_ManyUtxos_2000) { @@ -1364,7 +1364,7 @@ TEST(BitcoinSigning, Sign_ManyUtxos_2000) { input.utxos.push_back(utxo); utxoSum += utxo.amount; } - EXPECT_EQ(utxoSum, 22'010'000); + EXPECT_EQ(utxoSum, 22'010'000ul); input.coinType = TWCoinTypeBitcoin; input.hashType = hashTypeForCoin(TWCoinTypeBitcoin); @@ -1385,8 +1385,8 @@ TEST(BitcoinSigning, Sign_ManyUtxos_2000) { subset.push_back(val); subsetSum += val; } - EXPECT_EQ(subset.size(), 601); - EXPECT_EQ(subsetSum, 2'410'010); + EXPECT_EQ(subset.size(), 601ul); + EXPECT_EQ(subsetSum, 2'410'010ul); EXPECT_TRUE(verifyPlan(plan, subset, 2'000'000, 40'943)); // Extend input with keys, reuse plan, Sign @@ -1403,7 +1403,7 @@ TEST(BitcoinSigning, Sign_ManyUtxos_2000) { Data serialized; signedTx.encode(serialized); - EXPECT_EQ(serialized.size(), 89'339); + EXPECT_EQ(serialized.size(), 89'339ul); } TEST(BitcoinSigning, EncodeThreeOutput) { @@ -1433,7 +1433,7 @@ TEST(BitcoinSigning, EncodeThreeOutput) { Data unsignedData; unsignedTx.encode(unsignedData, Transaction::SegwitFormatMode::Segwit); - EXPECT_EQ(unsignedData.size(), 147); + EXPECT_EQ(unsignedData.size(), 147ul); EXPECT_EQ(hex(unsignedData), // printed using prettyPrintTransaction "01000000" // version "0001" // marker & flag @@ -1454,7 +1454,7 @@ TEST(BitcoinSigning, EncodeThreeOutput) { auto pubkey = PrivateKey(privkey).getPublicKey(TWPublicKeyTypeSECP256k1); EXPECT_EQ(hex(pubkey.bytes), "036739829f2cfec79cfe6aaf1c22ecb7d4867dfd8ab4deb7121b36a00ab646caed"); - auto utxo0Script = Script::lockScriptForAddress(ownAddress, coin); // buildPayToWitnessProgram() + auto utxo0Script = Script::lockScriptForAddress(ownAddress, coin); // buildPayToV0WitnessProgram() Data keyHashIn0; EXPECT_TRUE(utxo0Script.matchPayToWitnessPublicKeyHash(keyHashIn0)); EXPECT_EQ(hex(keyHashIn0), "5c74be45eb45a3459050667529022d9df8a1ecff"); @@ -1464,19 +1464,19 @@ TEST(BitcoinSigning, EncodeThreeOutput) { auto hashType = TWBitcoinSigHashType::TWBitcoinSigHashTypeAll; Data sighash = unsignedTx.getSignatureHash(redeemScript0, unsignedTx.inputs[0].previousOutput.index, - hashType, utxo0Amount, static_cast(unsignedTx.version)); - auto sig = privkey.signAsDER(sighash, TWCurveSECP256k1); + hashType, utxo0Amount, static_cast(unsignedTx._version)); + auto sig = privkey.signAsDER(sighash); ASSERT_FALSE(sig.empty()); sig.push_back(hashType); EXPECT_EQ(hex(sig), "30450221008d88197a37ffcb51ecacc7e826aa588cb1068a107a82373c4b54ec42318a395c02204abbf5408504614d8f943d67e7873506c575e85a5e1bd92a02cd345e5192a82701"); - + // add witness stack unsignedTx.inputs[0].scriptWitness.push_back(sig); unsignedTx.inputs[0].scriptWitness.push_back(pubkey.bytes); unsignedData.clear(); unsignedTx.encode(unsignedData, Transaction::SegwitFormatMode::Segwit); - EXPECT_EQ(unsignedData.size(), 254); + EXPECT_EQ(unsignedData.size(), 254ul); // https://blockchair.com/litecoin/transaction/9e3fe98565a904d2da5ec1b3ba9d2b3376dfc074f43d113ce1caac01bf51b34c EXPECT_EQ(hex(unsignedData), // printed using prettyPrintTransaction "01000000" // version @@ -1547,5 +1547,214 @@ TEST(BitcoinSigning, RedeemExtendedPubkeyUTXO) { Data encoded; signedTx.encode(encoded); - EXPECT_EQ(encoded.size(), 402); + EXPECT_EQ(encoded.size(), 402ul); +} + +TEST(BitcoinSigning, SignP2TR_5df51e) { + const auto privateKey = "13fcaabaf9e71ffaf915e242ec58a743d55f102cf836968e5bd4881135e0c52c"; + const auto ownAddress = "bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8"; + const auto toAddress = "bc1ptmsk7c2yut2xah4pgflpygh2s7fh0cpfkrza9cjj29awapv53mrslgd5cf"; // Taproot + const auto coin = TWCoinTypeBitcoin; + + // Setup input + SigningInput input; + input.hashType = hashTypeForCoin(coin); + input.amount = 1100; + input.useMaxAmount = false; + input.byteFee = 1; + input.toAddress = toAddress; + input.changeAddress = ownAddress; + input.coinType = coin; + + auto utxoKey0 = PrivateKey(parse_hex(privateKey)); + auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(pubKey0.bytes), "021e582a887bd94d648a9267143eb600449a8d59a0db0653740b1378067a6d0cee"); + EXPECT_EQ(SegwitAddress(pubKey0, "bc").string(), ownAddress); + auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(pubKey0.bytes)); + EXPECT_EQ(hex(utxoPubkeyHash), "0cb9f5c6b62c03249367bc20a90dd2425e6926af"); + input.privateKeys.push_back(utxoKey0); + + auto redeemScript = Script::lockScriptForAddress(input.toAddress, coin); + EXPECT_EQ(hex(redeemScript.bytes), "51205ee16f6144e2d46edea1427e1222ea879377e029b0c5d2e252517aee85948ec7"); + auto scriptHash = Hash::ripemd(Hash::sha256(redeemScript.bytes)); + EXPECT_EQ(hex(scriptHash), "e0a5001e7b394a1a6b2978cdcab272241280bf46"); + input.scripts[hex(scriptHash)] = redeemScript; + + UTXO utxo0; + auto utxo0Script = Script::lockScriptForAddress(ownAddress, coin); + EXPECT_EQ(hex(utxo0Script.bytes), "00140cb9f5c6b62c03249367bc20a90dd2425e6926af"); + utxo0.script = utxo0Script; + utxo0.amount = 49429; + auto hash0 = parse_hex("c24bd72e3eaea797bd5c879480a0db90980297bc7085efda97df2bf7d31413fb"); + std::reverse(hash0.begin(), hash0.end()); + utxo0.outPoint = OutPoint(hash0, 1, UINT32_MAX); + input.utxos.push_back(utxo0); + + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {49429}, 1100, 153)); + } + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{234, 125, 153})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + // https://mempool.space/tx/5df51e13bfeb79f386e1e17237f06d1b5c87c5bfcaa907c0c1cfe51cd7ca446d + EXPECT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "fb1314d3f72bdf97daef8570bc97029890dba08094875cbd97a7ae3e2ed74bc2" "01000000" "00" "" "ffffffff" + "02" // outputs + "4c04000000000000" "22" "51205ee16f6144e2d46edea1427e1222ea879377e029b0c5d2e252517aee85948ec7" + "30bc000000000000" "16" "00140cb9f5c6b62c03249367bc20a90dd2425e6926af" + // witness + "02" + "47" "3044022021cea91157fdab33226e38ee7c1a686538fc323f5e28feb35775cf82ba8c62210220723743b150cea8ead877d8b8d059499779a5df69f9bdc755c9f968c56cfb528f01" + "21" "021e582a887bd94d648a9267143eb600449a8d59a0db0653740b1378067a6d0cee" + "00000000" // nLockTime + ); +} + +TEST(BitcoinSigning, Build_OpReturn_THORChainSwap_eb4c) { + auto coin = TWCoinTypeBitcoin; + auto ownAddress = "bc1q7s0a2l4aguksehx8hf93hs9yggl6njxds6m02g"; + auto toAddress = "bc1qxu5a8gtnjxw3xwdlmr2gl9d76h9fysu3zl656e"; + auto utxoAmount = 342101; + auto toAmount = 300000; + int fee = 36888; + + auto unsignedTx = Transaction(2, 0); + + auto hash0 = parse_hex("30b82960291a39de3664ec4c844a815e3e680e29b4d3a919e450f0c119cf4e35"); + std::reverse(hash0.begin(), hash0.end()); + auto outpoint0 = TW::Bitcoin::OutPoint(hash0, 1); + unsignedTx.inputs.emplace_back(outpoint0, Script(), UINT32_MAX); + + auto lockingScriptTo = Script::lockScriptForAddress(toAddress, coin); + unsignedTx.outputs.push_back(TransactionOutput(toAmount, lockingScriptTo)); + // change + auto lockingScriptChange = Script::lockScriptForAddress(ownAddress, coin); + unsignedTx.outputs.push_back(TransactionOutput(utxoAmount - toAmount - fee, lockingScriptChange)); + // memo OP_RETURN + Data memo = data("SWAP:THOR.RUNE:thor1tpercamkkxec0q0jk6ltdnlqvsw29guap8wmcl:"); + auto lockingScriptOpReturn = Script::buildOpReturnScript(memo); + EXPECT_EQ(hex(lockingScriptOpReturn.bytes), "6a3b535741503a54484f522e52554e453a74686f72317470657263616d6b6b7865633071306a6b366c74646e6c7176737732396775617038776d636c3a"); + unsignedTx.outputs.push_back(TransactionOutput(0, lockingScriptOpReturn)); + + Data unsignedData; + unsignedTx.encode(unsignedData, Transaction::SegwitFormatMode::Segwit); + EXPECT_EQ(unsignedData.size(), 186ul); + EXPECT_EQ(hex(unsignedData), // printed using prettyPrintTransaction + "02000000" // version + "0001" // marker & flag + "01" // inputs + "354ecf19c1f050e419a9d3b4290e683e5e814a844cec6436de391a296029b830" "01000000" "00" "" "ffffffff" + "03" // outputs + "e093040000000000" "16" "00143729d3a173919d1339bfd8d48f95bed5ca924391" + "5d14000000000000" "16" "0014f41fd57ebd472d0cdcc7ba4b1bc0a4423fa9c8cd" + "0000000000000000" "3d" "6a3b535741503a54484f522e52554e453a74686f72317470657263616d6b6b7865633071306a6b366c74646e6c7176737732396775617038776d636c3a" + // witness + "00" + "00000000" // nLockTime + ); + + // add signature + auto pubkey = parse_hex("0206121b83ebfddbb1997b50cb87b968190857269333e21e295142c8b88af9312a"); + auto sig = parse_hex("3045022100876eba8f9324d3fbb00b9dad9a34a8166dd75127d4facda63484c19703e9c178022052495a6229cc465d5f0fcf3cde3b22a0f861e762d0bb10acde26a57598bfe7e701"); + + // add witness stack + unsignedTx.inputs[0].scriptWitness.push_back(sig); + unsignedTx.inputs[0].scriptWitness.push_back(pubkey); + + unsignedData.clear(); + unsignedTx.encode(unsignedData, Transaction::SegwitFormatMode::Segwit); + EXPECT_EQ(unsignedData.size(), 293ul); + // https://blockchair.com/bitcoin/transaction/eb4c1b064bfaf593d7cc6a5c73b75f932ffefe12a0478acf5a7e3145476683fc + EXPECT_EQ(hex(unsignedData), + "02000000000101354ecf19c1f050e419a9d3b4290e683e5e814a844cec6436de391a296029b8300100000000ffffffff03e0930400000000001600143729d3a1" + "73919d1339bfd8d48f95bed5ca9243915d14000000000000160014f41fd57ebd472d0cdcc7ba4b1bc0a4423fa9c8cd00000000000000003d6a3b535741503a54" + "484f522e52554e453a74686f72317470657263616d6b6b7865633071306a6b366c74646e6c7176737732396775617038776d636c3a02483045022100876eba8f" + "9324d3fbb00b9dad9a34a8166dd75127d4facda63484c19703e9c178022052495a6229cc465d5f0fcf3cde3b22a0f861e762d0bb10acde26a57598bfe7e70121" + "0206121b83ebfddbb1997b50cb87b968190857269333e21e295142c8b88af9312a00000000" + ); +} + +TEST(BitcoinSigning, Sign_OpReturn_THORChainSwap) { + PrivateKey privateKey = PrivateKey(parse_hex("6bd4096fa6f08bd3af2b437244ba0ca2d35045c5233b8d6796df37e61e974de5")); + PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + auto ownAddress = SegwitAddress(publicKey, "bc"); + auto ownAddressString = ownAddress.string(); + EXPECT_EQ(ownAddressString, "bc1q2gzg42w98ytatvmsgxfc8vrg6l24c25pydup9u"); + auto toAddress = "bc1qxu5a8gtnjxw3xwdlmr2gl9d76h9fysu3zl656e"; + auto utxoAmount = 342101; + auto toAmount = 300000; + int byteFee = 126; + Data memo = data("SWAP:THOR.RUNE:thor1tpercamkkxec0q0jk6ltdnlqvsw29guap8wmcl:"); + + SigningInput input; + input.coinType = TWCoinTypeBitcoin; + input.hashType = hashTypeForCoin(TWCoinTypeBitcoin); + input.amount = toAmount; + input.byteFee = byteFee; + input.toAddress = toAddress; + input.changeAddress = ownAddressString; + + input.privateKeys.push_back(privateKey); + input.outputOpReturn = memo; + + UTXO utxo; + auto utxoHash = parse_hex("30b82960291a39de3664ec4c844a815e3e680e29b4d3a919e450f0c119cf4e35"); + std::reverse(utxoHash.begin(), utxoHash.end()); + utxo.outPoint = OutPoint(utxoHash, 1, UINT32_MAX); + utxo.amount = utxoAmount; + + auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(publicKey.bytes)); + EXPECT_EQ(hex(utxoPubkeyHash), "52048aa9c53917d5b370419383b068d7d55c2a81"); + auto utxoScript = Script::buildPayToWitnessPublicKeyHash(utxoPubkeyHash); + EXPECT_EQ(hex(utxoScript.bytes), "001452048aa9c53917d5b370419383b068d7d55c2a81"); + utxo.script = utxoScript; + input.utxos.push_back(utxo); + + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {342101}, 300000, 26586)); + EXPECT_EQ(plan.outputOpReturn.size(), 59ul); + } + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{293, 183, 211})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "354ecf19c1f050e419a9d3b4290e683e5e814a844cec6436de391a296029b830" "01000000" "00" "" "ffffffff" + "03" // outputs + "e093040000000000" "16" "00143729d3a173919d1339bfd8d48f95bed5ca924391" + "9b3c000000000000" "16" "001452048aa9c53917d5b370419383b068d7d55c2a81" + "0000000000000000" "3d" "6a3b535741503a54484f522e52554e453a74686f72317470657263616d6b6b7865633071306a6b366c74646e6c7176737732396775617038776d636c3a" + // witness + "02" + "48" "3045022100ff6c0aaef512aa52f3036161bfbcef39046ac89eb9617fa461a0c9c43fe45eb3022055d208d3f81736e72e3ad8ef761dc79ac5dd3dc00721174bc69db416a74960e301" + "21" "02c2e5c8b4927812fb37444a7862466ad23978a4ac626f8eaf93e1d1a60d6abb80" + "00000000" // nLockTime + ); } +// clang-format on +} // namespace TW::Bitcoin diff --git a/tests/Bitcoin/TWBitcoinTransactionTests.cpp b/tests/chains/Bitcoin/TWBitcoinTransactionTests.cpp similarity index 68% rename from tests/Bitcoin/TWBitcoinTransactionTests.cpp rename to tests/chains/Bitcoin/TWBitcoinTransactionTests.cpp index 4086289a438..8df4be05c54 100644 --- a/tests/Bitcoin/TWBitcoinTransactionTests.cpp +++ b/tests/chains/Bitcoin/TWBitcoinTransactionTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -6,14 +6,10 @@ #include "Bitcoin/Transaction.h" #include "HexCoding.h" -#include "../interface/TWTestUtilities.h" - -#include #include -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { TEST(BitcoinTransaction, Encode) { auto transaction = Transaction(2, 0); @@ -35,7 +31,9 @@ TEST(BitcoinTransaction, Encode) { Data unsignedData; transaction.encode(unsignedData, Transaction::SegwitFormatMode::NonSegwit); - ASSERT_EQ(unsignedData.size(), 201); + ASSERT_EQ(unsignedData.size(), 201ul); ASSERT_EQ(hex(unsignedData), - "02000000035897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f0000000000ffffffffbf829c6bcf84579331337659d31f89dfd138f7f7785802d5501c92333145ca7c1200000000ffffffff22a6f904655d53ae2ff70e701a0bbd90aa3975c0f40bfc6cc996a9049e31cdfc0100000000ffffffff0280a81201000000001976a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac0084d717000000001976a914f2d4db28cad6502226ee484ae24505c2885cb12d88ac00000000"); + "02000000035897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f0000000000ffffffffbf829c6bcf84579331337659d31f89dfd138f7f7785802d5501c92333145ca7c1200000000ffffffff22a6f904655d53ae2ff70e701a0bbd90aa3975c0f40bfc6cc996a9049e31cdfc0100000000ffffffff0280a81201000000001976a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac0084d717000000001976a914f2d4db28cad6502226ee484ae24505c2885cb12d88ac00000000"); } + +} // namespace TW::Bitcoin diff --git a/tests/Bitcoin/TWCoinTypeTests.cpp b/tests/chains/Bitcoin/TWCoinTypeTests.cpp similarity index 97% rename from tests/Bitcoin/TWCoinTypeTests.cpp rename to tests/chains/Bitcoin/TWCoinTypeTests.cpp index 0cba7d8cb7e..3ce30dd594b 100644 --- a/tests/Bitcoin/TWCoinTypeTests.cpp +++ b/tests/chains/Bitcoin/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Bitcoin/TWSegwitAddressTests.cpp b/tests/chains/Bitcoin/TWSegwitAddressTests.cpp similarity index 68% rename from tests/Bitcoin/TWSegwitAddressTests.cpp rename to tests/chains/Bitcoin/TWSegwitAddressTests.cpp index cf426d39ff2..65c632ca6b9 100644 --- a/tests/Bitcoin/TWSegwitAddressTests.cpp +++ b/tests/chains/Bitcoin/TWSegwitAddressTests.cpp @@ -4,15 +4,16 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include #include -const char *address1 = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"; -const char *address2 = "bc1qr583w2swedy2acd7rung055k8t3n7udp7vyzyg"; +const char* address1 = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"; +const char* address2 = "bc1qr583w2swedy2acd7rung055k8t3n7udp7vyzyg"; +const char* address3Taproot = "bc1ptmsk7c2yut2xah4pgflpygh2s7fh0cpfkrza9cjj29awapv53mrslgd5cf"; TEST(TWSegwitAddress, PublicKeyToAddress) { auto pkData = DATA("0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798"); @@ -31,6 +32,23 @@ TEST(TWSegwitAddress, InitWithAddress) { ASSERT_TRUE(address.get() != nullptr); ASSERT_STREQ(address1, TWStringUTF8Bytes(description.get())); + + ASSERT_EQ(0, TWSegwitAddressWitnessVersion(address.get())); + + ASSERT_EQ(TWHRPBitcoin, TWSegwitAddressHRP(address.get())); +} + +TEST(TWSegwitAddress, TaprootString) { + const auto string = STRING(address3Taproot); + const auto address = WRAP(TWSegwitAddress, TWSegwitAddressCreateWithString(string.get())); + ASSERT_TRUE(address.get() != nullptr); + + const auto description = WRAPS(TWSegwitAddressDescription(address.get())); + ASSERT_STREQ(address3Taproot, TWStringUTF8Bytes(description.get())); + + ASSERT_EQ(1, TWSegwitAddressWitnessVersion(address.get())); // taproot has segwit version 1 + + ASSERT_EQ(TWHRPBitcoin, TWSegwitAddressHRP(address.get())); } TEST(TWSegwitAddress, InvalidAddress) { diff --git a/tests/Bitcoin/TransactionPlanTests.cpp b/tests/chains/Bitcoin/TransactionPlanTests.cpp similarity index 88% rename from tests/Bitcoin/TransactionPlanTests.cpp rename to tests/chains/Bitcoin/TransactionPlanTests.cpp index c290bf76a58..d906c863056 100644 --- a/tests/Bitcoin/TransactionPlanTests.cpp +++ b/tests/chains/Bitcoin/TransactionPlanTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -10,14 +10,13 @@ #include "Bitcoin/TransactionPlan.h" #include "Bitcoin/TransactionBuilder.h" #include "Bitcoin/FeeCalculator.h" +#include "HexCoding.h" #include "proto/Bitcoin.pb.h" #include #include -using namespace TW; -using namespace TW::Bitcoin; - +namespace TW::Bitcoin { TEST(TransactionPlan, OneTypical) { auto utxos = buildTestUTXOs({100'000}); @@ -120,6 +119,19 @@ TEST(TransactionPlan, OneFitsExactlyHighFee) { EXPECT_EQ(feeCalculator.calculate(1, 2, byteFee), 1740); } +TEST(TransactionPlan, OneMissingPrivateKey) { + auto utxos = buildTestUTXOs({100'000}); + auto byteFee = 1; + auto sigingInput = buildSigningInput(50'000, byteFee, utxos, false, TWCoinTypeBitcoin, true); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 50'000, 147)); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculate(1, 2, byteFee), 174); +} + TEST(TransactionPlan, TwoFirstEnough) { auto utxos = buildTestUTXOs({20'000, 80'000}); auto sigingInput = buildSigningInput(15'000, 1, utxos); @@ -182,7 +194,7 @@ TEST(TransactionPlan, ThreeNoDust) { } TEST(TransactionPlan, TenThree) { - auto utxos = buildTestUTXOs({1'000, 2'000, 100'000, 3'000, 4'000, 5,000, 125'000, 6'000, 150'000, 7'000}); + auto utxos = buildTestUTXOs({1'000, 2'000, 100'000, 3'000, 4'000, 5, 000, 125'000, 6'000, 150'000, 7'000}); auto sigingInput = buildSigningInput(300'000, 1, utxos); auto txPlan = TransactionBuilder::plan(sigingInput); @@ -249,7 +261,7 @@ TEST(TransactionPlan, Inputs5_33Req19NoDustFee2) { // UTXOs smaller than singleInputFee are not included auto txPlan = TransactionBuilder::plan(sigingInput); - auto expectedFee = 283*byteFee; + auto expectedFee = 283 * byteFee; EXPECT_TRUE(verifyPlan(txPlan, {6'000, 8'000, 10'000}, 19'000, expectedFee)); auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); @@ -264,7 +276,7 @@ TEST(TransactionPlan, Inputs5_33Req19Dust1Fee5) { // UTXOs smaller than singleInputFee are not included auto txPlan = TransactionBuilder::plan(sigingInput); - auto expectedFee = 283*byteFee; + auto expectedFee = 283 * byteFee; EXPECT_TRUE(verifyPlan(txPlan, {6'000, 8'000, 10'000}, 19'000, expectedFee)); auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); @@ -279,7 +291,7 @@ TEST(TransactionPlan, Inputs5_33Req19Dust1Fee9) { // UTXOs smaller than singleInputFee are not included auto txPlan = TransactionBuilder::plan(sigingInput); - auto expectedFee = 283*byteFee; + auto expectedFee = 283 * byteFee; EXPECT_TRUE(verifyPlan(txPlan, {6'000, 8'000, 10'000}, 19'000, expectedFee)); auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); @@ -305,7 +317,7 @@ TEST(TransactionPlan, Inputs5_33Req13Fee20) { // UTXOs smaller than singleInputFee are not included auto txPlan = TransactionBuilder::plan(sigingInput); - auto expectedFee = 283*byteFee; + auto expectedFee = 283 * byteFee; EXPECT_TRUE(verifyPlan(txPlan, {6'000, 8'000, 10'000}, 13'000, expectedFee)); auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); @@ -518,7 +530,7 @@ TEST(TransactionPlan, ManyUtxosNonmax_400) { valueSum += val; } const uint64_t requestedAmount = valueSum / 8; - EXPECT_EQ(requestedAmount, 1'002'500); + EXPECT_EQ(requestedAmount, 1'002'500ul); auto utxos = buildTestUTXOs(values); auto sigingInput = buildSigningInput(requestedAmount, byteFee, utxos, false, TWCoinTypeBitcoin); @@ -533,8 +545,8 @@ TEST(TransactionPlan, ManyUtxosNonmax_400) { subset.push_back(val); subsetSum += val; } - EXPECT_EQ(subset.size(), 27); - EXPECT_EQ(subsetSum, 1'044'900); + EXPECT_EQ(subset.size(), 27ul); + EXPECT_EQ(subsetSum, 1'044'900ul); EXPECT_TRUE(verifyPlan(txPlan, subset, requestedAmount, 19'150)); } @@ -549,7 +561,7 @@ TEST(TransactionPlan, ManyUtxosNonmax_5000_simple) { valueSum += val; } const uint64_t requestedAmount = valueSum / 20; - EXPECT_EQ(requestedAmount, 62'512'500); + EXPECT_EQ(requestedAmount, 62'512'500ul); // Use Ravencoin, because of faster non-segwit estimation, and one of the original issues was with this coin. auto utxos = buildTestUTXOs(values); @@ -565,8 +577,8 @@ TEST(TransactionPlan, ManyUtxosNonmax_5000_simple) { subset.push_back(val); subsetSum += val; } - EXPECT_EQ(subset.size(), 1220); - EXPECT_EQ(subsetSum, 76'189'000); + EXPECT_EQ(subset.size(), 1220ul); + EXPECT_EQ(subsetSum, 76'189'000ul); EXPECT_TRUE(verifyPlan(txPlan, subset, requestedAmount, 1'806'380)); } @@ -598,10 +610,10 @@ TEST(TransactionPlan, ManyUtxosMax_400) { filteredValueSum += val; } } - EXPECT_EQ(valueSum, 8'020'000); - EXPECT_EQ(dustLimit, 1480); - EXPECT_EQ(filteredValues.size(), 386); - EXPECT_EQ(filteredValueSum, 80'09'500); + EXPECT_EQ(valueSum, 8'020'000ul); + EXPECT_EQ(dustLimit, 1480ul); + EXPECT_EQ(filteredValues.size(), 386ul); + EXPECT_EQ(filteredValueSum, 80'09'500ul); EXPECT_TRUE(verifyPlan(txPlan, filteredValues, 7'437'780, 571'720)); } @@ -633,9 +645,46 @@ TEST(TransactionPlan, ManyUtxosMax_5000_simple) { filteredValueSum += val; } } - EXPECT_EQ(valueSum, 1'250'250'000); - EXPECT_EQ(dustLimit, 1500); - EXPECT_EQ(filteredValues.size(), 3000); - EXPECT_EQ(filteredValueSum, 454'350'000); + EXPECT_EQ(valueSum, 1'250'250'000ul); + EXPECT_EQ(dustLimit, 1500ul); + EXPECT_EQ(filteredValues.size(), 3000ul); + EXPECT_EQ(filteredValueSum, 454'350'000ul); EXPECT_TRUE(verifyPlan(txPlan, filteredValues, 449'909'560, 4'440'440)); } + +TEST(TransactionPlan, OpReturn) { + auto ownAddress = "bc1q7s0a2l4aguksehx8hf93hs9yggl6njxds6m02g"; + auto toAddress = "bc1qxu5a8gtnjxw3xwdlmr2gl9d76h9fysu3zl656e"; + auto utxoAmount = 342101; + auto toAmount = 300000; + int byteFee = 126; + Data memo = data("SWAP:THOR.RUNE:thor1tpercamkkxec0q0jk6ltdnlqvsw29guap8wmcl:"); + + auto signingInput = Proto::SigningInput(); + signingInput.set_hash_type(TWBitcoinSigHashTypeAll); + signingInput.set_amount(toAmount); + signingInput.set_byte_fee(byteFee); + signingInput.set_to_address(toAddress); + signingInput.set_change_address(ownAddress); + signingInput.set_output_op_return(memo.data(), memo.size()); + + auto& utxo = *signingInput.add_utxo(); + auto utxoHash = parse_hex("30b82960291a39de3664ec4c844a815e3e680e29b4d3a919e450f0c119cf4e35"); + std::reverse(utxoHash.begin(), utxoHash.end()); + utxo.mutable_out_point()->set_hash(utxoHash.data(), utxoHash.size()); + utxo.mutable_out_point()->set_index(1); + utxo.mutable_out_point()->set_sequence(UINT32_MAX); + utxo.set_amount(utxoAmount); + + auto txPlan = TransactionBuilder::plan(signingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {342101}, 300000, 205 * byteFee)); + EXPECT_EQ(txPlan.outputOpReturn.size(), 59ul); + EXPECT_EQ(hex(txPlan.outputOpReturn), "535741503a54484f522e52554e453a74686f72317470657263616d6b6b7865633071306a6b366c74646e6c7176737732396775617038776d636c3a"); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculate(1, 2, byteFee), 174 * byteFee); + EXPECT_EQ(feeCalculator.calculate(1, 3, byteFee), 205 * byteFee); +} + +} // namespace TW::Bitcoin diff --git a/tests/Bitcoin/TxComparisonHelper.cpp b/tests/chains/Bitcoin/TxComparisonHelper.cpp similarity index 84% rename from tests/Bitcoin/TxComparisonHelper.cpp rename to tests/chains/Bitcoin/TxComparisonHelper.cpp index bc2d55d4506..f99fae78c82 100644 --- a/tests/Bitcoin/TxComparisonHelper.cpp +++ b/tests/chains/Bitcoin/TxComparisonHelper.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -16,12 +16,10 @@ #include "HexCoding.h" #include "BinaryCoding.h" -#include #include #include -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { auto emptyTxOutPoint = OutPoint(parse_hex("1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"), 0); @@ -30,30 +28,33 @@ UTXO buildTestUTXO(int64_t amount) { utxo.amount = amount; utxo.outPoint = emptyTxOutPoint; utxo.outPoint.sequence = UINT32_MAX; - utxo.script = Script(parse_hex("0014" "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); + utxo.script = Script(parse_hex("0014" + "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); return utxo; } UTXOs buildTestUTXOs(const std::vector& amounts) { UTXOs utxos; - for (auto it = amounts.begin(); it != amounts.end(); it++) { - utxos.push_back(buildTestUTXO(*it)); + for (long long amount : amounts) { + utxos.push_back(buildTestUTXO(amount)); } return utxos; } -SigningInput buildSigningInput(Amount amount, int byteFee, const UTXOs& utxos, bool useMaxAmount, enum TWCoinType coin) { +SigningInput buildSigningInput(Amount amount, int byteFee, const UTXOs& utxos, bool useMaxAmount, enum TWCoinType coin, bool omitPrivateKey) { SigningInput input; input.amount = amount; input.byteFee = byteFee; input.useMaxAmount = useMaxAmount; input.coinType = coin; - - auto utxoKey = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); - auto pubKey = utxoKey.getPublicKey(TWPublicKeyTypeSECP256k1); - auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(pubKey.bytes)); - assert(hex(utxoPubkeyHash) == "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - input.privateKeys.push_back(utxoKey); + + if (!omitPrivateKey) { + auto utxoKey = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); + auto pubKey = utxoKey.getPublicKey(TWPublicKeyTypeSECP256k1); + auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(pubKey.bytes)); + assert(hex(utxoPubkeyHash) == "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + input.privateKeys.push_back(utxoKey); + } input.utxos = utxos; input.toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"; @@ -63,7 +64,7 @@ SigningInput buildSigningInput(Amount amount, int byteFee, const UTXOs& utxos, b int64_t sumUTXOs(const UTXOs& utxos) { int64_t s = 0u; - for (auto& utxo: utxos) { + for (auto& utxo : utxos) { s += utxo.amount; } return s; @@ -76,7 +77,7 @@ bool verifySelectedUTXOs(const UTXOs& selected, const std::vector& expe std::cerr << "Wrong number of selected UTXOs, " << selected.size() << " vs. " << expectedAmounts.size() << std::endl; } int errorCount = 0; - for (auto i = 0; i < selected.size() && i < expectedAmounts.size(); ++i) { + for (auto i = 0ul; i < selected.size() && i < expectedAmounts.size(); ++i) { if (expectedAmounts[i] != selected[i].amount) { ret = false; ++errorCount; @@ -102,8 +103,8 @@ bool verifyPlan(const TransactionPlan& plan, const std::vector& utxoAmo std::cerr << "Mismatch in fee, act " << plan.fee << ", exp " << fee << std::endl; } int64_t sumExpectedUTXOs = 0; - for (auto i = 0; i < utxoAmounts.size(); ++i) { - sumExpectedUTXOs += utxoAmounts[i]; + for (long long utxoAmount : utxoAmounts) { + sumExpectedUTXOs += utxoAmount; } if (plan.availableAmount != sumExpectedUTXOs) { ret = false; @@ -138,7 +139,7 @@ bool operator==(const EncodedTxSize& s1, const EncodedTxSize& s2) { } EncodedTxSize getEncodedTxSize(const Transaction& tx) { - EncodedTxSize size; + EncodedTxSize size{}; { // full segwit size Data data; tx.encode(data, Transaction::SegwitFormatMode::Segwit); @@ -154,13 +155,13 @@ EncodedTxSize getEncodedTxSize(const Transaction& tx) { Data data; tx.encodeWitness(data); witnessSize = data.size(); - assert(size.segwit - size.nonSegwit == 2 + witnessSize); + assert(size.segwit - size.nonSegwit == 2ul + witnessSize); } // compute virtual size: 3/4 of (smaller) non-segwit + 1/4 of segwit size - uint64_t sum = size.nonSegwit * 3 + size.segwit; + uint64_t sum = size.nonSegwit * 3 + size.segwit; size.virtualBytes = sum / 4 + (sum % 4 != 0); // alternative computation: (smaller) non-segwit + 1/4 of the diff (witness-only) - uint64_t vSize2 = size.nonSegwit + (witnessSize + 2)/ 4 + ((witnessSize + 2) % 4 != 0); + uint64_t vSize2 = size.nonSegwit + (witnessSize + 2) / 4 + ((witnessSize + 2) % 4 != 0); assert(size.virtualBytes == vSize2); return size; } @@ -194,7 +195,7 @@ void prettyPrintScript(const Script& script) { void prettyPrintTransaction(const Transaction& tx, bool useWitnessFormat) { Data data; - encode32LE(tx.version, data); + encode32LE(tx._version, data); std::cout << " \"" << hex(data) << "\" // version\n"; if (useWitnessFormat) { @@ -205,7 +206,7 @@ void prettyPrintTransaction(const Transaction& tx, bool useWitnessFormat) { data.clear(); encodeVarInt(tx.inputs.size(), data); std::cout << " \"" << hex(data) << "\" // inputs\n"; - for (auto& input: tx.inputs) { + for (auto& input : tx.inputs) { auto& outpoint = reinterpret_cast(input.previousOutput); std::cout << " \"" << hex(outpoint.hash) << "\""; data.clear(); @@ -221,7 +222,7 @@ void prettyPrintTransaction(const Transaction& tx, bool useWitnessFormat) { data.clear(); encodeVarInt(tx.outputs.size(), data); std::cout << " \"" << hex(data) << "\" // outputs\n"; - for (auto& output: tx.outputs) { + for (auto& output : tx.outputs) { data.clear(); encode64LE(output.value, data); std::cout << " \"" << hex(data) << "\""; @@ -231,11 +232,11 @@ void prettyPrintTransaction(const Transaction& tx, bool useWitnessFormat) { if (useWitnessFormat) { std::cout << " // witness\n"; - for (auto& input: tx.inputs) { + for (auto& input : tx.inputs) { data.clear(); encodeVarInt(input.scriptWitness.size(), data); std::cout << " \"" << hex(data) << "\"\n"; - for (auto& item: input.scriptWitness) { + for (auto& item : input.scriptWitness) { data.clear(); encodeVarInt(item.size(), data); std::cout << " \"" << hex(data) << "\""; @@ -249,3 +250,5 @@ void prettyPrintTransaction(const Transaction& tx, bool useWitnessFormat) { std::cout << " \"" << hex(data) << "\" // nLockTime\n"; std::cout << "\n"; } + +} // namespace TW::Bitcoin diff --git a/tests/Bitcoin/TxComparisonHelper.h b/tests/chains/Bitcoin/TxComparisonHelper.h similarity index 91% rename from tests/Bitcoin/TxComparisonHelper.h rename to tests/chains/Bitcoin/TxComparisonHelper.h index 984a818cc9b..4cb2f92a1d2 100644 --- a/tests/Bitcoin/TxComparisonHelper.h +++ b/tests/chains/Bitcoin/TxComparisonHelper.h @@ -16,9 +16,7 @@ #include #include -using namespace TW; -using namespace TW::Bitcoin; - +namespace TW::Bitcoin { /// Build a dummy UTXO with the given amount UTXO buildTestUTXO(int64_t amount); @@ -26,8 +24,8 @@ UTXO buildTestUTXO(int64_t amount); /// Build a set of dummy UTXO with the given amounts UTXOs buildTestUTXOs(const std::vector& amounts); -SigningInput buildSigningInput(Amount amount, int byteFee, const UTXOs& utxos, - bool useMaxAmount = false, enum TWCoinType coin = TWCoinTypeBitcoin); +SigningInput buildSigningInput(Amount amount, int byteFee, const UTXOs& utxos, + bool useMaxAmount = false, enum TWCoinType coin = TWCoinTypeBitcoin, bool omitPrivateKey = false); /// Compare a set of selected UTXOs to the expected set of amounts. /// Returns false on mismatch, and error is printed (stderr). @@ -56,3 +54,5 @@ bool validateEstimatedSize(const Transaction& tx, int smallerTolerance = -1, int /// Print out a transaction in a nice format, as structured hex strings. void prettyPrintTransaction(const Transaction& tx, bool useWitnessFormat = true); + +} // namespace TW::Bitcoin diff --git a/tests/BitcoinCash/TWBitcoinCashTests.cpp b/tests/chains/BitcoinCash/TWBitcoinCashTests.cpp similarity index 83% rename from tests/BitcoinCash/TWBitcoinCashTests.cpp rename to tests/chains/BitcoinCash/TWBitcoinCashTests.cpp index 30536e06cea..19f6fc564d4 100644 --- a/tests/BitcoinCash/TWBitcoinCashTests.cpp +++ b/tests/chains/BitcoinCash/TWBitcoinCashTests.cpp @@ -8,7 +8,7 @@ #include "Bitcoin/SigHashType.h" #include "HexCoding.h" #include "proto/Bitcoin.pb.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include @@ -23,21 +23,43 @@ #include using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin::tests { + +// clang-format off TEST(BitcoinCash, Address) { EXPECT_TRUE(TWAnyAddressIsValid(STRING("pqx578nanz2h2estzmkr53zqdg6qt8xyqvwhn6qeyc").get(), TWCoinTypeBitcoinCash)); + EXPECT_TRUE(TWAnyAddressIsValid(STRING("bitcoincash:pqx578nanz2h2estzmkr53zqdg6qt8xyqvwhn6qeyc").get(), TWCoinTypeBitcoinCash)); } TEST(BitcoinCash, ValidAddress) { auto string = STRING("bitcoincash:qqa2qx0d8tegw32xk8u75ws055en4x3h2u0e6k46y4"); auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeBitcoinCash)); ASSERT_NE(address.get(), nullptr); - + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(string.get(), TWCoinTypeBitcoinCash)); ASSERT_FALSE(TWBitcoinScriptSize(script.get()) == 0); } +TEST(BitcoinCash, InvalidAddress) { + // Wrong checksum + EXPECT_FALSE(TWAnyAddressIsValid(STRING("pqx578nanz2h2estzmkr53zqdg6qt8xyqvffffffff").get(), TWCoinTypeBitcoinCash)); + EXPECT_FALSE(TWAnyAddressIsValid(STRING("bitcoincash:pqx578nanz2h2estzmkr53zqdg6qt8xyqvffffffff").get(), TWCoinTypeBitcoinCash)); + + // Valid eCash addresses are invalid for BCH + EXPECT_TRUE(TWAnyAddressIsValid(STRING("pqx578nanz2h2estzmkr53zqdg6qt8xyqvh683mrz0").get(), TWCoinTypeECash)); + EXPECT_FALSE(TWAnyAddressIsValid(STRING("pqx578nanz2h2estzmkr53zqdg6qt8xyqvh683mrz0").get(), TWCoinTypeBitcoinCash)); + + EXPECT_TRUE(TWAnyAddressIsValid(STRING("ecash:pqx578nanz2h2estzmkr53zqdg6qt8xyqvh683mrz0").get(), TWCoinTypeECash)); + EXPECT_FALSE(TWAnyAddressIsValid(STRING("ecash:pqx578nanz2h2estzmkr53zqdg6qt8xyqvh683mrz0").get(), TWCoinTypeBitcoinCash)); + + // Wrong prefix + EXPECT_FALSE(TWAnyAddressIsValid(STRING("bcash:pqx578nanz2h2estzmkr53zqdg6qt8xyqvwhn6qeyc").get(), TWCoinTypeBitcoinCash)); + + // Wrong base 32 (characters o, i) + EXPECT_FALSE(TWAnyAddressIsValid(STRING("poi578nanz2h2estzmkr53zqdg6qt8xyqvwhn6qeyc").get(), TWCoinTypeBitcoinCash)); +} + TEST(BitcoinCash, LegacyToCashAddr) { auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("28071bf4e2b0340db41b807ed8a5514139e5d6427ff9d58dbd22b7ed187103a4").get())); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); @@ -131,7 +153,7 @@ TEST(BitcoinCash, SignTransaction) { EXPECT_EQ(output.transaction().outputs_size(), 2); EXPECT_EQ(output.transaction().outputs(0).value(), amount); EXPECT_EQ(output.transaction().outputs(1).value(), 4325); - EXPECT_EQ(output.encoded().length(), 226); + EXPECT_EQ(output.encoded().length(), 226ul); ASSERT_EQ(hex(output.encoded()), "01000000" "01" @@ -141,3 +163,6 @@ TEST(BitcoinCash, SignTransaction) { "e510000000000000" "1976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" "00000000"); } +// clang-format on + +} // namespace TW::Bitcoin::tests diff --git a/tests/BitcoinCash/TWCoinTypeTests.cpp b/tests/chains/BitcoinCash/TWCoinTypeTests.cpp similarity index 97% rename from tests/BitcoinCash/TWCoinTypeTests.cpp rename to tests/chains/BitcoinCash/TWCoinTypeTests.cpp index 9b56e10a7af..666aa70328c 100644 --- a/tests/BitcoinCash/TWCoinTypeTests.cpp +++ b/tests/chains/BitcoinCash/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/BitcoinGold/TWAddressTests.cpp b/tests/chains/BitcoinGold/TWAddressTests.cpp similarity index 96% rename from tests/BitcoinGold/TWAddressTests.cpp rename to tests/chains/BitcoinGold/TWAddressTests.cpp index 77c192461bb..977c77900e1 100644 --- a/tests/BitcoinGold/TWAddressTests.cpp +++ b/tests/chains/BitcoinGold/TWAddressTests.cpp @@ -5,7 +5,7 @@ // file LICENSE at the root of the source code distribution tree. // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include "Bitcoin/Address.h" #include "PrivateKey.h" #include "PublicKey.h" diff --git a/tests/BitcoinGold/TWBitcoinGoldTests.cpp b/tests/chains/BitcoinGold/TWBitcoinGoldTests.cpp similarity index 97% rename from tests/BitcoinGold/TWBitcoinGoldTests.cpp rename to tests/chains/BitcoinGold/TWBitcoinGoldTests.cpp index 1621692b37b..760cd79b9a6 100644 --- a/tests/BitcoinGold/TWBitcoinGoldTests.cpp +++ b/tests/chains/BitcoinGold/TWBitcoinGoldTests.cpp @@ -6,25 +6,27 @@ #include #include -#include #include +#include #include #include -#include "Bitcoin/SegwitAddress.h" -#include "proto/Bitcoin.pb.h" #include "Bitcoin/OutPoint.h" #include "Bitcoin/Script.h" +#include "Bitcoin/SegwitAddress.h" +#include "Bitcoin/SigHashType.h" #include "Bitcoin/Transaction.h" #include "Bitcoin/TransactionBuilder.h" #include "Bitcoin/TransactionSigner.h" -#include "Bitcoin/SigHashType.h" #include "HexCoding.h" -#include "../interface/TWTestUtilities.h" +#include "proto/Bitcoin.pb.h" +#include "TestUtilities.h" using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin::tests { + +// clang-format off TEST(TWBitcoinGoldScript, LockScriptTest) { auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("btg1q6572ulr0kmywle8a30lvagm9xsg9k9n5cmzfdj").get(), TWCoinTypeBitcoinGold)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); @@ -79,7 +81,7 @@ TEST(TWBitcoinGoldTxGeneration, TxGeneration) { auto utxoKey0 = parse_hex("cbe13a79b82ec7f8871b336a64fd8d531f598e7c9022e29c67e824cfd54af57f"); input.add_private_key(utxoKey0.data(), utxoKey0.size()); input.set_lock_time(0x00098971); - + auto scriptPub1 = Script(parse_hex("0014db746a75d9aae8995d135b1e19a04d7765242a8f")); auto scriptHash = std::vector(); @@ -94,7 +96,7 @@ TEST(TWBitcoinGoldTxGeneration, TxGeneration) { auto utxo0Script = parse_hex("0014d53cae7c6fb6c8efe4fd8bfecea36534105b1674"); utxo0->set_script(utxo0Script.data(), utxo0Script.size()); utxo0->set_amount(10000); - + auto hash0 = parse_hex("5727794fa2b94aa22a226e206130524201ede9b50e032526e713c848493a890f"); utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); utxo0->mutable_out_point()->set_index(0); @@ -122,4 +124,6 @@ TEST(TWBitcoinGoldTxGeneration, TxGeneration) { "71890900" // nLockTime ); } - +// clang-format on + +} // namespace TW::Bitcoin::tests diff --git a/tests/BitcoinGold/TWCoinTypeTests.cpp b/tests/chains/BitcoinGold/TWCoinTypeTests.cpp similarity index 97% rename from tests/BitcoinGold/TWCoinTypeTests.cpp rename to tests/chains/BitcoinGold/TWCoinTypeTests.cpp index 55b11cdfa52..5ab3db7a03b 100644 --- a/tests/BitcoinGold/TWCoinTypeTests.cpp +++ b/tests/chains/BitcoinGold/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/BitcoinGold/TWSegwitAddressTests.cpp b/tests/chains/BitcoinGold/TWSegwitAddressTests.cpp similarity index 83% rename from tests/BitcoinGold/TWSegwitAddressTests.cpp rename to tests/chains/BitcoinGold/TWSegwitAddressTests.cpp index c624b0a90de..0f8c1413fe1 100644 --- a/tests/BitcoinGold/TWSegwitAddressTests.cpp +++ b/tests/chains/BitcoinGold/TWSegwitAddressTests.cpp @@ -5,8 +5,9 @@ // file LICENSE at the root of the source code distribution tree. // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include "Bitcoin/SegwitAddress.h" +#include "Coin.h" #include "PrivateKey.h" #include "PublicKey.h" #include "HexCoding.h" @@ -29,12 +30,18 @@ TEST(TWBitcoinGoldSegwitAddress, WitnessProgramToAddress) { ASSERT_EQ(address.string(), "btg1qtesn92ddy8m5yvypgsdtft3zj5qldj9g2u52sk"); } +/// Get address data from a Bech32 address +TEST(TWBitcoinGoldSegwitAddress, addressToData) { + auto data = TW::addressToData(TWCoinTypeBitcoinGold, "btg1qtesn92ddy8m5yvypgsdtft3zj5qldj9g2u52sk"); + ASSERT_EQ(hex(data), "5e6132a9ad21f7423081441ab4ae229501f6c8a8"); +} + /// Initializes a Bech32 address with a public key and a HRP prefix. TEST(TWBitcoinGoldSegwitAddress, PubkeyToAddress) { const auto publicKey = PublicKey(parse_hex("02f74712b5d765a73b52a14c1e113f2ef3f9502d09d5987ee40f53828cfe68b9a6"), TWPublicKeyTypeSECP256k1); /// construct with public key - auto address = Bitcoin::SegwitAddress(publicKey, 0, "btg"); + auto address = Bitcoin::SegwitAddress(publicKey, "btg"); ASSERT_TRUE(Bitcoin::SegwitAddress::isValid(address.string())); ASSERT_EQ(address.string(), "btg1qtesn92ddy8m5yvypgsdtft3zj5qldj9g2u52sk"); diff --git a/tests/BitcoinGold/TWSignerTests.cpp b/tests/chains/BitcoinGold/TWSignerTests.cpp similarity index 92% rename from tests/BitcoinGold/TWSignerTests.cpp rename to tests/chains/BitcoinGold/TWSignerTests.cpp index 9dc2d2b44d7..673e6437912 100644 --- a/tests/BitcoinGold/TWSignerTests.cpp +++ b/tests/chains/BitcoinGold/TWSignerTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,20 +9,18 @@ #include #include -#include "Bitcoin/SegwitAddress.h" -#include "proto/Bitcoin.pb.h" #include "Bitcoin/OutPoint.h" #include "Bitcoin/Script.h" +#include "Bitcoin/SigHashType.h" #include "Bitcoin/Transaction.h" #include "Bitcoin/TransactionBuilder.h" #include "Bitcoin/TransactionSigner.h" -#include "Bitcoin/SigHashType.h" #include "HexCoding.h" -#include "../interface/TWTestUtilities.h" +#include "proto/Bitcoin.pb.h" #include "../Bitcoin/TxComparisonHelper.h" +#include "TestUtilities.h" -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { TEST(TWBitcoinGoldSigner, SignTransaction) { const int64_t amount = 10000; @@ -39,7 +37,6 @@ TEST(TWBitcoinGoldSigner, SignTransaction) { auto utxoKey0 = parse_hex("cbe13a79b82ec7f8871b336a64fd8d531f598e7c9022e29c67e824cfd54af57f"); input.add_private_key(utxoKey0.data(), utxoKey0.size()); - auto scriptPub1 = Script(parse_hex("0014db746a75d9aae8995d135b1e19a04d7765242a8f")); auto scriptHash = std::vector(); scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); @@ -53,18 +50,17 @@ TEST(TWBitcoinGoldSigner, SignTransaction) { auto utxo0Script = parse_hex("0014d53cae7c6fb6c8efe4fd8bfecea36534105b1674"); utxo0->set_script(utxo0Script.data(), utxo0Script.size()); utxo0->set_amount(99000); - + auto hash0 = parse_hex("1d4653041a1915b3a52d47aeaa119c8f79ed7634a93692a6e811173067464f03"); utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); utxo0->mutable_out_point()->set_index(1); utxo0->mutable_out_point()->set_sequence(0xfffffffd); input.set_lock_time(0x00098971); - Proto::TransactionPlan plan; { // try plan first - ANY_PLAN(input, plan, TWCoinTypeGroestlcoin); + ANY_PLAN(input, plan, TWCoinTypeBitcoinGold); EXPECT_TRUE(verifyPlan(plan, {99'000}, amount, 141)); } @@ -83,7 +79,8 @@ TEST(TWBitcoinGoldSigner, SignTransaction) { Data serialized; signedTx.encode(serialized); // BitcoinGold Mainnet: https://btg2.trezor.io/tx/db26faec66d070045df0da56140349beb5a12bd14bca12b162fded8f84d18afa - EXPECT_EQ(serialized.size(), 222); + EXPECT_EQ(serialized.size(), 222ul); + // clang-format off ASSERT_EQ(hex(serialized), "01000000" "0001" @@ -96,6 +93,8 @@ TEST(TWBitcoinGoldSigner, SignTransaction) { "4730440220325c56363b17e1b1329efeb400c0933a3d9adfb304f29889b3ef01084aef19e302202a69d9be9ef668b5a5517fbfa42e1fc26b3f8b582c721bd1eabd721322bc2b6c41" "2103e00b5dec8078d526fba090247bd92db6b67a4dd1953b788cea9b52de9471b8cf" "71890900" - ); + ); + // clang-format on } - + +} // namespace TW::Bitcoin diff --git a/tests/Bluzelle/TWCoinTypeTests.cpp b/tests/chains/Bluzelle/TWCoinTypeTests.cpp similarity index 97% rename from tests/Bluzelle/TWCoinTypeTests.cpp rename to tests/chains/Bluzelle/TWCoinTypeTests.cpp index 33acfa33f7b..09dcb1aed7b 100644 --- a/tests/Bluzelle/TWCoinTypeTests.cpp +++ b/tests/chains/Bluzelle/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/chains/Boba/TWCoinTypeTests.cpp b/tests/chains/Boba/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..9d9d27f52cf --- /dev/null +++ b/tests/chains/Boba/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// Copyright © 2017-2021 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(TWBobaCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeBoba)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x31533707c3feb3b10f7deeea387ff8893f229253e65ca6b14d2400bf95b5d103")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeBoba, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x4F96F50eDB37a19216d87693E5dB241e31bD3735")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeBoba, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeBoba)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeBoba)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeBoba), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeBoba)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeBoba)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeBoba)); + assertStringsEqual(symbol, "BOBAETH"); + assertStringsEqual(txUrl, "https://blockexplorer.boba.network/tx/0x31533707c3feb3b10f7deeea387ff8893f229253e65ca6b14d2400bf95b5d103"); + assertStringsEqual(accUrl, "https://blockexplorer.boba.network/address/0x4F96F50eDB37a19216d87693E5dB241e31bD3735"); + assertStringsEqual(id, "boba"); + assertStringsEqual(name, "Boba"); +} diff --git a/tests/Callisto/TWCoinTypeTests.cpp b/tests/chains/Callisto/TWCoinTypeTests.cpp similarity index 87% rename from tests/Callisto/TWCoinTypeTests.cpp rename to tests/chains/Callisto/TWCoinTypeTests.cpp index e08e4a46cc9..e742718974f 100644 --- a/tests/Callisto/TWCoinTypeTests.cpp +++ b/tests/chains/Callisto/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -27,8 +27,8 @@ TEST(TWCallistoCoinType, TWCoinType) { ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeCallisto)); ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeCallisto)); assertStringsEqual(symbol, "CLO"); - assertStringsEqual(txUrl, "https://explorer2.callisto.network/tx/t123"); - assertStringsEqual(accUrl, "https://explorer2.callisto.network/addr/a12"); + assertStringsEqual(txUrl, "https://explorer.callisto.network/tx/t123"); + assertStringsEqual(accUrl, "https://explorer.callisto.network/addr/a12"); assertStringsEqual(id, "callisto"); assertStringsEqual(name, "Callisto"); } diff --git a/tests/chains/Cardano/AddressTests.cpp b/tests/chains/Cardano/AddressTests.cpp new file mode 100644 index 00000000000..8a8918fe6c9 --- /dev/null +++ b/tests/chains/Cardano/AddressTests.cpp @@ -0,0 +1,510 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Cardano/AddressV3.h" + +#include "Coin.h" +#include "HDWallet.h" +#include "HexCoding.h" +#include "PrivateKey.h" + +#include + +using namespace TW; +using namespace std; + +namespace TW::Cardano::tests { + +const auto dummyKey = parse_hex("1111111111111111111111111111111111111111111111111111111111111111"); + +TEST(CardanoAddress, V3NetworkIdKind) { + EXPECT_EQ(AddressV3::firstByte(AddressV3::Network_Test, AddressV3::Kind_Base), 0); + EXPECT_EQ(AddressV3::firstByte(AddressV3::Network_Production, AddressV3::Kind_Base), 1); + EXPECT_EQ(AddressV3::firstByte(AddressV3::NetworkId(2), AddressV3::Kind(3)), 50); + + EXPECT_EQ(AddressV3::networkIdFromFirstByte(0), AddressV3::Network_Test); + EXPECT_EQ(AddressV3::networkIdFromFirstByte(1), AddressV3::Network_Production); + EXPECT_EQ(AddressV3::networkIdFromFirstByte(50), AddressV3::NetworkId(2)); + + EXPECT_EQ(AddressV3::kindFromFirstByte(0), AddressV3::Kind_Base); + EXPECT_EQ(AddressV3::kindFromFirstByte(1), AddressV3::Kind_Base); + EXPECT_EQ(AddressV3::kindFromFirstByte(50), AddressV3::Kind(3)); +} + +TEST(CardanoAddress, Validation) { + // valid V3 address + ASSERT_TRUE(AddressV3::isValidLegacy("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23")); + + ASSERT_TRUE(AddressV3::isValid("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23")); + ASSERT_TRUE(AddressV3::isValid("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5")); + ASSERT_TRUE(AddressV3::isValid("addr1vyuca7esanpgs4ke0um3ft6f4yaeuz3ftpfqx9nxpct2uyqu7dvlp")); // enterprise + ASSERT_TRUE(AddressV3::isValid("stake1uy9ggsc9qls4pu9qvyyacwnmr9tt0gzcdt5s0zj4au8qkqc65geks")); // reward + ASSERT_TRUE(AddressV3::isValid("addr1sxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qmxapsy")); // kind 8 + + // valid V2 address + ASSERT_TRUE(AddressV3::isValidLegacy("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvx")); + ASSERT_TRUE(AddressV3::isValidLegacy("Ae2tdPwUPEZ6RUCnjGHFqi59k5WZLiv3HoCCNGCW8SYc5H9srdTzn1bec4W")); + + ASSERT_FALSE(AddressV3::isValid("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvx")); + + // valid V1 address + ASSERT_TRUE(AddressV3::isValidLegacy("DdzFFzCqrhssmYoG5Eca1bKZFdGS8d6iag1mU4wbLeYcSPVvBNF2wRG8yhjzQqErbg63N6KJA4DHqha113tjKDpGEwS5x1dT2KfLSbSJ")); + ASSERT_TRUE(AddressV3::isValidLegacy("DdzFFzCqrht7HGoJ87gznLktJGywK1LbAJT2sbd4txmgS7FcYLMQFhawb18ojS9Hx55mrbsHPr7PTraKh14TSQbGBPJHbDZ9QVh6Z6Di")); + + // invalid V3, invalid network + ASSERT_FALSE(AddressV3::isValidLegacy("addr1sna05l45z33zpkm8z44q8f0h57wxvm0c86e34wlmua7gtcrdgrdrzy8ny3walyfjanhe33nsyuh088qr5gepqaen6jsa9r94xvvd7fh6jc3e6x")); + // invalid V3, invalid prefix + ASSERT_FALSE(AddressV3::isValidLegacy("prefix1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35q3hm7lv")); + // invalid V3, length + ASSERT_FALSE(AddressV3::isValidLegacy("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32xsmpqws7")); + // invalid checksum V3 + ASSERT_FALSE(AddressV3::isValidLegacy("PREFIX1qvqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0jqxuzx4s")); + // invalid checksum V2 + ASSERT_FALSE(AddressV3::isValidLegacy("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvm")); + // random + ASSERT_FALSE(AddressV3::isValidLegacy("hasoiusaodiuhsaijnnsajnsaiussai")); + // empty + ASSERT_FALSE(AddressV3::isValidLegacy("")); + ASSERT_FALSE(AddressV3::isValidLegacy("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk2")); +} + +TEST(CardanoAddress, FromStringV2) { + { + auto address = AddressV3("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + EXPECT_EQ(address.kind, AddressV3::Kind_Base); + EXPECT_EQ(hex(address.data()), "01" "8d98bea0414243dc84070f96265577e7e6cf702d62e871016885034e" "cc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468"); + } + { + auto address = AddressV3("addr1qxteqxsgxrs4he9d28lh70qu7qfz7saj6dmxwsqyle2yp3xvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35quehtx3"); + EXPECT_EQ(address.kind, AddressV3::Kind_Base); + EXPECT_EQ(hex(address.data()), "01" "97901a0830e15be4ad51ff7f3c1cf0122f43b2d376674004fe5440c4" "cc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468"); + } + { + auto address = AddressV3("addr1q8sfzcwce0fqll3symd7f0amayxqq68nxt2u8pgen9y00tkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35q40ytea"); + EXPECT_EQ(address.kind, AddressV3::Kind_Base); + EXPECT_EQ(hex(address.data()), "01" "e09161d8cbd20ffe3026dbe4bfbbe90c0068f332d5c385199948f7ae" "cc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468"); + } + { + auto address = AddressV3("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvx"); + ASSERT_EQ(address.string(), "Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvx"); + } + { + auto address = AddressV3("DdzFFzCqrhssmYoG5Eca1bKZFdGS8d6iag1mU4wbLeYcSPVvBNF2wRG8yhjzQqErbg63N6KJA4DHqha113tjKDpGEwS5x1dT2KfLSbSJ"); + ASSERT_EQ(address.string(), "DdzFFzCqrhssmYoG5Eca1bKZFdGS8d6iag1mU4wbLeYcSPVvBNF2wRG8yhjzQqErbg63N6KJA4DHqha113tjKDpGEwS5x1dT2KfLSbSJ"); + } +} + +TEST(CardanoAddress, FromStringV3_Base) { + { + auto address = AddressV3("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + EXPECT_EQ(address.string(), "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + EXPECT_EQ(address.string("addr"), "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + EXPECT_EQ(AddressV3::Network_Production, address.networkId); + EXPECT_EQ(AddressV3::Kind_Base, address.kind); + EXPECT_EQ("8d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468", hex(address.bytes)); + } +} + +TEST(CardanoAddress, FromStringV3_Enterprise) { + { + auto address = AddressV3("addr1vyuca7esanpgs4ke0um3ft6f4yaeuz3ftpfqx9nxpct2uyqu7dvlp"); + EXPECT_EQ(address.string(), "addr1vyuca7esanpgs4ke0um3ft6f4yaeuz3ftpfqx9nxpct2uyqu7dvlp"); + EXPECT_EQ(AddressV3::Network_Production, address.networkId); + EXPECT_EQ(AddressV3::Kind_Enterprise, address.kind); + EXPECT_EQ("398efb30ecc28856d97f3714af49a93b9e0a2958520316660e16ae10", hex(address.bytes)); + } +} + +TEST(CardanoAddress, FromStringV3_Reward) { + { + auto address = AddressV3("stake1uy9ggsc9qls4pu9qvyyacwnmr9tt0gzcdt5s0zj4au8qkqc65geks"); + EXPECT_EQ(address.string(), "stake1uy9ggsc9qls4pu9qvyyacwnmr9tt0gzcdt5s0zj4au8qkqc65geks"); + EXPECT_EQ(AddressV3::Network_Production, address.networkId); + EXPECT_EQ(AddressV3::Kind_Reward, address.kind); + EXPECT_EQ("0a84430507e150f0a06109dc3a7b1956b7a0586ae9078a55ef0e0b03", hex(address.bytes)); + } +} + +TEST(CardanoAddress, MnemonicToAddressV3) { + { + // Test from cardano-crypto.js; Test wallet + const auto mnemonic = "cost dash dress stove morning robust group affair stomach vacant route volume yellow salute laugh"; + const auto coin = TWCoinTypeCardano; + const auto derivPath = derivationPath(coin); + + const auto wallet = HDWallet(mnemonic, ""); + + // check entropy + EXPECT_EQ("30a6f50aeb58ff7699b822d63e0ef27aeff17d9f", hex(wallet.getEntropy())); + + { + PrivateKey masterPrivKey = wallet.getMasterKey(TWCurve::TWCurveED25519ExtendedCardano); + PrivateKey masterPrivKeyExt = wallet.getMasterKeyExtension(TWCurve::TWCurveED25519ExtendedCardano); + // the two together matches first half of keypair + ASSERT_EQ("a018cd746e128a0be0782b228c275473205445c33b9000a33dd5668b430b5744", hex(masterPrivKey.bytes)); + ASSERT_EQ("26877cfe435fddda02409b839b7386f3738f10a30b95a225f4b720ee71d2505b", hex(masterPrivKeyExt.bytes)); + + PublicKey masterPublicKey = masterPrivKey.getPublicKey(TWPublicKeyTypeED25519); + ASSERT_EQ("3aecb95953edd0b16db20366097ddedcb3512fe36193473c5fca2af774d44739", hex(masterPublicKey.bytes)); + } + { + string addr = wallet.deriveAddress(TWCoinType::TWCoinTypeCardano); + EXPECT_EQ("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq", addr); + } + { + const auto privateKey = wallet.getKey(coin, derivPath); + EXPECT_EQ(hex(privateKey.bytes), "e8c8c5b2df13f3abed4e6b1609c808e08ff959d7e6fc3d849e3f2880550b574437aa559095324d78459b9bb2da069da32337e1cc5da78f48e1bd084670107f3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26fae0d152bb611cb9ff34e945e4ff627e6fba81da687a601a879759cd76530b5744424db69a75edd4780a5fbc05d1a3c84ac4166ff8e424808481dd8e77627ce5f5bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276"); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + + const auto address = AddressV3(publicKey); + EXPECT_EQ(address.string(), "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + EXPECT_EQ(address.networkId, AddressV3::Network_Production); + EXPECT_EQ(address.kind, AddressV3::Kind_Base); + EXPECT_EQ(hex(address.bytes), "8d98bea0414243dc84070f96265577e7e6cf702d62e871016885034e" "cc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468"); + } + { + PrivateKey privateKey = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/1852'/1815'/0'/0/0")); + EXPECT_EQ("e8c8c5b2df13f3abed4e6b1609c808e08ff959d7e6fc3d849e3f2880550b5744", hex(privateKey.key())); + EXPECT_EQ("37aa559095324d78459b9bb2da069da32337e1cc5da78f48e1bd084670107f31", hex(privateKey.extension())); + EXPECT_EQ("10f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26fa", hex(privateKey.chainCode())); + EXPECT_EQ("e0d152bb611cb9ff34e945e4ff627e6fba81da687a601a879759cd76530b5744", hex(privateKey.secondKey())); + EXPECT_EQ("424db69a75edd4780a5fbc05d1a3c84ac4166ff8e424808481dd8e77627ce5f5", hex(privateKey.secondExtension())); + EXPECT_EQ("bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276", hex(privateKey.secondChainCode())); + PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + EXPECT_EQ("fafa7eb4146220db67156a03a5f7a79c666df83eb31abbfbe77c85e06d40da3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26faf4b8d5201961e68f2e177ba594101f513ee70fe70a41324e8ea8eb787ffda6f4bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276", hex(publicKey.bytes)); + string addr = AddressV3(publicKey).string(); + EXPECT_EQ("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq", addr); + } + { + PrivateKey privKey0 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/44'/1815'/0'/0/0")); + PublicKey pubKey0 = privKey0.getPublicKey(TWPublicKeyTypeED25519Cardano); + auto addr0 = AddressV2(pubKey0); + EXPECT_EQ("Ae2tdPwUPEZ6RUCnjGHFqi59k5WZLiv3HoCCNGCW8SYc5H9srdTzn1bec4W", addr0.string()); + } + { + PrivateKey privKey1 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/44'/1815'/0'/0/1")); + PublicKey pubKey1 = privKey1.getPublicKey(TWPublicKeyTypeED25519Cardano); + auto addr1 = AddressV2(pubKey1); + EXPECT_EQ("Ae2tdPwUPEZ7dnds6ZyhQdmgkrDFFPSDh8jG9RAhswcXt1bRauNw5jczjpV", addr1.string()); + } + { + PrivateKey privKey1 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/44'/1815'/0'/0/2")); + PublicKey pubKey1 = privKey1.getPublicKey(TWPublicKeyTypeED25519Cardano); + auto addr1 = AddressV2(pubKey1); + EXPECT_EQ("Ae2tdPwUPEZ8LAVy21zj4BF97iWxKCmPv12W6a18zLX3V7rZDFFVgqUBkKw", addr1.string()); + } + { + PrivateKey privKey0 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/1852'/1815'/0'/0/0")); + PublicKey pubKey0 = privKey0.getPublicKey(TWPublicKeyTypeED25519Cardano); + auto addr0 = AddressV3(pubKey0); + EXPECT_EQ("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq", addr0.string()); + } + { + PrivateKey privKey1 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/1852'/1815'/0'/0/1")); + PublicKey pubKey1 = privKey1.getPublicKey(TWPublicKeyTypeED25519Cardano); + auto addr1 = AddressV3(pubKey1); + EXPECT_EQ("addr1q9068st87h22h3l6w6t5evnlm067rag94llqya2hkjrsd3wvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qpmxzjt", addr1.string()); + } + { + PrivateKey privKey1 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/1852'/1815'/0'/0/2")); + PublicKey pubKey1 = privKey1.getPublicKey(TWPublicKeyTypeED25519Cardano); + auto addr1 = AddressV3(pubKey1); + EXPECT_EQ("addr1qxteqxsgxrs4he9d28lh70qu7qfz7saj6dmxwsqyle2yp3xvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35quehtx3", addr1.string()); + } + } + { + auto mnemonicPlay1 = "youth away raise north opinion slice dash bus soldier dizzy bitter increase saddle live champion"; + auto wallet = HDWallet(mnemonicPlay1, ""); + string addr = wallet.deriveAddress(TWCoinType::TWCoinTypeCardano); + EXPECT_EQ("addr1q83nm9ntq3eaz8dya49txxtle6nn8geq4gmyylrzhzs7v0qjdwm6zuahwwds6c7mj8t6a09rup6m2cnh6zvzddnafp2slmcu95", addr); + } + { + auto mnemonicPlay2 = "return custom two home gain guilt kangaroo supply market current curtain tomorrow heavy blue robot"; + auto wallet = HDWallet(mnemonicPlay2, ""); + string addr = wallet.deriveAddress(TWCoinType::TWCoinTypeCardano); + EXPECT_EQ("addr1qywxuqm7dx0yvqnn2yllye9urz5f2e4fgwanluzh008r22e53hart525dxgjcl0xzm0kes4n5tan8f5pz7ej0tkzgyrqtfmlal", addr); + } + { + auto mnemonicALDemo = "civil void tool perfect avocado sweet immense fluid arrow aerobic boil flash"; + auto wallet = HDWallet(mnemonicALDemo, ""); + string addr = wallet.deriveAddress(TWCoinType::TWCoinTypeCardano); + EXPECT_EQ("addr1q94zzrtl32tjp8j96auatnhxd2y35fnk6wuxqvqm9364vp9spdkjdsmyfhvfagjzh4uzp9zs6p5djw89jac2g0ujs2eqsuy7pu", addr); + } + { + // V2 Tested against AdaLite + auto mnemonicPlay1 = "youth away raise north opinion slice dash bus soldier dizzy bitter increase saddle live champion"; + auto wallet = HDWallet(mnemonicPlay1, ""); + PrivateKey privateKey = wallet.getKey(TWCoinTypeCardano, DerivationPath(TWPurposeBIP44, TWCoinTypeCardano, DerivationPathIndex(0, true).derivationIndex(), 0, 0)); + PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + string addr = AddressV2(publicKey).string(); + EXPECT_EQ("Ae2tdPwUPEZJYT9g1JgQWtLveUHavyRxQGi6hVzoQjct7yyCLGgk3pCyx7h", addr); + } + { + // V2 Tested against AdaLite + auto mnemonicPlay2 = "return custom two home gain guilt kangaroo supply market current curtain tomorrow heavy blue robot"; + auto wallet = HDWallet(mnemonicPlay2, ""); + PrivateKey privateKey = wallet.getKey(TWCoinTypeCardano, DerivationPath(TWPurposeBIP44, TWCoinTypeCardano, DerivationPathIndex(0, true).derivationIndex(), 0, 0)); + PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + string addr = AddressV2(publicKey).string(); + EXPECT_EQ("Ae2tdPwUPEZLtJx7LA2XZ3zzwonH9x9ieX3dMzaTBD3TfXuKczjMSjTecr1", addr); + } + { + // V2 AdaLite Demo phrase, 12-word. AdaLite uses V1 for it, in V2 it produces different addresses. + // In AdaLite V1 addr0 is DdzFFzCqrht7HGoJ87gznLktJGywK1LbAJT2sbd4txmgS7FcYLMQFhawb18ojS9Hx55mrbsHPr7PTraKh14TSQbGBPJHbDZ9QVh6Z6Di + auto mnemonicALDemo = "civil void tool perfect avocado sweet immense fluid arrow aerobic boil flash"; + auto wallet = HDWallet(mnemonicALDemo, ""); + PrivateKey privateKey = wallet.getKey(TWCoinTypeCardano, DerivationPath(TWPurposeBIP44, TWCoinTypeCardano, DerivationPathIndex(0, true).derivationIndex(), 0, 0)); + PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + string addr = AddressV2(publicKey).string(); + EXPECT_EQ("Ae2tdPwUPEZJbLcD8iLgN7hVGvq66WdR4zocntRekSP97Ds3MvCfmEDjJYu", addr); + } +} + +TEST(CardanoAddress, KeyHashV2) { + auto xpub = parse_hex("e6f04522f875c1563682ca876ddb04c2e2e3ae718e3ff9f11c03dd9f9dccf69869272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000"); + auto hash = AddressV2::keyHash(xpub); + ASSERT_EQ("a1eda96a9952a56c983d9f49117f935af325e8a6c9d38496e945faa8", hex(hash)); +} + +TEST(CardanoAddress, FromDataV3_Base) { + auto address0 = AddressV3("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + EXPECT_EQ(address0.string(), "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + EXPECT_EQ(hex(address0.data()), "018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468"); + { + auto address = AddressV3(parse_hex("018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468")); + EXPECT_EQ(address.string(), "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + EXPECT_EQ(address.kind, AddressV3::Kind_Base); + EXPECT_EQ(address.networkId, AddressV3::Network_Production); + EXPECT_EQ(hex(address.bytes), "8d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468"); + } + { + auto address = AddressV3::createBase(AddressV3::Network_Production, + PublicKey(parse_hex("fafa7eb4146220db67156a03a5f7a79c666df83eb31abbfbe77c85e06d40da31"), TWPublicKeyTypeED25519), + PublicKey(parse_hex("f4b8d5201961e68f2e177ba594101f513ee70fe70a41324e8ea8eb787ffda6f4"), TWPublicKeyTypeED25519)); + EXPECT_EQ(address.string(), "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + } +} + +TEST(CardanoAddress, FromDataV3_Enterprise) { + auto address = AddressV3(parse_hex("61398efb30ecc28856d97f3714af49a93b9e0a2958520316660e16ae10")); + EXPECT_EQ(address.string(), "addr1vyuca7esanpgs4ke0um3ft6f4yaeuz3ftpfqx9nxpct2uyqu7dvlp"); + EXPECT_EQ(address.kind, AddressV3::Kind_Enterprise); + EXPECT_EQ(address.networkId, AddressV3::Network_Production); + EXPECT_EQ(hex(address.bytes), "398efb30ecc28856d97f3714af49a93b9e0a2958520316660e16ae10"); +} + +TEST(CardanoAddress, FromDataV3_Reward) { + auto address = AddressV3(parse_hex("e10a84430507e150f0a06109dc3a7b1956b7a0586ae9078a55ef0e0b03")); + EXPECT_EQ(address.string(), "stake1uy9ggsc9qls4pu9qvyyacwnmr9tt0gzcdt5s0zj4au8qkqc65geks"); + EXPECT_EQ(address.kind, AddressV3::Kind_Reward); + EXPECT_EQ(address.networkId, AddressV3::Network_Production); + EXPECT_EQ(hex(address.bytes), "0a84430507e150f0a06109dc3a7b1956b7a0586ae9078a55ef0e0b03"); +} + +TEST(CardanoAddress, FromDataV3_Invalid) { + { // base, invalid length + auto address = AddressV3(parse_hex("018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a34")); + EXPECT_EQ(address.string(), "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32xsmpqws7"); + EXPECT_EQ(address.kind, AddressV3::Kind_Base); + EXPECT_EQ(address.networkId, AddressV3::Network_Production); + EXPECT_EQ(hex(address.bytes), "8d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a34"); + } + { // kind = 8 + auto address = AddressV3(parse_hex("818d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468")); + EXPECT_EQ(address.string(), "addr1sxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qmxapsy"); + EXPECT_EQ(address.kind, static_cast(8)); + EXPECT_EQ(address.networkId, AddressV3::Network_Production); + EXPECT_EQ(hex(address.bytes), "8d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468"); + } +} + +TEST(CardanoAddress, FromPublicKeyV2) { + { + // caradano-crypto.js test + auto publicKey = PublicKey(parse_hex( + "e6f04522f875c1563682ca876ddb04c2e2e3ae718e3ff9f11c03dd9f9dccf69869272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000" + "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" // dummy second + ), TWPublicKeyTypeED25519Cardano); + auto address = AddressV2(publicKey); + ASSERT_EQ(address.string(), "Ae2tdPwUPEZCxt4UV1Uj2AMMRvg5pYPypqZowVptz3GYpK4pkcvn3EjkuNH"); + } + { + // Adalite test account addr0 + auto publicKey = PublicKey(parse_hex( + "57fd54be7b38bb8952782c2f59aa276928a4dcbb66c8c62ce44f9d623ecd5a03bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4" + "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" // dummy second + ), TWPublicKeyTypeED25519Cardano); + auto address = AddressV2(publicKey); + ASSERT_EQ(address.string(), "Ae2tdPwUPEZ6RUCnjGHFqi59k5WZLiv3HoCCNGCW8SYc5H9srdTzn1bec4W"); + } + { + // Adalite test account addr1 + auto publicKey = PublicKey(parse_hex( + "25af99056d600f7956312406bdd1cd791975bb1ae91c9d034fc65f326195fcdb247ee97ec351c0820dd12de4ca500232f73a35fe6f86778745bcd57f34d1048d" + "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" // dummy second + ), TWPublicKeyTypeED25519Cardano); + auto address = AddressV2(publicKey); + ASSERT_EQ(address.string(), "Ae2tdPwUPEZ7dnds6ZyhQdmgkrDFFPSDh8jG9RAhswcXt1bRauNw5jczjpV"); + } + { + // Play1 addr0 + auto publicKey = PublicKey(parse_hex( + "7cee0f30b9d536a786547dd77b35679b6830e945ffde768eb4f2a061b9dba016e513fa1290da1d22e83a41f17eed72d4489483b561fff36b9555ffdb91c430e2" + "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" // dummy second + ), TWPublicKeyTypeED25519Cardano); + auto address = AddressV2(publicKey); + ASSERT_EQ(address.string(), "Ae2tdPwUPEZJYT9g1JgQWtLveUHavyRxQGi6hVzoQjct7yyCLGgk3pCyx7h"); + } +} + +TEST(CardanoAddress, FromPrivateKeyV2) { + { + // mnemonic Test, addr0 + auto privateKey = PrivateKey( + parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), + parse_hex("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff"), + parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4"), + dummyKey, dummyKey, dummyKey + ); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + ASSERT_EQ(hex(publicKey.bytes), + "57fd54be7b38bb8952782c2f59aa276928a4dcbb66c8c62ce44f9d623ecd5a03" + "bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4" + "857eed804ff087b97f87848f6493e87257a8c5203cb9f422f6e7a7d8a4d299f3" + "1111111111111111111111111111111111111111111111111111111111111111"); + auto address = AddressV2(publicKey); + ASSERT_EQ(address.string(), "Ae2tdPwUPEZ6RUCnjGHFqi59k5WZLiv3HoCCNGCW8SYc5H9srdTzn1bec4W"); + } + { + // mnemonic Play1, addr0 + auto privateKey = PrivateKey( + parse_hex("a089c9423100960440ccd5b7adbd202d1ab1993a7bb30fc88b287d94016df247"), + parse_hex("da86a87f08fb15de1431a6c0ccd5ebf51c3bee81f7eaf714801bbbe4d903154a"), + parse_hex("e513fa1290da1d22e83a41f17eed72d4489483b561fff36b9555ffdb91c430e2"), + dummyKey, dummyKey, dummyKey + ); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + ASSERT_EQ(hex(publicKey.bytes), + "7cee0f30b9d536a786547dd77b35679b6830e945ffde768eb4f2a061b9dba016" + "e513fa1290da1d22e83a41f17eed72d4489483b561fff36b9555ffdb91c430e2" + "857eed804ff087b97f87848f6493e87257a8c5203cb9f422f6e7a7d8a4d299f3" + "1111111111111111111111111111111111111111111111111111111111111111"); + auto address = AddressV2(publicKey); + ASSERT_EQ(address.string(), "Ae2tdPwUPEZJYT9g1JgQWtLveUHavyRxQGi6hVzoQjct7yyCLGgk3pCyx7h"); + } + { + // from cardano-crypto.js test + auto privateKey = PrivateKey( + parse_hex("d809b1b4b4c74734037f76aace501730a3fe2fca30b5102df99ad3f7c0103e48"), + parse_hex("d54cde47e9041b31f3e6873d700d83f7a937bea746dadfa2c5b0a6a92502356c"), + parse_hex("69272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000"), + dummyKey, dummyKey, dummyKey + ); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + ASSERT_EQ(hex(publicKey.bytes), + "e6f04522f875c1563682ca876ddb04c2e2e3ae718e3ff9f11c03dd9f9dccf698" + "69272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000" + "857eed804ff087b97f87848f6493e87257a8c5203cb9f422f6e7a7d8a4d299f3" + "1111111111111111111111111111111111111111111111111111111111111111"); + auto address = AddressV2(publicKey); + ASSERT_EQ(address.string(), "Ae2tdPwUPEZCxt4UV1Uj2AMMRvg5pYPypqZowVptz3GYpK4pkcvn3EjkuNH"); + } +} + +TEST(CardanoAddress, PrivateKeyExtended) { + // check extended key lengths, private key 2x3x32 bytes, public key 2x64 bytes + auto privateKeyExt = PrivateKey( + parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), + parse_hex("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff"), + parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4"), + dummyKey, dummyKey, dummyKey + ); + auto publicKeyExt = privateKeyExt.getPublicKey(TWPublicKeyTypeED25519Cardano); + ASSERT_EQ(128ul, publicKeyExt.bytes.size()); + + // Non-extended: both are 32 bytes. + auto privateKeyNonext = PrivateKey( + parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744") + ); + auto publicKeyNonext = privateKeyNonext.getPublicKey(TWPublicKeyTypeED25519); + ASSERT_EQ(32ul, publicKeyNonext.bytes.size()); +} + +TEST(CardanoAddress, FromStringNegativeInvalidString) { + try { + auto address = AddressV3("__INVALID_ADDRESS__"); + } catch (...) { + return; + } + FAIL() << "Expected exception!"; +} + +TEST(CardanoAddress, FromStringNegativeBadChecksumV2) { + try { + auto address = AddressV3("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvm"); + } catch (...) { + return; + } + FAIL() << "Expected exception!"; +} + +TEST(CardanoAddress, CopyConstructorLegacy) { + AddressV3 address1 = AddressV3("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvx"); + EXPECT_TRUE(address1.legacyAddressV2.has_value()); + AddressV3 address2 = AddressV3(address1); + EXPECT_TRUE(address2.legacyAddressV2.has_value()); + EXPECT_TRUE(*(address2.legacyAddressV2) == *(address1.legacyAddressV2)); + // if it was not a deep copy, double freeing would occur +} + +TEST(CardanoAddress, AssignmentOperatorLegacy) { + AddressV3 addr1leg = AddressV3("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvx"); + EXPECT_TRUE(addr1leg.legacyAddressV2.has_value()); + AddressV3 addr2nonleg = AddressV3("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + EXPECT_FALSE(addr2nonleg.legacyAddressV2.has_value()); + AddressV3 addr3leg = AddressV3("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvx"); + EXPECT_TRUE(addr3leg.legacyAddressV2.has_value()); + + AddressV3 address = addr1leg; + EXPECT_TRUE(address.legacyAddressV2.has_value()); + EXPECT_TRUE(*address.legacyAddressV2 == *addr1leg.legacyAddressV2); + address = addr2nonleg; + EXPECT_FALSE(address.legacyAddressV2.has_value()); + address = addr3leg; + EXPECT_TRUE(address.legacyAddressV2.has_value()); + EXPECT_TRUE(*address.legacyAddressV2 == *addr3leg.legacyAddressV2); +} + +TEST(CardanoAddress, StakingKey) { + { + auto address = AddressV3("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23"); + EXPECT_EQ(hex(address.data()), "01df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b"); + EXPECT_EQ(address.getStakingAddress(), "stake1u80jysjtdzqt88jt4jx93h5lumfr67d273r4vwyasfa2pxcwxllmx"); + EXPECT_EQ(hex(AddressV3(address.getStakingAddress()).data()), "e1df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b"); + EXPECT_EQ(hex(AddressV3(address.getStakingAddress()).bytes), "df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b"); + } + { + auto address = AddressV3("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + EXPECT_EQ(hex(address.data()), "018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468"); + EXPECT_EQ(address.getStakingAddress(), "stake1u8xxf0e93w8rxr8sehvlmvp7zz6wftqg7hdplhkxyg4rg6qwgxzhc"); + EXPECT_EQ(hex(AddressV3(address.getStakingAddress()).data()), "e1cc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468"); + EXPECT_EQ(hex(AddressV3(address.getStakingAddress()).bytes), "cc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468"); + } + { + auto address = AddressV3("addr1q8lcljuzfg8yvpuv94x02sytmwd8jsalzf6u0j8muhq69wng9ejcvpyczmw0zx7wguq2dml4xdl2wj3k7uexsfnxep2q9ja352"); + EXPECT_EQ(hex(address.data()), "01ff8fcb824a0e46078c2d4cf5408bdb9a7943bf1275c7c8fbe5c1a2ba682e6586049816dcf11bce4700a6eff5337ea74a36f732682666c854"); + EXPECT_EQ(address.getStakingAddress(), "stake1u95zuevxqjvpdh83r08ywq9xal6nxl48fgm0wvngyenvs4qh0hqf9"); + EXPECT_EQ(hex(AddressV3(address.getStakingAddress()).data()), "e1682e6586049816dcf11bce4700a6eff5337ea74a36f732682666c854"); + EXPECT_EQ(hex(AddressV3(address.getStakingAddress()).bytes), "682e6586049816dcf11bce4700a6eff5337ea74a36f732682666c854"); + } + { // negative case: cannot get staking address from non-base address + auto address = AddressV3("stake1u95zuevxqjvpdh83r08ywq9xal6nxl48fgm0wvngyenvs4qh0hqf9"); + EXPECT_EQ(hex(address.data()), "e1682e6586049816dcf11bce4700a6eff5337ea74a36f732682666c854"); + EXPECT_EQ(address.getStakingAddress(), ""); + } +} + +} // namespace TW::Cardano::tests diff --git a/tests/chains/Cardano/SigningTests.cpp b/tests/chains/Cardano/SigningTests.cpp new file mode 100644 index 00000000000..c4d6dcd1c73 --- /dev/null +++ b/tests/chains/Cardano/SigningTests.cpp @@ -0,0 +1,817 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Cardano/AddressV3.h" +#include "Cardano/Signer.h" +#include "proto/Cardano.pb.h" +#include + +#include "Cbor.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "uint256.h" +#include "TestUtilities.h" +#include + +#include +#include + +#ifdef _MSC_VER + typedef unsigned int uint; +#endif + +using namespace TW; +using namespace std; + +namespace TW::Cardano::SigningTests { + +const auto privateKeyTest1 = "089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e"; +const auto ownAddress1 = "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23"; +const auto sundaeTokenPolicy = "9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77"; + +TEST(CardanoSigning, SelectInputs) { + const auto inputs = std::vector({ + TxInput{{parse_hex("0001"), 0}, "ad01", 700, {}}, + TxInput{{parse_hex("0002"), 1}, "ad02", 900, {}}, + TxInput{{parse_hex("0003"), 2}, "ad03", 300, {}}, + TxInput{{parse_hex("0004"), 3}, "ad04", 600, {}}, + }); + + { // 2 + const auto s1 = Signer::selectInputsWithTokens(inputs, 1500, {}); + ASSERT_EQ(s1.size(), 2ul); + EXPECT_EQ(s1[0].amount, 900ul); + EXPECT_EQ(s1[1].amount, 700ul); + } + { // all + const auto s1 = Signer::selectInputsWithTokens(inputs, 10000, {}); + ASSERT_EQ(s1.size(), 4ul); + EXPECT_EQ(s1[0].amount, 900ul); + EXPECT_EQ(s1[1].amount, 700ul); + EXPECT_EQ(s1[2].amount, 600ul); + EXPECT_EQ(s1[3].amount, 300ul); + } + { // 3 + const auto s1 = Signer::selectInputsWithTokens(inputs, 2000, {}); + ASSERT_EQ(s1.size(), 3ul); + } + { // 1 + const auto s1 = Signer::selectInputsWithTokens(inputs, 500, {}); + ASSERT_EQ(s1.size(), 1ul); + } + { // at least 0 is returned + const auto s1 = Signer::selectInputsWithTokens(inputs, 0, {}); + ASSERT_EQ(s1.size(), 1ul); + } +} + +Proto::SigningInput createSampleInput(uint64_t amount, int utxoCount = 10, + const std::string& alternateToAddress = "", bool omitPrivateKey = false) { + const std::string toAddress = (alternateToAddress.length() > 0) ? alternateToAddress : "addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5"; + + Proto::SigningInput input; + if (utxoCount >= 1) { + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767"); + utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_output_index(1); + utxo1->set_address(ownAddress1); + utxo1->set_amount(1500000); + } + if (utxoCount >= 2) { + auto* utxo2 = input.add_utxos(); + const auto txHash2 = parse_hex("554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af0"); + utxo2->mutable_out_point()->set_tx_hash(txHash2.data(), txHash2.size()); + utxo2->mutable_out_point()->set_output_index(0); + utxo2->set_address(ownAddress1); + utxo2->set_amount(6500000); + } + + if (!omitPrivateKey) { + const auto privateKeyData = parse_hex(privateKeyTest1); + input.add_private_key(privateKeyData.data(), privateKeyData.size()); + } + input.mutable_transfer_message()->set_to_address(toAddress); + input.mutable_transfer_message()->set_change_address(ownAddress1); + input.mutable_transfer_message()->set_amount(amount); + input.mutable_transfer_message()->set_use_max_amount(false); + input.set_ttl(53333333); + return input; +} + +TEST(CardanoSigning, Plan) { + auto input = createSampleInput(7000000); + + { + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.utxos.size(), 2ul); + EXPECT_EQ(plan.availableAmount, 8000000ul); + EXPECT_EQ(plan.amount, 7000000ul); + EXPECT_EQ(plan.fee, 170196ul); + EXPECT_EQ(plan.change, 829804ul); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + EXPECT_EQ(plan.error, Common::Proto::OK); + } + { // very small target amount + input.mutable_transfer_message()->set_amount(1); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.utxos.size(), 1ul); + EXPECT_EQ(plan.availableAmount, 6500000ul); + EXPECT_EQ(plan.amount, 1ul); + EXPECT_EQ(plan.fee, 168435ul); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + } + { // small target amount + input.mutable_transfer_message()->set_amount(2000000); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.utxos.size(), 1ul); + EXPECT_EQ(plan.availableAmount, 6500000ul); + EXPECT_EQ(plan.amount, 2000000ul); + EXPECT_EQ(plan.fee, 168611ul); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + } + { // small target amount requested, but max amount + input.mutable_transfer_message()->set_amount(2000000); + input.mutable_transfer_message()->set_use_max_amount(true); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.utxos.size(), 2ul); + EXPECT_EQ(plan.availableAmount, 8000000ul); + EXPECT_EQ(plan.amount, 7832667ul); + EXPECT_EQ(plan.fee, 167333ul); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + } +} + +TEST(CardanoSigning, PlanForceFee) { + auto requestedAmount = 6500000ul; + auto availableAmount = 8000000ul; + auto input = createSampleInput(requestedAmount); + + { + auto fee = 170147ul; + input.mutable_transfer_message()->set_force_fee(fee); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.availableAmount, availableAmount); + EXPECT_EQ(plan.amount, requestedAmount); + EXPECT_EQ(plan.fee, fee); + EXPECT_EQ(plan.change, availableAmount - requestedAmount - fee); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + EXPECT_EQ(plan.error, Common::Proto::OK); + } + { // tiny fee + auto fee = 100ul; + input.mutable_transfer_message()->set_force_fee(fee); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.availableAmount, availableAmount); + EXPECT_EQ(plan.amount, requestedAmount); + EXPECT_EQ(plan.fee, fee); + EXPECT_EQ(plan.change, availableAmount - requestedAmount - fee); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + } + { // large fee + auto fee = 1200000ul; + input.mutable_transfer_message()->set_force_fee(fee); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.availableAmount, availableAmount); + EXPECT_EQ(plan.amount, requestedAmount); + EXPECT_EQ(plan.fee, fee); + EXPECT_EQ(plan.change, availableAmount - requestedAmount - fee); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + } + { // very large fee, larger than possible, truncated + auto fee = 3000000ul; + input.mutable_transfer_message()->set_force_fee(fee); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.availableAmount, availableAmount); + EXPECT_EQ(plan.amount, requestedAmount); + EXPECT_EQ(plan.fee, 1500000ul); + EXPECT_EQ(plan.change, 0ul); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + } + { // force fee and max amount: fee is used, amount is max, change 0 + auto fee = 160000ul; + input.mutable_transfer_message()->set_force_fee(fee); + input.mutable_transfer_message()->set_use_max_amount(true); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.availableAmount, availableAmount); + EXPECT_EQ(plan.amount, 7840000ul); + EXPECT_EQ(plan.fee, fee); + EXPECT_EQ(plan.change, 0ul); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + } +} + +TEST(CardanoSigning, PlanMissingPrivateKey) { + auto input = createSampleInput(7000000, 10, "", true); + + auto signer = Signer(input); + const auto plan = signer.doPlan(); + + EXPECT_EQ(plan.utxos.size(), 2ul); + EXPECT_EQ(plan.availableAmount, 8000000ul); + EXPECT_EQ(plan.amount, 7000000ul); + EXPECT_EQ(plan.fee, 170196ul); + EXPECT_EQ(plan.change, 829804ul); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + EXPECT_EQ(plan.error, Common::Proto::OK); +} + +TEST(CardanoSigning, SignTransfer1) { + const auto input = createSampleInput(7000000); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a40082825820554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af000825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd51a006acfc082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a000ca96c021a000298d4031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058407cf591599852b5f5e007fdc241062405c47e519266c0d884b0767c1d4f5eacce00db035998e53ed10ca4ba5ce4aac8693798089717ce6cf4415f345cc764200ef6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "9b5b15e133cd73ccaa85307d2986aebc846505118a2eb4e6111e6b4b67d1f389"); + + { + const auto decode = Cbor::Decode(encoded); + ASSERT_TRUE(decode.isValid()); + EXPECT_EQ(decode.dumpToString(), "[{0: [[h\"554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af0\", 0], [h\"f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767\", 1]], 1: [[h\"01558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd5\", 7000000], [h\"01df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\", 829804]], 2: 170196, 3: 53333333}, {0: [[h\"6d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df290\", h\"7cf591599852b5f5e007fdc241062405c47e519266c0d884b0767c1d4f5eacce00db035998e53ed10ca4ba5ce4aac8693798089717ce6cf4415f345cc764200e\"]]}, null]"); + EXPECT_EQ(decode.getArrayElements().size(), 3ul); + } +} + +TEST(CardanoSigning, PlanAndSignTransfer1) { + uint amount = 6000000; + auto input = createSampleInput(amount); + + { + // run plan and check result + auto signer = Signer(input); + const auto plan = signer.doPlan(); + + EXPECT_EQ(plan.availableAmount, 8000000ul); + EXPECT_EQ(plan.amount, amount); + EXPECT_EQ(plan.fee, 170196ul); + EXPECT_EQ(plan.change, 8000000 - amount - 170196); + ASSERT_EQ(plan.utxos.size(), 2ul); + EXPECT_EQ(plan.utxos[0].amount, 6500000ul); + EXPECT_EQ(plan.utxos[1].amount, 1500000ul); + + // perform sign with default plan + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a40082825820554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af000825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd51a005b8d8082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a001bebac021a000298d4031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058404abc749ffaffcf2f87970e4f1983c5e44b352ee1515b60017fc65e581d42b3a6ed146d5eb35d04a770460b0541a25afd5aedfd027fdaded82686f43454196a0cf6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "3852f809245d7000ad0c5ccb1357e5d333b0dd25158924581e4c7049ec68c564"); + } + + // set different plan, with one input only + input.mutable_plan()->set_amount(amount); + input.mutable_plan()->set_available_amount(6500000); + input.mutable_plan()->set_fee(165489); + input.mutable_plan()->set_change(17191988); + *(input.mutable_plan()->add_utxos()) = input.utxos(0); + input.mutable_plan()->set_error(Common::Proto::OK); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a40081825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd51a005b8d8082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a01065434021a00028671031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058408311a058035d75545a47b844fea401aa9c23e99fe7bc8136b554396eef135d4cd93062c5df38e613185c21bb1c98b881d1e0fd1024d3539b163c8e14d1a6e40df6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "e319c0bfc99cdb79d64f00b7e8fb8bfbf29fa70554c84f101e92b7dfed172448"); +} + +TEST(CardanoSigning, PlanAndSignMaxAmount) { + auto input = createSampleInput(7000000); + input.mutable_transfer_message()->set_use_max_amount(true); + + { + // run plan and check result + auto signer = Signer(input); + const auto plan = signer.doPlan(); + + EXPECT_EQ(plan.availableAmount, 8000000ul); + EXPECT_EQ(plan.amount, 8000000 - 167333ul); + EXPECT_EQ(plan.fee, 167333ul); + EXPECT_EQ(plan.change, 0ul); + ASSERT_EQ(plan.utxos.size(), 2ul); + EXPECT_EQ(plan.utxos[0].amount, 1500000ul); + EXPECT_EQ(plan.utxos[1].amount, 6500000ul); + } + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a40082825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701825820554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af000018182583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd51a0077845b021a00028da5031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058403e64473e08adc863953c0e9f820b658dda0b8a423d6172fdccff73fcd5559956c9df8ed93ff67405331d368a0c11fd18c69781046384946582e1555e9e8ec70bf6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "ca0f1e12f20c95011da7d686d206a1eb98df94accd74c4df4ef403c5ce836057"); +} + +TEST(CardanoSigning, SignNegative) { + { // plan with error + auto input = createSampleInput(7000000); + const auto error = Common::Proto::Error_invalid_memo; + input.mutable_plan()->set_error(error); + auto signer = Signer(input); + const auto output = signer.sign(); + EXPECT_EQ(output.error(), error); + } + { // zero requested amount + auto input = createSampleInput(0); + auto signer = Signer(input); + const auto output = signer.sign(); + EXPECT_EQ(output.error(), Common::Proto::Error_zero_amount_requested); + } + { // no utxo + auto input = createSampleInput(7000000, 0); + auto signer = Signer(input); + const auto output = signer.sign(); + EXPECT_EQ(output.error(), Common::Proto::Error_missing_input_utxos); + } + { // low balance + auto input = createSampleInput(7000000000); + auto signer = Signer(input); + const auto output = signer.sign(); + EXPECT_EQ(output.error(), Common::Proto::Error_low_balance); + } + { // missing private key + auto input = createSampleInput(7000000, 10, "", true); + auto signer = Signer(input); + const auto output = signer.sign(); + EXPECT_EQ(output.error(), Common::Proto::Error_missing_private_key); + } +} + +TEST(CardanoSigning, SignTransfer_0db1ea) { + const auto amount = 1100000ul; + + Proto::SigningInput input; + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("81b935447bb994567f041d181b628a0afbcd747d0199c9ff4cd895686bbee8c6"); + utxo1->mutable_out_point()->set_tx_hash(std::string(txHash1.begin(), txHash1.end())); + utxo1->mutable_out_point()->set_output_index(0); + utxo1->set_address(ownAddress1); + utxo1->set_amount(1000000); + auto* utxo2 = input.add_utxos(); + const auto txHash2 = parse_hex("3a9068a273cc2af59b45593b78973841d972d01802abe992c55dbeecdffc561b"); + utxo2->mutable_out_point()->set_tx_hash(std::string(txHash2.begin(), txHash2.end())); + utxo2->mutable_out_point()->set_output_index(0); + utxo2->set_address(ownAddress1); + utxo2->set_amount(1800000); + + const auto privateKeyData1 = parse_hex(privateKeyTest1); + input.add_private_key(privateKeyData1.data(), privateKeyData1.size()); + input.mutable_transfer_message()->set_to_address("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + input.mutable_transfer_message()->set_change_address(ownAddress1); + input.mutable_transfer_message()->set_amount(amount); + auto fee = 170147ul; + input.mutable_transfer_message()->set_use_max_amount(false); + input.mutable_transfer_message()->set_force_fee(fee); // use force fee feature here + input.set_ttl(54675589); + + { + // run plan and check result + auto signer = Signer(input); + const auto plan = signer.doPlan(); + + EXPECT_EQ(plan.availableAmount, 2800000ul); + EXPECT_EQ(plan.amount, amount); + EXPECT_EQ(plan.fee, fee); + EXPECT_EQ(plan.change, 2800000ul - amount - fee); + EXPECT_EQ(plan.utxos.size(), 2ul); + } + + // set plan with specific fee, to match the real transaction + input.mutable_plan()->set_amount(amount); + input.mutable_plan()->set_available_amount(2800000); + input.mutable_plan()->set_fee(fee); + input.mutable_plan()->set_change(2800000 - amount - fee); + *(input.mutable_plan()->add_utxos()) = input.utxos(0); + *(input.mutable_plan()->add_utxos()) = input.utxos(1); + input.mutable_plan()->set_error(Common::Proto::OK); + + auto signer = Signer(input); + const auto output = signer.sign(); + + // https://cardanoscan.io/transaction/0db1ea8c5c5828bbd027fcef3da02a63b86899db670ad7bb0630cefbe35944fa + // curl -d '{"txHash":"0db1ea..44fa","txBody":"83a400..06f6"}' -H "Content-Type: application/json" https:///api/txs/submit + EXPECT_EQ(output.error(), Common::Proto::OK); + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a4008282582081b935447bb994567f041d181b628a0afbcd747d0199c9ff4cd895686bbee8c6008258203a9068a273cc2af59b45593b78973841d972d01802abe992c55dbeecdffc561b000182825839018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a34681a0010c8e082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a001757fd021a000298a3031a03424885a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058406300b52aaff1e26067a3e0a48ae26f4f068765f46f934fabeab872c1d25535fc94893ec72feacd787f0174fbabd8933727d9a2b319b406e7a855843b0c051806f6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "0db1ea8c5c5828bbd027fcef3da02a63b86899db670ad7bb0630cefbe35944fa"); +} + +TEST(CardanoSigning, SignTransferFromLegacy) { + Proto::SigningInput input; + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767"); + utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_output_index(1); + utxo1->set_address("Ae2tdPwUPEZMRgecV9jV2e9RdbrmnWu7YgRie4de16xLdkWhy6q7ypmRhgn"); + utxo1->set_amount(1500000); + auto* utxo2 = input.add_utxos(); + const auto txHash2 = parse_hex("554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af0"); + utxo2->mutable_out_point()->set_tx_hash(txHash2.data(), txHash2.size()); + utxo2->mutable_out_point()->set_output_index(0); + utxo2->set_address("Ae2tdPwUPEZMRgecV9jV2e9RdbrmnWu7YgRie4de16xLdkWhy6q7ypmRhgn"); + utxo2->set_amount(6500000); + + const auto privateKeyData = parse_hex("c031e942f6bf2b2864700e7da20964ee6bb6d716345ce2e24d8c00e6500b574411111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"); + { + const auto privKey = PrivateKey(privateKeyData); + const auto pubKey = privKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto addr = AddressV2(pubKey); + EXPECT_EQ(addr.string(), "Ae2tdPwUPEZMRgecV9jV2e9RdbrmnWu7YgRie4de16xLdkWhy6q7ypmRhgn"); + } + input.add_private_key(privateKeyData.data(), privateKeyData.size()); + input.mutable_transfer_message()->set_to_address("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5"); + input.mutable_transfer_message()->set_change_address(ownAddress1); + input.mutable_transfer_message()->set_amount(7000000); + input.mutable_transfer_message()->set_use_max_amount(false); + input.set_ttl(53333333); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_address); + EXPECT_EQ(hex(output.encoded()), ""); +} + +TEST(CardanoSigning, SignTransferToLegacy) { + const auto toAddressLegacy = "DdzFFzCqrhssmYoG5Eca1bKZFdGS8d6iag1mU4wbLeYcSPVvBNF2wRG8yhjzQqErbg63N6KJA4DHqha113tjKDpGEwS5x1dT2KfLSbSJ"; + EXPECT_FALSE(AddressV3::isValid(toAddressLegacy)); // not V3 + EXPECT_TRUE(AddressV3::isValidLegacy(toAddressLegacy)); + + const auto input = createSampleInput(7000000, 10, toAddressLegacy); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + EXPECT_EQ(hex(output.encoded()), "83a40082825820554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af000825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701018282584c82d818584283581c6aebd89cf88271c3ee76339930d8956b03f018b2f4871522f88eb8f9a101581e581c692a37dae3bc63dfc3e1463f12011f26655ab1d1e0f4ed4b8fc63708001ad8a9555b1a006acfc082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a000ca627021a00029c19031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840db9becdc733f4c08c0e7abc29b5cc6469f9339d32f565df8bf77455439ae1f949facc9b831754e74d3fbb42e99647eedd6c28de1461d18c315485f5d24b5b90af6"); + EXPECT_EQ(hex(data(output.tx_id())), "f9b713e9987ec1377ac223f50d63c7a5e155915302de43f40d7b2627accabf69"); +} + +TEST(CardanoSigning, SignTransferToInvalid) { + const auto input = createSampleInput(7000000, 10, "__INVALID_ADDRESS__"); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_address); + EXPECT_EQ(hex(output.encoded()), ""); +} + +TEST(CardanoSigning, SignTransferToken) { + Proto::SigningInput input; + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767"); + utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_output_index(1); + utxo1->set_address(ownAddress1); + utxo1->set_amount(8051373); + // some token, to be preserved + auto* token3 = utxo1->add_token_amount(); + token3->set_policy_id(sundaeTokenPolicy); + token3->set_asset_name("CUBY"); + const auto tokenAmount3 = store(uint256_t(3000000)); + token3->set_amount(tokenAmount3.data(), tokenAmount3.size()); + + auto* utxo2 = input.add_utxos(); + const auto txHash2 = parse_hex("f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767"); + utxo2->mutable_out_point()->set_tx_hash(txHash2.data(), txHash2.size()); + utxo2->mutable_out_point()->set_output_index(2); + utxo2->set_address(ownAddress1); + utxo2->set_amount(2000000); + // some SUNDAE token, to be transferred + auto* token1 = utxo2->add_token_amount(); + token1->set_policy_id(sundaeTokenPolicy); + token1->set_asset_name("SUNDAE"); + const auto tokenAmount1 = store(uint256_t(80996569)); + token1->set_amount(tokenAmount1.data(), tokenAmount1.size()); + // some other token, to be preserved + auto* token2 = utxo2->add_token_amount(); + token2->set_policy_id(sundaeTokenPolicy); + token2->set_asset_name("CUBY"); + const auto tokenAmount2 = store(uint256_t(2000000)); + token2->set_amount(tokenAmount2.data(), tokenAmount2.size()); + + const auto privateKeyData = parse_hex(privateKeyTest1); + input.add_private_key(privateKeyData.data(), privateKeyData.size()); + input.mutable_transfer_message()->set_to_address("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5"); + input.mutable_transfer_message()->set_change_address("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + input.mutable_transfer_message()->set_amount(1500000); + auto* toToken = input.mutable_transfer_message()->mutable_token_amount()->add_token(); + toToken->set_policy_id(sundaeTokenPolicy); + toToken->set_asset_name("SUNDAE"); + const auto toTokenAmount = store(uint256_t(20000000)); + toToken->set_amount(toTokenAmount.data(), toTokenAmount.size()); + input.mutable_transfer_message()->set_use_max_amount(false); + input.set_ttl(53333333); + + { // check min ADA amount, set it + const auto bundleProtoData = data(input.transfer_message().token_amount().SerializeAsString()); + const auto minAdaAmount = TWCardanoMinAdaAmount(&bundleProtoData); + EXPECT_EQ(minAdaAmount, 1444443ul); + input.mutable_transfer_message()->set_amount(minAdaAmount); + } + + { + // run plan and check result + auto signer = Signer(input); + const auto plan = signer.doPlan(); + + EXPECT_EQ(plan.availableAmount, 10051373ul); + EXPECT_EQ(plan.amount, 1444443ul); + EXPECT_EQ(plan.fee, 174601ul); + EXPECT_EQ(plan.change, 8432329ul); + EXPECT_EQ(plan.utxos.size(), 2ul); + EXPECT_EQ(plan.availableTokens.size(), 2ul); + EXPECT_EQ(plan.availableTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_CUBY"), 5000000); + EXPECT_EQ(plan.availableTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 80996569); + EXPECT_EQ(plan.outputTokens.size(), 1ul); + EXPECT_EQ(plan.outputTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_CUBY"), 0); + EXPECT_EQ(plan.outputTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 20000000); + EXPECT_EQ(plan.changeTokens.size(), 2ul); + EXPECT_EQ(plan.changeTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_CUBY"), 5000000); + EXPECT_EQ(plan.changeTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 60996569); + } + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a40082825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76702018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd5821a00160a5ba1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a14653554e4441451a01312d00825839018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468821a0080aac9a1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a244435542591a004c4b404653554e4441451a03a2bbd9021a0002aa09031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840d90dcfbd190cbe59c42094e59eeb49b3de9d80a85b786cc311f932c5c9302d1c8c6c577b22aa70ff7955c139c700ea918f8cb425c3ba43a27980e1d238e4e908f6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "201c537693b005b64a0f0528e366ec67a84be0119ed4363b547f141f2a7770c2"); + + { + // also test proto toProto / fromProto + const Proto::TransactionPlan planProto = Signer::plan(input); + const auto plan2 = TransactionPlan::fromProto(planProto); + EXPECT_EQ(plan2.amount, 1444443ul); + EXPECT_EQ(plan2.change, 8432329ul); + } +} + +TEST(CardanoSigning, SignTransferToken_1dd248) { + Proto::SigningInput input; + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("f2d2b11c8c07c5c646f5b5af20fddf2f0a174743c6a1b13cca27e28a6ca34710"); + utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_output_index(0); + utxo1->set_address(ownAddress1); + utxo1->set_amount(1500000); + // some token + auto* token3 = utxo1->add_token_amount(); + token3->set_policy_id(sundaeTokenPolicy); + token3->set_asset_name("SUNDAE"); + const auto tokenAmount3 = store(uint256_t(20000000)); + token3->set_amount(tokenAmount3.data(), tokenAmount3.size()); + + auto* utxo2 = input.add_utxos(); + const auto txHash2 = parse_hex("6975fcf7bbca745c85f50777f956219868fd9cad14ba496fed1371252e8df60f"); + utxo2->mutable_out_point()->set_tx_hash(txHash2.data(), txHash2.size()); + utxo2->mutable_out_point()->set_output_index(0); + utxo2->set_address(ownAddress1); + utxo2->set_amount(10258890); + + const auto privateKeyData = parse_hex(privateKeyTest1); + input.add_private_key(privateKeyData.data(), privateKeyData.size()); + input.mutable_transfer_message()->set_to_address("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); // Test + input.mutable_transfer_message()->set_change_address(ownAddress1); + input.mutable_transfer_message()->set_amount(1600000); + auto* toToken = input.mutable_transfer_message()->mutable_token_amount()->add_token(); + toToken->set_policy_id(sundaeTokenPolicy); + toToken->set_asset_name("SUNDAE"); + const auto toTokenAmount = store(uint256_t(11000000)); + toToken->set_amount(toTokenAmount.data(), toTokenAmount.size()); + input.mutable_transfer_message()->set_use_max_amount(false); + input.set_ttl(61232158); + + { // check min ADA amount + const auto bundleProtoData = data(input.transfer_message().token_amount().SerializeAsString()); + EXPECT_EQ(TWCardanoMinAdaAmount(&bundleProtoData), 1444443ul); + EXPECT_GT(input.transfer_message().amount(), TWCardanoMinAdaAmount(&bundleProtoData)); + } + + { + // run plan and check result + auto signer = Signer(input); + const auto plan = signer.doPlan(); + + EXPECT_EQ(plan.availableAmount, 11758890ul); + EXPECT_EQ(plan.amount, 11758890 - 9984729 - 174161ul); + EXPECT_EQ(plan.fee, 174161ul); + EXPECT_EQ(plan.change, 9984729ul); + EXPECT_EQ(plan.utxos.size(), 2ul); + EXPECT_EQ(plan.availableTokens.size(), 1ul); + EXPECT_EQ(plan.availableTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 20000000); + EXPECT_EQ(plan.outputTokens.size(), 1ul); + EXPECT_EQ(plan.outputTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 11000000); + EXPECT_EQ(plan.changeTokens.size(), 1ul); + EXPECT_EQ(plan.changeTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 9000000); + } + + // set plan with specific fee, to match the real transaction + input.mutable_plan()->set_available_amount(11758890); + input.mutable_plan()->set_amount(1600000); + input.mutable_plan()->set_fee(174102); + input.mutable_plan()->set_change(9984788); + *(input.mutable_plan()->add_available_tokens()) = input.utxos(0).token_amount(0); + *(input.mutable_plan()->add_output_tokens()) = input.utxos(0).token_amount(0); + input.mutable_plan()->mutable_output_tokens(0)->set_amount(toTokenAmount.data(), toTokenAmount.size()); + *(input.mutable_plan()->add_change_tokens()) = input.utxos(0).token_amount(0); + const auto changeTokenAmount = store(uint256_t(9000000)); + input.mutable_plan()->mutable_change_tokens(0)->set_amount(changeTokenAmount.data(), changeTokenAmount.size()); + *(input.mutable_plan()->add_utxos()) = input.utxos(1); + *(input.mutable_plan()->add_utxos()) = input.utxos(0); + input.mutable_plan()->set_error(Common::Proto::OK); + + auto signer = Signer(input); + const auto output = signer.sign(); + + // https://cardanoscan.io/transaction/1dd24872d93d3b5091b98e19b9f920cd0c4369e4c5ca178e898152c52f00c162 + // curl -d '{"txHash":"1dd248..c162","txBody":"83a400..08f6"}' -H "Content-Type: application/json" https:///api/txs/submit + EXPECT_EQ(output.error(), Common::Proto::OK); + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a400828258206975fcf7bbca745c85f50777f956219868fd9cad14ba496fed1371252e8df60f00825820f2d2b11c8c07c5c646f5b5af20fddf2f0a174743c6a1b13cca27e28a6ca34710000182825839018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468821a00186a00a1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a14653554e4441451a00a7d8c082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b821a00985b14a1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a14653554e4441451a00895440021a0002a816031a03a6541ea100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840c8cdee32bfd584f55cf334b4ec6f734635144736d48f882e647a7a6283f230bc5a67d4dd66a9e523e0c29c812ed1e3589febbcf96547a1fc6d061a7ccfb81308f6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "1dd24872d93d3b5091b98e19b9f920cd0c4369e4c5ca178e898152c52f00c162"); +} + +TEST(CardanoSigning, SignTransferTokenMaxAmount_620b71) { + Proto::SigningInput input; + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("46964521ad00d9b3f3d41f77c07e1b3093848048dbdf2d95cf900e15cdac0d7f"); + utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_output_index(0); + utxo1->set_address(ownAddress1); + utxo1->set_amount(2170871); + // some token + auto* token1 = utxo1->add_token_amount(); + token1->set_policy_id(sundaeTokenPolicy); + token1->set_asset_name("SUNDAE"); + const auto tokenAmount1 = store(uint256_t(20000000)); + token1->set_amount(tokenAmount1.data(), tokenAmount1.size()); + + const auto privateKeyData = parse_hex(privateKeyTest1); + input.add_private_key(privateKeyData.data(), privateKeyData.size()); + input.mutable_transfer_message()->set_to_address("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5"); + input.mutable_transfer_message()->set_change_address(ownAddress1); + input.mutable_transfer_message()->set_amount(666); // doesn't matter, max is used + auto* toToken = input.mutable_transfer_message()->mutable_token_amount()->add_token(); + toToken->set_policy_id(sundaeTokenPolicy); + toToken->set_asset_name("SUNDAE"); + const auto toTokenAmount = store(uint256_t(666)); // doesn't matter, max is used + input.mutable_transfer_message()->set_use_max_amount(true); + input.set_ttl(61085916); + + { + // run plan and check result + auto signer = Signer(input); + const auto plan = signer.doPlan(); + + EXPECT_EQ(plan.availableAmount, 2170871ul); + EXPECT_EQ(plan.amount, 2170871 - 167730ul); + EXPECT_EQ(plan.fee, 167730ul); + EXPECT_EQ(plan.change, 0ul); + EXPECT_EQ(plan.utxos.size(), 1ul); + EXPECT_EQ(plan.availableTokens.size(), 1ul); + EXPECT_EQ(plan.availableTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 20000000); + EXPECT_EQ(plan.outputTokens.size(), 1ul); + EXPECT_EQ(plan.outputTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 20000000); + EXPECT_EQ(plan.changeTokens.size(), 0ul); + } + + // set plan with specific fee, to match the real transaction + input.mutable_plan()->set_available_amount(2170871); + input.mutable_plan()->set_amount(1998526); + input.mutable_plan()->set_fee(172345); + input.mutable_plan()->set_change(0); + *(input.mutable_plan()->add_available_tokens()) = input.utxos(0).token_amount(0); + *(input.mutable_plan()->add_output_tokens()) = input.utxos(0).token_amount(0); + *(input.mutable_plan()->add_utxos()) = input.utxos(0); + input.mutable_plan()->set_error(Common::Proto::OK); + + auto signer = Signer(input); + const auto output = signer.sign(); + + // https://cardanoscan.io/transaction/620b719338efb419b0e1417bfbe01fc94a62d5669a4b8cbbf4e32ecc1ca3b872 + // curl -d '{"txHash":"620b71..b872","txBody":"83a400..08f6"}' -H "Content-Type: application/json" https:///api/txs/submit + EXPECT_EQ(output.error(), Common::Proto::OK); + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a4008182582046964521ad00d9b3f3d41f77c07e1b3093848048dbdf2d95cf900e15cdac0d7f00018182583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd5821a001e7ebea1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a14653554e4441451a01312d00021a0002a139031a03a418dca100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840e1d1565cd747b20b0f10a92f068f3d5faebdee92b4b4a4b96ce14736d975e17d1446f7f51e64997a0bb38e0151dc738468161d574d6cfcd8040e4455ff46bc08f6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "620b719338efb419b0e1417bfbe01fc94a62d5669a4b8cbbf4e32ecc1ca3b872"); +} + +TEST(CardanoSigning, SignTransferTwoTokens) { + auto input = createSampleInput(7000000); + input.mutable_transfer_message()->set_amount(1500000); + auto* token1 = input.mutable_transfer_message()->mutable_token_amount()->add_token(); + token1->set_policy_id(sundaeTokenPolicy); + token1->set_asset_name("SUNDAE"); + const auto tokenAmount1 = store(uint256_t(40000000)); + token1->set_amount(tokenAmount1.data(), tokenAmount1.size()); + auto* token2 = input.mutable_transfer_message()->mutable_token_amount()->add_token(); + token2->set_policy_id(sundaeTokenPolicy); + token2->set_asset_name("CUBY"); + const auto tokenAmount2 = store(uint256_t(2000000)); + token2->set_amount(tokenAmount2.data(), tokenAmount2.size()); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_requested_token_amount); + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(output.encoded()), ""); +} + +TEST(CardanoSigning, SignMessageWithKey) { + // test case from cardano-crypto.js + + const auto privateKey = PrivateKey(parse_hex( + "d809b1b4b4c74734037f76aace501730a3fe2fca30b5102df99ad3f7c0103e48" + "d54cde47e9041b31f3e6873d700d83f7a937bea746dadfa2c5b0a6a92502356c" + "69272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000" + "1111111111111111111111111111111111111111111111111111111111111111" + "1111111111111111111111111111111111111111111111111111111111111111" + "1111111111111111111111111111111111111111111111111111111111111111")); + + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + EXPECT_EQ(hex(publicKey.bytes), + "e6f04522f875c1563682ca876ddb04c2e2e3ae718e3ff9f11c03dd9f9dccf698" + "69272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000" + "857eed804ff087b97f87848f6493e87257a8c5203cb9f422f6e7a7d8a4d299f3" + "1111111111111111111111111111111111111111111111111111111111111111"); + + const auto sampleMessageStr = "Hello world"; + const auto sampleMessage = data(sampleMessageStr); + + const auto signature = privateKey.sign(sampleMessage, TWCurveED25519ExtendedCardano); + + const auto sampleRightSignature = "1096ddcfb2ad21a4c0d861ef3fabe18841e8de88105b0d8e36430d7992c588634ead4100c32b2800b31b65e014d54a8238bdda63118d829bf0bcf1b631e86f0e"; + EXPECT_EQ(hex(signature), sampleRightSignature); +} + +TEST(CardanoSigning, AnySignTransfer1) { + const auto input = createSampleInput(7000000); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeCardano); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a40082825820554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af000825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd51a006acfc082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a000ca96c021a000298d4031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058407cf591599852b5f5e007fdc241062405c47e519266c0d884b0767c1d4f5eacce00db035998e53ed10ca4ba5ce4aac8693798089717ce6cf4415f345cc764200ef6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "9b5b15e133cd73ccaa85307d2986aebc846505118a2eb4e6111e6b4b67d1f389"); +} + +TEST(CardanoSigning, AnyPlan1) { + const auto input = createSampleInput(7000000); + + Proto::TransactionPlan plan; + ANY_PLAN(input, plan, TWCoinTypeCardano); + + EXPECT_EQ(plan.error(), Common::Proto::OK); + EXPECT_EQ(plan.amount(), 7000000ul); + EXPECT_EQ(plan.available_amount(), 8000000ul); + EXPECT_EQ(plan.fee(), 170196ul); + EXPECT_EQ(plan.change(), 829804ul); + ASSERT_EQ(plan.utxos_size(), 2); + EXPECT_EQ(plan.utxos(0).amount(), 6500000ul); + EXPECT_EQ(plan.utxos(1).amount(), 1500000ul); + + EXPECT_EQ(hex(plan.SerializeAsString()), "0880a4e80310c09fab0318d4b10a20ecd2324292010a220a20554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af01267616464723171383034336d356865656179646e76746d6d6b7975686536717635686176766873663064323671336a7967737370786c796670796b3679716b77307968747976747230666c656b6a3834753634617a38326375666d716e36357a6473796c7a6b323318a0dd8c034293010a240a20f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76710011267616464723171383034336d356865656179646e76746d6d6b7975686536717635686176766873663064323671336a7967737370786c796670796b3679716b77307968747976747230666c656b6a3834753634617a38326375666d716e36357a6473796c7a6b323318e0c65b"); + + { + // also test fromProto + const auto plan2 = TransactionPlan::fromProto(plan); + EXPECT_EQ(plan2.amount, plan.amount()); + EXPECT_EQ(plan2.change, plan.change()); + } +} + +} // namespace TW::Cardano::tests \ No newline at end of file diff --git a/tests/chains/Cardano/StakingTests.cpp b/tests/chains/Cardano/StakingTests.cpp new file mode 100644 index 00000000000..cf7636b23c5 --- /dev/null +++ b/tests/chains/Cardano/StakingTests.cpp @@ -0,0 +1,351 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Cardano/AddressV3.h" +#include "Cardano/Signer.h" +#include "proto/Cardano.pb.h" +#include + +#include "Cbor.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "uint256.h" +#include "TestUtilities.h" +#include + +#include +#include + +using namespace TW; +using namespace std; + +namespace TW::Cardano::StakingTests { + +const auto privateKeyTest1 = "089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e"; +const auto ownAddress1 = "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23"; +const auto stakingAddress1 = "stake1u80jysjtdzqt88jt4jx93h5lumfr67d273r4vwyasfa2pxcwxllmx"; +const auto poolIdNufi = "7d7ac07a2f2a25b7a4db868a40720621c4939cf6aefbb9a11464f1a6"; + +TEST(CardanoStaking, RegisterStakingKey) { + const auto privateKeyData = parse_hex(privateKeyTest1); + const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto ownAddress = AddressV3(publicKey).string(); + EXPECT_EQ(ownAddress, ownAddress1); + const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); + EXPECT_EQ(stakingAddress, stakingAddress1); + const auto poolId = parse_hex(poolIdNufi); + + Proto::SigningInput input; + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("cba84549f07f2128410c0a22731f2c57f2a617746e8edc61b295cd8792638dca"); + utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_output_index(1); + utxo1->set_address(ownAddress); + utxo1->set_amount(10000000ul); + + input.add_private_key(privateKeyData.data(), privateKeyData.size()); + + input.mutable_transfer_message()->set_to_address(ownAddress); + input.mutable_transfer_message()->set_change_address(ownAddress); + input.mutable_transfer_message()->set_amount(5000000ul); // not relevant if we use MaxAmount + input.mutable_transfer_message()->set_use_max_amount(true); + input.set_ttl(69986091ul); + + // Register staking key, 2 ADA deposit + input.mutable_register_staking_key()->set_staking_address(stakingAddress); + input.mutable_register_staking_key()->set_deposit_amount(2000000ul); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a50081825820cba84549f07f2128410c0a22731f2c57f2a617746e8edc61b295cd8792638dca01018182583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a007772fa021a00029f06031a042be72b048182008200581cdf22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09ba100828258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840d08ed71da87d0928090edd9e226496ab109f2eee7926ac2ce51e7abe89a4f513c4afe2b85b71595e862e7f6fc992d14d2416a6e53a1961da7d26d3cf3f823400825820e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e06932584079ed55400cebc70c56ca87ba09009dfc298c64768f90a9139bf2e7f134250927c614ee846253fac33e652f1b50373d349fdfe13c207968c2a10991824fe2a10ef6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "6a206fe4df76e12499b4fd9722f33429f4d93f8a996f9f523fa6c02a8301386b"); + + { + const auto decode = Cbor::Decode(encoded); + ASSERT_TRUE(decode.isValid()); + EXPECT_EQ(decode.dumpToString(), "[{0: [[h\"cba84549f07f2128410c0a22731f2c57f2a617746e8edc61b295cd8792638dca\", 1]], 1: [[h\"01df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\", 7828218]], 2: 171782, 3: 69986091, 4: [[0, [0, h\"df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\"]]]}, {0: [[h\"6d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df290\", h\"d08ed71da87d0928090edd9e226496ab109f2eee7926ac2ce51e7abe89a4f513c4afe2b85b71595e862e7f6fc992d14d2416a6e53a1961da7d26d3cf3f823400\"], [h\"e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e06932\", h\"79ed55400cebc70c56ca87ba09009dfc298c64768f90a9139bf2e7f134250927c614ee846253fac33e652f1b50373d349fdfe13c207968c2a10991824fe2a10e\"]]}, null]"); + EXPECT_EQ(decode.getArrayElements().size(), 3ul); + } +} + +TEST(CardanoStaking, DeregisterStakingKey) { + const auto privateKeyData = parse_hex(privateKeyTest1); + const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto ownAddress = AddressV3(publicKey).string(); + EXPECT_EQ(ownAddress, ownAddress1); + const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); + EXPECT_EQ(stakingAddress, stakingAddress1); + const auto poolId = parse_hex(poolIdNufi); + + Proto::SigningInput input; + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("cba84549f07f2128410c0a22731f2c57f2a617746e8edc61b295cd8792638dca"); + utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_output_index(1); + utxo1->set_address(ownAddress); + utxo1->set_amount(10000000ul); + + input.add_private_key(privateKeyData.data(), privateKeyData.size()); + + input.mutable_transfer_message()->set_to_address(ownAddress); + input.mutable_transfer_message()->set_change_address(ownAddress); + input.mutable_transfer_message()->set_amount(5000000ul); // not relevant if we use MaxAmount + input.mutable_transfer_message()->set_use_max_amount(true); + input.set_ttl(69986091ul); + + // Deregister staking key, get back 2 ADA deposit + input.mutable_deregister_staking_key()->set_staking_address(stakingAddress); + input.mutable_deregister_staking_key()->set_undeposit_amount(2000000ul); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a50081825820cba84549f07f2128410c0a22731f2c57f2a617746e8edc61b295cd8792638dca01018182583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a00b47bfa021a00029f06031a042be72b048182018200581cdf22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09ba100828258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df290584056619a7d6192b6f68c31a43e927c893161fd994d5c1bcc16f3710cf5e5e652e01f118d55f0110e9de34edc050d509748bea637db5c34f4fe342ae262ccb5520d825820e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e069325840d23680fdd8aa63e10efccc550eb726743b653008952f9d731d076d1df8106b0401823ebb195127b211389f1bc2c3f6ededbcec04bc8f0de93607a2409421e006f6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "1caae2456e5471cc77e73410da475fb0a23874c18c1ea55f9267c59767caef0a"); + + { + const auto decode = Cbor::Decode(encoded); + ASSERT_TRUE(decode.isValid()); + EXPECT_EQ(decode.dumpToString(), "[{0: [[h\"cba84549f07f2128410c0a22731f2c57f2a617746e8edc61b295cd8792638dca\", 1]], 1: [[h\"01df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\", 11828218]], 2: 171782, 3: 69986091, 4: [[1, [0, h\"df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\"]]]}, {0: [[h\"6d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df290\", h\"56619a7d6192b6f68c31a43e927c893161fd994d5c1bcc16f3710cf5e5e652e01f118d55f0110e9de34edc050d509748bea637db5c34f4fe342ae262ccb5520d\"], [h\"e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e06932\", h\"d23680fdd8aa63e10efccc550eb726743b653008952f9d731d076d1df8106b0401823ebb195127b211389f1bc2c3f6ededbcec04bc8f0de93607a2409421e006\"]]}, null]"); + EXPECT_EQ(decode.getArrayElements().size(), 3ul); + } +} + +TEST(CardanoStaking, Redelegate) { + const auto privateKeyData = parse_hex(privateKeyTest1); + const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto ownAddress = AddressV3(publicKey).string(); + EXPECT_EQ(ownAddress, ownAddress1); + const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); + EXPECT_EQ(stakingAddress, stakingAddress1); + const auto poolId = parse_hex(poolIdNufi); + + Proto::SigningInput input; + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("cba84549f07f2128410c0a22731f2c57f2a617746e8edc61b295cd8792638dca"); + utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_output_index(1); + utxo1->set_address(ownAddress); + utxo1->set_amount(10000000ul); + + input.add_private_key(privateKeyData.data(), privateKeyData.size()); + + input.mutable_transfer_message()->set_to_address(ownAddress); + input.mutable_transfer_message()->set_change_address(ownAddress); + input.mutable_transfer_message()->set_amount(5000000ul); // not relevant if we use MaxAmount + input.mutable_transfer_message()->set_use_max_amount(true); + input.set_ttl(69986091ul); + + // Delegate, no deposit + input.mutable_delegate()->set_staking_address(stakingAddress); + input.mutable_delegate()->set_pool_id(poolId.data(), poolId.size()); + input.mutable_delegate()->set_deposit_amount(0ul); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a50081825820cba84549f07f2128410c0a22731f2c57f2a617746e8edc61b295cd8792638dca01018182583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a0095f251021a0002a42f031a042be72b048183028200581cdf22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b581c7d7ac07a2f2a25b7a4db868a40720621c4939cf6aefbb9a11464f1a6a100828258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840fb48f3ddbfc2d4ca231a0581c5b456019aa4215ed5a2447ba89a4860569f9e7296afd3a0a81506882d8bda33683e623e6d8033786275369f7e247d866e017c06825820e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e069325840e26f696a6cd1c34101623568c9efe3796ff5855ada0e2e0cf557c7fc2148f6b2af176aff40a1f9c13fb29d9636c49f774d4a967c71f052f865cfaf0d02d5bb05f6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "985f613fb8b86dad35f075599099776e50fc2a6aa74ee4b37c14fd9f2c0f0891"); + + { + const auto decode = Cbor::Decode(encoded); + ASSERT_TRUE(decode.isValid()); + EXPECT_EQ(decode.dumpToString(), "[{0: [[h\"cba84549f07f2128410c0a22731f2c57f2a617746e8edc61b295cd8792638dca\", 1]], 1: [[h\"01df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\", 9826897]], 2: 173103, 3: 69986091, 4: [[2, [0, h\"df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\"], h\"7d7ac07a2f2a25b7a4db868a40720621c4939cf6aefbb9a11464f1a6\"]]}, {0: [[h\"6d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df290\", h\"fb48f3ddbfc2d4ca231a0581c5b456019aa4215ed5a2447ba89a4860569f9e7296afd3a0a81506882d8bda33683e623e6d8033786275369f7e247d866e017c06\"], [h\"e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e06932\", h\"e26f696a6cd1c34101623568c9efe3796ff5855ada0e2e0cf557c7fc2148f6b2af176aff40a1f9c13fb29d9636c49f774d4a967c71f052f865cfaf0d02d5bb05\"]]}, null]"); + EXPECT_EQ(decode.getArrayElements().size(), 3ul); + } +} + +TEST(CardanoStaking, RegisterAndDelegate_similar53339b) { + const auto privateKeyData = parse_hex(privateKeyTest1); + const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto ownAddress = AddressV3(publicKey).string(); + EXPECT_EQ(ownAddress, ownAddress1); + const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); + EXPECT_EQ(stakingAddress, stakingAddress1); + const auto poolId = parse_hex(poolIdNufi); + + Proto::SigningInput input; + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("9b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e"); + utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_output_index(0); + utxo1->set_address(ownAddress); + utxo1->set_amount(4000000ul); + auto* utxo2 = input.add_utxos(); + const auto txHash2 = parse_hex("9b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e"); + utxo2->mutable_out_point()->set_tx_hash(txHash2.data(), txHash2.size()); + utxo2->mutable_out_point()->set_output_index(1); + utxo2->set_address(ownAddress); + utxo2->set_amount(26651312ul); + + input.add_private_key(privateKeyData.data(), privateKeyData.size()); + + input.mutable_transfer_message()->set_to_address(ownAddress); + input.mutable_transfer_message()->set_change_address(ownAddress); + input.mutable_transfer_message()->set_amount(4000000ul); // not relevant if we use MaxAmount + input.mutable_transfer_message()->set_use_max_amount(true); + input.set_ttl(69885081ul); + + // Register staking key, 2 ADA desposit + input.mutable_register_staking_key()->set_staking_address(stakingAddress); + input.mutable_register_staking_key()->set_deposit_amount(2000000ul); + + // Delegate + input.mutable_delegate()->set_staking_address(stakingAddress); + input.mutable_delegate()->set_pool_id(poolId.data(), poolId.size()); + input.mutable_delegate()->set_deposit_amount(0ul); + + { + // run plan and check result + auto signer = Signer(input); + const auto plan = signer.doPlan(); + + const auto amount = 28475125ul; + const auto availAmount = 30651312ul; + EXPECT_EQ(plan.availableAmount, availAmount); + EXPECT_EQ(plan.amount, amount); + const auto fee = 176187ul; + EXPECT_EQ(plan.fee, fee); + EXPECT_EQ(plan.change, availAmount - 2000000ul - amount - fee); + EXPECT_EQ(plan.change, 0ul); + + // perform sign with default plan + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a500828258209b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e008258209b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e01018182583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a01b27ef5021a0002b03b031a042a5c99048282008200581cdf22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b83028200581cdf22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b581c7d7ac07a2f2a25b7a4db868a40720621c4939cf6aefbb9a11464f1a6a100828258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840677c901704be027d9a1734e8aa06f0700009476fa252baaae0de280331746a320a61456d842d948ea5c0e204fc36f3bd04c88ca7ee3d657d5a38014243c37c07825820e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e0693258401fa21bdc62b85ca217bf08cbacdeba2fadaf33dc09ee3af9cc25b40f24822a1a42cfbc03585cc31a370ef75aaec4d25db6edcf329e40a4e725ec8718c94f220af6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "96a781fd6481b6a7fd3926da110265e8c44b53947b81daa84da5b148825d02aa"); + } + + // set different plan, with exact fee + const auto amount = 28467322ul; + input.mutable_plan()->set_amount(amount); + input.mutable_plan()->set_available_amount(28651312ul); + input.mutable_plan()->set_fee(183990); + input.mutable_plan()->set_change(0); + *(input.mutable_plan()->add_utxos()) = input.utxos(0); + *(input.mutable_plan()->add_utxos()) = input.utxos(1); + input.mutable_plan()->set_error(Common::Proto::OK); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + // Similar to (but with different key): https://cardanoscan.io/transaction/53339b758009a0896a87e9569cadcdb5a095ffe0c100cc7483d72e817e81b60b + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a500828258209b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e008258209b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e01018182583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a01b2607a021a0002ceb6031a042a5c99048282008200581cdf22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b83028200581cdf22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b581c7d7ac07a2f2a25b7a4db868a40720621c4939cf6aefbb9a11464f1a6a100828258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058405d8b21c993aec7a7bdf0c832e5688920b64b665e1e36a2e6040d6dd8ad195e7774df3c1377047737d8b676fa4115e38fbf6ef854904db6d9c8ee3e26e8561408825820e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e06932584088a3f6387693f9077d11a6e245e024b791074bcaa26c034e687d67f3324b6f90a437d33d0343e11c7dee1a28270c223e02080e452fe97cdc93d26c720ab6b805f6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "23e1d1bc27f6de57e323d232d44c909fb41ee2ebfff28b82ca8cae6947866ea7"); + + { + const auto decode = Cbor::Decode(encoded); + ASSERT_TRUE(decode.isValid()); + EXPECT_EQ(decode.dumpToString(), "[{0: [[h\"9b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e\", 0], [h\"9b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e\", 1]], 1: [[h\"01df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\", 28467322]], 2: 183990, 3: 69885081, 4: [[0, [0, h\"df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\"]], [2, [0, h\"df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\"], h\"7d7ac07a2f2a25b7a4db868a40720621c4939cf6aefbb9a11464f1a6\"]]}, {0: [[h\"6d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df290\", h\"5d8b21c993aec7a7bdf0c832e5688920b64b665e1e36a2e6040d6dd8ad195e7774df3c1377047737d8b676fa4115e38fbf6ef854904db6d9c8ee3e26e8561408\"], [h\"e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e06932\", h\"88a3f6387693f9077d11a6e245e024b791074bcaa26c034e687d67f3324b6f90a437d33d0343e11c7dee1a28270c223e02080e452fe97cdc93d26c720ab6b805\"]]}, null]"); + EXPECT_EQ(decode.getArrayElements().size(), 3ul); + } +} + +TEST(CardanoStaking, Withdraw_similarf48098) { + const auto privateKeyData = parse_hex(privateKeyTest1); + const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto ownAddress = AddressV3(publicKey).string(); + EXPECT_EQ(ownAddress, ownAddress1); + const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); + EXPECT_EQ(stakingAddress, stakingAddress1); + + Proto::SigningInput input; + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("7dfd2c579794314b1f84efc9db932a098e440ccefb874945591f1d4e85a9152a"); + utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_output_index(0); + utxo1->set_address(ownAddress); + utxo1->set_amount(6305913ul); + + input.add_private_key(privateKeyData.data(), privateKeyData.size()); + + input.mutable_transfer_message()->set_to_address(ownAddress); + input.mutable_transfer_message()->set_change_address(ownAddress); + input.mutable_transfer_message()->set_amount(6000000ul); // not relevant if we use MaxAmount + input.mutable_transfer_message()->set_use_max_amount(true); + input.set_ttl(71678326ul); + + // Withdraw available amount + const auto withdrawAmount = 3468ul; + input.mutable_withdraw()->set_staking_address(stakingAddress); + input.mutable_withdraw()->set_withdraw_amount(withdrawAmount); + + { + // run plan and check result + auto signer = Signer(input); + const auto plan = signer.doPlan(); + + const auto amount = 6137599ul; + const auto availAmount = 6305913ul; + EXPECT_EQ(plan.availableAmount, availAmount); + EXPECT_EQ(plan.amount, amount); + const auto fee = 171782ul; + EXPECT_EQ(plan.fee, fee); + EXPECT_EQ(plan.change, availAmount + withdrawAmount - amount - fee); + EXPECT_EQ(plan.change, 0ul); + + // perform sign with default plan + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a500818258207dfd2c579794314b1f84efc9db932a098e440ccefb874945591f1d4e85a9152a00018182583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a005da6ff021a00029f06031a0445b97605a1581de1df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b190d8ca100828258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058401ebaca2876fd17122404912a2558a98109cdf0f990a938d2917fa2c3b8c4e55e18a2cbabfa82fff03fa0d7ab8b88ca01ed18e42af3bfc4cda7f423a3aa30c00b825820e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e069325840777f04fa8f083fe562aecf78898aaaaac36e2cc6ca962f6ffb01e84a421cae1860496db79b2c5fb2879524c3d5121060b9ea1e693336230c6e5338e14c4c3303f6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "6dcf3956232953fc25b8355fb1ded1e912b5802090fd21434d789087d6329683"); + } + + // set different plan, with exact fee + const auto amount = 6137599ul; + input.mutable_plan()->set_amount(amount); + input.mutable_plan()->set_available_amount(6305913ul); + input.mutable_plan()->set_fee(171782ul); + input.mutable_plan()->set_change(0); + *(input.mutable_plan()->add_utxos()) = input.utxos(0); + input.mutable_plan()->set_error(Common::Proto::OK); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + // Similar to (but with different key): https://cardanoscan.io/transaction/f480985662886e419a22673d31944455ab8891a80940bf392a37d9288ea9cf01?tab=withdrawals + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a500818258207dfd2c579794314b1f84efc9db932a098e440ccefb874945591f1d4e85a9152a00018182583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a005da6ff021a00029f06031a0445b97605a1581de1df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b190d8ca100828258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058401ebaca2876fd17122404912a2558a98109cdf0f990a938d2917fa2c3b8c4e55e18a2cbabfa82fff03fa0d7ab8b88ca01ed18e42af3bfc4cda7f423a3aa30c00b825820e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e069325840777f04fa8f083fe562aecf78898aaaaac36e2cc6ca962f6ffb01e84a421cae1860496db79b2c5fb2879524c3d5121060b9ea1e693336230c6e5338e14c4c3303f6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "6dcf3956232953fc25b8355fb1ded1e912b5802090fd21434d789087d6329683"); + + { + const auto decode = Cbor::Decode(encoded); + ASSERT_TRUE(decode.isValid()); + EXPECT_EQ(decode.dumpToString(), "[{0: [[h\"7dfd2c579794314b1f84efc9db932a098e440ccefb874945591f1d4e85a9152a\", 0]], 1: [[h\"01df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\", 6137599]], 2: 171782, 3: 71678326, 5: {h\"e1df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\": 3468}}, {0: [[h\"6d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df290\", h\"1ebaca2876fd17122404912a2558a98109cdf0f990a938d2917fa2c3b8c4e55e18a2cbabfa82fff03fa0d7ab8b88ca01ed18e42af3bfc4cda7f423a3aa30c00b\"], [h\"e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e06932\", h\"777f04fa8f083fe562aecf78898aaaaac36e2cc6ca962f6ffb01e84a421cae1860496db79b2c5fb2879524c3d5121060b9ea1e693336230c6e5338e14c4c3303\"]]}, null]"); + + EXPECT_EQ(decode.getArrayElements().size(), 3ul); + } +} + +} // namespace TW::Cardano::tests diff --git a/tests/chains/Cardano/TWCardanoAddressTests.cpp b/tests/chains/Cardano/TWCardanoAddressTests.cpp new file mode 100644 index 00000000000..f610ffd1bba --- /dev/null +++ b/tests/chains/Cardano/TWCardanoAddressTests.cpp @@ -0,0 +1,74 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include +#include +#include +#include +#include +#include +#include +#include "TestUtilities.h" +#include "PrivateKey.h" + +#include + +TEST(TWCardano, AddressFromPublicKey) { + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA( + "b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71effbf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4" + "639aadd8b6499ae39b78018b79255fbd8f585cbda9cbb9e907a72af86afb7a05d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7bed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a" + ).get())); + ASSERT_NE(nullptr, privateKey.get()); + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519Cardano(privateKey.get())); + ASSERT_NE(nullptr, publicKey.get()); + ASSERT_EQ(128ul, publicKey.get()->impl.bytes.size()); + auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeCardano)); + auto addressString = WRAPS(TWAnyAddressDescription(address.get())); + assertStringsEqual(addressString, "addr1qx4z6twzknkkux0hhp0kq6hvdfutczp56g56y5em8r8mgvxalp7nkkk25vuspleke2zltaetmlwrfxv7t049cq9jmwjswmfw6t"); + + auto address2 = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(STRING("addr1qx4z6twzknkkux0hhp0kq6hvdfutczp56g56y5em8r8mgvxalp7nkkk25vuspleke2zltaetmlwrfxv7t049cq9jmwjswmfw6t").get(), TWCoinTypeCardano)); + ASSERT_NE(nullptr, address2.get()); + auto address2String = WRAPS(TWAnyAddressDescription(address2.get())); + assertStringsEqual(address2String, "addr1qx4z6twzknkkux0hhp0kq6hvdfutczp56g56y5em8r8mgvxalp7nkkk25vuspleke2zltaetmlwrfxv7t049cq9jmwjswmfw6t"); + + ASSERT_TRUE(TWAnyAddressEqual(address.get(), address2.get())); +} + +TEST(TWCardano, AddressFromWallet) { + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic( + STRING("cost dash dress stove morning robust group affair stomach vacant route volume yellow salute laugh").get(), + STRING("").get() + )); + auto privateKey = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeCardano)); + auto privateKeyData = WRAPD(TWPrivateKeyData(privateKey.get())); + EXPECT_EQ(TWDataSize(privateKeyData.get()), 192ul); + + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519Cardano(privateKey.get())); + auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); + EXPECT_EQ(TWDataSize(publicKeyData.get()), 128ul); + assertHexEqual(publicKeyData, "fafa7eb4146220db67156a03a5f7a79c666df83eb31abbfbe77c85e06d40da3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26faf4b8d5201961e68f2e177ba594101f513ee70fe70a41324e8ea8eb787ffda6f4bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276"); + + auto address = WRAPS(TWCoinTypeDeriveAddress(TWCoinTypeCardano, privateKey.get())); + assertStringsEqual(address, "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); +} + +/* +TEST(TWCardano, GetStakingKey) { + { + auto stakingAddress = WRAPS(TWCardanoGetStakingAddress(STRING("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23").get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(stakingAddress.get())), "stake1u80jysjtdzqt88jt4jx93h5lumfr67d273r4vwyasfa2pxcwxllmx"); + } + { // negative case: cannot get staking address from non-base address + auto stakingAddress = WRAPS(TWCardanoGetStakingAddress(STRING("stake1u95zuevxqjvpdh83r08ywq9xal6nxl48fgm0wvngyenvs4qh0hqf9").get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(stakingAddress.get())), ""); + } + { // negative case: cannot get staking address from invalid address, should not throw + auto stakingAddress = WRAPS(TWCardanoGetStakingAddress(STRING("__THIS_IS_NOT_A_VALID_CARDANO_ADDRESS__").get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(stakingAddress.get())), ""); + } +} +*/ diff --git a/tests/Cardano/TWCoinTypeTests.cpp b/tests/chains/Cardano/TWCoinTypeTests.cpp similarity index 81% rename from tests/Cardano/TWCoinTypeTests.cpp rename to tests/chains/Cardano/TWCoinTypeTests.cpp index bf21fe8c6e0..33f8c9e197d 100644 --- a/tests/Cardano/TWCoinTypeTests.cpp +++ b/tests/chains/Cardano/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -27,8 +27,8 @@ TEST(TWCardanoCoinType, TWCoinType) { ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeCardano)); ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeCardano)); assertStringsEqual(symbol, "ADA"); - assertStringsEqual(txUrl, "https://shelleyexplorer.cardano.org/tx/b7a6c5cadab0f64bdc89c77ee4a351463aba5c33f2cef6bbd6542a74a90a3af3"); - assertStringsEqual(accUrl, "https://shelleyexplorer.cardano.org/address/DdzFFzCqrhstpwKc8WMvPwwBb5oabcTW9zc5ykA37wJR4tYQucvsR9dXb2kEGNXkFJz2PtrpzfRiZkx8R1iNo8NYqdsukVmv7EAybFwC"); + assertStringsEqual(txUrl, "https://cardanoscan.io/transaction/b7a6c5cadab0f64bdc89c77ee4a351463aba5c33f2cef6bbd6542a74a90a3af3"); + assertStringsEqual(accUrl, "https://cardanoscan.io/address/DdzFFzCqrhstpwKc8WMvPwwBb5oabcTW9zc5ykA37wJR4tYQucvsR9dXb2kEGNXkFJz2PtrpzfRiZkx8R1iNo8NYqdsukVmv7EAybFwC"); assertStringsEqual(id, "cardano"); assertStringsEqual(name, "Cardano"); } diff --git a/tests/chains/Cardano/TransactionTests.cpp b/tests/chains/Cardano/TransactionTests.cpp new file mode 100644 index 00000000000..702a32ce77a --- /dev/null +++ b/tests/chains/Cardano/TransactionTests.cpp @@ -0,0 +1,126 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Cardano/Transaction.h" +#include "Cardano/AddressV3.h" +#include + +#include "HexCoding.h" +#include "Cbor.h" + +#include + + +namespace TW::Cardano { + +Transaction createTx() { + Transaction tx; + tx.inputs.emplace_back(parse_hex("f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767"), 1); + tx.inputs.emplace_back(parse_hex("554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af0"), 0); + tx.outputs.emplace_back( + AddressV3("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23").data(), + 2000000); + tx.outputs.emplace_back( + AddressV3("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5").data(), + 16749189); + tx.fee = 165555; + tx.ttl = 53333345; + return tx; +} + +TEST(CardanoTransaction, Encode) { + const Transaction tx = createTx(); + + const auto encoded = tx.encode(); + EXPECT_EQ(hex(encoded), "a40082825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701825820554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af000018282583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a001e848082583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd51a00ff9285021a000286b3031a032dcd61"); + + { + const auto decode = Cbor::Decode(encoded); + ASSERT_TRUE(decode.isValid()); + EXPECT_EQ(decode.getMapElements().size(), 4ul); + EXPECT_EQ(decode.dumpToString(), "{0: [[h\"f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767\", 1], [h\"554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af0\", 0]], 1: [[h\"01df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\", 2000000], [h\"01558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd5\", 16749189]], 2: 165555, 3: 53333345}"); + } +} + +TEST(CardanoTransaction, GetId) { + const Transaction tx = createTx(); + + const auto txid = tx.getId(); + EXPECT_EQ(hex(txid), "cc262713a3e15a0fa245b062f33ffc6c2aa5a64c3ae7bfa793414069914e1bbf"); +} + +TEST(CardanoTransaction, minAdaAmount) { + const auto policyId = "9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77"; + + { // ADA only + const auto tb = TokenBundle(); + EXPECT_EQ(tb.minAdaAmount(), 1000000ul); + } + { // 1 policyId, 1 6-char asset name + const auto tb = TokenBundle({TokenAmount(policyId, "TOKEN1", 0)}); + EXPECT_EQ(tb.minAdaAmount(), 1444443ul); + } + { // 2 policyId, 2 4-char asset names + auto tb = TokenBundle(); + tb.add(TokenAmount("012345678901234567890POLICY1", "TOK1", 20)); + tb.add(TokenAmount("012345678901234567890POLICY2", "TOK2", 20)); + EXPECT_EQ(tb.minAdaAmount(), 1629628ul); + } + { // 10 policyId, 10 6-char asset names + auto tb = TokenBundle(); + for (auto i = 0; i < 10; ++i) { + std::string policyId1 = +"012345678901234567890123456" + std::to_string(i); + std::string name = "ASSET" + std::to_string(i); + tb.add(TokenAmount(policyId1, name, 0)); + } + EXPECT_EQ(tb.minAdaAmount(), 3370367ul); + } + + EXPECT_EQ(TokenBundle::minAdaAmountHelper(0, 0, 0), 1000000ul); // ADA only + EXPECT_EQ(TokenBundle::minAdaAmountHelper(1, 0, 0), 1370369ul); // 1 policyId, no asset name + EXPECT_EQ(TokenBundle::minAdaAmountHelper(1, 1, 1), 1444443ul); // 1 policyId, 1 1-char asset name + EXPECT_EQ(TokenBundle::minAdaAmountHelper(1, 1, 6), 1444443ul); // 1 policyId, 1 6-char asset name + EXPECT_EQ(TokenBundle::minAdaAmountHelper(1, 1, 32), 1555554ul); // 1 policyId, 1 32-char asset name + EXPECT_EQ(TokenBundle::minAdaAmountHelper(1, 110, 110 * 32), 23777754ul); // 1 policyId, 110 32-char asset name + EXPECT_EQ(TokenBundle::minAdaAmountHelper(2, 2, 8), 1629628ul); // 2 policyId, 2 4-char asset names + EXPECT_EQ(TokenBundle::minAdaAmountHelper(3, 5, 20), 1999998ul); // 3 policyId, 5 4-char asset names + EXPECT_EQ(TokenBundle::minAdaAmountHelper(10, 10, 10 * 6), 3370367ul); // 10 policyId, 10 6-char asset names + EXPECT_EQ(TokenBundle::minAdaAmountHelper(60, 60, 60 * 32), 21222201ul); // 60 policyId, 60 32-char asset names +} + +TEST(CardanoTransaction, getPolicyIDs) { + const auto policyId1 = "012345678901234567890POLICY1"; + const auto policyId2 = "012345678901234567890POLICY2"; + const auto tb = TokenBundle({ + TokenAmount(policyId1, "TOK1", 10), + TokenAmount(policyId2, "TOK2", 20), + TokenAmount(policyId2, "TOK3", 30), // duplicate policyId + }); + ASSERT_EQ(tb.getPolicyIds().size(), 2ul); + EXPECT_TRUE(tb.getPolicyIds().contains(policyId1)); + EXPECT_TRUE(tb.getPolicyIds().contains(policyId2)); + + EXPECT_EQ(tb.getByPolicyId(policyId1).size(), 1ul); + EXPECT_EQ(tb.getByPolicyId(policyId2).size(), 2ul); +} + +TEST(TWCardanoTransaction, minAdaAmount) { + { // ADA-only + const auto bundleProto = TokenBundle().toProto(); + const auto bundleProtoData = data(bundleProto.SerializeAsString()); + EXPECT_EQ(TWCardanoMinAdaAmount(&bundleProtoData), 1000000ul); + } + { // 2 policyId, 2 4-char asset names + auto bundle = TokenBundle(); + bundle.add(TokenAmount("012345678901234567890POLICY1", "TOK1", 20)); + bundle.add(TokenAmount("012345678901234567890POLICY2", "TOK2", 20)); + const auto bundleProto = bundle.toProto(); + const auto bundleProtoData = data(bundleProto.SerializeAsString()); + EXPECT_EQ(TWCardanoMinAdaAmount(&bundleProtoData), 1629628ul); + } +} + +} // namespace TW::Cardano diff --git a/tests/Celo/TWCoinTypeTests.cpp b/tests/chains/Celo/TWCoinTypeTests.cpp similarity index 97% rename from tests/Celo/TWCoinTypeTests.cpp rename to tests/chains/Celo/TWCoinTypeTests.cpp index 7a66fc66eda..35fd5a76d73 100644 --- a/tests/Celo/TWCoinTypeTests.cpp +++ b/tests/chains/Celo/TWCoinTypeTests.cpp @@ -5,7 +5,7 @@ // file LICENSE at the root of the source code distribution tree. // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Cosmos/AddressTests.cpp b/tests/chains/Cosmos/AddressTests.cpp similarity index 100% rename from tests/Cosmos/AddressTests.cpp rename to tests/chains/Cosmos/AddressTests.cpp diff --git a/tests/chains/Cosmos/Protobuf/.gitignore b/tests/chains/Cosmos/Protobuf/.gitignore new file mode 100644 index 00000000000..c96d61208c0 --- /dev/null +++ b/tests/chains/Cosmos/Protobuf/.gitignore @@ -0,0 +1,3 @@ +*.cc +*.h + diff --git a/tests/chains/Cosmos/Protobuf/Article.proto b/tests/chains/Cosmos/Protobuf/Article.proto new file mode 100644 index 00000000000..d6f8ade819e --- /dev/null +++ b/tests/chains/Cosmos/Protobuf/Article.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; +package blog; + +enum Type { + TYPE_UNSPECIFIED = 0; + IMAGES = 1; + NEWS = 2; +}; + +enum Review { + REVIEW_UNSPECIFIED = 0; + ACCEPTED = 1; + REJECTED = 2; +}; + +message Article { + string title = 1; + string description = 2; + uint64 created = 3; + uint64 updated = 4; + bool public = 5; + bool promoted = 6; + Type type = 7; + Review review = 8; + repeated string comments = 9; + repeated string backlinks = 10; +}; diff --git a/tests/chains/Cosmos/ProtobufTests.cpp b/tests/chains/Cosmos/ProtobufTests.cpp new file mode 100644 index 00000000000..89b36346ac5 --- /dev/null +++ b/tests/chains/Cosmos/ProtobufTests.cpp @@ -0,0 +1,67 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Base64.h" +#include "Cosmos/Address.h" +#include "Cosmos/Protobuf/authz_tx.pb.h" +#include "Cosmos/Protobuf/bank_tx.pb.h" +#include "Cosmos/Protobuf/tx.pb.h" +#include "Data.h" +#include "HexCoding.h" + +#include "Protobuf/Article.pb.h" +#include "TestUtilities.h" + +#include +#include + +#include + +namespace TW::Cosmos::tests { + +using json = nlohmann::json; + +TEST(CosmosProtobuf, SendMsg) { + auto msgSend = cosmos::bank::v1beta1::MsgSend(); + msgSend.set_from_address("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); + msgSend.set_to_address("cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"); + auto coin = msgSend.add_amount(); + coin->set_denom("muon"); + coin->set_amount("1"); + + auto txBody = cosmos::TxBody(); + txBody.add_messages()->PackFrom(msgSend); + txBody.set_memo(""); + txBody.set_timeout_height(0); + + const auto serialized = data(txBody.SerializeAsString()); + EXPECT_EQ(hex(serialized), "0a9c010a2f747970652e676f6f676c65617069732e636f6d2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412690a2d636f736d6f733168736b366a727979716a6668703564686335357463396a74636b796778306570683664643032122d636f736d6f73317a743530617a7570616e716c66616d356166687633686578777975746e756b656834633537331a090a046d756f6e120131"); + EXPECT_EQ(Base64::encode(serialized), "CpwBCi90eXBlLmdvb2dsZWFwaXMuY29tL2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZBJpCi1jb3Ntb3MxaHNrNmpyeXlxamZocDVkaGM1NXRjOWp0Y2t5Z3gwZXBoNmRkMDISLWNvc21vczF6dDUwYXp1cGFucWxmYW01YWZodjNoZXh3eXV0bnVrZWg0YzU3MxoJCgRtdW9uEgEx"); + + std::string json; + google::protobuf::util::MessageToJsonString(txBody, &json); + assertJSONEqual(json, R"({"messages":[{"@type":"type.googleapis.com/cosmos.bank.v1beta1.MsgSend","amount":[{"amount":"1","denom":"muon"}],"fromAddress":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","toAddress":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}]})"); +} + +TEST(CosmosProtobuf, DeterministicSerialization_Article) { + // https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-027-deterministic-protobuf-serialization.md + auto article = blog::Article(); + article.set_title("The world needs change 🌳"); + article.set_description(""); + article.set_created(1596806111080); + article.set_updated(0); + article.set_public_(true); + article.set_promoted(false); + article.set_type(blog::NEWS); + article.set_review(blog::REVIEW_UNSPECIFIED); + *article.add_comments() = "Nice one"; + *article.add_comments() = "Thank you"; + + const auto serialized = data(article.SerializeAsString()); + EXPECT_EQ(hex(serialized), "0a1b54686520776f726c64206e65656473206368616e676520f09f8cb318e8bebec8bc2e280138024a084e696365206f6e654a095468616e6b20796f75"); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/SignerTests.cpp b/tests/chains/Cosmos/SignerTests.cpp new file mode 100644 index 00000000000..d9202607cc0 --- /dev/null +++ b/tests/chains/Cosmos/SignerTests.cpp @@ -0,0 +1,325 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" +#include "Base64.h" +#include "proto/Cosmos.pb.h" +#include "Cosmos/Address.h" +#include "Cosmos/Signer.h" +#include "TestUtilities.h" +#include "Cosmos/Protobuf/bank_tx.pb.h" + +#include +#include + +namespace TW::Cosmos::tests { + +TEST(CosmosSigner, SignTxProtobuf) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(1037); + input.set_chain_id("gaia-13003"); + input.set_memo(""); + input.set_sequence(8); + + auto fromAddress = Address("cosmos", parse_hex("BC2DA90C84049370D1B7C528BC164BC588833F21")); + auto toAddress = Address("cosmos", parse_hex("12E8FE8B81ECC1F4F774EA6EC8DF267138B9F2D9")); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address(fromAddress.string()); + message.set_to_address(toAddress.string()); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("muon"); + amountOfTx->set_amount("1"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("muon"); + amountOfFee->set_amount("200"); + + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + + EXPECT_EQ(R"({"signingMode":"Protobuf","accountNumber":"1037","chainId":"gaia-13003","fee":{"amounts":[{"denom":"muon","amount":"200"}],"gas":"200000"},"sequence":"8","messages":[{"sendCoinsMessage":{"fromAddress":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","toAddress":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573","amounts":[{"denom":"muon","amount":"1"}]}}]})", json); + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeCosmos); + + assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); + EXPECT_EQ(hex(output.signature()), "f9e1f4001657a42009c4eb6859625d2e41e961fc72efd2842909c898e439fc1f549916e4ecac676ee353c7d54c5ae30a29b4210b8bff0ebfdcb375e105002f47"); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error(), ""); +} + +TEST(CosmosSigner, SignProtobuf_ErrorMissingMessage) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(1037); + input.set_chain_id("gaia-13003"); + input.set_memo(""); + input.set_sequence(8); + + auto fromAddress = Address("cosmos", parse_hex("BC2DA90C84049370D1B7C528BC164BC588833F21")); + auto toAddress = Address("cosmos", parse_hex("12E8FE8B81ECC1F4F774EA6EC8DF267138B9F2D9")); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("muon"); + amountOfFee->set_amount("200"); + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeCosmos); + + EXPECT_EQ(output.error(), "Error: No message found"); + EXPECT_EQ(output.serialized(), ""); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(hex(output.signature()), ""); +} + +TEST(CosmosSigner, SignTxJson) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::JSON); // obsolete + input.set_account_number(1037); + input.set_chain_id("gaia-13003"); + input.set_memo(""); + input.set_sequence(8); + + auto fromAddress = Address("cosmos", parse_hex("BC2DA90C84049370D1B7C528BC164BC588833F21")); + auto toAddress = Address("cosmos", parse_hex("12E8FE8B81ECC1F4F774EA6EC8DF267138B9F2D9")); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address(fromAddress.string()); + message.set_to_address(toAddress.string()); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("muon"); + amountOfTx->set_amount("1"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("muon"); + amountOfFee->set_amount("200"); + + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + + EXPECT_EQ(R"({"accountNumber":"1037","chainId":"gaia-13003","fee":{"amounts":[{"denom":"muon","amount":"200"}],"gas":"200000"},"sequence":"8","messages":[{"sendCoinsMessage":{"fromAddress":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","toAddress":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573","amounts":[{"denom":"muon","amount":"1"}]}}]})", json); + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeCosmos); + + // the sample tx on testnet + // https://hubble.figment.network/chains/gaia-13003/blocks/142933/transactions/3A9206598C3D2E75A5EC074FD33EA53EB18EC729357F0965971C1C51F812AEA3?format=json + EXPECT_EQ(R"({"mode":"block","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"muon"}],"from_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","to_address":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"/D74mdIGyIB3/sQvIboLTfS9P9EV/fYGrgHZE2/vNj9X6eM6e57G3atljNB+PABnRw3pTk51uXmhCFop8O/ZJg=="}]}})", output.json()); + + EXPECT_EQ(hex(output.signature()), "fc3ef899d206c88077fec42f21ba0b4df4bd3fd115fdf606ae01d9136fef363f57e9e33a7b9ec6ddab658cd07e3c0067470de94e4e75b979a1085a29f0efd926"); + EXPECT_EQ(output.serialized(), ""); + EXPECT_EQ(output.error(), ""); +} + +TEST(CosmosSigner, SignTxJson_WithMode) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::JSON); // obsolete + input.set_account_number(1037); + input.set_chain_id("gaia-13003"); + input.set_memo(""); + input.set_sequence(8); + input.set_mode(Proto::BroadcastMode::ASYNC); + + auto fromAddress = Address("cosmos", parse_hex("BC2DA90C84049370D1B7C528BC164BC588833F21")); + auto toAddress = Address("cosmos", parse_hex("12E8FE8B81ECC1F4F774EA6EC8DF267138B9F2D9")); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address(fromAddress.string()); + message.set_to_address(toAddress.string()); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("muon"); + amountOfTx->set_amount("1"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("muon"); + amountOfFee->set_amount("200"); + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + { + auto output = Signer::sign(input, TWCoinTypeCosmos); + EXPECT_EQ(R"({"mode":"async","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"muon"}],"from_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","to_address":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"/D74mdIGyIB3/sQvIboLTfS9P9EV/fYGrgHZE2/vNj9X6eM6e57G3atljNB+PABnRw3pTk51uXmhCFop8O/ZJg=="}]}})", output.json()); + EXPECT_EQ(output.error(), ""); + } + input.set_mode(Proto::BroadcastMode::SYNC); + { + auto output = Signer::sign(input, TWCoinTypeCosmos); + EXPECT_EQ(R"({"mode":"sync","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"muon"}],"from_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","to_address":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"/D74mdIGyIB3/sQvIboLTfS9P9EV/fYGrgHZE2/vNj9X6eM6e57G3atljNB+PABnRw3pTk51uXmhCFop8O/ZJg=="}]}})", output.json()); + EXPECT_EQ(output.error(), ""); + } +} + +TEST(CosmosSigner, SignIbcTransferProtobuf_817101) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(546179); + input.set_chain_id("cosmoshub-4"); + input.set_sequence(2); + + Address fromAddress; + EXPECT_TRUE(Address::decode("cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx", fromAddress)); + Address toAddress; + EXPECT_TRUE(Address::decode("osmo18s0hdnsllgcclweu9aymw4ngktr2k0rkvn7jmn", toAddress)); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_transfer_tokens_message(); + message.set_source_port("transfer"); + message.set_source_channel("channel-141"); + message.set_sender(fromAddress.string()); + message.set_receiver(toAddress.string()); + message.mutable_token()->set_denom("uatom"); + message.mutable_token()->set_amount("100000"); // 0.1 ATOM + message.mutable_timeout_height()->set_revision_number(1); + message.mutable_timeout_height()->set_revision_height(8800000); + + auto& fee = *input.mutable_fee(); + fee.set_gas(500000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uatom"); + amountOfFee->set_amount("12500"); + + auto privateKey = parse_hex("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af"); + EXPECT_EQ(Cosmos::Address(TWCoinTypeCosmos, PrivateKey(privateKey).getPublicKey(TWPublicKeyTypeSECP256k1)).string(), "cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeCosmos); + + // real-world tx: https://www.mintscan.io/cosmos/txs/817101F3D96314AD028733248B28BAFAD535024D7D2C8875D3FE31DC159F096B + // curl -H 'Content-Type: application/json' --data-binary '{"tx_bytes": "Cr4BCr...1yKOU=", "mode": "BROADCAST_MODE_BLOCK"}' https://api.cosmos.network/cosmos/tx/v1beta1/txs + // also similar TX: BCDAC36B605576C8182C2829C808B30A69CAD4959D5ED1E6FF9984ABF280D603 + assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"Cr4BCrsBCikvaWJjLmFwcGxpY2F0aW9ucy50cmFuc2Zlci52MS5Nc2dUcmFuc2ZlchKNAQoIdHJhbnNmZXISC2NoYW5uZWwtMTQxGg8KBXVhdG9tEgYxMDAwMDAiLWNvc21vczFta3k2OWNuOGVrdHd5MDg0NXZlYzl1cHNkcGhrdHh0MDNna3dseCorb3NtbzE4czBoZG5zbGxnY2Nsd2V1OWF5bXc0bmdrdHIyazBya3ZuN2ptbjIHCAEQgI6ZBBJoClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEC7O9c5DejAsZ/lUaN5LMfNukR9GfX5qUrQcHhPh1WNkkSBAoCCAEYAhIUCg4KBXVhdG9tEgUxMjUwMBCgwh4aQK0HIWdFMk+C6Gi1KG/vELe1ffcc1aEWUIqz2t/ZhwqNNHxUUSp27wteiugHEMVTEIOBhs84t2gIcT/nD/1yKOU=\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); + EXPECT_EQ(hex(output.signature()), "ad07216745324f82e868b5286fef10b7b57df71cd5a116508ab3dadfd9870a8d347c54512a76ef0b5e8ae80710c55310838186cf38b76808713fe70ffd7228e5"); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error(), ""); +} + +TEST(CosmosSigner, SignDirect1) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(1037); + input.set_chain_id("gaia-13003"); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_sign_direct_message(); + const auto bodyBytes = parse_hex("0a89010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412690a2d636f736d6f733168736b366a727979716a6668703564686335357463396a74636b796778306570683664643032122d636f736d6f73317a743530617a7570616e716c66616d356166687633686578777975746e756b656834633537331a090a046d756f6e120131"); + message.set_body_bytes(bodyBytes.data(), bodyBytes.size()); + const auto authInfoBytes = parse_hex("0a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a210257286ec3f37d33557bbbaa000b27744ac9023aa9967cae75a181d1ff91fa9dc512040a020801180812110a0b0a046d756f6e120332303010c09a0c"); + message.set_auth_info_bytes(authInfoBytes.data(), authInfoBytes.size()); + + { + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + EXPECT_EQ(R"({"signingMode":"Protobuf","accountNumber":"1037","chainId":"gaia-13003","messages":[{"signDirectMessage":{"bodyBytes":"CokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATE=","authInfoBytes":"ClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECVyhuw/N9M1V7u6oACyd0SskCOqmWfK51oYHR/5H6ncUSBAoCCAEYCBIRCgsKBG11b24SAzIwMBDAmgw="}}]})", json); + } + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeCosmos); + + assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); + EXPECT_EQ(hex(output.signature()), "f9e1f4001657a42009c4eb6859625d2e41e961fc72efd2842909c898e439fc1f549916e4ecac676ee353c7d54c5ae30a29b4210b8bff0ebfdcb375e105002f47"); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error(), ""); +} + +TEST(CosmosSigner, SignDirect_0a90010a) { + const auto bodyBytes = parse_hex("0a90010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412700a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d120731323334353637"); + { // verify contents of body + auto msgSend = cosmos::bank::v1beta1::MsgSend(); + msgSend.set_from_address("cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6"); + msgSend.set_to_address("cosmos1qypqxpq9qcrsszg2pvxq6rs0zqg3yyc5lzv7xu"); + auto amount = msgSend.add_amount(); + amount->set_denom("ucosm"); + amount->set_amount("1234567"); + const auto msgSendSer = msgSend.SerializeAsString(); + const auto bodyBytes1 = data(msgSendSer); + ASSERT_EQ(hex(bodyBytes1), "0a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d120731323334353637"); + const auto prefix = "/cosmos.bank.v1beta1.MsgSend"; + const auto bodyBytes2 = parse_hex("0a90010a1c" + hex(data(prefix)) + "1270" + hex(bodyBytes1)); + ASSERT_EQ(hex(bodyBytes2), hex(bodyBytes)); + } + + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(1); + input.set_chain_id("cosmoshub-4"); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_sign_direct_message(); + message.set_body_bytes(bodyBytes.data(), bodyBytes.size()); + const auto authInfoBytes = parse_hex("0a0a0a0012040a020801180112130a0d0a0575636f736d12043230303010c09a0c"); + message.set_auth_info_bytes(authInfoBytes.data(), authInfoBytes.size()); + + { + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + EXPECT_EQ(R"({"signingMode":"Protobuf","accountNumber":"1","chainId":"cosmoshub-4","messages":[{"signDirectMessage":{"bodyBytes":"CpABChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEnAKLWNvc21vczFwa3B0cmU3ZmRrbDZnZnJ6bGVzamp2aHhobGMzcjRnbW1rOHJzNhItY29zbW9zMXF5cHF4cHE5cWNyc3N6ZzJwdnhxNnJzMHpxZzN5eWM1bHp2N3h1GhAKBXVjb3NtEgcxMjM0NTY3","authInfoBytes":"CgoKABIECgIIARgBEhMKDQoFdWNvc20SBDIwMDAQwJoM"}}]})", json); + } + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeCosmos); + + assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"CpMBCpABChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEnAKLWNvc21vczFwa3B0cmU3ZmRrbDZnZnJ6bGVzamp2aHhobGMzcjRnbW1rOHJzNhItY29zbW9zMXF5cHF4cHE5cWNyc3N6ZzJwdnhxNnJzMHpxZzN5eWM1bHp2N3h1GhAKBXVjb3NtEgcxMjM0NTY3EiEKCgoAEgQKAggBGAESEwoNCgV1Y29zbRIEMjAwMBDAmgwaQEgXmSAlm4M5bz+OX1GtvvZ3fBV2wrZrp4A/Imd55KM7ASivB/siYJegmYiOKzQ82uwoEmFalNnG2BrHHDwDR2Y=\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); + EXPECT_EQ(hex(output.signature()), "48179920259b83396f3f8e5f51adbef6777c1576c2b66ba7803f226779e4a33b0128af07fb226097a099888e2b343cdaec2812615a94d9c6d81ac71c3c034766"); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error(), ""); +} + +TEST(CosmosSigner, MsgVote) { + // Successfully broadcasted https://www.mintscan.io/cosmos/txs/2EFA054B842B1641B131137B13360F95164C6C1D51BB4A4AC6DE8F75F504AA4C + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(1366160); + input.set_chain_id("cosmoshub-4"); + input.set_memo(""); + input.set_sequence(0); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_msg_vote(); + message.set_voter("cosmos1mry47pkga5tdswtluy0m8teslpalkdq07pswu4"); + message.set_proposal_id(77); + message.set_option(TW::Cosmos::Proto::Message_VoteOption_YES); + + auto& fee = *input.mutable_fee(); + fee.set_gas(97681); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uatom"); + amountOfFee->set_amount("2418"); + + auto privateKey = parse_hex("a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeCosmos); + auto expected = R"( + {"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"ClQKUgobL2Nvc21vcy5nb3YudjFiZXRhMS5Nc2dWb3RlEjMITRItY29zbW9zMW1yeTQ3cGtnYTV0ZHN3dGx1eTBtOHRlc2xwYWxrZHEwN3Bzd3U0GAESZQpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAsv9teRyiTMiKU5gzwiD1D30MeEInSnstEep5tVQRarlEgQKAggBEhMKDQoFdWF0b20SBDI0MTgQkfsFGkA+Nb3NULc38quGC1x+8ZXry4w9mMX3IA7wUjFboTv7kVOwPlleIc8UqIsjVvKTUFnUuW8dlGQzNR1KkvbvZ1NA"})"; + assertJSONEqual(output.serialized(), expected); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/StakingTests.cpp b/tests/chains/Cosmos/StakingTests.cpp new file mode 100644 index 00000000000..0abee814e66 --- /dev/null +++ b/tests/chains/Cosmos/StakingTests.cpp @@ -0,0 +1,246 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Base64.h" +#include "Coin.h" +#include "Cosmos/Address.h" +#include "Cosmos/Signer.h" +#include "HexCoding.h" +#include "proto/Cosmos.pb.h" +#include "TestUtilities.h" +#include + +namespace TW::Cosmos::tests { + +TEST(CosmosStaking, CompoundingAuthz) { + // Successfully broadcasted https://www.mintscan.io/cosmos/txs/C4629BC7C88690518D8F448E7A8D239C9D63975B11F8E1CE2F95CC2ADA3CCF67 + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(1290826); + input.set_chain_id("cosmoshub-4"); + input.set_memo(""); + input.set_sequence(5); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_auth_grant(); + message.set_granter("cosmos13k0q0l7lg2kr32kvt7ly236ppldy8v9dzwh3gd"); + message.set_grantee("cosmos1fs7lu28hx5m9akm7rp0c2422cn8r2f7gurujhf"); + auto& grant_stake = *message.mutable_grant_stake(); + grant_stake.mutable_allow_list()->add_address("cosmosvaloper1gjtvly9lel6zskvwtvlg5vhwpu9c9waw7sxzwx"); + grant_stake.set_authorization_type(TW::Cosmos::Proto::Message_AuthorizationType_DELEGATE); + message.set_expiration(1692309600); + + auto& fee = *input.mutable_fee(); + fee.set_gas(96681); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uatom"); + amountOfFee->set_amount("2418"); + + auto privateKey = parse_hex("c7764249cdf77f8f1d840fa8af431579e5e41cf1af937e1e23afa22f3f4f0ccc"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeCosmos); + auto expected = R"( + { + "mode":"BROADCAST_MODE_BLOCK", + "tx_bytes":"CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczEzazBxMGw3bGcya3IzMmt2dDdseTIzNnBwbGR5OHY5ZHp3aDNnZBItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjoSNgo0Y29zbW9zdmFsb3BlcjFnanR2bHk5bGVsNnpza3Z3dHZsZzV2aHdwdTljOXdhdzdzeHp3eCABEgYI4LD6pgYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAUSEwoNCgV1YXRvbRIEMjQxOBCp8wUaQIFyfuijGKf87Hz61ZqxasfLI1PZnNge4RDq/tRyB/tZI6p80iGRqHecoV6+84EQkc9GTlNRQOSlApRCsivT9XI=" + })"; + assertJSONEqual(output.serialized(), expected); +} + +TEST(CosmosStaking, RevokeCompoundingAuthz) { + // Successfully broadcasted: https://www.mintscan.io/cosmos/txs/E3218F634BB6A1BE256545EBE38275D5B02D41E88F504A43F97CD9CD2B624D44 + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(1290826); + input.set_chain_id("cosmoshub-4"); + input.set_memo(""); + input.set_sequence(4); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_auth_revoke(); + message.set_grantee("cosmos1fs7lu28hx5m9akm7rp0c2422cn8r2f7gurujhf"); + message.set_granter("cosmos13k0q0l7lg2kr32kvt7ly236ppldy8v9dzwh3gd"); + message.set_msg_type_url("/cosmos.staking.v1beta1.MsgDelegate"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(87735); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uatom"); + amountOfFee->set_amount("2194"); + + auto privateKey = parse_hex("c7764249cdf77f8f1d840fa8af431579e5e41cf1af937e1e23afa22f3f4f0ccc"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeCosmos); + auto expected = R"( + { + "mode":"BROADCAST_MODE_BLOCK", + "tx_bytes":"CqoBCqcBCh8vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnUmV2b2tlEoMBCi1jb3Ntb3MxM2swcTBsN2xnMmtyMzJrdnQ3bHkyMzZwcGxkeTh2OWR6d2gzZ2QSLWNvc21vczFmczdsdTI4aHg1bTlha203cnAwYzI0MjJjbjhyMmY3Z3VydWpoZhojL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuTXNnRGVsZWdhdGUSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAQSEwoNCgV1YXRvbRIEMjE5NBC3rQUaQI7K+W7MMBoD6FbFZxRBqs9VTjErztjWTy57+fvrLaTCIZ+eBs7CuaKqfUZdSN8otjubSHVTQID3k9DpPAX0yDo=" + })"; + assertJSONEqual(output.serialized(), expected); +} + +TEST(CosmosStaking, Staking) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(1037); + input.set_chain_id("gaia-13003"); + input.set_memo(""); + input.set_sequence(7); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_stake_message(); + message.set_delegator_address("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); + message.set_validator_address("cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"); + auto& amountOfTx = *message.mutable_amount(); + amountOfTx.set_denom("muon"); + amountOfTx.set_amount("10"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(101721); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("muon"); + amountOfFee->set_amount("1018"); + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeCosmos); + + assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"CpsBCpgBCiMvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dEZWxlZ2F0ZRJxCi1jb3Ntb3MxaHNrNmpyeXlxamZocDVkaGM1NXRjOWp0Y2t5Z3gwZXBoNmRkMDISNGNvc21vc3ZhbG9wZXIxemt1cHI4M2hyemtuM3VwNWVsa3R6Y3EzdHVmdDhueHNtd2RxZ3AaCgoEbXVvbhICMTASZgpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAcSEgoMCgRtdW9uEgQxMDE4ENmaBhpA8O9Jm/kL6Za2I3poDs5vpMowYJgNvYCJBRU/vxAjs0lNZYsq40qpTbwOTbORjJA5UjQ6auc40v6uCFT4q4z+uA==\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); + EXPECT_EQ(hex(output.signature()), "f0ef499bf90be996b6237a680ece6fa4ca3060980dbd808905153fbf1023b3494d658b2ae34aa94dbc0e4db3918c903952343a6ae738d2feae0854f8ab8cfeb8"); + EXPECT_EQ(output.error(), ""); + + { // Json-serialization, for coverage (to be removed later) + input.set_signing_mode(Proto::JSON); + auto signingOutput = Signer::sign(input, TWCoinTypeCosmos); + ASSERT_EQ(hex(signingOutput.signature()), "c08bdf6c2b0b4428f37975e85d329f1cb19745b000994a743b5df81d57d573aa5f755349befcc848c1d1507818723b1288594bc91df685e89aff22e0303b4861"); + EXPECT_EQ(signingOutput.error(), ""); + EXPECT_EQ(hex(signingOutput.serialized()), ""); + } +} + +TEST(CosmosStaking, Unstaking) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(1037); + input.set_chain_id("gaia-13003"); + input.set_memo(""); + input.set_sequence(7); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_unstake_message(); + message.set_delegator_address("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); + message.set_validator_address("cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"); + auto& amountOfTx = *message.mutable_amount(); + amountOfTx.set_denom("muon"); + amountOfTx.set_amount("10"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(101721); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("muon"); + amountOfFee->set_amount("1018"); + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeCosmos); + + assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"Cp0BCpoBCiUvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dVbmRlbGVnYXRlEnEKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhI0Y29zbW9zdmFsb3BlcjF6a3VwcjgzaHJ6a24zdXA1ZWxrdHpjcTN0dWZ0OG54c213ZHFncBoKCgRtdW9uEgIxMBJmClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECVyhuw/N9M1V7u6oACyd0SskCOqmWfK51oYHR/5H6ncUSBAoCCAEYBxISCgwKBG11b24SBDEwMTgQ2ZoGGkBhlxHFnjBERxLtjLbMCKXcrDctaSZ9djtWCa3ely1bpV6m+6aAFjpr8aEZH+q2AtjJSEdgpQRJxP+9/gQsRTnZ\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); + EXPECT_EQ(hex(output.signature()), "619711c59e30444712ed8cb6cc08a5dcac372d69267d763b5609adde972d5ba55ea6fba680163a6bf1a1191feab602d8c9484760a50449c4ffbdfe042c4539d9"); + EXPECT_EQ(output.error(), ""); + + { // Json-serialization, for coverage (to be removed later) + input.set_signing_mode(Proto::JSON); + auto signingOutput = Signer::sign(input, TWCoinTypeCosmos); + ASSERT_EQ(hex(signingOutput.signature()), "8f85a9515a211881daebfb346c2beeca3ab5c2d406a9b3ad402cfddaa3d08e2b13378e13cfef8ecf1d6500fe85d0ce3e793034dd77aba90f216427807cbff79f"); + EXPECT_EQ(signingOutput.error(), ""); + EXPECT_EQ(hex(signingOutput.serialized()), ""); + } +} + +TEST(CosmosStaking, Restaking) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(1037); + input.set_chain_id("gaia-13003"); + input.set_memo(""); + input.set_sequence(7); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_restake_message(); + message.set_delegator_address("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); + message.set_validator_dst_address("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); + message.set_validator_src_address("cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"); + + auto& amountOfTx = *message.mutable_amount(); + amountOfTx.set_denom("muon"); + amountOfTx.set_amount("10"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(101721); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("muon"); + amountOfFee->set_amount("1018"); + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeCosmos); + + assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"CtIBCs8BCiovY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dCZWdpblJlZGVsZWdhdGUSoAEKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhI0Y29zbW9zdmFsb3BlcjF6a3VwcjgzaHJ6a24zdXA1ZWxrdHpjcTN0dWZ0OG54c213ZHFncBotY29zbW9zMWhzazZqcnl5cWpmaHA1ZGhjNTV0YzlqdGNreWd4MGVwaDZkZDAyIgoKBG11b24SAjEwEmYKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQJXKG7D830zVXu7qgALJ3RKyQI6qZZ8rnWhgdH/kfqdxRIECgIIARgHEhIKDAoEbXVvbhIEMTAxOBDZmgYaQJ52qO5xdtBkNUeFeWrnqUXkngyHFKCXnOPPClyVI0HrULdp5jbwGra2RujEOn4BrbFCb3JFnpc2o1iuLXbKQxg=\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); + EXPECT_EQ(hex(output.signature()), "9e76a8ee7176d064354785796ae7a945e49e0c8714a0979ce3cf0a5c952341eb50b769e636f01ab6b646e8c43a7e01adb1426f72459e9736a358ae2d76ca4318"); + EXPECT_EQ(output.error(), ""); + + { // Json-serialization, for coverage (to be removed later) + input.set_signing_mode(Proto::JSON); + auto signingOutput = Signer::sign(input, TWCoinTypeCosmos); + ASSERT_EQ(hex(signingOutput.signature()), "e64d3761bd25a28befcda80c0a0e208d024fdb0a2b89955170e65a5c5d454aba2ce81d57e01f0c126de5a59c2b58124c109560c9803d65a17a14b548dd6c50db"); + EXPECT_EQ(signingOutput.error(), ""); + EXPECT_EQ(hex(signingOutput.serialized()), ""); + } +} + +TEST(CosmosStaking, Withdraw) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(1037); + input.set_chain_id("gaia-13003"); + input.set_memo(""); + input.set_sequence(7); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_withdraw_stake_reward_message(); + message.set_delegator_address("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); + message.set_validator_address("cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(101721); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("muon"); + amountOfFee->set_amount("1018"); + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeCosmos); + + assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"CqMBCqABCjcvY29zbW9zLmRpc3RyaWJ1dGlvbi52MWJldGExLk1zZ1dpdGhkcmF3RGVsZWdhdG9yUmV3YXJkEmUKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhI0Y29zbW9zdmFsb3BlcjF6a3VwcjgzaHJ6a24zdXA1ZWxrdHpjcTN0dWZ0OG54c213ZHFncBJmClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECVyhuw/N9M1V7u6oACyd0SskCOqmWfK51oYHR/5H6ncUSBAoCCAEYBxISCgwKBG11b24SBDEwMTgQ2ZoGGkBW1Cd+0pNfMPEVXQtqG1VIijDjZP2UOiDlvUF478axnxlF8PaOAsY0S5OdUE3Wz7+nu8YVmrLZQS/8mlqLaK05\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); + EXPECT_EQ(hex(output.signature()), "56d4277ed2935f30f1155d0b6a1b55488a30e364fd943a20e5bd4178efc6b19f1945f0f68e02c6344b939d504dd6cfbfa7bbc6159ab2d9412ffc9a5a8b68ad39"); + EXPECT_EQ(output.error(), ""); + + { // Json-serialization, for coverage (to be removed later) + input.set_signing_mode(Proto::JSON); + auto signingOutput = Signer::sign(input, TWCoinTypeCosmos); + ASSERT_EQ(hex(signingOutput.signature()), "546f0d67356f6af94cfb5ab22b974e499c33123f2c2c292f4f0e64878e0e728f4643105fd771550beb3f2371f08880aaa38fa8f2334c103a779f1d82d2db98d6"); + EXPECT_EQ(signingOutput.error(), ""); + EXPECT_EQ(hex(signingOutput.serialized()), ""); + } +} + +} // namespace TW::Cosmos::tests diff --git a/tests/Cosmos/TWAnyAddressTests.cpp b/tests/chains/Cosmos/TWAnyAddressTests.cpp similarity index 95% rename from tests/Cosmos/TWAnyAddressTests.cpp rename to tests/chains/Cosmos/TWAnyAddressTests.cpp index 5605e54aef0..27559eff580 100644 --- a/tests/Cosmos/TWAnyAddressTests.cpp +++ b/tests/chains/Cosmos/TWAnyAddressTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Cosmos/TWAnySignerTests.cpp b/tests/chains/Cosmos/TWAnySignerTests.cpp similarity index 54% rename from tests/Cosmos/TWAnySignerTests.cpp rename to tests/chains/Cosmos/TWAnySignerTests.cpp index 26a69e3e71c..014bd26d4f4 100644 --- a/tests/Cosmos/TWAnySignerTests.cpp +++ b/tests/chains/Cosmos/TWAnySignerTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -7,43 +7,50 @@ #include "Cosmos/Address.h" #include "HexCoding.h" #include "proto/Cosmos.pb.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include -using namespace TW; -using namespace TW::Cosmos; +namespace TW::Cosmos::tests { TEST(TWAnySignerCosmos, SignTx) { - auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + auto privateKey = parse_hex("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af"); Proto::SigningInput input; - input.set_account_number(1037); - input.set_chain_id("gaia-13003"); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(546179); + input.set_chain_id("cosmoshub-4"); input.set_memo(""); - input.set_sequence(8); + input.set_sequence(0); input.set_private_key(privateKey.data(), privateKey.size()); - auto fromAddress = Address("cosmos", parse_hex("BC2DA90C84049370D1B7C528BC164BC588833F21")); - auto toAddress = Address("cosmos", parse_hex("12E8FE8B81ECC1F4F774EA6EC8DF267138B9F2D9")); + Address fromAddress; + EXPECT_TRUE(Address::decode("cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx", fromAddress)); + Address toAddress; + EXPECT_TRUE(Address::decode("cosmos18s0hdnsllgcclweu9aymw4ngktr2k0rkygdzdp", toAddress)); auto msg = input.add_messages(); auto& message = *msg->mutable_send_coins_message(); message.set_from_address(fromAddress.string()); message.set_to_address(toAddress.string()); auto amountOfTx = message.add_amounts(); - amountOfTx->set_denom("muon"); - amountOfTx->set_amount(1); + amountOfTx->set_denom("uatom"); + amountOfTx->set_amount("400000"); auto& fee = *input.mutable_fee(); fee.set_gas(200000); auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("muon"); - amountOfFee->set_amount(200); + amountOfFee->set_denom("uatom"); + amountOfFee->set_amount("1000"); Proto::SigningOutput output; ANY_SIGN(input, TWCoinTypeCosmos); - ASSERT_EQ(output.json(), R"({"mode":"block","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"muon"}],"from_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","to_address":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"/D74mdIGyIB3/sQvIboLTfS9P9EV/fYGrgHZE2/vNj9X6eM6e57G3atljNB+PABnRw3pTk51uXmhCFop8O/ZJg=="}]}})"); + // https://www.mintscan.io/cosmos/txs/85392373F54577562067030BF0D61596C91188AA5E6CA8FFE731BD0349296411 + // curl -H 'Content-Type: application/json' --data-binary '{"tx_bytes": "CpIBC...JXoCX", "mode": "BROADCAST_MODE_BLOCK"}' https://api.cosmos.network/cosmos/tx/v1beta1/txs + assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"CpIBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLWNvc21vczFta3k2OWNuOGVrdHd5MDg0NXZlYzl1cHNkcGhrdHh0MDNna3dseBItY29zbW9zMThzMGhkbnNsbGdjY2x3ZXU5YXltdzRuZ2t0cjJrMHJreWdkemRwGg8KBXVhdG9tEgY0MDAwMDASZQpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJEgQKAggBEhMKDQoFdWF0b20SBDEwMDAQwJoMGkCvvVE6d29P30cO9/lnXyGunWMPxNY12NuqDcCnFkNM0H4CUQdl1Gc9+ogIJbro5nyzZzlv9rl2/GsZox/JXoCX\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); + EXPECT_EQ(hex(output.signature()), "afbd513a776f4fdf470ef7f9675f21ae9d630fc4d635d8dbaa0dc0a716434cd07e02510765d4673dfa880825bae8e67cb367396ff6b976fc6b19a31fc95e8097"); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error(), ""); } TEST(TWAnySignerCosmos, SignJSON) { @@ -54,3 +61,5 @@ TEST(TWAnySignerCosmos, SignJSON) { ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeCosmos)); assertStringsEqual(result, R"({"mode":"block","tx":{"fee":{"amount":[{"amount":"5000","denom":"uatom"}],"gas":"200000"},"memo":"Testing","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"995000","denom":"uatom"}],"from_address":"cosmos1ufwv9ymhqaal6xz47n0jhzm2wf4empfqvjy575","to_address":"cosmos135qla4294zxarqhhgxsx0sw56yssa3z0f78pm0"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A6EsukEXB53GhohQVeDpxtkeH8KQIayd/Co/ApYRYkTm"},"signature":"ULEpUqNzoAnYEx2x22F3ANAiPXquAU9+mqLWoAA/ZOUGTMsdb6vryzsW6AKX2Kqj1pGNdrTcQ58Z09JPyjpgEA=="}]}})"); } + +} // namespace TW::Cosmos::tests diff --git a/tests/Cosmos/TWCoinTypeTests.cpp b/tests/chains/Cosmos/TWCoinTypeTests.cpp similarity index 65% rename from tests/Cosmos/TWCoinTypeTests.cpp rename to tests/chains/Cosmos/TWCoinTypeTests.cpp index 747ba90f2fe..228564d05d3 100644 --- a/tests/Cosmos/TWCoinTypeTests.cpp +++ b/tests/chains/Cosmos/TWCoinTypeTests.cpp @@ -8,27 +8,29 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include TEST(TWCosmosCoinType, TWCoinType) { auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeCosmos)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("541FA06FB37AC1BF61922143783DD76FECA361830F9876D0342536EE8A87A790")); auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeCosmos, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("cosmos1gu6y2a0ffteesyeyeesk23082c6998xyzmt9mz")); auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeCosmos, accId.get())); auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeCosmos)); auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeCosmos)); + const auto chainId = WRAPS(TWCoinTypeChainId(TWCoinTypeCosmos)); ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeCosmos), 6); ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeCosmos)); ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeCosmos)); ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeCosmos)); + assertStringsEqual(chainId, "cosmoshub-4"); assertStringsEqual(symbol, "ATOM"); - assertStringsEqual(txUrl, "https://www.mintscan.io/txs/t123"); - assertStringsEqual(accUrl, "https://www.mintscan.io/account/a12"); + assertStringsEqual(txUrl, "https://mintscan.io/cosmos/txs/541FA06FB37AC1BF61922143783DD76FECA361830F9876D0342536EE8A87A790"); + assertStringsEqual(accUrl, "https://mintscan.io/cosmos/account/cosmos1gu6y2a0ffteesyeyeesk23082c6998xyzmt9mz"); assertStringsEqual(id, "cosmos"); - assertStringsEqual(name, "Cosmos"); + assertStringsEqual(name, "Cosmos Hub"); } diff --git a/tests/chains/Cronos/TWAnyAddressTests.cpp b/tests/chains/Cronos/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..115c061283f --- /dev/null +++ b/tests/chains/Cronos/TWAnyAddressTests.cpp @@ -0,0 +1,21 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "TestUtilities.h" + +#include +#include + +#include + +TEST(CronosAnyAddress, Validate) { + auto string = STRING("0xEC49280228b0D05Aa8e8b756503254e1eE7835ab"); + + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeCronosChain)); + + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "ec49280228b0d05aa8e8b756503254e1ee7835ab"); +} diff --git a/tests/chains/Cronos/TWCoinTypeTests.cpp b/tests/chains/Cronos/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..b3f60a93b58 --- /dev/null +++ b/tests/chains/Cronos/TWCoinTypeTests.cpp @@ -0,0 +1,28 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "TestUtilities.h" +#include +#include + +TEST(TWCronosCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeCronosChain)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x850131282053328ad569fa91200aa970cbed7d97b52951fe8b449cca3708789e")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeCronosChain, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x44eed2bb80b688a8778173c19fe11cd6876af15a")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeCronosChain, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeCronosChain)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeCronosChain)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeCronosChain), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeCronosChain)); + + assertStringsEqual(symbol, "CRO"); + assertStringsEqual(txUrl, "https://cronoscan.com/tx/0x850131282053328ad569fa91200aa970cbed7d97b52951fe8b449cca3708789e"); + assertStringsEqual(accUrl, "https://cronoscan.com/address/0x44eed2bb80b688a8778173c19fe11cd6876af15a"); + assertStringsEqual(id, "cronos"); + assertStringsEqual(name, "Cronos Chain"); +} diff --git a/tests/CryptoOrg/AddressTests.cpp b/tests/chains/CryptoOrg/AddressTests.cpp similarity index 95% rename from tests/CryptoOrg/AddressTests.cpp rename to tests/chains/CryptoOrg/AddressTests.cpp index 833a79016fa..8bd3bfdd62f 100644 --- a/tests/CryptoOrg/AddressTests.cpp +++ b/tests/chains/CryptoOrg/AddressTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -13,8 +13,7 @@ #include #include -using namespace TW; -using namespace TW::Cosmos; +namespace TW::Cosmos::tests { TEST(CryptoorgAddress, Valid) { ASSERT_TRUE(Address::isValid(TWCoinTypeCryptoOrg, "cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0")); @@ -50,3 +49,5 @@ TEST(CryptoorgAddress, FromString) { EXPECT_EQ(address.string(), "cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0"); EXPECT_EQ(hex(address.getKeyHash()), "c2dcbc3828b42c429cedbaefa943e66679b471ed"); } + +} // namespace TW::Cosmos::tests \ No newline at end of file diff --git a/tests/CryptoOrg/SignerTests.cpp b/tests/chains/CryptoOrg/SignerTests.cpp similarity index 95% rename from tests/CryptoOrg/SignerTests.cpp rename to tests/chains/CryptoOrg/SignerTests.cpp index d3421aae2f7..bfc9dc634b5 100644 --- a/tests/CryptoOrg/SignerTests.cpp +++ b/tests/chains/CryptoOrg/SignerTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -8,15 +8,13 @@ #include "Cosmos/Signer.h" #include "Cosmos/Address.h" #include "HexCoding.h" -#include "PrivateKey.h" #include "PublicKey.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include -using namespace TW; -using namespace TW::Cosmos; +namespace TW::Cosmos::tests { TEST(CryptoorgSigner, SignTx_DDCCE4) { auto input = Cosmos::Proto::SigningInput(); @@ -30,13 +28,13 @@ TEST(CryptoorgSigner, SignTx_DDCCE4) { message.set_to_address("cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus"); auto amountOfTx = message.add_amounts(); amountOfTx->set_denom("basecro"); - amountOfTx->set_amount(100000000); + amountOfTx->set_amount("100000000"); auto& fee = *input.mutable_fee(); fee.set_gas(200000); auto amountOfFee = fee.add_amounts(); amountOfFee->set_denom("basecro"); - amountOfFee->set_amount(5000); + amountOfFee->set_amount("5000"); std::string json; google::protobuf::util::MessageToJsonString(input, &json); @@ -74,7 +72,7 @@ TEST(CryptoorgSigner, SignTx_DDCCE4) { auto privateKey = parse_hex("200e439e39cf1aad465ee3de6166247f914cbc0f823fc2dd48bf16dcd556f39d"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Cosmos::Signer::sign(input); + auto output = Cosmos::Signer::sign(input, TWCoinTypeCryptoOrg); assertJSONEqual(output.json(), R"( { @@ -127,7 +125,7 @@ TEST(CryptoorgSigner, SignJson) { auto inputJson = R"({"accountNumber":"125798","chainId":"crypto-org-chain-mainnet-1","fee":{"amounts":[{"denom":"basecro","amount":"5000"}],"gas":"200000"},"messages":[{"sendCoinsMessage":{"fromAddress":"cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0","toAddress":"cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus","amounts":[{"denom":"basecro","amount":"100000000"}]}}]})"; auto privateKey = parse_hex("200e439e39cf1aad465ee3de6166247f914cbc0f823fc2dd48bf16dcd556f39d"); - auto outputJson = Cosmos::Signer::signJSON(inputJson, privateKey); + auto outputJson = Cosmos::Signer::signJSON(inputJson, privateKey, TWCoinTypeCryptoOrg); assertJSONEqual(outputJson, R"( { @@ -171,3 +169,5 @@ TEST(CryptoorgSigner, SignJson) { } )"); } + +} // namespace TW::Cosmos::tests diff --git a/tests/CryptoOrg/TWAnyAddressTests.cpp b/tests/chains/CryptoOrg/TWAnyAddressTests.cpp similarity index 96% rename from tests/CryptoOrg/TWAnyAddressTests.cpp rename to tests/chains/CryptoOrg/TWAnyAddressTests.cpp index e9771fed810..4af5812a464 100644 --- a/tests/CryptoOrg/TWAnyAddressTests.cpp +++ b/tests/chains/CryptoOrg/TWAnyAddressTests.cpp @@ -7,7 +7,7 @@ #include #include "HexCoding.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include using namespace TW; diff --git a/tests/chains/CryptoOrg/TWAnySignerTests.cpp b/tests/chains/CryptoOrg/TWAnySignerTests.cpp new file mode 100644 index 00000000000..62519f35671 --- /dev/null +++ b/tests/chains/CryptoOrg/TWAnySignerTests.cpp @@ -0,0 +1,132 @@ +// Copyright © 2017-2021 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include "proto/Cosmos.pb.h" +#include "HexCoding.h" +#include "Base64.h" +#include "Data.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + + +const auto Address1 = "cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0"; +const auto Address2 = "cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus"; +const auto PrivateKey1 = "200e439e39cf1aad465ee3de6166247f914cbc0f823fc2dd48bf16dcd556f39d"; + +TEST(TWAnySignerCryptoorg, SignTx_Proto_BCB213) { + auto input = Cosmos::Proto::SigningInput(); + input.set_signing_mode(Cosmos::Proto::Protobuf); + input.set_account_number(125798); + input.set_sequence(2); + input.set_chain_id("crypto-org-chain-mainnet-1"); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address(Address1); + message.set_to_address(Address2); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("basecro"); + amountOfTx->set_amount("50000000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("basecro"); + amountOfFee->set_amount("5000"); + + auto privateKey = parse_hex(PrivateKey1); + input.set_private_key(privateKey.data(), privateKey.size()); + + Cosmos::Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeCryptoOrg); + + // https://crypto.org/explorer/tx/BCB213B0A121F0CF11BECCF52475F1C8328D6070F3CFDA9E14C42E6DB30E847E + // curl -H 'Content-Type: application/json' --data-binary '{"tx_bytes": "CpABC...F0SI=", "mode": "BROADCAST_MODE_BLOCK"}' https://mainnet.crypto.org:1317/cosmos/tx/v1beta1/txs + assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"CpABCo0BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm0KKmNybzFjdHd0Y3dwZ2tza3k5ODhkaHRoNmpzbHh2ZXVtZ3UwZDQ1emdmMBIqY3JvMXhwYWh5NmM3d2xkeGFjdjZsZDk5aDQzNW1odmZuc3VwMjR2Y3VzGhMKB2Jhc2Vjcm8SCDUwMDAwMDAwEmkKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQOIMbBhYj5+i+WdiIxxCEpFyNCJMHy/XsVdxiwde9Vr4xIECgIIARgCEhUKDwoHYmFzZWNybxIENTAwMBDAmgwaQAcxK9xk6r69gmz+1UWaCnYxNuXPXZdp59YcqKPJE5d6fp+IICTBOwd2rs8MiApcf8kNSrbZ6oECxcGQAdxF0SI=\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); + EXPECT_EQ(hex(output.signature()), "07312bdc64eabebd826cfed5459a0a763136e5cf5d9769e7d61ca8a3c913977a7e9f882024c13b0776aecf0c880a5c7fc90d4ab6d9ea8102c5c19001dc45d122"); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error(), ""); +} + +TEST(TWAnySignerCryptoorg, SignTx_Json_DDCCE4) { + auto input = Cosmos::Proto::SigningInput(); + input.set_signing_mode(Cosmos::Proto::JSON); // obsolete + input.set_account_number(125798); + input.set_sequence(0); + input.set_chain_id("crypto-org-chain-mainnet-1"); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address(Address1); + message.set_to_address(Address2); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("basecro"); + amountOfTx->set_amount("100000000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("basecro"); + amountOfFee->set_amount("5000"); + + auto privateKey = parse_hex(PrivateKey1); + input.set_private_key(privateKey.data(), privateKey.size()); + + Cosmos::Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeCryptoOrg); + + assertJSONEqual(output.json(), R"( + { + "mode": "block", + "tx": { + "fee": { + "amount": [ + { + "amount": "5000", + "denom": "basecro" + } + ], + "gas": "200000" + }, + "memo": "", + "msg": [ + { + "type": "cosmos-sdk/MsgSend", + "value": { + "amount": [ + { + "amount": "100000000", + "denom": "basecro" + } + ], + "from_address": "cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0", + "to_address": "cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus" + } + } + ], + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A4gxsGFiPn6L5Z2IjHEISkXI0IkwfL9exV3GLB171Wvj" + }, + "signature": "5+5rSFFg0FE9cTklQWQHNktBDJsz7UCnMSgF0t0+gYcrIhEWUyTtibXaHZQbKAAaciJ1BkHXYREjU55VswByVg==" + } + ] + } + } + )"); + EXPECT_EQ(hex(output.signature()), "e7ee6b485160d0513d713925416407364b410c9b33ed40a7312805d2dd3e81872b2211165324ed89b5da1d941b28001a7222750641d7611123539e55b3007256"); + EXPECT_EQ(output.serialized(), ""); + EXPECT_EQ(output.error(), ""); + + /// https://crypto.org/explorer/tx/DDCCE4052040B05914CADEFE78C0A75BE363AE39504E7EF6B2EDB8A9072AD44B + /// curl -H 'Content-Type: application/json' --data-binary '{"mode":"block","tx":{"fee": ... }}' https://mainnet.crypto.org:1317/txs +} diff --git a/tests/CryptoOrg/TWCoinTypeTests.cpp b/tests/chains/CryptoOrg/TWCoinTypeTests.cpp similarity index 97% rename from tests/CryptoOrg/TWCoinTypeTests.cpp rename to tests/chains/CryptoOrg/TWCoinTypeTests.cpp index a6e06751ec4..0e19d390f0b 100644 --- a/tests/CryptoOrg/TWCoinTypeTests.cpp +++ b/tests/chains/CryptoOrg/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Dash/TWCoinTypeTests.cpp b/tests/chains/Dash/TWCoinTypeTests.cpp similarity index 97% rename from tests/Dash/TWCoinTypeTests.cpp rename to tests/chains/Dash/TWCoinTypeTests.cpp index 20fa3257b39..9995965fda6 100644 --- a/tests/Dash/TWCoinTypeTests.cpp +++ b/tests/chains/Dash/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Dash/TWDashTests.cpp b/tests/chains/Dash/TWDashTests.cpp similarity index 95% rename from tests/Dash/TWDashTests.cpp rename to tests/chains/Dash/TWDashTests.cpp index c1ad9c58117..a0d86239464 100644 --- a/tests/Dash/TWDashTests.cpp +++ b/tests/chains/Dash/TWDashTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include diff --git a/tests/Decred/AddressTests.cpp b/tests/chains/Decred/AddressTests.cpp similarity index 93% rename from tests/Decred/AddressTests.cpp rename to tests/chains/Decred/AddressTests.cpp index 49f627dabe9..9629a821d21 100644 --- a/tests/Decred/AddressTests.cpp +++ b/tests/chains/Decred/AddressTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -12,8 +12,7 @@ #include -using namespace TW; -using namespace TW::Decred; +namespace TW::Decred::tests { TEST(DecredAddress, FromPublicKey) { const auto publicKey = PublicKey(parse_hex("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), TWPublicKeyTypeSECP256k1); @@ -45,3 +44,5 @@ TEST(DecredAddress, Derive) { const auto address = TW::deriveAddress(TWCoinTypeDecred, wallet.getKey(TWCoinTypeDecred, path)); ASSERT_EQ(address, "DsVMHD5D86dpRnt2GPZvv4bYUJZg6B9Pzqa"); } + +} // namespace TW::Decred::tests diff --git a/tests/Decred/SignerTests.cpp b/tests/chains/Decred/SignerTests.cpp similarity index 98% rename from tests/Decred/SignerTests.cpp rename to tests/chains/Decred/SignerTests.cpp index 8ad2081c970..85cf02ea128 100644 --- a/tests/Decred/SignerTests.cpp +++ b/tests/chains/Decred/SignerTests.cpp @@ -16,8 +16,10 @@ #include using namespace TW; -using namespace TW::Decred; +namespace TW::Decred::tests { + +// clang-format off TEST(DecredSigner, SignP2PKH) { const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); @@ -86,7 +88,7 @@ TEST(DecredSigner, SignP2PKH) { // Sign auto signer = Signer(std::move(input)); - signer.transaction = redeemTx; + signer._transaction = redeemTx; signer.txPlan.amount = 100'000'000; const auto result = signer.sign(); @@ -193,7 +195,7 @@ TEST(DecredSigner, SignP2SH) { // Sign auto signer = Signer(std::move(input)); - signer.transaction = redeemTx; + signer._transaction = redeemTx; signer.txPlan.amount = 100'000'000; const auto result = signer.sign(); @@ -273,7 +275,7 @@ TEST(DecredSigner, SignNegativeNoUtxo) { // Sign auto signer = Signer(std::move(input)); - signer.transaction = redeemTx; + signer._transaction = redeemTx; signer.txPlan.amount = 100'000'000; const auto result = signer.sign(); @@ -342,7 +344,7 @@ TEST(DecredSigner, SignP2PKH_NoPlan) { // Sign auto signer = Signer(std::move(input)); - signer.transaction = redeemTx; + signer._transaction = redeemTx; //signer.txPlan.utxos.push_back(*utxo0); //signer.txPlan.amount = 100'000'000; const auto result = signer.sign(); @@ -427,3 +429,6 @@ TEST(DecredSigning, SignP2WPKH_NegativeAddressWrongType) { ASSERT_FALSE(result) << std::to_string(result.error()); } +// clang-format on + +} // namespace TW::Decred::tests diff --git a/tests/Decred/TWAnySignerTests.cpp b/tests/chains/Decred/TWAnySignerTests.cpp similarity index 98% rename from tests/Decred/TWAnySignerTests.cpp rename to tests/chains/Decred/TWAnySignerTests.cpp index e1d08bbb221..2960a5fcb1c 100644 --- a/tests/Decred/TWAnySignerTests.cpp +++ b/tests/chains/Decred/TWAnySignerTests.cpp @@ -5,7 +5,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include "HexCoding.h" #include "proto/Bitcoin.pb.h" @@ -103,7 +103,7 @@ TEST(TWAnySignerDecred, PlanAndSign) { ANY_SIGN(input, TWCoinTypeDecred); ASSERT_EQ(output.error(), Common::Proto::OK); - ASSERT_EQ(output.encoded().size(), 251); + ASSERT_EQ(output.encoded().size(), 251ul); ASSERT_EQ(hex(output.encoded()), "0100000001fdbfe9dd703f306794a467f175be5bd9748a7925033ea1cf9889d7cf4dd1155000000000000000000002809698000000000000001976a914989b1aecabf1c24e213cc0f2d8a22ffee25dd4e188ace23bc8010000000000001976a9142a194fc92e27fef9cc2b057bc9060c580cbb484888ac000000000000000001000000000000000000000000ffffffff6a47304402203e6ee9e16d6bc36bb4242f7a4cac333a1c2a150ea16143412b88b721f6ae16bf02201019affdf815a5c22e4b0fb7e4685c4707094922d6a41354f9055d3bb0f26e630121026cc34b92cefb3a4537b3edb0b6044c04af27c01583c577823ecc69a9a21119b6"); } diff --git a/tests/Decred/TWCoinTypeTests.cpp b/tests/chains/Decred/TWCoinTypeTests.cpp similarity index 92% rename from tests/Decred/TWCoinTypeTests.cpp rename to tests/chains/Decred/TWCoinTypeTests.cpp index 1fb338d04f3..f355997d41b 100644 --- a/tests/Decred/TWCoinTypeTests.cpp +++ b/tests/chains/Decred/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -23,7 +23,7 @@ TEST(TWDecredCoinType, TWCoinType) { auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeDecred)); ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeDecred), 8); - ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeDecred)); + ASSERT_EQ(TWBlockchainDecred, TWCoinTypeBlockchain(TWCoinTypeDecred)); ASSERT_EQ(0x1a, TWCoinTypeP2shPrefix(TWCoinTypeDecred)); ASSERT_EQ(0x7, TWCoinTypeStaticPrefix(TWCoinTypeDecred)); assertStringsEqual(symbol, "DCR"); diff --git a/tests/Decred/TWDecredTests.cpp b/tests/chains/Decred/TWDecredTests.cpp similarity index 98% rename from tests/Decred/TWDecredTests.cpp rename to tests/chains/Decred/TWDecredTests.cpp index 1b05944929b..661420c887c 100644 --- a/tests/Decred/TWDecredTests.cpp +++ b/tests/chains/Decred/TWDecredTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/DigiByte/TWCoinTypeTests.cpp b/tests/chains/DigiByte/TWCoinTypeTests.cpp similarity index 97% rename from tests/DigiByte/TWCoinTypeTests.cpp rename to tests/chains/DigiByte/TWCoinTypeTests.cpp index 0140cef4893..46d2adfa5de 100644 --- a/tests/DigiByte/TWCoinTypeTests.cpp +++ b/tests/chains/DigiByte/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/DigiByte/TWDigiByteTests.cpp b/tests/chains/DigiByte/TWDigiByteTests.cpp similarity index 89% rename from tests/DigiByte/TWDigiByteTests.cpp rename to tests/chains/DigiByte/TWDigiByteTests.cpp index c135a7d662a..c824018607c 100644 --- a/tests/DigiByte/TWDigiByteTests.cpp +++ b/tests/chains/DigiByte/TWDigiByteTests.cpp @@ -1,10 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include "Bitcoin/OutPoint.h" #include "Bitcoin/TransactionBuilder.h" @@ -16,8 +16,7 @@ #include -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { TEST(DigiByteTransaction, SignTransaction) { /* @@ -71,18 +70,19 @@ TEST(DigiByteTransaction, SignTransaction) { hex(serialized), "01000000" "01" - "ea63bdc39035ebe02df7ad999581156f996303a70f9a3358811454a7ca806b96" - "00000000" - "6a" - "473044022003e9756b12ecbe5788fdb6eb4b6d7b58f9f9410df32f3047edb0dd0ebffb0d630220499d00d17e50c48b4bac6c0ce148f13bb3109a8845fa3400a2d6a57dabf2c4010121024e525e582452cece7b869532d9e354cfec58b71cbed76f7238c91274a64b2116" - "ffffffff" - "02" - "4023050600000000""19" - "76a9142d5b215a11029ee51a1dd9404d271c7e4a74f5f288ac" - "18053d0000000000""19" - "76a91447825943ca6a936b177fdc7c9dc05251640169c288ac" + "ea63bdc39035ebe02df7ad999581156f996303a70f9a3358811454a7ca806b96" "00000000" - ); + "6a" + "473044022003e9756b12ecbe5788fdb6eb4b6d7b58f9f9410df32f3047edb0dd0ebffb0d630220499d00d17e50c48b4bac6c0ce148f13bb3109a8845fa3400a2d6a57dabf2c4010121024e525e582452cece7b869532d9e354cfec58b71cbed76f7238c91274a64b2116" + "ffffffff" + "02" + "4023050600000000" + "19" + "76a9142d5b215a11029ee51a1dd9404d271c7e4a74f5f288ac" + "18053d0000000000" + "19" + "76a91447825943ca6a936b177fdc7c9dc05251640169c288ac" + "00000000"); } TEST(DigiByteTransaction, SignP2WPKH) { @@ -127,7 +127,7 @@ TEST(DigiByteTransaction, SignP2WPKH) { TEST(DigiByteTransaction, LockScripts) { // https://dgb2.trezor.io/tx/966b80caa754148158339a0fa70363996f15819599adf72de0eb3590c3bd63ea - + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("DBfCffUdSbhqKZhjuvrJ6AgvJofT4E2kp4").get(), TWCoinTypeDigiByte)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a91447825943ca6a936b177fdc7c9dc05251640169c288ac"); @@ -137,8 +137,10 @@ TEST(DigiByteTransaction, LockScripts) { assertHexEqual(scriptData2, "0014885534ab5dc680b68d95c0af49ec2acc2e9915c4"); // https://dgb2.trezor.io/tx/965eb4afcd0aa6e3f4f8fc3513ca042f09e6e2235367fa006cbd1f546c293a2a - + auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("SUngTA1vaC2E62mbnc81Mdos3TcvZHwsVo").get(), TWCoinTypeDigiByte)); auto scriptData3 = WRAPD(TWBitcoinScriptData(script3.get())); assertHexEqual(scriptData3, "a91452356ed3d2d31eb8b263ace5d164e3cf3b37096687"); } + +} // namespace TW::Bitcoin diff --git a/tests/Dogecoin/TWCoinTypeTests.cpp b/tests/chains/Dogecoin/TWCoinTypeTests.cpp similarity index 97% rename from tests/Dogecoin/TWCoinTypeTests.cpp rename to tests/chains/Dogecoin/TWCoinTypeTests.cpp index decba7e836e..3196965f758 100644 --- a/tests/Dogecoin/TWCoinTypeTests.cpp +++ b/tests/chains/Dogecoin/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Dogecoin/TWDogeTests.cpp b/tests/chains/Dogecoin/TWDogeTests.cpp similarity index 95% rename from tests/Dogecoin/TWDogeTests.cpp rename to tests/chains/Dogecoin/TWDogeTests.cpp index 76f83bdb96a..627fc78a72e 100644 --- a/tests/Dogecoin/TWDogeTests.cpp +++ b/tests/chains/Dogecoin/TWDogeTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include diff --git a/tests/HECO/TWCoinTypeTests.cpp b/tests/chains/ECO/TWCoinTypeTests.cpp similarity index 97% rename from tests/HECO/TWCoinTypeTests.cpp rename to tests/chains/ECO/TWCoinTypeTests.cpp index 3f2f2ae6b96..a72cb7b7877 100644 --- a/tests/HECO/TWCoinTypeTests.cpp +++ b/tests/chains/ECO/TWCoinTypeTests.cpp @@ -5,7 +5,7 @@ // file LICENSE at the root of the source code distribution tree. // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/chains/ECash/TWCoinTypeTests.cpp b/tests/chains/ECash/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..d8f9305bc12 --- /dev/null +++ b/tests/chains/ECash/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// Copyright © 2017-2021 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(TWECashCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeECash)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("6bc767e69cfacffd954c9e5acd178d3140bf00d094b92c6f6052b517500c553b")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeECash, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("ecash:pqnqv9lt7e5vjyp0w88zf2af0l92l8rxdg2jj94l5j")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeECash, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeECash)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeECash)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeECash), 2); + ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeECash)); + ASSERT_EQ(0x5, TWCoinTypeP2shPrefix(TWCoinTypeECash)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeECash)); + assertStringsEqual(symbol, "XEC"); + assertStringsEqual(txUrl, "https://explorer.bitcoinabc.org/tx/6bc767e69cfacffd954c9e5acd178d3140bf00d094b92c6f6052b517500c553b"); + assertStringsEqual(accUrl, "https://explorer.bitcoinabc.org/address/ecash:pqnqv9lt7e5vjyp0w88zf2af0l92l8rxdg2jj94l5j"); + assertStringsEqual(id, "ecash"); + assertStringsEqual(name, "eCash"); +} diff --git a/tests/chains/ECash/TWECashTests.cpp b/tests/chains/ECash/TWECashTests.cpp new file mode 100644 index 00000000000..f258e4a4816 --- /dev/null +++ b/tests/chains/ECash/TWECashTests.cpp @@ -0,0 +1,168 @@ +// Copyright © 2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Bitcoin/Address.h" +#include "Bitcoin/SigHashType.h" +#include "HexCoding.h" +#include "proto/Bitcoin.pb.h" +#include "TestUtilities.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace TW::Bitcoin { + +TEST(ECash, Address) { + EXPECT_TRUE(TWAnyAddressIsValid(STRING("pqx578nanz2h2estzmkr53zqdg6qt8xyqvh683mrz0").get(), TWCoinTypeECash)); +} + +TEST(ECash, ValidAddress) { + auto string = STRING("ecash:qqra3amvnyyhrltyn5h97klwe68cuw3sfcgry9hl9k"); + auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeECash)); + ASSERT_NE(address.get(), nullptr); + + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(string.get(), TWCoinTypeECash)); + ASSERT_FALSE(TWBitcoinScriptSize(script.get()) == 0); +} + +TEST(ECash, InvalidAddress) { + // Wrong checksum + EXPECT_FALSE(TWAnyAddressIsValid(STRING("pqx578nanz2h2estzmkr53zqdg6qt8xyqvffffffff").get(), TWCoinTypeECash)); + EXPECT_FALSE(TWAnyAddressIsValid(STRING("ecash:pqx578nanz2h2estzmkr53zqdg6qt8xyqvffffffff").get(), TWCoinTypeECash)); + + // Valid BCH addresses are invalid for eCash + EXPECT_TRUE(TWAnyAddressIsValid(STRING("pqx578nanz2h2estzmkr53zqdg6qt8xyqvwhn6qeyc").get(), TWCoinTypeBitcoinCash)); + EXPECT_FALSE(TWAnyAddressIsValid(STRING("pqx578nanz2h2estzmkr53zqdg6qt8xyqvwhn6qeyc").get(), TWCoinTypeECash)); + + EXPECT_TRUE(TWAnyAddressIsValid(STRING("bitcoincash:pqx578nanz2h2estzmkr53zqdg6qt8xyqvwhn6qeyc").get(), TWCoinTypeBitcoinCash)); + EXPECT_FALSE(TWAnyAddressIsValid(STRING("bitcoincash:pqx578nanz2h2estzmkr53zqdg6qt8xyqvwhn6qeyc").get(), TWCoinTypeECash)); + + // Wrong prefix + EXPECT_FALSE(TWAnyAddressIsValid(STRING("fcash:pqx578nanz2h2estzmkr53zqdg6qt8xyqvh683mrz0").get(), TWCoinTypeECash)); + + // Wrong base 32 (characters o, i) + EXPECT_FALSE(TWAnyAddressIsValid(STRING("poi578nanz2h2estzmkr53zqdg6qt8xyqvwhn6qeyc").get(), TWCoinTypeECash)); +} + +TEST(ECash, LegacyToECashAddr) { + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("28071bf4e2b0340db41b807ed8a5514139e5d6427ff9d58dbd22b7ed187103a4").get())); + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); + auto address = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(publicKey.get(), 0)); + auto addressString = WRAPS(TWBitcoinAddressDescription(address.get())); + assertStringsEqual(addressString, "1PeUvjuxyf31aJKX6kCXuaqxhmG78ZUdL1"); + + auto ecashAddress = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeECash)); + auto ecashAddressString = WRAPS(TWAnyAddressDescription(ecashAddress.get())); + assertStringsEqual(ecashAddressString, "ecash:qruxj7zq6yzpdx8dld0e9hfvt7u47zrw9gswqul42q"); +} + +TEST(ECash, LockScript) { + auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(STRING("ecash:qpk05r5kcd8uuzwqunn8rlx5xvuvzjqju564r6szft").get(), TWCoinTypeECash)); + auto data = WRAPD(TWAnyAddressData(address.get())); + auto rawData = WRAPD(TWDataCreateWithSize(0)); + TWDataAppendByte(rawData.get(), 0x00); + TWDataAppendData(rawData.get(), data.get()); + + auto legacyAddress = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithData(rawData.get())); + auto legacyString = WRAPS(TWBitcoinAddressDescription(legacyAddress.get())); + assertStringsEqual(legacyString, "1AwDXywmyhASpCCFWkqhySgZf8KiswFoGh"); + + auto keyHash = WRAPD(TWDataCreateWithBytes(legacyAddress.get()->impl.bytes.data() + 1, 20)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildPayToPublicKeyHash(keyHash.get())); + auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); + assertHexEqual(scriptData, "76a9146cfa0e96c34fce09c0e4e671fcd43338c14812e588ac"); + + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("pzukqjmcyzrkh3gsqzdcy3e3d39cqxhl3gkypy0vjg").get(), TWCoinTypeECash)); + auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); + assertHexEqual(scriptData2, "a914b9604b7820876bc510009b8247316c4b801aff8a87"); +} + +TEST(ECash, ExtendedKeys) { + // Same test as BCH, but with the 899 derivation path + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic( + STRING("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal").get(), + STRING("TREZOR").get())); + + auto xprv = WRAPS(TWHDWalletGetExtendedPrivateKey(wallet.get(), TWPurposeBIP44, TWCoinTypeECash, TWHDVersionXPRV)); + auto xpub = WRAPS(TWHDWalletGetExtendedPublicKey(wallet.get(), TWPurposeBIP44, TWCoinTypeECash, TWHDVersionXPUB)); + + assertStringsEqual(xprv, "xprv9xjBcTizebJaV61xMkuMJ89vis7saMmwFgTYeF83KwinEksJ4frk7wB4mDiKiwXDCbJmgmh6Bp1FkF8SopNZhbF3B5wyX32cuDVFZtuUDvB"); + assertStringsEqual(xpub, "xpub6BiY1yFtUxrsha6RTnSMfG6fGtxMypVncuP9SdXetHFm7ZCScDAzfjVYcW32bkNCGJ5DTqawAHSTbJdTBL8wVxqUDGpxnRtukrhhBoS7Wy7"); +} + +TEST(ECash, DeriveFromXPub) { + auto xpub = STRING("xpub6BiY1yFtUxrsha6RTnSMfG6fGtxMypVncuP9SdXetHFm7ZCScDAzfjVYcW32bkNCGJ5DTqawAHSTbJdTBL8wVxqUDGpxnRtukrhhBoS7Wy7"); + auto pubKey2 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeECash, STRING("m/44'/899'/0'/0/2").get())); + auto pubKey9 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeECash, STRING("m/44'/899'/0'/0/9").get())); + + auto address2 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey2.get(), TWCoinTypeECash)); + auto address2String = WRAPS(TWAnyAddressDescription(address2.get())); + + auto address9 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey9.get(), TWCoinTypeECash)); + auto address9String = WRAPS(TWAnyAddressDescription(address9.get())); + + assertStringsEqual(address2String, "ecash:qpttymfhuq3v8tasfv7drlglhq6ne6zxquqltu3dcj"); + assertStringsEqual(address9String, "ecash:qqjraw2s5pwqwzql4znjpvp4vtvy3c9gmugq62r2j7"); +} + +TEST(ECash, SignTransaction) { + const int64_t amount = 600; + + // Transaction on eCash Mainnet + // https://blockchair.com/ecash/transaction/96ee20002b34e468f9d3c5ee54f6a8ddaa61c118889c4f35395c2cd93ba5bbb4 + + auto input = Proto::SigningInput(); + input.set_hash_type(hashTypeForCoin(TWCoinTypeECash)); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address("ecash:qpmfhhledgp0jy66r5vmwjwmdfu0up7ujqpvm4v8rm"); + input.set_change_address("ecash:qz0q3xmg38sr94rw8wg45vujah7kzma3cs0tssg5fd"); + + auto hash0 = DATA("e28c2b955293159898e34c6840d99bf4d390e2ee1c6f606939f18ee1e2000d05"); + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(TWDataBytes(hash0.get()), TWDataSize(hash0.get())); + utxo0->mutable_out_point()->set_index(2); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo0->set_amount(5151); + auto script0 = parse_hex("76a914aff1e0789e5fe316b729577665aa0a04d5b0f8c788ac"); + utxo0->set_script(script0.data(), script0.size()); + + auto utxoKey0 = DATA("7fdafb9db5bc501f2096e7d13d331dc7a75d9594af3d251313ba8b6200f4e384"); + input.add_private_key(TWDataBytes(utxoKey0.get()), TWDataSize(utxoKey0.get())); + + // Sign + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeECash); + + EXPECT_EQ(output.transaction().outputs_size(), 2); + EXPECT_EQ(output.transaction().outputs(0).value(), amount); + EXPECT_EQ(output.transaction().outputs(1).value(), 4325); + EXPECT_EQ(output.encoded().length(), 226ul); + ASSERT_EQ(hex(output.encoded()), + "01000000" + "01" + "e28c2b955293159898e34c6840d99bf4d390e2ee1c6f606939f18ee1e2000d05" + "02000000" + "6b483045022100b70d158b43cbcded60e6977e93f9a84966bc0cec6f2dfd1463d1223a90563f0d02207548d081069de570a494d0967ba388ff02641d91cadb060587ead95a98d4e3534121038eab72ec78e639d02758e7860cdec018b49498c307791f785aa3019622f4ea5b" + "ffffffff" + "02" + "5802000000000000" + "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "e510000000000000" + "1976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + "00000000"); +} + +} // namespace TW::Bitcoin diff --git a/tests/EOS/AddressTests.cpp b/tests/chains/EOS/AddressTests.cpp similarity index 71% rename from tests/EOS/AddressTests.cpp rename to tests/chains/EOS/AddressTests.cpp index 25ff60f2dd9..e11efc7feeb 100644 --- a/tests/EOS/AddressTests.cpp +++ b/tests/chains/EOS/AddressTests.cpp @@ -12,7 +12,8 @@ #include using namespace TW; -using namespace TW::EOS; + +namespace TW::EOS::tests { TEST(EOSAddress, Invalid) { ASSERT_FALSE(Address::isValid("abc")); @@ -28,40 +29,36 @@ TEST(EOSAddress, Invalid) { TEST(EOSAddress, Base58) { ASSERT_EQ( Address("EOS65QzSGJ579GPNKtZoZkChTzsxR4B48RCfiS82m2ymJR6VZCjTF").string(), - "EOS65QzSGJ579GPNKtZoZkChTzsxR4B48RCfiS82m2ymJR6VZCjTF" - ); + "EOS65QzSGJ579GPNKtZoZkChTzsxR4B48RCfiS82m2ymJR6VZCjTF"); ASSERT_EQ( Address("EOS55hdeEZHoArE8LLTv6drj2yR1K1AH8wAPT4kjTVSnkmQc3nzwQ").string(), - "EOS55hdeEZHoArE8LLTv6drj2yR1K1AH8wAPT4kjTVSnkmQc3nzwQ" - ); + "EOS55hdeEZHoArE8LLTv6drj2yR1K1AH8wAPT4kjTVSnkmQc3nzwQ"); ASSERT_EQ( Address("PUB_R1_5hieQEFWh68h6bjaYAY25Ptd2bmqLCaFsunaneh9gZsmSgUBUe").string(), - "PUB_R1_5hieQEFWh68h6bjaYAY25Ptd2bmqLCaFsunaneh9gZsmSgUBUe" - ); + "PUB_R1_5hieQEFWh68h6bjaYAY25Ptd2bmqLCaFsunaneh9gZsmSgUBUe"); ASSERT_EQ( Address("PUB_R1_7M9ckjr6p5CmS3N3yLPg9vcTB5NHmLcMHwZ3iGccEVfbjJRHv3").string(), - "PUB_R1_7M9ckjr6p5CmS3N3yLPg9vcTB5NHmLcMHwZ3iGccEVfbjJRHv3" - ); + "PUB_R1_7M9ckjr6p5CmS3N3yLPg9vcTB5NHmLcMHwZ3iGccEVfbjJRHv3"); } TEST(EOSAddress, FromPrivateKey) { - std::string privArray[] { "8e14ef506fee5e0aaa32f03a45242d32d0eb993ffe25ce77542ef07219db667c", - "e2bfd815c5923f404388a3257aa5527f0f52e92ce364e1e26a04d270c901edda", - "e6b783120a21cb234d8e15077ce186c47261d1043781ab8b16b84f2acd377668", - "bb96c0a4a6ec9c93ccc0b2cbad6b0e8110b9ca4731aef9c6937b99552a319b03" }; + std::string privArray[]{"8e14ef506fee5e0aaa32f03a45242d32d0eb993ffe25ce77542ef07219db667c", + "e2bfd815c5923f404388a3257aa5527f0f52e92ce364e1e26a04d270c901edda", + "e6b783120a21cb234d8e15077ce186c47261d1043781ab8b16b84f2acd377668", + "bb96c0a4a6ec9c93ccc0b2cbad6b0e8110b9ca4731aef9c6937b99552a319b03"}; - Type privTypes[] { Type::Legacy, Type::Legacy, Type::ModernR1, Type::ModernR1 }; + Type privTypes[]{Type::Legacy, Type::Legacy, Type::ModernR1, Type::ModernR1}; - std::string pubArray[] { "EOS6TFKUKVvtvjRq9T4fV9pdxNUuJke92nyb4rzSFtZfdR5ssmVuY", - "EOS5YtaCcbPJ3BknNBTDezE9eJoGNnAVuUwT8bnxhSRS5dqRvyfxr", - "PUB_R1_67itCyDj42CRgtpyP4fLbAccBYnVHGeZQujQAeK3fyNbvfvZM6", - "PUB_R1_5DpVkbrMBDnY4JRhiEdHLmdLDKGQLNfL7X7it2pqT7Uk83ccDL" }; + std::string pubArray[]{"EOS6TFKUKVvtvjRq9T4fV9pdxNUuJke92nyb4rzSFtZfdR5ssmVuY", + "EOS5YtaCcbPJ3BknNBTDezE9eJoGNnAVuUwT8bnxhSRS5dqRvyfxr", + "PUB_R1_67itCyDj42CRgtpyP4fLbAccBYnVHGeZQujQAeK3fyNbvfvZM6", + "PUB_R1_5DpVkbrMBDnY4JRhiEdHLmdLDKGQLNfL7X7it2pqT7Uk83ccDL"}; - for (int i = 0; i < 4; i++) { + for (int i = 0; i < 4; i++) { const auto privateKey = PrivateKey(parse_hex(privArray[i])); const auto publicKey = PublicKey(privateKey.getPublicKey(privTypes[i] == Type::Legacy ? TWPublicKeyTypeSECP256k1 : TWPublicKeyTypeNIST256p1)); const auto address = Address(publicKey, privTypes[i]); - + ASSERT_EQ(address.string(), pubArray[i]); } } @@ -74,4 +71,6 @@ TEST(EOSAddress, IsValid) { ASSERT_NO_THROW(Address(parse_hex("039d91164ea04f4e751762643ef4ae520690af361b8e677cf341fd213419956b356cb721b7"), Type::ModernR1)); ASSERT_NO_THROW(Address(parse_hex("02d3c8e736a9a50889766caf3c37bd16e2fecc7340b3130e25d4c01b153f996a10a78afc0e"), Type::Legacy)); -} \ No newline at end of file +} + +} // namespace TW::EOS::tests diff --git a/tests/EOS/AssetTests.cpp b/tests/chains/EOS/AssetTests.cpp similarity index 90% rename from tests/EOS/AssetTests.cpp rename to tests/chains/EOS/AssetTests.cpp index 979d54e2550..b697bea654b 100644 --- a/tests/EOS/AssetTests.cpp +++ b/tests/chains/EOS/AssetTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,8 +9,7 @@ #include -using namespace TW; -using namespace TW::EOS; +namespace TW::EOS::tests { TEST(EOSAsset, Serialization) { Data buf; @@ -30,3 +29,5 @@ TEST(EOSAsset, Serialization) { // add tests for negative amounts, fractional amounts } + +} // namespace TW::EOS diff --git a/tests/EOS/NameTests.cpp b/tests/chains/EOS/NameTests.cpp similarity index 76% rename from tests/EOS/NameTests.cpp rename to tests/chains/EOS/NameTests.cpp index 76cd5cb5e52..e9b64201926 100644 --- a/tests/EOS/NameTests.cpp +++ b/tests/chains/EOS/NameTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -6,18 +6,16 @@ #include "EOS/Name.h" #include "HexCoding.h" -#include "PrivateKey.h" #include -using namespace TW; -using namespace TW::EOS; +namespace TW::EOS::tests { TEST(EOSName, Invalid) { ASSERT_THROW(Name(std::string(14, 'a')), std::invalid_argument); - std::string invalidNames[] = {"Alice", "alice16", "12345satoshis"}; - for(auto name: invalidNames) { + std::string invalidNames[] = {"Alice", "alice16", "12345satoshis"}; + for (auto name : invalidNames) { ASSERT_FALSE(Name(name).string() == name); } } @@ -30,4 +28,6 @@ TEST(EOSName, Valid) { Data buf; Name(validName).serialize(buf); ASSERT_EQ(hex(buf), "458608d8354cb3c1"); -} \ No newline at end of file +} + +} // namespace TW::EOS::tests \ No newline at end of file diff --git a/tests/EOS/SignatureTests.cpp b/tests/chains/EOS/SignatureTests.cpp similarity index 85% rename from tests/EOS/SignatureTests.cpp rename to tests/chains/EOS/SignatureTests.cpp index 28bb3ee51a2..72f3fb85a2c 100644 --- a/tests/EOS/SignatureTests.cpp +++ b/tests/chains/EOS/SignatureTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,23 +9,20 @@ #include -using namespace TW; -using namespace TW::EOS; +namespace TW::EOS::tests { TEST(EOSSignature, Serialization) { Data buf; - Signature *sig = new Signature(parse_hex("1f14262320d5b145220fb94d8fe204117edd25e52bbe9557b6e0909dd00307af266f5be1deef001446979523ac9de32c7eae5e5be4180b5a60c0e6bf14b2dd3e05"), Type::ModernK1); + Signature* sig = new Signature(parse_hex("1f14262320d5b145220fb94d8fe204117edd25e52bbe9557b6e0909dd00307af266f5be1deef001446979523ac9de32c7eae5e5be4180b5a60c0e6bf14b2dd3e05"), Type::ModernK1); sig->serialize(buf); ASSERT_EQ( hex(buf), - "001f14262320d5b145220fb94d8fe204117edd25e52bbe9557b6e0909dd00307af266f5be1deef001446979523ac9de32c7eae5e5be4180b5a60c0e6bf14b2dd3e05" - ); + "001f14262320d5b145220fb94d8fe204117edd25e52bbe9557b6e0909dd00307af266f5be1deef001446979523ac9de32c7eae5e5be4180b5a60c0e6bf14b2dd3e05"); ASSERT_EQ( sig->string(), - "SIG_K1_JwtfgsdSx5RuF5aejedQ7FJTexaKMrQyYosPUWUrU1mzdLx6JUgLTZJd7zWA8q8VdnXht3YmVt7jafmD2eEK7hTRpT9rY5" - ); + "SIG_K1_JwtfgsdSx5RuF5aejedQ7FJTexaKMrQyYosPUWUrU1mzdLx6JUgLTZJd7zWA8q8VdnXht3YmVt7jafmD2eEK7hTRpT9rY5"); delete sig; sig = new Signature(parse_hex("1f5c419d16f573ddbf07d2eb959621f690f9cb856ea2d113e3af02b3b40005488410e82ffa37a079e119844d213f4eb066a640507db68851752bea6e61eb864d84"), Type::ModernR1); @@ -34,15 +31,15 @@ TEST(EOSSignature, Serialization) { ASSERT_EQ( hex(buf), - "011f5c419d16f573ddbf07d2eb959621f690f9cb856ea2d113e3af02b3b40005488410e82ffa37a079e119844d213f4eb066a640507db68851752bea6e61eb864d84" - ); + "011f5c419d16f573ddbf07d2eb959621f690f9cb856ea2d113e3af02b3b40005488410e82ffa37a079e119844d213f4eb066a640507db68851752bea6e61eb864d84"); ASSERT_EQ( sig->string(), - "SIG_R1_K7KpdLYqa6ebCP22TuiYAY9YoJh1dTWTZEVkdPzdoadFL6f8PkMYk5N8wtsF11cneEJ91XnEZP6wDJHhRyqr1fr68ouYcz" - ); + "SIG_R1_K7KpdLYqa6ebCP22TuiYAY9YoJh1dTWTZEVkdPzdoadFL6f8PkMYk5N8wtsF11cneEJ91XnEZP6wDJHhRyqr1fr68ouYcz"); delete sig; ASSERT_THROW(sig = new Signature(parse_hex("1f5c419d16f573ddbf07d2eb959621f690f9cb856ea2d113e3af02b3b40005488410e82ffa37a079e119844d213f4eb066a640507db68851752bea6e61eb864d84"), Type::Legacy), std::invalid_argument); ASSERT_THROW(sig = new Signature(parse_hex("011f5c419d16f573ddbf07d2eb959621f690f9cb856ea2d113e3af02b3b40005488410e82ffa37a079e119844d213f4eb066a640507db68851752bea6e61eb864d84"), Type::ModernR1), std::invalid_argument); } + +} // namespace TW::EOS::tests \ No newline at end of file diff --git a/tests/EOS/TWAnySignerTests.cpp b/tests/chains/EOS/TWAnySignerTests.cpp similarity index 94% rename from tests/EOS/TWAnySignerTests.cpp rename to tests/chains/EOS/TWAnySignerTests.cpp index 537ae52b042..e075c7ad8d8 100644 --- a/tests/EOS/TWAnySignerTests.cpp +++ b/tests/chains/EOS/TWAnySignerTests.cpp @@ -1,18 +1,17 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include "HexCoding.h" #include "proto/EOS.pb.h" #include -using namespace TW; -using namespace TW::EOS; +namespace TW::EOS::tests { TEST(TWAnySignerEOS, Sign) { Proto::SigningInput input; @@ -51,3 +50,5 @@ TEST(TWAnySignerEOS, Sign) { EXPECT_TRUE(output.json_encoded().empty()); } } + +} // namespace TW::EOS::tests diff --git a/tests/EOS/TWCoinTypeTests.cpp b/tests/chains/EOS/TWCoinTypeTests.cpp similarity index 97% rename from tests/EOS/TWCoinTypeTests.cpp rename to tests/chains/EOS/TWCoinTypeTests.cpp index 65412f55772..18edd68aa46 100644 --- a/tests/EOS/TWCoinTypeTests.cpp +++ b/tests/chains/EOS/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/EOS/TransactionTests.cpp b/tests/chains/EOS/TransactionTests.cpp similarity index 91% rename from tests/EOS/TransactionTests.cpp rename to tests/chains/EOS/TransactionTests.cpp index 8d2df015518..65bfa0fb392 100644 --- a/tests/EOS/TransactionTests.cpp +++ b/tests/chains/EOS/TransactionTests.cpp @@ -15,7 +15,7 @@ #include using namespace TW; -using namespace TW::EOS; +namespace TW::EOS::tests { static std::string k1Sigs[5] { "SIG_K1_KfCdjsrTnx5cBpbA5cUdHZAsRYsnC9uKzuS1shFeqfMCfdZwX4PBm9pfHwGRT6ffz3eavhtkyNci5GoFozQAx8P8PBnDmj", @@ -104,4 +104,13 @@ TEST(EOSTransaction, Serialization) { r1Sigs[i] ); } -} \ No newline at end of file +} + +TEST(EOSTransaction, formatDate) { + EXPECT_EQ(Transaction::formatDate(1554209148), "2019-04-02T12:45:48"); + EXPECT_EQ(Transaction::formatDate(1654160000), "2022-06-02T08:53:20"); + EXPECT_EQ(Transaction::formatDate(0), "1970-01-01T00:00:00"); + EXPECT_EQ(Transaction::formatDate(std::numeric_limits::max()), "2038-01-19T03:14:07"); +} + +} // namespace TW::EOS::tests diff --git a/tests/Elrond/AddressTests.cpp b/tests/chains/Elrond/AddressTests.cpp similarity index 78% rename from tests/Elrond/AddressTests.cpp rename to tests/chains/Elrond/AddressTests.cpp index 3dd1fb6f166..a96a0f53c1a 100644 --- a/tests/Elrond/AddressTests.cpp +++ b/tests/chains/Elrond/AddressTests.cpp @@ -7,20 +7,19 @@ #include #include +#include "Elrond/Address.h" #include "HexCoding.h" -#include "PublicKey.h" #include "PrivateKey.h" -#include "Elrond/Address.h" +#include "PublicKey.h" #include "TestAccounts.h" using namespace TW; -using namespace TW::Elrond; +namespace TW::Elrond::tests { TEST(ElrondAddress, Valid) { ASSERT_TRUE(Address::isValid(ALICE_BECH32)); ASSERT_TRUE(Address::isValid(BOB_BECH32)); - ASSERT_TRUE(Address::isValid(CAROL_BECH32)); } TEST(ElrondAddress, Invalid) { @@ -36,21 +35,17 @@ TEST(ElrondAddress, FromString) { Address alice, bob, carol; ASSERT_TRUE(Address::decode(ALICE_BECH32, alice)); ASSERT_TRUE(Address::decode(BOB_BECH32, bob)); - ASSERT_TRUE(Address::decode(CAROL_BECH32, carol)); - + ASSERT_EQ(ALICE_PUBKEY_HEX, hex(alice.getKeyHash())); ASSERT_EQ(BOB_PUBKEY_HEX, hex(bob.getKeyHash())); - ASSERT_EQ(CAROL_PUBKEY_HEX, hex(carol.getKeyHash())); } TEST(ElrondAddress, FromData) { const auto alice = Address(parse_hex(ALICE_PUBKEY_HEX)); const auto bob = Address(parse_hex(BOB_PUBKEY_HEX)); - const auto carol = Address(parse_hex(CAROL_PUBKEY_HEX)); - + ASSERT_EQ(ALICE_BECH32, alice.string()); ASSERT_EQ(BOB_BECH32, bob.string()); - ASSERT_EQ(CAROL_BECH32, carol.string()); } TEST(ElrondAddress, FromPrivateKey) { @@ -61,10 +56,6 @@ TEST(ElrondAddress, FromPrivateKey) { auto bobKey = PrivateKey(parse_hex(BOB_SEED_HEX)); auto bob = Address(bobKey.getPublicKey(TWPublicKeyTypeED25519)); ASSERT_EQ(BOB_BECH32, bob.string()); - - auto carolKey = PrivateKey(parse_hex(CAROL_SEED_HEX)); - auto carol = Address(carolKey.getPublicKey(TWPublicKeyTypeED25519)); - ASSERT_EQ(CAROL_BECH32, carol.string()); } TEST(ElrondAddress, FromPublicKey) { @@ -73,7 +64,6 @@ TEST(ElrondAddress, FromPublicKey) { auto bob = PublicKey(parse_hex(BOB_PUBKEY_HEX), TWPublicKeyTypeED25519); ASSERT_EQ(BOB_BECH32, Address(bob).string()); - - auto carol = PublicKey(parse_hex(CAROL_PUBKEY_HEX), TWPublicKeyTypeED25519); - ASSERT_EQ(CAROL_BECH32, Address(carol).string()); } + +} // namespace TW::Elrond::tests diff --git a/tests/Elrond/SerializationTests.cpp b/tests/chains/Elrond/SerializationTests.cpp similarity index 53% rename from tests/Elrond/SerializationTests.cpp rename to tests/chains/Elrond/SerializationTests.cpp index 35110688955..c1a730de8c7 100644 --- a/tests/Elrond/SerializationTests.cpp +++ b/tests/chains/Elrond/SerializationTests.cpp @@ -4,57 +4,60 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +#include "boost/format.hpp" #include #include -#include "boost/format.hpp" -#include "HexCoding.h" #include "Elrond/Serialization.h" +#include "HexCoding.h" #include "TestAccounts.h" using namespace TW; -using namespace TW::Elrond; + +namespace TW::Elrond::tests { TEST(ElrondSerialization, SignableString) { - Proto::TransactionMessage message; - message.set_nonce(42); - message.set_value("43"); - message.set_sender("alice"); - message.set_receiver("bob"); - message.set_data("foo"); - message.set_chain_id("1"); - message.set_version(1); - - string jsonString = serializeTransaction(message); + Transaction transaction; + transaction.nonce = 42; + transaction.value = "43"; + transaction.sender = "alice"; + transaction.receiver = "bob"; + transaction.data = "foo"; + transaction.chainID = "1"; + transaction.version = 1; + + string jsonString = serializeTransaction(transaction); ASSERT_EQ(R"({"nonce":42,"value":"43","receiver":"bob","sender":"alice","gasPrice":0,"gasLimit":0,"data":"Zm9v","chainID":"1","version":1})", jsonString); } TEST(ElrondSerialization, SignableStringWithRealData) { - Proto::TransactionMessage message; - message.set_nonce(15); - message.set_value("100"); - message.set_sender(ALICE_BECH32); - message.set_receiver(BOB_BECH32); - message.set_gas_price(1000000000); - message.set_gas_limit(50000); - message.set_data("foo"); - message.set_chain_id("1"); - message.set_version(1); + Transaction transaction; + transaction.nonce = 15; + transaction.value = "100"; + transaction.sender = ALICE_BECH32; + transaction.receiver = BOB_BECH32; + transaction.gasPrice = 1000000000; + transaction.gasLimit = 50000; + transaction.data = "foo"; + transaction.chainID = "1"; + transaction.version = 1; string expected = (boost::format(R"({"nonce":15,"value":"100","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1})") % BOB_BECH32 % ALICE_BECH32).str(); - string actual = serializeTransaction(message); + string actual = serializeTransaction(transaction); ASSERT_EQ(expected, actual); } TEST(ElrondSerialization, SignableStringWithoutData) { - Proto::TransactionMessage message; - message.set_nonce(42); - message.set_value("43"); - message.set_sender("feed"); - message.set_receiver("abba"); - message.set_chain_id("1"); - message.set_version(1); - - string jsonString = serializeTransaction(message); + Transaction transaction; + transaction.nonce = 42; + transaction.value = "43"; + transaction.sender = "feed"; + transaction.receiver = "abba"; + transaction.chainID = "1"; + transaction.version = 1; + + string jsonString = serializeTransaction(transaction); ASSERT_EQ(R"({"nonce":42,"value":"43","receiver":"abba","sender":"feed","gasPrice":0,"gasLimit":0,"chainID":"1","version":1})", jsonString); } + +} // namespace TW::Elrond::tests diff --git a/tests/chains/Elrond/SignerTests.cpp b/tests/chains/Elrond/SignerTests.cpp new file mode 100644 index 00000000000..aeaa8455a69 --- /dev/null +++ b/tests/chains/Elrond/SignerTests.cpp @@ -0,0 +1,234 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "boost/format.hpp" +#include + +#include "Elrond/Address.h" +#include "Elrond/Signer.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TestAccounts.h" + +using namespace TW; + +namespace TW::Elrond::tests { + +TEST(ElrondSigner, SignGenericAction) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(7); + input.mutable_generic_action()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_generic_action()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_generic_action()->set_value("0"); + input.mutable_generic_action()->set_data("foo"); + input.mutable_generic_action()->set_version(1); + input.set_gas_price(1000000000); + input.set_gas_limit(50000); + input.set_chain_id("1"); + + auto output = Signer::sign(input); + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "e8647dae8b16e034d518a1a860c6a6c38d16192d0f1362833e62424f424e5da660770dff45f4b951d9cc58bfb9d14559c977d443449bfc4b8783ff9c84065700"; + auto expectedEncoded = (boost::format(R"({"nonce":7,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); + + ASSERT_EQ(expectedEncoded, encoded); + ASSERT_EQ(expectedSignature, signature); +} + +TEST(ElrondSigner, SignGenericActionJSON) { + // Shuffle some fields, assume arbitrary order in the input + auto input = (boost::format(R"({"genericAction" : {"accounts": {"senderNonce": 7, "receiver": "%1%", "sender": "%2%"}, "data": "foo", "value": "0", "version": 1}, "gasPrice": 1000000000, "gasLimit": 50000, "chainId": "1"})") % BOB_BECH32 % ALICE_BECH32).str(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + + auto encoded = Signer::signJSON(input, privateKey.bytes); + auto expectedSignature = "e8647dae8b16e034d518a1a860c6a6c38d16192d0f1362833e62424f424e5da660770dff45f4b951d9cc58bfb9d14559c977d443449bfc4b8783ff9c84065700"; + auto expectedEncoded = (boost::format(R"({"nonce":7,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); + + ASSERT_EQ(expectedEncoded, encoded); +} + +TEST(ElrondSigner, SignWithoutData) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(0); + input.mutable_generic_action()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_generic_action()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_generic_action()->set_value("0"); + input.mutable_generic_action()->set_data(""); + input.mutable_generic_action()->set_version(1); + input.set_gas_price(1000000000); + input.set_gas_limit(50000); + input.set_chain_id("1"); + + auto output = Signer::sign(input); + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "c7253b821c68011584ebd3a5bb050ade19235c2d10260e411e523105826c40a79849b3eeb96fcc2a7a6b1fa140b6756f50b249e005be056ce0cf53125e0b1b00"; + auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":1,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); + + ASSERT_EQ(expectedSignature, signature); + ASSERT_EQ(expectedEncoded, encoded); +} + +TEST(ElrondSigner, SignJSONWithoutData) { + // Shuffle some fields, assume arbitrary order in the input + auto input = (boost::format(R"({"genericAction" : {"accounts": {"senderNonce": 0, "receiver": "%1%", "sender": "%2%"}, "value": "0", "version": 1}, "gasPrice": 1000000000, "gasLimit": 50000, "chainId": "1"})") % BOB_BECH32 % ALICE_BECH32).str(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + + auto encoded = Signer::signJSON(input, privateKey.bytes); + auto expectedSignature = "c7253b821c68011584ebd3a5bb050ade19235c2d10260e411e523105826c40a79849b3eeb96fcc2a7a6b1fa140b6756f50b249e005be056ce0cf53125e0b1b00"; + auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":1,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); + + ASSERT_EQ(expectedEncoded, encoded); +} + +TEST(ElrondSigner, SignWithUsernames) { + // https://github.com/ElrondNetwork/elrond-go/blob/master/examples/construction_test.go, scenario "TestConstructTransaction_Usernames". + + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(89); + input.mutable_generic_action()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_generic_action()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_generic_action()->mutable_accounts()->set_sender_username("alice"); + input.mutable_generic_action()->mutable_accounts()->set_receiver_username("bob"); + input.mutable_generic_action()->set_value("0"); + input.mutable_generic_action()->set_data(""); + input.mutable_generic_action()->set_version(1); + input.set_gas_price(1000000000); + input.set_gas_limit(50000); + input.set_chain_id("local-testnet"); + + auto output = Signer::sign(input); + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "1bed82c3f91c9d24f3a163e7b93a47453d70e8743201fe7d3656c0214569566a76503ef0968279ac942ca43b9c930bd26638dfb075a220ce80b058ab7bca140a"; + auto expectedEncoded = + (boost::format(R"({"nonce":89,"value":"0","receiver":"%1%","sender":"%2%","senderUsername":"%3%","receiverUsername":"%4%","gasPrice":1000000000,"gasLimit":50000,"chainID":"local-testnet","version":1,"signature":"%5%"})") % BOB_BECH32 % ALICE_BECH32 + // "alice" + % "YWxpY2U=" + // "bob" + % "Ym9i" % expectedSignature) + .str(); + + ASSERT_EQ(expectedSignature, signature); + ASSERT_EQ(expectedEncoded, encoded); +} + +TEST(ElrondSigner, SignWithOptions) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(89); + input.mutable_generic_action()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_generic_action()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_generic_action()->set_value("0"); + input.mutable_generic_action()->set_data(""); + input.mutable_generic_action()->set_version(1); + // We'll set a dummy value on the "options" field (merely an example). + // Currently, the "options" field should be ignored (not set) by applications using TW Core. + // In the future, TW Core will handle specific transaction options + // (such as the "SignedWithHash" flag, as seen in https://github.com/ElrondNetwork/elrond-go-core/blob/main/core/versioning/txVersionChecker.go) + // when building and signing transactions. + input.mutable_generic_action()->set_options(42); + input.set_gas_price(1000000000); + input.set_gas_limit(50000); + input.set_chain_id("local-testnet"); + + auto output = Signer::sign(input); + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "d9a624f13960ae1cc471de48bdb43b101b9d469bb8b159f68bb629bb32d0109e1acfebb62d6d2fc5786c0b85f9e7ce2caff74988864a8285f34797c5a5fa5801"; + auto expectedEncoded = + (boost::format(R"({"nonce":89,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"chainID":"local-testnet","version":1,"options":42,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); + + ASSERT_EQ(expectedEncoded, encoded); + ASSERT_EQ(expectedSignature, signature); +} + +TEST(ElrondSigner, SignEGLDTransfer) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + input.mutable_egld_transfer()->mutable_accounts()->set_sender_nonce(7); + input.mutable_egld_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_egld_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_egld_transfer()->set_amount("1000000000000000000"); + + auto output = Signer::sign(input); + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "7e1c4c63b88ea72dcf7855a54463b1a424eb357ac3feb4345221e512ce07c7a50afb6d7aec6f480b554e32cf2037082f3bc17263d1394af1f3ef240be53c930b"; + auto expectedEncoded = + (boost::format(R"({"nonce":7,"value":"1000000000000000000","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":1,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); + + ASSERT_EQ(expectedSignature, signature); + ASSERT_EQ(expectedEncoded, encoded); +} + +TEST(ElrondSigner, SignESDTTransfer) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + input.mutable_esdt_transfer()->mutable_accounts()->set_sender_nonce(7); + input.mutable_esdt_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_esdt_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_esdt_transfer()->set_token_identifier("MYTOKEN-1234"); + input.mutable_esdt_transfer()->set_amount("10000000000000"); + + auto output = Signer::sign(input); + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "9add6d9ac3f1a1fddb07b934e8a73cad3b8c232bdf29d723c1b38ad619905f03e864299d06eb3fe3bbb48a9f1d9b7f14e21dc5eaffe0c87f5718ad0c4198bb0c"; + auto expectedEncoded = + (boost::format(R"({"nonce":7,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":425000,"data":"%3%","chainID":"1","version":1,"signature":"%4%"})") % BOB_BECH32 % ALICE_BECH32 + // "ESDTTransfer@4d59544f4b454e2d31323334@09184e72a000" + % "RVNEVFRyYW5zZmVyQDRkNTk1NDRmNGI0NTRlMmQzMTMyMzMzNEAwOTE4NGU3MmEwMDA=" % expectedSignature) + .str(); + + ASSERT_EQ(expectedSignature, signature); + ASSERT_EQ(expectedEncoded, encoded); +} + +TEST(ElrondSigner, SignESDTNFTTransfer) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + input.mutable_esdtnft_transfer()->mutable_accounts()->set_sender_nonce(7); + input.mutable_esdtnft_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_esdtnft_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_esdtnft_transfer()->set_token_collection("LKMEX-aab910"); + input.mutable_esdtnft_transfer()->set_token_nonce(4); + input.mutable_esdtnft_transfer()->set_amount("184300000000000000"); + + auto output = Signer::sign(input); + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "cc935685d5b31525e059a16a832cba98dee751983a5a93de4198f6553a2c55f5f1e0b4300fe9077376fa754546da0b0f6697e66462101a209aafd0fc775ab60a"; + auto expectedEncoded = + (boost::format(R"({"nonce":7,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":937500,"data":"%3%","chainID":"1","version":1,"signature":"%4%"})") % ALICE_BECH32 % ALICE_BECH32 + // "ESDTNFTTransfer@4c4b4d45582d616162393130@04@028ec3dfa01ac000@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8" + % "RVNEVE5GVFRyYW5zZmVyQDRjNGI0ZDQ1NTgyZDYxNjE2MjM5MzEzMEAwNEAwMjhlYzNkZmEwMWFjMDAwQDgwNDlkNjM5ZTVhNjk4MGQxY2QyMzkyYWJjY2U0MTAyOWNkYTc0YTE1NjM1MjNhMjAyZjA5NjQxY2MyNjE4Zjg=" % expectedSignature) + .str(); + + ASSERT_EQ(expectedSignature, signature); + ASSERT_EQ(expectedEncoded, encoded); +} + +} // namespace TW::Elrond::tests diff --git a/tests/Elrond/TWAnySignerTests.cpp b/tests/chains/Elrond/TWAnySignerTests.cpp similarity index 55% rename from tests/Elrond/TWAnySignerTests.cpp rename to tests/chains/Elrond/TWAnySignerTests.cpp index 0124614bf06..09785661191 100644 --- a/tests/Elrond/TWAnySignerTests.cpp +++ b/tests/chains/Elrond/TWAnySignerTests.cpp @@ -4,41 +4,41 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include #include "boost/format.hpp" +#include -#include -#include "HexCoding.h" -#include "../interface/TWTestUtilities.h" #include "Elrond/Signer.h" +#include "HexCoding.h" #include "TestAccounts.h" +#include "TestUtilities.h" +#include using namespace TW; -using namespace TW::Elrond; +namespace TW::Elrond::tests { TEST(TWAnySignerElrond, Sign) { auto input = Proto::SigningInput(); auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.mutable_transaction()->set_nonce(0); - input.mutable_transaction()->set_value("0"); - input.mutable_transaction()->set_sender(ALICE_BECH32); - input.mutable_transaction()->set_receiver(BOB_BECH32); - input.mutable_transaction()->set_gas_price(1000000000); - input.mutable_transaction()->set_gas_limit(50000); - input.mutable_transaction()->set_data("foo"); - input.mutable_transaction()->set_chain_id("1"); - input.mutable_transaction()->set_version(1); + input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(7); + input.mutable_generic_action()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_generic_action()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_generic_action()->set_value("0"); + input.mutable_generic_action()->set_data("foo"); + input.mutable_generic_action()->set_version(1); + input.set_gas_price(1000000000); + input.set_gas_limit(50000); + input.set_chain_id("1"); Proto::SigningOutput output; ANY_SIGN(input, TWCoinTypeElrond); auto signature = output.signature(); auto encoded = output.encoded(); - auto expectedSignature = "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00"; - auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); + auto expectedSignature = "e8647dae8b16e034d518a1a860c6a6c38d16192d0f1362833e62424f424e5da660770dff45f4b951d9cc58bfb9d14559c977d443449bfc4b8783ff9c84065700"; + auto expectedEncoded = (boost::format(R"({"nonce":7,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); ASSERT_EQ(expectedSignature, signature); ASSERT_EQ(expectedEncoded, encoded); @@ -46,12 +46,14 @@ TEST(TWAnySignerElrond, Sign) { TEST(TWAnySignerElrond, SignJSON) { // Shuffle some fields, assume arbitrary order in the input - auto input = STRING((boost::format(R"({"transaction" : {"data":"foo","value":"0","nonce":0,"receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"chainId":"1","version":1}})") % BOB_BECH32 % ALICE_BECH32).str().c_str()); + auto input = STRING((boost::format(R"({"genericAction" : {"accounts": {"senderNonce": 7, "receiver": "%1%", "sender": "%2%"}, "data": "foo", "value": "0", "version": 1}, "gasPrice": 1000000000, "gasLimit": 50000, "chainId": "1"})") % BOB_BECH32 % ALICE_BECH32).str().c_str()); auto privateKey = DATA(ALICE_SEED_HEX); auto encoded = WRAPS(TWAnySignerSignJSON(input.get(), privateKey.get(), TWCoinTypeElrond)); - auto expectedSignature = "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00"; - auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); + auto expectedSignature = "e8647dae8b16e034d518a1a860c6a6c38d16192d0f1362833e62424f424e5da660770dff45f4b951d9cc58bfb9d14559c977d443449bfc4b8783ff9c84065700"; + auto expectedEncoded = (boost::format(R"({"nonce":7,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeElrond)); assertStringsEqual(encoded, expectedEncoded.c_str()); } + +} // namespace TW::Elrond::tests diff --git a/tests/Elrond/TWCoinTypeTests.cpp b/tests/chains/Elrond/TWCoinTypeTests.cpp similarity index 97% rename from tests/Elrond/TWCoinTypeTests.cpp rename to tests/chains/Elrond/TWCoinTypeTests.cpp index 900d36abd6d..1f7a977bdc0 100644 --- a/tests/Elrond/TWCoinTypeTests.cpp +++ b/tests/chains/Elrond/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/chains/Elrond/TestAccounts.h b/tests/chains/Elrond/TestAccounts.h new file mode 100644 index 00000000000..80e45e638c3 --- /dev/null +++ b/tests/chains/Elrond/TestAccounts.h @@ -0,0 +1,16 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +// Well-known accounts on Testnet & Devnet, +// https://github.com/ElrondNetwork/elrond-sdk-erdpy/tree/main/erdpy/testnet/wallets/users: +const auto ALICE_BECH32 = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"; +const auto ALICE_PUBKEY_HEX = "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1"; +const auto ALICE_SEED_HEX = "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9"; +const auto BOB_BECH32 = "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"; +const auto BOB_PUBKEY_HEX = "8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8"; +const auto BOB_SEED_HEX = "b8ca6f8203fb4b545a8e83c5384da033c415db155b53fb5b8eba7ff5a039d639"; diff --git a/tests/chains/Elrond/TransactionFactoryTests.cpp b/tests/chains/Elrond/TransactionFactoryTests.cpp new file mode 100644 index 00000000000..8837d2cab6b --- /dev/null +++ b/tests/chains/Elrond/TransactionFactoryTests.cpp @@ -0,0 +1,192 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include + +#include "Elrond/TransactionFactory.h" +#include "TestAccounts.h" + +namespace TW::Elrond::tests { + +TEST(ElrondTransactionFactory, fromEGLDTransfer) { + auto input = Proto::SigningInput(); + input.mutable_egld_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_egld_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_egld_transfer()->set_amount("1000000000000000000"); + + TransactionFactory factory; + Transaction transaction = factory.fromEGLDTransfer(input); + + ASSERT_EQ(ALICE_BECH32, transaction.sender); + ASSERT_EQ(BOB_BECH32, transaction.receiver); + ASSERT_EQ("", transaction.data); + ASSERT_EQ("1000000000000000000", transaction.value); + ASSERT_EQ(50000ul, transaction.gasLimit); + ASSERT_EQ(1000000000ul, transaction.gasPrice); + ASSERT_EQ("1", transaction.chainID); + ASSERT_EQ(1ul, transaction.version); +} + +TEST(ElrondTransactionFactory, fromESDTTransfer) { + auto input = Proto::SigningInput(); + input.mutable_esdt_transfer()->mutable_accounts()->set_sender_nonce(7); + input.mutable_esdt_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_esdt_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_esdt_transfer()->set_token_identifier("MYTOKEN-1234"); + input.mutable_esdt_transfer()->set_amount("10000000000000"); + + TransactionFactory factory; + Transaction transaction = factory.fromESDTTransfer(input); + + ASSERT_EQ(ALICE_BECH32, transaction.sender); + ASSERT_EQ(BOB_BECH32, transaction.receiver); + ASSERT_EQ("ESDTTransfer@4d59544f4b454e2d31323334@09184e72a000", transaction.data); + ASSERT_EQ("0", transaction.value); + ASSERT_EQ(425000ul, transaction.gasLimit); + ASSERT_EQ(1000000000ul, transaction.gasPrice); + ASSERT_EQ("1", transaction.chainID); + ASSERT_EQ(1ul, transaction.version); +} + +TEST(ElrondTransactionFactory, fromESDTNFTTransfer) { + auto input = Proto::SigningInput(); + input.mutable_esdtnft_transfer()->mutable_accounts()->set_sender_nonce(7); + input.mutable_esdtnft_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_esdtnft_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_esdtnft_transfer()->set_token_collection("LKMEX-aab910"); + input.mutable_esdtnft_transfer()->set_token_nonce(4); + input.mutable_esdtnft_transfer()->set_amount("184300000000000000"); + + TransactionFactory factory; + Transaction transaction = factory.fromESDTNFTTransfer(input); + + ASSERT_EQ(ALICE_BECH32, transaction.sender); + ASSERT_EQ(ALICE_BECH32, transaction.receiver); + ASSERT_EQ("ESDTNFTTransfer@4c4b4d45582d616162393130@04@028ec3dfa01ac000@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8", transaction.data); + ASSERT_EQ("0", transaction.value); + ASSERT_EQ(937500ul, transaction.gasLimit); + ASSERT_EQ(1000000000ul, transaction.gasPrice); + ASSERT_EQ("1", transaction.chainID); + ASSERT_EQ(1ul, transaction.version); +} + +TEST(ElrondTransactionFactory, createTransfersWithProvidedNetworkConfig) { + NetworkConfig networkConfig; + + // Set dummy values: + networkConfig.setChainId("T"); + networkConfig.setMinGasPrice(1500000000); + networkConfig.setMinGasLimit(60000); + networkConfig.setGasPerDataByte(2000); + networkConfig.setGasCostESDTTransfer(300000); + networkConfig.setGasCostESDTNFTTransfer(300000); + + Proto::SigningInput signingInputWithEGLDTransfer; + signingInputWithEGLDTransfer.mutable_egld_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + signingInputWithEGLDTransfer.mutable_egld_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + signingInputWithEGLDTransfer.mutable_egld_transfer()->set_amount("0"); + + Proto::SigningInput signingInputWithESDTTransfer; + signingInputWithESDTTransfer.mutable_esdt_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + signingInputWithESDTTransfer.mutable_esdt_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + signingInputWithESDTTransfer.mutable_esdt_transfer()->set_token_identifier("MYTOKEN-1234"); + signingInputWithESDTTransfer.mutable_esdt_transfer()->set_amount("10000000000000"); + + Proto::SigningInput signingInputWithESDTNFTTransfer; + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->set_token_collection("LKMEX-aab910"); + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->set_token_nonce(4); + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->set_amount("184300000000000000"); + + TransactionFactory factory(networkConfig); + Transaction tx1 = factory.fromEGLDTransfer(signingInputWithEGLDTransfer); + Transaction tx2 = factory.fromESDTTransfer(signingInputWithESDTTransfer); + Transaction tx3 = factory.fromESDTNFTTransfer(signingInputWithESDTNFTTransfer); + + ASSERT_EQ(60000ul, tx1.gasLimit); + ASSERT_EQ(1500000000ul, tx1.gasPrice); + ASSERT_EQ("T", tx1.chainID); + + ASSERT_EQ(560000ul, tx2.gasLimit); + ASSERT_EQ(1500000000ul, tx2.gasPrice); + ASSERT_EQ("T", tx2.chainID); + + ASSERT_EQ(1110000ul, tx3.gasLimit); + ASSERT_EQ(1500000000ul, tx3.gasPrice); + ASSERT_EQ("T", tx3.chainID); +} + +TEST(ElrondTransactionFactory, createTransfersWithOverriddenNetworkParameters) { + Proto::SigningInput signingInputWithEGLDTransfer; + signingInputWithEGLDTransfer.set_gas_limit(50500); + signingInputWithEGLDTransfer.set_gas_price(1000000001); + signingInputWithEGLDTransfer.set_chain_id("A"); + + Proto::SigningInput signingInputWithESDTTransfer; + signingInputWithESDTTransfer.set_gas_limit(5000000); + signingInputWithESDTTransfer.set_gas_price(1000000002); + signingInputWithESDTTransfer.set_chain_id("B"); + + Proto::SigningInput signingInputWithESDTNFTTransfer; + signingInputWithESDTNFTTransfer.set_gas_limit(10000000); + signingInputWithESDTNFTTransfer.set_gas_price(1000000003); + signingInputWithESDTNFTTransfer.set_chain_id("C"); + + TransactionFactory factory; + Transaction tx1 = factory.fromEGLDTransfer(signingInputWithEGLDTransfer); + Transaction tx2 = factory.fromESDTTransfer(signingInputWithESDTTransfer); + Transaction tx3 = factory.fromESDTNFTTransfer(signingInputWithESDTNFTTransfer); + + ASSERT_EQ(50500ul, tx1.gasLimit); + ASSERT_EQ(1000000001ul, tx1.gasPrice); + ASSERT_EQ("A", tx1.chainID); + + ASSERT_EQ(5000000ul, tx2.gasLimit); + ASSERT_EQ(1000000002ul, tx2.gasPrice); + ASSERT_EQ("B", tx2.chainID); + + ASSERT_EQ(10000000ul, tx3.gasLimit); + ASSERT_EQ(1000000003ul, tx3.gasPrice); + ASSERT_EQ("C", tx3.chainID); +} + +TEST(ElrondTransactionFactory, create) { + Proto::SigningInput signingInputWithGenericAction; + signingInputWithGenericAction.mutable_generic_action()->set_data("hello"); + + Proto::SigningInput signingInputWithEGLDTransfer; + signingInputWithEGLDTransfer.mutable_egld_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + signingInputWithEGLDTransfer.mutable_egld_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + signingInputWithEGLDTransfer.mutable_egld_transfer()->set_amount("1"); + + Proto::SigningInput signingInputWithESDTTransfer; + signingInputWithESDTTransfer.mutable_esdt_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + signingInputWithESDTTransfer.mutable_esdt_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + signingInputWithESDTTransfer.mutable_esdt_transfer()->set_token_identifier("MYTOKEN-1234"); + signingInputWithESDTTransfer.mutable_esdt_transfer()->set_amount("10000000000000"); + + Proto::SigningInput signingInputWithESDTNFTTransfer; + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->set_token_collection("LKMEX-aab910"); + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->set_token_nonce(4); + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->set_amount("184300000000000000"); + + TransactionFactory factory; + Transaction tx1 = factory.create(signingInputWithGenericAction); + Transaction tx2 = factory.create(signingInputWithEGLDTransfer); + Transaction tx3 = factory.create(signingInputWithESDTTransfer); + Transaction tx4 = factory.create(signingInputWithESDTNFTTransfer); + + ASSERT_EQ("hello", tx1.data); + ASSERT_EQ("1", tx2.value); + ASSERT_EQ("ESDTTransfer@4d59544f4b454e2d31323334@09184e72a000", tx3.data); + ASSERT_EQ("ESDTNFTTransfer@4c4b4d45582d616162393130@04@028ec3dfa01ac000@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8", tx4.data); +} + +} // namespace TW::Elrond::tests diff --git a/tests/Ethereum/AbiStructTests.cpp b/tests/chains/Ethereum/AbiStructTests.cpp similarity index 87% rename from tests/Ethereum/AbiStructTests.cpp rename to tests/chains/Ethereum/AbiStructTests.cpp index c636f554e2c..259fb4ef3f6 100644 --- a/tests/Ethereum/AbiStructTests.cpp +++ b/tests/chains/Ethereum/AbiStructTests.cpp @@ -7,19 +7,21 @@ #include "Ethereum/ABI.h" #include "Ethereum/Address.h" #include "Ethereum/Signer.h" +#include "TestUtilities.h" #include #include -#include "../interface/TWTestUtilities.h" #include #include -using namespace TW::Ethereum::ABI; -using namespace TW::Ethereum; using namespace TW; extern std::string TESTS_ROOT; +namespace TW::Ethereum::tests { + +using namespace ABI; + std::string load_file(const std::string path) { std::ifstream stream(path); std::string content((std::istreambuf_iterator(stream)), (std::istreambuf_iterator())); @@ -27,7 +29,7 @@ std::string load_file(const std::string path) { } // https://github.com/MetaMask/eth-sig-util/blob/main/test/index.ts - +// clang-format off ParamStruct msgPersonCow2("Person", std::vector>{ std::make_shared("name", std::make_shared("Cow")), std::make_shared("wallets", std::make_shared(std::vector>{ @@ -65,7 +67,7 @@ ParamStruct msgMailCow2Bob3("Mail", std::vector>{ std::make_shared("to", std::make_shared(std::make_shared(msgPersonBob3))), std::make_shared("contents", std::make_shared("Hello, Bob!")) }); -ParamStruct msgEIP712Domain("EIP712Domain", std::vector>{ +ParamStruct gMsgEIP712Domain("EIP712Domain", std::vector>{ std::make_shared("name", std::make_shared("Ether Mail")), std::make_shared("version", std::make_shared("1")), std::make_shared("chainId", std::make_shared(1)), @@ -86,7 +88,7 @@ TEST(EthereumAbiStruct, encodeTypes) { EXPECT_EQ(hex(msgMailCow1Bob1.hashStruct()), "c52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e"); - EXPECT_EQ(hex(msgEIP712Domain.hashStruct()), "f2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f"); + EXPECT_EQ(hex(gMsgEIP712Domain.hashStruct()), "f2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f"); Address address = Address(privateKeyCow.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); EXPECT_EQ(address.string(), "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"); @@ -150,7 +152,7 @@ TEST(EthereumAbiStruct, encodeTypes_v3) { EXPECT_EQ(hex(msgMailCow1Bob1.hashStruct()), "c52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e"); - EXPECT_EQ(hex(msgEIP712Domain.hashStruct()), "f2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f"); + EXPECT_EQ(hex(gMsgEIP712Domain.hashStruct()), "f2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f"); Address address = Address(privateKeyCow.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); EXPECT_EQ(address.string(), "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"); @@ -212,17 +214,17 @@ TEST(EthereumAbiStruct, encodeTypes_v4) { EXPECT_EQ(hex(msgPersonCow2.hashType()), "fabfe1ed996349fc6027709802be19d047da1aa5d6894ff5f6486d92db2e6860"); - EXPECT_EQ(hex(msgPersonCow2.encodeHashes()), - "fabfe1ed996349fc6027709802be19d047da1aa5d6894ff5f6486d92db2e6860" - "8c1d2bd5348394761719da11ec67eedae9502d137e8940fee8ecd6f641ee1648" - "8a8bfe642b9fc19c25ada5dadfd37487461dc81dd4b0778f262c163ed81b5e2a"); + EXPECT_EQ(hex(msgPersonCow2.encodeHashes()), + "fabfe1ed996349fc6027709802be19d047da1aa5d6894ff5f6486d92db2e6860" + "8c1d2bd5348394761719da11ec67eedae9502d137e8940fee8ecd6f641ee1648" + "8a8bfe642b9fc19c25ada5dadfd37487461dc81dd4b0778f262c163ed81b5e2a"); EXPECT_EQ(hex(msgPersonCow2.hashStruct()), "9b4846dd48b866f0ac54d61b9b21a9e746f921cefa4ee94c4c0a1c49c774f67f"); - EXPECT_EQ(hex(msgPersonBob3.encodeHashes()), - "fabfe1ed996349fc6027709802be19d047da1aa5d6894ff5f6486d92db2e6860" - "28cac318a86c8a0a6a9156c2dba2c8c2363677ba0514ef616592d81557e679b6" - "d2734f4c86cc3bd9cabf04c3097589d3165d95e4648fc72d943ed161f651ec6d"); + EXPECT_EQ(hex(msgPersonBob3.encodeHashes()), + "fabfe1ed996349fc6027709802be19d047da1aa5d6894ff5f6486d92db2e6860" + "28cac318a86c8a0a6a9156c2dba2c8c2363677ba0514ef616592d81557e679b6" + "d2734f4c86cc3bd9cabf04c3097589d3165d95e4648fc72d943ed161f651ec6d"); EXPECT_EQ(hex(msgPersonBob3.hashStruct()), "efa62530c7ae3a290f8a13a5fc20450bdb3a6af19d9d9d2542b5a94e631a9168"); @@ -230,15 +232,15 @@ TEST(EthereumAbiStruct, encodeTypes_v4) { EXPECT_EQ(hex(msgMailCow2Bob3.hashType()), "4bd8a9a2b93427bb184aca81e24beb30ffa3c747e2a33d4225ec08bf12e2e753"); - EXPECT_EQ(hex(msgMailCow2Bob3.encodeHashes()), - "4bd8a9a2b93427bb184aca81e24beb30ffa3c747e2a33d4225ec08bf12e2e753" - "9b4846dd48b866f0ac54d61b9b21a9e746f921cefa4ee94c4c0a1c49c774f67f" - "ca322beec85be24e374d18d582a6f2997f75c54e7993ab5bc07404ce176ca7cd" - "b5aadf3154a261abdd9086fc627b61efca26ae5702701d05cd2305f7c52a2fc8"); + EXPECT_EQ(hex(msgMailCow2Bob3.encodeHashes()), + "4bd8a9a2b93427bb184aca81e24beb30ffa3c747e2a33d4225ec08bf12e2e753" + "9b4846dd48b866f0ac54d61b9b21a9e746f921cefa4ee94c4c0a1c49c774f67f" + "ca322beec85be24e374d18d582a6f2997f75c54e7993ab5bc07404ce176ca7cd" + "b5aadf3154a261abdd9086fc627b61efca26ae5702701d05cd2305f7c52a2fc8"); EXPECT_EQ(hex(msgMailCow2Bob3.hashStruct()), "eb4221181ff3f1a83ea7313993ca9218496e424604ba9492bb4052c03d5c3df8"); - EXPECT_EQ(hex(msgEIP712Domain.hashStruct()), "f2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f"); + EXPECT_EQ(hex(gMsgEIP712Domain.hashStruct()), "f2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f"); Address address = Address(privateKeyCow.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); EXPECT_EQ(address.string(), "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"); @@ -331,27 +333,27 @@ TEST(EthereumAbiStruct, encodeTypes_v4Rec) { EXPECT_EQ(hex(msgPersonRecursive.hashType()), "7c5c8e90cb92c8da53b893b24962513be98afcf1b57b00327ae4cc14e3a64116"); - EXPECT_EQ(hex(msgPersonRecursiveMother.encodeHashes()), - "7c5c8e90cb92c8da53b893b24962513be98afcf1b57b00327ae4cc14e3a64116" - "afe4142a2b3e7b0503b44951e6030e0e2c5000ef83c61857e2e6003e7aef8570" - "0000000000000000000000000000000000000000000000000000000000000000" - "88f14be0dd46a8ec608ccbff6d3923a8b4e95cdfc9648f0db6d92a99a264cb36"); + EXPECT_EQ(hex(msgPersonRecursiveMother.encodeHashes()), + "7c5c8e90cb92c8da53b893b24962513be98afcf1b57b00327ae4cc14e3a64116" + "afe4142a2b3e7b0503b44951e6030e0e2c5000ef83c61857e2e6003e7aef8570" + "0000000000000000000000000000000000000000000000000000000000000000" + "88f14be0dd46a8ec608ccbff6d3923a8b4e95cdfc9648f0db6d92a99a264cb36"); EXPECT_EQ(hex(msgPersonRecursiveMother.hashStruct()), "9ebcfbf94f349de50bcb1e3aa4f1eb38824457c99914fefda27dcf9f99f6178b"); - EXPECT_EQ(hex(msgPersonRecursiveFather.encodeHashes()), - "7c5c8e90cb92c8da53b893b24962513be98afcf1b57b00327ae4cc14e3a64116" - "b2a7c7faba769181e578a391a6a6811a3e84080c6a3770a0bf8a856dfa79d333" - "0000000000000000000000000000000000000000000000000000000000000000" - "02cc7460f2c9ff107904cff671ec6fee57ba3dd7decf999fe9fe056f3fd4d56e"); + EXPECT_EQ(hex(msgPersonRecursiveFather.encodeHashes()), + "7c5c8e90cb92c8da53b893b24962513be98afcf1b57b00327ae4cc14e3a64116" + "b2a7c7faba769181e578a391a6a6811a3e84080c6a3770a0bf8a856dfa79d333" + "0000000000000000000000000000000000000000000000000000000000000000" + "02cc7460f2c9ff107904cff671ec6fee57ba3dd7decf999fe9fe056f3fd4d56e"); EXPECT_EQ(hex(msgPersonRecursiveFather.hashStruct()), "b852e5abfeff916a30cb940c4e24c43cfb5aeb0fa8318bdb10dd2ed15c8c70d8"); - EXPECT_EQ(hex(msgPersonRecursive.encodeHashes()), - "7c5c8e90cb92c8da53b893b24962513be98afcf1b57b00327ae4cc14e3a64116" - "e8d55aa98b6b411f04dbcf9b23f29247bb0e335a6bc5368220032fdcb9e5927f" - "9ebcfbf94f349de50bcb1e3aa4f1eb38824457c99914fefda27dcf9f99f6178b" - "b852e5abfeff916a30cb940c4e24c43cfb5aeb0fa8318bdb10dd2ed15c8c70d8"); + EXPECT_EQ(hex(msgPersonRecursive.encodeHashes()), + "7c5c8e90cb92c8da53b893b24962513be98afcf1b57b00327ae4cc14e3a64116" + "e8d55aa98b6b411f04dbcf9b23f29247bb0e335a6bc5368220032fdcb9e5927f" + "9ebcfbf94f349de50bcb1e3aa4f1eb38824457c99914fefda27dcf9f99f6178b" + "b852e5abfeff916a30cb940c4e24c43cfb5aeb0fa8318bdb10dd2ed15c8c70d8"); EXPECT_EQ(hex(msgPersonRecursive.hashStruct()), "fdc7b6d35bbd81f7fa78708604f57569a10edff2ca329c8011373f0667821a45"); @@ -462,7 +464,7 @@ TEST(EthereumAbiStruct, hashStructJson) { })"); ASSERT_EQ(hex(hash), "0b4bb85394b9ebb1c2425e283c9e734a9a7a832622e97c998f77e1c7a3f01a20"); } - { // edge cases + { // edge cases EXPECT_EXCEPTION(ParamStruct::hashStructJson("NOT_A_JSON"), "Could not parse Json"); EXPECT_EXCEPTION(ParamStruct::hashStructJson("+/{\\"), "Could not parse Json"); EXPECT_EXCEPTION(ParamStruct::hashStructJson(""), "Could not parse Json"); @@ -484,9 +486,35 @@ TEST(EthereumAbiStruct, hashStructJson) { } } +TEST(EthereumAbiStruct, hashStruct_emptyString) { + auto path = TESTS_ROOT + "/chains/Ethereum/Data/eip712_emptyString.json"; + auto typeData = load_file(path); + auto hash = ParamStruct::hashStructJson(typeData); + EXPECT_EQ(hex(hash), "bc9d33285c5e42b00571f5deaf9636d2e498a6fa50e0d1be81095bded070117a"); + + // sign the hash + const auto rsv = Signer::sign(privateKeyOilTimes12, hash, true, 0); + EXPECT_EQ(hex(store(rsv.r)), "5df6cb46d874bc0acc519695f393008a837ca9d2e316836b669b8f0de7673638"); + EXPECT_EQ(hex(store(rsv.s)), "54cc0bcc0ad657f9222f7e7be3fbe0ec4a8edb9385c39d578dfac8d38727af12"); + EXPECT_EQ(hex(store(rsv.v)), "1c"); +} + +TEST(EthereumAbiStruct, hashStruct_emptyArray) { + auto path = TESTS_ROOT + "/chains/Ethereum/Data/eip712_emptyArray.json"; + auto typeData = load_file(path); + auto hash = ParamStruct::hashStructJson(typeData); + EXPECT_EQ(hex(hash), "9f1a1bc718e966d683c544aef6fd0b73c85a1d6244af9b64bb8f4a6fa6716086"); + + // sign the hash + const auto rsv = Signer::sign(privateKeyOilTimes12, hash, true, 0); + EXPECT_EQ(hex(store(rsv.r)), "de47efd592493f7189d44f071424ecb24b50d80750d3bd2bb6fc80451c13a52f"); + EXPECT_EQ(hex(store(rsv.s)), "202b8a2be1ef3c466853e8cd5275a6af15b11e7e1cc0ae4a7e249bc9bad591eb"); + EXPECT_EQ(hex(store(rsv.v)), "1c"); +} + TEST(EthereumAbiStruct, hashStruct_walletConnect) { // https://github.com/WalletConnect/walletconnect-example-dapp/blob/master/src/helpers/eip712.ts - auto path = TESTS_ROOT + "/Ethereum/Data/eip712_walletconnect.json"; + auto path = TESTS_ROOT + "/chains/Ethereum/Data/eip712_walletconnect.json"; auto typeData = load_file(path); auto hash = ParamStruct::hashStructJson(typeData); EXPECT_EQ(hex(hash), "abc79f527273b9e7bca1b3f1ac6ad1a8431fa6dc34ece900deabcd6969856b5e"); @@ -499,7 +527,7 @@ TEST(EthereumAbiStruct, hashStruct_walletConnect) { } TEST(EthereumAbiStruct, hashStruct_cryptofights) { - auto path = TESTS_ROOT + "/Ethereum/Data/eip712_cryptofights.json"; + auto path = TESTS_ROOT + "/chains/Ethereum/Data/eip712_cryptofights.json"; auto typeData = load_file(path); auto hash = ParamStruct::hashStructJson(typeData); EXPECT_EQ(hex(hash), "db12328a6d193965801548e1174936c3aa7adbe1b54b3535a3c905bd4966467c"); @@ -512,7 +540,7 @@ TEST(EthereumAbiStruct, hashStruct_cryptofights) { } TEST(EthereumAbiStruct, hashStruct_rarible) { - auto path = TESTS_ROOT + "/Ethereum/Data/eip712_rarible.json"; + auto path = TESTS_ROOT + "/chains/Ethereum/Data/eip712_rarible.json"; auto typeData = load_file(path); auto hash = ParamStruct::hashStructJson(typeData); EXPECT_EQ(hex(hash), "df0200de55c05eb55af2597012767ea3af653d68000be49580f8e05acd91d366"); @@ -525,7 +553,7 @@ TEST(EthereumAbiStruct, hashStruct_rarible) { } TEST(EthereumAbiStruct, hashStruct_snapshot) { - auto path = TESTS_ROOT + "/Ethereum/Data/eip712_snapshot_v4.json"; + auto path = TESTS_ROOT + "/chains/Ethereum/Data/eip712_snapshot_v4.json"; auto typeData = load_file(path); auto hash = ParamStruct::hashStructJson(typeData); EXPECT_EQ(hex(hash), "f558d08ad4a7651dbc9ec028cfcb4a8e6878a249073ef4fa694f85ee95f61c0f"); @@ -555,7 +583,7 @@ TEST(EthereumAbiStruct, ParamStructMakeStruct) { })"); ASSERT_NE(s.get(), nullptr); EXPECT_EQ(s->getType(), "Person"); - ASSERT_EQ(s->getCount(), 2); + ASSERT_EQ(s->getCount(), 2ul); EXPECT_EQ(s->encodeType(), "Person(string name,address wallet)"); EXPECT_EQ(hex(s->hashStruct()), "fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8"); } @@ -569,7 +597,7 @@ TEST(EthereumAbiStruct, ParamStructMakeStruct) { })"); ASSERT_NE(s.get(), nullptr); EXPECT_EQ(s->getType(), "Person"); - ASSERT_EQ(s->getCount(), 2); + ASSERT_EQ(s->getCount(), 2ul); EXPECT_EQ(s->encodeType(), "Person(string name,address[] wallets)"); EXPECT_EQ(hex(s->hashStruct()), "9b4846dd48b866f0ac54d61b9b21a9e746f921cefa4ee94c4c0a1c49c774f67f"); } @@ -579,12 +607,12 @@ TEST(EthereumAbiStruct, ParamStructMakeStruct) { R"({"Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}],"Mail": [{"name": "from", "type": "Person"},{"name": "to", "type": "Person"},{"name": "contents", "type": "string"}]})"); ASSERT_NE(s.get(), nullptr); EXPECT_EQ(s->getType(), "Mail"); - ASSERT_EQ(s->getCount(), 3); + ASSERT_EQ(s->getCount(), 3ul); EXPECT_EQ(s->encodeType(), "Mail(Person from,Person to,string contents)Person(string name,address wallet)"); EXPECT_EQ(hex(s->hashStruct()), "c52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e"); } - { // extra param + { // extra param std::shared_ptr s = ParamStruct::makeStruct("Person", R"({"extra_param": "extra_value", "name": "Cow", "wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"})", R"({"Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}]})"); @@ -592,14 +620,14 @@ TEST(EthereumAbiStruct, ParamStructMakeStruct) { EXPECT_EQ(s->encodeType(), "Person(string name,address wallet)"); EXPECT_EQ(hex(s->hashStruct()), "fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8"); } - { // empty array + { // empty array std::shared_ptr s = ParamStruct::makeStruct("Person", R"({"extra_param": "extra_value", "name": "Cow", "wallets": []})", R"({"Person": [{"name": "name", "type": "string"}, {"name": "wallets", "type": "address[]"}]})"); ASSERT_NE(s.get(), nullptr); EXPECT_EQ(s->encodeType(), "Person(string name,address[] wallets)"); } - { // missing param + { // missing param std::shared_ptr s = ParamStruct::makeStruct("Person", R"({"wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"})", R"({"Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}]})"); @@ -649,7 +677,7 @@ TEST(EthereumAbiStruct, ParamStructMakeStruct) { TEST(EthereumAbiStruct, ParamFactoryMakeTypes) { { std::vector> tt = ParamStruct::makeTypes(R"({"Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}]})"); - ASSERT_EQ(tt.size(), 1); + ASSERT_EQ(tt.size(), 1ul); EXPECT_EQ(tt[0]->encodeType(), "Person(string name,address wallet)"); } { @@ -658,22 +686,22 @@ TEST(EthereumAbiStruct, ParamFactoryMakeTypes) { "Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}], "Mail": [{"name": "from", "type": "Person"}, {"name": "to", "type": "Person"}, {"name": "contents", "type": "string"}] })"); - ASSERT_EQ(tt.size(), 2); + ASSERT_EQ(tt.size(), 2ul); EXPECT_EQ(tt[0]->encodeType(), "Mail(Person from,Person to,string contents)Person(string name,address wallet)"); EXPECT_EQ(tt[1]->encodeType(), "Person(string name,address wallet)"); } - { // edge cases + { // edge cases EXPECT_EXCEPTION(ParamStruct::makeTypes("NOT_A_JSON"), "Could not parse types Json"); EXPECT_EXCEPTION(ParamStruct::makeTypes("+/{\\"), "Could not parse types Json"); EXPECT_EXCEPTION(ParamStruct::makeTypes(""), "Could not parse types Json"); EXPECT_EXCEPTION(ParamStruct::makeTypes("0"), "Expecting object"); EXPECT_EXCEPTION(ParamStruct::makeTypes("[]"), "Expecting object"); EXPECT_EXCEPTION(ParamStruct::makeTypes("[{}]"), "Expecting object"); - EXPECT_EQ(ParamStruct::makeTypes("{}").size(), 0); + EXPECT_EQ(ParamStruct::makeTypes("{}").size(), 0ul); EXPECT_EXCEPTION(ParamStruct::makeTypes("{\"a\": 0}"), "Expecting array"); EXPECT_EXCEPTION(ParamStruct::makeTypes(R"({"name": 0})"), "Expecting array"); // order does not matter - EXPECT_EQ(ParamStruct::makeTypes(R"({"Mail": [{"name": "from", "type": "Person"}, {"name": "to", "type": "Person"}, {"name": "contents", "type": "string"}], "Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}]})").size(), 2); + EXPECT_EQ(ParamStruct::makeTypes(R"({"Mail": [{"name": "from", "type": "Person"}, {"name": "to", "type": "Person"}, {"name": "contents", "type": "string"}], "Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}]})").size(), 2ul); } } @@ -682,10 +710,10 @@ TEST(EthereumAbiStruct, ParamFactoryMakeType) { std::shared_ptr t = ParamStruct::makeType("Person", R"([{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}])"); EXPECT_NE(t.get(), nullptr); EXPECT_EQ(t->getType(), "Person"); - ASSERT_EQ(t->getCount(), 2); + ASSERT_EQ(t->getCount(), 2ul); ASSERT_EQ(t->encodeType(), "Person(string name,address wallet)"); } - { // edge cases + { // edge cases EXPECT_EXCEPTION(ParamStruct::makeType("", ""), "Missing type name"); EXPECT_EXCEPTION(ParamStruct::makeType("Person", "NOT_A_JSON"), "Could not parse type Json"); EXPECT_EXCEPTION(ParamStruct::makeType("Person", "+/{\\"), "Could not parse type Json"); @@ -714,7 +742,7 @@ TEST(EthereumAbiStruct, ParamNamedMethods) { EXPECT_EQ(hex(encoded), "000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000"); size_t offset = 0; EXPECT_EQ(pn->decode(encoded, offset), true); - EXPECT_EQ(offset, 64); + EXPECT_EQ(offset, 64ul); pn->setValueJson("World"); EXPECT_EQ(ps->getVal(), "World"); } @@ -723,7 +751,7 @@ TEST(EthereumAbiStruct, ParamSetNamed) { const auto pn1 = std::make_shared("param1", std::make_shared("Hello")); const auto pn2 = std::make_shared("param2", std::make_shared("World")); auto ps = std::make_shared(std::vector>{pn1, pn2}); - EXPECT_EQ(ps->getCount(), 2); + EXPECT_EQ(ps->getCount(), 2ul); EXPECT_EQ(ps->addParam(std::shared_ptr(nullptr)), -1); EXPECT_EQ(ps->findParamByName("NO_SUCH_PARAM"), nullptr); auto pf1 = ps->findParamByName("param2"); @@ -736,14 +764,14 @@ TEST(EthereumAbiStruct, ParamStructMethods) { const auto pn2 = std::make_shared("param2", std::make_shared("World")); auto ps = std::make_shared("struct", std::vector>{pn1, pn2}); - EXPECT_EQ(ps->getSize(), 2); + EXPECT_EQ(ps->getSize(), 2ul); EXPECT_EQ(ps->isDynamic(), true); Data encoded; ps->encode(encoded); EXPECT_EQ(hex(encoded), ""); size_t offset = 0; EXPECT_EQ(ps->decode(encoded, offset), true); - EXPECT_EQ(offset, 0); + EXPECT_EQ(offset, 0ul); EXPECT_FALSE(ps->setValueJson("dummy")); EXPECT_EQ(ps->findParamByName("param2")->getName(), "param2"); } @@ -864,6 +892,24 @@ TEST(EthereumAbiStruct, ParamHashStruct) { EXPECT_TRUE(p->setValueJson("0x0000000000000000000000000000000123456789")); EXPECT_EQ(hex(p->hashStruct()), "0000000000000000000000000000000000000000000000000000000123456789"); } + { + using collection = std::vector>; + auto p = std::make_shared(collection{std::make_shared(), std::make_shared(), std::make_shared()}); + EXPECT_TRUE(p->setValueJson("[1,0,1]")); + EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"); + } + { + using collection = std::vector>; + auto p = std::make_shared(collection{std::make_shared(), std::make_shared(), std::make_shared()}); + EXPECT_TRUE(p->setValueJson("[13,14,15]")); + EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f"); + + // Coverage + EXPECT_FALSE(p->setValueJson("NotValidJson")); + EXPECT_FALSE(p->setValueJson("{}")); + EXPECT_FALSE(p->setValueJson("[1,2,3,4]")); + EXPECT_FALSE(p->setValueJson("[1,2]")); + } { auto p = std::make_shared(std::make_shared()); EXPECT_TRUE(p->setValueJson("[13,14,15]")); @@ -880,3 +926,5 @@ TEST(EthereumAbiStruct, ParamHashStruct) { EXPECT_EQ(hex(p->hashStruct()), "5c6090c0461491a2941743bda5c3658bf1ea53bbd3edcde54e16205e18b45792"); } } +// clang-format on +} // namespace TW::Ethereum::tests diff --git a/tests/Ethereum/AbiTests.cpp b/tests/chains/Ethereum/AbiTests.cpp similarity index 84% rename from tests/Ethereum/AbiTests.cpp rename to tests/chains/Ethereum/AbiTests.cpp index 6aa15115163..3575dcb0f95 100644 --- a/tests/Ethereum/AbiTests.cpp +++ b/tests/chains/Ethereum/AbiTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -6,13 +6,11 @@ #include "Ethereum/ABI.h" #include "HexCoding.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include -using namespace TW; -using namespace TW::Ethereum::ABI; - +namespace TW::Ethereum::ABI::tests { ///// Parameter types @@ -34,11 +32,23 @@ TEST(EthereumAbi, ParamTypeNames) { EXPECT_EQ("address", ParamAddress().getType()); EXPECT_EQ("bytes", ParamByteArray().getType()); EXPECT_EQ("bytes168", ParamByteArrayFix(168).getType()); - // ParamArray: needs a child parameter to obtain type - auto paramArray = ParamArray(); - EXPECT_EQ("empty[]", paramArray.getType()); - paramArray.addParam(std::make_shared()); - EXPECT_EQ("bool[]", paramArray.getType()); + { + // ParamArray, non-empty + auto paramArray = ParamArray(); + paramArray.addParam(std::make_shared()); + EXPECT_EQ("bool[]", paramArray.getType()); + } + { + // ParamArray, empty with prototype + auto paramArray = ParamArray(); + paramArray.setProto(std::make_shared()); + EXPECT_EQ("bool[]", paramArray.getType()); + } + { + // ParamArray, empty, no prototype + auto paramArray = ParamArray(); + EXPECT_EQ("__empty__[]", paramArray.getType()); + } EXPECT_EQ("()", ParamTuple().getType()); } @@ -51,7 +61,7 @@ TEST(EthereumAbi, ParamBool) { EXPECT_EQ("bool", param.getType()); EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32, param.getSize()); + EXPECT_EQ(32ul, param.getSize()); } { auto param = ParamBool(false); @@ -82,7 +92,7 @@ TEST(EthereumAbi, ParamUInt8) { EXPECT_EQ("uint8", param.getType()); EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32, param.getSize()); + EXPECT_EQ(32ul, param.getSize()); } { auto param = ParamUInt8(101); @@ -113,7 +123,7 @@ TEST(EthereumAbi, ParamUInt16) { EXPECT_EQ("uint16", param.getType()); EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32, param.getSize()); + EXPECT_EQ(32ul, param.getSize()); } { auto param = ParamUInt16(101); @@ -138,13 +148,13 @@ TEST(EthereumAbi, ParamUInt16) { TEST(EthereumAbi, ParamUInt32) { { auto param = ParamUInt32(101); - EXPECT_EQ(101, param.getVal()); + EXPECT_EQ(101ul, param.getVal()); param.setVal(1234); - EXPECT_EQ(1234, param.getVal()); + EXPECT_EQ(1234ul, param.getVal()); EXPECT_EQ("uint32", param.getType()); EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32, param.getSize()); + EXPECT_EQ(32ul, param.getSize()); } { auto param = ParamUInt32(101); @@ -153,7 +163,7 @@ TEST(EthereumAbi, ParamUInt32) { EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000065", hex(encoded)); size_t offset = 0; EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(101, param.getVal()); + EXPECT_EQ(101ul, param.getVal()); } { auto param = ParamUInt32(1234); @@ -162,20 +172,20 @@ TEST(EthereumAbi, ParamUInt32) { EXPECT_EQ("00000000000000000000000000000000000000000000000000000000000004d2", hex(encoded)); size_t offset = 0; EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(1234, param.getVal()); + EXPECT_EQ(1234ul, param.getVal()); } } TEST(EthereumAbi, ParamUInt64) { { auto param = ParamUInt64(101); - EXPECT_EQ(101, param.getVal()); + EXPECT_EQ(101ul, param.getVal()); param.setVal(1234); - EXPECT_EQ(1234, param.getVal()); + EXPECT_EQ(1234ul, param.getVal()); EXPECT_EQ("uint64", param.getType()); EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32, param.getSize()); + EXPECT_EQ(32ul, param.getSize()); } { auto param = ParamUInt64(101); @@ -184,7 +194,7 @@ TEST(EthereumAbi, ParamUInt64) { EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000065", hex(encoded)); size_t offset = 0; EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(101, param.getVal()); + EXPECT_EQ(101ul, param.getVal()); } { auto param = ParamUInt64(1234); @@ -193,7 +203,7 @@ TEST(EthereumAbi, ParamUInt64) { EXPECT_EQ("00000000000000000000000000000000000000000000000000000000000004d2", hex(encoded)); size_t offset = 0; EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(1234, param.getVal()); + EXPECT_EQ(1234ul, param.getVal()); } } @@ -207,7 +217,7 @@ TEST(EthereumAbi, ParamUInt256) { EXPECT_EQ("uint256", param.getType()); EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32, param.getSize()); + EXPECT_EQ(32ul, param.getSize()); } { auto param = ParamUInt256(uint256_t(101)); @@ -256,7 +266,7 @@ TEST(EthereumAbi, ParamUInt80) { EXPECT_EQ("uint80", param.getType()); EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32, param.getSize()); + EXPECT_EQ(32ul, param.getSize()); // above number of bits, masked param.setVal(load(Data(parse_hex("1010101010101010101010101010101010101010101010101010101010101010")))); @@ -294,7 +304,7 @@ TEST(EthereumAbi, ParamInt80) { EXPECT_EQ("int80", param.getType()); EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32, param.getSize()); + EXPECT_EQ(32ul, param.getSize()); param.setVal(int256_t(-101)); EXPECT_EQ(int256_t(-101), param.getVal()); @@ -350,7 +360,7 @@ TEST(EthereumAbi, ParamString) { EXPECT_EQ("string", param.getType()); EXPECT_TRUE(param.isDynamic()); - EXPECT_EQ(3 * 32, param.getSize()); + EXPECT_EQ(3 * 32ul, param.getSize()); } { auto param = ParamString(helloStr); @@ -361,8 +371,8 @@ TEST(EthereumAbi, ParamString) { "48656c6c6f20576f726c64212020202048656c6c6f20576f726c642120202020" "48656c6c6f20576f726c64210000000000000000000000000000000000000000", hex(encoded)); - EXPECT_EQ(3 * 32, encoded.size()); - EXPECT_EQ(3 * 32, param.getSize()); + EXPECT_EQ(3 * 32ul, encoded.size()); + EXPECT_EQ(3 * 32ul, param.getSize()); size_t offset = 0; EXPECT_TRUE(param.decode(encoded, offset)); EXPECT_EQ(helloStr, param.getVal()); @@ -378,7 +388,7 @@ TEST(EthereumAbi, ParamAddress) { EXPECT_EQ("address", param.getType()); EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32, param.getSize()); + EXPECT_EQ(32ul, param.getSize()); } { auto param = ParamAddress(val1); @@ -413,20 +423,20 @@ TEST(EthereumAbi, ParamByteArray) { EXPECT_EQ("bytes", param.getType()); EXPECT_TRUE(param.isDynamic()); - EXPECT_EQ(2 * 32, param.getSize()); + EXPECT_EQ(2 * 32ul, param.getSize()); } { auto param = ParamByteArray(data10); Data encoded; param.encode(encoded); - EXPECT_EQ(2 * 32, encoded.size()); - EXPECT_EQ(2 * 32, param.getSize()); + EXPECT_EQ(2 * 32ul, encoded.size()); + EXPECT_EQ(2 * 32ul, param.getSize()); EXPECT_EQ( "000000000000000000000000000000000000000000000000000000000000000a" - "3132333435363738393000000000000000000000000000000000000000000000", + "3132333435363738393000000000000000000000000000000000000000000000", hex(encoded)); - EXPECT_EQ(2 * 32, encoded.size()); - EXPECT_EQ(2 * 32, param.getSize()); + EXPECT_EQ(2 * 32ul, encoded.size()); + EXPECT_EQ(2 * 32ul, param.getSize()); size_t offset = 0; EXPECT_TRUE(param.decode(encoded, offset)); EXPECT_EQ(data10, param.getVal()); @@ -441,16 +451,16 @@ TEST(EthereumAbi, ParamByteArrayFix) { EXPECT_EQ("bytes10", param.getType()); EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32, param.getSize()); + EXPECT_EQ(32ul, param.getSize()); } { auto param = ParamByteArrayFix(10, data10); Data encoded; param.encode(encoded); - EXPECT_EQ(32, encoded.size()); - EXPECT_EQ(32, param.getSize()); + EXPECT_EQ(32ul, encoded.size()); + EXPECT_EQ(32ul, param.getSize()); EXPECT_EQ( - "3132333435363738393000000000000000000000000000000000000000000000", + "3132333435363738393000000000000000000000000000000000000000000000", hex(encoded)); size_t offset = 0; EXPECT_TRUE(param.decode(encoded, offset)); @@ -464,14 +474,14 @@ TEST(EthereumAbi, ParamArrayByte) { param.addParam(std::make_shared(49)); param.addParam(std::make_shared(50)); param.addParam(std::make_shared(51)); - EXPECT_EQ(3, param.getVal().size()); + EXPECT_EQ(3ul, param.getVal().size()); EXPECT_EQ(49, (std::dynamic_pointer_cast(param.getVal()[0]))->getVal()); EXPECT_EQ(51, (std::dynamic_pointer_cast(param.getVal()[2]))->getVal()); EXPECT_EQ("uint8[]", param.getType()); EXPECT_TRUE(param.isDynamic()); - EXPECT_EQ((3 + 1) * 32, param.getSize()); - EXPECT_EQ(3, param.getCount()); + EXPECT_EQ((3 + 1) * 32ul, param.getSize()); + EXPECT_EQ(3ul, param.getCount()); } { auto param = ParamArray(); @@ -480,35 +490,86 @@ TEST(EthereumAbi, ParamArrayByte) { param.addParam(std::make_shared(51)); Data encoded; param.encode(encoded); - EXPECT_EQ(4 * 32, encoded.size()); - EXPECT_EQ(4 * 32, param.getSize()); + EXPECT_EQ(4 * 32ul, encoded.size()); + EXPECT_EQ(4 * 32ul, param.getSize()); EXPECT_EQ( "0000000000000000000000000000000000000000000000000000000000000003" "0000000000000000000000000000000000000000000000000000000000000031" "0000000000000000000000000000000000000000000000000000000000000032" "0000000000000000000000000000000000000000000000000000000000000033", hex(encoded)); - EXPECT_EQ(4 * 32, encoded.size()); - EXPECT_EQ(4 * 32, param.getSize()); + EXPECT_EQ(4 * 32ul, encoded.size()); + EXPECT_EQ(4 * 32ul, param.getSize()); size_t offset = 0; EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(3, param.getVal().size()); + EXPECT_EQ(3ul, param.getVal().size()); EXPECT_EQ(49, (std::dynamic_pointer_cast(param.getVal()[0]))->getVal()); EXPECT_EQ(51, (std::dynamic_pointer_cast(param.getVal()[2]))->getVal()); } } +TEST(EthereumAbi, ParamArrayEmpty) { + auto param = ParamArray(); + param.setProto(std::make_shared(0)); + + EXPECT_EQ(0ul, param.getCount()); + Data encoded; + param.encode(encoded); + EXPECT_EQ(32ul, encoded.size()); + EXPECT_EQ(32ul, param.getSize()); + EXPECT_EQ( + "0000000000000000000000000000000000000000000000000000000000000000", + hex(encoded)); + EXPECT_EQ("uint8[]", param.getType()); + EXPECT_EQ("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", hex(param.hashStruct())); +} + +TEST(EthereumAbi, ParamFixedArrayAddress) { + { + auto param = ParamArrayFix({std::make_shared(Data(parse_hex("f784682c82526e245f50975190ef0fff4e4fc077")))}); + EXPECT_EQ(param.getType(), "address[1]"); + EXPECT_EQ(param.getCount(), 1ul); + EXPECT_EQ(param.getSize(), 32ul); + EXPECT_FALSE(param.isDynamic()); + Data encoded; + param.encode(encoded); + EXPECT_EQ(hex(encoded), "000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077"); + std::size_t offset{0}; + EXPECT_TRUE(param.decode(encoded, offset)); + } + { + auto param = ParamArrayFix({std::make_shared(Data(parse_hex("f784682c82526e245f50975190ef0fff4e4fc077"))), + std::make_shared(Data(parse_hex("2e00cd222cb42b616d86d037cc494e8ab7f5c9a3")))}); + EXPECT_EQ(param.getType(), "address[2]"); + EXPECT_EQ(param.getCount(), 2ul); + Data encoded; + param.encode(encoded); + EXPECT_EQ(hex(encoded), "000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc0770000000000000000000000002e00cd222cb42b616d86d037cc494e8ab7f5c9a3"); + std::size_t offset{0}; + EXPECT_TRUE(param.decode(encoded, offset)); + } + { + // nullptr + EXPECT_THROW(ParamArrayFix({nullptr}), std::runtime_error); + // Should be the same type + EXPECT_THROW( + ParamArrayFix({std::make_shared(Data(parse_hex("f784682c82526e245f50975190ef0fff4e4fc077"))), + std::make_shared("Foo")}), + std::runtime_error); + } +} + TEST(EthereumAbi, ParamArrayAddress) { { auto param = ParamArray(); param.addParam(std::make_shared(Data(parse_hex("f784682c82526e245f50975190ef0fff4e4fc077")))); param.addParam(std::make_shared(Data(parse_hex("2e00cd222cb42b616d86d037cc494e8ab7f5c9a3")))); - EXPECT_EQ(2, param.getVal().size()); + EXPECT_EQ(2ul, param.getVal().size()); EXPECT_EQ("address[]", param.getType()); EXPECT_TRUE(param.isDynamic()); - EXPECT_EQ((2 + 1) * 32, param.getSize()); - EXPECT_EQ(2, param.getCount()); + EXPECT_EQ((2 + 1) * 32ul, param.getSize()); + EXPECT_EQ(2ul, param.getCount()); } { auto param = ParamArray(); @@ -516,21 +577,21 @@ TEST(EthereumAbi, ParamArrayAddress) { param.addParam(std::make_shared(Data(parse_hex("2e00cd222cb42b616d86d037cc494e8ab7f5c9a3")))); Data encoded; param.encode(encoded); - EXPECT_EQ(3 * 32, encoded.size()); - EXPECT_EQ(3 * 32, param.getSize()); + EXPECT_EQ(3 * 32ul, encoded.size()); + EXPECT_EQ(3 * 32ul, param.getSize()); EXPECT_EQ( "0000000000000000000000000000000000000000000000000000000000000002" "000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077" "0000000000000000000000002e00cd222cb42b616d86d037cc494e8ab7f5c9a3", hex(encoded)); - EXPECT_EQ(3 * 32, encoded.size()); - EXPECT_EQ(3 * 32, param.getSize()); + EXPECT_EQ(3 * 32ul, encoded.size()); + EXPECT_EQ(3 * 32ul, param.getSize()); size_t offset = 0; EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(2, param.getCount()); - EXPECT_EQ(2, param.getVal().size()); + EXPECT_EQ(2ul, param.getCount()); + EXPECT_EQ(2ul, param.getVal().size()); EXPECT_EQ( - "2e00cd222cb42b616d86d037cc494e8ab7f5c9a3", + "2e00cd222cb42b616d86d037cc494e8ab7f5c9a3", hex((std::dynamic_pointer_cast(param.getVal()[1]))->getData())); } } @@ -540,12 +601,12 @@ TEST(EthereumAbi, ParamArrayOfByteArray) { param.addParam(std::make_shared(parse_hex("1011"))); param.addParam(std::make_shared(parse_hex("102222"))); param.addParam(std::make_shared(parse_hex("10333333"))); - EXPECT_EQ(3, param.getVal().size()); + EXPECT_EQ(3ul, param.getVal().size()); EXPECT_EQ("bytes[]", param.getType()); EXPECT_TRUE(param.isDynamic()); - EXPECT_EQ((1 + 3 + 3 * 2) * 32, param.getSize()); - EXPECT_EQ(3, param.getCount()); + EXPECT_EQ((1 + 3 + 3 * 2) * 32ul, param.getSize()); + EXPECT_EQ(3ul, param.getCount()); } TEST(EthereumAbi, ParamArrayBytesContract) { @@ -554,17 +615,17 @@ TEST(EthereumAbi, ParamArrayBytesContract) { param.addParam(std::make_shared(parse_hex("0x304e6adee71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000"))); param.addParam(std::make_shared(parse_hex("0x8b95dd71e71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001447331175b23c2f067204b506ca1501c26731c990000000000000000000000000"))); param.addParam(std::make_shared(parse_hex("0x8b95dd71e71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f00000000000000000000000000000000000000000000000000000000000002ca00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000014d30f834b53d8f7e851e87b90ffa65757a35b8505000000000000000000000000"))); - EXPECT_EQ(4, param.getCount()); - EXPECT_EQ(4, param.getVal().size()); + EXPECT_EQ(4ul, param.getCount()); + EXPECT_EQ(4ul, param.getVal().size()); - EXPECT_EQ("bytes[]", param.getType()); + EXPECT_EQ("bytes[]", param.getType()); EXPECT_TRUE(param.isDynamic()); Data encoded; param.encode(encoded); - EXPECT_EQ(896, encoded.size()); + EXPECT_EQ(896ul, encoded.size()); - EXPECT_EQ(896, param.getSize()); + EXPECT_EQ(896ul, param.getSize()); } TEST(EthereumAbi, ParamTupleStatic) { @@ -574,18 +635,18 @@ TEST(EthereumAbi, ParamTupleStatic) { param.addParam(std::make_shared(123)); EXPECT_EQ("(bool,uint64)", param.getType()); EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(2, param.getCount()); - EXPECT_EQ(64, param.getSize()); + EXPECT_EQ(2ul, param.getCount()); + EXPECT_EQ(64ul, param.getSize()); Data encoded; param.encode(encoded); EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000007b", hex(encoded)); - { // decode + { // decode size_t offset = 0; auto param2 = ParamTuple(); param2.addParam(std::make_shared()); param2.addParam(std::make_shared()); EXPECT_TRUE(param2.decode(encoded, offset)); - EXPECT_EQ(2, param2.getCount()); + EXPECT_EQ(2ul, param2.getCount()); Data encoded2; param2.encode(encoded2); EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000007b", hex(encoded)); @@ -597,8 +658,8 @@ TEST(EthereumAbi, ParamTupleStatic) { param.addParam(std::make_shared(parse_hex("000102030405060708090a0b0c0d0e0f10111213"))); EXPECT_EQ("(uint64,address)", param.getType()); EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(2, param.getCount()); - EXPECT_EQ(64, param.getSize()); + EXPECT_EQ(2ul, param.getCount()); + EXPECT_EQ(64ul, param.getSize()); Data encoded; param.encode(encoded); EXPECT_EQ("00000000000000000000000000000000000000000000000000000000000001c8000000000000000000000000000102030405060708090a0b0c0d0e0f10111213", hex(encoded)); @@ -613,8 +674,8 @@ TEST(EthereumAbi, ParamTupleDynamic) { param.addParam(std::make_shared(parse_hex("00010203040506070809"))); EXPECT_EQ("(string,uint64,bytes)", param.getType()); EXPECT_TRUE(param.isDynamic()); - EXPECT_EQ(3, param.getCount()); - EXPECT_EQ(7 * 32, param.getSize()); + EXPECT_EQ(3ul, param.getCount()); + EXPECT_EQ(7 * 32ul, param.getSize()); Data encoded; param.encode(encoded); EXPECT_EQ( @@ -624,7 +685,8 @@ TEST(EthereumAbi, ParamTupleDynamic) { "0000000000000000000000000000000000000000000000000000000000000014" // len "446f6e27742074727573742c2076657269667921000000000000000000000000" "000000000000000000000000000000000000000000000000000000000000000a" // len - "0001020304050607080900000000000000000000000000000000000000000000", hex(encoded)); + "0001020304050607080900000000000000000000000000000000000000000000", + hex(encoded)); } } @@ -645,14 +707,14 @@ TEST(EthereumAbi, EncodeVectorByte) { p.encode(encoded); EXPECT_EQ( "000000000000000000000000000000000000000000000000000000000000000a" - "3132333435363738393000000000000000000000000000000000000000000000", hex(encoded)); + "3132333435363738393000000000000000000000000000000000000000000000", + hex(encoded)); } TEST(EthereumAbi, EncodeArrayByte) { auto p = ParamArray(std::vector>{ std::make_shared(parse_hex("1011")), - std::make_shared(parse_hex("102222")) - }); + std::make_shared(parse_hex("102222"))}); EXPECT_EQ("bytes[]", p.getType()); Data encoded; p.encode(encoded); @@ -664,10 +726,9 @@ TEST(EthereumAbi, EncodeArrayByte) { "1011000000000000000000000000000000000000000000000000000000000000" "0000000000000000000000000000000000000000000000000000000000000003" "1022220000000000000000000000000000000000000000000000000000000000", - hex(encoded) - ); - EXPECT_EQ((1 + 2 + 2 * 2) * 32, encoded.size()); - EXPECT_EQ((1 + 2 + 2 * 2) * 32, p.getSize()); + hex(encoded)); + EXPECT_EQ((1 + 2 + 2 * 2) * 32ul, encoded.size()); + EXPECT_EQ((1 + 2 + 2 * 2) * 32ul, p.getSize()); } TEST(EthereumAbi, DecodeUInt) { @@ -677,7 +738,7 @@ TEST(EthereumAbi, DecodeUInt) { bool res = ParamNumberType::decodeNumber(encoded, decoded, offset); EXPECT_TRUE(res); EXPECT_EQ(uint256_t(42), decoded); - EXPECT_EQ(32, offset); + EXPECT_EQ(32ul, offset); } TEST(EthereumAbi, DecodeUInt8) { @@ -687,7 +748,7 @@ TEST(EthereumAbi, DecodeUInt8) { bool res = ParamNumberType::decodeNumber(encoded, decoded, offset); EXPECT_TRUE(res); EXPECT_EQ(24, decoded); - EXPECT_EQ(32, offset); + EXPECT_EQ(32ul, offset); } TEST(EthereumAbi, DecodeUInt8WithOffset) { @@ -697,7 +758,7 @@ TEST(EthereumAbi, DecodeUInt8WithOffset) { bool res = ParamNumberType::decodeNumber(encoded, decoded, offset); EXPECT_TRUE(res); EXPECT_EQ(24, decoded); - EXPECT_EQ(3 + 32, offset); + EXPECT_EQ(3 + 32ul, offset); } TEST(EthereumAbi, DecodeUIntWithOffset) { @@ -707,7 +768,7 @@ TEST(EthereumAbi, DecodeUIntWithOffset) { bool res = decode(encoded, decoded, offset); EXPECT_TRUE(res); EXPECT_EQ(uint256_t(42), decoded); - EXPECT_EQ(3 + 32, offset); + EXPECT_EQ(3 + 32ul, offset); } TEST(EthereumAbi, DecodeUIntErrorTooShort) { @@ -717,7 +778,7 @@ TEST(EthereumAbi, DecodeUIntErrorTooShort) { bool res = decode(encoded, decoded, offset); EXPECT_FALSE(res); EXPECT_EQ(uint256_t(0), decoded); - EXPECT_EQ(0, offset); + EXPECT_EQ(0ul, offset); } TEST(EthereumAbi, DecodeArrayUint) { @@ -728,12 +789,12 @@ TEST(EthereumAbi, DecodeArrayUint) { std::vector decoded; bool res = ParamByteArray::decodeBytes(encoded, decoded, offset); EXPECT_TRUE(res); - EXPECT_EQ(10, decoded.size()); + EXPECT_EQ(10ul, decoded.size()); if (decoded.size() >= 2) { EXPECT_EQ(49u, decoded[0]); EXPECT_EQ(50u, decoded[1]); } - EXPECT_EQ(32 + 32, offset); + EXPECT_EQ(32 + 32ul, offset); } TEST(EthereumAbi, DecodeArrayTooShort) { @@ -763,7 +824,7 @@ TEST(EthereumAbi, DecodeByteArray) { bool res = ParamByteArray::decodeBytes(encoded, decoded, offset); EXPECT_TRUE(res); EXPECT_EQ("31323334353637383930", hex(decoded)); - EXPECT_EQ(32 + 32, offset); + EXPECT_EQ(32 + 32ul, offset); } TEST(EthereumAbi, DecodeByteArray10) { @@ -772,10 +833,10 @@ TEST(EthereumAbi, DecodeByteArray10) { Data decoded; bool res = ParamByteArrayFix::decodeBytesFix(encoded, 10, decoded, offset); EXPECT_TRUE(res); - EXPECT_EQ(10, decoded.size()); + EXPECT_EQ(10ul, decoded.size()); EXPECT_EQ(49u, decoded[0]); EXPECT_EQ(50u, decoded[1]); - EXPECT_EQ(32, offset); + EXPECT_EQ(32ul, offset); } TEST(EthereumAbi, DecodeArrayOfByteArray) { @@ -786,8 +847,7 @@ TEST(EthereumAbi, DecodeArrayOfByteArray) { "0000000000000000000000000000000000000000000000000000000000000002" "1011000000000000000000000000000000000000000000000000000000000000" "0000000000000000000000000000000000000000000000000000000000000003" - "1022200000000000000000000000000000000000000000000000000000000000" - ); + "1022200000000000000000000000000000000000000000000000000000000000"); size_t offset = 0; Data decoded; auto param = ParamArray(); @@ -795,24 +855,24 @@ TEST(EthereumAbi, DecodeArrayOfByteArray) { param.addParam(std::make_shared(Data())); bool res = param.decode(encoded, offset); EXPECT_TRUE(res); - EXPECT_EQ(2, param.getCount()); - EXPECT_EQ(7 * 32, offset); - EXPECT_EQ(2, param.getVal().size()); + EXPECT_EQ(2ul, param.getCount()); + EXPECT_EQ(7 * 32ul, offset); + EXPECT_EQ(2ul, param.getVal().size()); } ///// Parameters encode & decode TEST(EthereumAbi, EncodeParamsSimple) { - auto p = Parameters(std::vector>{ + auto p = Parameters(std::vector>{ std::make_shared(16u), std::make_shared(17u), - std::make_shared(true) }); + std::make_shared(true)}); EXPECT_EQ("(uint256,uint256,bool)", p.getType()); Data encoded; p.encode(encoded); - EXPECT_EQ(3 * 32, encoded.size()); - EXPECT_EQ(3 * 32, p.getSize()); + EXPECT_EQ(3 * 32ul, encoded.size()); + EXPECT_EQ(3 * 32ul, p.getSize()); EXPECT_EQ( "0000000000000000000000000000000000000000000000000000000000000010" "0000000000000000000000000000000000000000000000000000000000000011" @@ -822,22 +882,20 @@ TEST(EthereumAbi, EncodeParamsSimple) { TEST(EthereumAbi, EncodeParamsMixed) { auto p = Parameters(std::vector>{ - std::make_shared(69u), + std::make_shared(69u), std::make_shared(std::vector>{ std::make_shared(1), std::make_shared(2), - std::make_shared(3) - }), + std::make_shared(3)}), std::make_shared(true), std::make_shared("Hello"), - std::make_shared(Data{0x64, 0x61, 0x76, 0x65}) - }); + std::make_shared(Data{0x64, 0x61, 0x76, 0x65})}); EXPECT_EQ("(uint256,uint256[],bool,string,bytes)", p.getType()); Data encoded; p.encode(encoded); - EXPECT_EQ(13 * 32, encoded.size()); - EXPECT_EQ(13 * 32, p.getSize()); + EXPECT_EQ(13 * 32ul, encoded.size()); + EXPECT_EQ(13 * 32ul, p.getSize()); EXPECT_EQ( "0000000000000000000000000000000000000000000000000000000000000045" "00000000000000000000000000000000000000000000000000000000000000a0" @@ -879,8 +937,7 @@ TEST(EthereumAbi, DecodeParamsSimple) { auto p = Parameters(std::vector>{ std::make_shared(0), std::make_shared(0), - std::make_shared(false) - }); + std::make_shared(false)}); EXPECT_EQ("(uint256,uint256,bool)", p.getType()); size_t offset = 0; bool res = p.decode(encoded, offset); @@ -888,7 +945,7 @@ TEST(EthereumAbi, DecodeParamsSimple) { EXPECT_EQ(uint256_t(16u), (std::dynamic_pointer_cast(p.getParam(0)))->getVal()); EXPECT_EQ(uint256_t(17u), (std::dynamic_pointer_cast(p.getParam(1)))->getVal()); EXPECT_EQ(true, (std::dynamic_pointer_cast(p.getParam(2)))->getVal()); - EXPECT_EQ(3 * 32, offset); + EXPECT_EQ(3 * 32ul, offset); } TEST(EthereumAbi, DecodeParamsMixed) { @@ -906,8 +963,9 @@ TEST(EthereumAbi, DecodeParamsMixed) { append(encoded, parse_hex("48656c6c6f000000000000000000000000000000000000000000000000000000")); append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000004")); append(encoded, parse_hex("6461766500000000000000000000000000000000000000000000000000000000")); + // clang-format off auto p = Parameters(std::vector>{ - std::make_shared(), + std::make_shared(), std::make_shared(std::vector>{ std::make_shared(), std::make_shared(), @@ -917,37 +975,41 @@ TEST(EthereumAbi, DecodeParamsMixed) { std::make_shared(), std::make_shared() }); + // clang-format on EXPECT_EQ("(uint256,uint256[],bool,string,bytes)", p.getType()); size_t offset = 0; bool res = p.decode(encoded, offset); EXPECT_TRUE(res); EXPECT_EQ(uint256_t(69u), (std::dynamic_pointer_cast(p.getParam(0)))->getVal()); - EXPECT_EQ(3, (std::dynamic_pointer_cast(p.getParam(1)))->getCount()); + EXPECT_EQ(3ul, (std::dynamic_pointer_cast(p.getParam(1)))->getCount()); EXPECT_EQ(1, (std::dynamic_pointer_cast((std::dynamic_pointer_cast(p.getParam(1)))->getParam(0)))->getVal()); EXPECT_EQ(3, (std::dynamic_pointer_cast((std::dynamic_pointer_cast(p.getParam(1)))->getParam(2)))->getVal()); EXPECT_EQ(true, (std::dynamic_pointer_cast(p.getParam(2)))->getVal()); EXPECT_EQ("Hello", (std::dynamic_pointer_cast(p.getParam(3)))->getVal()); - EXPECT_EQ(13 * 32, offset); + EXPECT_EQ(13 * 32ul, offset); } ///// Function encode & decode TEST(EthereumAbi, EncodeSignature) { + // clang-format off auto func = Function("baz", std::vector>{ std::make_shared(69u), std::make_shared(true) }); + // clang-format on EXPECT_EQ("baz(uint256,bool)", func.getType()); Data encoded; func.encode(encoded); - EXPECT_EQ(encoded.size(), 32 * 2 + 4); + EXPECT_EQ(encoded.size(), 32 * 2 + 4ul); EXPECT_EQ(hex(encoded.begin(), encoded.begin() + 4), "72ed38b6"); - EXPECT_EQ(hex(encoded.begin() + 4, encoded.begin() + 36 ), "0000000000000000000000000000000000000000000000000000000000000045"); - EXPECT_EQ(hex(encoded.begin() + 36, encoded.begin() + 68 ), "0000000000000000000000000000000000000000000000000000000000000001"); + EXPECT_EQ(hex(encoded.begin() + 4, encoded.begin() + 36), "0000000000000000000000000000000000000000000000000000000000000045"); + EXPECT_EQ(hex(encoded.begin() + 36, encoded.begin() + 68), "0000000000000000000000000000000000000000000000000000000000000001"); } TEST(EthereumAbi, EncodeFunctionWithDynamicArgumentsCase1) { + // clang-format off auto func = Function("sam", std::vector>{ std::make_shared(Data{0x64, 0x61, 0x76, 0x65}), std::make_shared(true), @@ -957,15 +1019,16 @@ TEST(EthereumAbi, EncodeFunctionWithDynamicArgumentsCase1) { std::make_shared(3) }) }); + // clang-format on EXPECT_EQ("sam(bytes,bool,uint256[])", func.getType()); Data encoded; func.encode(encoded); - EXPECT_EQ(encoded.size(), 32 * 9 + 4); - EXPECT_EQ(hex(encoded.begin() + 0, encoded.begin() + 4 ), "a5643bf2"); - EXPECT_EQ(hex(encoded.begin() + 4, encoded.begin() + 36 ), "0000000000000000000000000000000000000000000000000000000000000060"); - EXPECT_EQ(hex(encoded.begin() + 36, encoded.begin() + 68 ), "0000000000000000000000000000000000000000000000000000000000000001"); - EXPECT_EQ(hex(encoded.begin() + 68, encoded.begin() + 100), "00000000000000000000000000000000000000000000000000000000000000a0"); + EXPECT_EQ(encoded.size(), 32 * 9 + 4ul); + EXPECT_EQ(hex(encoded.begin() + 0, encoded.begin() + 4), "a5643bf2"); + EXPECT_EQ(hex(encoded.begin() + 4, encoded.begin() + 36), "0000000000000000000000000000000000000000000000000000000000000060"); + EXPECT_EQ(hex(encoded.begin() + 36, encoded.begin() + 68), "0000000000000000000000000000000000000000000000000000000000000001"); + EXPECT_EQ(hex(encoded.begin() + 68, encoded.begin() + 100), "00000000000000000000000000000000000000000000000000000000000000a0"); EXPECT_EQ(hex(encoded.begin() + 100, encoded.begin() + 132), "0000000000000000000000000000000000000000000000000000000000000004"); EXPECT_EQ(hex(encoded.begin() + 132, encoded.begin() + 164), "6461766500000000000000000000000000000000000000000000000000000000"); EXPECT_EQ(hex(encoded.begin() + 164, encoded.begin() + 196), "0000000000000000000000000000000000000000000000000000000000000003"); @@ -975,24 +1038,25 @@ TEST(EthereumAbi, EncodeFunctionWithDynamicArgumentsCase1) { } TEST(EthereumAbi, EncodeFunctionWithDynamicArgumentsCase2) { + // clang-format off auto func = Function("f", std::vector>{ std::make_shared(0x123), std::make_shared(std::vector>{ std::make_shared(0x456), - std::make_shared(0x789) - }), + std::make_shared(0x789)}), std::make_shared(10, std::vector{0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30}), std::make_shared("Hello, world!") }); + // clamp-format on EXPECT_EQ("f(uint256,uint32[],bytes10,string)", func.getType()); Data encoded; func.encode(encoded); - EXPECT_EQ(encoded.size(), 32 * 9 + 4); - EXPECT_EQ(hex(encoded.begin() + 0, encoded.begin() + 4 ), "47b941bf"); - EXPECT_EQ(hex(encoded.begin() + 4, encoded.begin() + 36 ), "0000000000000000000000000000000000000000000000000000000000000123"); - EXPECT_EQ(hex(encoded.begin() + 36, encoded.begin() + 68 ), "0000000000000000000000000000000000000000000000000000000000000080"); - EXPECT_EQ(hex(encoded.begin() + 68, encoded.begin() + 100), "3132333435363738393000000000000000000000000000000000000000000000"); + EXPECT_EQ(encoded.size(), 32 * 9 + 4ul); + EXPECT_EQ(hex(encoded.begin() + 0, encoded.begin() + 4), "47b941bf"); + EXPECT_EQ(hex(encoded.begin() + 4, encoded.begin() + 36), "0000000000000000000000000000000000000000000000000000000000000123"); + EXPECT_EQ(hex(encoded.begin() + 36, encoded.begin() + 68), "0000000000000000000000000000000000000000000000000000000000000080"); + EXPECT_EQ(hex(encoded.begin() + 68, encoded.begin() + 100), "3132333435363738393000000000000000000000000000000000000000000000"); EXPECT_EQ(hex(encoded.begin() + 100, encoded.begin() + 132), "00000000000000000000000000000000000000000000000000000000000000e0"); EXPECT_EQ(hex(encoded.begin() + 132, encoded.begin() + 164), "0000000000000000000000000000000000000000000000000000000000000002"); EXPECT_EQ(hex(encoded.begin() + 164, encoded.begin() + 196), "0000000000000000000000000000000000000000000000000000000000000456"); @@ -1005,54 +1069,56 @@ TEST(EthereumAbi, DecodeFunctionOutputCase1) { Data encoded; append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000045")); + // clang-format off auto func = Function("readout", std::vector>{ std::make_shared(parse_hex("f784682c82526e245f50975190ef0fff4e4fc077")), std::make_shared(1000) }); + // clang-format on func.addOutParam(std::make_shared()); EXPECT_EQ("readout(address,uint64)", func.getType()); // original output value std::shared_ptr param; EXPECT_TRUE(func.getOutParam(0, param)); - EXPECT_EQ(0, (std::dynamic_pointer_cast(param))->getVal()); + EXPECT_EQ(0ul, (std::dynamic_pointer_cast(param))->getVal()); size_t offset = 0; bool res = func.decodeOutput(encoded, offset); EXPECT_TRUE(res); - EXPECT_EQ(32, offset); + EXPECT_EQ(32ul, offset); // new output value - EXPECT_EQ(0x45, (std::dynamic_pointer_cast(param))->getVal()); + EXPECT_EQ(0x45ul, (std::dynamic_pointer_cast(param))->getVal()); } TEST(EthereumAbi, DecodeFunctionOutputCase2) { + // clang-format off auto func = Function("getAmountsOut", std::vector>{ std::make_shared(100), std::make_shared(std::make_shared(parse_hex("000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077"))) }); + // clang-format on func.addOutParam(std::make_shared(std::vector>{ std::make_shared(66), - std::make_shared(67) - })); + std::make_shared(67)})); EXPECT_EQ("getAmountsOut(uint256,address[])", func.getType()); Data encoded; append(encoded, parse_hex( - "0000000000000000000000000000000000000000000000000000000000000020" - "0000000000000000000000000000000000000000000000000000000000000002" - "0000000000000000000000000000000000000000000000000000000000000004" - "0000000000000000000000000000000000000000000000000000000000000005" - )); + "0000000000000000000000000000000000000000000000000000000000000020" + "0000000000000000000000000000000000000000000000000000000000000002" + "0000000000000000000000000000000000000000000000000000000000000004" + "0000000000000000000000000000000000000000000000000000000000000005")); size_t offset = 0; bool res = func.decodeOutput(encoded, offset); EXPECT_TRUE(res); - EXPECT_EQ(128, offset); + EXPECT_EQ(128ul, offset); // new output values std::shared_ptr param; EXPECT_TRUE(func.getOutParam(0, param)); - EXPECT_EQ(2, (std::dynamic_pointer_cast(param))->getCount()); + EXPECT_EQ(2ul, (std::dynamic_pointer_cast(param))->getCount()); EXPECT_EQ(4, (std::dynamic_pointer_cast((std::dynamic_pointer_cast(param))->getParam(0)))->getVal()); EXPECT_EQ(5, (std::dynamic_pointer_cast((std::dynamic_pointer_cast(param))->getParam(1)))->getVal()); } @@ -1062,9 +1128,11 @@ TEST(EthereumAbi, DecodeInputSignature) { append(encoded, parse_hex("72ed38b6")); append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000045")); append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000001")); + // clang-format off auto func = Function("baz", std::vector>{ std::make_shared(), std::make_shared() }); + // clang-format on EXPECT_EQ("baz(uint256,bool)", func.getType()); size_t offset = 0; bool res = func.decodeInput(encoded, offset); @@ -1074,7 +1142,7 @@ TEST(EthereumAbi, DecodeInputSignature) { EXPECT_EQ(69u, (std::dynamic_pointer_cast(param))->getVal()); EXPECT_TRUE(func.getInParam(1, param)); EXPECT_EQ(true, (std::dynamic_pointer_cast(param))->getVal()); - EXPECT_EQ(4 + 2 * 32, offset); + EXPECT_EQ(4 + 2 * 32ul, offset); } TEST(EthereumAbi, DecodeFunctionInputWithDynamicArgumentsCase1) { @@ -1090,6 +1158,7 @@ TEST(EthereumAbi, DecodeFunctionInputWithDynamicArgumentsCase1) { append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000002")); append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000003")); + // clang-format off auto func = Function("sam", std::vector>{ std::make_shared(Data{0x64, 0x61, 0x76, 0x65}), std::make_shared(true), @@ -1099,6 +1168,7 @@ TEST(EthereumAbi, DecodeFunctionInputWithDynamicArgumentsCase1) { std::make_shared(3) }) }); + // clang-format on EXPECT_EQ("sam(bytes,bool,uint256[])", func.getType()); size_t offset = 0; @@ -1106,16 +1176,16 @@ TEST(EthereumAbi, DecodeFunctionInputWithDynamicArgumentsCase1) { EXPECT_TRUE(res); std::shared_ptr param; EXPECT_TRUE(func.getInParam(0, param)); - EXPECT_EQ(4, (std::dynamic_pointer_cast(param))->getCount()); + EXPECT_EQ(4ul, (std::dynamic_pointer_cast(param))->getCount()); EXPECT_EQ(0x64, (std::dynamic_pointer_cast(param))->getVal()[0]); EXPECT_EQ(0x65, (std::dynamic_pointer_cast(param))->getVal()[3]); EXPECT_TRUE(func.getInParam(1, param)); EXPECT_EQ(true, (std::dynamic_pointer_cast(param))->getVal()); EXPECT_TRUE(func.getInParam(2, param)); - EXPECT_EQ(3, (std::dynamic_pointer_cast(param))->getCount()); + EXPECT_EQ(3ul, (std::dynamic_pointer_cast(param))->getCount()); EXPECT_EQ(uint256_t(1), (std::dynamic_pointer_cast((std::dynamic_pointer_cast(param))->getVal()[0]))->getVal()); EXPECT_EQ(uint256_t(3), (std::dynamic_pointer_cast((std::dynamic_pointer_cast(param))->getVal()[2]))->getVal()); - EXPECT_EQ(4 + 9 * 32, offset); + EXPECT_EQ(4 + 9 * 32ul, offset); } TEST(EthereumAbi, DecodeFunctionInputWithDynamicArgumentsCase2) { @@ -1131,15 +1201,16 @@ TEST(EthereumAbi, DecodeFunctionInputWithDynamicArgumentsCase2) { append(encoded, parse_hex("000000000000000000000000000000000000000000000000000000000000000d")); append(encoded, parse_hex("48656c6c6f2c20776f726c642100000000000000000000000000000000000000")); + // clang-format off auto func = Function("f", std::vector>{ std::make_shared(0x123), std::make_shared(std::vector>{ std::make_shared(0x456), - std::make_shared(0x789) - }), + std::make_shared(0x789)}), std::make_shared(10, std::vector{0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30}), std::make_shared("Hello, world!") }); + // clang-format on EXPECT_EQ("f(uint256,uint32[],bytes10,string)", func.getType()); size_t offset = 0; @@ -1149,15 +1220,15 @@ TEST(EthereumAbi, DecodeFunctionInputWithDynamicArgumentsCase2) { EXPECT_TRUE(func.getInParam(0, param)); EXPECT_EQ(uint256_t(0x123), (std::dynamic_pointer_cast(param))->getVal()); EXPECT_TRUE(func.getInParam(1, param)); - EXPECT_EQ(2, (std::dynamic_pointer_cast(param))->getCount()); - EXPECT_EQ(0x456, (std::dynamic_pointer_cast((std::dynamic_pointer_cast(param))->getVal()[0]))->getVal()); - EXPECT_EQ(0x789, (std::dynamic_pointer_cast((std::dynamic_pointer_cast(param))->getVal()[1]))->getVal()); + EXPECT_EQ(2ul, (std::dynamic_pointer_cast(param))->getCount()); + EXPECT_EQ(0x456ul, (std::dynamic_pointer_cast((std::dynamic_pointer_cast(param))->getVal()[0]))->getVal()); + EXPECT_EQ(0x789ul, (std::dynamic_pointer_cast((std::dynamic_pointer_cast(param))->getVal()[1]))->getVal()); EXPECT_TRUE(func.getInParam(2, param)); - EXPECT_EQ(10, (std::dynamic_pointer_cast(param))->getCount()); + EXPECT_EQ(10ul, (std::dynamic_pointer_cast(param))->getCount()); EXPECT_EQ("31323334353637383930", hex((std::dynamic_pointer_cast(param))->getVal())); EXPECT_TRUE(func.getInParam(3, param)); EXPECT_EQ(std::string("Hello, world!"), (std::dynamic_pointer_cast(param))->getVal()); - EXPECT_EQ(4 + 9 * 32, offset); + EXPECT_EQ(4 + 9 * 32ul, offset); } TEST(EthereumAbi, DecodeFunctionContractMulticall) { @@ -1183,8 +1254,9 @@ TEST(EthereumAbi, DecodeFunctionContractMulticall) { "000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000" "000000000000000000000000000000000014d30f834b53d8f7e851e87b90ffa65757a35b850500000000000000" "000000000000000000000000000000000000000000000000000000000000000000"); - ASSERT_EQ(4 + 928, encoded.size()); + ASSERT_EQ(4 + 928ul, encoded.size()); + // clang-format off auto func = Function("multicall", std::vector>{ std::make_shared(std::vector>{ std::make_shared(Data()), @@ -1193,12 +1265,13 @@ TEST(EthereumAbi, DecodeFunctionContractMulticall) { std::make_shared(Data()) }), }); + // clang-format on EXPECT_EQ("multicall(bytes[])", func.getType()); size_t offset = 0; bool res = func.decodeInput(encoded, offset); EXPECT_TRUE(res); - EXPECT_EQ(4 + 29 * 32, offset); + EXPECT_EQ(4 + 29 * 32ul, offset); } TEST(EthereumAbi, ParamFactoryMake) { @@ -1266,18 +1339,18 @@ TEST(EthereumAbi, ParamFactoryGetArrayValue) { { auto pArray = std::make_shared(std::make_shared()); const auto vals = ParamFactory::getArrayValue(pArray, pArray->getType()); - ASSERT_EQ(vals.size(), 1); + ASSERT_EQ(vals.size(), 1ul); EXPECT_EQ(vals[0], "0"); } - { // wrong type, not array + { // wrong type, not array auto pArray = std::make_shared(std::make_shared()); const auto vals = ParamFactory::getArrayValue(pArray, "bool"); - EXPECT_EQ(vals.size(), 0); + EXPECT_EQ(vals.size(), 0ul); } - { // wrong param, not array + { // wrong param, not array auto pArray = std::make_shared(); const auto vals = ParamFactory::getArrayValue(pArray, "uint8[]"); - EXPECT_EQ(vals.size(), 0); + EXPECT_EQ(vals.size(), 0ul); } } @@ -1429,7 +1502,7 @@ TEST(EthereumAbi, ParamFactorySetGetValue) { EXPECT_EQ("", ParamFactory::getValue(p, p->getType())); EXPECT_TRUE(p->setValueJson("ABCdefGHI")); EXPECT_EQ("ABCdefGHI", ParamFactory::getValue(p, p->getType())); - EXPECT_EQ(9, p->getCount()); + EXPECT_EQ(9ul, p->getCount()); } { auto p = std::make_shared(); @@ -1478,12 +1551,31 @@ TEST(EthereumAbi, ParamFactorySetGetValue) { TEST(EthereumAbi, ParamFactoryGetValue) { const std::vector types = { - "uint8", "uint16", "uint32", "uint64", "uint128", "uint168", "uint256", - "int8", "int16", "int32", "int64", "int128", "int168", "int256", - "bool", "string", "bytes", "bytes168", "address", - "uint8[]", "address[]", "bool[]", "bytes[]", + "uint8", + "uint16", + "uint32", + "uint64", + "uint128", + "uint168", + "uint256", + "int8", + "int16", + "int32", + "int64", + "int128", + "int168", + "int256", + "bool", + "string", + "bytes", + "bytes168", + "address", + "uint8[]", + "address[]", + "bool[]", + "bytes[]", }; - for (auto t: types) { + for (auto t : types) { std::shared_ptr p = ParamFactory::make(t); EXPECT_EQ(t, p->getType()); @@ -1521,8 +1613,8 @@ TEST(EthereumAbi, ParamSetMethods) { { auto p = ParamSet(std::vector>{ std::make_shared(16u), - std::make_shared(true) }); - EXPECT_EQ(p.getCount(), 2); + std::make_shared(true)}); + EXPECT_EQ(p.getCount(), 2ul); EXPECT_EQ(p.addParam(std::shared_ptr(nullptr)), -1); std::shared_ptr getparam; @@ -1544,9 +1636,11 @@ TEST(EthereumAbi, ParamSetMethods) { TEST(EthereumAbi, ParametersMethods) { auto p = Parameters(std::vector>{ std::make_shared(16u), - std::make_shared(true) }); + std::make_shared(true)}); EXPECT_TRUE(p.isDynamic()); - EXPECT_EQ(p.getCount(), 2); + EXPECT_EQ(p.getCount(), 2ul); EXPECT_FALSE(p.setValueJson("value")); EXPECT_EQ(hex(p.hashStruct()), "755311b9e2cee471a91b161ccc5deed933d844b5af2b885543cc3c04eb640983"); } + +} // namespace TW::Ethereum::ABI::tests diff --git a/tests/Ethereum/AddressTests.cpp b/tests/chains/Ethereum/AddressTests.cpp similarity index 84% rename from tests/Ethereum/AddressTests.cpp rename to tests/chains/Ethereum/AddressTests.cpp index f2fab5a7c5a..2c4ca5745e7 100644 --- a/tests/Ethereum/AddressTests.cpp +++ b/tests/chains/Ethereum/AddressTests.cpp @@ -11,7 +11,8 @@ #include using namespace TW; -using namespace TW::Ethereum; + +namespace TW::Ethereum::tests { TEST(EthereumAddress, Invalid) { ASSERT_FALSE(Address::isValid("abc")); @@ -22,24 +23,19 @@ TEST(EthereumAddress, Invalid) { TEST(EthereumAddress, EIP55) { ASSERT_EQ( Address(parse_hex("5aaeb6053f3e94c9b9a09f33669435e7ef1beaed")).string(), - "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed" - ); + "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"); ASSERT_EQ( Address(parse_hex("0x5AAEB6053F3E94C9b9A09f33669435E7Ef1BEAED")).string(), - "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed" - ); + "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"); ASSERT_EQ( Address(parse_hex("0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359")).string(), - "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359" - ); + "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359"); ASSERT_EQ( Address(parse_hex("0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB")).string(), - "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB" - ); + "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB"); ASSERT_EQ( Address(parse_hex("0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb")).string(), - "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb" - ); + "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb"); } TEST(EthereumAddress, String) { @@ -59,3 +55,5 @@ TEST(EthereumAddress, IsValid) { ASSERT_FALSE(Address::isValid("abc")); ASSERT_TRUE(Address::isValid("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")); } + +} // namespace TW::Ethereum::tests diff --git a/tests/Ethereum/ContractCallTests.cpp b/tests/chains/Ethereum/ContractCallTests.cpp similarity index 94% rename from tests/Ethereum/ContractCallTests.cpp rename to tests/chains/Ethereum/ContractCallTests.cpp index f7e27e1e634..bf21bcd21cb 100644 --- a/tests/Ethereum/ContractCallTests.cpp +++ b/tests/chains/Ethereum/ContractCallTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -10,11 +10,10 @@ #include #include -using namespace TW; -using namespace TW::Ethereum::ABI; - extern std::string TESTS_ROOT; +namespace TW::Ethereum::ABI::tests { + static nlohmann::json load_json(std::string path) { std::ifstream stream(path); nlohmann::json json; @@ -23,7 +22,7 @@ static nlohmann::json load_json(std::string path) { } TEST(ContractCall, Approval) { - auto path = TESTS_ROOT + "/Ethereum/Data/erc20.json"; + auto path = TESTS_ROOT + "/chains/Ethereum/Data/erc20.json"; auto abi = load_json(path); auto call = parse_hex("095ea7b30000000000000000000000005aaeb6053f3e94c9b9a09f33669435e7ef1beaed" "0000000000000000000000000000000000000000000000000000000000000001"); @@ -36,7 +35,7 @@ TEST(ContractCall, Approval) { } TEST(ContractCall, UniswapSwapTokens) { - auto path = TESTS_ROOT + "/Ethereum/Data/uniswap_router_v2.json"; + auto path = TESTS_ROOT + "/chains/Ethereum/Data/uniswap_router_v2.json"; auto abi = load_json(path); // https://etherscan.io/tx/0x57a2414f3cd9ca373b7e663ae67ecf933e40cb77a6e4ed28e4e28b5aa0d8ec63 auto call = parse_hex( @@ -56,7 +55,7 @@ TEST(ContractCall, UniswapSwapTokens) { } TEST(ContractCall, KyberTrade) { - auto path = TESTS_ROOT + "/Ethereum/Data/kyber_proxy.json"; + auto path = TESTS_ROOT + "/chains/Ethereum/Data/kyber_proxy.json"; auto abi = load_json(path); // https://etherscan.io/tx/0x51ffab782b9a27d754389505d5a50db525c04c68142ce20512d579f10f9e13e4 @@ -78,7 +77,7 @@ TEST(ContractCall, KyberTrade) { } TEST(ContractCall, ApprovalForAll) { - auto path = TESTS_ROOT + "/Ethereum/Data/erc721.json"; + auto path = TESTS_ROOT + "/chains/Ethereum/Data/erc721.json"; auto abi = load_json(path); // https://etherscan.io/tx/0xc2744000a107aee4761cf8a638657f91c3003a54e2f1818c37d781be7e48187a @@ -93,7 +92,7 @@ TEST(ContractCall, ApprovalForAll) { } TEST(ContractCall, CustomCall) { - auto path = TESTS_ROOT + "/Ethereum/Data/custom.json"; + auto path = TESTS_ROOT + "/chains/Ethereum/Data/custom.json"; auto abi = load_json(path); auto call = parse_hex("ec37a4a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000067472757374790000000000000000000000000000000000000000000000000000"); @@ -107,7 +106,7 @@ TEST(ContractCall, CustomCall) { TEST(ContractCall, SetResolver) { auto call = parse_hex("0x1896f70ae71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c" "6f0000000000000000000000004976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41"); - auto path = TESTS_ROOT + "/Ethereum/Data/ens.json"; + auto path = TESTS_ROOT + "/chains/Ethereum/Data/ens.json"; auto abi = load_json(path); auto decoded = decodeCall(call, abi); auto expected = @@ -121,7 +120,7 @@ TEST(ContractCall, RenewENS) { "0xacf1a84100000000000000000000000000000000000000000000000000000000000000400000000000000000" "000000000000000000000000000000000000000001e18558000000000000000000000000000000000000000000" "000000000000000000000a68657769676f76656e7300000000000000000000000000000000000000000000"); - auto path = TESTS_ROOT + "/Ethereum/Data/ens.json"; + auto path = TESTS_ROOT + "/chains/Ethereum/Data/ens.json"; auto abi = load_json(path); auto decoded = decodeCall(call, abi); auto expected = @@ -153,8 +152,8 @@ TEST(ContractCall, Multicall) { "000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000" "000000000000000000000000000000000014d30f834b53d8f7e851e87b90ffa65757a35b850500000000000000" "000000000000000000000000000000000000000000000000000000000000000000"); - ASSERT_EQ(4 + 928, call.size()); - auto path = TESTS_ROOT + "/Ethereum/Data/ens.json"; + ASSERT_EQ(4 + 928ul, call.size()); + auto path = TESTS_ROOT + "/chains/Ethereum/Data/ens.json"; auto abi = load_json(path); auto decoded = decodeCall(call, abi); auto expected = @@ -174,9 +173,8 @@ TEST(ContractCall, GetAmountsOut) { "0000000000000000000000000000000000000000000000000000000000000064" "0000000000000000000000000000000000000000000000000000000000000040" "0000000000000000000000000000000000000000000000000000000000000001" - "000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077" - ); - auto path = TESTS_ROOT + "/Ethereum/Data/getAmountsOut.json"; + "000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077"); + auto path = TESTS_ROOT + "/chains/Ethereum/Data/getAmountsOut.json"; auto abi = load_json(path); auto decoded = decodeCall(call, abi); @@ -187,13 +185,12 @@ TEST(ContractCall, GetAmountsOut) { } TEST(ContractCall, 1inch) { - auto path = TESTS_ROOT + "/Ethereum/Data/1inch.json"; + auto path = TESTS_ROOT + "/chains/Ethereum/Data/1inch.json"; auto abi = load_json(path); // https://etherscan.io/tx/0xc2d113151124579c21332d4cc6ab2b7f61e81d62392ed8596174513cb47e35ba auto call = parse_hex( - "7c02520000000000000000000000000027239549dd40e1d60f5b80b0c4196923745b1fd2000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001800000000000000000000000002b591e99afe9f32eaa6214f7b7629768c40eeb39000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000027239549dd40e1d60f5b80b0c4196923745b1fd20000000000000000000000001611c227725c5e420ef058275ae772b41775e261000000000000000000000000000000000000000000000000000005d0fadb1c0000000000000000000000000000000000000000000000000000000005c31df1da000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002080000000000000000000000069d91b94f0aaf8e8a2586909fa77a5c2c89818d50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000104128acb080000000000000000000000001611c227725c5e420ef058275ae772b41775e2610000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000005d0fadb1c0000000000000000000000000000000000000000000000000000000001000276a400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000400000000000000000000000002b591e99afe9f32eaa6214f7b7629768c40eeb39000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000" - ); + "7c02520000000000000000000000000027239549dd40e1d60f5b80b0c4196923745b1fd2000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001800000000000000000000000002b591e99afe9f32eaa6214f7b7629768c40eeb39000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000027239549dd40e1d60f5b80b0c4196923745b1fd20000000000000000000000001611c227725c5e420ef058275ae772b41775e261000000000000000000000000000000000000000000000000000005d0fadb1c0000000000000000000000000000000000000000000000000000000005c31df1da000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002080000000000000000000000069d91b94f0aaf8e8a2586909fa77a5c2c89818d50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000104128acb080000000000000000000000001611c227725c5e420ef058275ae772b41775e2610000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000005d0fadb1c0000000000000000000000000000000000000000000000000000000001000276a400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000400000000000000000000000002b591e99afe9f32eaa6214f7b7629768c40eeb39000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000"); auto decoded = decodeCall(call, abi); ASSERT_TRUE(decoded.has_value()); auto expected = @@ -202,7 +199,7 @@ TEST(ContractCall, 1inch) { } TEST(ContractCall, TupleNested) { - auto path = TESTS_ROOT + "/Ethereum/Data/tuple_nested.json"; + auto path = TESTS_ROOT + "/chains/Ethereum/Data/tuple_nested.json"; auto abi = load_json(path); auto call = parse_hex( @@ -212,11 +209,12 @@ TEST(ContractCall, TupleNested) { "0000000000000000000000000000000000000000000000000000000000000003" "0000000000000000000000000000000000000000000000000000000000000004" "0000000000000000000000000000000000000000000000000000000000000005" - "0000000000000000000000000000000000000000000000000000000000000001" - ); + "0000000000000000000000000000000000000000000000000000000000000001"); auto decoded = decodeCall(call, abi); ASSERT_TRUE(decoded.has_value()); auto expected = R"|({"function":"nested_tuple(uint16,(uint16,(uint16,uint64),uint32),bool)","inputs":[{"name":"param1","type":"uint16","value":"1"},{"components":[{"name":"param21","type":"uint16","value":"2"},{"components":[{"name":"param221","type":"uint16","value":"3"},{"name":"param222","type":"uint64","value":"4"}],"name":"param22","type":"tuple"},{"name":"param23","type":"uint32","value":"5"}],"name":"param2","type":"tuple"},{"name":"param3","type":"bool","value":true}]})|"; EXPECT_EQ(decoded.value(), expected); } + +} // namespace TW::Ethereum::ABI::tests diff --git a/tests/Ethereum/Data/1inch.json b/tests/chains/Ethereum/Data/1inch.json similarity index 100% rename from tests/Ethereum/Data/1inch.json rename to tests/chains/Ethereum/Data/1inch.json diff --git a/tests/Ethereum/Data/custom.json b/tests/chains/Ethereum/Data/custom.json similarity index 100% rename from tests/Ethereum/Data/custom.json rename to tests/chains/Ethereum/Data/custom.json diff --git a/tests/Ethereum/Data/eip712_cryptofights.json b/tests/chains/Ethereum/Data/eip712_cryptofights.json similarity index 100% rename from tests/Ethereum/Data/eip712_cryptofights.json rename to tests/chains/Ethereum/Data/eip712_cryptofights.json diff --git a/tests/chains/Ethereum/Data/eip712_emptyArray.json b/tests/chains/Ethereum/Data/eip712_emptyArray.json new file mode 100644 index 00000000000..676a776d76c --- /dev/null +++ b/tests/chains/Ethereum/Data/eip712_emptyArray.json @@ -0,0 +1,108 @@ +{ + "types": { + "EIP712Domain": [ + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "verifyingContract", + "type": "address" + }, + { + "name": "version", + "type": "string" + } + ], + "Action": [ + { + "name": "action", + "type": "string" + }, + { + "name": "params", + "type": "string" + } + ], + "Cell": [ + { + "name": "capacity", + "type": "string" + }, + { + "name": "lock", + "type": "string" + }, + { + "name": "type", + "type": "string" + }, + { + "name": "data", + "type": "string" + }, + { + "name": "extraData", + "type": "string" + } + ], + "Transaction": [ + { + "name": "DAS_MESSAGE", + "type": "string" + }, + { + "name": "inputsCapacity", + "type": "string" + }, + { + "name": "outputsCapacity", + "type": "string" + }, + { + "name": "fee", + "type": "string" + }, + { + "name": "action", + "type": "Action" + }, + { + "name": "inputs", + "type": "Cell[]" + }, + { + "name": "outputs", + "type": "Cell[]" + }, + { + "name": "digest", + "type": "bytes32" + } + ] + }, + "primaryType": "Transaction", + "domain": { + "chainId": 1, + "name": "da.systems", + "verifyingContract": "0x0000000000000000000000000000000020210722", + "version": "1" + }, + "message": { + "DAS_MESSAGE": "TRANSFER FROM 0x54366bcd1e73baf55449377bd23123274803236e(906.74221046 CKB) TO ckt1qyqvsej8jggu4hmr45g4h8d9pfkpd0fayfksz44t9q(764.13228446 CKB), 0x54366bcd1e73baf55449377bd23123274803236e(142.609826 CKB)", + "inputsCapacity": "906.74221046 CKB", + "outputsCapacity": "906.74211046 CKB", + "fee": "0.0001 CKB", + "digest": "0x29cd28dbeb470adb17548563ceb4988953fec7b499e716c16381e5ae4b04021f", + "action": { + "action": "transfer", + "params": "0x00" + }, + "inputs": [], + "outputs": [] + } +} \ No newline at end of file diff --git a/tests/chains/Ethereum/Data/eip712_emptyString.json b/tests/chains/Ethereum/Data/eip712_emptyString.json new file mode 100644 index 00000000000..0f507959149 --- /dev/null +++ b/tests/chains/Ethereum/Data/eip712_emptyString.json @@ -0,0 +1,132 @@ + +{ + "types": { + "EIP712Domain": [ + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "verifyingContract", + "type": "address" + }, + { + "name": "version", + "type": "string" + } + ], + "Action": [ + { + "name": "action", + "type": "string" + }, + { + "name": "params", + "type": "string" + } + ], + "Cell": [ + { + "name": "capacity", + "type": "string" + }, + { + "name": "lock", + "type": "string" + }, + { + "name": "type", + "type": "string" + }, + { + "name": "data", + "type": "string" + }, + { + "name": "extraData", + "type": "string" + } + ], + "Transaction": [ + { + "name": "DAS_MESSAGE", + "type": "string" + }, + { + "name": "inputsCapacity", + "type": "string" + }, + { + "name": "outputsCapacity", + "type": "string" + }, + { + "name": "fee", + "type": "string" + }, + { + "name": "action", + "type": "Action" + }, + { + "name": "inputs", + "type": "Cell[]" + }, + { + "name": "outputs", + "type": "Cell[]" + }, + { + "name": "digest", + "type": "bytes32" + } + ] + }, + "primaryType": "Transaction", + "domain": { + "chainId": "1", + "name": "did.id", + "verifyingContract": "0x0000000000000000000000000000000020210722", + "version": "1" + }, + "message": { + "DAS_MESSAGE": "SELL specer.bit FOR 100000 CKB", + "inputsCapacity": "1216.9999 CKB", + "outputsCapacity": "1216.9998 CKB", + "fee": "0.0001 CKB", + "digest": "0x53a6c0f19ec281604607f5d6817e442082ad1882bef0df64d84d3810dae561eb", + "action": { + "action": "start_account_sale", + "params": "0x00" + }, + "inputs": [ + { + "capacity": "218 CKB", + "lock": "das-lock,0x01,0x051c152f77f8efa9c7c6d181cc97ee67c165c506...", + "type": "account-cell-type,0x01,0x", + "data": "{ account: specer.bit, expired_at: 1670913958 }", + "extraData": "{ status: 0, records_hash: 0x55478d76900611eb079b22088081124ed6c8bae21a05dd1a0d197efcc7c114ce }" + } + ], + "outputs": [ + { + "capacity": "218 CKB", + "lock": "das-lock,0x01,0x051c152f77f8efa9c7c6d181cc97ee67c165c506...", + "type": "account-cell-type,0x01,0x", + "data": "{ account: specer.bit, expired_at: 1670913958 }", + "extraData": "{ status: 1, records_hash: 0x55478d76900611eb079b22088081124ed6c8bae21a05dd1a0d197efcc7c114ce }" + }, + { + "capacity": "201 CKB", + "lock": "das-lock,0x01,0x051c152f77f8efa9c7c6d181cc97ee67c165c506...", + "type": "account-sale-cell-type,0x01,0x", + "data": "0x1209460ef3cb5f1c68ed2c43a3e020eec2d9de6e...", + "extraData": "" + } + ] + } +} \ No newline at end of file diff --git a/tests/Ethereum/Data/eip712_rarible.json b/tests/chains/Ethereum/Data/eip712_rarible.json similarity index 100% rename from tests/Ethereum/Data/eip712_rarible.json rename to tests/chains/Ethereum/Data/eip712_rarible.json diff --git a/tests/Ethereum/Data/eip712_snapshot_v4.json b/tests/chains/Ethereum/Data/eip712_snapshot_v4.json similarity index 100% rename from tests/Ethereum/Data/eip712_snapshot_v4.json rename to tests/chains/Ethereum/Data/eip712_snapshot_v4.json diff --git a/tests/Ethereum/Data/eip712_walletconnect.json b/tests/chains/Ethereum/Data/eip712_walletconnect.json similarity index 100% rename from tests/Ethereum/Data/eip712_walletconnect.json rename to tests/chains/Ethereum/Data/eip712_walletconnect.json diff --git a/tests/Ethereum/Data/ens.json b/tests/chains/Ethereum/Data/ens.json similarity index 100% rename from tests/Ethereum/Data/ens.json rename to tests/chains/Ethereum/Data/ens.json diff --git a/tests/Ethereum/Data/erc20.json b/tests/chains/Ethereum/Data/erc20.json similarity index 100% rename from tests/Ethereum/Data/erc20.json rename to tests/chains/Ethereum/Data/erc20.json diff --git a/tests/Ethereum/Data/erc721.json b/tests/chains/Ethereum/Data/erc721.json similarity index 100% rename from tests/Ethereum/Data/erc721.json rename to tests/chains/Ethereum/Data/erc721.json diff --git a/tests/Ethereum/Data/eth_feeHistory.json b/tests/chains/Ethereum/Data/eth_feeHistory.json similarity index 100% rename from tests/Ethereum/Data/eth_feeHistory.json rename to tests/chains/Ethereum/Data/eth_feeHistory.json diff --git a/tests/Ethereum/Data/eth_feeHistory2.json b/tests/chains/Ethereum/Data/eth_feeHistory2.json similarity index 100% rename from tests/Ethereum/Data/eth_feeHistory2.json rename to tests/chains/Ethereum/Data/eth_feeHistory2.json diff --git a/tests/Ethereum/Data/eth_feeHistory3.json b/tests/chains/Ethereum/Data/eth_feeHistory3.json similarity index 100% rename from tests/Ethereum/Data/eth_feeHistory3.json rename to tests/chains/Ethereum/Data/eth_feeHistory3.json diff --git a/tests/Ethereum/Data/eth_feeHistory4.json b/tests/chains/Ethereum/Data/eth_feeHistory4.json similarity index 100% rename from tests/Ethereum/Data/eth_feeHistory4.json rename to tests/chains/Ethereum/Data/eth_feeHistory4.json diff --git a/tests/Ethereum/Data/getAmountsOut.json b/tests/chains/Ethereum/Data/getAmountsOut.json similarity index 100% rename from tests/Ethereum/Data/getAmountsOut.json rename to tests/chains/Ethereum/Data/getAmountsOut.json diff --git a/tests/Ethereum/Data/kyber_proxy.json b/tests/chains/Ethereum/Data/kyber_proxy.json similarity index 100% rename from tests/Ethereum/Data/kyber_proxy.json rename to tests/chains/Ethereum/Data/kyber_proxy.json diff --git a/tests/chains/Ethereum/Data/seaport_712.json b/tests/chains/Ethereum/Data/seaport_712.json new file mode 100644 index 00000000000..3a427db3584 --- /dev/null +++ b/tests/chains/Ethereum/Data/seaport_712.json @@ -0,0 +1,84 @@ +{ + "types": { + "EIP712Domain": [ + { "name": "name", "type": "string" }, + { "name": "version", "type": "string" }, + { "name": "chainId", "type": "uint256" }, + { "name": "verifyingContract", "type": "address" } + ], + "OrderComponents": [ + { "name": "offerer", "type": "address" }, + { "name": "zone", "type": "address" }, + { "name": "offer", "type": "OfferItem[]" }, + { "name": "consideration", "type": "ConsiderationItem[]" }, + { "name": "orderType", "type": "uint8" }, + { "name": "startTime", "type": "uint256" }, + { "name": "endTime", "type": "uint256" }, + { "name": "zoneHash", "type": "bytes32" }, + { "name": "salt", "type": "uint256" }, + { "name": "conduitKey", "type": "bytes32" }, + { "name": "counter", "type": "uint256" } + ], + "OfferItem": [ + { "name": "itemType", "type": "uint8" }, + { "name": "token", "type": "address" }, + { "name": "identifierOrCriteria", "type": "uint256" }, + { "name": "startAmount", "type": "uint256" }, + { "name": "endAmount", "type": "uint256" } + ], + "ConsiderationItem": [ + { "name": "itemType", "type": "uint8" }, + { "name": "token", "type": "address" }, + { "name": "identifierOrCriteria", "type": "uint256" }, + { "name": "startAmount", "type": "uint256" }, + { "name": "endAmount", "type": "uint256" }, + { "name": "recipient", "type": "address" } + ] + }, + "primaryType": "OrderComponents", + "domain": { + "name": "Seaport", + "version": "1.1", + "chainId": "1", + "verifyingContract": "0x00000000006c3852cbEf3e08E8dF289169EdE581" + }, + "message": { + "offerer": "0x7d8bf18C7cE84b3E175b339c4Ca93aEd1dD166F1", + "offer": [ + { + "itemType": "2", + "token": "0x3F53082981815Ed8142384EDB1311025cA750Ef1", + "identifierOrCriteria": "134", + "startAmount": "1", + "endAmount": "1" + } + ], + "orderType": "2", + "consideration": [ + { + "itemType": "0", + "token": "0x0000000000000000000000000000000000000000", + "identifierOrCriteria": "0", + "startAmount": "975000000000000000", + "endAmount": "975000000000000000", + "recipient": "0x7d8bf18C7cE84b3E175b339c4Ca93aEd1dD166F1" + }, + { + "itemType": "0", + "token": "0x0000000000000000000000000000000000000000", + "identifierOrCriteria": "0", + "startAmount": "25000000000000000", + "endAmount": "25000000000000000", + "recipient": "0x8De9C5A032463C561423387a9648c5C7BCC5BC90" + } + ], + "startTime": "1655450129", + "endTime": "1658042129", + "zone": "0x004C00500000aD104D7DBd00e3ae0A5C00560C00", + "zoneHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "salt": "795459960395409", + "conduitKey": "0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000", + "totalOriginalConsiderationItems": "2", + "counter": "0" + } +} diff --git a/tests/Ethereum/Data/tuple_nested.json b/tests/chains/Ethereum/Data/tuple_nested.json similarity index 100% rename from tests/Ethereum/Data/tuple_nested.json rename to tests/chains/Ethereum/Data/tuple_nested.json diff --git a/tests/Ethereum/Data/uniswap_router_v2.json b/tests/chains/Ethereum/Data/uniswap_router_v2.json similarity index 100% rename from tests/Ethereum/Data/uniswap_router_v2.json rename to tests/chains/Ethereum/Data/uniswap_router_v2.json diff --git a/tests/Ethereum/Data/zilliqa_data_tx.json b/tests/chains/Ethereum/Data/zilliqa_data_tx.json similarity index 100% rename from tests/Ethereum/Data/zilliqa_data_tx.json rename to tests/chains/Ethereum/Data/zilliqa_data_tx.json diff --git a/tests/Ethereum/RLPTests.cpp b/tests/chains/Ethereum/RLPTests.cpp similarity index 82% rename from tests/Ethereum/RLPTests.cpp rename to tests/chains/Ethereum/RLPTests.cpp index ed8dd1181c4..52b01b75de4 100644 --- a/tests/Ethereum/RLPTests.cpp +++ b/tests/chains/Ethereum/RLPTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,23 +9,25 @@ #include -using namespace TW; -using namespace TW::Ethereum; +namespace TW::Ethereum::tests { + using boost::multiprecision::uint256_t; std::string stringifyItems(const RLP::DecodedItem& di); std::string stringifyData(const Data& data) { - if (data.size() == 0) return "0"; + if (data.size() == 0) + return "0"; // try if only letters bool isLettersOnly = true; - for(auto i: data) { + for (auto i : data) { if (!((i >= 'A' && i <= 'Z') || (i >= 'a' && i <= 'z') || i == ' ' || i == ',')) { isLettersOnly = false; break; } } - if (isLettersOnly) return std::string("'") + std::string(data.begin(), data.end()) + "'"; + if (isLettersOnly) + return std::string("'") + std::string(data.begin(), data.end()) + "'"; // try if it can be parsed (recursive) if (data.size() >= 2) { try { @@ -33,7 +35,8 @@ std::string stringifyData(const Data& data) { if (di.decoded.size() > 0 && di.remainder.size() == 0) { return stringifyItems(di); } - } catch (...) {} + } catch (...) { + } } // any other: as hex string return hex(data); @@ -49,8 +52,9 @@ std::string stringifyItems(const RLP::DecodedItem& di) { } std::string res = "(" + std::to_string(n) + ": "; int count = 0; - for(auto i: di.decoded) { - if (count++) res += " "; + for (auto i : di.decoded) { + if (count++) + res += " "; res += stringifyData(i); } res += ")"; @@ -95,16 +99,13 @@ TEST(RLP, EncodeUInt256) { EXPECT_EQ(hex(RLP::encode(uint256_t(0xffffffffffffffULL))), "87ffffffffffffff"); EXPECT_EQ( hex(RLP::encode(uint256_t("0x102030405060708090a0b0c0d0e0f2"))), - "8f102030405060708090a0b0c0d0e0f2" - ); + "8f102030405060708090a0b0c0d0e0f2"); EXPECT_EQ( hex(RLP::encode(uint256_t("0x0100020003000400050006000700080009000a000b000c000d000e01"))), - "9c0100020003000400050006000700080009000a000b000c000d000e01" - ); + "9c0100020003000400050006000700080009000a000b000c000d000e01"); EXPECT_EQ( hex(RLP::encode(uint256_t("0x0100000000000000000000000000000000000000000000000000000000000000"))), - "a00100000000000000000000000000000000000000000000000000000000000000" - ); + "a00100000000000000000000000000000000000000000000000000000000000000"); } TEST(RLP, EncodeList) { @@ -161,7 +162,7 @@ TEST(RLP, DecodeString) { EXPECT_EQ(decodeHelper("a00100000000000000000000000000000000000000000000000000000000000000"), "0100000000000000000000000000000000000000000000000000000000000000"); // long string EXPECT_EQ(decodeHelper("b87674686973206973206120612076657279206c6f6e6720737472696e672c2074686973206973206120612076657279206c6f6e6720737472696e672c2074686973206973206120612076657279206c6f6e6720737472696e672c2074686973206973206120612076657279206c6f6e6720737472696e67"), - "'this is a a very long string, this is a a very long string, this is a a very long string, this is a a very long string'"); + "'this is a a very long string, this is a a very long string, this is a a very long string, this is a a very long string'"); } TEST(RLP, DecodeList) { @@ -174,40 +175,39 @@ TEST(RLP, DecodeList) { // long list, raw ether transfer tx EXPECT_EQ(decodeHelper("f86b81a985051f4d5ce982520894515778891c99e3d2e7ae489980cb7c77b37b5e76861b48eb57e0008025a0ad01c32a7c974df9d0bd48c8d7e0ecab62e90811917aa7dc0c966751a0c3f475a00dc77d9ec68484481bdf87faac14378f4f18d477f84c0810d29480372c1bbc65"), - "(9: " - "a9 " // nonce - "051f4d5ce9 " // gas price - "5208 " // gas limit - "515778891c99e3d2e7ae489980cb7c77b37b5e76 " // to - "1b48eb57e000 " // amount - "0 " // data - "25 " // v - "ad01c32a7c974df9d0bd48c8d7e0ecab62e90811917aa7dc0c966751a0c3f475 " // r - "0dc77d9ec68484481bdf87faac14378f4f18d477f84c0810d29480372c1bbc65" // s - ")" - ); + "(9: " + "a9 " // nonce + "051f4d5ce9 " // gas price + "5208 " // gas limit + "515778891c99e3d2e7ae489980cb7c77b37b5e76 " // to + "1b48eb57e000 " // amount + "0 " // data + "25 " // v + "ad01c32a7c974df9d0bd48c8d7e0ecab62e90811917aa7dc0c966751a0c3f475 " // r + "0dc77d9ec68484481bdf87faac14378f4f18d477f84c0810d29480372c1bbc65" // s + ")"); // long list, raw token transfer tx EXPECT_EQ(decodeHelper("f8aa81d485077359400082db9194dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000c6b6b55c8c4971145a842cc4e5db92d879d0b3e00000000000000000000000000000000000000000000000000000000002faf0801ca02843d8ed66b9623392dc336dd36d5dd5a630b2019962869b6e50fdb4ecb5b6aca05d9ea377bc65e2921f7fc257de8135530cc74e3188b6ba57a4b9cb284393050a"), - "(9: " - "d4 " - "0773594000 " - "db91 " - "dac17f958d2ee523a2206206994597c13d831ec7 " - "0 " - "a9059cbb000000000000000000000000c6b6b55c8c4971145a842cc4e5db92d879d0b3e00000000000000000000000000000000000000000000000000000000002faf080 " - "1c " - "2843d8ed66b9623392dc336dd36d5dd5a630b2019962869b6e50fdb4ecb5b6ac " - "5d9ea377bc65e2921f7fc257de8135530cc74e3188b6ba57a4b9cb284393050a" - ")" - ); + "(9: " + "d4 " + "0773594000 " + "db91 " + "dac17f958d2ee523a2206206994597c13d831ec7 " + "0 " + "a9059cbb000000000000000000000000c6b6b55c8c4971145a842cc4e5db92d879d0b3e00000000000000000000000000000000000000000000000000000000002faf080 " + "1c " + "2843d8ed66b9623392dc336dd36d5dd5a630b2019962869b6e50fdb4ecb5b6ac " + "5d9ea377bc65e2921f7fc257de8135530cc74e3188b6ba57a4b9cb284393050a" + ")"); { // long list, with 2-byte size const std::string elem = "0123"; - const int n = 500; + const std::size_t n = 500; std::vector longarr; - for (auto i = 0; i < n; ++i) longarr.push_back(elem); + for (auto i = 0ul; i < n; ++i) + longarr.push_back(elem); const Data encoded = RLP::encodeList(longarr); ASSERT_EQ(hex(subData(encoded, 0, 20)), "f909c48430313233843031323384303132338430"); @@ -221,12 +221,13 @@ TEST(RLP, DecodeList) { { // long list, with 3-byte size const std::string elem = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"; - const int n = 650; + const std::size_t n = 650; std::vector longarr; - for (auto i = 0; i < n; ++i) longarr.push_back(elem); + for (auto i = 0ul; i < n; ++i) + longarr.push_back(elem); const Data encoded = RLP::encodeList(longarr); - ASSERT_EQ(encoded.size(), 66304); + ASSERT_EQ(encoded.size(), 66304ul); ASSERT_EQ(hex(subData(encoded, 0, 30)), "fa0102fcb864303132333435363738393031323334353637383930313233"); auto decoded = RLP::decode(encoded); @@ -235,7 +236,7 @@ TEST(RLP, DecodeList) { // nested list EXPECT_EQ(decodeHelper("f8479cdb84c301020395d4856170706c658662616e616e6186636865727279a9e890cf83abcdef8a0001020304050607080996d587626974636f696e88626565656e62656583657468"), - "(2: (2: (3: 01 02 03) (3: 'apple' 'banana' 'cherry')) (2: (2: abcdef 00010203040506070809) (3: 'bitcoin' 'beeenbee' 'eth')))"); + "(2: (2: (3: 01 02 03) (3: 'apple' 'banana' 'cherry')) (2: (2: abcdef 00010203040506070809) (3: 'bitcoin' 'beeenbee' 'eth')))"); } TEST(RLP, DecodeInvalid) { @@ -295,9 +296,11 @@ TEST(RLP, parseVarInt) { EXPECT_EQ(hex(store(RLP::parseVarInt(7, parse_hex("01020304050607"), 0))), "01020304050607"); EXPECT_EQ(hex(store(RLP::parseVarInt(8, parse_hex("0102030405060708"), 0))), "0102030405060708"); EXPECT_EQ(hex(store(RLP::parseVarInt(8, parse_hex("abcd0102030405060708"), 2))), "0102030405060708"); - EXPECT_THROW(RLP::parseVarInt(0, parse_hex("01"), 0), std::invalid_argument); // wrong size + EXPECT_THROW(RLP::parseVarInt(0, parse_hex("01"), 0), std::invalid_argument); // wrong size EXPECT_THROW(RLP::parseVarInt(9, parse_hex("010203040506070809"), 0), std::invalid_argument); // wrong size - EXPECT_THROW(RLP::parseVarInt(4, parse_hex("0102"), 0), std::invalid_argument); // too short - EXPECT_THROW(RLP::parseVarInt(4, parse_hex("01020304"), 2), std::invalid_argument); // too short - EXPECT_THROW(RLP::parseVarInt(2, parse_hex("0002"), 0), std::invalid_argument); // starts with 0 + EXPECT_THROW(RLP::parseVarInt(4, parse_hex("0102"), 0), std::invalid_argument); // too short + EXPECT_THROW(RLP::parseVarInt(4, parse_hex("01020304"), 2), std::invalid_argument); // too short + EXPECT_THROW(RLP::parseVarInt(2, parse_hex("0002"), 0), std::invalid_argument); // starts with 0 } + +} // namespace TW::Ethereum::tests diff --git a/tests/Ethereum/SignerTests.cpp b/tests/chains/Ethereum/SignerTests.cpp similarity index 100% rename from tests/Ethereum/SignerTests.cpp rename to tests/chains/Ethereum/SignerTests.cpp diff --git a/tests/Ethereum/TWAnySignerTests.cpp b/tests/chains/Ethereum/TWAnySignerTests.cpp similarity index 95% rename from tests/Ethereum/TWAnySignerTests.cpp rename to tests/chains/Ethereum/TWAnySignerTests.cpp index b57e9b26232..c50cef17583 100644 --- a/tests/Ethereum/TWAnySignerTests.cpp +++ b/tests/chains/Ethereum/TWAnySignerTests.cpp @@ -1,10 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include "HexCoding.h" #include "uint256.h" @@ -15,9 +15,7 @@ #include -using namespace TW; -using namespace TW::Ethereum; -using namespace TW::Ethereum::ABI; +namespace TW::Ethereum { TEST(TWEthereumSigner, EmptyValue) { auto str = std::string(""); @@ -80,7 +78,7 @@ TEST(TWAnySignerEthereum, SignERC20TransferAsERC20) { auto chainId = store(uint256_t(1)); auto nonce = store(uint256_t(0)); auto gasPrice = store(uint256_t(42000000000)); // 0x09c7652400 - auto gasLimit = store(uint256_t(78009)); // 130B9 + auto gasLimit = store(uint256_t(78009)); // 130B9 auto toAddress = "0x5322b34c88ed0691971bf52a7047448f0f4efc84"; auto token = "0x6b175474e89094c44da98b954eedeac495271d0f"; // DAI auto amount = uint256_t(2000000000000000000); @@ -111,10 +109,9 @@ TEST(TWAnySignerEthereum, SignERC20TransferAsERC20) { // expected payload Data payload; { - auto func = Function("transfer", std::vector>{ - std::make_shared(parse_hex(toAddress)), - std::make_shared(amount) - }); + auto func = ABI::Function("transfer", std::vector>{ + std::make_shared(parse_hex(toAddress)), + std::make_shared(amount)}); func.encode(payload); } ASSERT_EQ(hex(output.data()), hex(payload)); @@ -124,8 +121,8 @@ TEST(TWAnySignerEthereum, SignERC20TransferAsERC20) { TEST(TWAnySignerEthereum, SignERC20TransferAsGenericContract) { auto chainId = store(uint256_t(1)); auto nonce = store(uint256_t(0)); - auto gasPrice = store(uint256_t(42000000000)); // 0x09c7652400 - auto gasLimit = store(uint256_t(78009)); // 130B9 + auto gasPrice = store(uint256_t(42000000000)); // 0x09c7652400 + auto gasLimit = store(uint256_t(78009)); // 130B9 auto toAddress = "0x6b175474e89094c44da98b954eedeac495271d0f"; // DAI // payload: transfer(0x5322b34c88ed0691971bf52a7047448f0f4efc84, 2000000000000000000) auto data = parse_hex("0xa9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000"); @@ -157,7 +154,7 @@ TEST(TWAnySignerEthereum, SignERC20TransferInvalidAddress) { auto chainId = store(uint256_t(1)); auto nonce = store(uint256_t(0)); auto gasPrice = store(uint256_t(42000000000)); // 0x09c7652400 - auto gasLimit = store(uint256_t(78009)); // 130B9 + auto gasLimit = store(uint256_t(78009)); // 130B9 auto invalidAddress = "0xdeadbeef"; auto amount = store(uint256_t(2000000000000000000)); auto key = parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); @@ -185,7 +182,7 @@ TEST(TWAnySignerEthereum, SignERC20Approve) { auto chainId = store(uint256_t(1)); auto nonce = store(uint256_t(0)); auto gasPrice = store(uint256_t(42000000000)); // 0x09c7652400 - auto gasLimit = store(uint256_t(78009)); // 130B9 + auto gasLimit = store(uint256_t(78009)); // 130B9 auto spenderAddress = "0x5322b34c88ed0691971bf52a7047448f0f4efc84"; auto token = "0x6b175474e89094c44da98b954eedeac495271d0f"; // DAI auto amount = store(uint256_t(2000000000000000000)); @@ -286,13 +283,12 @@ TEST(TWAnySignerEthereum, SignERC1155Transfer) { // expected payload Data payload; { - auto func = Function("safeTransferFrom", std::vector>{ - std::make_shared(parse_hex(fromAddress)), - std::make_shared(parse_hex(toAddress)), - std::make_shared(uint256_t(0x23c47ee5)), - std::make_shared(value), - std::make_shared(data) - }); + auto func = ABI::Function("safeTransferFrom", std::vector>{ + std::make_shared(parse_hex(fromAddress)), + std::make_shared(parse_hex(toAddress)), + std::make_shared(uint256_t(0x23c47ee5)), + std::make_shared(value), + std::make_shared(data)}); func.encode(payload); } ASSERT_EQ(hex(output.data()), hex(payload)); @@ -312,9 +308,9 @@ TEST(TWAnySignerEthereum, PlanNotSupported) { // Ethereum does not use plan(), call it nonetheless Proto::SigningInput input; auto inputData = input.SerializeAsString(); - auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData.data(), inputData.size())); + auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t*)inputData.data(), inputData.size())); auto outputTWData = WRAPD(TWAnySignerPlan(inputTWData.get(), TWCoinTypeEthereum)); - EXPECT_EQ(TWDataSize(outputTWData.get()), 0); + EXPECT_EQ(TWDataSize(outputTWData.get()), 0ul); } TEST(TWAnySignerEthereum, SignERC1559Transfer_1442) { @@ -358,9 +354,9 @@ TEST(TWAnySignerEthereum, SignERC1559Transfer_1442) { TEST(TWAnySignerEthereum, SignERC20Transfer_1559) { auto chainId = store(uint256_t(1)); auto nonce = store(uint256_t(0)); - auto gasLimit = store(uint256_t(78009)); // 130B9 + auto gasLimit = store(uint256_t(78009)); // 130B9 auto maxInclusionFeePerGas = store(uint256_t(2000000000)); // 77359400 - auto maxFeePerGas = store(uint256_t(3000000000)); // B2D05E00 + auto maxFeePerGas = store(uint256_t(3000000000)); // B2D05E00 auto toAddress = "0x5322b34c88ed0691971bf52a7047448f0f4efc84"; auto token = "0x6b175474e89094c44da98b954eedeac495271d0f"; // DAI auto amount = uint256_t(2000000000000000000); @@ -488,3 +484,5 @@ TEST(TWAnySignerEthereum, SignERC1155Transfer_1559) { ASSERT_EQ(hex(output.encoded()), "02f901500180847735940084b2d05e00830130b9944e45e92ed38f885d39a733c14f1817217a89d42580b8e4f242432a000000000000000000000000718046867b5b1782379a14ea4fc0c9b724da94fc0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000000000023c47ee50000000000000000000000000000000000000000000000001bc16d674ec8000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000040102030400000000000000000000000000000000000000000000000000000000c080a0533df41dda5540c57257b7fe89c29cefff0155c333e063220df2bf9680fcc15aa036a844fd20de5a51de96ceaaf078558e87d86426a4a5d4b215ee1fd0fa397f8a"); } + +} // namespace TW::Ethereum diff --git a/tests/Ethereum/TWCoinTypeTests.cpp b/tests/chains/Ethereum/TWCoinTypeTests.cpp similarity index 92% rename from tests/Ethereum/TWCoinTypeTests.cpp rename to tests/chains/Ethereum/TWCoinTypeTests.cpp index 1bdfedd0e25..39878ade96c 100644 --- a/tests/Ethereum/TWCoinTypeTests.cpp +++ b/tests/chains/Ethereum/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -21,6 +21,7 @@ TEST(TWEthereumCoinType, TWCoinType) { auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeEthereum, accId.get())); auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeEthereum)); auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeEthereum)); + const auto chainId = WRAPS(TWCoinTypeChainId(TWCoinTypeEthereum)); ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeEthereum), 18); ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeEthereum)); @@ -31,4 +32,5 @@ TEST(TWEthereumCoinType, TWCoinType) { assertStringsEqual(accUrl, "https://etherscan.io/address/0x5bb497e8d9fe26e92dd1be01e32076c8e024d167"); assertStringsEqual(id, "ethereum"); assertStringsEqual(name, "Ethereum"); + assertStringsEqual(chainId, "1"); } diff --git a/tests/Ethereum/TWEthereumAbiTests.cpp b/tests/chains/Ethereum/TWEthereumAbiTests.cpp similarity index 97% rename from tests/Ethereum/TWEthereumAbiTests.cpp rename to tests/chains/Ethereum/TWEthereumAbiTests.cpp index f64d3886540..9cb63bd7569 100644 --- a/tests/Ethereum/TWEthereumAbiTests.cpp +++ b/tests/chains/Ethereum/TWEthereumAbiTests.cpp @@ -13,7 +13,7 @@ #include "HexCoding.h" #include "uint256.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -41,7 +41,7 @@ TEST(TWEthereumAbi, FuncCreate1) { EXPECT_EQ(0, p2index); // check back get value auto p2val2 = TWEthereumAbiFunctionGetParamUInt64(func, p2index, true); - EXPECT_EQ(9, p2val2); + EXPECT_EQ(9ul, p2val2); auto type = WRAPS(TWEthereumAbiFunctionGetType(func)); std::string type2 = TWStringUTF8Bytes(type.get()); @@ -187,7 +187,7 @@ TEST(TWEthereumAbi, EncodeFuncMonster) { // check back out params EXPECT_EQ(1, TWEthereumAbiFunctionGetParamUInt8(func, 0, false)); - EXPECT_EQ(4, TWEthereumAbiFunctionGetParamUInt64(func, 3, false)); + EXPECT_EQ(4ul, TWEthereumAbiFunctionGetParamUInt64(func, 3, false)); EXPECT_EQ(true, TWEthereumAbiFunctionGetParamBool(func, 12, false)); EXPECT_EQ(std::string("Hello, world!"), std::string(TWStringUTF8Bytes(WRAPS(TWEthereumAbiFunctionGetParamString(func, 13, false)).get()))); WRAPD(TWEthereumAbiFunctionGetParamAddress(func, 14, false)); @@ -200,7 +200,7 @@ TEST(TWEthereumAbi, EncodeFuncMonster) { auto encoded = WRAPD(TWEthereumAbiEncode(func)); Data enc2 = data(TWDataBytes(encoded.get()), TWDataSize(encoded.get())); - EXPECT_EQ(4 + 76 * 32, enc2.size()); + EXPECT_EQ(4ul + 76 * 32, enc2.size()); // delete TWEthereumAbiFunctionDelete(func); @@ -216,14 +216,14 @@ TEST(TWEthereumAbi, DecodeOutputFuncCase1) { EXPECT_EQ(0, TWEthereumAbiFunctionAddParamUInt64(func, 0, true)); // original output value - EXPECT_EQ(0, TWEthereumAbiFunctionGetParamUInt64(func, 0, true)); + EXPECT_EQ(0ul, TWEthereumAbiFunctionGetParamUInt64(func, 0, true)); // decode auto encoded = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0000000000000000000000000000000000000000000000000000000000000045")).get())); EXPECT_EQ(true, TWEthereumAbiDecodeOutput(func, encoded.get())); // new output value - EXPECT_EQ(0x45, TWEthereumAbiFunctionGetParamUInt64(func, 0, true)); + EXPECT_EQ(0x45ul, TWEthereumAbiFunctionGetParamUInt64(func, 0, true)); // delete TWEthereumAbiFunctionDelete(func); @@ -238,11 +238,11 @@ TEST(TWEthereumAbi, GetParamWrongType) { // GetParameter with correct types EXPECT_EQ(1, TWEthereumAbiFunctionGetParamUInt8(func, 0, true)); - EXPECT_EQ(2, TWEthereumAbiFunctionGetParamUInt64(func, 1, true)); + EXPECT_EQ(2ul, TWEthereumAbiFunctionGetParamUInt64(func, 1, true)); // GetParameter with wrong type, default value (0) is returned EXPECT_EQ(0, TWEthereumAbiFunctionGetParamUInt8(func, 1, true)); - EXPECT_EQ(0, TWEthereumAbiFunctionGetParamUInt64(func, 0, true)); + EXPECT_EQ(0ul, TWEthereumAbiFunctionGetParamUInt64(func, 0, true)); EXPECT_EQ("00", hex(*(static_cast(WRAPD(TWEthereumAbiFunctionGetParamUInt256(func, 0, true)).get())))); EXPECT_EQ(false, TWEthereumAbiFunctionGetParamBool(func, 0, true)); EXPECT_EQ("", std::string(TWStringUTF8Bytes(WRAPS(TWEthereumAbiFunctionGetParamString(func, 0, true)).get()))); @@ -250,7 +250,7 @@ TEST(TWEthereumAbi, GetParamWrongType) { // GetParameter with wrong index, default value (0) is returned EXPECT_EQ(0, TWEthereumAbiFunctionGetParamUInt8(func, 99, true)); - EXPECT_EQ(0, TWEthereumAbiFunctionGetParamUInt64(func, 99, true)); + EXPECT_EQ(0ul, TWEthereumAbiFunctionGetParamUInt64(func, 99, true)); EXPECT_EQ("00", hex(*(static_cast(WRAPD(TWEthereumAbiFunctionGetParamUInt256(func, 99, true)).get())))); EXPECT_EQ(false, TWEthereumAbiFunctionGetParamBool(func, 99, true)); EXPECT_EQ("", std::string(TWStringUTF8Bytes(WRAPS(TWEthereumAbiFunctionGetParamString(func, 99, true)).get()))); diff --git a/tests/Ethereum/TWEthereumAbiValueDecoderTests.cpp b/tests/chains/Ethereum/TWEthereumAbiValueDecoderTests.cpp similarity index 99% rename from tests/Ethereum/TWEthereumAbiValueDecoderTests.cpp rename to tests/chains/Ethereum/TWEthereumAbiValueDecoderTests.cpp index 2f84327195b..da8dcc4b728 100644 --- a/tests/Ethereum/TWEthereumAbiValueDecoderTests.cpp +++ b/tests/chains/Ethereum/TWEthereumAbiValueDecoderTests.cpp @@ -8,7 +8,7 @@ #include "Data.h" #include "HexCoding.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include using namespace TW; diff --git a/tests/Ethereum/TWEthereumAbiValueEncodeTests.cpp b/tests/chains/Ethereum/TWEthereumAbiValueEncodeTests.cpp similarity index 98% rename from tests/Ethereum/TWEthereumAbiValueEncodeTests.cpp rename to tests/chains/Ethereum/TWEthereumAbiValueEncodeTests.cpp index b4b120adbd3..7235666c804 100644 --- a/tests/Ethereum/TWEthereumAbiValueEncodeTests.cpp +++ b/tests/chains/Ethereum/TWEthereumAbiValueEncodeTests.cpp @@ -9,7 +9,7 @@ #include "Data.h" #include "HexCoding.h" #include "uint256.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include using namespace TW; diff --git a/tests/Ethereum/ValueDecoderTests.cpp b/tests/chains/Ethereum/ValueDecoderTests.cpp similarity index 83% rename from tests/Ethereum/ValueDecoderTests.cpp rename to tests/chains/Ethereum/ValueDecoderTests.cpp index 78d52a80f4c..c576971e9d4 100644 --- a/tests/Ethereum/ValueDecoderTests.cpp +++ b/tests/chains/Ethereum/ValueDecoderTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,8 +9,7 @@ #include -using namespace TW; -using namespace TW::Ethereum; +namespace TW::Ethereum::tests { uint256_t decodeFromHex(std::string s) { auto data = parse_hex(s); @@ -31,12 +30,12 @@ TEST(EthereumAbiValueDecoder, decodeValue) { EXPECT_EQ("24", ABI::ValueDecoder::decodeValue(parse_hex("0000000000000000000000000000000000000000000000000000000000000018"), "uint8")); EXPECT_EQ("123456", ABI::ValueDecoder::decodeValue(parse_hex("000000000000000000000000000000000000000000000000000000000001e240"), "uint256")); EXPECT_EQ("0xf784682c82526e245f50975190ef0fff4e4fc077", ABI::ValueDecoder::decodeValue(parse_hex("000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077"), "address")); - EXPECT_EQ("Hello World! Hello World! Hello World!", - ABI::ValueDecoder::decodeValue(parse_hex( - "000000000000000000000000000000000000000000000000000000000000002c" - "48656c6c6f20576f726c64212020202048656c6c6f20576f726c642120202020" - "48656c6c6f20576f726c64210000000000000000000000000000000000000000" - ), "string")); + EXPECT_EQ("Hello World! Hello World! Hello World!", + ABI::ValueDecoder::decodeValue(parse_hex( + "000000000000000000000000000000000000000000000000000000000000002c" + "48656c6c6f20576f726c64212020202048656c6c6f20576f726c642120202020" + "48656c6c6f20576f726c64210000000000000000000000000000000000000000"), + "string")); EXPECT_EQ("0x31323334353637383930", ABI::ValueDecoder::decodeValue(parse_hex("3132333435363738393000000000000000000000000000000000000000000000"), "bytes10")); } @@ -47,10 +46,9 @@ TEST(EthereumAbiValueDecoder, decodeArray) { "0000000000000000000000000000000000000000000000000000000000000003" "0000000000000000000000000000000000000000000000000000000000000031" "0000000000000000000000000000000000000000000000000000000000000032" - "0000000000000000000000000000000000000000000000000000000000000033" - ); + "0000000000000000000000000000000000000000000000000000000000000033"); auto res = ABI::ValueDecoder::decodeArray(input, "uint8[]"); - EXPECT_EQ(3, res.size()); + EXPECT_EQ(3ul, res.size()); EXPECT_EQ("49", res[0]); EXPECT_EQ("50", res[1]); EXPECT_EQ("51", res[2]); @@ -60,10 +58,9 @@ TEST(EthereumAbiValueDecoder, decodeArray) { Data input = parse_hex( "0000000000000000000000000000000000000000000000000000000000000002" "000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077" - "0000000000000000000000002e00cd222cb42b616d86d037cc494e8ab7f5c9a3" - ); + "0000000000000000000000002e00cd222cb42b616d86d037cc494e8ab7f5c9a3"); auto res = ABI::ValueDecoder::decodeArray(input, "address[]"); - EXPECT_EQ(2, res.size()); + EXPECT_EQ(2ul, res.size()); EXPECT_EQ("0xf784682c82526e245f50975190ef0fff4e4fc077", res[0]); EXPECT_EQ("0x2e00cd222cb42b616d86d037cc494e8ab7f5c9a3", res[1]); } @@ -76,11 +73,12 @@ TEST(EthereumAbiValueDecoder, decodeArray) { "0000000000000000000000000000000000000000000000000000000000000002" "1011000000000000000000000000000000000000000000000000000000000000" "0000000000000000000000000000000000000000000000000000000000000003" - "1022220000000000000000000000000000000000000000000000000000000000" - ); + "1022220000000000000000000000000000000000000000000000000000000000"); auto res = ABI::ValueDecoder::decodeArray(input, "bytes[]"); - EXPECT_EQ(2, res.size()); + EXPECT_EQ(2ul, res.size()); EXPECT_EQ("0x1011", res[0]); EXPECT_EQ("0x102222", res[1]); } } + +} // namespace TW::Ethereum::tests diff --git a/tests/Ethereum/ValueEncoderTests.cpp b/tests/chains/Ethereum/ValueEncoderTests.cpp similarity index 87% rename from tests/Ethereum/ValueEncoderTests.cpp rename to tests/chains/Ethereum/ValueEncoderTests.cpp index bc6f8fd22a5..29a621d2a05 100644 --- a/tests/Ethereum/ValueEncoderTests.cpp +++ b/tests/chains/Ethereum/ValueEncoderTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,14 +9,12 @@ #include -using namespace TW; -using namespace TW::Ethereum; - -Data data; +namespace TW::Ethereum::tests { void checkLast32BytesEqual(const Data& data, const char* expected) { EXPECT_EQ(hex(subData(data, data.size() - 32, 32)), expected); } + TEST(EthereumAbiValueEncoder, encodeBool) { Data data; ABI::ValueEncoder::encodeBool(false, data); @@ -121,16 +119,18 @@ TEST(EthereumAbiValueEncoder, uint256FromInt256) { } TEST(EthereumAbiValueEncoder, pad32) { - EXPECT_EQ(64, ABI::ValueEncoder::paddedTo32(40)); - EXPECT_EQ(32, ABI::ValueEncoder::paddedTo32(32)); - EXPECT_EQ(64, ABI::ValueEncoder::paddedTo32(33)); - EXPECT_EQ(64, ABI::ValueEncoder::paddedTo32(63)); - EXPECT_EQ(64, ABI::ValueEncoder::paddedTo32(64)); - EXPECT_EQ(96, ABI::ValueEncoder::paddedTo32(65)); - EXPECT_EQ(24, ABI::ValueEncoder::padNeeded32(40)); - EXPECT_EQ(0, ABI::ValueEncoder::padNeeded32(32)); - EXPECT_EQ(31, ABI::ValueEncoder::padNeeded32(33)); - EXPECT_EQ(1, ABI::ValueEncoder::padNeeded32(63)); - EXPECT_EQ(0, ABI::ValueEncoder::padNeeded32(64)); - EXPECT_EQ(31, ABI::ValueEncoder::padNeeded32(65)); + EXPECT_EQ(64ul, ABI::ValueEncoder::paddedTo32(40)); + EXPECT_EQ(32ul, ABI::ValueEncoder::paddedTo32(32)); + EXPECT_EQ(64ul, ABI::ValueEncoder::paddedTo32(33)); + EXPECT_EQ(64ul, ABI::ValueEncoder::paddedTo32(63)); + EXPECT_EQ(64ul, ABI::ValueEncoder::paddedTo32(64)); + EXPECT_EQ(96ul, ABI::ValueEncoder::paddedTo32(65)); + EXPECT_EQ(24ul, ABI::ValueEncoder::padNeeded32(40)); + EXPECT_EQ(0ul, ABI::ValueEncoder::padNeeded32(32)); + EXPECT_EQ(31ul, ABI::ValueEncoder::padNeeded32(33)); + EXPECT_EQ(1ul, ABI::ValueEncoder::padNeeded32(63)); + EXPECT_EQ(0ul, ABI::ValueEncoder::padNeeded32(64)); + EXPECT_EQ(31ul, ABI::ValueEncoder::padNeeded32(65)); } + +} // namespace TW::Ethereum::tests diff --git a/tests/EthereumClassic/TWCoinTypeTests.cpp b/tests/chains/EthereumClassic/TWCoinTypeTests.cpp similarity index 92% rename from tests/EthereumClassic/TWCoinTypeTests.cpp rename to tests/chains/EthereumClassic/TWCoinTypeTests.cpp index 6774a3c294e..6ac2854fa36 100644 --- a/tests/EthereumClassic/TWCoinTypeTests.cpp +++ b/tests/chains/EthereumClassic/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -21,6 +21,7 @@ TEST(TWEthereumClassicCoinType, TWCoinType) { auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeEthereumClassic, accId.get())); auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeEthereumClassic)); auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeEthereumClassic)); + const auto chainId = WRAPS(TWCoinTypeChainId(TWCoinTypeEthereumClassic)); ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeEthereumClassic), 18); ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeEthereumClassic)); @@ -31,4 +32,5 @@ TEST(TWEthereumClassicCoinType, TWCoinType) { assertStringsEqual(accUrl, "https://blockscout.com/etc/mainnet/address/0x9eab4b0fc468a7f5d46228bf5a76cb52370d068d"); assertStringsEqual(id, "classic"); assertStringsEqual(name, "Ethereum Classic"); + assertStringsEqual(chainId, "61"); } diff --git a/tests/chains/Everscale/AddressTests.cpp b/tests/chains/Everscale/AddressTests.cpp new file mode 100644 index 00000000000..5896183c21d --- /dev/null +++ b/tests/chains/Everscale/AddressTests.cpp @@ -0,0 +1,52 @@ +// Copyright © 2017-2021 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 "Everscale/Address.h" +#include "Everscale/WorkchainType.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include +#include + +using namespace TW; + +namespace TW::Everscale { + +TEST(EverscaleAddress, Valid) { + ASSERT_TRUE(Address::isValid("0:83a0352908060fa87839195d8a763a8d9ab28f8fa41468832b398a719cc6469a")); +} + +TEST(EverscaleAddress, Invalid) { + ASSERT_FALSE(Address::isValid("hello world")); + ASSERT_FALSE(Address::isValid("83a0352908060fa87839195d8a763a8d9ab28f8fa41468832b398a719cc6469a")); + ASSERT_FALSE(Address::isValid("1:83a0352908060fa87839195d8a763a8d9ab28f8fa41468832b398a719cc6469a")); + ASSERT_FALSE(Address::isValid("-2:83a0352908060fa87839195d8a763a8d9ab28f8fa41468832b398a719cc6469a")); + ASSERT_FALSE(Address::isValid("2147483648:83a0352908060fa87839195d8a763a8d9ab28f8fa41468832b398a719cc6469a")); + ASSERT_FALSE(Address::isValid("0:83a0352908060fa87839195d8a763a8d9ab28f8fa41468832b398a719cc6469ab")); +} + +TEST(EverscaleAddress, FromString) { + auto address = Address("0:83a0352908060fa87839195d8a763a8d9ab28f8fa41468832b398a719cc6469a"); + ASSERT_EQ(address.string(), "0:83a0352908060fa87839195d8a763a8d9ab28f8fa41468832b398a719cc6469a"); + + auto address_uppercase = Address("0:83A0352908060FA87839195D8A763A8D9AB28F8FA41468832B398A719CC6469A"); + ASSERT_EQ(address_uppercase.string(), "0:83a0352908060fa87839195d8a763a8d9ab28f8fa41468832b398a719cc6469a"); +} + +TEST(EverscaleAddress, FromPrivateKey) { + auto privateKey = PrivateKey(parse_hex("5b59e0372d19b6355c73fa8cc708fa3301ae2ec21bb6277e8b79d386ccb7846f")); + auto address = Address(privateKey.getPublicKey(TWPublicKeyTypeED25519), WorkchainType::Basechain); + ASSERT_EQ(address.string(), "0:269fee242eb410786abe1777a14785c8bbeb1e34100c7570e17698b36ad66fb0"); +} + +TEST(EverscaleAddress, FromPublicKey) { + auto publicKey = PublicKey(parse_hex("e4925f9932df8d7fd0042efff3e2178a972028b644ded3a3b66f6d0577f82e78"), TWPublicKeyTypeED25519); + auto address = Address(publicKey, WorkchainType::Basechain); + ASSERT_EQ(address.string(), "0:269fee242eb410786abe1777a14785c8bbeb1e34100c7570e17698b36ad66fb0"); +} + +} // namespace TW::Everscale diff --git a/tests/chains/Everscale/CellBuilderTest.cpp b/tests/chains/Everscale/CellBuilderTest.cpp new file mode 100644 index 00000000000..860f3bac6e5 --- /dev/null +++ b/tests/chains/Everscale/CellBuilderTest.cpp @@ -0,0 +1,121 @@ +// Copyright © 2017-2021 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "BinaryCoding.h" +#include "HexCoding.h" +#include "PublicKey.h" + +#include "Everscale/Cell.h" +#include "Everscale/CellBuilder.h" +#include "Everscale/Wallet.h" + +#include + +using namespace TW; +using boost::multiprecision::uint128_t; + +namespace TW::Everscale { + +void checkBuilder(const uint128_t& value, uint16_t bitLen, const std::string& hash) { + CellBuilder dataBuilder; + dataBuilder.appendU128(value); + const auto cell = dataBuilder.intoCell(); + ASSERT_EQ(cell->bitLen, bitLen); + ASSERT_EQ(hex(cell->hash), hash); +} + +TEST(EverscaleCell, BuilderVarUint16) { + const uint128_t oneEver{1'000'000'000u}; + + checkBuilder(0, 4, "5331fed036518120c7f345726537745c5929b8ea1fa37b99b2bb58f702671541"); + checkBuilder(1, 12, "d46edee086ccbace01f45c13d26d49b68f74cd1b7616f4662e699c82c6ec728b"); + checkBuilder(255, 12, "bd16b2d60c93163fbed832e91a5faec484715c48176857c57dcedf9f6e0f32f6"); + checkBuilder(256, 20, "16559011ce6f0f7aaa765179e73ef293f39610f5baa3838a1dc8c52da95793b3"); + checkBuilder(oneEver, 36, "e139b2d96d0bd76da98c3c23b0dc0481dcfe19562798fefbb7bf2e56d8ef37b5"); + checkBuilder(10 * oneEver, 44, "8882fead71f2deb3aa7b8dbd15bbb42c651fcaae8da82e6d5cf8e49825eed12b"); + checkBuilder(1000000 * oneEver, 60, "125f2f85da07f9d92148c067bc19aecbf4da65becdd6b51f17ae3a2aeb2c1bdd"); + checkBuilder(1'000'000'000'000u * oneEver, 76, "39bcb314cdb31de5159764d9c28779de27be44210ffcc52a27aa01bff1d82bf7"); +} + +TEST(EverscaleCell, ComputeContractAddress) { + const auto seqno = 0; + const auto walletId = WALLET_ID; + const auto publicKey = PublicKey(parse_hex("7dbe83e9b223157e85bed2628430e2cdb531d5c99ab428618b7dd29b567a0369"), TWPublicKeyTypeED25519); + + CellBuilder dataBuilder; + dataBuilder.appendU32(seqno); + dataBuilder.appendU32(walletId); + dataBuilder.appendRaw(publicKey.bytes, 256); + + const auto data = dataBuilder.intoCell(); + + // Builder should be empty after `intoCell` + { + const auto emptyCell = dataBuilder.intoCell(); + ASSERT_EQ(hex(emptyCell->hash), "96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7"); + } + + const auto code = Cell::deserialize(Wallet::code.data(), Wallet::code.size()); + + CellBuilder stateInitBuilder; + stateInitBuilder.appendBitZero(); // split_depth + stateInitBuilder.appendBitZero(); // special + stateInitBuilder.appendBitOne(); // code + stateInitBuilder.appendReferenceCell(code); + stateInitBuilder.appendBitOne(); // data + stateInitBuilder.appendReferenceCell(data); + stateInitBuilder.appendBitZero(); // library + + auto stateInit = stateInitBuilder.intoCell(); + + ASSERT_EQ(hex(stateInit->hash), "5a0f742c28067da91e05830f0b072a2069f0617a5f6529d295f6c517d63d67c6"); +} + +TEST(EverscaleCell, UnalignedRead) { + CellBuilder dataBuilder; + dataBuilder.appendU32(0x12312312); + + auto cell = dataBuilder.intoCell(); + auto slice = CellSlice(cell.get()); + + slice.dataOffset += 1; + const auto nextBytes = slice.getNextBytes(2); + ASSERT_TRUE(nextBytes.size() == 2 && nextBytes[0] == 0x24 && nextBytes[1] == 0x62); +} + +TEST(EverscaleCell, ReadZeroBytes) { + CellBuilder dataBuilder; + dataBuilder.appendU32(0x12312312); + + auto cell = dataBuilder.intoCell(); + auto slice = CellSlice(cell.get()); + + ASSERT_EQ(slice.getNextBytes(0), Data{}); +} + +TEST(EverscaleCell, InvalidBuilderData) { + CellBuilder dataBuilder; + ASSERT_ANY_THROW(dataBuilder.appendRaw(Data{}, 1)); +} + +TEST(EverscaleCell, DataOverflow) { + CellBuilder dataBuilder; + + Data data(128, 0x00); + ASSERT_ANY_THROW(dataBuilder.appendRaw(data, data.size() * 8)); +} + +TEST(EverscaleCell, DataUnderflow) { + CellBuilder dataBuilder; + dataBuilder.appendU32(0x12312312); + + auto cell = dataBuilder.intoCell(); + auto slice = CellSlice(cell.get()); + + ASSERT_ANY_THROW(slice.getNextBytes(100)); +} + +} // namespace TW::Everscale diff --git a/tests/chains/Everscale/CellTests.cpp b/tests/chains/Everscale/CellTests.cpp new file mode 100644 index 00000000000..c9b1b821cb9 --- /dev/null +++ b/tests/chains/Everscale/CellTests.cpp @@ -0,0 +1,106 @@ +// Copyright © 2017-2021 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Base64.h" +#include "Everscale/Cell.h" +#include "Everscale/Wallet.h" +#include "HexCoding.h" +#include +#include +#include +#include + +using namespace TW; + +namespace TW::Everscale { + +// All hashes could be verified using https://ever.bytie.moe/visualizer + +static constexpr auto TX = "te6ccgECDgEAAyUAA7V6uRyM7ESqbjssMUQyAqYyQTlEkfDkEhWjBiC1fvKLabAAAaKsEuhQGeqOSyMlWG32ehDzoVCXMh6ugfMLr6pOPj3b6KD4DR/wAAGirA4jnSYtmdCAADR6V/lIBQQBAg8MQQYcxc1EQAMCAG/JkrcETDHn2AAAAAAAAgAAAAAAAop8XDVxQ98+QpgCzzW0U0opAulbEzfySLp3wLLoHzboQRA6FACdROMjE4gAAAAAAAAAACHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIACCcgkc5v5RyBDaeDdbY8Q+SpmX5OOkhzxFyB/ug4o/bJwJXDMKjAeOEr9OvfcJlgyx80ukSBl43/FO2DXIt1+SuAQCAeAJBgEB3wcBsWgBVyORnYiVTcdlhiiGQFTGSCcokj4cgkK0YMQWr95RbTcAIXxdqfc1KmavZDEIGsfjdBuS4lE5Ox4rJZ+Z+rauOxDRZaC8AAYx6CQAADRVgl0KBMWzOhDACAF7C04VCAAAAAAvMK5pAAAAAAAAAAAAAAAAAA7xIIAFdGVusOGq5cSrATb2hH5h5turvUDrer4E0Mf51wPlefAMAd2IAVcjkZ2IlU3HZYYohkBUxkgnKJI+HIJCtGDEFq/eUW02AMrbR14n5UXrp1deXDU5rl8kQvDfSKbnA+d09dtQe2mo8t94jbtllx/DjCgucIpvywjhJBhWNQjtWXh4dP6qiDAAAAADFszqDukuhIYKAQTQAwsB42IAQvi7U+5qVM1eyGIQNY/G6DclxKJydjxWSz8z9W1cdiGiy0F4AAAAAAAAAAAAAAAAAAALThUIAAAAAC8wrmkAAAAAAAAAAAAAAAAADvEggAV0ZW6w4arlxKsBNvaEfmHm26u9QOt6vgTQx/nXA+V58AwBY4AUk5qcKxU0Kqq8xI6zxcnOzaRMAW8AGxI8jfJSPgiVJ6AAAAAAAAAAAAAARVadlK9wDQBDgBVyORnYiVTcdlhiiGQFTGSCcokj4cgkK0YMQWr95RbTcA=="; +static constexpr auto BIG_TX = "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"; + +TEST(EverscaleCell, DeserializeTransaction) { + const auto tx = Cell::fromBase64(TX); + ASSERT_EQ(hex(tx->hash), "88a02e7bd8833d384f37d63d4d01deef9a1806937b94a313cc5e8c3cc7643032"); + + const auto bigTx = Cell::fromBase64(BIG_TX); + ASSERT_EQ(hex(bigTx->hash), "37f29d9f3aa5c7cc783962d861c08705f245f5e5bfedd208dfe08d02e80a47d8"); +} + +TEST(EverscaleCell, DeserializeWallet) { + const auto cell = Cell::deserialize(Wallet::code.data(), Wallet::code.size()); + ASSERT_EQ(hex(cell->hash), "84dafa449f98a6987789ba232358072bc0f76dc4524002a5d0918b9a75d2d599"); +} + +TEST(EverscaleCell, SerializeTransaction) { + const auto cell = Cell::fromBase64(TX); + Data data; + cell->serialize(data); + + const auto decoded = Cell::deserialize(data.data(), data.size()); + ASSERT_EQ(hex(decoded->hash), "88a02e7bd8833d384f37d63d4d01deef9a1806937b94a313cc5e8c3cc7643032"); +} + +TEST(EverscaleCell, SerializeWallet) { + const auto cell = Cell::deserialize(Wallet::code.data(), Wallet::code.size()); + Data data; + cell->serialize(data); + + const auto decoded = Cell::deserialize(data.data(), data.size()); + ASSERT_EQ(hex(decoded->hash), "84dafa449f98a6987789ba232358072bc0f76dc4524002a5d0918b9a75d2d599"); +} + +TEST(EverscaleCell, EmptyCell) { + const auto EMPTY_CELL = "96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7"; + + const auto cell1 = Cell::fromBase64("te6ccgEBAQEAAgAAAA=="); + ASSERT_EQ(hex(cell1->hash), EMPTY_CELL); + + // With index + const auto cell2 = Cell::fromBase64("te6ccoEBAQEAAwACAAA="); + ASSERT_EQ(hex(cell2->hash), EMPTY_CELL); + + // With d2 > 0 && d2%8 != 0 but without end flag (computeBitLen should just return 0) + const auto cell3 = Cell::fromBase64("te6ccgEBAQEAAwAABQAAAA=="); + ASSERT_EQ(hex(cell3->hash), EMPTY_CELL); + + // With `storeHashes` (provided hash should be skipped) + const auto cell4 = Cell::fromBase64("te6ccgEBAQEAJAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); + ASSERT_EQ(hex(cell4->hash), EMPTY_CELL); +} + +TEST(EverscaleCell, InvalidCell) { + // unexpected eof + ASSERT_THROW(Cell::fromBase64("te4="), std::runtime_error); + // unknown magic + ASSERT_THROW(Cell::fromBase64("aGVsbG8gd29ybGQK"), std::runtime_error); + // refSize > 4 + ASSERT_THROW(Cell::fromBase64("te6ccgUCAAAAAAEAAAAAAQAAAAAAFD4AAAAAAAO3etQ="), std::runtime_error); + // unsupported root count + ASSERT_THROW(Cell::fromBase64("te6ccgECdRIAFD4AA7d61A=="), std::runtime_error); + // root count is greater than cell count + ASSERT_THROW(Cell::fromBase64("te6ccgECAAEAFD4AA7d61A=="), std::runtime_error); + // absent cells are not supported + ASSERT_THROW(Cell::fromBase64("te6ccgECAQESFD4AA7d61A=="), std::runtime_error); + // non-zero level is not supported + ASSERT_THROW(Cell::fromBase64("te6ccgECAQEAFD4AY7d61FeCHf8yG3+VsHLEilQ6UuYy8wcpWi1+/Q=="), std::runtime_error); + // exotic cells are not supported + ASSERT_THROW(Cell::fromBase64("te6ccgECAQEAFD4AC7d61FeCHf8yG3+VsHLEilQ6UuYy8wcpWi1+/Q=="), std::runtime_error); + // absent cells are not supported + ASSERT_THROW(Cell::fromBase64("te6ccgECAQEAFD4AF7d61FeCHf8yG3+VsHLEilQ6UuYy8wcpWi1+/Q=="), std::runtime_error); + // invalid ref count + ASSERT_THROW(Cell::fromBase64("te6ccgEBAQEAAwAFAA=="), std::runtime_error); + // invalid child index + ASSERT_THROW(Cell::fromBase64("te6ccgEBAQEAJAABAP8="), std::runtime_error); + // invalid child index (reference to itself) + ASSERT_THROW(Cell::fromBase64("te6ccgEBAQEAAgABAAA="), std::runtime_error); + // invalid root index + ASSERT_THROW(Cell::fromBase64("te6ccgEBAQEAAwEAAA"), std::runtime_error); + // child cell not found + ASSERT_THROW(Cell::fromBase64("te6ccgEBAQEAAgABAAE="), std::runtime_error); +} + +} // namespace TW::Everscale diff --git a/tests/chains/Everscale/SignerTests.cpp b/tests/chains/Everscale/SignerTests.cpp new file mode 100644 index 00000000000..80eb521a52d --- /dev/null +++ b/tests/chains/Everscale/SignerTests.cpp @@ -0,0 +1,88 @@ +// Copyright © 2017-2021 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 "Everscale/Messages.h" +#include "Everscale/Signer.h" + +#include "Base64.h" +#include "HexCoding.h" + +#include + +using namespace TW; + +namespace TW::Everscale { + +TEST(EverscaleSigner, TransferWithDeploy) { + auto input = Proto::SigningInput(); + + auto& transfer = *input.mutable_transfer(); + transfer.set_bounce(false); + transfer.set_behavior(Proto::MessageBehavior::SimpleTransfer); + transfer.set_amount(500000000); + transfer.set_expired_at(1680770631); + transfer.set_to("0:db18a67f4626f15ac0537a18445937f685f9b30184f0d7b28be4bdeb92d2fd90"); + + // NOTE: There is `set_encoded_contract_data` because contract was not deployed yet + + auto privateKey = parse_hex("542bd4288352f1c6b270046f153d406aec054a0a06000ab9b36b5c6dd3050ad4"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input); + + ASSERT_EQ(hex(Cell::fromBase64(output.encoded())->hash), "bfb18e56e9d00d783c7eb1726f08bf613dd0f01a110a130c0f8f91bb13390a39"); + + // Link to the message: https://everscan.io/messages/bfb18e56e9d00d783c7eb1726f08bf613dd0f01a110a130c0f8f91bb13390a39 + ASSERT_EQ(output.encoded(), "te6ccgICAAQAAQAAAUoAAAPhiAG+Ilaz1wTyTEauoymMGl6o+NGqhszIlHS8BXAmXniYrBGMBTen55/RbfcIBoeCrPB1cxPMcHRx7xyBzJmdtewBPaTu/WuHgnqg09jQaxTEcii+Nuqm7p3b6iMq+/6598ggCXUlsUyF0MjgAAAAAHAAAwACAAEAaEIAbYxTP6MTeK1gKb0MIiyb+0L82YDCeGvZRfJe9clpfsgg7msoAAAAAAAAAAAAAAAAAAAAUAAAAABLqS2KOWKN+7Y5OSiKhKisiw6t/h2ovvR3WbapyAtrdctwupwA3v8AIN0gggFMl7ohggEznLqxn3Gw7UTQ0x/THzHXC//jBOCk8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/T/9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVA=="); +} + +TEST(EverscaleSigner, Transfer1) { + auto input = Proto::SigningInput(); + + auto& transfer = *input.mutable_transfer(); + transfer.set_bounce(false); + transfer.set_behavior(Proto::MessageBehavior::SimpleTransfer); + transfer.set_amount(100000000); + transfer.set_expired_at(1680770631); + transfer.set_to("0:db18a67f4626f15ac0537a18445937f685f9b30184f0d7b28be4bdeb92d2fd90"); + + transfer.set_encoded_contract_data("te6ccgEBAQEAKgAAUAAAAAFLqS2KOWKN+7Y5OSiKhKisiw6t/h2ovvR3WbapyAtrdctwupw="); + + auto privateKey = parse_hex("542bd4288352f1c6b270046f153d406aec054a0a06000ab9b36b5c6dd3050ad4"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input); + + ASSERT_EQ(hex(Cell::fromBase64(output.encoded())->hash), "73807b0a3ca2d8564c023dccd5b9da222a270f68338c6fc2c064dda376a2c59d"); + + // Link to the message: https://everscan.io/messages/73807b0a3ca2d8564c023dccd5b9da222a270f68338c6fc2c064dda376a2c59d + ASSERT_EQ(output.encoded(), "te6ccgICAAIAAQAAAKoAAAHfiAG+Ilaz1wTyTEauoymMGl6o+NGqhszIlHS8BXAmXniYrAImASIQKH2jIwoA65IGC6aua4gAA4fFo/Nuxgb3sIRELhZnSXIS7IsE2E4D+8hk3EWGVZX+ICqlN/ka9DvXduhaXUlsUyF0MjgAAAAIHAABAGhCAG2MUz+jE3itYCm9DCIsm/tC/NmAwnhr2UXyXvXJaX7IIC+vCAAAAAAAAAAAAAAAAAAA"); +} + +TEST(EverscaleSigner, Transfer2) { + auto input = Proto::SigningInput(); + + auto& transfer = *input.mutable_transfer(); + transfer.set_bounce(true); + transfer.set_behavior(Proto::MessageBehavior::SendAllBalance); + transfer.set_amount(200000000); + transfer.set_expired_at(1680770631); + transfer.set_to("0:df112b59eb82792623575194c60d2f547c68d54366644a3a5e02b8132f3c4c56"); + + transfer.set_encoded_contract_data("te6ccgEBAQEAKgAAUAAAAAJLqS2KOWKN+7Y5OSiKhKisiw6t/h2ovvR3WbapyAtrdctwupw="); + + auto privateKey = parse_hex("542bd4288352f1c6b270046f153d406aec054a0a06000ab9b36b5c6dd3050ad4"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input); + + ASSERT_EQ(hex(Cell::fromBase64(output.encoded())->hash), "e35616cfa88e115580f07c6b41ae3ded1902d2bab1efefb74f677b4aececef24"); + + // Link to the message: https://everscan.io/messages/e35616cfa88e115580f07c6b41ae3ded1902d2bab1efefb74f677b4aececef24 + ASSERT_EQ(output.encoded(), "te6ccgICAAIAAQAAAKoAAAHfiAG+Ilaz1wTyTEauoymMGl6o+NGqhszIlHS8BXAmXniYrANrT0ivIEpuMGjKoyS9J03Wbl24jowXvdzQdLD6L3USLETUyRGbbmbUfBcNtF1FwKtmIQd0lNR1qIX9K/eloMgaXUlsUyF0MjgAAAAUFAABAGhiAG+Ilaz1wTyTEauoymMGl6o+NGqhszIlHS8BXAmXniYrIF9eEAAAAAAAAAAAAAAAAAAA"); +} + +} // namespace TW::Everscale diff --git a/tests/chains/Everscale/TWAnyAddressTests.cpp b/tests/chains/Everscale/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..6cf7627b0f6 --- /dev/null +++ b/tests/chains/Everscale/TWAnyAddressTests.cpp @@ -0,0 +1,30 @@ +// Copyright © 2017-2021 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include + +#include "TestUtilities.h" +#include + +namespace TW::Everscale { + +TEST(TWEverscale, HDWallet) { + auto mnemonic = + STRING("shoot island position soft burden budget tooth cruel issue economy destroy above"); + auto passphrase = STRING(""); + + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(mnemonic.get(), passphrase.get())); + + auto privateKey = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), TWCoinTypeEverscale, WRAPS(TWCoinTypeDerivationPath(TWCoinTypeEverscale)).get())); + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519(privateKey.get())); + auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeEverscale)); + auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + + assertStringsEqual(addressStr, "0:0c39661089f86ec5926ea7d4ee4223d634ba4ed6dcc2e80c7b6a8e6d59f79b04"); +} + +} // namespace TW::Everscale diff --git a/tests/chains/Everscale/TWAnySignerTests.cpp b/tests/chains/Everscale/TWAnySignerTests.cpp new file mode 100644 index 00000000000..d404ce00210 --- /dev/null +++ b/tests/chains/Everscale/TWAnySignerTests.cpp @@ -0,0 +1,38 @@ +// Copyright © 2017-2021 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Base64.h" +#include "HexCoding.h" +#include "proto/Everscale.pb.h" +#include + +#include "TestUtilities.h" +#include + +namespace TW::Everscale { + +TEST(TWAnySignerEverscale, SignMessageToDeployWallet) { + Proto::SigningInput input; + + auto& transfer = *input.mutable_transfer(); + transfer.set_bounce(false); + transfer.set_behavior(Proto::MessageBehavior::SimpleTransfer); + transfer.set_amount(500000000); + transfer.set_expired_at(1680770631); + transfer.set_to("0:db18a67f4626f15ac0537a18445937f685f9b30184f0d7b28be4bdeb92d2fd90"); + + // NOTE: There is `set_encoded_contract_data` because contract was not deployed yet + + auto privateKey = parse_hex("542bd4288352f1c6b270046f153d406aec054a0a06000ab9b36b5c6dd3050ad4"); + input.set_private_key(privateKey.data(), privateKey.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEverscale); + + ASSERT_EQ(output.encoded(), "te6ccgICAAQAAQAAAUoAAAPhiAG+Ilaz1wTyTEauoymMGl6o+NGqhszIlHS8BXAmXniYrBGMBTen55/RbfcIBoeCrPB1cxPMcHRx7xyBzJmdtewBPaTu/WuHgnqg09jQaxTEcii+Nuqm7p3b6iMq+/6598ggCXUlsUyF0MjgAAAAAHAAAwACAAEAaEIAbYxTP6MTeK1gKb0MIiyb+0L82YDCeGvZRfJe9clpfsgg7msoAAAAAAAAAAAAAAAAAAAAUAAAAABLqS2KOWKN+7Y5OSiKhKisiw6t/h2ovvR3WbapyAtrdctwupwA3v8AIN0gggFMl7ohggEznLqxn3Gw7UTQ0x/THzHXC//jBOCk8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/T/9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVA=="); +} + +} // namespace TW::Everscale diff --git a/tests/chains/Everscale/TWCoinTypeTests.cpp b/tests/chains/Everscale/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..95a9297cef2 --- /dev/null +++ b/tests/chains/Everscale/TWCoinTypeTests.cpp @@ -0,0 +1,38 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + +namespace TW::Everscale { + +TEST(TWEverscaleCoinType, TWCoinType) { + const auto coin = TWCoinTypeEverscale; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("781238b2b0d15cd4cd2e2a0a142753750cd5e1b2c8b506fcede75a90e02f1268")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0:d2bf59964a05dee84a0dd1ddc0ad83ba44d49719cf843d689dc8b726d0fb59d8")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "everscale"); + assertStringsEqual(name, "Everscale"); + assertStringsEqual(symbol, "EVER"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 9); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEverscale); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(txUrl, "https://everscan.io/transactions/781238b2b0d15cd4cd2e2a0a142753750cd5e1b2c8b506fcede75a90e02f1268"); + assertStringsEqual(accUrl, "https://everscan.io/accounts/0:d2bf59964a05dee84a0dd1ddc0ad83ba44d49719cf843d689dc8b726d0fb59d8"); +} + +} // namespace TW::Everscale diff --git a/tests/chains/Evmos/SignerTests.cpp b/tests/chains/Evmos/SignerTests.cpp new file mode 100644 index 00000000000..0dc9d3e816c --- /dev/null +++ b/tests/chains/Evmos/SignerTests.cpp @@ -0,0 +1,126 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "HexCoding.h" +#include "Base64.h" +#include "proto/Cosmos.pb.h" +#include "Cosmos/Address.h" +#include "Cosmos/Signer.h" +#include "TestUtilities.h" + +#include + +#include +#include + +namespace TW::Cosmos::evmos::tests { + +TEST(EvmosSigner, SignTxJsonEthermintKeyType) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::JSON); // obsolete + input.set_account_number(1037); + input.set_chain_id("evmos_9001-2"); + input.set_memo(""); + input.set_sequence(8); + + auto fromAddress = Address("evmos", parse_hex("BC2DA90C84049370D1B7C528BC164BC588833F21")); + auto toAddress = Address("evmos", parse_hex("12E8FE8B81ECC1F4F774EA6EC8DF267138B9F2D9")); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address(fromAddress.string()); + message.set_to_address(toAddress.string()); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("muon"); + amountOfTx->set_amount("1"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("muon"); + amountOfFee->set_amount("200"); + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeNativeEvmos); + auto anotherExpectedJson =R"( + { + "mode":"block", + "tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"}, + "memo":"", + "msg":[{"type":"cosmos-sdk/MsgSend", + "value":{"amount":[{"amount":"1","denom":"muon"}], + "from_address":"evmos1hsk6jryyqjfhp5dhc55tc9jtckygx0ep4mur4z", + "to_address":"evmos1zt50azupanqlfam5afhv3hexwyutnuke45f6ye"}}], + "signatures": + [ + { + "pub_key": + { + "type":"ethermint/PubKeyEthSecp256k1", + "value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F" + }, + "signature":"RWt8aaBxdMAeEjym8toWskJ6WaJpEF9Ciucz2lAHkvNnTicGpzxwTUzJbJXRirSnGkejhISaYtDw2RBiq0vg5w==" + } + ]} + })"_json; + + /// This tx is not broadcasted, we just want to test the signature format (ethermint/PubKeyEthSecp256k1) + EXPECT_EQ(anotherExpectedJson, nlohmann::json::parse(output.json())); + + auto signatures = nlohmann::json::parse(output.signature_json()); + + auto expectedSignatures = R"( + [ + { + "pub_key": + { + "type":"ethermint/PubKeyEthSecp256k1", + "value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F" + }, + "signature":"RWt8aaBxdMAeEjym8toWskJ6WaJpEF9Ciucz2lAHkvNnTicGpzxwTUzJbJXRirSnGkejhISaYtDw2RBiq0vg5w==" + } + ])"_json; + EXPECT_EQ(signatures, expectedSignatures); +} + +TEST(EvmosSigner, CompoundingAuthz) { + // Successfully broadcasted https://www.mintscan.io/evmos/txs/8D811CEC078420C41220F0B584EA0AC037763380FAC31E0E352E4BB4D1D18B79 + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(2139877); + input.set_chain_id("evmos_9001-2"); + input.set_memo(""); + input.set_sequence(3); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_auth_grant(); + message.set_granter("evmos12m9grgas60yk0kult076vxnsrqz8xpjy9rpf3e"); + message.set_grantee("evmos18fzq4nac28gfma6gqfvkpwrgpm5ctar2z9mxf3"); + auto& grant_stake = *message.mutable_grant_stake(); + grant_stake.mutable_allow_list()->add_address("evmosvaloper1umk407eed7af6anvut6llg2zevnf0dn0feqqny"); + grant_stake.set_authorization_type(TW::Cosmos::Proto::Message_AuthorizationType_DELEGATE); + message.set_expiration(1692309600); + + auto& fee = *input.mutable_fee(); + fee.set_gas(180859); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("aevmos"); + amountOfFee->set_amount("4521475000000000"); + + auto privateKey = parse_hex("79bcbded1a5678ab34e6d9db9ad78e4e480e7b22723cc5fbf59e843732e1a8e5"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeNativeEvmos); + auto expected = R"( + { + "mode":"BROADCAST_MODE_BLOCK", + "tx_bytes":"CvUBCvIBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQSzwEKLGV2bW9zMTJtOWdyZ2FzNjB5azBrdWx0MDc2dnhuc3Jxejh4cGp5OXJwZjNlEixldm1vczE4ZnpxNG5hYzI4Z2ZtYTZncWZ2a3B3cmdwbTVjdGFyMno5bXhmMxpxCmcKKi9jb3Ntb3Muc3Rha2luZy52MWJldGExLlN0YWtlQXV0aG9yaXphdGlvbhI5EjUKM2V2bW9zdmFsb3BlcjF1bWs0MDdlZWQ3YWY2YW52dXQ2bGxnMnpldm5mMGRuMGZlcXFueSABEgYI4LD6pgYSfQpZCk8KKC9ldGhlcm1pbnQuY3J5cHRvLnYxLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohA4B2WHbj6sH/GWE7z/YW5PRnXYFGaGRAov7gZZI2Fv2nEgQKAggBGAMSIAoaCgZhZXZtb3MSEDQ1MjE0NzUwMDAwMDAwMDAQ+4QLGkAm17CZgB7m+CPVlITnrHosklMTL9zrUeGRs8FL8N0GcRami9zdJ+e3xuXOtJmwP7G6QNh85CRYjFj8a8lpmmJM" + })"; + assertJSONEqual(output.serialized(), expected); +} +} diff --git a/tests/chains/Evmos/TWAnyAddressTests.cpp b/tests/chains/Evmos/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..de3468d4cce --- /dev/null +++ b/tests/chains/Evmos/TWAnyAddressTests.cpp @@ -0,0 +1,39 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "TestUtilities.h" + +#include +#include + +#include + +namespace TW::Evmos::tests { + +TEST(EvmosAnyAddress, EvmosValidate) { + auto string = STRING("0x30627903124Aa1e71384bc52e1cb96E4AB3252b6"); + + EXPECT_TRUE(TWAnyAddressIsValid(string.get(), TWCoinTypeEvmos)); + + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeEvmos)); + + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "30627903124aa1e71384bc52e1cb96e4ab3252b6"); +} + +TEST(EvmosAnyAddress, EvmosCreate) { + auto publicKeyHex = "045a0c6b83b8bd9827e507270cadb499b7e3a9095246f6a2213281f783d877c98b256742741b0639f317768fe4f4c2762660c2112283a7685d815507dee3229173"; // shoot island position ... + const auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA(publicKeyHex).get(), TWPublicKeyTypeSECP256k1Extended)); + + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeEvmos)); + + EXPECT_EQ(std::string(TWStringUTF8Bytes(WRAPS(TWAnyAddressDescription(addr.get())).get())), std::string("0x8f348F300873Fd5DA36950B2aC75a26584584feE")); + + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "8f348f300873fd5da36950b2ac75a26584584fee"); +} + +} // namespace TW::Evmos::tests diff --git a/tests/chains/Evmos/TWCoinTypeTests.cpp b/tests/chains/Evmos/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..346d9c98117 --- /dev/null +++ b/tests/chains/Evmos/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "TestUtilities.h" +#include +#include + +namespace TW::Evmos::tests { + +TEST(TWEvmosCoinType, TWCoinTypeEvmos) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeEvmos)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x24af42cf4977a96d62e3a82c3cd9b519c3e7c53dd83398b88f0cb435d867b422")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeEvmos, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x30627903124Aa1e71384bc52e1cb96E4AB3252b6")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeEvmos, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeEvmos)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeEvmos)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeEvmos), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeEvmos)); + + assertStringsEqual(symbol, "EVMOS"); + assertStringsEqual(txUrl, "https://evm.evmos.org/tx/0x24af42cf4977a96d62e3a82c3cd9b519c3e7c53dd83398b88f0cb435d867b422"); + assertStringsEqual(accUrl, "https://evm.evmos.org/address/0x30627903124Aa1e71384bc52e1cb96E4AB3252b6"); + assertStringsEqual(id, "evmos"); + assertStringsEqual(name, "Evmos"); +} + +} // namespace TW::Evmos::tests diff --git a/tests/FIO/AddressTests.cpp b/tests/chains/FIO/AddressTests.cpp similarity index 94% rename from tests/FIO/AddressTests.cpp rename to tests/chains/FIO/AddressTests.cpp index f78e3ae25f2..aea76097f2e 100644 --- a/tests/FIO/AddressTests.cpp +++ b/tests/chains/FIO/AddressTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -11,8 +11,7 @@ #include -using namespace TW; -using namespace TW::FIO; +namespace TW::FIO::tests { TEST(FIOAddress, ValidateString) { ASSERT_FALSE(Address::isValid("abc")); @@ -24,7 +23,7 @@ TEST(FIOAddress, ValidateString) { TEST(FIOAddress, ValidateData) { Address address("FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o"); - EXPECT_EQ(address.bytes.size(), 37); + EXPECT_EQ(address.bytes.size(), 37ul); Data addrData = TW::data(address.bytes.data(), address.bytes.size()); EXPECT_EQ(Address::isValid(addrData), true); @@ -64,3 +63,5 @@ TEST(FIOAddress, GetPublicKey) { auto address = Address(publicKey); EXPECT_EQ(hex(address.publicKey().bytes), publicKeyHex); } + +} // namespace TW::FIO::tests diff --git a/tests/FIO/EncryptionTests.cpp b/tests/chains/FIO/EncryptionTests.cpp similarity index 97% rename from tests/FIO/EncryptionTests.cpp rename to tests/chains/FIO/EncryptionTests.cpp index 55aa29793f1..ac0c2ff9321 100644 --- a/tests/FIO/EncryptionTests.cpp +++ b/tests/chains/FIO/EncryptionTests.cpp @@ -15,8 +15,8 @@ #include -using namespace TW; -using namespace TW::FIO; +namespace TW::FIO::EncryptionTests { + using namespace std; TEST(FIOEncryption, checkEncrypt) { @@ -24,7 +24,7 @@ TEST(FIOEncryption, checkEncrypt) { const Data secret = parse_hex("02332627b9325cb70510a70f0f6be4bcb008fbbc7893ca51dedf5bf46aa740c0fc9d3fbd737d09a3c4046d221f4f1a323f515332c3fef46e7f075db561b1a2c9"); const Data plaintext = TW::data("secret message"); Data iv = parse_hex("f300888ca4f512cebdc0020ff0f7224c"); - + Data result = Encryption::checkEncrypt(secret, plaintext, iv); EXPECT_EQ(hex(result), "f300888ca4f512cebdc0020ff0f7224c7f896315e90e172bed65d005138f224da7301d5563614e3955750e4480aabf7753f44b4975308aeb8e23c31e114962ab"); } @@ -34,7 +34,7 @@ TEST(FIOEncryption, checkDecrypt) { const Data secret = parse_hex("02332627b9325cb70510a70f0f6be4bcb008fbbc7893ca51dedf5bf46aa740c0fc9d3fbd737d09a3c4046d221f4f1a323f515332c3fef46e7f075db561b1a2c9"); const Data encrypted = parse_hex("f300888ca4f512cebdc0020ff0f7224c7f896315e90e172bed65d005138f224da7301d5563614e3955750e4480aabf7753f44b4975308aeb8e23c31e114962ab"); const Data expectedPlaintext = TW::data("secret message"); - + Data result = Encryption::checkDecrypt(secret, encrypted); EXPECT_EQ(hex(result), hex(expectedPlaintext)); } @@ -53,7 +53,7 @@ TEST(FIOEncryption, checkEncryptInvalidIvLength) { TEST(FIOEncryption, checkDecryptInvalidMessageHMAC) { const Data secret = parse_hex("02332627b9325cb70510a70f0f6be4bcb008fbbc7893ca51dedf5bf46aa740c0fc9d3fbd737d09a3c4046d221f4f1a323f515332c3fef46e7f075db561b1a2c9"); const Data encrypted = parse_hex("f300888ca4f512cebdc0020ff0f7224c7f896315e90e172bed65d005138f224da7301d5563614e3955750e4480aabf7753f44b4975308aeb8e23c31e114962ab00"); - try { + try { Encryption::checkDecrypt(secret, encrypted); } catch (std::invalid_argument&) { // expected exception, OK @@ -64,7 +64,7 @@ TEST(FIOEncryption, checkDecryptInvalidMessageHMAC) { TEST(FIOEncryption, checkDecryptMessageTooShort) { const Data secret = parse_hex("02332627b9325cb70510a70f0f6be4bcb008fbbc7893ca51dedf5bf46aa740c0fc9d3fbd737d09a3c4046d221f4f1a323f515332c3fef46e7f075db561b1a2c9"); - try { + try { Encryption::checkDecrypt(secret, Data(60)); } catch (std::invalid_argument&) { // expected exception, OK @@ -75,7 +75,7 @@ TEST(FIOEncryption, checkDecryptMessageTooShort) { Data randomBuffer(size_t size) { Data d(size); - for (auto i = 0; i < size; ++i) { + for (auto i = 0ul; i < size; ++i) { d[i] = (TW::byte)(256.0 * rand() / RAND_MAX); } return d; @@ -116,21 +116,21 @@ TEST(FIOEncryption, getSharedSecret) { const PrivateKey privateKey(parse_hex("2bd806c97f0e00af1a1fc3328fa763a9269723c8db8fac4f93af71db186d6e90")); const PublicKey publicKey(parse_hex("024edfcf9dfe6c0b5c83d1ab3f78d1b39a46ebac6798e08e19761f5ed89ec83c10"), TWPublicKeyTypeSECP256k1); Data secret = Encryption::getSharedSecret(privateKey, publicKey); - EXPECT_EQ(secret.size(), 64); + EXPECT_EQ(secret.size(), 64ul); EXPECT_EQ(hex(secret), "a71b4ec5a9577926a1d2aa1d9d99327fd3b68f6a1ea597200a0d890bd3331df300a2d49fec0b2b3e6969ce9263c5d6cf47c191c1ef149373ecc9f0d98116b598"); } { const PrivateKey privateKey(parse_hex("81b637d8fcd2c6da6359e6963113a1170de795e4b725b84d1e0b4cfd9ec58ce9")); const PublicKey publicKey(parse_hex("039997a497d964fc1a62885b05a51166a65a90df00492c8d7cf61d6accf54803be"), TWPublicKeyTypeSECP256k1); Data secret = Encryption::getSharedSecret(privateKey, publicKey); - EXPECT_EQ(secret.size(), 64); + EXPECT_EQ(secret.size(), 64ul); EXPECT_EQ(hex(secret), "a71b4ec5a9577926a1d2aa1d9d99327fd3b68f6a1ea597200a0d890bd3331df300a2d49fec0b2b3e6969ce9263c5d6cf47c191c1ef149373ecc9f0d98116b598"); } { const PrivateKey privateKey(parse_hex("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")); const PublicKey publicKey(parse_hex("03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd"), TWPublicKeyTypeSECP256k1); Data secret = Encryption::getSharedSecret(privateKey, publicKey); - EXPECT_EQ(secret.size(), 64); + EXPECT_EQ(secret.size(), 64ul); EXPECT_EQ(hex(secret), "3f0840df1912e24d85f39008a56550c31403e096fce7fa9d7886fab8e5c2ceb66b4139c8f4f4172fd9f455e76c2e8913a3d734f51a1951090ce9ec660671957d"); } } @@ -170,7 +170,7 @@ TEST(FIOEncryption, encryptEncodeDecodeDecrypt) { EXPECT_EQ(addressBob.string(), "FIO5VE6Dgy9FUmd1mFotXwF88HkQN1KysCWLPqpVnDMjRvGRi1YrM"); const Data message = parse_hex("0b70757273652e616c69636501310a66696f2e7265716f6274000000"); const Data iv = parse_hex("f300888ca4f512cebdc0020ff0f7224c"); - + const Data encrypted = Encryption::encrypt(privateKeyAlice, publicKeyBob, message, iv); EXPECT_EQ(hex(encrypted), "f300888ca4f512cebdc0020ff0f7224c0db2984c4ad9afb12629f01a8c6a76328bbde17405655dc4e3cb30dad272996fb1dea8e662e640be193e25d41147a904c571b664a7381ab41ef062448ac1e205"); const string encoded = Encryption::encode(encrypted); @@ -182,3 +182,5 @@ TEST(FIOEncryption, encryptEncodeDecodeDecrypt) { // verify that decrypted is the same as the original EXPECT_EQ(hex(decrypted), hex(message)); } + +} // namespace TW::FIO::EncryptionTests diff --git a/tests/FIO/SignerTests.cpp b/tests/chains/FIO/SignerTests.cpp similarity index 90% rename from tests/FIO/SignerTests.cpp rename to tests/chains/FIO/SignerTests.cpp index cdf1277a11b..871db8d8091 100644 --- a/tests/FIO/SignerTests.cpp +++ b/tests/chains/FIO/SignerTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -7,19 +7,19 @@ #include "FIO/Actor.h" #include "FIO/Signer.h" -#include "HexCoding.h" -#include "Hash.h" #include "Base58.h" +#include "Hash.h" +#include "HexCoding.h" #include -using namespace TW; -using namespace TW::FIO; -using namespace std; +namespace TW::FIO::tests { +using namespace std; TEST(FIOSigner, SignEncode) { - string sig1 = Signer::signatureToBsase58(parse_hex("1f4fccc30bcba876963aef6de584daf7258306c02f4528fe25b116b517de8b349968bdc080cd6bef36f5a46d31a7c01ed0806ad215bb66a94f61e27a895d610983"));; + string sig1 = Signer::signatureToBase58(parse_hex("1f4fccc30bcba876963aef6de584daf7258306c02f4528fe25b116b517de8b349968bdc080cd6bef36f5a46d31a7c01ed0806ad215bb66a94f61e27a895d610983")); + EXPECT_EQ("SIG_K1_K5hJTPeiV4bDkNR13mf66N2DY5AtVL4NU1iWE4G4dsczY2q68oCcUVxhzFdxjgV2eAeb2jNV1biqtCJ3SaNL8kkNgoZ43H", sig1); } @@ -37,7 +37,7 @@ TEST(FIOSigner, SignInternals) { Data sign2 = Signer::signData(pk, rawData); EXPECT_EQ("1f4ae8d1b993f94d0de4b249d5185481770de0711863ad640b3aac21de598fcc02761c6e5395106bafb7b09aab1c7aa5ac0573dbd821c2d255725391a5105d30d1", hex(sign2)); - string sigStr = Signer::signatureToBsase58(sign2); + string sigStr = Signer::signatureToBase58(sign2); EXPECT_EQ("SIG_K1_K54CA1jmhgWrSdvrNrkokPyvqh7dwsSoQHNU9xgD3Ezf6cJySzhKeUubVRqmpYdnjoP1DM6SorroVAgrCu3qqvJ9coAQ6u", sigStr); EXPECT_TRUE(Signer::verify(pk.getPublicKey(TWPublicKeyTypeSECP256k1), hash, sign2)); } @@ -74,3 +74,5 @@ TEST(FIOSigner, Actor) { EXPECT_EQ(actorArr[i], actor); } } + +} // namespace TW::FIO::tests diff --git a/tests/FIO/TWCoinTypeTests.cpp b/tests/chains/FIO/TWCoinTypeTests.cpp similarity index 97% rename from tests/FIO/TWCoinTypeTests.cpp rename to tests/chains/FIO/TWCoinTypeTests.cpp index fcbb7a47bb6..2dec77e3723 100644 --- a/tests/FIO/TWCoinTypeTests.cpp +++ b/tests/chains/FIO/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/FIO/TWFIOAccountTests.cpp b/tests/chains/FIO/TWFIOAccountTests.cpp similarity index 97% rename from tests/FIO/TWFIOAccountTests.cpp rename to tests/chains/FIO/TWFIOAccountTests.cpp index 3c4764d4801..e9b0f948a09 100644 --- a/tests/FIO/TWFIOAccountTests.cpp +++ b/tests/chains/FIO/TWFIOAccountTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/FIO/TWFIOTests.cpp b/tests/chains/FIO/TWFIOTests.cpp similarity index 91% rename from tests/FIO/TWFIOTests.cpp rename to tests/chains/FIO/TWFIOTests.cpp index 81ea60022a7..9777e10f5c5 100644 --- a/tests/FIO/TWFIOTests.cpp +++ b/tests/chains/FIO/TWFIOTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -10,23 +10,22 @@ #include "proto/FIO.pb.h" #include "FIO/Address.h" #include "Data.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include "HexCoding.h" #include "PrivateKey.h" #include -using namespace TW; -using namespace TW::FIO; -using namespace std; +namespace TW::FIO::TWFIOTests { +using namespace std; TEST(TWFIO, Address) { auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035").get())); ASSERT_NE(nullptr, privateKey.get()); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), false)); ASSERT_NE(nullptr, publicKey.get()); - ASSERT_EQ(65, publicKey.get()->impl.bytes.size()); + ASSERT_EQ(65ul, publicKey.get()->impl.bytes.size()); auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeFIO)); auto addressString = WRAPS(TWAnyAddressDescription(address.get())); assertStringsEqual(addressString, "FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf"); @@ -39,7 +38,7 @@ TEST(TWFIO, Address) { ASSERT_TRUE(TWAnyAddressEqual(address.get(), address2.get())); } -const Data chainId = parse_hex("4e46572250454b796d7296eec9e8896327ea82dd40f2cd74cf1b1d8ba90bcd77"); +const Data gChainId = parse_hex("4e46572250454b796d7296eec9e8896327ea82dd40f2cd74cf1b1d8ba90bcd77"); // 5KEDWtAUJcFX6Vz38WXsAQAv2geNqT7UaZC8gYu9kTuryr3qkri FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf const PrivateKey privKeyBA = PrivateKey(parse_hex("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035")); const PublicKey pubKey6M = privKeyBA.getPublicKey(TWPublicKeyTypeSECP256k1); @@ -48,7 +47,7 @@ const Address addr6M(pubKey6M); TEST(TWFIO, RegisterFioAddress) { Proto::SigningInput input; input.set_expiry(1579784511); - input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); + input.mutable_chain_params()->set_chain_id(string(gChainId.begin(), gChainId.end())); input.mutable_chain_params()->set_head_block_number(39881); input.mutable_chain_params()->set_ref_block_prefix(4279583376); input.set_private_key(string(privKeyBA.bytes.begin(), privKeyBA.bytes.end())); @@ -66,7 +65,7 @@ TEST(TWFIO, RegisterFioAddress) { TEST(TWFIO, AddPubAddress) { Proto::SigningInput input; input.set_expiry(1579729429); - input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); + input.mutable_chain_params()->set_chain_id(string(gChainId.begin(), gChainId.end())); input.mutable_chain_params()->set_head_block_number(11565); input.mutable_chain_params()->set_ref_block_prefix(4281229859); input.set_private_key(string(privKeyBA.bytes.begin(), privKeyBA.bytes.end())); @@ -93,7 +92,7 @@ TEST(TWFIO, AddPubAddress) { TEST(TWFIO, Transfer) { Proto::SigningInput input; input.set_expiry(1579790000); - input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); + input.mutable_chain_params()->set_chain_id(string(gChainId.begin(), gChainId.end())); input.mutable_chain_params()->set_head_block_number(50000); input.mutable_chain_params()->set_ref_block_prefix(4000123456); input.set_private_key(string(privKeyBA.bytes.begin(), privKeyBA.bytes.end())); @@ -111,7 +110,7 @@ TEST(TWFIO, Transfer) { TEST(TWFIO, RenewFioAddress) { Proto::SigningInput input; input.set_expiry(1579785000); - input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); + input.mutable_chain_params()->set_chain_id(string(gChainId.begin(), gChainId.end())); input.mutable_chain_params()->set_head_block_number(39881); input.mutable_chain_params()->set_ref_block_prefix(4279583376); input.set_private_key(string(privKeyBA.bytes.begin(), privKeyBA.bytes.end())); @@ -129,7 +128,7 @@ TEST(TWFIO, RenewFioAddress) { TEST(TWFIO, NewFundsRequest) { Proto::SigningInput input; input.set_expiry(1579785000); - input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); + input.mutable_chain_params()->set_chain_id(string(gChainId.begin(), gChainId.end())); input.mutable_chain_params()->set_head_block_number(39881); input.mutable_chain_params()->set_ref_block_prefix(4279583376); input.set_private_key(string(privKeyBA.bytes.begin(), privKeyBA.bytes.end())); @@ -147,10 +146,11 @@ TEST(TWFIO, NewFundsRequest) { Proto::SigningOutput output; ANY_SIGN(input, TWCoinTypeFIO); - // Packed transacton varies, as there is no way to control encryption IV parameter from this level. + // Packed transaction varies, as there is no way to control encryption IV parameter from this level. // Therefore full equality cannot be checked, tail is cut off. The first N chars are checked, works in this case. EXPECT_EQ( R"({"compression":"none","packed_context_free_data":"","packed_trx":"289b295ec99b904215ff000000000100403ed4aa0ba85b00acba384dbdb89a01102b2f46fca756b200000000a8ed32328802106d6172696f4066696f746573746)", - output.json().substr(0, 195) - ); + output.json().substr(0, 195)); } + +} // namespace TW::FIO::TWFIOTests diff --git a/tests/FIO/TransactionBuilderTests.cpp b/tests/chains/FIO/TransactionBuilderTests.cpp similarity index 83% rename from tests/FIO/TransactionBuilderTests.cpp rename to tests/chains/FIO/TransactionBuilderTests.cpp index 04b36498cf2..ff7b73d9484 100644 --- a/tests/FIO/TransactionBuilderTests.cpp +++ b/tests/chains/FIO/TransactionBuilderTests.cpp @@ -1,32 +1,28 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. #include "FIO/Action.h" +#include "FIO/NewFundsRequest.h" #include "FIO/Transaction.h" #include "FIO/TransactionBuilder.h" -#include "FIO/NewFundsRequest.h" -#include "HexCoding.h" #include "BinaryCoding.h" +#include "HexCoding.h" #include - #include -#include -using namespace TW; -using namespace TW::FIO; +namespace TW::FIO::TransactionBuilderTests { using namespace std; - const Data chainId = parse_hex("4e46572250454b796d7296eec9e8896327ea82dd40f2cd74cf1b1d8ba90bcd77"); // 5KEDWtAUJcFX6Vz38WXsAQAv2geNqT7UaZC8gYu9kTuryr3qkri FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf -const PrivateKey privKeyBA = PrivateKey(parse_hex("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035")); -const PublicKey pubKey6M = privKeyBA.getPublicKey(TWPublicKeyTypeSECP256k1); -const Address addr6M(pubKey6M); +const PrivateKey gPrivKeyBA = PrivateKey(parse_hex("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035")); +const PublicKey gPubKey6MA = gPrivKeyBA.getPublicKey(TWPublicKeyTypeSECP256k1); +const Address gAddr6M(gPubKey6MA); TEST(FIOTransactionBuilder, RegisterFioAddressGeneric) { Proto::SigningInput input; @@ -34,10 +30,10 @@ TEST(FIOTransactionBuilder, RegisterFioAddressGeneric) { input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); input.mutable_chain_params()->set_head_block_number(39881); input.mutable_chain_params()->set_ref_block_prefix(4279583376); - input.set_private_key(string(privKeyBA.bytes.begin(), privKeyBA.bytes.end())); + input.set_private_key(string(gPrivKeyBA.bytes.begin(), gPrivKeyBA.bytes.end())); input.set_tpid("rewards@wallet"); input.mutable_action()->mutable_register_fio_address_message()->set_fio_address("adam@fiotestnet"); - input.mutable_action()->mutable_register_fio_address_message()->set_owner_fio_public_key(addr6M.string()); + input.mutable_action()->mutable_register_fio_address_message()->set_owner_fio_public_key(gAddr6M.string()); input.mutable_action()->mutable_register_fio_address_message()->set_fee(5000000000); auto json = TransactionBuilder::sign(input); @@ -49,8 +45,8 @@ TEST(FIOTransactionBuilder, RegisterFioAddress) { ChainParams chainParams{chainId, 39881, 4279583376}; uint64_t fee = 5000000000; - string t = TransactionBuilder::createRegisterFioAddress(addr6M, privKeyBA, "adam@fiotestnet", - chainParams, fee, "rewards@wallet", 1579784511); + string t = TransactionBuilder::createRegisterFioAddress(gAddr6M, gPrivKeyBA, "adam@fiotestnet", + chainParams, fee, "rewards@wallet", 1579784511); EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"3f99295ec99b904215ff0000000001003056372503a85b0000c6eaa66498ba01102b2f46fca756b200000000a8ed3232650f6164616d4066696f746573746e65743546494f366d31664d645470526b52426e6564765973685843784c4669433573755255384b44667838787874587032686e7478706e6600f2052a01000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_K19ugLriG3ApYgjJCRDsy21p9xgsjbDtqBuZrmAEix9XYzndR1kNbJ6fXCngMJMAhxUHfwHAsPnh58otXiJZkazaM1EkS5"]})", t); } @@ -58,11 +54,8 @@ TEST(FIOTransactionBuilder, RegisterFioAddress) { TEST(FIOTransactionBuilder, AddPubAddress) { ChainParams chainParams{chainId, 11565, 4281229859}; - string t = TransactionBuilder::createAddPubAddress(addr6M, privKeyBA, "adam@fiotestnet", { - {"BTC", "bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v"}, - {"ETH", "0xce5cB6c92Da37bbBa91Bd40D4C9D4D724A3a8F51"}, - {"BNB", "bnb1ts3dg54apwlvr9hupv2n0j6e46q54znnusjk9s"}}, - chainParams, 0, "rewards@wallet", 1579729429); + string t = TransactionBuilder::createAddPubAddress(gAddr6M, gPrivKeyBA, "adam@fiotestnet", {{"BTC", "bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v"}, {"ETH", "0xce5cB6c92Da37bbBa91Bd40D4C9D4D724A3a8F51"}, {"BNB", "bnb1ts3dg54apwlvr9hupv2n0j6e46q54znnusjk9s"}}, + chainParams, 0, "rewards@wallet", 1579729429); EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"15c2285e2d2d23622eff0000000001003056372503a85b0000c6eaa664523201102b2f46fca756b200000000a8ed3232c9010f6164616d4066696f746573746e65740303425443034254432a626331717679343037347267676b647232707a773576706e6e3632656730736d7a6c787770373064377603455448034554482a30786365356342366339324461333762624261393142643430443443394434443732344133613846353103424e4203424e422a626e6231747333646735346170776c76723968757076326e306a366534367135347a6e6e75736a6b39730000000000000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_K3zimaMKU8cBhVRPw46KM2u7uQWaAKXrnoeYZ7MEbp6sVJcDQmQR2RtdavpUPwkAnYUkd8NqLun8H48tcxZBcTUgkiPGVJ"]})", t); } @@ -73,8 +66,8 @@ TEST(FIOTransactionBuilder, Transfer) { uint64_t amount = 1000000000; uint64_t fee = 250000000; - string t = TransactionBuilder::createTransfer(addr6M, privKeyBA, payee, amount, - chainParams, fee, "rewards@wallet", 1579790000); + string t = TransactionBuilder::createTransfer(gAddr6M, gPrivKeyBA, payee, amount, + chainParams, fee, "rewards@wallet", 1579790000); EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"b0ae295e50c3400a6dee00000000010000980ad20ca85be0e1d195ba85e7cd01102b2f46fca756b200000000a8ed32325d3546494f37754d5a6f6565693548745841443234433479436b70575762663234626a597472524e6a57646d474358485a63637775694500ca9a3b0000000080b2e60e00000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_K9VRCnvaTYN7vgcoVKVXgyJTdKUGV8hLXgFLoEbvqAcFxy7DXQ1rSnAfEuabi4ATkgmvnpaSBdVFN7TBtM1wrbZYqeJQw9"]})", t); } @@ -83,8 +76,8 @@ TEST(FIOTransactionBuilder, RenewFioAddress) { ChainParams chainParams{chainId, 39881, 4279583376}; uint64_t fee = 3000000000; - string t = TransactionBuilder::createRenewFioAddress(addr6M, privKeyBA, "nick@fiotestnet", - chainParams, fee, "rewards@wallet", 1579785000); + string t = TransactionBuilder::createRenewFioAddress(gAddr6M, gPrivKeyBA, "nick@fiotestnet", + chainParams, fee, "rewards@wallet", 1579785000); EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"289b295ec99b904215ff0000000001003056372503a85b80b1ba2919aea6ba01102b2f46fca756b200000000a8ed32322f0f6e69636b4066696f746573746e6574005ed0b200000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_Jxz7oCJ7Z4ECsxqb2utqBcyP3zPQCeQCBws9wWQjyptUKoWVk2AyCVEqtdMHJwqtLniio5Z7npMnaZB8E4pa2G75P9uGkb"]})", t); } @@ -94,7 +87,7 @@ TEST(FIOTransactionBuilder, NewFundsRequest) { ChainParams chainParams{chainId, 18484, 3712870657}; const Data iv = parse_hex("000102030405060708090a0b0c0d0e0f"); // use fixed iv for testability string t = TransactionBuilder::createNewFundsRequest( - Address("FIO5NMm9Vf3NjYFnhoc7yxTCrLW963KPUCzeMGv3SJ6zR3GMez4ub"), privKeyBA, + Address("FIO5NMm9Vf3NjYFnhoc7yxTCrLW963KPUCzeMGv3SJ6zR3GMez4ub"), gPrivKeyBA, "tag@fiotestnet", "FIO7iYHtDhs45smFgSqLyJ6Zi4D3YG8K5bZGyxmshLCDXXBPbbmJN", "dapixbp@fiotestnet", "14R4wEsGT58chmqo7nB65Dy4je6TiijDWc", "1", "BTC", "payment", "", "", chainParams, 800000000, "", 1583528215, iv); @@ -103,19 +96,19 @@ TEST(FIOTransactionBuilder, NewFundsRequest) { ChainParams chainParams{chainId, 39881, 4279583376}; uint64_t fee = 3000000000; - + const Data iv = parse_hex("000102030405060708090a0b0c0d0e0f"); // use fixed iv for testability - string t = TransactionBuilder::createNewFundsRequest(addr6M, privKeyBA, - "mario@fiotestnet", "FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o", "alice@fiotestnet", "bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v", - "5", "BTC", "Memo", "Hash", "https://trustwallet.com", - chainParams, fee, "rewards@wallet", 1579785000, iv); + string t = TransactionBuilder::createNewFundsRequest(gAddr6M, gPrivKeyBA, + "mario@fiotestnet", "FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o", "alice@fiotestnet", "bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v", + "5", "BTC", "Memo", "Hash", "https://trustwallet.com", + chainParams, fee, "rewards@wallet", 1579785000, iv); EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"289b295ec99b904215ff000000000100403ed4aa0ba85b00acba384dbdb89a01102b2f46fca756b200000000a8ed32328802106d6172696f4066696f746573746e657410616c6963654066696f746573746e6574c00141414543417751464267634943516f4c4441304f442f3575342f6b436b7042554c4a44682f546951334d31534f4e4938426668496c4e54766d39354249586d54396f616f7a55632f6c6c3942726e57426563464e767a76766f6d3751577a517250717241645035683433305732716b52355266416555446a704f514732364c347a6936767241553052764855474e382b685779736a6971506b2b7a455a444952534678426268796c69686d59334f4752342f5a46466358484967343241327834005ed0b2000000000c716466656a7a32613577706c0e726577617264734077616c6c657400","signatures":["SIG_K1_Kk79iVcQMpqpVgZwGTmC1rxgCTLy5BDFtHd8FvjRNm2FqNHR9dpeUmPTNqBKGMNG3BsPy4c5u26TuEDpS87SnyMpF43cZk"]})", t); } TEST(FIOTransaction, ActionRegisterFioAddressInternal) { - RegisterFioAddressData radata("adam@fiotestnet", addr6M.string(), - 5000000000, "rewards@wallet", "qdfejz2a5wpl"); + RegisterFioAddressData radata("adam@fiotestnet", gAddr6M.string(), + 5000000000, "rewards@wallet", "qdfejz2a5wpl"); Data ser1; radata.serialize(ser1); EXPECT_EQ( @@ -153,11 +146,8 @@ TEST(FIOTransaction, ActionRegisterFioAddressInternal) { } TEST(FIOTransaction, ActionAddPubAddressInternal) { - AddPubAddressData aadata("adam@fiotestnet", { - {"BTC", "BTC", "bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v"}, - {"ETH", "ETH", "0xce5cB6c92Da37bbBa91Bd40D4C9D4D724A3a8F51"}, - {"BNB", "BNB", "bnb1ts3dg54apwlvr9hupv2n0j6e46q54znnusjk9s"}}, - 0, "rewards@wallet", "qdfejz2a5wpl"); + AddPubAddressData aadata("adam@fiotestnet", {{"BTC", "BTC", "bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v"}, {"ETH", "ETH", "0xce5cB6c92Da37bbBa91Bd40D4C9D4D724A3a8F51"}, {"BNB", "BNB", "bnb1ts3dg54apwlvr9hupv2n0j6e46q54znnusjk9s"}}, + 0, "rewards@wallet", "qdfejz2a5wpl"); Data ser1; aadata.serialize(ser1); EXPECT_EQ( @@ -196,21 +186,21 @@ TEST(FIOTransaction, ActionAddPubAddressInternal) { TEST(FIONewFundsContent, serialize) { { - NewFundsContent newFunds {"bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v", "10", "BTC", "BTC", "Memo", "Hash", "https://trustwallet.com"}; + NewFundsContent newFunds{"bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v", "10", "BTC", "BTC", "Memo", "Hash", "https://trustwallet.com"}; Data ser; newFunds.serialize(ser); EXPECT_EQ(hex(ser), "2a626331717679343037347267676b647232707a773576706e6e3632656730736d7a6c78777037306437760231300342544303425443044d656d6f04486173681768747470733a2f2f747275737477616c6c65742e636f6d0000000000"); } { // empty struct - NewFundsContent newFunds {"", "", "", "", "", "", ""}; + NewFundsContent newFunds{"", "", "", "", "", "", ""}; Data ser; newFunds.serialize(ser); EXPECT_EQ(hex(ser), "000000000000000000000000"); } { // test from https://github.com/fioprotocol/fiojs/blob/master/src/tests/encryption-fio.test.ts - NewFundsContent newFunds {"purse.alice", "1", "", "fio.reqobt", "", "", ""}; + NewFundsContent newFunds{"purse.alice", "1", "", "fio.reqobt", "", "", ""}; Data ser; newFunds.serialize(ser); EXPECT_EQ(hex(ser), "0b70757273652e616c6963650131000a66696f2e7265716f62740000000000000000"); @@ -241,18 +231,17 @@ TEST(FIONewFundsContent, deserialize) { TEST(FIOTransactionBuilder, expirySetDefault) { uint32_t expiry = 1579790000; EXPECT_EQ(TransactionBuilder::expirySetDefaultIfNeeded(expiry), false); - EXPECT_EQ(expiry, 1579790000); + EXPECT_EQ(expiry, 1579790000ul); expiry = 0; - EXPECT_EQ(expiry, 0); + EXPECT_EQ(expiry, 0ul); EXPECT_EQ(TransactionBuilder::expirySetDefaultIfNeeded(expiry), true); EXPECT_TRUE(expiry > 1579790000); } // May throw nlohmann::json::type_error void createTxWithChainParam(const ChainParams& paramIn, ChainParams& paramOut) { - string tx = TransactionBuilder::createAddPubAddress(addr6M, privKeyBA, "adam@fiotestnet", { - {"BTC", "bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v"}}, - paramIn, 0, "rewards@wallet", 1579729429); + string tx = TransactionBuilder::createAddPubAddress(gAddr6M, gPrivKeyBA, "adam@fiotestnet", {{"BTC", "bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v"}}, + paramIn, 0, "rewards@wallet", 1579729429); // retrieve chain params from encoded tx; parse out packed tx try { nlohmann::json txJson = nlohmann::json::parse(tx); @@ -350,9 +339,11 @@ TEST(FIOTransactionBuilder, encodeString) { { Data data; const string text = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"; - EXPECT_EQ(text.length(), 130); + EXPECT_EQ(text.length(), 130ul); TW::FIO::encodeString(text, data); // length on 2 bytes EXPECT_EQ(hex(data), "8201" + hex(text)); } } + +} // namespace TW::FIO::TransactionBuilderTests diff --git a/tests/Fantom/TWCoinTypeTests.cpp b/tests/chains/Fantom/TWCoinTypeTests.cpp similarity index 97% rename from tests/Fantom/TWCoinTypeTests.cpp rename to tests/chains/Fantom/TWCoinTypeTests.cpp index acd79755342..15dcde31182 100644 --- a/tests/Fantom/TWCoinTypeTests.cpp +++ b/tests/chains/Fantom/TWCoinTypeTests.cpp @@ -5,7 +5,7 @@ // file LICENSE at the root of the source code distribution tree. // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Filecoin/AddressTests.cpp b/tests/chains/Filecoin/AddressTests.cpp similarity index 99% rename from tests/Filecoin/AddressTests.cpp rename to tests/chains/Filecoin/AddressTests.cpp index db657eda357..be2bd2355b9 100644 --- a/tests/Filecoin/AddressTests.cpp +++ b/tests/chains/Filecoin/AddressTests.cpp @@ -11,8 +11,8 @@ #include using namespace TW; -using namespace TW::Filecoin; +namespace TW::Filecoin::tests { // clang-format off struct address_test { @@ -97,3 +97,5 @@ TEST(FilecoinAddress, FromString) { ASSERT_EQ(hex(a.bytes), test.hex) << "Address(" << test.encoded << ")"; } } + +} diff --git a/tests/Filecoin/SignerTests.cpp b/tests/chains/Filecoin/SignerTests.cpp similarity index 100% rename from tests/Filecoin/SignerTests.cpp rename to tests/chains/Filecoin/SignerTests.cpp diff --git a/tests/Filecoin/TWAnySignerTests.cpp b/tests/chains/Filecoin/TWAnySignerTests.cpp similarity index 97% rename from tests/Filecoin/TWAnySignerTests.cpp rename to tests/chains/Filecoin/TWAnySignerTests.cpp index ee9c85f7f95..a4af47a4332 100644 --- a/tests/Filecoin/TWAnySignerTests.cpp +++ b/tests/chains/Filecoin/TWAnySignerTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include "Data.h" @@ -15,7 +15,8 @@ #include using namespace TW; -using namespace Filecoin; + +namespace TW::Filecoin::tests { TEST(TWAnySignerFilecoin, Sign) { Proto::SigningInput input; @@ -67,3 +68,5 @@ TEST(TWAnySignerFilecoin, SignJSON) { ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeFilecoin)); assertStringsEqual(result, R"({"Message":{"From":"f1z4a36sc7mfbv4z3qwutblp2flycdui3baffytbq","GasFeeCap":"700000000000000000000","GasLimit":1000,"GasPremium":"800000000000000000000","Nonce":2,"To":"f3um6uo3qt5of54xjbx3hsxbw5mbsc6auxzrvfxekn5bv3duewqyn2tg5rhrlx73qahzzpkhuj7a34iq7oifsq","Value":"600000000000000000000"},"Signature":{"Data":"jMRu+OZ/lfppgmqSfGsntFrRLWZnUg3ZYmJTTRLsVt4V1310vR3VKGJpaE6S4sNvDOE6sEgmN9YmfTkPVK2qMgE=","Type":1}})"); } + +} // namespace TW::Filecoin::tests diff --git a/tests/Filecoin/TWCoinTypeTests.cpp b/tests/chains/Filecoin/TWCoinTypeTests.cpp similarity index 97% rename from tests/Filecoin/TWCoinTypeTests.cpp rename to tests/chains/Filecoin/TWCoinTypeTests.cpp index 9e5ff95f72e..16d5499b5b1 100644 --- a/tests/Filecoin/TWCoinTypeTests.cpp +++ b/tests/chains/Filecoin/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Filecoin/TransactionTests.cpp b/tests/chains/Filecoin/TransactionTests.cpp similarity index 100% rename from tests/Filecoin/TransactionTests.cpp rename to tests/chains/Filecoin/TransactionTests.cpp diff --git a/tests/Zcoin/TWCoinTypeTests.cpp b/tests/chains/Firo/TWCoinTypeTests.cpp similarity index 69% rename from tests/Zcoin/TWCoinTypeTests.cpp rename to tests/chains/Firo/TWCoinTypeTests.cpp index c11de5e831f..2f22a9c23ac 100644 --- a/tests/Zcoin/TWCoinTypeTests.cpp +++ b/tests/chains/Firo/TWCoinTypeTests.cpp @@ -8,27 +8,27 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include -TEST(TWZcoinCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeZcoin)); +TEST(TWFiroCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeFiro)); auto txId = WRAPS(TWStringCreateWithUTF8Bytes("09a60d58b3d17519a42a8eca60750c33b710ca8f3ca71994192e05c248a2a111")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeZcoin, txId.get())); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeFiro, txId.get())); auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a8ULhhDgfdSiXJhSZVdhb8EuDc6R3ogsaM")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeZcoin, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeZcoin)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeZcoin)); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeFiro, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeFiro)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeFiro)); - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeZcoin), 8); - ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeZcoin)); - ASSERT_EQ(0x7, TWCoinTypeP2shPrefix(TWCoinTypeZcoin)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeZcoin)); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeFiro), 8); + ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeFiro)); + ASSERT_EQ(0x7, TWCoinTypeP2shPrefix(TWCoinTypeFiro)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeFiro)); assertStringsEqual(symbol, "FIRO"); assertStringsEqual(txUrl, "https://explorer.firo.org/tx/09a60d58b3d17519a42a8eca60750c33b710ca8f3ca71994192e05c248a2a111"); assertStringsEqual(accUrl, "https://explorer.firo.org/address/a8ULhhDgfdSiXJhSZVdhb8EuDc6R3ogsaM"); - assertStringsEqual(id, "zcoin"); + assertStringsEqual(id, "firo"); assertStringsEqual(name, "Firo"); } diff --git a/tests/Zcoin/TWZCoinAddressTests.cpp b/tests/chains/Firo/TWZCoinAddressTests.cpp similarity index 84% rename from tests/Zcoin/TWZCoinAddressTests.cpp rename to tests/chains/Firo/TWZCoinAddressTests.cpp index 1957520ac6e..33ccf2516e8 100644 --- a/tests/Zcoin/TWZCoinAddressTests.cpp +++ b/tests/chains/Firo/TWZCoinAddressTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -19,7 +19,7 @@ TEST(TWZCoin, Address) { auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730").get())); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); - auto address = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeZcoin))); + auto address = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeFiro))); auto addressString = WRAPS(TWBitcoinAddressDescription(address.get())); assertStringsEqual(addressString, "aAbqxogrjdy2YHVcnQxFHMzqpt2fhjCTVT"); } @@ -30,8 +30,8 @@ TEST(TWZCoin, ExtendedKeys) { STRING("TREZOR").get() )); - auto xpub = WRAPS(TWHDWalletGetExtendedPublicKey(wallet.get(), TWPurposeBIP44, TWCoinTypeZcoin, TWHDVersionXPUB)); - auto xprv = WRAPS(TWHDWalletGetExtendedPrivateKey(wallet.get(), TWPurposeBIP44, TWCoinTypeZcoin, TWHDVersionXPRV)); + auto xpub = WRAPS(TWHDWalletGetExtendedPublicKey(wallet.get(), TWPurposeBIP44, TWCoinTypeFiro, TWHDVersionXPUB)); + auto xprv = WRAPS(TWHDWalletGetExtendedPrivateKey(wallet.get(), TWPurposeBIP44, TWCoinTypeFiro, TWHDVersionXPRV)); assertStringsEqual(xpub, "xpub6Cb8Q6pDeS8PdKNbDv9Hvq4WpJXL3JvKvmHHwR1wD2H543hiCUE1f1tB5AXE6yg13k7xZ6PzEXMNUFHXk6kkx4RYte8VB1i4tCX9rwQVR4a"); assertStringsEqual(xprv, "xprv9ybmzbHKp4a6QqJ87tcHZh7nGGgqdrCUZYMh92cKegk6BFNZevum7DZhDuVDqqMdcBT9B4wJSEmwJW9JNdkMcUUjEWKqppxNrJjKFSyKsCr"); @@ -39,13 +39,13 @@ TEST(TWZCoin, ExtendedKeys) { TEST(TWZcoin, DeriveFromXpub) { auto xpub = STRING("xpub6Cb8Q6pDeS8PdKNbDv9Hvq4WpJXL3JvKvmHHwR1wD2H543hiCUE1f1tB5AXE6yg13k7xZ6PzEXMNUFHXk6kkx4RYte8VB1i4tCX9rwQVR4a"); - auto pubKey3 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeZcoin, STRING("m/44'/136'/0'/0/3").get())); - auto pubKey5 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeZcoin, STRING("m/44'/136'/0'/0/5").get())); + auto pubKey3 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeFiro, STRING("m/44'/136'/0'/0/3").get())); + auto pubKey5 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeFiro, STRING("m/44'/136'/0'/0/5").get())); - auto address3 = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(pubKey3.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeZcoin))); + auto address3 = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(pubKey3.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeFiro))); auto address3String = WRAPS(TWBitcoinAddressDescription(address3.get())); - auto address5 = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(pubKey5.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeZcoin))); + auto address5 = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(pubKey5.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeFiro))); auto address5String = WRAPS(TWBitcoinAddressDescription(address5.get())); assertStringsEqual(address3String, "aLnztJEbyACnxF9H7SFC8YjUxedwyQsgVm"); @@ -53,11 +53,11 @@ TEST(TWZcoin, DeriveFromXpub) { } TEST(TWZcoin, LockScripts) { - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("a4YtT82mWWxHZhLmdx7e5aroW92dqJoRs3").get(), TWCoinTypeZcoin)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("a4YtT82mWWxHZhLmdx7e5aroW92dqJoRs3").get(), TWCoinTypeFiro)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "76a9142a10f88e30768d2712665c279922b9621ce58bc788ac"); - auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("4CFa4fnAQvFz4VpikGNzQ9XfCDXMmdk6sh").get(), TWCoinTypeZcoin)); + auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("4CFa4fnAQvFz4VpikGNzQ9XfCDXMmdk6sh").get(), TWCoinTypeFiro)); auto scriptData3 = WRAPD(TWBitcoinScriptData(script3.get())); assertHexEqual(scriptData3, "a914f010b17a9189e0f2737d71ae9790359eb5bbc13787"); } diff --git a/tests/GoChain/TWCoinTypeTests.cpp b/tests/chains/GoChain/TWCoinTypeTests.cpp similarity index 97% rename from tests/GoChain/TWCoinTypeTests.cpp rename to tests/chains/GoChain/TWCoinTypeTests.cpp index 7e8b1b75a94..70e8170b723 100644 --- a/tests/GoChain/TWCoinTypeTests.cpp +++ b/tests/chains/GoChain/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Groestlcoin/AddressTests.cpp b/tests/chains/Groestlcoin/AddressTests.cpp similarity index 93% rename from tests/Groestlcoin/AddressTests.cpp rename to tests/chains/Groestlcoin/AddressTests.cpp index d3910b6c382..a07719aaa0c 100644 --- a/tests/Groestlcoin/AddressTests.cpp +++ b/tests/chains/Groestlcoin/AddressTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -12,8 +12,7 @@ #include -using namespace TW; -using namespace TW::Groestlcoin; +namespace TW::Groestlcoin::tests { TEST(GroestlcoinAddress, FromPublicKey) { const auto publicKey = PublicKey(parse_hex("03b85cc59b67c35851eb5060cfc3a759a482254553c5857075c9e247d74d412c91"), TWPublicKeyTypeSECP256k1); @@ -43,3 +42,5 @@ TEST(GroestlcoinAddress, Derive) { const auto address = TW::deriveAddress(TWCoinTypeGroestlcoin, wallet.getKey(TWCoinTypeGroestlcoin, path)); ASSERT_EQ(address, "grs1qw4teyraux2s77nhjdwh9ar8rl9dt7zww8r6lne"); } + +} // namespace TW::Groestlcoin::tests diff --git a/tests/Groestlcoin/TWCoinTypeTests.cpp b/tests/chains/Groestlcoin/TWCoinTypeTests.cpp similarity index 92% rename from tests/Groestlcoin/TWCoinTypeTests.cpp rename to tests/chains/Groestlcoin/TWCoinTypeTests.cpp index 8b621892521..2cb15c927b2 100644 --- a/tests/Groestlcoin/TWCoinTypeTests.cpp +++ b/tests/chains/Groestlcoin/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -23,7 +23,7 @@ TEST(TWGroestlcoinCoinType, TWCoinType) { auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeGroestlcoin)); ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeGroestlcoin), 8); - ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeGroestlcoin)); + ASSERT_EQ(TWBlockchainGroestlcoin, TWCoinTypeBlockchain(TWCoinTypeGroestlcoin)); ASSERT_EQ(0x5, TWCoinTypeP2shPrefix(TWCoinTypeGroestlcoin)); ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeGroestlcoin)); assertStringsEqual(symbol, "GRS"); diff --git a/tests/Groestlcoin/TWGroestlcoinSigningTests.cpp b/tests/chains/Groestlcoin/TWGroestlcoinSigningTests.cpp similarity index 98% rename from tests/Groestlcoin/TWGroestlcoinSigningTests.cpp rename to tests/chains/Groestlcoin/TWGroestlcoinSigningTests.cpp index 7e81ec429b1..994c8977942 100644 --- a/tests/Groestlcoin/TWGroestlcoinSigningTests.cpp +++ b/tests/chains/Groestlcoin/TWGroestlcoinSigningTests.cpp @@ -4,16 +4,13 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "Bitcoin/OutPoint.h" #include "Bitcoin/Script.h" #include "Hash.h" #include "HexCoding.h" #include "PrivateKey.h" #include "proto/Bitcoin.pb.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include "../Bitcoin/TxComparisonHelper.h" - -#include #include #include #include @@ -21,8 +18,7 @@ #include -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { TEST(GroestlcoinSigning, SignP2WPKH) { Proto::SigningInput input; @@ -187,3 +183,5 @@ TEST(GroestlcoinSigning, PlanP2WPKH) { EXPECT_TRUE(verifyPlan(plan, {4774}, 2500, 145)); EXPECT_EQ(plan.branch_id(), ""); } + +} // namespace TW::Bitcoin diff --git a/tests/Groestlcoin/TWGroestlcoinTests.cpp b/tests/chains/Groestlcoin/TWGroestlcoinTests.cpp similarity index 99% rename from tests/Groestlcoin/TWGroestlcoinTests.cpp rename to tests/chains/Groestlcoin/TWGroestlcoinTests.cpp index a00b684b019..e685bc59646 100644 --- a/tests/Groestlcoin/TWGroestlcoinTests.cpp +++ b/tests/chains/Groestlcoin/TWGroestlcoinTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Harmony/AddressTests.cpp b/tests/chains/Harmony/AddressTests.cpp similarity index 96% rename from tests/Harmony/AddressTests.cpp rename to tests/chains/Harmony/AddressTests.cpp index e5ed260abda..eac8744c266 100644 --- a/tests/Harmony/AddressTests.cpp +++ b/tests/chains/Harmony/AddressTests.cpp @@ -11,7 +11,8 @@ #include using namespace TW; -using namespace TW::Harmony; + +namespace TW::Harmony::tests { TEST(HarmonyAddress, FromString) { Address sender; @@ -42,3 +43,5 @@ TEST(HarmonyAddress, FromPrivateKey) { const auto address = Address(publicKey); ASSERT_EQ(address.string(), "one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe"); } + +} // namespace TW::Harmony::tests diff --git a/tests/Harmony/SignerTests.cpp b/tests/chains/Harmony/SignerTests.cpp similarity index 100% rename from tests/Harmony/SignerTests.cpp rename to tests/chains/Harmony/SignerTests.cpp diff --git a/tests/Harmony/StakingTests.cpp b/tests/chains/Harmony/StakingTests.cpp similarity index 100% rename from tests/Harmony/StakingTests.cpp rename to tests/chains/Harmony/StakingTests.cpp diff --git a/tests/Harmony/TWAnyAddressTests.cpp b/tests/chains/Harmony/TWAnyAddressTests.cpp similarity index 95% rename from tests/Harmony/TWAnyAddressTests.cpp rename to tests/chains/Harmony/TWAnyAddressTests.cpp index 759eef52c84..a289284b229 100644 --- a/tests/Harmony/TWAnyAddressTests.cpp +++ b/tests/chains/Harmony/TWAnyAddressTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Harmony/TWAnySignerTests.cpp b/tests/chains/Harmony/TWAnySignerTests.cpp similarity index 97% rename from tests/Harmony/TWAnySignerTests.cpp rename to tests/chains/Harmony/TWAnySignerTests.cpp index 08415e5356a..3edae0c8247 100644 --- a/tests/Harmony/TWAnySignerTests.cpp +++ b/tests/chains/Harmony/TWAnySignerTests.cpp @@ -6,15 +6,16 @@ // file LICENSE at the root of the source code distribution tree. #include "HexCoding.h" -#include "../interface/TWTestUtilities.h" #include "proto/Harmony.pb.h" #include "uint256.h" +#include "TestUtilities.h" #include #include using namespace TW; -using namespace Harmony; + +namespace TW::Harmony::tests { static auto TEST_RECEIVER = "one129r9pj3sk0re76f7zs3qz92rggmdgjhtwge62k"; @@ -72,3 +73,5 @@ TEST(TWAnySignerHarmony, SignJSON) { ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeHarmony)); assertStringsEqual(result, "f86a0180825208018094514650ca30b3c79f693e14220115434236d44aeb8906bfc8da5ee82200008028a084cc200aab11f5e1b2f7ece0d56ec67385ac50cefb6e3dc2a2f3e036ed575a5ca0643f18005b790cac8d8e7dc90e6147df0b83874b52db198864694ea28a79e6fc"); } + +} // namespace TW::Harmony::tests diff --git a/tests/Harmony/TWCoinTypeTests.cpp b/tests/chains/Harmony/TWCoinTypeTests.cpp similarity index 97% rename from tests/Harmony/TWCoinTypeTests.cpp rename to tests/chains/Harmony/TWCoinTypeTests.cpp index 5541e8ca94f..1147cfeb362 100644 --- a/tests/Harmony/TWCoinTypeTests.cpp +++ b/tests/chains/Harmony/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Harmony/TWHarmonyStakingTests.cpp b/tests/chains/Harmony/TWHarmonyStakingTests.cpp similarity index 99% rename from tests/Harmony/TWHarmonyStakingTests.cpp rename to tests/chains/Harmony/TWHarmonyStakingTests.cpp index 81f6ae50108..164d37edacb 100644 --- a/tests/Harmony/TWHarmonyStakingTests.cpp +++ b/tests/chains/Harmony/TWHarmonyStakingTests.cpp @@ -9,7 +9,7 @@ #include "Harmony/Staking.h" #include "HexCoding.h" #include "PrivateKey.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include "proto/Harmony.pb.h" #include "uint256.h" #include @@ -17,7 +17,7 @@ #include using namespace TW; -using namespace Harmony; +namespace TW::Harmony::tests { static auto TEST_ACCOUNT = "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9"; @@ -91,7 +91,6 @@ TEST(TWHarmonyStakingSigner, CreateValidator) { value = store(uint256_t("0x64")); stakingMessage->set_gas_limit(value.data(), value.size()); - Proto::SigningOutput output; ANY_SIGN(input, TWCoinTypeHarmony); @@ -104,7 +103,6 @@ TEST(TWHarmonyStakingSigner, CreateValidator) { ASSERT_EQ(hex(output.s()), shouldBeS); } - TEST(TWHarmonyStakingSigner, EditValidator) { auto input = Proto::SigningInput(); input.set_private_key(PRIVATE_KEY.bytes.data(), PRIVATE_KEY.bytes.size()); @@ -149,7 +147,7 @@ TEST(TWHarmonyStakingSigner, EditValidator) { "5c864771cafef7b96be541cb3c521a98f01838dd94"); editValidatorMsg->set_slot_key_to_add_sig(value.data(), value.size()); - //test active + // test active value = store(uint256_t("1")); editValidatorMsg->set_active(value.data(), value.size()); @@ -219,7 +217,7 @@ TEST(TWHarmonyStakingSigner, EditValidator2) { "5c864771cafef7b96be541cb3c521a98f01838dd94"); editValidatorMsg->set_slot_key_to_add_sig(value.data(), value.size()); - //test null + // test null value = store(uint256_t("0")); editValidatorMsg->set_active(value.data(), value.size()); @@ -289,7 +287,7 @@ TEST(TWHarmonyStakingSigner, EditValidator3) { "5c864771cafef7b96be541cb3c521a98f01838dd94"); editValidatorMsg->set_slot_key_to_add_sig(value.data(), value.size()); - //test inactive + // test inactive value = store(uint256_t("2")); editValidatorMsg->set_active(value.data(), value.size()); @@ -314,7 +312,6 @@ TEST(TWHarmonyStakingSigner, EditValidator3) { ASSERT_EQ(hex(output.s()), shouldBeS); } - TEST(TWHarmonyStakingSigner, Delegate) { auto input = Proto::SigningInput(); input.set_private_key(PRIVATE_KEY.bytes.data(), PRIVATE_KEY.bytes.size()); @@ -351,8 +348,6 @@ TEST(TWHarmonyStakingSigner, Delegate) { ASSERT_EQ(hex(output.s()), shouldBeS); } - - TEST(TWHarmonyStakingSigner, Undelegate) { auto input = Proto::SigningInput(); input.set_private_key(PRIVATE_KEY.bytes.data(), PRIVATE_KEY.bytes.size()); @@ -420,3 +415,5 @@ TEST(TWHarmonyStakingSigner, CollectRewards) { ASSERT_EQ(hex(output.r()), shouldBeR); ASSERT_EQ(hex(output.s()), shouldBeS); } + +} // namespace TW::Harmony::tests diff --git a/tests/Icon/AddressTests.cpp b/tests/chains/ICON/AddressTests.cpp similarity index 96% rename from tests/Icon/AddressTests.cpp rename to tests/chains/ICON/AddressTests.cpp index f5fbc583a2c..a9ed8262e39 100644 --- a/tests/Icon/AddressTests.cpp +++ b/tests/chains/ICON/AddressTests.cpp @@ -4,14 +4,15 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "Icon/Address.h" #include "HexCoding.h" +#include "Icon/Address.h" #include "PrivateKey.h" #include using namespace TW; -using namespace TW::Icon; + +namespace TW::Icon::tests { TEST(IconAddress, Validation) { ASSERT_TRUE(Address::isValid("cx116f042497e5f34268b1b91e742680f84cf4e9f3")); @@ -37,3 +38,5 @@ TEST(IconAddress, FromPrivateKey) { ASSERT_EQ(address.string(), "hx98c0832ca5bd8e8bf355ca9491888aa9725c2c48"); } + +} // namespace TW::Icon::tests diff --git a/tests/Icon/TWAnySignerTests.cpp b/tests/chains/ICON/TWAnySignerTests.cpp similarity index 95% rename from tests/Icon/TWAnySignerTests.cpp rename to tests/chains/ICON/TWAnySignerTests.cpp index a1b6e5ce289..74a57537568 100644 --- a/tests/Icon/TWAnySignerTests.cpp +++ b/tests/chains/ICON/TWAnySignerTests.cpp @@ -5,17 +5,18 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" -#include #include "Data.h" #include "HexCoding.h" -#include "uint256.h" #include "proto/Icon.pb.h" +#include "uint256.h" +#include "TestUtilities.h" +#include #include using namespace TW; -using namespace TW::Icon; + +namespace TW::Icon::tests { TEST(TWAnySignerIcon, Sign) { auto key = parse_hex("2d42994b2f7735bbc93a3e64381864d06747e574aa94655c516f9ad0a74eed79"); @@ -46,3 +47,5 @@ TEST(TWAnySignerIcon, Sign) { auto expected = std::string("{\"from\":\"hxbe258ceb872e08851f1f59694dac2558708ece11\",\"nid\":\"0x1\",\"nonce\":\"0x1\",\"signature\":\"xR6wKs+IA+7E91bT8966jFKlK5mayutXCvayuSMCrx9KB7670CsWa0B7LQzgsxU0GLXaovlAT2MLs1XuDiSaZQE=\",\"stepLimit\":\"0x12345\",\"timestamp\":\"0x563a6cf330136\",\"to\":\"hx5bfdb090f43a808005ffc27c25b213145e80b7cd\",\"value\":\"0xde0b6b3a7640000\",\"version\":\"0x3\"}"); ASSERT_EQ(output.encoded(), expected); } + +} // namespace TW::Icon::tests diff --git a/tests/ICON/TWCoinTypeTests.cpp b/tests/chains/ICON/TWCoinTypeTests.cpp similarity index 97% rename from tests/ICON/TWCoinTypeTests.cpp rename to tests/chains/ICON/TWCoinTypeTests.cpp index bffbd0459f0..5fc84a70033 100644 --- a/tests/ICON/TWCoinTypeTests.cpp +++ b/tests/chains/ICON/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/IoTeX/AddressTests.cpp b/tests/chains/IoTeX/AddressTests.cpp similarity index 89% rename from tests/IoTeX/AddressTests.cpp rename to tests/chains/IoTeX/AddressTests.cpp index 60616bf1002..36fd80b38d9 100644 --- a/tests/IoTeX/AddressTests.cpp +++ b/tests/chains/IoTeX/AddressTests.cpp @@ -45,8 +45,8 @@ TEST(IoTeXAddress, FromPrivateKey) { EXPECT_THROW({ try { - const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const auto address = Address(publicKey); + const auto _publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); + const auto _address = Address(_publicKey); } catch( const std::invalid_argument& e ) { @@ -64,8 +64,8 @@ TEST(IoTeXAddress, FromKeyHash) { EXPECT_THROW({ try { - const auto keyHash = parse_hex("3f9c20bcec9de520d88d98cbe07ee7b5ded0da"); - const auto address = Address(keyHash); + const auto _keyHash = parse_hex("3f9c20bcec9de520d88d98cbe07ee7b5ded0da"); + const auto _address = Address(_keyHash); } catch( const std::invalid_argument& e ) { diff --git a/tests/IoTeX/SignerTests.cpp b/tests/chains/IoTeX/SignerTests.cpp similarity index 100% rename from tests/IoTeX/SignerTests.cpp rename to tests/chains/IoTeX/SignerTests.cpp diff --git a/tests/IoTeX/StakingTests.cpp b/tests/chains/IoTeX/StakingTests.cpp similarity index 98% rename from tests/IoTeX/StakingTests.cpp rename to tests/chains/IoTeX/StakingTests.cpp index fd0e074d312..e5f37330dee 100644 --- a/tests/IoTeX/StakingTests.cpp +++ b/tests/chains/IoTeX/StakingTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -6,16 +6,13 @@ #include "Data.h" #include "HexCoding.h" -#include "IoTeX/Signer.h" #include "IoTeX/Staking.h" -#include "PrivateKey.h" #include "proto/IoTeX.pb.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include -using namespace TW; -using namespace TW::IoTeX; +namespace TW::IoTeX::tests { TEST(TWIoTeXStaking, Create) { std::string IOTEX_STAKING_CANDIDATE = "io19d0p3ah4g8ww9d7kcxfq87yxe7fnr8rpth5shj"; @@ -131,8 +128,7 @@ TEST(TWIoTeXStaking, CandidateUpdate) { "326e756b7034766763776b32676e6335637539617964"); } -Proto::SigningInput createSigningInput() -{ +Proto::SigningInput createSigningInput() { auto keyhex = parse_hex("cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1"); auto input = Proto::SigningInput(); input.set_version(1); @@ -332,3 +328,5 @@ TEST(TWIoTeXStaking, SignAll) { "35f53a536e014b32b85df50483ef04849b80ad60635b3b1979c5ba1096b65237"); } } + +} // namespace TW::IoTeX::tests diff --git a/tests/IoTeX/TWAnySignerTests.cpp b/tests/chains/IoTeX/TWAnySignerTests.cpp similarity index 91% rename from tests/IoTeX/TWAnySignerTests.cpp rename to tests/chains/IoTeX/TWAnySignerTests.cpp index 3bcb630ece4..4fbeafd20ac 100644 --- a/tests/IoTeX/TWAnySignerTests.cpp +++ b/tests/chains/IoTeX/TWAnySignerTests.cpp @@ -1,19 +1,17 @@ - -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include "HexCoding.h" #include "proto/IoTeX.pb.h" #include -using namespace TW; -using namespace TW::IoTeX; +namespace TW::IoTeX::tests { TEST(TWAnySignerIoTeX, Sign) { auto key = parse_hex("68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748"); @@ -32,3 +30,5 @@ TEST(TWAnySignerIoTeX, Sign) { ASSERT_EQ(hex(output.encoded()), "0a39080110011801220131522e0a01311229696f3165326e7173797437666b707a733578377a6632756b306a6a3732746575356e36616b75337472124104fb30b196ce3e976593ecc2da220dca9cdea8c84d2373770042a930b892ac0f5cf762f20459c9100eb9d4d7597f5817bf21e10b53a0120b9ec1ba5cddfdcb669b1a41ec9757ae6c9009315830faaab250b6db0e9535b00843277f596ae0b2b9efc0bd4e14138c056fc4cdfa285d13dd618052b3d1cb7a3f554722005a2941bfede96601"); } + +} // namespace TW::IoTeX::tests diff --git a/tests/IoTeX/TWCoinTypeTests.cpp b/tests/chains/IoTeX/TWCoinTypeTests.cpp similarity index 97% rename from tests/IoTeX/TWCoinTypeTests.cpp rename to tests/chains/IoTeX/TWCoinTypeTests.cpp index ce42df73442..4cdc2d68ad4 100644 --- a/tests/IoTeX/TWCoinTypeTests.cpp +++ b/tests/chains/IoTeX/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/chains/Juno/TWAnyAddressTests.cpp b/tests/chains/Juno/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..992176b9510 --- /dev/null +++ b/tests/chains/Juno/TWAnyAddressTests.cpp @@ -0,0 +1,50 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include "HexCoding.h" +#include "Hash.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TWJunoAnyAddress, IsValid) { + EXPECT_TRUE(TWAnyAddressIsValidBech32(STRING("juno1gckvjxau7k56f8wg8c8xj80khyp83y8x8eqc94").get(), TWCoinTypeCosmos, STRING("juno").get())); + EXPECT_FALSE(TWAnyAddressIsValidBech32(STRING("juno1gckvjxau7k56f8wg8c8xj80khyp83y8x8eqc94").get(), TWCoinTypeBitcoin, STRING("juno").get())); + EXPECT_FALSE(TWAnyAddressIsValid(STRING("juno1gckvjxau7k56f8wg8c8xj80khyp83y8x8eqc94").get(), TWCoinTypeCosmos)); + EXPECT_FALSE(TWAnyAddressIsValid(STRING("juno1gckvjxau7k56f8wg8c8xj80khyp83y8x8eqc94").get(), TWCoinTypeBitcoin)); + +} + +TEST(TWJunoAnyAddress, createFromPubKeyJuno) { + const auto hrp = STRING("juno"); + const auto data = DATA("02753f5c275e1847ba4d2fd3df36ad00af2e165650b35fe3991e9c9c46f68b12bc"); + const auto pubkey = TWPublicKeyCreateWithData(data.get(), TWPublicKeyTypeSECP256k1); + const auto twAddress = TWAnyAddressCreateBech32WithPublicKey(pubkey, TWCoinTypeCosmos, hrp.get()); + auto twData = TWAnyAddressData(twAddress); + auto hexData = hex(*reinterpret_cast(twData)); + ASSERT_EQ(hexData, "c494c4cb388e23fe24a93158d6cd1fbdca8ebb73"); + ASSERT_EQ(hex(Bech32Address("juno", TW::Hash::HasherSha256ripemd, pubkey->impl).getKeyHash()), hexData); + auto address = TWAnyAddressDescription(twAddress); + EXPECT_EQ("juno1cj2vfjec3c3luf9fx9vddnglhh9gawmncn4k5n", *reinterpret_cast(address)); + TWStringDelete(address); + TWAnyAddressDelete(twAddress); + TWDataDelete(twData); + TWPublicKeyDelete(pubkey); +} + +TEST(TWJunoAnyAddress, createFromStringJuno) { + const auto junoAddress = STRING("juno1cj2vfjec3c3luf9fx9vddnglhh9gawmncn4k5n"); + const auto hrp = STRING("juno"); + const auto anyAddr = TWAnyAddressCreateBech32(junoAddress.get(), TWCoinTypeCosmos, hrp.get()); + const auto addrDescription = TWAnyAddressDescription(anyAddr); + ASSERT_TRUE(TWAnyAddressIsValidBech32(addrDescription, TWCoinTypeCosmos, hrp.get())); + TWStringDelete(addrDescription); + TWAnyAddressDelete(anyAddr); + +} diff --git a/tests/Kava/TWCoinTypeTests.cpp b/tests/chains/Kava/TWCoinTypeTests.cpp similarity index 77% rename from tests/Kava/TWCoinTypeTests.cpp rename to tests/chains/Kava/TWCoinTypeTests.cpp index 1e2ed1b261b..fd4a19b7412 100644 --- a/tests/Kava/TWCoinTypeTests.cpp +++ b/tests/chains/Kava/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -17,18 +17,20 @@ TEST(TWKavaCoinType, TWCoinType) { auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeKava)); auto txId = WRAPS(TWStringCreateWithUTF8Bytes("2988DF83FCBFAA38179D583A96734CBD071541D6768221BB23111BC8136D5E6A")); auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeKava, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("kava1jf9aaj9myrzsnmpdr7twecnaftzmku2mdpy2a7")); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("kava1xd39avn2f008jmvua0eupg39zsp2xn3wf802vn")); auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeKava, accId.get())); auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeKava)); auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeKava)); + const auto chainId = WRAPS(TWCoinTypeChainId(TWCoinTypeKava)); ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeKava), 6); ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeKava)); ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeKava)); ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeKava)); + assertStringsEqual(chainId, "kava_2222-10"); assertStringsEqual(symbol, "KAVA"); - assertStringsEqual(txUrl, "https://kava.mintscan.io/txs/2988DF83FCBFAA38179D583A96734CBD071541D6768221BB23111BC8136D5E6A"); - assertStringsEqual(accUrl, "https://kava.mintscan.io/account/kava1jf9aaj9myrzsnmpdr7twecnaftzmku2mdpy2a7"); + assertStringsEqual(txUrl, "https://mintscan.io/kava/txs/2988DF83FCBFAA38179D583A96734CBD071541D6768221BB23111BC8136D5E6A"); + assertStringsEqual(accUrl, "https://mintscan.io/kava/account/kava1xd39avn2f008jmvua0eupg39zsp2xn3wf802vn"); assertStringsEqual(id, "kava"); assertStringsEqual(name, "Kava"); } diff --git a/tests/chains/KavaEvm/TWCoinTypeTests.cpp b/tests/chains/KavaEvm/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..a658eba031a --- /dev/null +++ b/tests/chains/KavaEvm/TWCoinTypeTests.cpp @@ -0,0 +1,37 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWKavaEvmCoinType, TWCoinType) { + const auto coin = TWCoinTypeKavaEvm; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xa999bd5183568ba178795e6a9d1561566fbf4a9ccc813cc475168832bc4909b3")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xF92d3DB0d9f912f285b1ec69578A6201A78487d7")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "kavaevm"); + assertStringsEqual(name, "KavaEvm"); + assertStringsEqual(symbol, "KAVA"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "2222"); + assertStringsEqual(txUrl, "https://explorer.kava.io/tx/0xa999bd5183568ba178795e6a9d1561566fbf4a9ccc813cc475168832bc4909b3"); + assertStringsEqual(accUrl, "https://explorer.kava.io/address/0xF92d3DB0d9f912f285b1ec69578A6201A78487d7"); +} diff --git a/tests/Kin/TWCoinTypeTests.cpp b/tests/chains/Kin/TWCoinTypeTests.cpp similarity index 97% rename from tests/Kin/TWCoinTypeTests.cpp rename to tests/chains/Kin/TWCoinTypeTests.cpp index 9120ef0e2f9..8f37b7f3607 100644 --- a/tests/Kin/TWCoinTypeTests.cpp +++ b/tests/chains/Kin/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/chains/Klaytn/TWCoinTypeTests.cpp b/tests/chains/Klaytn/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..d7eb5c71136 --- /dev/null +++ b/tests/chains/Klaytn/TWCoinTypeTests.cpp @@ -0,0 +1,37 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWKlaytnCoinType, TWCoinType) { + const auto coin = TWCoinTypeKlaytn; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x93ea92687845fe7bb6cacd69c76a16a2a3c2bbb85a8a93ff0e032d0098d583d7")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x2ad9656bf5b82caf10847b431012e28e301e83ba")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "klaytn"); + assertStringsEqual(name, "Klaytn"); + assertStringsEqual(symbol, "KLAY"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "8217"); + assertStringsEqual(txUrl, "https://scope.klaytn.com/tx/0x93ea92687845fe7bb6cacd69c76a16a2a3c2bbb85a8a93ff0e032d0098d583d7"); + assertStringsEqual(accUrl, "https://scope.klaytn.com/account/0x2ad9656bf5b82caf10847b431012e28e301e83ba"); +} diff --git a/tests/chains/KuCoinCommunityChain/TWCoinTypeTests.cpp b/tests/chains/KuCoinCommunityChain/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..354bc8b7ba1 --- /dev/null +++ b/tests/chains/KuCoinCommunityChain/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// Copyright © 2017-2021 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(TWKuCoinCommunityChainCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeKuCoinCommunityChain)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x2f0d79cd289a02f3181b68b9583a64c3809fe7387810b274275985c29d02c80d")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeKuCoinCommunityChain, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x4446fc4eb47f2f6586f9faab68b3498f86c07521")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeKuCoinCommunityChain, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeKuCoinCommunityChain)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeKuCoinCommunityChain)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeKuCoinCommunityChain), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeKuCoinCommunityChain)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeKuCoinCommunityChain)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeKuCoinCommunityChain)); + assertStringsEqual(symbol, "KCS"); + assertStringsEqual(txUrl, "https://explorer.kcc.io/en/tx/0x2f0d79cd289a02f3181b68b9583a64c3809fe7387810b274275985c29d02c80d"); + assertStringsEqual(accUrl, "https://explorer.kcc.io/en/address/0x4446fc4eb47f2f6586f9faab68b3498f86c07521"); + assertStringsEqual(id, "kcc"); + assertStringsEqual(name, "KuCoin Community Chain"); +} diff --git a/tests/Kusama/AddressTests.cpp b/tests/chains/Kusama/AddressTests.cpp similarity index 95% rename from tests/Kusama/AddressTests.cpp rename to tests/chains/Kusama/AddressTests.cpp index 7a7229a1167..10f6178d1df 100644 --- a/tests/Kusama/AddressTests.cpp +++ b/tests/chains/Kusama/AddressTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -11,8 +11,7 @@ #include #include -using namespace TW; -using namespace TW::Kusama; +namespace TW::Kusama::tests { TEST(KusamaAddress, Validation) { // Substrate ed25519 @@ -49,3 +48,5 @@ TEST(KusamaAddress, FromString) { auto address = Address("CeVXtoU4py9e7F6upfM2ZarVave299TjcdaTSxhDDZrYgnM"); ASSERT_EQ(address.string(), "CeVXtoU4py9e7F6upfM2ZarVave299TjcdaTSxhDDZrYgnM"); } + +} // namespace TW::Kusama::tests \ No newline at end of file diff --git a/tests/Kusama/SignerTests.cpp b/tests/chains/Kusama/SignerTests.cpp similarity index 95% rename from tests/Kusama/SignerTests.cpp rename to tests/chains/Kusama/SignerTests.cpp index f9ef03d127f..3af2df1c9ec 100644 --- a/tests/Kusama/SignerTests.cpp +++ b/tests/chains/Kusama/SignerTests.cpp @@ -6,7 +6,7 @@ #include "Polkadot/Signer.h" #include "Polkadot/Extrinsic.h" -#include "SS58Address.h" +#include "Polkadot/SS58Address.h" #include "HexCoding.h" #include "PrivateKey.h" #include "PublicKey.h" @@ -17,7 +17,7 @@ #include -namespace TW::Polkadot { +namespace TW::Polkadot::tests { extern PrivateKey privateKey; extern PublicKey toPublicKey; auto genesisHashKSM = parse_hex("b0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); @@ -49,4 +49,4 @@ TEST(PolkadotSigner, SignTransferKSM) { ASSERT_EQ(hex(output.encoded()), "25028488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee000765cfa76cfe19499f4f19ef7dc4527652ec5b2e6b5ecfaf68725dafd48ae2694ad52e61f44152a544784e847de10ddb2c56bee4406574dcbcfdb5e5d35b6d0300000004008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0"); } -} // namespace +} // namespace TW::Polkadot::tests diff --git a/tests/Kusama/TWAnySignerTests.cpp b/tests/chains/Kusama/TWAnySignerTests.cpp similarity index 94% rename from tests/Kusama/TWAnySignerTests.cpp rename to tests/chains/Kusama/TWAnySignerTests.cpp index d3bf8fd5adb..c23abdd9acb 100644 --- a/tests/Kusama/TWAnySignerTests.cpp +++ b/tests/chains/Kusama/TWAnySignerTests.cpp @@ -7,13 +7,12 @@ #include "HexCoding.h" #include "proto/Polkadot.pb.h" #include "uint256.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include -using namespace TW; -using namespace TW::Polkadot; +namespace TW::Polkadot::tests { TEST(TWAnySignerKusama, Sign) { auto key = parse_hex("0x8cdc538e96f460da9d639afc5c226f477ce98684d77fb31e88db74c1f1dd86b2"); @@ -39,3 +38,5 @@ TEST(TWAnySignerKusama, Sign) { ASSERT_EQ(hex(output.encoded()), "350284f41296779fd61a5bed6c2f506cc6c9ea93d6aeb357b9c69717193f434ba24ae700cd78b46eff36c433e642d7e9830805aab4f43eef70067ef32c8b2a294c510673a841c5f8a6e8900c03be40cfa475ae53e6f8aa61961563cb7cc0fa169ef9630d00040004000e33fdfb980e4499e5c3576e742a563b6a4fc0f6f598b1917fd7a6fe393ffc720700e40b5402"); } + +} // namespace TW::Polkadot::tests diff --git a/tests/Kusama/TWCoinTypeTests.cpp b/tests/chains/Kusama/TWCoinTypeTests.cpp similarity index 93% rename from tests/Kusama/TWCoinTypeTests.cpp rename to tests/chains/Kusama/TWCoinTypeTests.cpp index 929af6a5bc1..09cd2bf8c0f 100644 --- a/tests/Kusama/TWCoinTypeTests.cpp +++ b/tests/chains/Kusama/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -23,7 +23,7 @@ TEST(TWKusamaCoinType, TWCoinType) { auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeKusama)); ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeKusama), 12); - ASSERT_EQ(TWBlockchainPolkadot, TWCoinTypeBlockchain(TWCoinTypeKusama)); + ASSERT_EQ(TWBlockchainKusama, TWCoinTypeBlockchain(TWCoinTypeKusama)); ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeKusama)); ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeKusama)); assertStringsEqual(symbol, "KSM"); diff --git a/tests/chains/Litecoin/LitecoinAddressTests.cpp b/tests/chains/Litecoin/LitecoinAddressTests.cpp new file mode 100644 index 00000000000..6858723ce13 --- /dev/null +++ b/tests/chains/Litecoin/LitecoinAddressTests.cpp @@ -0,0 +1,41 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Bitcoin/Address.h" +#include "Bitcoin/SegwitAddress.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PublicKey.h" + +#include +#include + +#include +#include + +using namespace TW; + +namespace TW::Bitcoin::tests { + +TEST(LitecoinAddress, deriveAddress_legacy) { + const auto pubKey = PublicKey(parse_hex("03b49081a4d7ad24b20e209bc6fe10491aadb5607777baf0509a036cce96025db0"), TWPublicKeyTypeSECP256k1); + auto addrStr = deriveAddress(TWCoinTypeLitecoin, pubKey, TWDerivationLitecoinLegacy); + EXPECT_EQ(addrStr, "LW6HjAU6GL9fK2LZWUA6VZCzomTdrpx3nr"); + + const auto address = Address(pubKey, TWCoinTypeP2pkhPrefix(TWCoinTypeLitecoin)); + EXPECT_EQ(address.string(), addrStr); +} + +TEST(LitecoinAddress, deriveAddress_segwit) { + const auto pubKey = PublicKey(parse_hex("030fc2fdd1a0b5d43b31227a4b1cd57e7d35a6edc93fb12f9315e67762abeb8be0"), TWPublicKeyTypeSECP256k1); + auto addrStr = deriveAddress(TWCoinTypeLitecoin, pubKey, TWDerivationBitcoinSegwit); + EXPECT_EQ(addrStr, "ltc1q3m3ujh350qrqdl33pv7pjw0d0m9qnm6qjcjpga"); + + const auto address = SegwitAddress(pubKey, stringForHRP(TWCoinTypeHRP(TWCoinTypeLitecoin))); + EXPECT_EQ(address.string(), addrStr); +} + +} // namespace TW::Bitcoin::tests diff --git a/tests/Litecoin/TWCoinTypeTests.cpp b/tests/chains/Litecoin/TWCoinTypeTests.cpp similarity index 97% rename from tests/Litecoin/TWCoinTypeTests.cpp rename to tests/chains/Litecoin/TWCoinTypeTests.cpp index 3d96044505d..441d729e66d 100644 --- a/tests/Litecoin/TWCoinTypeTests.cpp +++ b/tests/chains/Litecoin/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Litecoin/TWLitecoinTests.cpp b/tests/chains/Litecoin/TWLitecoinTests.cpp similarity index 95% rename from tests/Litecoin/TWLitecoinTests.cpp rename to tests/chains/Litecoin/TWLitecoinTests.cpp index 46dd87b280f..0008fd375d1 100644 --- a/tests/Litecoin/TWLitecoinTests.cpp +++ b/tests/chains/Litecoin/TWLitecoinTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -15,6 +15,8 @@ #include +namespace TW::Litecoin::tests { + TEST(Litecoin, LegacyAddress) { auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730").get())); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); @@ -46,9 +48,8 @@ TEST(Litecoin, LockScriptForAddressM) { TEST(Litecoin, ExtendedKeys) { auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic( - STRING("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal").get(), - STRING("TREZOR").get() - )); + STRING("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal").get(), + STRING("TREZOR").get())); // .bip44 auto lptv = WRAPS(TWHDWalletGetExtendedPrivateKey(wallet.get(), TWPurposeBIP44, TWCoinTypeLitecoin, TWHDVersionLTPV)); @@ -99,3 +100,5 @@ TEST(Litecoin, LockScripts) { auto scriptData3 = WRAPD(TWBitcoinScriptData(script3.get())); assertHexEqual(scriptData3, "76a914e771c6695c5dd189ccc4ef00cd0f3db3096d79bd88ac"); } + +} // namespace TW::Litecoin::tests diff --git a/tests/chains/Meter/TWCoinTypeTests.cpp b/tests/chains/Meter/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..b9c4df9d838 --- /dev/null +++ b/tests/chains/Meter/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWMeterCoinType, TWCoinType) { + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeMeter)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x8ea268d5dbb40217c763b800a75fc063cf28b56f40f2bc69dc043f5c4bbdc144")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeMeter, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xe5a273954d24eddf9ae9ea4cef2347d584cfa3dd")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeMeter, accId.get())); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeMeter)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeMeter)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeMeter), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeMeter)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeMeter)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeMeter)); + assertStringsEqual(symbol, "MTR"); + assertStringsEqual(txUrl, "https://scan.meter.io/tx/0x8ea268d5dbb40217c763b800a75fc063cf28b56f40f2bc69dc043f5c4bbdc144"); + assertStringsEqual(accUrl, "https://scan.meter.io/address/0xe5a273954d24eddf9ae9ea4cef2347d584cfa3dd"); + assertStringsEqual(id, "meter"); + assertStringsEqual(name, "Meter"); +} diff --git a/tests/chains/Metis/TWCoinTypeTests.cpp b/tests/chains/Metis/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..60733b44741 --- /dev/null +++ b/tests/chains/Metis/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// Copyright © 2017-2021 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(TWMetisCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeMetis)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x422f2ebbede32d4434ad0cf0ae55d44a84e14d3d5725a760133255b42676d8ce")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeMetis, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xBe9E8Ec25866B21bA34e97b9393BCabBcB4A5C86")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeMetis, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeMetis)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeMetis)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeMetis), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeMetis)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeMetis)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeMetis)); + assertStringsEqual(symbol, "METIS"); + assertStringsEqual(txUrl, "https://andromeda-explorer.metis.io/tx/0x422f2ebbede32d4434ad0cf0ae55d44a84e14d3d5725a760133255b42676d8ce"); + assertStringsEqual(accUrl, "https://andromeda-explorer.metis.io/address/0xBe9E8Ec25866B21bA34e97b9393BCabBcB4A5C86"); + assertStringsEqual(id, "metis"); + assertStringsEqual(name, "Metis"); +} diff --git a/tests/Monacoin/TWCoinTypeTests.cpp b/tests/chains/Monacoin/TWCoinTypeTests.cpp similarity index 97% rename from tests/Monacoin/TWCoinTypeTests.cpp rename to tests/chains/Monacoin/TWCoinTypeTests.cpp index e539e3924e2..82fc75a0c68 100644 --- a/tests/Monacoin/TWCoinTypeTests.cpp +++ b/tests/chains/Monacoin/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Monacoin/TWMonacoinAddressTests.cpp b/tests/chains/Monacoin/TWMonacoinAddressTests.cpp similarity index 99% rename from tests/Monacoin/TWMonacoinAddressTests.cpp rename to tests/chains/Monacoin/TWMonacoinAddressTests.cpp index e8f6c7c985d..d46964f25f5 100644 --- a/tests/Monacoin/TWMonacoinAddressTests.cpp +++ b/tests/chains/Monacoin/TWMonacoinAddressTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Monacoin/TWMonacoinTransactionTests.cpp b/tests/chains/Monacoin/TWMonacoinTransactionTests.cpp similarity index 87% rename from tests/Monacoin/TWMonacoinTransactionTests.cpp rename to tests/chains/Monacoin/TWMonacoinTransactionTests.cpp index 5cc5da208b1..94f26493815 100644 --- a/tests/Monacoin/TWMonacoinTransactionTests.cpp +++ b/tests/chains/Monacoin/TWMonacoinTransactionTests.cpp @@ -1,11 +1,10 @@ - -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include "HexCoding.h" #include "PublicKey.h" #include "proto/Bitcoin.pb.h" @@ -15,8 +14,7 @@ #include -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { TEST(MonacoinTransaction, SignTransaction) { /* @@ -67,8 +65,7 @@ TEST(MonacoinTransaction, SignTransaction) { ASSERT_EQ(output.error(), Common::Proto::OK); ASSERT_EQ(hex(output.encoded()), - "0100000001441a513dccc3b660c09c42ceaac147fcdc12b5de4b8b56a078fce5d5ce420aed000000006a473044022047789dc7483b178199439bbfce0ab0caf532fec51095ba099d0d9b0b2169033402201745a0160d8d327655a8ef0542367396ce86bbb13df6b183d58c922e422cfa10012102fc08693599fda741558613cd44a50fc65953b1be797637f8790a495b85554f3effffffff0280f0fa02000000001976a914076df984229a2731cbf465ec8fbd35b8da94380f88ac60a2fa02000000001976a914fea39370769d4fed2d8ab98dd5daa482cc56113b88ac00000000" - ); + "0100000001441a513dccc3b660c09c42ceaac147fcdc12b5de4b8b56a078fce5d5ce420aed000000006a473044022047789dc7483b178199439bbfce0ab0caf532fec51095ba099d0d9b0b2169033402201745a0160d8d327655a8ef0542367396ce86bbb13df6b183d58c922e422cfa10012102fc08693599fda741558613cd44a50fc65953b1be797637f8790a495b85554f3effffffff0280f0fa02000000001976a914076df984229a2731cbf465ec8fbd35b8da94380f88ac60a2fa02000000001976a914fea39370769d4fed2d8ab98dd5daa482cc56113b88ac00000000"); } TEST(MonacoinTransaction, LockScripts) { @@ -93,3 +90,5 @@ TEST(MonacoinTransaction, LockScripts) { auto scriptData3 = WRAPD(TWBitcoinScriptData(script3.get())); assertHexEqual(scriptData3, "001422e6014ad3631f1939281c3625bc98db808fbfb0"); } + +} // namespace TW::Bitcoin diff --git a/tests/chains/Moonbeam/TWCoinTypeTests.cpp b/tests/chains/Moonbeam/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..0c59369a597 --- /dev/null +++ b/tests/chains/Moonbeam/TWCoinTypeTests.cpp @@ -0,0 +1,37 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWMoonbeamCoinType, TWCoinType) { + const auto coin = TWCoinTypeMoonbeam; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xb22a146c933e6e51affbfa5f712a266b5f5e92ae453cd2f252bcc3c36ff035a6")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x201bb4f276C765dF7225e5A4153E17edD23a67eC")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "moonbeam"); + assertStringsEqual(name, "Moonbeam"); + assertStringsEqual(symbol, "GLMR"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "1284"); + assertStringsEqual(txUrl, "https://moonscan.io/tx/0xb22a146c933e6e51affbfa5f712a266b5f5e92ae453cd2f252bcc3c36ff035a6"); + assertStringsEqual(accUrl, "https://moonscan.io/address/0x201bb4f276C765dF7225e5A4153E17edD23a67eC"); +} diff --git a/tests/chains/Moonriver/TWCoinTypeTests.cpp b/tests/chains/Moonriver/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..b19f8a84b7b --- /dev/null +++ b/tests/chains/Moonriver/TWCoinTypeTests.cpp @@ -0,0 +1,37 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWMoonriverCoinType, TWCoinType) { + const auto coin = TWCoinTypeMoonriver; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x2e2daa3943ba65d9bbb910a4f6765aa6a466a0ef8935090547ca9d30e201e032")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x899831D937937d011305E73EE782cce0455DF15a")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "moonriver"); + assertStringsEqual(name, "Moonriver"); + assertStringsEqual(symbol, "MOVR"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "1285"); + assertStringsEqual(txUrl, "https://moonriver.moonscan.io/tx/0x2e2daa3943ba65d9bbb910a4f6765aa6a466a0ef8935090547ca9d30e201e032"); + assertStringsEqual(accUrl, "https://moonriver.moonscan.io/address/0x899831D937937d011305E73EE782cce0455DF15a"); +} diff --git a/tests/NEAR/AccountTests.cpp b/tests/chains/NEAR/AccountTests.cpp similarity index 88% rename from tests/NEAR/AccountTests.cpp rename to tests/chains/NEAR/AccountTests.cpp index 36d38993b78..f1294306588 100644 --- a/tests/NEAR/AccountTests.cpp +++ b/tests/chains/NEAR/AccountTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -6,11 +6,9 @@ #include "NEAR/Account.h" #include "HexCoding.h" - #include -using namespace TW; -using namespace TW::NEAR; +namespace TW::NEAR::tests { TEST(NEARAccount, Validation) { ASSERT_FALSE(Account::isValid("a")); @@ -22,3 +20,5 @@ TEST(NEARAccount, Validation) { ASSERT_TRUE(Account::isValid("test-trust.vlad.near")); ASSERT_TRUE(Account::isValid("deadbeef")); } + +} // namespace TW::NEAR::tests diff --git a/tests/NEAR/AddressTests.cpp b/tests/chains/NEAR/AddressTests.cpp similarity index 92% rename from tests/NEAR/AddressTests.cpp rename to tests/chains/NEAR/AddressTests.cpp index 1da6ca22089..214cd3fab2b 100644 --- a/tests/NEAR/AddressTests.cpp +++ b/tests/chains/NEAR/AddressTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -11,8 +11,7 @@ #include -using namespace TW; -using namespace TW::NEAR; +namespace TW::NEAR::tests { TEST(NEARAddress, Validation) { ASSERT_FALSE(Address::isValid("abc")); @@ -26,12 +25,10 @@ TEST(NEARAddress, Validation) { TEST(NEARAddress, FromString) { ASSERT_EQ( Address("NEAR2758Nk7CMUcxTwXdjVdSxNEidiZQWMZN3USJzj76q5ia3v2v2v").string(), - "917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d" - ); + "917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d"); ASSERT_EQ( Address("9685af3fe2dc231e5069ccff8ec6950eb961d42ebb9116a8ab9c0d38f9e45249").string(), - "9685af3fe2dc231e5069ccff8ec6950eb961d42ebb9116a8ab9c0d38f9e45249" - ); + "9685af3fe2dc231e5069ccff8ec6950eb961d42ebb9116a8ab9c0d38f9e45249"); } TEST(NEARAddress, FromPrivateKey) { @@ -42,3 +39,5 @@ TEST(NEARAddress, FromPrivateKey) { ASSERT_EQ(address.string(), "917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d"); } + +} // namespace TW::NEAR::tests diff --git a/tests/chains/NEAR/SerializationTests.cpp b/tests/chains/NEAR/SerializationTests.cpp new file mode 100644 index 00000000000..7f6904988b6 --- /dev/null +++ b/tests/chains/NEAR/SerializationTests.cpp @@ -0,0 +1,278 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "HexCoding.h" +#include "Base58.h" +#include "proto/NEAR.pb.h" +#include "NEAR/Serialization.h" + +#include +#include + +namespace TW::NEAR { + +TEST(NEARSerialization, SerializeTransferTransaction) { + auto publicKey = Base58::bitcoin.decode("Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC"); + + auto input = Proto::SigningInput(); + input.set_signer_id("test.near"); + input.set_nonce(1); + input.set_receiver_id("whatever.near"); + + input.add_actions(); + auto& transfer = *input.mutable_actions(0)->mutable_transfer(); + Data deposit(16, 0); + deposit[0] = 1; + transfer.set_deposit(deposit.data(), deposit.size()); + + auto blockHash = Base58::bitcoin.decode("244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM"); + input.set_block_hash(blockHash.data(), blockHash.size()); + + auto privateKey = Base58::bitcoin.decode("3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv"); + input.set_private_key(privateKey.data(), 32); + + auto serialized = transactionData(input); + auto serializedHex = hex(serialized); + + ASSERT_EQ(serializedHex, "09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef6010000000301000000000000000000000000000000"); +} + +TEST(NEARSerialization, SerializeFunctionCallTransaction) { + auto publicKey = Base58::bitcoin.decode("Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC"); + + auto input = Proto::SigningInput(); + input.set_signer_id("test.near"); + input.set_nonce(1); + input.set_receiver_id("whatever.near"); + + input.add_actions(); + auto& functionCall = *input.mutable_actions(0)->mutable_function_call(); + + functionCall.set_method_name("qqq"); + functionCall.set_gas(1000); + + Data deposit(16, 0); + deposit[0] = 1; + functionCall.set_deposit(deposit.data(), deposit.size()); + + Data args(3, 0); + args[0] = 1; + args[1] = 2; + args[2] = 3; + functionCall.set_args(args.data(), args.size()); + + auto blockHash = Base58::bitcoin.decode("244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM"); + input.set_block_hash(blockHash.data(), blockHash.size()); + + auto privateKey = Base58::bitcoin.decode("3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv"); + input.set_private_key(privateKey.data(), 32); + + auto serialized = transactionData(input); + auto serializedHex = hex(serialized); + + ASSERT_EQ(serializedHex, "09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef601000000020300000071717103000000010203e80300000000000001000000000000000000000000000000"); +} + +TEST(NEARSerialization, SerializeStakeTransaction) { + auto publicKey = Base58::bitcoin.decode("Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC"); + + auto input = Proto::SigningInput(); + input.set_signer_id("test.near"); + input.set_nonce(1); + input.set_receiver_id("whatever.near"); + + input.add_actions(); + auto& stake = *input.mutable_actions(0)->mutable_stake(); + Data amount(16, 0); + amount[0] = 1; + stake.set_stake(amount.data(), amount.size()); + + auto& pKey = *stake.mutable_public_key(); + pKey.set_data(publicKey.data(), publicKey.size()); + pKey.set_key_type(0); + + auto blockHash = Base58::bitcoin.decode("244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM"); + input.set_block_hash(blockHash.data(), blockHash.size()); + + auto privateKey = Base58::bitcoin.decode("3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv"); + input.set_private_key(privateKey.data(), 32); + + auto serialized = transactionData(input); + auto serializedHex = hex(serialized); + + ASSERT_EQ(serializedHex, "09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef601000000040100000000000000000000000000000000917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d"); +} + +TEST(NEARSerialization, SerializeStakeTransaction2) { + auto publicKey = Base58::bitcoin.decode("C2P7YcEmBv31vtCHLBcESteN4Yi4vSCkXEXMTANyB649"); + + auto input = Proto::SigningInput(); + input.set_signer_id("vdx.testnet"); + input.set_nonce(93128451000005); + input.set_receiver_id("vdx.testnet"); + + input.add_actions(); + auto& stake = *input.mutable_actions(0)->mutable_stake(); + // 2490000000000000000000000000 + auto amount = parse_hex("000000fa4f3f757902ae0b0800000000"); // little endian + stake.set_stake(amount.data(), amount.size()); + + auto& pKey = *stake.mutable_public_key(); + pKey.set_data(publicKey.data(), publicKey.size()); + pKey.set_key_type(0); + + auto blockHash = Base58::bitcoin.decode("ByDnm7c25npQXwNUX5yivbYbpjFcNuNumF6BJjaK3vhJ"); + input.set_block_hash(blockHash.data(), blockHash.size()); + + auto privateKey = Base58::bitcoin.decode("5Cfk7QBnmDxxFxQk75FFq4ADrQS9gxHKe6vtuGH6JCCm8WV8aRPEGVqp579JHNmmHMUt49gkCVcH2t7NRnh2v7Qu"); + input.set_private_key(privateKey.data(), 32); + + auto serialized = transactionData(input); + auto serializedHex = hex(serialized); + + ASSERT_EQ(serializedHex, "0b0000007664782e746573746e657400a3cb23dbb9810abd4a6804328eec47a17236383b5c234cae903b064e9dc426dac5863d28b35400000b0000007664782e746573746e6574a2fbdae8a769c636d109952e4fe760b03688e629933cbf693aedfd97a470c7a50100000004000000fa4f3f757902ae0b080000000000a3cb23dbb9810abd4a6804328eec47a17236383b5c234cae903b064e9dc426da"); +} + +TEST(NEARSerialization, SerializeAddKeyFunctionCallTransaction) { + auto publicKey = Base58::bitcoin.decode("Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC"); + + auto input = Proto::SigningInput(); + input.set_signer_id("test.near"); + input.set_nonce(1); + input.set_receiver_id("whatever.near"); + + input.add_actions(); + auto& addKey = *input.mutable_actions(0)->mutable_add_key(); + + auto& pKey = *addKey.mutable_public_key(); + pKey.set_data(publicKey.data(), publicKey.size()); + pKey.set_key_type(0); + + auto& accessKey = *addKey.mutable_access_key(); + accessKey.set_nonce(0); + auto& functionCallPermission = *accessKey.mutable_function_call(); + functionCallPermission.set_receiver_id("zzz"); + functionCallPermission.add_method_names("www"); + + auto blockHash = Base58::bitcoin.decode("244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM"); + input.set_block_hash(blockHash.data(), blockHash.size()); + + auto privateKey = Base58::bitcoin.decode("3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv"); + input.set_private_key(privateKey.data(), 32); + + auto serialized = transactionData(input); + auto serializedHex = hex(serialized); + + ASSERT_EQ(serializedHex, "09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef6010000000500917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d00000000000000000000030000007a7a7a0100000003000000777777"); +} + +TEST(NEARSerialization, SerializeAddKeyFullAccessTransaction) { + auto publicKey = Base58::bitcoin.decode("Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC"); + + auto input = Proto::SigningInput(); + input.set_signer_id("test.near"); + input.set_nonce(1); + input.set_receiver_id("whatever.near"); + + input.add_actions(); + auto& addKey = *input.mutable_actions(0)->mutable_add_key(); + + auto& pKey = *addKey.mutable_public_key(); + pKey.set_data(publicKey.data(), publicKey.size()); + pKey.set_key_type(0); + + auto& accessKey = *addKey.mutable_access_key(); + accessKey.set_nonce(0); + + accessKey.mutable_full_access(); + + auto blockHash = Base58::bitcoin.decode("244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM"); + input.set_block_hash(blockHash.data(), blockHash.size()); + + auto privateKey = Base58::bitcoin.decode("3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv"); + input.set_private_key(privateKey.data(), 32); + + auto serialized = transactionData(input); + auto serializedHex = hex(serialized); + + ASSERT_EQ(serializedHex, "09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef6010000000500917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d000000000000000001"); +} + +TEST(NEARSerialization, SerializeDeleteKeyTransaction) { + auto publicKey = Base58::bitcoin.decode("Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC"); + + auto input = Proto::SigningInput(); + input.set_signer_id("test.near"); + input.set_nonce(1); + input.set_receiver_id("whatever.near"); + + input.add_actions(); + auto& deleteKey = *input.mutable_actions(0)->mutable_delete_key(); + + auto& pKey = *deleteKey.mutable_public_key(); + pKey.set_data(publicKey.data(), publicKey.size()); + pKey.set_key_type(0); + + auto blockHash = Base58::bitcoin.decode("244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM"); + input.set_block_hash(blockHash.data(), blockHash.size()); + + auto privateKey = Base58::bitcoin.decode("3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv"); + input.set_private_key(privateKey.data(), 32); + + auto serialized = transactionData(input); + auto serializedHex = hex(serialized); + + ASSERT_EQ(serializedHex, "09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef6010000000600917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d"); +} + +TEST(NEARSerialization, SerializeCreateAccountTransaction) { + auto publicKey = Base58::bitcoin.decode("Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC"); + + auto input = Proto::SigningInput(); + input.set_signer_id("test.near"); + input.set_nonce(1); + input.set_receiver_id("whatever.near"); + + input.add_actions(); + input.mutable_actions(0)->mutable_create_account(); + + auto blockHash = Base58::bitcoin.decode("244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM"); + input.set_block_hash(blockHash.data(), blockHash.size()); + + auto privateKey = Base58::bitcoin.decode("3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv"); + input.set_private_key(privateKey.data(), 32); + + auto serialized = transactionData(input); + auto serializedHex = hex(serialized); + + ASSERT_EQ(serializedHex, "09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef60100000000"); +} + +TEST(NEARSerialization, SerializeDeleteAccountTransaction) { + auto publicKey = Base58::bitcoin.decode("Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC"); + + auto input = Proto::SigningInput(); + input.set_signer_id("test.near"); + input.set_nonce(1); + input.set_receiver_id("whatever.near"); + + input.add_actions(); + auto& deleteAccount = *input.mutable_actions(0)->mutable_delete_account(); + deleteAccount.set_beneficiary_id("123"); + + auto blockHash = Base58::bitcoin.decode("244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM"); + input.set_block_hash(blockHash.data(), blockHash.size()); + + auto privateKey = Base58::bitcoin.decode("3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv"); + input.set_private_key(privateKey.data(), 32); + + auto serialized = transactionData(input); + auto serializedHex = hex(serialized); + + ASSERT_EQ(serializedHex, "09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef6010000000703000000313233"); +} + +} diff --git a/tests/NEAR/SignerTests.cpp b/tests/chains/NEAR/SignerTests.cpp similarity index 93% rename from tests/NEAR/SignerTests.cpp rename to tests/chains/NEAR/SignerTests.cpp index a0b0da08920..2a41296484c 100644 --- a/tests/NEAR/SignerTests.cpp +++ b/tests/chains/NEAR/SignerTests.cpp @@ -6,6 +6,7 @@ #include "Base64.h" #include "Base58.h" +#include "HexCoding.h" #include "proto/NEAR.pb.h" #include "NEAR/Signer.h" @@ -41,6 +42,7 @@ TEST(NEARSigner, SignTx) { auto outputInBase64 = Base64::encode(Data(signed_transaction.begin(), signed_transaction.end())); ASSERT_EQ(outputInBase64, "CQAAAHRlc3QubmVhcgCRez0mjUtY9/7BsVC9aNab4+5dTMOYVeNBU4Rlu3eGDQEAAAAAAAAADQAAAHdoYXRldmVyLm5lYXIPpHP9JpAd8pa+atxMxN800EDvokNSJLaYaRDmMML+9gEAAAADAQAAAAAAAAAAAAAAAAAAAACWmoMzIYbul1Xkg5MlUlgG4Ymj0tK7S0dg6URD6X4cTyLe7vAFmo6XExAO2m4ZFE2n6KDvflObIHCLodjQIb0B"); + ASSERT_EQ(hex(output.hash()), "eea6e680f3ea51a7f667e9a801d0bfadf66e03d41ed54975b3c6006351461b32"); } } diff --git a/tests/chains/NEAR/TWAnySignerTests.cpp b/tests/chains/NEAR/TWAnySignerTests.cpp new file mode 100644 index 00000000000..afa81ba9a79 --- /dev/null +++ b/tests/chains/NEAR/TWAnySignerTests.cpp @@ -0,0 +1,71 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "HexCoding.h" +#include "proto/NEAR.pb.h" +#include "TestUtilities.h" +#include +#include + +namespace TW::NEAR { + +TEST(TWAnySignerNEAR, SignTransfer) { + + auto privateKey = parse_hex("8737b99bf16fba78e1e753e23ba00c4b5423ac9c45d9b9caae9a519434786568"); + auto blockHash = parse_hex("0fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef6"); + // uint128_t / little endian byte order + auto deposit = parse_hex("01000000000000000000000000000000"); + + Proto::SigningInput input; + input.set_signer_id("test.near"); + input.set_nonce(1); + input.set_receiver_id("whatever.near"); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_block_hash(blockHash.data(), blockHash.size()); + + auto& action = *input.add_actions(); + auto& transfer = *action.mutable_transfer(); + transfer.set_deposit(deposit.data(), deposit.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNEAR); + + ASSERT_EQ(hex(output.signed_transaction()), "09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef601000000030100000000000000000000000000000000969a83332186ee9755e4839325525806e189a3d2d2bb4b4760e94443e97e1c4f22deeef0059a8e9713100eda6e19144da7e8a0ef7e539b20708ba1d8d021bd01"); + ASSERT_EQ(hex(output.hash()), "eea6e680f3ea51a7f667e9a801d0bfadf66e03d41ed54975b3c6006351461b32"); +} + +TEST(TWAnySignerNEAR, SignStake) { + + auto privateKey = parse_hex("d22149327ceb8e86f70962be0c7293f8308d85d0cbea2cc24e47c3033da7440f"); + auto publicKey = parse_hex("a3cb23dbb9810abd4a6804328eec47a17236383b5c234cae903b064e9dc426da"); + auto blockHash = parse_hex("a2fbdae8a769c636d109952e4fe760b03688e629933cbf693aedfd97a470c7a5"); + + // 2490000000000000000000000000 + auto amount = parse_hex("000000fa4f3f757902ae0b0800000000"); // little endian + + Proto::SigningInput input; + input.set_signer_id("vdx.testnet"); + input.set_nonce(93128451000005); + input.set_receiver_id("vdx.testnet"); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_block_hash(blockHash.data(), blockHash.size()); + + auto& action = *input.add_actions(); + auto& stake = *action.mutable_stake(); + stake.set_stake(amount.data(), amount.size()); + + auto& pubkey = *stake.mutable_public_key(); + pubkey.set_data(publicKey.data(), publicKey.size()); + pubkey.set_key_type(0); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNEAR); + + ASSERT_EQ(hex(output.signed_transaction()), "0b0000007664782e746573746e657400a3cb23dbb9810abd4a6804328eec47a17236383b5c234cae903b064e9dc426dac5863d28b35400000b0000007664782e746573746e6574a2fbdae8a769c636d109952e4fe760b03688e629933cbf693aedfd97a470c7a50100000004000000fa4f3f757902ae0b080000000000a3cb23dbb9810abd4a6804328eec47a17236383b5c234cae903b064e9dc426da0011fdbc234d4ce470ec7f2ac5e4d3d8f8fe1525f83e9a2425e7000aea52f7260ff4f5191beaa1a5ac29256e68c6acd368ada0d06ed033e9a204ee119f5ef1b104"); + ASSERT_EQ(hex(output.hash()), "c8aedbf75fcaa9b663a3959d27f1deae809e1923460791471e5219eafecc4ba8"); +} + +} // namespace TW::NEAR diff --git a/tests/NEAR/TWCoinTypeTests.cpp b/tests/chains/NEAR/TWCoinTypeTests.cpp similarity index 97% rename from tests/NEAR/TWCoinTypeTests.cpp rename to tests/chains/NEAR/TWCoinTypeTests.cpp index e5b5951a368..c5a04325d1f 100644 --- a/tests/NEAR/TWCoinTypeTests.cpp +++ b/tests/chains/NEAR/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/NEAR/TWNEARAccountTests.cpp b/tests/chains/NEAR/TWNEARAccountTests.cpp similarity index 96% rename from tests/NEAR/TWNEARAccountTests.cpp rename to tests/chains/NEAR/TWNEARAccountTests.cpp index c97ba7d5c0d..0be2cf6dbc7 100644 --- a/tests/NEAR/TWNEARAccountTests.cpp +++ b/tests/chains/NEAR/TWNEARAccountTests.cpp @@ -6,7 +6,7 @@ // // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/NEO/AddressTests.cpp b/tests/chains/NEO/AddressTests.cpp similarity index 97% rename from tests/NEO/AddressTests.cpp rename to tests/chains/NEO/AddressTests.cpp index fa6adb863c0..c17f9b8a5c9 100644 --- a/tests/NEO/AddressTests.cpp +++ b/tests/chains/NEO/AddressTests.cpp @@ -4,16 +4,17 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "PublicKey.h" #include "HexCoding.h" #include "NEO/Address.h" #include "NEO/Signer.h" +#include "PublicKey.h" #include using namespace std; using namespace TW; -using namespace TW::NEO; + +namespace TW::NEO::tests { TEST(NEOAddress, FromPublicKey) { const auto publicKey = PublicKey(parse_hex("0222b2277d039d67f4197a638dd5a1d99c290b17aa8c4a16ccee5165fe612de66a"), TWPublicKeyTypeSECP256k1); @@ -55,7 +56,6 @@ TEST(NEOAddress, fromString) { EXPECT_THROW(new Address(errB58Str), std::invalid_argument); } - TEST(NEOAddress, Valid) { ASSERT_TRUE(Address::isValid("ANDfjwrUroaVtvBguDtrWKRMyxFwvVwnZD")); } @@ -71,3 +71,4 @@ TEST(NEOAddress, FromPrivateKey) { ASSERT_EQ(address.string(), "AQCSMB3oSDA1dHPn6GXN6KB4NHmdo1fX41"); } +} // namespace TW::NEO::tests diff --git a/tests/NEO/CoinReferenceTests.cpp b/tests/chains/NEO/CoinReferenceTests.cpp similarity index 70% rename from tests/NEO/CoinReferenceTests.cpp rename to tests/chains/NEO/CoinReferenceTests.cpp index 9c707d5bdeb..1101986a96f 100644 --- a/tests/NEO/CoinReferenceTests.cpp +++ b/tests/chains/NEO/CoinReferenceTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -7,12 +7,11 @@ #include "uint256.h" #include "HexCoding.h" #include "NEO/CoinReference.h" - #include +namespace TW::NEO::tests { + using namespace std; -using namespace TW; -using namespace TW::NEO; TEST(NEOCoinReference, Serialize) { auto coinReference = CoinReference(); @@ -22,9 +21,19 @@ TEST(NEOCoinReference, Serialize) { EXPECT_EQ(prevHash + "0100", hex(coinReference.serialize())); } +TEST(NEOCoinReference, SerializeWithZeroLeading) { + auto coinReference = CoinReference(); + string prevHash = "0037ebf259ca5c6c43a5e7117c910858ea1146290e07d39e48554bc00d890b94"; + coinReference.prevHash = load(parse_hex(prevHash)); + coinReference.prevIndex = 1; + EXPECT_EQ(prevHash + "0100", hex(coinReference.serialize())); +} + TEST(NEOCoinReference, Deserialize) { auto coinReference = CoinReference(); coinReference.deserialize(parse_hex("bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a0100")); EXPECT_EQ("bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a", hex(store(coinReference.prevHash))); EXPECT_EQ(1, coinReference.prevIndex); } + +} // namespace TW::NEO::tests diff --git a/tests/NEO/SignerTests.cpp b/tests/chains/NEO/SignerTests.cpp similarity index 93% rename from tests/NEO/SignerTests.cpp rename to tests/chains/NEO/SignerTests.cpp index 6f3b10fd913..fe33508c885 100644 --- a/tests/NEO/SignerTests.cpp +++ b/tests/chains/NEO/SignerTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -11,9 +11,9 @@ #include +namespace TW::NEO::tests { + using namespace std; -using namespace TW; -using namespace TW::NEO; TEST(NEOSigner, FromPublicPrivateKey) { auto hexPrvKey = "4646464646464646464646464646464646464646464646464646464646464646"; @@ -57,14 +57,14 @@ TEST(NEOSigner, SigningTransaction) { transaction.version = 0x00; CoinReference coin; - coin.prevHash = load(parse_hex("9c85b39cd5677e2bfd6bf8a711e8da93a2f1d172b2a52c6ca87757a4bccc24de")); //reverse hash - coin.prevIndex = (uint16_t) 1; + coin.prevHash = load(parse_hex("9c85b39cd5677e2bfd6bf8a711e8da93a2f1d172b2a52c6ca87757a4bccc24de")); // reverse hash + coin.prevIndex = (uint16_t)1; transaction.inInputs.push_back(coin); { TransactionOutput out; out.assetId = load(parse_hex("9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5")); - out.value = (int64_t) 1 * 100000000; + out.value = (int64_t)1 * 100000000; auto scriptHash = TW::NEO::Address("Ad9A1xPbuA5YBFr1XPznDwBwQzdckAjCev").toScriptHash(); out.scriptHash = load(scriptHash); transaction.outputs.push_back(out); @@ -73,7 +73,7 @@ TEST(NEOSigner, SigningTransaction) { { TransactionOutput out; out.assetId = load(parse_hex("9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5")); - out.value = (int64_t) 892 * 100000000; + out.value = (int64_t)892 * 100000000; auto scriptHash = TW::NEO::Address("AdtSLMBqACP4jv8tRWwyweXGpyGG46eMXV").toScriptHash(); out.scriptHash = load(scriptHash); transaction.outputs.push_back(out); @@ -82,3 +82,5 @@ TEST(NEOSigner, SigningTransaction) { auto signedTx = transaction.serialize(); EXPECT_EQ(hex(signedTx), "800000019c85b39cd5677e2bfd6bf8a711e8da93a2f1d172b2a52c6ca87757a4bccc24de0100029b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500e1f50500000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500fcbbc414000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac0141405046619c8e20e1fdeec92ce95f3019f6e7cc057294eb16b2d5e55c105bf32eb27e1fc01c1858576228f1fef8c0945a8ad69688e52a4ed19f5b85f5eff7e961d7232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"); } + +} // namespace TW::NEO::tests diff --git a/tests/NEO/TWAnySignerTests.cpp b/tests/chains/NEO/TWAnySignerTests.cpp similarity index 97% rename from tests/NEO/TWAnySignerTests.cpp rename to tests/chains/NEO/TWAnySignerTests.cpp index 3bead1f67e6..9a92c1133c0 100644 --- a/tests/NEO/TWAnySignerTests.cpp +++ b/tests/chains/NEO/TWAnySignerTests.cpp @@ -1,18 +1,17 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include "HexCoding.h" #include "proto/NEO.pb.h" #include -using namespace TW; -using namespace TW::NEO; +namespace TW::NEO::tests { Proto::SigningInput createInput() { const std::string NEO_ASSET_ID = "9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5"; @@ -21,11 +20,11 @@ Proto::SigningInput createInput() { Proto::SigningInput input; auto privateKey = parse_hex("F18B2F726000E86B4950EBEA7BFF151F69635951BC4A31C44F28EE6AF7AEC128"); input.set_private_key(privateKey.data(), privateKey.size()); - input.set_fee(12345); //too low + input.set_fee(12345); // too low input.set_gas_asset_id(GAS_ASSET_ID); input.set_gas_change_address("AdtSLMBqACP4jv8tRWwyweXGpyGG46eMXV"); -#define ADD_UTXO_INPUT(hash, index , value, assetId) \ +#define ADD_UTXO_INPUT(hash, index, value, assetId) \ { \ auto utxo = input.add_inputs(); \ utxo->set_prev_hash(parse_hex(hash).data(), parse_hex(hash).size()); \ @@ -101,3 +100,5 @@ TEST(TWAnySignerNEO, Plan) { EXPECT_EQ(plan.fee(), 1408000); EXPECT_EQ(plan.error(), Common::Proto::OK); } + +} // namespace TW::NEO::tests diff --git a/tests/NEO/TWCoinTypeTests.cpp b/tests/chains/NEO/TWCoinTypeTests.cpp similarity index 97% rename from tests/NEO/TWCoinTypeTests.cpp rename to tests/chains/NEO/TWCoinTypeTests.cpp index 8b986b73a4d..fa87f98119b 100644 --- a/tests/NEO/TWCoinTypeTests.cpp +++ b/tests/chains/NEO/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/NEO/TWNEOAddressTests.cpp b/tests/chains/NEO/TWNEOAddressTests.cpp similarity index 96% rename from tests/NEO/TWNEOAddressTests.cpp rename to tests/chains/NEO/TWNEOAddressTests.cpp index 029f3bdb95b..cc36db2af5f 100644 --- a/tests/NEO/TWNEOAddressTests.cpp +++ b/tests/chains/NEO/TWNEOAddressTests.cpp @@ -9,7 +9,7 @@ #include #include "HexCoding.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include using namespace TW; diff --git a/tests/NEO/TransactionAttributeTests.cpp b/tests/chains/NEO/TransactionAttributeTests.cpp similarity index 72% rename from tests/NEO/TransactionAttributeTests.cpp rename to tests/chains/NEO/TransactionAttributeTests.cpp index b99ca38141b..73b64b14554 100644 --- a/tests/NEO/TransactionAttributeTests.cpp +++ b/tests/chains/NEO/TransactionAttributeTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,44 +9,44 @@ #include "NEO/ReadData.h" #include "NEO/TransactionAttribute.h" #include "NEO/TransactionAttributeUsage.h" - -#include #include +namespace TW::NEO::tests { + using namespace std; -using namespace TW; -using namespace TW::NEO; TEST(NEOTransactionAttribute, Serialize) { auto transactionAttribute = TransactionAttribute(); string data = "bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a"; transactionAttribute.usage = TransactionAttributeUsage::TAU_ContractHash; - transactionAttribute.data = parse_hex(data); + transactionAttribute._data = parse_hex(data); EXPECT_EQ("00" + data, hex(transactionAttribute.serialize())); data = "bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4b"; transactionAttribute.usage = TransactionAttributeUsage::TAU_Vote; - transactionAttribute.data = parse_hex(data); + transactionAttribute._data = parse_hex(data); EXPECT_EQ("30" + data, hex(transactionAttribute.serialize())); transactionAttribute.usage = TransactionAttributeUsage::TAU_ECDH02; - transactionAttribute.data = parse_hex(data); + transactionAttribute._data = {(TW::byte)transactionAttribute.usage}; + auto d = parse_hex(data); + transactionAttribute._data.insert(transactionAttribute._data.end(), d.begin(), d.end()); EXPECT_EQ("02" + data, hex(transactionAttribute.serialize())); data = "bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af"; transactionAttribute.usage = TransactionAttributeUsage::TAU_Script; - transactionAttribute.data = parse_hex(data); + transactionAttribute._data = parse_hex(data); EXPECT_EQ("20" + data, hex(transactionAttribute.serialize())); data = "bd"; transactionAttribute.usage = TransactionAttributeUsage::TAU_DescriptionUrl; - transactionAttribute.data = parse_hex(data); - EXPECT_EQ("81" + data, hex(transactionAttribute.serialize())); + transactionAttribute._data = parse_hex(data); + EXPECT_EQ("8101" + data, hex(transactionAttribute.serialize())); data = "bdecbb623eee6f9ade28d5a8ff5fb3ea"; transactionAttribute.usage = TransactionAttributeUsage::TAU_Remark; - transactionAttribute.data = parse_hex(data); - EXPECT_EQ("f0" + data, hex(transactionAttribute.serialize())); + transactionAttribute._data = parse_hex(data); + EXPECT_EQ("f010" + data, hex(transactionAttribute.serialize())); } TEST(NEOTransactionAttribute, Deserialize) { @@ -54,31 +54,31 @@ TEST(NEOTransactionAttribute, Deserialize) { string data = "bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a"; transactionAttribute.deserialize(parse_hex("00" + data)); EXPECT_EQ(TransactionAttributeUsage::TAU_ContractHash, transactionAttribute.usage); - EXPECT_EQ(data, hex(transactionAttribute.data)); + EXPECT_EQ(data, hex(transactionAttribute._data)); data = "bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4b"; transactionAttribute.deserialize(parse_hex("30" + data)); EXPECT_EQ(TransactionAttributeUsage::TAU_Vote, transactionAttribute.usage); - EXPECT_EQ(data, hex(transactionAttribute.data)); + EXPECT_EQ(data, hex(transactionAttribute._data)); transactionAttribute.deserialize(parse_hex("02" + data)); EXPECT_EQ(TransactionAttributeUsage::TAU_ECDH02, transactionAttribute.usage); - EXPECT_EQ(data, hex(transactionAttribute.data)); + EXPECT_EQ("02" + data, hex(transactionAttribute._data)); data = "bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af"; transactionAttribute.deserialize(parse_hex("20" + data)); EXPECT_EQ(TransactionAttributeUsage::TAU_Script, transactionAttribute.usage); - EXPECT_EQ(data, hex(transactionAttribute.data)); + EXPECT_EQ(data, hex(transactionAttribute._data)); data = "bd"; - transactionAttribute.deserialize(parse_hex("81" + data)); + transactionAttribute.deserialize(parse_hex("8101" + data)); EXPECT_EQ(TransactionAttributeUsage::TAU_DescriptionUrl, transactionAttribute.usage); - EXPECT_EQ(data, hex(transactionAttribute.data)); + EXPECT_EQ(data, hex(transactionAttribute._data)); data = "bdecbb623eee6f9ade28d5a8ff5fb3ea"; - transactionAttribute.deserialize(parse_hex("f0" + data)); + transactionAttribute.deserialize(parse_hex("f010" + data)); EXPECT_EQ(TransactionAttributeUsage::TAU_Remark, transactionAttribute.usage); - EXPECT_EQ(data, hex(transactionAttribute.data)); + EXPECT_EQ(data, hex(transactionAttribute._data)); EXPECT_THROW(transactionAttribute.deserialize(parse_hex("b1" + data)), std::invalid_argument); } @@ -86,6 +86,8 @@ TEST(NEOTransactionAttribute, Deserialize) { TEST(NEOTransactionAttribute, DeserializeInitialPositionAfterData) { auto transactionAttribute = TransactionAttribute(); EXPECT_THROW(transactionAttribute.deserialize(Data(), 1), std::invalid_argument); - EXPECT_THROW(transactionAttribute.deserialize(Data({1}), 2), std::invalid_argument); } + + +} // namespace TW::NEO::tests diff --git a/tests/NEO/TransactionOutputTests.cpp b/tests/chains/NEO/TransactionOutputTests.cpp similarity index 94% rename from tests/NEO/TransactionOutputTests.cpp rename to tests/chains/NEO/TransactionOutputTests.cpp index 5c695cd9aab..84753ba029a 100644 --- a/tests/NEO/TransactionOutputTests.cpp +++ b/tests/chains/NEO/TransactionOutputTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -7,13 +7,11 @@ #include "uint256.h" #include "HexCoding.h" #include "NEO/TransactionOutput.h" - -#include #include +namespace TW::NEO::tests { + using namespace std; -using namespace TW; -using namespace TW::NEO; TEST(NEOTransactionOutput, Serialize) { auto transactionOutput = TransactionOutput(); @@ -42,3 +40,5 @@ TEST(NEOTransactionOutput, Deserialize) { EXPECT_EQ(assetId, hex(store(transactionOutput.assetId))); EXPECT_EQ(scriptHash, hex(store(transactionOutput.scriptHash))); } + +} // namespace TW::NEO::tests diff --git a/tests/NEO/TransactionTests.cpp b/tests/chains/NEO/TransactionTests.cpp similarity index 93% rename from tests/NEO/TransactionTests.cpp rename to tests/chains/NEO/TransactionTests.cpp index 1c0aa39ecf5..fc973084e90 100644 --- a/tests/NEO/TransactionTests.cpp +++ b/tests/chains/NEO/TransactionTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -10,23 +10,21 @@ #include "NEO/TransactionType.h" #include "NEO/TransactionAttributeUsage.h" #include "NEO/TransactionAttribute.h" - -#include #include +namespace TW::NEO::tests { + using namespace std; -using namespace TW; -using namespace TW::NEO; TEST(NEOTransaction, SerializeDeserializeEmpty) { auto transaction = Transaction(); EXPECT_EQ(transaction, transaction); - EXPECT_EQ(0, transaction.attributes.size()); - EXPECT_EQ(0, transaction.inInputs.size()); - EXPECT_EQ(0, transaction.outputs.size()); + EXPECT_EQ(0ul, transaction.attributes.size()); + EXPECT_EQ(0ul, transaction.inInputs.size()); + EXPECT_EQ(0ul, transaction.outputs.size()); auto serialized = transaction.serialize(); - + auto deserializedTransaction = Transaction(); deserializedTransaction.deserialize(serialized); EXPECT_EQ(transaction, deserializedTransaction); @@ -54,7 +52,7 @@ TEST(NEOTransaction, SerializeDeserializeAttribute) { const string oneVarLong = "01"; transaction.attributes.push_back(TransactionAttribute()); transaction.attributes[0].usage = TransactionAttributeUsage::TAU_ContractHash; - transaction.attributes[0].data = parse_hex("bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a"); + transaction.attributes[0]._data = parse_hex("bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a"); auto serialized = transaction.serialize(); EXPECT_EQ("8007" + oneVarLong + hex(transaction.attributes[0].serialize()) + zeroVarLong + zeroVarLong, hex(serialized)); @@ -64,7 +62,7 @@ TEST(NEOTransaction, SerializeDeserializeAttribute) { transaction.attributes.push_back(TransactionAttribute()); transaction.attributes[1].usage = TransactionAttributeUsage::TAU_ECDH02; - transaction.attributes[1].data = parse_hex("b7ecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a"); + transaction.attributes[1]._data = parse_hex("02b7ecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a"); serialized = transaction.serialize(); const string twoVarLong = "02"; string expectedSerialized = "8007" + twoVarLong; @@ -150,7 +148,7 @@ TEST(NEOTransaction, SerializeDeserialize) { transaction.attributes.push_back(TransactionAttribute()); transaction.attributes[0].usage = TransactionAttributeUsage::TAU_ContractHash; - transaction.attributes[0].data = parse_hex("bdecbb623eee6f9ade28d5a8ff5fbdea9c9d73af039e0286201b3b0291fb4d4a"); + transaction.attributes[0]._data = parse_hex("bdecbb623eee6f9ade28d5a8ff5fbdea9c9d73af039e0286201b3b0291fb4d4a"); transaction.inInputs.push_back(CoinReference()); transaction.inInputs[0].prevHash = load(parse_hex("bdecbb623eee679ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a")); @@ -220,9 +218,8 @@ TEST(NEOTransaction, SerializeDeserializeMiner) { string notMiner = "1000d11f7a2800000000"; EXPECT_THROW( - std::unique_ptr deserializedTransaction(Transaction::deserializeFrom(parse_hex(notMiner))), - std::invalid_argument - ); + std::unique_ptr _deserializedTransaction(Transaction::deserializeFrom(parse_hex(notMiner))), + std::invalid_argument); } TEST(NEOTransaction, GetHash) { @@ -246,5 +243,7 @@ TEST(NEOTransaction, SerializeSize) { EXPECT_EQ(hex(verSerialized), hex(serialized)); EXPECT_EQ(verSerialized, serialized); - EXPECT_EQ(serialized.size(), transaction.size()); + EXPECT_EQ(serialized.size(), static_cast(transaction.size())); } + +} // namespace TW::NEO::tests diff --git a/tests/NEO/WitnessTests.cpp b/tests/chains/NEO/WitnessTests.cpp similarity index 96% rename from tests/NEO/WitnessTests.cpp rename to tests/chains/NEO/WitnessTests.cpp index ff2a579846b..7bd68fda171 100644 --- a/tests/NEO/WitnessTests.cpp +++ b/tests/chains/NEO/WitnessTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -11,9 +11,9 @@ #include +namespace TW::NEO::tests { + using namespace std; -using namespace TW; -using namespace TW::NEO; TEST(NEOWitness, Serialize) { auto witness = Witness(); @@ -62,3 +62,5 @@ TEST(NEOWitness, SerializeDeserialize) { deWitness.deserialize(witness.serialize()); EXPECT_EQ(witness, deWitness); } + +} // namespace TW::NEO::tests diff --git a/tests/NULS/AddressTests.cpp b/tests/chains/NULS/AddressTests.cpp similarity index 95% rename from tests/NULS/AddressTests.cpp rename to tests/chains/NULS/AddressTests.cpp index 9adba2e43e9..0082fac96fc 100644 --- a/tests/NULS/AddressTests.cpp +++ b/tests/chains/NULS/AddressTests.cpp @@ -1,24 +1,22 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "NULS/Address.h" +#include "NULS/Address.h" #include "HexCoding.h" #include "PrivateKey.h" #include -using namespace TW; -using namespace TW::NULS; - +namespace TW::NULS::tests { TEST(NULSAddress, StaticInvalid) { ASSERT_FALSE(Address::isValid("abc")); ASSERT_FALSE(Address::isValid("aaeb60f3e94c9b9a09f33669435e7ef1beaed")); ASSERT_FALSE(Address::isValid("NULSd6HgbwcM8wz48f6UkFYHLVriT1L81X9z")); ASSERT_TRUE(Address::isValid("NULSd6HgUxmcJWc88iELEJ7RH9XHsazBQqnJc")); - ASSERT_TRUE(Address::isValid("NULSd6HgbwcM8wz48f6UkFYHLVriT1L81X9z2")); + ASSERT_TRUE(Address::isValid("NULSd6HgbwcM8wz48f6UkFYHLVriT1L81X9z2")); } TEST(NULSAddress, ChainID) { @@ -60,3 +58,5 @@ TEST(NULSAddress, FromPrivateKey33) { ASSERT_EQ(address.string(), "NULSd6HgXx8YkwEjePLWUmdRSZzPQzK6BXnsB"); } + +} // namespace TW::NULS::tests diff --git a/tests/NULS/TWAnySignerTests.cpp b/tests/chains/NULS/TWAnySignerTests.cpp similarity index 93% rename from tests/NULS/TWAnySignerTests.cpp rename to tests/chains/NULS/TWAnySignerTests.cpp index bfab5b9177b..749de87342b 100644 --- a/tests/NULS/TWAnySignerTests.cpp +++ b/tests/chains/NULS/TWAnySignerTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,11 +9,10 @@ #include #include "uint256.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include -using namespace TW; -using namespace TW::NULS; +namespace TW::NULS::tests { TEST(TWAnySignerNULS, Sign) { auto privateKey = parse_hex("0x9ce21dad67e0f0af2599b41b515a7f7018059418bab892a7b68f283d489abc4b"); @@ -37,3 +36,5 @@ TEST(TWAnySignerNULS, Sign) { EXPECT_EQ(hex(output.encoded()), "0200f885885d00008c0117010001f7ec6473df12e751d64cf20a8baa7edd50810f8101000100201d9a0000000000000000000000000000000000000000000000000000000000080000000000000000000117010001f05e7878971f3374515eabb6f16d75219d8873120100010080969800000000000000000000000000000000000000000000000000000000000000000000000000692103958b790c331954ed367d37bac901de5c2f06ac8368b37d7bd6cd5ae143c1d7e3463044022028019c0099e2233c7adb84bb03a9a5666ece4a5b65a026a090fa460f3679654702204df0fcb8762b5944b3aba033fa1a287ccb098150035dd8b66f52dc58d3d0843a"); } + +} // namespace TW::NULS::tests diff --git a/tests/NULS/TWCoinTypeTests.cpp b/tests/chains/NULS/TWCoinTypeTests.cpp similarity index 97% rename from tests/NULS/TWCoinTypeTests.cpp rename to tests/chains/NULS/TWCoinTypeTests.cpp index 3d048c09faf..5618222dcb2 100644 --- a/tests/NULS/TWCoinTypeTests.cpp +++ b/tests/chains/NULS/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Nano/AddressTests.cpp b/tests/chains/Nano/AddressTests.cpp similarity index 97% rename from tests/Nano/AddressTests.cpp rename to tests/chains/Nano/AddressTests.cpp index 8812a0c152b..2c3435a11ee 100644 --- a/tests/Nano/AddressTests.cpp +++ b/tests/chains/Nano/AddressTests.cpp @@ -5,14 +5,15 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "Nano/Address.h" #include "HexCoding.h" +#include "Nano/Address.h" #include using namespace std; using namespace TW; -using namespace TW::Nano; + +namespace TW::Nano::tests { TEST(NanoAddress, FromPublicKey) { { @@ -53,3 +54,5 @@ TEST(NanoAddress, isValid) { ASSERT_FALSE(Address::isValid(faultyChecksumAddress)); ASSERT_FALSE(Address::isValid(bitcoinAddress)); } + +} // namespace TW::Nano::tests diff --git a/tests/Nano/SignerTests.cpp b/tests/chains/Nano/SignerTests.cpp similarity index 99% rename from tests/Nano/SignerTests.cpp rename to tests/chains/Nano/SignerTests.cpp index 30dd6845de7..966198c3bc0 100644 --- a/tests/Nano/SignerTests.cpp +++ b/tests/chains/Nano/SignerTests.cpp @@ -5,13 +5,14 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "Nano/Signer.h" #include "HexCoding.h" +#include "Nano/Signer.h" #include using namespace TW; -using namespace TW::Nano; + +namespace TW::Nano::tests { const std::string kPrivateKey{"173c40e97fe2afcd24187e74f6b603cb949a5365e72fbdd065a6b165e2189e34"}; const std::string kRepOfficial1{"xrb_3arg3asgtigae3xckabaaewkx3bzsh7nwz7jkmjos79ihyaxwphhm6qgjps4"}; @@ -205,3 +206,5 @@ TEST(NanoSigner, signInvalid7) { ASSERT_THROW(Signer signer(input), std::invalid_argument); } + +} // namespace TW::Nano::tests diff --git a/tests/Nano/TWAnySignerTests.cpp b/tests/chains/Nano/TWAnySignerTests.cpp similarity index 97% rename from tests/Nano/TWAnySignerTests.cpp rename to tests/chains/Nano/TWAnySignerTests.cpp index 54638c40b5b..aa76abc83f5 100644 --- a/tests/Nano/TWAnySignerTests.cpp +++ b/tests/chains/Nano/TWAnySignerTests.cpp @@ -7,12 +7,13 @@ #include "HexCoding.h" #include "proto/Nano.pb.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include using namespace TW; -using namespace TW::Nano; + +namespace TW::Nano::tests { TEST(TWAnySignerNano, sign) { const auto privateKey = parse_hex("173c40e97fe2afcd24187e74f6b603cb949a5365e72fbdd065a6b165e2189e34"); @@ -49,3 +50,5 @@ TEST(TWAnySignerNano, SignJSON) { ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeNano)); assertStringsEqual(result, R"({"account":"nano_1bhbsc9yuh15anq3owu1izw1nk7bhhqefrkhfo954fyt8dk1q911buk1kk4c","balance":"96242336390000000000000000000","link":"491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507","link_as_account":"nano_1kazsap8mc481zbqbcqjytpf9mmigj87qr5k5fhf97579t4k8fa94octjx6d","previous":"0000000000000000000000000000000000000000000000000000000000000000","representative":"nano_3arg3asgtigae3xckabaaewkx3bzsh7nwz7jkmjos79ihyaxwphhm6qgjps4","signature":"d247f6b90383b24e612569c75a12f11242f6e03b4914eadc7d941577dcf54a3a7cb7f0a4aba4246a40d9ebb5ee1e00b4a0a834ad5a1e7bef24e11f62b95a9e09","type":"state"})"); } + +} // namespace TW::Nano::tests diff --git a/tests/Nano/TWCoinTypeTests.cpp b/tests/chains/Nano/TWCoinTypeTests.cpp similarity index 95% rename from tests/Nano/TWCoinTypeTests.cpp rename to tests/chains/Nano/TWCoinTypeTests.cpp index 1a7ac8ab7af..c6458843538 100644 --- a/tests/Nano/TWCoinTypeTests.cpp +++ b/tests/chains/Nano/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -26,7 +26,7 @@ TEST(TWNanoCoinType, TWCoinType) { ASSERT_EQ(TWBlockchainNano, TWCoinTypeBlockchain(TWCoinTypeNano)); ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeNano)); ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeNano)); - assertStringsEqual(symbol, "NANO"); + assertStringsEqual(symbol, "XNO"); assertStringsEqual(txUrl, "https://nanocrawler.cc/explorer/block/C264DB7BF40738F0CEFF19B606746CB925B713E4B8699A055699E0DC8ABBC70F"); assertStringsEqual(accUrl, "https://nanocrawler.cc/explorer/account/nano_1wpj616kwhe1y38y1mspd8aub8i334cwybqco511iyuxm55zx8d67ptf1tsf"); assertStringsEqual(id, "nano"); diff --git a/tests/Nano/TWNanoAddressTests.cpp b/tests/chains/Nano/TWNanoAddressTests.cpp similarity index 97% rename from tests/Nano/TWNanoAddressTests.cpp rename to tests/chains/Nano/TWNanoAddressTests.cpp index afcd1587cf4..44d653119aa 100644 --- a/tests/Nano/TWNanoAddressTests.cpp +++ b/tests/chains/Nano/TWNanoAddressTests.cpp @@ -5,7 +5,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include "HexCoding.h" #include "PrivateKey.h" #include "PublicKey.h" diff --git a/tests/chains/NativeEvmos/TWAnyAddressTests.cpp b/tests/chains/NativeEvmos/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..ed2a02cd50f --- /dev/null +++ b/tests/chains/NativeEvmos/TWAnyAddressTests.cpp @@ -0,0 +1,39 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "TestUtilities.h" + +#include +#include + +#include + +namespace TW::NativeEvmos::tests { + +TEST(EvmosAnyAddress, NativeEvmosValidate) { + auto string = STRING("evmos1mwspdc5y6lj0glm90j9sg7vmr5ksmqcnzhg0h9"); + + EXPECT_TRUE(TWAnyAddressIsValid(string.get(), TWCoinTypeNativeEvmos)); + + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeNativeEvmos)); + + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "dba016e284d7e4f47f657c8b04799b1d2d0d8313"); +} + +TEST(EvmosAnyAddress, NativeEvmosCreate) { + auto publicKeyHex = "035a0c6b83b8bd9827e507270cadb499b7e3a9095246f6a2213281f783d877c98b"; // shoot island position ... + const auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA(publicKeyHex).get(), TWPublicKeyTypeSECP256k1)); + + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeNativeEvmos)); + + EXPECT_EQ(std::string(TWStringUTF8Bytes(WRAPS(TWAnyAddressDescription(addr.get())).get())), std::string("evmos1mwspdc5y6lj0glm90j9sg7vmr5ksmqcnzhg0h9")); + + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "dba016e284d7e4f47f657c8b04799b1d2d0d8313"); +} + +} // namespace TW::NativeEvmos::tests diff --git a/tests/chains/NativeEvmos/TWCoinTypeTests.cpp b/tests/chains/NativeEvmos/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..6df42eaa394 --- /dev/null +++ b/tests/chains/NativeEvmos/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "TestUtilities.h" +#include +#include + +namespace TW::NativeEvmos::tests { + +TEST(TWEvmosCoinType, TWCoinTypeNativeEvmos) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeNativeEvmos)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("A16C211C83AD1E684DE46F694FAAC17D8465C864BD7385A81EC062CDE0638811")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeNativeEvmos, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeNativeEvmos, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeNativeEvmos)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeNativeEvmos)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeNativeEvmos), 18); + ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeNativeEvmos)); + + assertStringsEqual(symbol, "EVMOS"); + assertStringsEqual(txUrl, "https://mintscan.io/evmos/txs/A16C211C83AD1E684DE46F694FAAC17D8465C864BD7385A81EC062CDE0638811"); + assertStringsEqual(accUrl, "https://mintscan.io/evmos/account/evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34"); + assertStringsEqual(id, "nativeevmos"); + assertStringsEqual(name, "Native Evmos"); +} + +} // namespace TW::NativeEvmos::tests diff --git a/tests/Nebulas/AddressTests.cpp b/tests/chains/Nebulas/AddressTests.cpp similarity index 87% rename from tests/Nebulas/AddressTests.cpp rename to tests/chains/Nebulas/AddressTests.cpp index 3cca8382bb1..45a70df9a09 100644 --- a/tests/Nebulas/AddressTests.cpp +++ b/tests/chains/Nebulas/AddressTests.cpp @@ -1,19 +1,17 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. #include "Nebulas/Address.h" -#include "../src/Base58.h" #include "HexCoding.h" #include "PrivateKey.h" - #include +namespace TW::Nebulas::tests { + using namespace std; -using namespace TW; -using namespace TW::Nebulas; TEST(NebulasAddress, Invalid) { ASSERT_FALSE(Address::isValid("abc")); @@ -28,11 +26,10 @@ TEST(NebulasAddress, Invalid) { TEST(NebulasAddress, String) { ASSERT_THROW(Address("abc"), std::invalid_argument); ASSERT_EQ(Address("n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY").string(), - "n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); + "n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); ASSERT_EQ(Address(Base58::bitcoin.decode("n1TgpFZWCMmFd2sphb6RKsCvsEyMCNa2Yyv")).string(), - "n1TgpFZWCMmFd2sphb6RKsCvsEyMCNa2Yyv" - ); - + "n1TgpFZWCMmFd2sphb6RKsCvsEyMCNa2Yyv"); + const auto address = Address("n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); ASSERT_EQ(address.string(), "n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); } @@ -41,7 +38,7 @@ TEST(NebulasAddress, Data) { Data data; EXPECT_THROW(Address(data).string(), std::invalid_argument); ASSERT_EQ(Address(Base58::bitcoin.decode("n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY")).string(), - "n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); + "n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); } TEST(NebulasAddress, FromPrivateKey) { @@ -52,3 +49,5 @@ TEST(NebulasAddress, FromPrivateKey) { EXPECT_THROW(Address(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1)), std::invalid_argument); } + +} // namespace TW::Nebulas::tests diff --git a/tests/Nebulas/SignerTests.cpp b/tests/chains/Nebulas/SignerTests.cpp similarity index 100% rename from tests/Nebulas/SignerTests.cpp rename to tests/chains/Nebulas/SignerTests.cpp diff --git a/tests/Nebulas/TWAnySignerTests.cpp b/tests/chains/Nebulas/TWAnySignerTests.cpp similarity index 80% rename from tests/Nebulas/TWAnySignerTests.cpp rename to tests/chains/Nebulas/TWAnySignerTests.cpp index 69008c4d8a6..c850acf6935 100644 --- a/tests/Nebulas/TWAnySignerTests.cpp +++ b/tests/chains/Nebulas/TWAnySignerTests.cpp @@ -1,10 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include "HexCoding.h" #include "uint256.h" @@ -12,33 +12,34 @@ #include -using namespace TW; -using namespace TW::Nebulas; +namespace TW::Nebulas::tests { TEST(TWAnySignerNebulas, Sign) { Proto::SigningInput input; input.set_from_address("n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); input.set_to_address("n1SAeQRVn33bamxN4ehWUT7JGdxipwn8b17"); auto value = store(uint256_t(7)); - input.set_nonce(value.data(),value.size()); + input.set_nonce(value.data(), value.size()); value = store(uint256_t(1000000)); - input.set_gas_price(value.data(),value.size()); + input.set_gas_price(value.data(), value.size()); value = store(uint256_t(200000)); - input.set_gas_limit(value.data(),value.size()); + input.set_gas_limit(value.data(), value.size()); value = store(uint256_t(11000000000000000000ULL)); - input.set_amount(value.data(),value.size()); + input.set_amount(value.data(), value.size()); input.set_payload(""); value = store(uint256_t(1560052938)); - input.set_timestamp(value.data(),value.size()); + input.set_timestamp(value.data(), value.size()); const auto privateKey = parse_hex("d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b"); input.set_private_key(privateKey.data(), privateKey.size()); auto chainid = store(uint256_t(1)); input.set_chain_id(chainid.data(), chainid.size()); - + Proto::SigningOutput output; ANY_SIGN(input, TWCoinTypeNebulas); EXPECT_EQ(hex(output.signature()), "f53f4a9141ff8e462b094138eccd8c3a5d7865f9e9ab509626c78460a9e0b0fc35f7ed5ba1795ceb81a5e46b7580a6f7fb431d44fdba92515399cf6a8e47e71500"); EXPECT_EQ(output.raw(), "CiBQXdR2neMqnEu21q/U+OHqZHSBX9Q0hNiRfL2eCZO4hRIaGVefwtw23wEobqA40/7aIwQHghETxH4r+50aGhlXf89CeLWgHFjKu9/6tn4KNbelsMDAIIi2IhAAAAAAAAAAAJin2bgxTAAAKAcwyony5wU6CAoGYmluYXJ5QAFKEAAAAAAAAAAAAAAAAAAPQkBSEAAAAAAAAAAAAAAAAAADDUBYAWJB9T9KkUH/jkYrCUE47M2MOl14Zfnpq1CWJseEYKngsPw19+1boXlc64Gl5Gt1gKb3+0MdRP26klFTmc9qjkfnFQA="); } + +} // namespace TW::Nebulas::tests diff --git a/tests/Nebulas/TWCoinTypeTests.cpp b/tests/chains/Nebulas/TWCoinTypeTests.cpp similarity index 97% rename from tests/Nebulas/TWCoinTypeTests.cpp rename to tests/chains/Nebulas/TWCoinTypeTests.cpp index c3eac69cffa..d0f30bcc619 100644 --- a/tests/Nebulas/TWCoinTypeTests.cpp +++ b/tests/chains/Nebulas/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Nebulas/TWNebulasAddressTests.cpp b/tests/chains/Nebulas/TWNebulasAddressTests.cpp similarity index 98% rename from tests/Nebulas/TWNebulasAddressTests.cpp rename to tests/chains/Nebulas/TWNebulasAddressTests.cpp index 32e7ae08fa4..50a2cdcea9b 100644 --- a/tests/Nebulas/TWNebulasAddressTests.cpp +++ b/tests/chains/Nebulas/TWNebulasAddressTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Nebulas/TransactionTests.cpp b/tests/chains/Nebulas/TransactionTests.cpp similarity index 94% rename from tests/Nebulas/TransactionTests.cpp rename to tests/chains/Nebulas/TransactionTests.cpp index 2774e02d623..0651403fd2a 100644 --- a/tests/Nebulas/TransactionTests.cpp +++ b/tests/chains/Nebulas/TransactionTests.cpp @@ -1,6 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -13,12 +11,12 @@ #include -using namespace std; -using namespace TW; -using namespace TW::Nebulas; - extern std::string htmlescape(const std::string& str); +namespace TW::Nebulas::tests { + +using namespace std; + TEST(NebulasTransaction, serialize) { auto from = Address("n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); auto to = Address("n1SAeQRVn33bamxN4ehWUT7JGdxipwn8b17"); @@ -36,7 +34,7 @@ TEST(NebulasTransaction, serialize) { auto signer = Signer(1); signer.sign(privateKey, transaction); transaction.serializeToRaw(); - + ASSERT_EQ(TW::Base64::encode(transaction.raw), "CiBQXdR2neMqnEu21q/U+OHqZHSBX9Q0hNiRfL2eCZO4hRIaGVefwtw23wEobqA40/7aIwQHghETxH4r+50aGhlXf89CeLWgHFjKu9/6tn4KNbelsMDAIIi2IhAAAAAAAAAAAJin2bgxTAAAKAcwyony5wU6CAoGYmluYXJ5QAFKEAAAAAAAAAAAAAAAAAAPQkBSEAAAAAAAAAAAAAAAAAADDUBYAWJB9T9KkUH/jkYrCUE47M2MOl14Zfnpq1CWJseEYKngsPw19+1boXlc64Gl5Gt1gKb3+0MdRP26klFTmc9qjkfnFQA="); } @@ -79,5 +77,7 @@ TEST(NebulasTransaction, serializeUnsigned) { /* timestamp: */ 1560052938, /* payload: */ std::string()); - ASSERT_THROW(transaction.serializeToRaw(),std::logic_error); -} \ No newline at end of file + ASSERT_THROW(transaction.serializeToRaw(), std::logic_error); +} + +} // namespace TW::Nebulas::tests diff --git a/tests/chains/Nervos/AddressTests.cpp b/tests/chains/Nervos/AddressTests.cpp new file mode 100644 index 00000000000..15fb9cadfe0 --- /dev/null +++ b/tests/chains/Nervos/AddressTests.cpp @@ -0,0 +1,88 @@ +// Copyright © 2017-2021 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 "HDWallet.h" +#include "HexCoding.h" +#include "Nervos/Address.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include +#include + +namespace TW::Nervos::tests { + +TEST(NervosAddress, Valid) { + ASSERT_TRUE(Address::isValid("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9e" + "rg8furras980hksatlslfaktks7epf25")); + ASSERT_TRUE(Address::isValid("ckb1qyqvfdgvtjxswncx8mq2wl0dp6hlp7nmvhdqcecnt6")); + ASSERT_TRUE(Address::isValid("ckb1qjda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xw3394p3wg6" + "p60qclvpfmaa582lu860dja5h0fk0v")); +} + +TEST(NervosAddress, Invalid) { + ASSERT_FALSE(Address::isValid("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9" + "erg8furras980hksatlslfaktks7epf26")); + ASSERT_FALSE(Address::isValid("ckb1qyqvfdgvtjxswncx8mq2wl0dp6hlp7nmvhdqcecnt7")); + ASSERT_FALSE(Address::isValid("ckb1qjda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xw3394p3wg" + "6p60qclvpfmaa582lu860dja5h0fk0w")); +} + +TEST(NervosAddress, FromPrivateKey) { + auto privateKey = + PrivateKey(parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb")); + auto address = Address(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); + ASSERT_EQ(address.string(), "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9er" + "g8furras980hksatlslfaktks7epf25"); +} + +TEST(NervosAddress, FromPublicKey) { + auto publicKey = + PublicKey(parse_hex("026c9e4cbb95d4b3a123c1fc80795feacc38029683a1b3e16bccf49bba25fb2858"), + TWPublicKeyTypeSECP256k1); + auto address = Address(publicKey); + ASSERT_EQ(address.string(), "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9er" + "g8furras980hksatlslfaktks7epf25"); +} + +TEST(NervosAddress, FromString) { + auto address1 = Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8fu" + "rras980hksatlslfaktks7epf25"); + ASSERT_EQ(address1.string(), "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9e" + "rg8furras980hksatlslfaktks7epf25"); + auto address2 = Address("ckb1qyqvfdgvtjxswncx8mq2wl0dp6hlp7nmvhdqcecnt6"); + ASSERT_EQ(address2.string(), "ckb1qyqvfdgvtjxswncx8mq2wl0dp6hlp7nmvhdqcecnt6"); + auto address3 = Address("ckb1qjda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xw3394p3wg6p60qc" + "lvpfmaa582lu860dja5h0fk0v"); + ASSERT_EQ(address3.string(), "ckb1qjda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xw3394p3wg6" + "p60qclvpfmaa582lu860dja5h0fk0v"); +} + +TEST(TWNervosAddress, AddressFromPublicKey) { + auto privateKey = + PrivateKey(parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb")); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + ASSERT_EQ(publicKey.bytes.size(), 33ul); + auto address = Address(publicKey); + ASSERT_EQ(address.string(), "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9er" + "g8furras980hksatlslfaktks7epf25"); +} + +TEST(NervosAddress, AddressFromString) { + auto address = Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8fur" + "ras980hksatlslfaktks7epf25"); + ASSERT_EQ(address.string(), "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9er" + "g8furras980hksatlslfaktks7epf25"); +} + +TEST(NervosAddress, AddressFromWallet) { + auto hdWallet = HDWallet( + "alpha draw toss question picnic endless recycle wrong enable roast success palm", ""); + auto addressString = hdWallet.deriveAddress(TWCoinTypeNervos); + ASSERT_EQ(addressString, "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8f" + "urras980hksatlslfaktks7epf25"); +} + +} // namespace TW::Nervos::tests diff --git a/tests/chains/Nervos/SignerTests.cpp b/tests/chains/Nervos/SignerTests.cpp new file mode 100644 index 00000000000..1b8bdfefeb6 --- /dev/null +++ b/tests/chains/Nervos/SignerTests.cpp @@ -0,0 +1,929 @@ +// Copyright © 2017-2021 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 "Nervos/Address.h" +#include "Nervos/Cell.h" +#include "Nervos/Serialization.h" +#include "Nervos/Signer.h" +#include "Nervos/Transaction.h" +#include "Nervos/TransactionPlan.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "uint256.h" +#include "TestUtilities.h" +#include + +#include + +namespace TW::Nervos::tests { + +std::vector getPrivateKeys(Proto::SigningInput& input) { + std::vector privateKeys; + privateKeys.reserve(input.private_key_size()); + for (auto&& privateKey : input.private_key()) { + privateKeys.emplace_back(privateKey); + } + return privateKeys; +} + +Proto::SigningInput getInput1() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_native_transfer(); + + operation.set_to_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02" + "wectaumxn0664yw2jd53lqk4mxg3"); + operation.set_change_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqds6ed78" + "yze6eyfyvd537z66ur22c9mmrgz82ama"); + operation.set_amount(10000000000); + input.set_byte_fee(1); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(100000000000); + *cell1.mutable_out_point() = + OutPoint(parse_hex("71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3"), 1) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto& cell2 = *input.add_cell(); + cell2.set_capacity(20000000000); + *cell2.mutable_out_point() = + OutPoint(parse_hex("71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3"), 0) + .proto(); + *cell2.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto privateKey = parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey.begin(), privateKey.end())); + + return input; +} + +void checkOutput1(Transaction& tx) { + // https://explorer.nervos.org/transaction/0xf2c32afde7e72011985583873bc16c0a3c01fc01fc161eb4b914fcf84c53cdf8 + ASSERT_EQ(tx.hash(), + parse_hex("f2c32afde7e72011985583873bc16c0a3c01fc01fc161eb4b914fcf84c53cdf8")); + + ASSERT_EQ(tx.cellDeps.size(), 1ul); + + ASSERT_EQ(tx.cellDeps[0].outPoint.txHash, + parse_hex("71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c")); + ASSERT_EQ(tx.cellDeps[0].outPoint.index, 0ul); + ASSERT_EQ(tx.cellDeps[0].depType, DepType::DepGroup); + + ASSERT_EQ(tx.headerDeps.size(), 0ul); + + ASSERT_EQ(tx.inputs.size(), 1ul); + + ASSERT_EQ(tx.inputs[0].previousOutput.txHash, + parse_hex("71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3")); + ASSERT_EQ(tx.inputs[0].previousOutput.index, 0ul); + ASSERT_EQ(tx.inputs[0].since, 0ul); + + ASSERT_EQ(tx.outputs.size(), 2ul); + ASSERT_EQ(tx.outputsData.size(), 2ul); + + ASSERT_EQ(tx.outputs[0].capacity, 10000000000ul); + ASSERT_EQ(tx.outputs[0].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(tx.outputs[0].lock.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[0].lock.args, parse_hex("ab201f55b02f53b385f79b34dfad548e549b48fc")); + ASSERT_EQ(tx.outputs[0].type.codeHash.size(), 0ul); + ASSERT_EQ(tx.outputs[0].type.args.size(), 0ul); + ASSERT_EQ(tx.outputsData[0].size(), 0ul); + + ASSERT_EQ(tx.outputs[1].capacity, 9999999536ul); + ASSERT_EQ(tx.outputs[1].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(tx.outputs[1].lock.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[1].lock.args, parse_hex("b0d65be39059d6489231b48f85ad706a560bbd8d")); + ASSERT_EQ(tx.outputs[1].type.codeHash.size(), 0ul); + ASSERT_EQ(tx.outputs[1].type.args.size(), 0ul); + ASSERT_EQ(tx.outputsData[1].size(), 0ul); + + ASSERT_EQ(tx.serializedWitnesses.size(), 1ul); + ASSERT_EQ( + hex(tx.serializedWitnesses[0]), + "55000000100000005500000055000000410000002a9ef2ad7829e5ea0c7a32735d29a0cb2ec20434f6fd5bf6e2" + "9cda56b28e08140156191cbbf80313d3c9cae4b74607acce7b28eb21d52ef058ed8491cdde70b700"); +} + +void checkPlan1(TransactionPlan& txPlan) { + ASSERT_EQ(txPlan.error, Common::Proto::SigningError::OK); + + ASSERT_EQ(txPlan.cellDeps.size(), 1ul); + + ASSERT_EQ(txPlan.cellDeps[0].outPoint.txHash, + parse_hex("71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c")); + ASSERT_EQ(txPlan.cellDeps[0].outPoint.index, 0ul); + ASSERT_EQ(txPlan.cellDeps[0].depType, DepType::DepGroup); + + ASSERT_EQ(txPlan.headerDeps.size(), 0ul); + + ASSERT_EQ(txPlan.selectedCells.size(), 1ul); + + ASSERT_EQ(txPlan.selectedCells[0].outPoint.txHash, + parse_hex("71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3")); + ASSERT_EQ(txPlan.selectedCells[0].outPoint.index, 0ul); + ASSERT_EQ(txPlan.selectedCells[0].capacity, 20000000000ul); + ASSERT_EQ(txPlan.selectedCells[0].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(txPlan.selectedCells[0].lock.hashType, HashType::Type1); + ASSERT_EQ(txPlan.selectedCells[0].lock.args, + parse_hex("c4b50c5c8d074f063ec0a77ded0eaff0fa7b65da")); + ASSERT_EQ(txPlan.selectedCells[0].type.codeHash.size(), 0ul); + ASSERT_EQ(txPlan.selectedCells[0].type.args.size(), 0ul); + ASSERT_EQ(txPlan.selectedCells[0].data.size(), 0ul); + + ASSERT_EQ(txPlan.outputs.size(), 2ul); + ASSERT_EQ(txPlan.outputsData.size(), 2ul); + + ASSERT_EQ(txPlan.outputs[0].capacity, 10000000000ul); + ASSERT_EQ(txPlan.outputs[0].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(txPlan.outputs[0].lock.hashType, HashType::Type1); + ASSERT_EQ(txPlan.outputs[0].lock.args, parse_hex("ab201f55b02f53b385f79b34dfad548e549b48fc")); + ASSERT_EQ(txPlan.outputs[0].type.codeHash.size(), 0ul); + ASSERT_EQ(txPlan.outputs[0].type.args.size(), 0ul); + ASSERT_EQ(txPlan.outputsData[0].size(), 0ul); + + ASSERT_EQ(txPlan.outputs[1].capacity, 9999999536ul); + ASSERT_EQ(txPlan.outputs[1].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(txPlan.outputs[1].lock.hashType, HashType::Type1); + ASSERT_EQ(txPlan.outputs[1].lock.args, parse_hex("b0d65be39059d6489231b48f85ad706a560bbd8d")); + ASSERT_EQ(txPlan.outputs[1].type.codeHash.size(), 0ul); + ASSERT_EQ(txPlan.outputs[1].type.args.size(), 0ul); + ASSERT_EQ(txPlan.outputsData[1].size(), 0ul); +} + +TEST(NervosSigner, PlanAndSign_Native_Simple) { + auto input = getInput1(); + TransactionPlan txPlan; + txPlan.plan(input); + ASSERT_EQ(txPlan.error, Common::Proto::SigningError::OK); + checkPlan1(txPlan); + Transaction tx; + tx.build(txPlan); + auto error = tx.sign(getPrivateKeys(input)); + ASSERT_EQ(error, Common::Proto::SigningError::OK); + checkOutput1(tx); +} + +TEST(NervosSigner, Sign_NegativeMissingKey) { + auto input = getInput1(); + TransactionPlan txPlan; + txPlan.plan(input); + ASSERT_EQ(txPlan.error, Common::Proto::SigningError::OK); + Transaction tx; + tx.build(txPlan); + auto privateKey = parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61fec"); + *input.mutable_private_key(0) = std::string(privateKey.begin(), privateKey.end()); + auto error = tx.sign(getPrivateKeys(input)); + ASSERT_EQ(error, Common::Proto::Error_missing_private_key); +} + +TEST(NervosSigner, Sign_NegativeNotEnoughUtxos) { + auto input = getInput1(); + auto& operation = *input.mutable_native_transfer(); + operation.set_amount(1000000000000); + TransactionPlan txPlan; + txPlan.plan(input); + ASSERT_EQ(txPlan.error, Common::Proto::Error_not_enough_utxos); +} + +Proto::SigningInput getInput2() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_native_transfer(); + + operation.set_to_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02" + "wectaumxn0664yw2jd53lqk4mxg3"); + operation.set_change_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqds6ed78" + "yze6eyfyvd537z66ur22c9mmrgz82ama"); + operation.set_use_max_amount(true); + input.set_byte_fee(1); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(11410040620); + *cell1.mutable_out_point() = + OutPoint(parse_hex("c75567c80dc9b97aaf4e5c23f4c7f37b077f2b33a50dd7abd952abfbd5beb247"), 0) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto privateKey = parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey.begin(), privateKey.end())); + + return input; +} + +TEST(NervosSigner, Sign_Native_SendMaximum) { + auto input = getInput2(); + TransactionPlan txPlan; + txPlan.plan(input); + ASSERT_EQ(txPlan.error, Common::Proto::SigningError::OK); + Transaction tx; + tx.build(txPlan); + auto error = tx.sign(getPrivateKeys(input)); + + ASSERT_EQ(error, Common::Proto::SigningError::OK); + + // https://explorer.nervos.org/transaction/0x298f5e04b6900796614b89062eb96cec63c3b2c460d01058736a793b567bc5c8 + ASSERT_EQ(tx.hash(), + parse_hex("298f5e04b6900796614b89062eb96cec63c3b2c460d01058736a793b567bc5c8")); + + ASSERT_EQ(tx.cellDeps.size(), 1ul); + + ASSERT_EQ(tx.cellDeps[0].outPoint.txHash, + parse_hex("71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c")); + ASSERT_EQ(tx.cellDeps[0].outPoint.index, 0ul); + ASSERT_EQ(tx.cellDeps[0].depType, DepType::DepGroup); + + ASSERT_EQ(tx.headerDeps.size(), 0ul); + + ASSERT_EQ(tx.inputs.size(), 1ul); + + ASSERT_EQ(tx.inputs[0].previousOutput.txHash, + parse_hex("c75567c80dc9b97aaf4e5c23f4c7f37b077f2b33a50dd7abd952abfbd5beb247")); + ASSERT_EQ(tx.inputs[0].previousOutput.index, 0ul); + ASSERT_EQ(tx.inputs[0].since, 0ul); + + ASSERT_EQ(tx.outputs.size(), 1ul); + ASSERT_EQ(tx.outputsData.size(), 1ul); + + ASSERT_EQ(tx.outputs[0].capacity, 11410040265ul); + ASSERT_EQ(tx.outputs[0].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(tx.outputs[0].lock.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[0].lock.args, parse_hex("ab201f55b02f53b385f79b34dfad548e549b48fc")); + ASSERT_EQ(tx.outputs[0].type.codeHash.size(), 0ul); + ASSERT_EQ(tx.outputs[0].type.args.size(), 0ul); + ASSERT_EQ(tx.outputsData[0].size(), 0ul); + + ASSERT_EQ(tx.serializedWitnesses.size(), 1ul); + ASSERT_EQ( + hex(tx.serializedWitnesses[0]), + "5500000010000000550000005500000041000000daf6e65e5a1fe447a4feb7199886b6635c44738e04ea594576" + "08fb1c447e068026529d57b02014ddc144622f886153df426853f22083f8891461eeb50b5ce97d01"); +} + +Proto::SigningInput getInput3() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_sudt_transfer(); + + operation.set_to_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02" + "wectaumxn0664yw2jd53lqk4mxg3"); + operation.set_change_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqds6ed78" + "yze6eyfyvd537z66ur22c9mmrgz82ama"); + uint256_t amount = 1000000000000000; + operation.set_amount(toString(amount)); + input.set_byte_fee(1); + auto sudtAddress = + parse_hex("9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8"); + *operation.mutable_sudt_address() = std::string(sudtAddress.begin(), sudtAddress.end()); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(14399998906); + *cell1.mutable_out_point() = + OutPoint(parse_hex("5b12911e7413e011f251c1fb5fae4e76fd5fcae4f0d4c6412dcc5b0bfcece823"), 0) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto& cell2 = *input.add_cell(); + cell2.set_capacity(14400000000); + *cell2.mutable_out_point() = + OutPoint(parse_hex("e118bd11a73d381daf288381ce182d92b6cf2f52d25886bbda9e1a61525c7c4a"), 0) + .proto(); + *cell2.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + *cell2.mutable_type() = + Script(parse_hex("5e7a36a77e68eecc013dfa2fe6a23f3b6c344b04005808694ae6dd45eea4cfd5"), + HashType::Type1, + parse_hex("9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8")) + .proto(); + auto cell2Data = parse_hex("00e0e4c9b9f84f000000000000000000"); + *cell2.mutable_data() = std::string(cell2Data.begin(), cell2Data.end()); + + auto& cell3 = *input.add_cell(); + cell3.set_capacity(8210025567); + *cell3.mutable_out_point() = + OutPoint(parse_hex("09a45a15e48f985b554a0b6e5f0857913cc492ec061cc9b0b2befa4b24609a4a"), 1) + .proto(); + *cell3.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqds6ed78yze6eyfyvd537z66ur22c9mmrgz82ama")) + .proto(); + + auto privateKey1 = + parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey1.begin(), privateKey1.end())); + auto privateKey2 = + parse_hex("0c8859a9d9084a8c2b55963268b352e258756f9240f2a1f4645c610ed191dae9"); + input.add_private_key(std::string(privateKey2.begin(), privateKey2.end())); + + return input; +} + +TEST(NervosSigner, Sign_SUDT_Simple) { + auto input = getInput3(); + + TransactionPlan txPlan; + txPlan.plan(input); + ASSERT_EQ(txPlan.error, Common::Proto::SigningError::OK); + Transaction tx; + tx.build(txPlan); + auto error = tx.sign(getPrivateKeys(input)); + + ASSERT_EQ(error, Common::Proto::SigningError::OK); + + // https://explorer.nervos.org/transaction/0x9b15f2bea26b98201540d8e20e8b1c21d96dd77ad246520b405c6aabb7173371 + ASSERT_EQ(tx.hash(), + parse_hex("9b15f2bea26b98201540d8e20e8b1c21d96dd77ad246520b405c6aabb7173371")); + + ASSERT_EQ(tx.cellDeps.size(), 2ul); + + ASSERT_EQ(tx.cellDeps[0].outPoint.txHash, + parse_hex("71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c")); + ASSERT_EQ(tx.cellDeps[0].outPoint.index, 0ul); + ASSERT_EQ(tx.cellDeps[0].depType, DepType::DepGroup); + + ASSERT_EQ(tx.cellDeps[1].outPoint.txHash, + parse_hex("c7813f6a415144643970c2e88e0bb6ca6a8edc5dd7c1022746f628284a9936d5")); + ASSERT_EQ(tx.cellDeps[1].outPoint.index, 0ul); + ASSERT_EQ(tx.cellDeps[1].depType, DepType::Code); + + ASSERT_EQ(tx.headerDeps.size(), 0ul); + + ASSERT_EQ(tx.inputs.size(), 3ul); + + ASSERT_EQ(tx.inputs[0].previousOutput.txHash, + parse_hex("e118bd11a73d381daf288381ce182d92b6cf2f52d25886bbda9e1a61525c7c4a")); + ASSERT_EQ(tx.inputs[0].previousOutput.index, 0ul); + ASSERT_EQ(tx.inputs[0].since, 0ul); + + ASSERT_EQ(tx.inputs[1].previousOutput.txHash, + parse_hex("09a45a15e48f985b554a0b6e5f0857913cc492ec061cc9b0b2befa4b24609a4a")); + ASSERT_EQ(tx.inputs[1].previousOutput.index, 1ul); + ASSERT_EQ(tx.inputs[1].since, 0ul); + + ASSERT_EQ(tx.inputs[2].previousOutput.txHash, + parse_hex("5b12911e7413e011f251c1fb5fae4e76fd5fcae4f0d4c6412dcc5b0bfcece823")); + ASSERT_EQ(tx.inputs[2].previousOutput.index, 0ul); + ASSERT_EQ(tx.inputs[2].since, 0ul); + + ASSERT_EQ(tx.outputs.size(), 3ul); + ASSERT_EQ(tx.outputsData.size(), 3ul); + + ASSERT_EQ(tx.outputs[0].capacity, 14400000000ul); + ASSERT_EQ(tx.outputs[0].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(tx.outputs[0].lock.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[0].lock.args, parse_hex("ab201f55b02f53b385f79b34dfad548e549b48fc")); + ASSERT_EQ(tx.outputs[0].type.codeHash, + parse_hex("5e7a36a77e68eecc013dfa2fe6a23f3b6c344b04005808694ae6dd45eea4cfd5")); + ASSERT_EQ(tx.outputs[0].type.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[0].type.args, + parse_hex("9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8")); + ASSERT_EQ(hex(tx.outputsData[0]), "0080c6a47e8d03000000000000000000"); + + ASSERT_EQ(tx.outputs[1].capacity, 14400000000ul); + ASSERT_EQ(tx.outputs[1].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(tx.outputs[1].lock.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[1].lock.args, parse_hex("b0d65be39059d6489231b48f85ad706a560bbd8d")); + ASSERT_EQ(tx.outputs[1].type.codeHash, + parse_hex("5e7a36a77e68eecc013dfa2fe6a23f3b6c344b04005808694ae6dd45eea4cfd5")); + ASSERT_EQ(tx.outputs[1].type.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[1].type.args, + parse_hex("9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8")); + ASSERT_EQ(hex(tx.outputsData[1]), "00601e253b6b4c000000000000000000"); + + ASSERT_EQ(tx.outputs[2].capacity, 8210023387ul); + ASSERT_EQ(tx.outputs[2].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(tx.outputs[2].lock.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[2].lock.args, parse_hex("b0d65be39059d6489231b48f85ad706a560bbd8d")); + ASSERT_EQ(tx.outputs[2].type.codeHash.size(), 0ul); + ASSERT_EQ(tx.outputs[2].type.args.size(), 0ul); + ASSERT_EQ(tx.outputsData[2].size(), 0ul); + + ASSERT_EQ(tx.serializedWitnesses.size(), 3ul); + ASSERT_EQ( + hex(tx.serializedWitnesses[0]), + "550000001000000055000000550000004100000035d55fd46316f248552eb6af7ac9589c9ec533c4e5b71896b0" + "5cdf697e2d18551ceff54d7b860ebb2f4dd5f6c5bb4af1da15460a7621f5aa4bc7d5585a0504de00"); + ASSERT_EQ( + hex(tx.serializedWitnesses[1]), + "5500000010000000550000005500000041000000eaa4bf69126d3016ab786610f2f0668b2ef353915d623d0b01" + "84fc25cec3dcad6dc08a1504a2d7dd9faced17b041d79d4c21f1977e57859713360f5e3609583501"); + ASSERT_EQ(hex(tx.serializedWitnesses[2]), ""); +} + +Proto::SigningInput getInput4() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_sudt_transfer(); + + operation.set_to_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02" + "wectaumxn0664yw2jd53lqk4mxg3"); + operation.set_change_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqds6ed78" + "yze6eyfyvd537z66ur22c9mmrgz82ama"); + operation.set_use_max_amount(true); + input.set_byte_fee(1); + auto sudtAddress = + parse_hex("9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8"); + *operation.mutable_sudt_address() = std::string(sudtAddress.begin(), sudtAddress.end()); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(8210026306); + *cell1.mutable_out_point() = + OutPoint(parse_hex("430cb60ee816e2631d6d9605659c18fec8eb3de94526f5fd4ad51feaad6f1664"), 0) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto& cell2 = *input.add_cell(); + cell2.set_capacity(14400000000); + *cell2.mutable_out_point() = + OutPoint(parse_hex("378b6bd2f7fc2b1599ee55be7e8fa17fdd6e0d25e2e146d5f46006e0292d6564"), 0) + .proto(); + *cell2.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + *cell2.mutable_type() = + Script(parse_hex("5e7a36a77e68eecc013dfa2fe6a23f3b6c344b04005808694ae6dd45eea4cfd5"), + HashType::Type1, + parse_hex("9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8")) + .proto(); + auto cell2Data = parse_hex("00601e253b6b4c000000000000000000"); + *cell2.mutable_data() = std::string(cell2Data.begin(), cell2Data.end()); + + auto privateKey = parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey.begin(), privateKey.end())); + + return input; +} + +TEST(NervosSigner, Sign_SUDT_SendMaximum) { + auto input = getInput4(); + TransactionPlan txPlan; + txPlan.plan(input); + ASSERT_EQ(txPlan.error, Common::Proto::SigningError::OK); + Transaction tx; + tx.build(txPlan); + auto error = tx.sign(getPrivateKeys(input)); + + ASSERT_EQ(error, Common::Proto::SigningError::OK); + + // https://explorer.nervos.org/transaction/0x09a45a15e48f985b554a0b6e5f0857913cc492ec061cc9b0b2befa4b24609a4a + ASSERT_EQ(tx.hash(), + parse_hex("09a45a15e48f985b554a0b6e5f0857913cc492ec061cc9b0b2befa4b24609a4a")); + + ASSERT_EQ(tx.cellDeps.size(), 2ul); + + ASSERT_EQ(tx.cellDeps[0].outPoint.txHash, + parse_hex("71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c")); + ASSERT_EQ(tx.cellDeps[0].outPoint.index, 0ul); + ASSERT_EQ(tx.cellDeps[0].depType, DepType::DepGroup); + + ASSERT_EQ(tx.cellDeps[1].outPoint.txHash, + parse_hex("c7813f6a415144643970c2e88e0bb6ca6a8edc5dd7c1022746f628284a9936d5")); + ASSERT_EQ(tx.cellDeps[1].outPoint.index, 0ul); + ASSERT_EQ(tx.cellDeps[1].depType, DepType::Code); + + ASSERT_EQ(tx.headerDeps.size(), 0ul); + + ASSERT_EQ(tx.inputs.size(), 2ul); + + ASSERT_EQ(tx.inputs[0].previousOutput.txHash, + parse_hex("378b6bd2f7fc2b1599ee55be7e8fa17fdd6e0d25e2e146d5f46006e0292d6564")); + ASSERT_EQ(tx.inputs[0].previousOutput.index, 0ul); + ASSERT_EQ(tx.inputs[0].since, 0ul); + + ASSERT_EQ(tx.inputs[1].previousOutput.txHash, + parse_hex("430cb60ee816e2631d6d9605659c18fec8eb3de94526f5fd4ad51feaad6f1664")); + ASSERT_EQ(tx.inputs[1].previousOutput.index, 0ul); + ASSERT_EQ(tx.inputs[1].since, 0ul); + + ASSERT_EQ(tx.outputs.size(), 2ul); + ASSERT_EQ(tx.outputsData.size(), 2ul); + + ASSERT_EQ(tx.outputs[0].capacity, 14400000000ul); + ASSERT_EQ(tx.outputs[0].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(tx.outputs[0].lock.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[0].lock.args, parse_hex("ab201f55b02f53b385f79b34dfad548e549b48fc")); + ASSERT_EQ(tx.outputs[0].type.codeHash, + parse_hex("5e7a36a77e68eecc013dfa2fe6a23f3b6c344b04005808694ae6dd45eea4cfd5")); + ASSERT_EQ(tx.outputs[0].type.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[0].type.args, + parse_hex("9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8")); + ASSERT_EQ(hex(tx.outputsData[0]), "00601e253b6b4c000000000000000000"); + + ASSERT_EQ(tx.outputs[1].capacity, 8210025567ul); + ASSERT_EQ(tx.outputs[1].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(tx.outputs[1].lock.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[1].lock.args, parse_hex("b0d65be39059d6489231b48f85ad706a560bbd8d")); + ASSERT_EQ(tx.outputs[1].type.codeHash.size(), 0ul); + ASSERT_EQ(tx.outputs[1].type.args.size(), 0ul); + ASSERT_EQ(tx.outputsData[1].size(), 0ul); + + ASSERT_EQ(tx.serializedWitnesses.size(), 2ul); + ASSERT_EQ( + hex(tx.serializedWitnesses[0]), + "5500000010000000550000005500000041000000da7c908bdf2cb091b7ff9bb682b762d1323c5e1ecf9b2ce0eb" + "edb9d55f6625c52ab14910ae401833112f2ea516ab11bc9ef691c3dff7886e3238c9348c3d73a701"); + ASSERT_EQ(hex(tx.serializedWitnesses[1]), ""); +} + +Proto::SigningInput getInput5() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_dao_deposit(); + + operation.set_to_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8f" + "urras980hksatlslfaktks7epf25"); + operation.set_change_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9e" + "rg8furras980hksatlslfaktks7epf25"); + + operation.set_amount(10200000000); + input.set_byte_fee(1); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(8210021909); + *cell1.mutable_out_point() = + OutPoint(parse_hex("c7dacd4aab49f5f9643e87752428cebde38eeb49c7726781c4d8b526822004a1"), 1) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqds6ed78yze6eyfyvd537z66ur22c9mmrgz82ama")) + .proto(); + + auto& cell2 = *input.add_cell(); + cell2.set_capacity(14399998167); + *cell2.mutable_out_point() = + OutPoint(parse_hex("d3c3263170815b326779e2fd8d548f846ae13eff9d9a82833c7071069a1d32bf"), 0) + .proto(); + *cell2.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto privateKey1 = + parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey1.begin(), privateKey1.end())); + auto privateKey2 = + parse_hex("0c8859a9d9084a8c2b55963268b352e258756f9240f2a1f4645c610ed191dae9"); + input.add_private_key(std::string(privateKey2.begin(), privateKey2.end())); + + return input; +} + +TEST(NervosSigner, Sign_DAO_Deposit) { + auto input = getInput5(); + + TransactionPlan txPlan; + txPlan.plan(input); + ASSERT_EQ(txPlan.error, Common::Proto::SigningError::OK); + Transaction tx; + tx.build(txPlan); + auto error = tx.sign(getPrivateKeys(input)); + + ASSERT_EQ(error, Common::Proto::SigningError::OK); + + // https://explorer.nervos.org/transaction/0x583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683 + ASSERT_EQ(tx.hash(), + parse_hex("583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683")); + + ASSERT_EQ(tx.cellDeps.size(), 2ul); + + ASSERT_EQ(tx.cellDeps[0].outPoint.txHash, + parse_hex("71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c")); + ASSERT_EQ(tx.cellDeps[0].outPoint.index, 0ul); + ASSERT_EQ(tx.cellDeps[0].depType, DepType::DepGroup); + + ASSERT_EQ(tx.cellDeps[1].outPoint.txHash, + parse_hex("e2fb199810d49a4d8beec56718ba2593b665db9d52299a0f9e6e75416d73ff5c")); + ASSERT_EQ(tx.cellDeps[1].outPoint.index, 2ul); + ASSERT_EQ(tx.cellDeps[1].depType, DepType::Code); + + ASSERT_EQ(tx.headerDeps.size(), 0ul); + + ASSERT_EQ(tx.inputs.size(), 2ul); + + ASSERT_EQ(tx.inputs[0].previousOutput.txHash, + parse_hex("c7dacd4aab49f5f9643e87752428cebde38eeb49c7726781c4d8b526822004a1")); + ASSERT_EQ(tx.inputs[0].previousOutput.index, 1ul); + ASSERT_EQ(tx.inputs[0].since, 0ul); + + ASSERT_EQ(tx.inputs[1].previousOutput.txHash, + parse_hex("d3c3263170815b326779e2fd8d548f846ae13eff9d9a82833c7071069a1d32bf")); + ASSERT_EQ(tx.inputs[1].previousOutput.index, 0ul); + ASSERT_EQ(tx.inputs[1].since, 0ul); + + ASSERT_EQ(tx.outputs.size(), 2ul); + ASSERT_EQ(tx.outputsData.size(), 2ul); + + ASSERT_EQ(tx.outputs[0].capacity, 10200000000ul); + ASSERT_EQ(tx.outputs[0].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(tx.outputs[0].lock.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[0].lock.args, parse_hex("c4b50c5c8d074f063ec0a77ded0eaff0fa7b65da")); + ASSERT_EQ(tx.outputs[0].type.codeHash, + parse_hex("82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e")); + ASSERT_EQ(tx.outputs[0].type.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[0].type.args, parse_hex("")); + ASSERT_EQ(hex(tx.outputsData[0]), "0000000000000000"); + + ASSERT_EQ(tx.outputs[1].capacity, 12410019377ul); + ASSERT_EQ(tx.outputs[1].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(tx.outputs[1].lock.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[1].lock.args, parse_hex("c4b50c5c8d074f063ec0a77ded0eaff0fa7b65da")); + ASSERT_EQ(tx.outputs[1].type.codeHash.size(), 0ul); + ASSERT_EQ(tx.outputs[1].type.args.size(), 0ul); + ASSERT_EQ(tx.outputsData[1].size(), 0ul); + + ASSERT_EQ(tx.serializedWitnesses.size(), 2ul); + ASSERT_EQ( + hex(tx.serializedWitnesses[0]), + "5500000010000000550000005500000041000000305d09c7de3f34a4d53bc4e0031ee59c95b9abc4fc3ff5548e" + "1a17ca726c069a232012c9c4be6ec4d4ffbe88613ca5e686e3e4b7d0b9bbd7038003e23ffdcdd601"); + ASSERT_EQ( + hex(tx.serializedWitnesses[1]), + "55000000100000005500000055000000410000007c514c77482dd1e1086f41a6d17364c9b5ed16364d61df6f7f" + "d8540f8bf7c131275c877943786b1b72fbf4f9d817ee5dd554a689808b7919543c691b5068e5be01"); +} + +Proto::SigningInput getInput6() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_dao_withdraw_phase1(); + + operation.set_change_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9e" + "rg8furras980hksatlslfaktks7epf25"); + auto& depositCell = *operation.mutable_deposit_cell(); + depositCell.set_capacity(10200000000); + *depositCell.mutable_out_point() = + OutPoint(parse_hex("583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683"), 0) + .proto(); + *depositCell.mutable_lock() = + Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8furras9" + "80hksatlslfaktks7epf25")) + .proto(); + *depositCell.mutable_type() = + Script(parse_hex("82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e"), + HashType::Type1, Data()) + .proto(); + auto depositCellData = parse_hex("0000000000000000"); + *depositCell.mutable_data() = std::string(depositCellData.begin(), depositCellData.end()); + depositCell.set_block_number(7575466); + auto blockHashData = + parse_hex("3dfdb4b702a355a5593315016f8af0537d5a2f3292811b79420ded78a092be6a"); + *depositCell.mutable_block_hash() = std::string(blockHashData.begin(), blockHashData.end()); + input.set_byte_fee(1); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(10200000000); + *cell1.mutable_out_point() = + OutPoint(parse_hex("583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683"), 0) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + *cell1.mutable_type() = + Script(parse_hex("82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e"), + HashType::Type1, Data()) + .proto(); + auto cell1Data = parse_hex("0000000000000000"); + *cell1.mutable_data() = std::string(cell1Data.begin(), cell1Data.end()); + + auto& cell2 = *input.add_cell(); + cell2.set_capacity(12410019377); + *cell2.mutable_out_point() = + OutPoint(parse_hex("583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683"), 1) + .proto(); + *cell2.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto privateKey1 = + parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey1.begin(), privateKey1.end())); + + return input; +} + +TEST(NervosSigner, Sign_DAO_Withdraw_Phase1) { + auto input = getInput6(); + + TransactionPlan txPlan; + txPlan.plan(input); + ASSERT_EQ(txPlan.error, Common::Proto::SigningError::OK); + Transaction tx; + tx.build(txPlan); + auto error = tx.sign(getPrivateKeys(input)); + + ASSERT_EQ(error, Common::Proto::SigningError::OK); + + // https://explorer.nervos.org/transaction/0xb4e62bc5f5108275b0ef3da8f8cc3fb0172843c4a2a9cdfef3b04d6c65e9acca + ASSERT_EQ(tx.hash(), + parse_hex("b4e62bc5f5108275b0ef3da8f8cc3fb0172843c4a2a9cdfef3b04d6c65e9acca")); + + ASSERT_EQ(tx.cellDeps.size(), 2ul); + + ASSERT_EQ(tx.cellDeps[0].outPoint.txHash, + parse_hex("71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c")); + ASSERT_EQ(tx.cellDeps[0].outPoint.index, 0ul); + ASSERT_EQ(tx.cellDeps[0].depType, DepType::DepGroup); + + ASSERT_EQ(tx.cellDeps[1].outPoint.txHash, + parse_hex("e2fb199810d49a4d8beec56718ba2593b665db9d52299a0f9e6e75416d73ff5c")); + ASSERT_EQ(tx.cellDeps[1].outPoint.index, 2ul); + ASSERT_EQ(tx.cellDeps[1].depType, DepType::Code); + + ASSERT_EQ(tx.headerDeps.size(), 1ul); + ASSERT_EQ(tx.headerDeps[0], + parse_hex("3dfdb4b702a355a5593315016f8af0537d5a2f3292811b79420ded78a092be6a")); + + ASSERT_EQ(tx.inputs.size(), 2ul); + + ASSERT_EQ(tx.inputs[0].previousOutput.txHash, + parse_hex("583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683")); + ASSERT_EQ(tx.inputs[0].previousOutput.index, 0ul); + ASSERT_EQ(tx.inputs[0].since, 0ul); + + ASSERT_EQ(tx.inputs[1].previousOutput.txHash, + parse_hex("583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683")); + ASSERT_EQ(tx.inputs[1].previousOutput.index, 1ul); + ASSERT_EQ(tx.inputs[1].since, 0ul); + + ASSERT_EQ(tx.outputs.size(), 2ul); + ASSERT_EQ(tx.outputsData.size(), 2ul); + + ASSERT_EQ(tx.outputs[0].capacity, 10200000000ul); + ASSERT_EQ(tx.outputs[0].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(tx.outputs[0].lock.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[0].lock.args, parse_hex("c4b50c5c8d074f063ec0a77ded0eaff0fa7b65da")); + ASSERT_EQ(tx.outputs[0].type.codeHash, + parse_hex("82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e")); + ASSERT_EQ(tx.outputs[0].type.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[0].type.args, parse_hex("")); + ASSERT_EQ(hex(tx.outputsData[0]), "aa97730000000000"); + + ASSERT_EQ(tx.outputs[1].capacity, 12410018646ul); + ASSERT_EQ(tx.outputs[1].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(tx.outputs[1].lock.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[1].lock.args, parse_hex("c4b50c5c8d074f063ec0a77ded0eaff0fa7b65da")); + ASSERT_EQ(tx.outputs[1].type.codeHash.size(), 0ul); + ASSERT_EQ(tx.outputs[1].type.args.size(), 0ul); + ASSERT_EQ(tx.outputsData[1].size(), 0ul); + + ASSERT_EQ(tx.serializedWitnesses.size(), 2ul); + ASSERT_EQ( + hex(tx.serializedWitnesses[0]), + "5500000010000000550000005500000041000000d5131c1a6b8eca11e2c280b72c5db09ea00bb788fd3262eace" + "d861c39db2aad04a36f9d174b6f167a9c98b85d2bccf537a163c44459d23467dfa86408f48dd5f01"); + ASSERT_EQ(tx.serializedWitnesses[1].size(), 0ul); +} + +Proto::SigningInput getInput7() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_dao_withdraw_phase2(); + + auto& depositCell = *operation.mutable_deposit_cell(); + depositCell.set_capacity(10200000000); + *depositCell.mutable_out_point() = + OutPoint(parse_hex("583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683"), 0) + .proto(); + *depositCell.mutable_lock() = + Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8furras9" + "80hksatlslfaktks7epf25")) + .proto(); + *depositCell.mutable_type() = + Script(parse_hex("82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e"), + HashType::Type1, Data()) + .proto(); + auto depositCellData = parse_hex("0000000000000000"); + *depositCell.mutable_data() = std::string(depositCellData.begin(), depositCellData.end()); + depositCell.set_block_number(7575466); + auto blockHashData1 = + parse_hex("3dfdb4b702a355a5593315016f8af0537d5a2f3292811b79420ded78a092be6a"); + *depositCell.mutable_block_hash() = std::string(blockHashData1.begin(), blockHashData1.end()); + + auto& withdrawingCell = *operation.mutable_withdrawing_cell(); + withdrawingCell.set_capacity(10200000000); + *withdrawingCell.mutable_out_point() = + OutPoint(parse_hex("b4e62bc5f5108275b0ef3da8f8cc3fb0172843c4a2a9cdfef3b04d6c65e9acca"), 0) + .proto(); + *withdrawingCell.mutable_lock() = + Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8furras9" + "80hksatlslfaktks7epf25")) + .proto(); + *withdrawingCell.mutable_type() = + Script(parse_hex("82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e"), + HashType::Type1, Data()) + .proto(); + auto withdrawingCellData = parse_hex("aa97730000000000"); + *withdrawingCell.mutable_data() = + std::string(withdrawingCellData.begin(), withdrawingCellData.end()); + withdrawingCell.set_block_number(7575534); + auto blockHashData2 = + parse_hex("b070d5364afd47c23fe267077d454009d6f665f200a915e68af1616e46f4aa52"); + *withdrawingCell.mutable_block_hash() = + std::string(blockHashData2.begin(), blockHashData2.end()); + withdrawingCell.set_since(0x20037c0000001731); + + operation.set_amount(10200000000); + input.set_byte_fee(1); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(10200000000); + *cell1.mutable_out_point() = + OutPoint(parse_hex("b4e62bc5f5108275b0ef3da8f8cc3fb0172843c4a2a9cdfef3b04d6c65e9acca"), 0) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + *cell1.mutable_type() = + Script(parse_hex("82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e"), + HashType::Type1, Data()) + .proto(); + auto cell1Data = parse_hex("aa97730000000000"); + *cell1.mutable_data() = std::string(cell1Data.begin(), cell1Data.end()); + + auto privateKey1 = + parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey1.begin(), privateKey1.end())); + + return input; +} + +TEST(NervosSigner, Sign_DAO_Withdraw_Phase2) { + auto input = getInput7(); + + TransactionPlan txPlan; + txPlan.plan(input); + ASSERT_EQ(txPlan.error, Common::Proto::SigningError::OK); + Transaction tx; + tx.build(txPlan); + auto error = tx.sign(getPrivateKeys(input)); + + ASSERT_EQ(error, Common::Proto::SigningError::OK); + + ASSERT_EQ(tx.hash(), + parse_hex("4fde13c93fc5d24ab7f660070aaa0b1725809d585f6258605e595cdbd856ea1c")); + + ASSERT_EQ(tx.cellDeps.size(), 2ul); + + ASSERT_EQ(tx.cellDeps[0].outPoint.txHash, + parse_hex("71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c")); + ASSERT_EQ(tx.cellDeps[0].outPoint.index, 0ul); + ASSERT_EQ(tx.cellDeps[0].depType, DepType::DepGroup); + + ASSERT_EQ(tx.cellDeps[1].outPoint.txHash, + parse_hex("e2fb199810d49a4d8beec56718ba2593b665db9d52299a0f9e6e75416d73ff5c")); + ASSERT_EQ(tx.cellDeps[1].outPoint.index, 2ul); + ASSERT_EQ(tx.cellDeps[1].depType, DepType::Code); + + ASSERT_EQ(tx.headerDeps.size(), 2ul); + ASSERT_EQ(tx.headerDeps[0], + parse_hex("3dfdb4b702a355a5593315016f8af0537d5a2f3292811b79420ded78a092be6a")); + ASSERT_EQ(tx.headerDeps[1], + parse_hex("b070d5364afd47c23fe267077d454009d6f665f200a915e68af1616e46f4aa52")); + + ASSERT_EQ(tx.inputs.size(), 1ul); + + ASSERT_EQ(tx.inputs[0].previousOutput.txHash, + parse_hex("b4e62bc5f5108275b0ef3da8f8cc3fb0172843c4a2a9cdfef3b04d6c65e9acca")); + ASSERT_EQ(tx.inputs[0].previousOutput.index, 0ul); + ASSERT_EQ(tx.inputs[0].since, 0x20037c0000001731ul); + + ASSERT_EQ(tx.outputs.size(), 1ul); + ASSERT_EQ(tx.outputsData.size(), 1ul); + + ASSERT_EQ(tx.outputs[0].capacity, 10199999532ul); + ASSERT_EQ(tx.outputs[0].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(tx.outputs[0].lock.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[0].lock.args, parse_hex("c4b50c5c8d074f063ec0a77ded0eaff0fa7b65da")); + ASSERT_EQ(tx.outputs[0].type.codeHash.size(), 0ul); + ASSERT_EQ(tx.outputs[0].type.args.size(), 0ul); + ASSERT_EQ(tx.outputsData[0].size(), 0ul); + + ASSERT_EQ(tx.serializedWitnesses.size(), 1ul); + ASSERT_EQ(hex(tx.serializedWitnesses[0]), + "6100000010000000550000006100000041000000743f86c5557f4e2d3327f4d17e7bad27209b29c1e9cd" + "bab42ab03f7094af917b4b203ddd7f2e87102e09ae579f2fe7f6adb7900b7386b58c1183ba0011b7c421" + "00080000000000000000000000"); +} + +} // namespace TW::Nervos::tests diff --git a/tests/chains/Nervos/TWAnyAddressTests.cpp b/tests/chains/Nervos/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..c514723797c --- /dev/null +++ b/tests/chains/Nervos/TWAnyAddressTests.cpp @@ -0,0 +1,70 @@ +// Copyright © 2017-2021 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 "PrivateKey.h" +#include +#include +#include +#include +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TWAnyAddressNervos, AddressFromPublicKey) { + auto privateKey = + WRAP(TWPrivateKey, + TWPrivateKeyCreateWithData( + DATA("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb").get())); + ASSERT_NE(nullptr, privateKey.get()); + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); + ASSERT_NE(nullptr, publicKey.get()); + ASSERT_EQ(33ul, publicKey.get()->impl.bytes.size()); + auto address = + WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeNervos)); + auto addressString = WRAPS(TWAnyAddressDescription(address.get())); + assertStringsEqual(addressString, "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwy" + "k5x9erg8furras980hksatlslfaktks7epf25"); +} + +TEST(TWAnyAddressNervos, AddressFromString) { + auto address = + WRAP(TWAnyAddress, + TWAnyAddressCreateWithString(STRING("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthy" + "waa50xwsqwyk5x9erg8furras980hksatlslfaktks7epf25") + .get(), + TWCoinTypeNervos)); + ASSERT_NE(nullptr, address.get()); + auto addressString = WRAPS(TWAnyAddressDescription(address.get())); + assertStringsEqual(addressString, "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwy" + "k5x9erg8furras980hksatlslfaktks7epf25"); +} + +TEST(TWAnyAddressNervos, AddressFromWallet) { + auto wallet = WRAP( + TWHDWallet, + TWHDWalletCreateWithMnemonic( + STRING( + "alpha draw toss question picnic endless recycle wrong enable roast success palm") + .get(), + STRING("").get())); + auto privateKey = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeNervos)); + auto privateKeyData = WRAPD(TWPrivateKeyData(privateKey.get())); + EXPECT_EQ(TWDataSize(privateKeyData.get()), 32ul); + + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); + auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); + EXPECT_EQ(TWDataSize(publicKeyData.get()), 33ul); + assertHexEqual(publicKeyData, + "026c9e4cbb95d4b3a123c1fc80795feacc38029683a1b3e16bccf49bba25fb2858"); + + auto address = WRAPS(TWCoinTypeDeriveAddress(TWCoinTypeNervos, privateKey.get())); + assertStringsEqual(address, "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9er" + "g8furras980hksatlslfaktks7epf25"); +} diff --git a/tests/chains/Nervos/TWAnySignerTests.cpp b/tests/chains/Nervos/TWAnySignerTests.cpp new file mode 100644 index 00000000000..c213e802d0a --- /dev/null +++ b/tests/chains/Nervos/TWAnySignerTests.cpp @@ -0,0 +1,665 @@ +// Copyright © 2017-2021 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 "Nervos/Address.h" +#include "Nervos/Serialization.h" +#include "Nervos/TransactionPlan.h" +#include "PublicKey.h" +#include "proto/Nervos.pb.h" +#include "uint256.h" +#include "TestUtilities.h" +#include + +#include + +namespace TW::Nervos::tests { + +Proto::SigningInput getAnySignerInput1() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_native_transfer(); + + operation.set_to_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02" + "wectaumxn0664yw2jd53lqk4mxg3"); + operation.set_change_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqds6ed78" + "yze6eyfyvd537z66ur22c9mmrgz82ama"); + operation.set_amount(10000000000); + input.set_byte_fee(1); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(100000000000); + *cell1.mutable_out_point() = + OutPoint(parse_hex("71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3"), 1) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto& cell2 = *input.add_cell(); + cell2.set_capacity(20000000000); + *cell2.mutable_out_point() = + OutPoint(parse_hex("71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3"), 0) + .proto(); + *cell2.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto privateKey = parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey.begin(), privateKey.end())); + + return input; +} + +void checkAnySignerOutput1(Proto::SigningOutput& output) { + ASSERT_EQ(output.error(), Common::Proto::OK); + // https://explorer.nervos.org/transaction/0xf2c32afde7e72011985583873bc16c0a3c01fc01fc161eb4b914fcf84c53cdf8 + ASSERT_EQ(output.transaction_id(), + "0xf2c32afde7e72011985583873bc16c0a3c01fc01fc161eb4b914fcf84c53cdf8"); + ASSERT_EQ( + output.transaction_json(), + "{\"cell_deps\":[{\"dep_type\":\"dep_group\",\"out_point\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c\"}}],\"header_deps\":" + "[],\"inputs\":[{\"previous_output\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0x71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3\"},\"since\":\"0x0\"}" + "],\"outputs\":[{\"capacity\":\"0x2540be400\",\"lock\":{\"args\":" + "\"0xab201f55b02f53b385f79b34dfad548e549b48fc\",\"code_hash\":" + "\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_type\":" + "\"type\"},\"type\":null},{\"capacity\":\"0x2540be230\",\"lock\":{\"args\":" + "\"0xb0d65be39059d6489231b48f85ad706a560bbd8d\",\"code_hash\":" + "\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_type\":" + "\"type\"},\"type\":null}],\"outputs_data\":[\"0x\",\"0x\"],\"version\":\"0x0\"," + "\"witnesses\":[" + "\"0x55000000100000005500000055000000410000002a9ef2ad7829e5ea0c7a32735d29a0cb2ec20434f6fd5b" + "f6e29cda56b28e08140156191cbbf80313d3c9cae4b74607acce7b28eb21d52ef058ed8491cdde70b700\"]}"); +} + +void checkPlan1(Proto::TransactionPlan& txPlanProto) { + ASSERT_EQ(txPlanProto.error(), Common::Proto::OK); + + ASSERT_EQ(txPlanProto.cell_deps_size(), 1); + + const auto cellDep1 = txPlanProto.cell_deps(0); + const auto cellDep1TxHash = + parse_hex("71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c"); + ASSERT_EQ(cellDep1.out_point().tx_hash(), + std::string(cellDep1TxHash.begin(), cellDep1TxHash.end())); + ASSERT_EQ(cellDep1.out_point().index(), 0ul); + ASSERT_EQ(cellDep1.dep_type(), "dep_group"); + + ASSERT_EQ(txPlanProto.header_deps_size(), 0); + + ASSERT_EQ(txPlanProto.selected_cells_size(), 1); + + auto cell1 = Cell(txPlanProto.selected_cells(0)); + ASSERT_EQ(cell1.outPoint.txHash, + parse_hex("71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3")); + ASSERT_EQ(cell1.outPoint.index, 0ul); + ASSERT_EQ(cell1.capacity, 20000000000ul); + ASSERT_EQ(cell1.lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(cell1.lock.hashType, HashType::Type1); + ASSERT_EQ(cell1.lock.args, parse_hex("c4b50c5c8d074f063ec0a77ded0eaff0fa7b65da")); + ASSERT_EQ(cell1.type.codeHash.size(), 0ul); + ASSERT_EQ(cell1.type.args.size(), 0ul); + ASSERT_EQ(cell1.data.size(), 0ul); + + ASSERT_EQ(txPlanProto.outputs_size(), 2); + ASSERT_EQ(txPlanProto.outputs_data_size(), 2); + + auto cellOutput1 = CellOutput(txPlanProto.outputs(0)); + ASSERT_EQ(cellOutput1.capacity, 10000000000ul); + ASSERT_EQ(cellOutput1.lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(cellOutput1.lock.hashType, HashType::Type1); + ASSERT_EQ(cellOutput1.lock.args, parse_hex("ab201f55b02f53b385f79b34dfad548e549b48fc")); + ASSERT_EQ(cellOutput1.type.codeHash.size(), 0ul); + ASSERT_EQ(cellOutput1.type.args.size(), 0ul); + ASSERT_EQ(txPlanProto.outputs_data(0).length(), 0ul); + + auto cellOutput2 = CellOutput(txPlanProto.outputs(1)); + ASSERT_EQ(cellOutput2.capacity, 9999999536ul); + ASSERT_EQ(cellOutput2.lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(cellOutput2.lock.hashType, HashType::Type1); + ASSERT_EQ(cellOutput2.lock.args, parse_hex("b0d65be39059d6489231b48f85ad706a560bbd8d")); + ASSERT_EQ(cellOutput2.type.codeHash.size(), 0ul); + ASSERT_EQ(cellOutput2.type.args.size(), 0ul); + ASSERT_EQ(txPlanProto.outputs_data(1).length(), 0ul); +} + +TEST(TWAnySignerNervos, Sign_Native_Simple) { + auto input = getAnySignerInput1(); + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNervos); + checkAnySignerOutput1(output); +} + +TEST(TWAnySignerNervos, PlanAndSign_Native_Simple) { + auto input = getAnySignerInput1(); + Proto::TransactionPlan txPlanProto; + ANY_PLAN(input, txPlanProto, TWCoinTypeNervos); + checkPlan1(txPlanProto); + *input.mutable_plan() = txPlanProto; + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNervos); + checkAnySignerOutput1(output); +} + +TEST(TWAnySignerNervos, Sign_NegativeMissingKey) { + auto input = getAnySignerInput1(); + input.clear_private_key(); + auto privateKey = parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61fec"); + input.add_private_key(std::string(privateKey.begin(), privateKey.end())); + Proto::SigningOutput output; + + ANY_SIGN(input, TWCoinTypeNervos); + + ASSERT_EQ(output.error(), Common::Proto::Error_missing_private_key); +} + +TEST(TWAnySignerNervos, Sign_NegativeNotEnoughUtxos) { + auto input = getAnySignerInput1(); + auto& operation = *input.mutable_native_transfer(); + operation.set_amount(1000000000000); + Proto::SigningOutput output; + + ANY_SIGN(input, TWCoinTypeNervos); + + ASSERT_EQ(output.error(), Common::Proto::Error_not_enough_utxos); +} + +Proto::SigningInput getAnySignerInput2() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_native_transfer(); + + operation.set_to_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02" + "wectaumxn0664yw2jd53lqk4mxg3"); + operation.set_change_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqds6ed78" + "yze6eyfyvd537z66ur22c9mmrgz82ama"); + operation.set_use_max_amount(true); + input.set_byte_fee(1); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(11410040620); + *cell1.mutable_out_point() = + OutPoint(parse_hex("c75567c80dc9b97aaf4e5c23f4c7f37b077f2b33a50dd7abd952abfbd5beb247"), 0) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto privateKey = parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey.begin(), privateKey.end())); + + return input; +} + +TEST(TWAnySignerNervos, Sign_Native_SendMaximum) { + auto input = getAnySignerInput2(); + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNervos); + ASSERT_EQ(output.error(), Common::Proto::OK); + // https://explorer.nervos.org/transaction/0x298f5e04b6900796614b89062eb96cec63c3b2c460d01058736a793b567bc5c8 + ASSERT_EQ(output.transaction_id(), + "0x298f5e04b6900796614b89062eb96cec63c3b2c460d01058736a793b567bc5c8"); + ASSERT_EQ( + output.transaction_json(), + "{\"cell_deps\":[{\"dep_type\":\"dep_group\",\"out_point\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c\"}}],\"header_deps\":" + "[],\"inputs\":[{\"previous_output\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0xc75567c80dc9b97aaf4e5c23f4c7f37b077f2b33a50dd7abd952abfbd5beb247\"},\"since\":\"0x0\"}" + "],\"outputs\":[{\"capacity\":\"0x2a81765c9\",\"lock\":{\"args\":" + "\"0xab201f55b02f53b385f79b34dfad548e549b48fc\",\"code_hash\":" + "\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_type\":" + "\"type\"},\"type\":null}],\"outputs_data\":[\"0x\"],\"version\":\"0x0\",\"witnesses\":[" + "\"0x5500000010000000550000005500000041000000daf6e65e5a1fe447a4feb7199886b6635c44738e04ea59" + "457608fb1c447e068026529d57b02014ddc144622f886153df426853f22083f8891461eeb50b5ce97d01\"]}"); +} + +Proto::SigningInput getAnySignerInput3() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_sudt_transfer(); + + operation.set_to_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02" + "wectaumxn0664yw2jd53lqk4mxg3"); + operation.set_change_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqds6ed78" + "yze6eyfyvd537z66ur22c9mmrgz82ama"); + uint256_t amount = 1000000000000000; + operation.set_amount(toString(amount)); + input.set_byte_fee(1); + auto sudtAddress = + parse_hex("9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8"); + *operation.mutable_sudt_address() = std::string(sudtAddress.begin(), sudtAddress.end()); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(14399998906); + *cell1.mutable_out_point() = + OutPoint(parse_hex("5b12911e7413e011f251c1fb5fae4e76fd5fcae4f0d4c6412dcc5b0bfcece823"), 0) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto& cell2 = *input.add_cell(); + cell2.set_capacity(14400000000); + *cell2.mutable_out_point() = + OutPoint(parse_hex("e118bd11a73d381daf288381ce182d92b6cf2f52d25886bbda9e1a61525c7c4a"), 0) + .proto(); + *cell2.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + *cell2.mutable_type() = + Script(parse_hex("5e7a36a77e68eecc013dfa2fe6a23f3b6c344b04005808694ae6dd45eea4cfd5"), + HashType::Type1, + parse_hex("9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8")) + .proto(); + auto cell2Data = parse_hex("00e0e4c9b9f84f000000000000000000"); + *cell2.mutable_data() = std::string(cell2Data.begin(), cell2Data.end()); + + auto& cell3 = *input.add_cell(); + cell3.set_capacity(8210025567); + *cell3.mutable_out_point() = + OutPoint(parse_hex("09a45a15e48f985b554a0b6e5f0857913cc492ec061cc9b0b2befa4b24609a4a"), 1) + .proto(); + *cell3.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqds6ed78yze6eyfyvd537z66ur22c9mmrgz82ama")) + .proto(); + + auto privateKey1 = + parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey1.begin(), privateKey1.end())); + auto privateKey2 = + parse_hex("0c8859a9d9084a8c2b55963268b352e258756f9240f2a1f4645c610ed191dae9"); + input.add_private_key(std::string(privateKey2.begin(), privateKey2.end())); + + return input; +} + +TEST(TWAnySignerNervos, Sign_SUDT_Simple) { + auto input = getAnySignerInput3(); + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNervos); + ASSERT_EQ(output.error(), Common::Proto::OK); + // https://explorer.nervos.org/transaction/0x9b15f2bea26b98201540d8e20e8b1c21d96dd77ad246520b405c6aabb7173371 + ASSERT_EQ(output.transaction_id(), + "0x9b15f2bea26b98201540d8e20e8b1c21d96dd77ad246520b405c6aabb7173371"); + ASSERT_EQ( + output.transaction_json(), + "{\"cell_deps\":[{\"dep_type\":\"dep_group\",\"out_point\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c\"}},{\"dep_type\":" + "\"code\",\"out_point\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0xc7813f6a415144643970c2e88e0bb6ca6a8edc5dd7c1022746f628284a9936d5\"}}],\"header_deps\":" + "[],\"inputs\":[{\"previous_output\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0xe118bd11a73d381daf288381ce182d92b6cf2f52d25886bbda9e1a61525c7c4a\"},\"since\":\"0x0\"}" + ",{\"previous_output\":{\"index\":\"0x1\",\"tx_hash\":" + "\"0x09a45a15e48f985b554a0b6e5f0857913cc492ec061cc9b0b2befa4b24609a4a\"},\"since\":\"0x0\"}" + ",{\"previous_output\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0x5b12911e7413e011f251c1fb5fae4e76fd5fcae4f0d4c6412dcc5b0bfcece823\"},\"since\":\"0x0\"}" + "],\"outputs\":[{\"capacity\":\"0x35a4e9000\",\"lock\":{\"args\":" + "\"0xab201f55b02f53b385f79b34dfad548e549b48fc\",\"code_hash\":" + "\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_type\":" + "\"type\"},\"type\":{\"args\":" + "\"0x9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8\",\"code_hash\":" + "\"0x5e7a36a77e68eecc013dfa2fe6a23f3b6c344b04005808694ae6dd45eea4cfd5\",\"hash_type\":" + "\"type\"}},{\"capacity\":\"0x35a4e9000\",\"lock\":{\"args\":" + "\"0xb0d65be39059d6489231b48f85ad706a560bbd8d\",\"code_hash\":" + "\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_type\":" + "\"type\"},\"type\":{\"args\":" + "\"0x9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8\",\"code_hash\":" + "\"0x5e7a36a77e68eecc013dfa2fe6a23f3b6c344b04005808694ae6dd45eea4cfd5\",\"hash_type\":" + "\"type\"}},{\"capacity\":\"0x1e95b03db\",\"lock\":{\"args\":" + "\"0xb0d65be39059d6489231b48f85ad706a560bbd8d\",\"code_hash\":" + "\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_type\":" + "\"type\"},\"type\":null}],\"outputs_data\":[\"0x0080c6a47e8d03000000000000000000\"," + "\"0x00601e253b6b4c000000000000000000\",\"0x\"],\"version\":\"0x0\",\"witnesses\":[" + "\"0x550000001000000055000000550000004100000035d55fd46316f248552eb6af7ac9589c9ec533c4e5b718" + "96b05cdf697e2d18551ceff54d7b860ebb2f4dd5f6c5bb4af1da15460a7621f5aa4bc7d5585a0504de00\"," + "\"0x5500000010000000550000005500000041000000eaa4bf69126d3016ab786610f2f0668b2ef353915d623d" + "0b0184fc25cec3dcad6dc08a1504a2d7dd9faced17b041d79d4c21f1977e57859713360f5e3609583501\"," + "\"0x\"]}"); +} + +Proto::SigningInput getAnySignerInput4() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_sudt_transfer(); + + operation.set_to_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02" + "wectaumxn0664yw2jd53lqk4mxg3"); + operation.set_change_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqds6ed78" + "yze6eyfyvd537z66ur22c9mmrgz82ama"); + operation.set_use_max_amount(true); + input.set_byte_fee(1); + auto sudtAddress = + parse_hex("9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8"); + *operation.mutable_sudt_address() = std::string(sudtAddress.begin(), sudtAddress.end()); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(8210026306); + *cell1.mutable_out_point() = + OutPoint(parse_hex("430cb60ee816e2631d6d9605659c18fec8eb3de94526f5fd4ad51feaad6f1664"), 0) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto& cell2 = *input.add_cell(); + cell2.set_capacity(14400000000); + *cell2.mutable_out_point() = + OutPoint(parse_hex("378b6bd2f7fc2b1599ee55be7e8fa17fdd6e0d25e2e146d5f46006e0292d6564"), 0) + .proto(); + *cell2.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + *cell2.mutable_type() = + Script(parse_hex("5e7a36a77e68eecc013dfa2fe6a23f3b6c344b04005808694ae6dd45eea4cfd5"), + HashType::Type1, + parse_hex("9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8")) + .proto(); + auto cell2Data = parse_hex("00601e253b6b4c000000000000000000"); + *cell2.mutable_data() = std::string(cell2Data.begin(), cell2Data.end()); + + auto privateKey = parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey.begin(), privateKey.end())); + + return input; +} + +TEST(TWAnySignerNervos, Sign_SUDT_SendMaximum) { + auto input = getAnySignerInput4(); + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNervos); + ASSERT_EQ(output.error(), Common::Proto::OK); + // https://explorer.nervos.org/transaction/0x09a45a15e48f985b554a0b6e5f0857913cc492ec061cc9b0b2befa4b24609a4a + ASSERT_EQ(output.transaction_id(), + "0x09a45a15e48f985b554a0b6e5f0857913cc492ec061cc9b0b2befa4b24609a4a"); + ASSERT_EQ(output.transaction_json(), + "{\"cell_deps\":[{\"dep_type\":\"dep_group\",\"out_point\":{\"index\":\"0x0\",\"tx_" + "hash\":\"0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c\"}},{" + "\"dep_type\":\"code\",\"out_point\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0xc7813f6a415144643970c2e88e0bb6ca6a8edc5dd7c1022746f628284a9936d5\"}}],\"header_" + "deps\":[],\"inputs\":[{\"previous_output\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0x378b6bd2f7fc2b1599ee55be7e8fa17fdd6e0d25e2e146d5f46006e0292d6564\"},\"since\":" + "\"0x0\"},{\"previous_output\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0x430cb60ee816e2631d6d9605659c18fec8eb3de94526f5fd4ad51feaad6f1664\"},\"since\":" + "\"0x0\"}],\"outputs\":[{\"capacity\":\"0x35a4e9000\",\"lock\":{\"args\":" + "\"0xab201f55b02f53b385f79b34dfad548e549b48fc\",\"code_hash\":" + "\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_" + "type\":\"type\"},\"type\":{\"args\":" + "\"0x9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8\",\"code_" + "hash\":\"0x5e7a36a77e68eecc013dfa2fe6a23f3b6c344b04005808694ae6dd45eea4cfd5\"," + "\"hash_type\":\"type\"}},{\"capacity\":\"0x1e95b0c5f\",\"lock\":{\"args\":" + "\"0xb0d65be39059d6489231b48f85ad706a560bbd8d\",\"code_hash\":" + "\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_" + "type\":\"type\"},\"type\":null}],\"outputs_data\":[" + "\"0x00601e253b6b4c000000000000000000\",\"0x\"],\"version\":\"0x0\",\"witnesses\":[" + "\"0x5500000010000000550000005500000041000000da7c908bdf2cb091b7ff9bb682b762d1323c5e1e" + "cf9b2ce0ebedb9d55f6625c52ab14910ae401833112f2ea516ab11bc9ef691c3dff7886e3238c9348c3d" + "73a701\",\"0x\"]}"); +} + +Proto::SigningInput getAnySignerInput5() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_dao_deposit(); + + operation.set_to_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8f" + "urras980hksatlslfaktks7epf25"); + operation.set_change_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9e" + "rg8furras980hksatlslfaktks7epf25"); + operation.set_amount(10200000000); + input.set_byte_fee(1); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(8210021909); + *cell1.mutable_out_point() = + OutPoint(parse_hex("c7dacd4aab49f5f9643e87752428cebde38eeb49c7726781c4d8b526822004a1"), 1) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqds6ed78yze6eyfyvd537z66ur22c9mmrgz82ama")) + .proto(); + + auto& cell2 = *input.add_cell(); + cell2.set_capacity(14399998167); + *cell2.mutable_out_point() = + OutPoint(parse_hex("d3c3263170815b326779e2fd8d548f846ae13eff9d9a82833c7071069a1d32bf"), 0) + .proto(); + *cell2.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto privateKey1 = + parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey1.begin(), privateKey1.end())); + auto privateKey2 = + parse_hex("0c8859a9d9084a8c2b55963268b352e258756f9240f2a1f4645c610ed191dae9"); + input.add_private_key(std::string(privateKey2.begin(), privateKey2.end())); + + return input; +} + +TEST(TWAnySignerNervos, Sign_DAO_Deposit) { + auto input = getAnySignerInput5(); + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNervos); + ASSERT_EQ(output.error(), Common::Proto::OK); + // https://explorer.nervos.org/transaction/0x583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683 + ASSERT_EQ(output.transaction_id(), + "0x583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683"); + ASSERT_EQ( + output.transaction_json(), + "{\"cell_deps\":[{\"dep_type\":\"dep_group\",\"out_point\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c\"}},{\"dep_type\":" + "\"code\",\"out_point\":{\"index\":\"0x2\",\"tx_hash\":" + "\"0xe2fb199810d49a4d8beec56718ba2593b665db9d52299a0f9e6e75416d73ff5c\"}}],\"header_deps\":" + "[],\"inputs\":[{\"previous_output\":{\"index\":\"0x1\",\"tx_hash\":" + "\"0xc7dacd4aab49f5f9643e87752428cebde38eeb49c7726781c4d8b526822004a1\"},\"since\":\"0x0\"}" + ",{\"previous_output\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0xd3c3263170815b326779e2fd8d548f846ae13eff9d9a82833c7071069a1d32bf\"},\"since\":\"0x0\"}" + "],\"outputs\":[{\"capacity\":\"0x25ff7a600\",\"lock\":{\"args\":" + "\"0xc4b50c5c8d074f063ec0a77ded0eaff0fa7b65da\",\"code_hash\":" + "\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_type\":" + "\"type\"},\"type\":{\"args\":\"0x\",\"code_hash\":" + "\"0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e\",\"hash_type\":" + "\"type\"}},{\"capacity\":\"0x2e3b1de31\",\"lock\":{\"args\":" + "\"0xc4b50c5c8d074f063ec0a77ded0eaff0fa7b65da\",\"code_hash\":" + "\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_type\":" + "\"type\"},\"type\":null}],\"outputs_data\":[\"0x0000000000000000\",\"0x\"],\"version\":" + "\"0x0\",\"witnesses\":[" + "\"0x5500000010000000550000005500000041000000305d09c7de3f34a4d53bc4e0031ee59c95b9abc4fc3ff5" + "548e1a17ca726c069a232012c9c4be6ec4d4ffbe88613ca5e686e3e4b7d0b9bbd7038003e23ffdcdd601\"," + "\"0x55000000100000005500000055000000410000007c514c77482dd1e1086f41a6d17364c9b5ed16364d61df" + "6f7fd8540f8bf7c131275c877943786b1b72fbf4f9d817ee5dd554a689808b7919543c691b5068e5be01\"]}"); +} + +Proto::SigningInput getAnySignerInput6() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_dao_withdraw_phase1(); + + operation.set_change_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9e" + "rg8furras980hksatlslfaktks7epf25"); + auto& depositCell = *operation.mutable_deposit_cell(); + depositCell.set_capacity(10200000000); + *depositCell.mutable_out_point() = + OutPoint(parse_hex("583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683"), 0) + .proto(); + *depositCell.mutable_lock() = + Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8furras9" + "80hksatlslfaktks7epf25")) + .proto(); + *depositCell.mutable_type() = + Script(parse_hex("82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e"), + HashType::Type1, Data()) + .proto(); + auto depositCellData = parse_hex("0000000000000000"); + *depositCell.mutable_data() = std::string(depositCellData.begin(), depositCellData.end()); + depositCell.set_block_number(7575466); + auto blockHashData = + parse_hex("3dfdb4b702a355a5593315016f8af0537d5a2f3292811b79420ded78a092be6a"); + *depositCell.mutable_block_hash() = std::string(blockHashData.begin(), blockHashData.end()); + input.set_byte_fee(1); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(10200000000); + *cell1.mutable_out_point() = + OutPoint(parse_hex("583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683"), 0) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + *cell1.mutable_type() = + Script(parse_hex("82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e"), + HashType::Type1, Data()) + .proto(); + auto cell1Data = parse_hex("0000000000000000"); + *cell1.mutable_data() = std::string(cell1Data.begin(), cell1Data.end()); + + auto& cell2 = *input.add_cell(); + cell2.set_capacity(12410019377); + *cell2.mutable_out_point() = + OutPoint(parse_hex("583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683"), 1) + .proto(); + *cell2.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto privateKey1 = + parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey1.begin(), privateKey1.end())); + + return input; +} + +TEST(TWAnySignerNervos, Sign_DAO_Withdraw_Phase1) { + auto input = getAnySignerInput6(); + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNervos); + ASSERT_EQ(output.error(), Common::Proto::OK); + // https://explorer.nervos.org/transaction/0xb4e62bc5f5108275b0ef3da8f8cc3fb0172843c4a2a9cdfef3b04d6c65e9acca + ASSERT_EQ(output.transaction_id(), + "0xb4e62bc5f5108275b0ef3da8f8cc3fb0172843c4a2a9cdfef3b04d6c65e9acca"); + ASSERT_EQ(output.transaction_json(), + "{\"cell_deps\":[{\"dep_type\":\"dep_group\",\"out_point\":{\"index\":\"0x0\",\"tx_" + "hash\":\"0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c\"}},{" + "\"dep_type\":\"code\",\"out_point\":{\"index\":\"0x2\",\"tx_hash\":" + "\"0xe2fb199810d49a4d8beec56718ba2593b665db9d52299a0f9e6e75416d73ff5c\"}}],\"header_" + "deps\":[\"0x3dfdb4b702a355a5593315016f8af0537d5a2f3292811b79420ded78a092be6a\"]," + "\"inputs\":[{\"previous_output\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0x583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683\"},\"since\":" + "\"0x0\"},{\"previous_output\":{\"index\":\"0x1\",\"tx_hash\":" + "\"0x583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683\"},\"since\":" + "\"0x0\"}],\"outputs\":[{\"capacity\":\"0x25ff7a600\",\"lock\":{\"args\":" + "\"0xc4b50c5c8d074f063ec0a77ded0eaff0fa7b65da\",\"code_hash\":" + "\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_" + "type\":\"type\"},\"type\":{\"args\":\"0x\",\"code_hash\":" + "\"0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e\",\"hash_" + "type\":\"type\"}},{\"capacity\":\"0x2e3b1db56\",\"lock\":{\"args\":" + "\"0xc4b50c5c8d074f063ec0a77ded0eaff0fa7b65da\",\"code_hash\":" + "\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_" + "type\":\"type\"},\"type\":null}],\"outputs_data\":[\"0xaa97730000000000\",\"0x\"]," + "\"version\":\"0x0\",\"witnesses\":[" + "\"0x5500000010000000550000005500000041000000d5131c1a6b8eca11e2c280b72c5db09ea00bb788" + "fd3262eaced861c39db2aad04a36f9d174b6f167a9c98b85d2bccf537a163c44459d23467dfa86408f48" + "dd5f01\",\"0x\"]}"); +} + +Proto::SigningInput getAnySignerInput7() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_dao_withdraw_phase2(); + + auto& depositCell = *operation.mutable_deposit_cell(); + depositCell.set_capacity(10200000000); + *depositCell.mutable_out_point() = + OutPoint(parse_hex("583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683"), 0) + .proto(); + *depositCell.mutable_lock() = + Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8furras9" + "80hksatlslfaktks7epf25")) + .proto(); + *depositCell.mutable_type() = + Script(parse_hex("82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e"), + HashType::Type1, Data()) + .proto(); + auto depositCellData = parse_hex("0000000000000000"); + *depositCell.mutable_data() = std::string(depositCellData.begin(), depositCellData.end()); + depositCell.set_block_number(7575466); + auto blockHashData1 = + parse_hex("3dfdb4b702a355a5593315016f8af0537d5a2f3292811b79420ded78a092be6a"); + *depositCell.mutable_block_hash() = std::string(blockHashData1.begin(), blockHashData1.end()); + + auto& withdrawingCell = *operation.mutable_withdrawing_cell(); + withdrawingCell.set_capacity(10200000000); + *withdrawingCell.mutable_out_point() = + OutPoint(parse_hex("b4e62bc5f5108275b0ef3da8f8cc3fb0172843c4a2a9cdfef3b04d6c65e9acca"), 0) + .proto(); + *withdrawingCell.mutable_lock() = + Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8furras9" + "80hksatlslfaktks7epf25")) + .proto(); + *withdrawingCell.mutable_type() = + Script(parse_hex("82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e"), + HashType::Type1, Data()) + .proto(); + auto withdrawingCellData = parse_hex("aa97730000000000"); + *withdrawingCell.mutable_data() = + std::string(withdrawingCellData.begin(), withdrawingCellData.end()); + withdrawingCell.set_block_number(7575534); + auto blockHashData2 = + parse_hex("b070d5364afd47c23fe267077d454009d6f665f200a915e68af1616e46f4aa52"); + *withdrawingCell.mutable_block_hash() = + std::string(blockHashData2.begin(), blockHashData2.end()); + withdrawingCell.set_since(0x20037c0000001731); + + operation.set_amount(10200000000); + input.set_byte_fee(1); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(10200000000); + *cell1.mutable_out_point() = + OutPoint(parse_hex("b4e62bc5f5108275b0ef3da8f8cc3fb0172843c4a2a9cdfef3b04d6c65e9acca"), 0) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + *cell1.mutable_type() = + Script(parse_hex("82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e"), + HashType::Type1, Data()) + .proto(); + auto cell1Data = parse_hex("aa97730000000000"); + *cell1.mutable_data() = std::string(cell1Data.begin(), cell1Data.end()); + + auto privateKey1 = + parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey1.begin(), privateKey1.end())); + + return input; +} + +TEST(TWAnySignerNervos, Sign_DAO_Withdraw_Phase2) { + auto input = getAnySignerInput7(); + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNervos); + ASSERT_EQ(output.error(), Common::Proto::OK); + ASSERT_EQ(output.transaction_id(), + "0x4fde13c93fc5d24ab7f660070aaa0b1725809d585f6258605e595cdbd856ea1c"); + ASSERT_EQ( + output.transaction_json(), + "{\"cell_deps\":[{\"dep_type\":\"dep_group\",\"out_point\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c\"}},{\"dep_type\":" + "\"code\",\"out_point\":{\"index\":\"0x2\",\"tx_hash\":" + "\"0xe2fb199810d49a4d8beec56718ba2593b665db9d52299a0f9e6e75416d73ff5c\"}}],\"header_deps\":" + "[\"0x3dfdb4b702a355a5593315016f8af0537d5a2f3292811b79420ded78a092be6a\"," + "\"0xb070d5364afd47c23fe267077d454009d6f665f200a915e68af1616e46f4aa52\"],\"inputs\":[{" + "\"previous_output\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0xb4e62bc5f5108275b0ef3da8f8cc3fb0172843c4a2a9cdfef3b04d6c65e9acca\"},\"since\":" + "\"0x20037c0000001731\"}],\"outputs\":[{\"capacity\":\"0x25ff7a42c\",\"lock\":{\"args\":" + "\"0xc4b50c5c8d074f063ec0a77ded0eaff0fa7b65da\",\"code_hash\":" + "\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_type\":" + "\"type\"},\"type\":null}],\"outputs_data\":[\"0x\"],\"version\":\"0x0\",\"witnesses\":[" + "\"0x6100000010000000550000006100000041000000743f86c5557f4e2d3327f4d17e7bad27209b29c1e9cdba" + "b42ab03f7094af917b4b203ddd7f2e87102e09ae579f2fe7f6adb7900b7386b58c1183ba0011b7c42100080000" + "000000000000000000\"]}"); +} + +} // namespace TW::Nervos::tests \ No newline at end of file diff --git a/tests/chains/Nervos/TWCoinTypeTests.cpp b/tests/chains/Nervos/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..1150536151a --- /dev/null +++ b/tests/chains/Nervos/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWNervosCoinType, TWCoinType) { + const auto coin = TWCoinTypeNervos; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "nervos"); + assertStringsEqual(name, "Nervos"); + assertStringsEqual(symbol, "CKB"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 8); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainNervos); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(txUrl, "https://explorer.nervos.org/transaction/t123"); + assertStringsEqual(accUrl, "https://explorer.nervos.org/address/a12"); +} diff --git a/tests/chains/Nervos/TWNervosAddressTests.cpp b/tests/chains/Nervos/TWNervosAddressTests.cpp new file mode 100644 index 00000000000..cf7a8560e0d --- /dev/null +++ b/tests/chains/Nervos/TWNervosAddressTests.cpp @@ -0,0 +1,30 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "HexCoding.h" +#include "PrivateKey.h" +#include + +#include "TestUtilities.h" +#include + +namespace TW::Nervos::tests { + +TEST(TWNervosAddress, Create) { + const auto ckbAddress = "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8furras980hksatlslfaktks7epf25"; + const auto addr = WRAP(TWNervosAddress, TWNervosAddressCreateWithString(STRING(ckbAddress).get())); + + const auto codeCash = WRAPD(TWNervosAddressCodeHash(addr.get())); + const auto args = WRAPD(TWNervosAddressArgs(addr.get())); + const auto hashType = WRAPS(TWNervosAddressHashType(addr.get())); + + EXPECT_TRUE(TWNervosAddressIsValidString(STRING(ckbAddress).get())); + assertHexEqual(codeCash, "9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8"); + assertHexEqual(args, "c4b50c5c8d074f063ec0a77ded0eaff0fa7b65da"); + assertStringsEqual(hashType, "type"); +} + +} // namespace TW::Nervos::tests diff --git a/tests/Nimiq/AddressTests.cpp b/tests/chains/Nimiq/AddressTests.cpp similarity index 88% rename from tests/Nimiq/AddressTests.cpp rename to tests/chains/Nimiq/AddressTests.cpp index 2bc4cfa2403..b25fda815cb 100644 --- a/tests/Nimiq/AddressTests.cpp +++ b/tests/chains/Nimiq/AddressTests.cpp @@ -12,7 +12,8 @@ #include using namespace TW; -using namespace TW::Nimiq; + +namespace TW::Nimiq::tests { TEST(NimiqAddress, IsValid) { // No address @@ -35,18 +36,15 @@ TEST(NimiqAddress, String) { // Address to string ASSERT_EQ( Address(parse_hex("5b3e9e5f32b89abafc3708765dc8f00216cefbb1")).string(), - "NQ61 BCY9 UPRJ P2DB MY1P 11T5 TJ7G 08BC VXVH" - ); + "NQ61 BCY9 UPRJ P2DB MY1P 11T5 TJ7G 08BC VXVH"); // Without spaces ASSERT_EQ( Address("NQ862H8FYGU5RM77QSN9LYLHC56ACYYR0MLA").string(), - "NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA" - ); + "NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA"); // With spaces ASSERT_EQ( Address("NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA").string(), - "NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA" - ); + "NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA"); } TEST(NimiqAddress, FromPublicKey) { @@ -55,3 +53,5 @@ TEST(NimiqAddress, FromPublicKey) { const auto address = Address(publicKey); ASSERT_EQ(address.string(), "NQ27 GBAY EVHP HK5X 6JHV JGFJ 5M3H BF4Y G7GD"); } + +} // namespace TW::Nimiq::tests diff --git a/tests/Nimiq/SignerTests.cpp b/tests/chains/Nimiq/SignerTests.cpp similarity index 100% rename from tests/Nimiq/SignerTests.cpp rename to tests/chains/Nimiq/SignerTests.cpp diff --git a/tests/Nimiq/TWAnySignerTests.cpp b/tests/chains/Nimiq/TWAnySignerTests.cpp similarity index 93% rename from tests/Nimiq/TWAnySignerTests.cpp rename to tests/chains/Nimiq/TWAnySignerTests.cpp index 5a827a87009..b9a9ee32b24 100644 --- a/tests/Nimiq/TWAnySignerTests.cpp +++ b/tests/chains/Nimiq/TWAnySignerTests.cpp @@ -8,11 +8,10 @@ #include "proto/Nimiq.pb.h" #include -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include -using namespace TW; -using namespace TW::Nimiq; +namespace TW::Nimiq::tests { TEST(TWAnySignerNimiq, Sign) { auto privateKey = parse_hex("e3cc33575834add098f8487123cd4bca543ee859b3e8cfe624e7e6a97202b756"); @@ -30,3 +29,5 @@ TEST(TWAnySignerNimiq, Sign) { EXPECT_EQ(hex(output.encoded()), "0070c7492aaa9c9ac7a05bc0d9c5db2dae9372029654f71f0c7f95deed5099b7021450ffc385cd4e7c6ac9a7e91614ca67ff90568a00000000028182ba00000000000003e80004cb2f2a74dc7f6e0ab58a0bf52cc6e8801b0cca132dd4229d9a3e3a3d2f90e4d8f045d981b771bf5fc3851a98f3c617b1a943228f963e910e061808a721cfa0e3cad50b"); } + +} // namespace TW::Nimiq::tests diff --git a/tests/Nimiq/TWCoinTypeTests.cpp b/tests/chains/Nimiq/TWCoinTypeTests.cpp similarity index 97% rename from tests/Nimiq/TWCoinTypeTests.cpp rename to tests/chains/Nimiq/TWCoinTypeTests.cpp index 40f585625e3..f973c71604e 100644 --- a/tests/Nimiq/TWCoinTypeTests.cpp +++ b/tests/chains/Nimiq/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Nimiq/TransactionTests.cpp b/tests/chains/Nimiq/TransactionTests.cpp similarity index 100% rename from tests/Nimiq/TransactionTests.cpp rename to tests/chains/Nimiq/TransactionTests.cpp diff --git a/tests/chains/OKXChain/TWCoinTypeTests.cpp b/tests/chains/OKXChain/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..af4eed1e128 --- /dev/null +++ b/tests/chains/OKXChain/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWCoinTypeOKXChain, TWCoinType) { + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeOKXChain)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x46C3A947E8248570FBD28E4FE456CC8F80DFD90716533878FB67857B95FA3D37")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeOKXChain, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x074faafd0b20fad2efa115b8ed7e75993e580b85")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeOKXChain, accId.get())); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeOKXChain)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeOKXChain)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeOKXChain), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeOKXChain)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeOKXChain)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeOKXChain)); + assertStringsEqual(symbol, "OKT"); + assertStringsEqual(txUrl, "https://www.oklink.com/en/okc/tx/0x46C3A947E8248570FBD28E4FE456CC8F80DFD90716533878FB67857B95FA3D37"); + assertStringsEqual(accUrl, "https://www.oklink.com/en/okc/address/0x074faafd0b20fad2efa115b8ed7e75993e580b85"); + assertStringsEqual(id, "okc"); + assertStringsEqual(name, "OKX Chain"); +} diff --git a/tests/Oasis/AddressTests.cpp b/tests/chains/Oasis/AddressTests.cpp similarity index 85% rename from tests/Oasis/AddressTests.cpp rename to tests/chains/Oasis/AddressTests.cpp index 543fb51fdcf..ce1436525f5 100644 --- a/tests/Oasis/AddressTests.cpp +++ b/tests/chains/Oasis/AddressTests.cpp @@ -6,13 +6,14 @@ #include "HexCoding.h" #include "Oasis/Address.h" -#include "PublicKey.h" #include "PrivateKey.h" +#include "PublicKey.h" #include #include using namespace TW; -using namespace TW::Oasis; + +namespace TW::Oasis::tests { TEST(OasisAddress, Valid) { ASSERT_TRUE(Address::isValid("oasis1qp0cnmkjl22gky6p6qeghjytt4v7dkxsrsmueweh")); @@ -26,8 +27,8 @@ TEST(OasisAddress, Invalid) { TEST(OasisAddress, ForceInvalid) { try { auto addressString = "oasis1qp0cnmkjl22gky6p6qeghjytt4v7dkxsrsmuewehj"; - auto address = Address( addressString ); - } catch( std::invalid_argument& e1 ) { + auto address = Address(addressString); + } catch (std::invalid_argument& e1) { return; } FAIL() << "This test should generate an exception as it an invalid address"; @@ -36,8 +37,8 @@ TEST(OasisAddress, ForceInvalid) { TEST(OasisAddress, FromWrongData) { try { auto dataString = "asdadfasdfsdfwrwrsadasdasdsad"; - auto address = Address( data( dataString ) ); - } catch( std::invalid_argument& e1 ) { + auto address = Address(data(dataString)); + } catch (std::invalid_argument& e1) { return; } FAIL() << "This test should generate an exception as it an invalid data"; @@ -57,12 +58,12 @@ TEST(OasisAddress, FromPublicKey) { TEST(OasisAddress, WrongPublicKeyType) { try { - auto publicKey = PublicKey(parse_hex("aba52c0dcb80c2fe96ed4c3741af40c573a0500c0d73acda22795c37cb0f1739"), TWPublicKeyTypeED25519Extended); + auto publicKey = PublicKey(parse_hex("aba52c0dcb80c2fe96ed4c3741af40c573a0500c0d73acda22795c37cb0f1739"), TWPublicKeyTypeED25519Cardano); auto address = Address(publicKey); - } catch( std::invalid_argument& e1 ) { + } catch (std::invalid_argument& e1) { return; } - FAIL() << "TWPublicKeyTypeED25519Extended should generate an exception as it an invalid publicKey type"; + FAIL() << "TWPublicKeyTypeED25519Cardano should generate an exception as it an invalid publicKey type"; } TEST(OasisAddress, FromString) { @@ -72,3 +73,5 @@ TEST(OasisAddress, FromString) { ASSERT_FALSE(Address::decode("oasis1hts399h023jqd7v6vgm6dxvcguwe4rgvqqgvq38ng", address)); } + +} // namespace TW::Oasis::tests diff --git a/tests/Oasis/SignerTests.cpp b/tests/chains/Oasis/SignerTests.cpp similarity index 66% rename from tests/Oasis/SignerTests.cpp rename to tests/chains/Oasis/SignerTests.cpp index 9e8b454f8d7..b4cc6ee2a6b 100644 --- a/tests/Oasis/SignerTests.cpp +++ b/tests/chains/Oasis/SignerTests.cpp @@ -4,16 +4,17 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "Oasis/Signer.h" -#include "Oasis/Address.h" #include "HexCoding.h" +#include "Oasis/Address.h" +#include "Oasis/Signer.h" #include "PrivateKey.h" #include "PublicKey.h" #include using namespace TW; -using namespace TW::Oasis; + +namespace TW::Oasis::tests { TEST(OasisSigner, Sign) { auto input = Proto::SigningInput(); @@ -33,5 +34,7 @@ TEST(OasisSigner, Sign) { Proto::SigningOutput output = Signer::sign(input); - ASSERT_EQ(hex(output.encoded()),"a273756e747275737465645f7261775f76616c7565585ea4656e6f6e636500666d6574686f64707374616b696e672e5472616e7366657263666565a2636761730066616d6f756e74410064626f6479a262746f5500c73cc001463434915ba3f39751beb7c0905b45eb66616d6f756e744400989680697369676e6174757265a26a7075626c69635f6b6579582093d8f8a455f50527976a8aa87ebde38d5606efa86cb985d3fb466aff37000e3b697369676e61747572655840e331ce731ed819106586152b13cd98ecf3248a880bdc71174ee3d83f6d5f3f8ee8fc34c19b22032f2f1e3e06d382720125d7a517fba9295c813228cc2b63170b"); + ASSERT_EQ(hex(output.encoded()), "a2697369676e6174757265a2697369676e617475726558406e51c18c9b2015c9b49414b3307336597f51ff331873d214ce2db81c9651a34d99529ccaa294a39ccd01c6b0bc2c2239d87c624e5ba4840cf99ac8f9283e240c6a7075626c69635f6b6579582093d8f8a455f50527976a8aa87ebde38d5606efa86cb985d3fb466aff37000e3b73756e747275737465645f7261775f76616c7565585ea463666565a2636761730066616d6f756e74410064626f6479a262746f5500c73cc001463434915ba3f39751beb7c0905b45eb66616d6f756e744400989680656e6f6e636500666d6574686f64707374616b696e672e5472616e73666572"); } + +} // namespace TW::Oasis::tests diff --git a/tests/Oasis/TWAnySignerTests.cpp b/tests/chains/Oasis/TWAnySignerTests.cpp similarity index 64% rename from tests/Oasis/TWAnySignerTests.cpp rename to tests/chains/Oasis/TWAnySignerTests.cpp index 0cae4b34bcd..d390b051db5 100644 --- a/tests/Oasis/TWAnySignerTests.cpp +++ b/tests/chains/Oasis/TWAnySignerTests.cpp @@ -8,11 +8,11 @@ #include "HexCoding.h" #include "proto/Oasis.pb.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include using namespace TW; -using namespace TW::Oasis; +namespace TW::Oasis::tests { TEST(TWAnySignerOasis, Sign) { auto input = Proto::SigningInput(); @@ -26,12 +26,13 @@ TEST(TWAnySignerOasis, Sign) { transfer.set_amount("10000000"); transfer.set_context("oasis-core/consensus: tx for chain a245619497e580dd3bc1aa3256c07f68b8dcc13f92da115eadc3b231b083d3c4"); - auto key = parse_hex("4f8b5676990b00e23d9904a92deb8d8f428ff289c8939926358f1d20537c21a0"); input.set_private_key(key.data(), key.size()); ANY_SIGN(input, TWCoinTypeOasis); - EXPECT_EQ("a273756e747275737465645f7261775f76616c7565585ea4656e6f6e636500666d6574686f64707374616b696e672e5472616e7366657263666565a2636761730066616d6f756e74410064626f6479a262746f5500c73cc001463434915ba3f39751beb7c0905b45eb66616d6f756e744400989680697369676e6174757265a26a7075626c69635f6b6579582093d8f8a455f50527976a8aa87ebde38d5606efa86cb985d3fb466aff37000e3b697369676e61747572655840e331ce731ed819106586152b13cd98ecf3248a880bdc71174ee3d83f6d5f3f8ee8fc34c19b22032f2f1e3e06d382720125d7a517fba9295c813228cc2b63170b", + EXPECT_EQ("a2697369676e6174757265a2697369676e617475726558406e51c18c9b2015c9b49414b3307336597f51ff331873d214ce2db81c9651a34d99529ccaa294a39ccd01c6b0bc2c2239d87c624e5ba4840cf99ac8f9283e240c6a7075626c69635f6b6579582093d8f8a455f50527976a8aa87ebde38d5606efa86cb985d3fb466aff37000e3b73756e747275737465645f7261775f76616c7565585ea463666565a2636761730066616d6f756e74410064626f6479a262746f5500c73cc001463434915ba3f39751beb7c0905b45eb66616d6f756e744400989680656e6f6e636500666d6574686f64707374616b696e672e5472616e73666572", hex(output.encoded())); } + +} // namespace TW::Oasis::tests diff --git a/tests/Oasis/TWCoinTypeTests.cpp b/tests/chains/Oasis/TWCoinTypeTests.cpp similarity index 95% rename from tests/Oasis/TWCoinTypeTests.cpp rename to tests/chains/Oasis/TWCoinTypeTests.cpp index ab703446d39..86ff3764832 100644 --- a/tests/Oasis/TWCoinTypeTests.cpp +++ b/tests/chains/Oasis/TWCoinTypeTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2021 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Ontology/AccountTests.cpp b/tests/chains/Ontology/AccountTests.cpp similarity index 88% rename from tests/Ontology/AccountTests.cpp rename to tests/chains/Ontology/AccountTests.cpp index e6dcbfa56e9..1fdf49f4806 100644 --- a/tests/Ontology/AccountTests.cpp +++ b/tests/chains/Ontology/AccountTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -13,7 +13,7 @@ #include using namespace TW; -using namespace TW::Ontology; +namespace TW::Ontology::tests { TEST(OntologyAccount, validity) { auto hexPrvKey = "4646464646464646464646464646464646464646464646464646464646464646"; @@ -23,4 +23,6 @@ TEST(OntologyAccount, validity) { auto pubKey = signer.getPublicKey(); EXPECT_EQ(hexPrvKey, hex(prvKey.bytes)); EXPECT_EQ(hexPubKey, hex(pubKey.bytes)); -} \ No newline at end of file +} + +} // namespace TW::Ontology::tests diff --git a/tests/Ontology/AddressTests.cpp b/tests/chains/Ontology/AddressTests.cpp similarity index 94% rename from tests/Ontology/AddressTests.cpp rename to tests/chains/Ontology/AddressTests.cpp index f27c2cdd691..f6917282e9b 100644 --- a/tests/Ontology/AddressTests.cpp +++ b/tests/chains/Ontology/AddressTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -12,8 +12,7 @@ #include -using namespace TW; -using namespace TW::Ontology; +namespace TW::Ontology::tests { TEST(OntologyAddress, validation) { ASSERT_FALSE(Address::isValid("abc")); @@ -44,4 +43,6 @@ TEST(OntologyAddress, fromMultiPubKeys) { uint8_t m = 2; auto multiAddress = Address(m, pubKeys); EXPECT_EQ("AYGWgijVZnrUa2tRoCcydsHUXR1111DgdW", multiAddress.string()); -} \ No newline at end of file +} + +} // namespace TW::Ontology::tests diff --git a/tests/chains/Ontology/Oep4Tests.cpp b/tests/chains/Ontology/Oep4Tests.cpp new file mode 100644 index 00000000000..be49303bd69 --- /dev/null +++ b/tests/chains/Ontology/Oep4Tests.cpp @@ -0,0 +1,140 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "HexCoding.h" + +#include "Ontology/Oep4.h" +#include "Ontology/Signer.h" +#include +#include + +namespace TW::Ontology::tests { + +TEST(OntologyOep4, name) { + std::string wing_hex{"ff31ec74d01f7b7d45ed2add930f5d2239f7de33"}; + auto wing_addr = Address(parse_hex(wing_hex.begin(), wing_hex.end())); + Oep4 wing(wing_addr); + + uint32_t nonce = 0x1234; + auto tx = wing.name(nonce); + auto rawTx = hex(tx.serialize()); + EXPECT_EQ("00d1341200000000000000000000000000000000000000000000000000000000000000000000000000001c00c1046e616d656733def739225d0f93dd2aed457d7b1fd074ec31ff0000", rawTx); +} + +TEST(OntologyOep4, symbol) { + std::string wing_hex{"ff31ec74d01f7b7d45ed2add930f5d2239f7de33"}; + auto wing_addr = Address(parse_hex(wing_hex.begin(), wing_hex.end())); + Oep4 wing(wing_addr); + + uint32_t nonce = 0x1234; + auto tx = wing.symbol(nonce); + auto rawTx = hex(tx.serialize()); + EXPECT_EQ("00d1341200000000000000000000000000000000000000000000000000000000000000000000000000001e00c10673796d626f6c6733def739225d0f93dd2aed457d7b1fd074ec31ff0000", rawTx); +} + +TEST(OntologyOep4, decimals) { + std::string wing_hex{"ff31ec74d01f7b7d45ed2add930f5d2239f7de33"}; + auto wing_addr = Address(parse_hex(wing_hex.begin(), wing_hex.end())); + Oep4 wing(wing_addr); + + uint32_t nonce = 0x1234; + auto tx = wing.decimals(nonce); + auto rawTx = hex(tx.serialize()); + EXPECT_EQ("00d1341200000000000000000000000000000000000000000000000000000000000000000000000000002000c108646563696d616c736733def739225d0f93dd2aed457d7b1fd074ec31ff0000", rawTx); +} + +TEST(OntologyOep4, totalSupply) { + std::string wing_hex{"ff31ec74d01f7b7d45ed2add930f5d2239f7de33"}; + auto wing_addr = Address(parse_hex(wing_hex.begin(), wing_hex.end())); + Oep4 wing(wing_addr); + + uint32_t nonce = 0x1234; + auto tx = wing.totalSupply(nonce); + auto rawTx = hex(tx.serialize()); + EXPECT_EQ("00d1341200000000000000000000000000000000000000000000000000000000000000000000000000002300c10b746f74616c537570706c796733def739225d0f93dd2aed457d7b1fd074ec31ff0000", rawTx); +} + +TEST(OntologyOep4, balanceOf) { + std::string wing_hex{"ff31ec74d01f7b7d45ed2add930f5d2239f7de33"}; + auto wing_addr = Address(parse_hex(wing_hex.begin(), wing_hex.end())); + Oep4 wing(wing_addr); + auto user = Address("AeaThtPwh5kAYnjHavzwmvxPd725nVTvbM"); + + uint32_t nonce = 0x1234; + auto tx = wing.balanceOf(user, nonce); + auto rawTx = hex(tx.serialize()); + EXPECT_EQ("00d1341200000000000000000000000000000000000000000000000000000000000000000000000000003614fa2254ffaee3c3e1172e8e98f800e4105c74988e51c10962616c616e63654f666733def739225d0f93dd2aed457d7b1fd074ec31ff0000", rawTx); +} + +TEST(OntologyOep4, addressHack) { + auto ownerbin = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); + auto payerbin = parse_hex("4646464646464646464646464646464646464646464646464646464646464652"); + + PrivateKey owner(ownerbin); + PrivateKey payer(payerbin); + + auto pubKey = owner.getPublicKey(TWPublicKeyTypeNIST256p1); + Address addr(pubKey); + + EXPECT_EQ("AeicEjZyiXKgUeSBbYQHxsU1X3V5Buori5", addr.string()); + + auto payerpub_key = payer.getPublicKey(TWPublicKeyTypeNIST256p1); + Address payer_addr(payerpub_key); + EXPECT_EQ("APniYDGozkhUh8Tk7pe35aah2HGJ4fJfVd", payer_addr.string()); +} + +TEST(OntologyOep4, transfer) { + auto from = Signer( + PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464652"))); + + auto payer = Signer( + PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"))); + + auto toAddress = Address("AVY6LfvxauVQAVHDV9hC3ZCv7cQqzfDotH"); + + uint32_t nonce = 0x1234; + uint64_t amount = 233; + uint64_t gasPrice = 2500; + uint64_t gasLimit = 50000; + + std::string wing_hex{"ff31ec74d01f7b7d45ed2add930f5d2239f7de33"}; + auto wing_addr = Address(parse_hex(wing_hex.begin(), wing_hex.end())); + Oep4 wing(wing_addr); + + auto tx = wing.transfer(from, toAddress, amount, payer, gasPrice, gasLimit, nonce); + auto rawTx = hex(tx.serialize()); + // Transaction Hex tab + // https://explorer.ont.io/testnet/tx/710266b8d497e794ecd47e01e269e4aeb6f4ff2b01eaeafc4cd371e062b13757 + EXPECT_EQ("00d134120000c40900000000000050c3000000000000fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a4d02e9001496f688657b95be51c11a87b51adfda4ab69e9cbb1457e9d1a61f9aafa798b6c7fbeae35639681d7df653c1087472616e736665726733def739225d0f93dd2aed457d7b1fd074ec31ff00024140bd2923854d7b84b97a107bb3cddf18c8e3dddd2f36b41a1f5f5b23366484daa22871cfb819923fe01e9cb1e9ed16baa2b05c2feb76bcbe2ec125f72701c5e965232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac41406d638653597774ce45812ea2653250806b657b32b7c6ad3e027ddeba91e9a9da4bb5dacd23dafba868cb31bacb38b4a6ff2607682a426c1dc09b05a1e158d6cd2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac", rawTx); +} + +TEST(OntologyOep4, transferMainnet) { + auto from = Signer( + PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464652"))); + + auto payer = Signer( + PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"))); + + auto toAddress = Address("AUJJhwRNi4RsNfvuexLETxXEb6szu9D5Ad"); + + uint32_t nonce = 0x1234; + uint64_t amount = 233; + uint64_t gasPrice = 2500; + uint64_t gasLimit = 50000; + + // wing oep4 mainnet address + std::string wing_hex{"00c59fcd27a562d6397883eab1f2fff56e58ef80"}; + auto wing_addr = Address(parse_hex(wing_hex.begin(), wing_hex.end())); + Oep4 wing(wing_addr); + + auto tx = wing.transfer(from, toAddress, amount, payer, gasPrice, gasLimit, nonce); + auto rawTx = hex(tx.serialize()); + // Transaction Hex tab + // https://explorer.ont.io/tx/70b276aaeb6b4578237390ec339b6a196f4620bdef8df1717032d32576ccef4a + EXPECT_EQ("00d134120000c40900000000000050c3000000000000fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a4d02e900148962e81f62cb76068b5f204ea5425d64d57147191457e9d1a61f9aafa798b6c7fbeae35639681d7df653c1087472616e736665726780ef586ef5fff2b1ea837839d662a527cd9fc500000241403c3a5e738f99e8f98ac4f59e225e549e2483bb60aee1771ef8ef189255e1670825d6a4c401f2e103348877393d8355c4d295b21fdfaf3dc4fea9b0459f1e1507232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac41409501ccaab299dc660da9084dd6e8f22658f7687e77319b17b97149c3f023806d04b300baa52874eae57ccde935bb64e2c16c59e00e0efe7086ae93c1153b80722321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac", rawTx); +} + +} // namespace TW::Ontology::tests diff --git a/tests/Ontology/OngTests.cpp b/tests/chains/Ontology/OngTests.cpp similarity index 97% rename from tests/Ontology/OngTests.cpp rename to tests/chains/Ontology/OngTests.cpp index afb5ba4fd67..d4b4994ca50 100644 --- a/tests/Ontology/OngTests.cpp +++ b/tests/chains/Ontology/OngTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -11,8 +11,7 @@ #include #include -using namespace TW; -using namespace TW::Ontology; +namespace TW::Ontology::tests { TEST(OntologyOng, decimals) { uint32_t nonce = 0; @@ -78,4 +77,6 @@ TEST(OntologyOng, withdraw) { "0ea6435f6f2b1335192a5d1b346fd431e8af912bfa4e1a23ad7d0ab7fc5b808655af5c9043232103d9fd62df33" "2403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", rawTx); -} \ No newline at end of file +} + +} // namespace TW::Ontology::tests diff --git a/tests/Ontology/OntTests.cpp b/tests/chains/Ontology/OntTests.cpp similarity index 96% rename from tests/Ontology/OntTests.cpp rename to tests/chains/Ontology/OntTests.cpp index e2d26a06895..20e9c3bbeb6 100644 --- a/tests/Ontology/OntTests.cpp +++ b/tests/chains/Ontology/OntTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -13,8 +13,7 @@ #include #include -using namespace TW; -using namespace TW::Ontology; +namespace TW::Ontology::tests { TEST(OntologyOnt, decimals) { uint32_t nonce = 0; @@ -57,4 +56,6 @@ TEST(OntologyOnt, transfer) { "0576d7b092fabafd0913a67ccf8b2f8e3d2bd708f768c2bb67e2d2f759805608232103d9fd62df332403" "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", rawTx); -} \ No newline at end of file +} + +} // namespace TW::Ontology::tests diff --git a/tests/Ontology/ParamsBuilderTests.cpp b/tests/chains/Ontology/ParamsBuilderTests.cpp similarity index 58% rename from tests/Ontology/ParamsBuilderTests.cpp rename to tests/chains/Ontology/ParamsBuilderTests.cpp index bb8a3540d1e..0534472852d 100644 --- a/tests/Ontology/ParamsBuilderTests.cpp +++ b/tests/chains/Ontology/ParamsBuilderTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -13,8 +13,7 @@ #include -using namespace TW; -using namespace TW::Ontology; +namespace TW::Ontology::tests { TEST(ParamsBuilder, pushInt) { std::vector numVector{0, @@ -51,7 +50,7 @@ TEST(ParamsBuilder, pushInt) { "050000000010", "08ffffffffffffff00", "08ffffffffffffff0f"}; - for (auto index = 0; index < numVector.size(); index++) { + for (auto index = 0ul; index < numVector.size(); index++) { auto builder = ParamsBuilder(); builder.push(numVector[index]); EXPECT_EQ(codeVector[index], hex(builder.getBytes())); @@ -59,9 +58,9 @@ TEST(ParamsBuilder, pushInt) { } TEST(ParamsBuilder, balanceInvokeCode) { - auto balanceParam = Address("ANDfjwrUroaVtvBguDtrWKRMyxFwvVwnZD").data; + auto balanceParam = Address("ANDfjwrUroaVtvBguDtrWKRMyxFwvVwnZD")._data; auto invokeCode = ParamsBuilder::buildNativeInvokeCode(Ont().contractAddress(), 0x00, - "balanceOf", balanceParam); + "balanceOf", {balanceParam}); auto hexInvokeCode = "1446b1a18af6b7c9f8a4602f9f73eeb3030f0c29b70962616c616e63654f661400000000000000000000000000" "000000000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b65"; @@ -69,16 +68,60 @@ TEST(ParamsBuilder, balanceInvokeCode) { } TEST(ParamsBuilder, transferInvokeCode) { - auto fromAddress = Address("ANDfjwrUroaVtvBguDtrWKRMyxFwvVwnZD").data; - auto toAddress = Address("Af1n2cZHhMZumNqKgw9sfCNoTWu9de4NDn").data; + auto fromAddress = Address("ANDfjwrUroaVtvBguDtrWKRMyxFwvVwnZD")._data; + auto toAddress = Address("Af1n2cZHhMZumNqKgw9sfCNoTWu9de4NDn")._data; uint64_t amount = 1; - std::list transferParam{fromAddress, toAddress, amount}; - std::vector args{transferParam}; + NeoVmParamValue::ParamList transferParam{fromAddress, toAddress, amount}; + NeoVmParamValue::ParamArray args{transferParam}; auto invokeCode = - ParamsBuilder::buildNativeInvokeCode(Ont().contractAddress(), 0x00, "transfer", args); + ParamsBuilder::buildNativeInvokeCode(Ont().contractAddress(), 0x00, "transfer", {args}); auto hexInvokeCode = "00c66b1446b1a18af6b7c9f8a4602f9f73eeb3030f0c29b76a7cc814feec06b79ed299ea06fcb94abac41aaf3e" "ad76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000010068" "164f6e746f6c6f67792e4e61746976652e496e766f6b65"; EXPECT_EQ(hexInvokeCode, hex(invokeCode)); -} \ No newline at end of file +} + +TEST(ParamsBuilder, invokeOep4Code) { + std::string wing_hex{"ff31ec74d01f7b7d45ed2add930f5d2239f7de33"}; + auto wing_addr = Address(parse_hex(wing_hex.begin(), wing_hex.end())); + + NeoVmParamValue::ParamArray args{}; + std::string method{"name"}; + auto invokeCode = ParamsBuilder::buildOep4InvokeCode(wing_addr, method, {args}); + + auto expectCode = "00c1046e616d656733def739225d0f93dd2aed457d7b1fd074ec31ff"; + EXPECT_EQ(expectCode, hex(invokeCode)); +} + +TEST(ParamsBuilder, invokeOep4CodeBalanceOf) { + std::string wing_hex{"ff31ec74d01f7b7d45ed2add930f5d2239f7de33"}; + auto wing_addr = Address(parse_hex(wing_hex.begin(), wing_hex.end())); + auto user_addr = Address("AeaThtPwh5kAYnjHavzwmvxPd725nVTvbM"); + Data d(std::begin(user_addr._data), std::end(user_addr._data)); + + NeoVmParamValue::ParamArray args{d}; + std::string method{"balanceOf"}; + auto invokeCode = ParamsBuilder::buildOep4InvokeCode(wing_addr, method, {args}); + + auto expectCode = "14fa2254ffaee3c3e1172e8e98f800e4105c74988e51c10962616c616e63654f666733def739225d0f93dd2aed457d7b1fd074ec31ff"; + EXPECT_EQ(expectCode, hex(invokeCode)); +} + +TEST(OntologyOep4, invokeOep4CodeTransfer) { + std::string wing_hex{"ff31ec74d01f7b7d45ed2add930f5d2239f7de33"}; + auto wing_addr = Address(parse_hex(wing_hex.begin(), wing_hex.end())); + auto from = Address("APniYDGozkhUh8Tk7pe35aah2HGJ4fJfVd"); + auto to = Address("AVY6LfvxauVQAVHDV9hC3ZCv7cQqzfDotH"); + uint64_t amount = 253; + + NeoVmParamValue::ParamArray args{from._data, to._data, amount}; + std::reverse(args.begin(), args.end()); + std::string method{"transfer"}; + auto invokeCode = ParamsBuilder::buildOep4InvokeCode(wing_addr, method, {args}); + + auto expectCode = "02fd001496f688657b95be51c11a87b51adfda4ab69e9cbb1457e9d1a61f9aafa798b6c7fbeae35639681d7df653c1087472616e736665726733def739225d0f93dd2aed457d7b1fd074ec31ff"; + EXPECT_EQ(expectCode, hex(invokeCode)); +} + +} // namespace TW::Ontology::tests diff --git a/tests/Ontology/TWAnySignerTests.cpp b/tests/chains/Ontology/TWAnySignerTests.cpp similarity index 78% rename from tests/Ontology/TWAnySignerTests.cpp rename to tests/chains/Ontology/TWAnySignerTests.cpp index bb6786dc0ef..99f2c8b1559 100644 --- a/tests/Ontology/TWAnySignerTests.cpp +++ b/tests/chains/Ontology/TWAnySignerTests.cpp @@ -1,12 +1,13 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. #include "HexCoding.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" +#include "Ontology/Oep4TxBuilder.h" #include "Ontology/OngTxBuilder.h" #include "Ontology/OntTxBuilder.h" @@ -15,7 +16,8 @@ #include using namespace TW; -using namespace TW::Ontology; + +namespace TW::Ontology::tests { TEST(TWAnySingerOntology, OntBalanceOf) { // curl -H "Content-Type: application/json" -X POST -d '{"Action":"sendrawtransaction", @@ -188,3 +190,51 @@ TEST(TWAnySingerOntology, OngWithdraw) { "2403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", rawTx); } + +TEST(TWAnySingerOntology, Oep4Decimal) { + auto input = Proto::SigningInput(); + input.set_contract("ff31ec74d01f7b7d45ed2add930f5d2239f7de33"); + input.set_method("decimals"); + input.set_nonce(0x1234); + auto data = Oep4TxBuilder::build(input); + auto rawTx = hex(data); + EXPECT_EQ("00d1341200000000000000000000000000000000000000000000000000000000000000000000000000002000c108646563696d616c736733def739225d0f93dd2aed457d7b1fd074ec31ff0000", rawTx); +} + +TEST(TWAnySingerOntology, Oep4BalanceOf) { + // read only method don't need signer + auto input = Proto::SigningInput(); + input.set_contract("ff31ec74d01f7b7d45ed2add930f5d2239f7de33"); + input.set_method("balanceOf"); + input.set_query_address("AeaThtPwh5kAYnjHavzwmvxPd725nVTvbM"); + input.set_nonce(0x1234); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeOntology); + EXPECT_EQ("00d1341200000000000000000000000000000000000000000000000000000000000000000000000000003614fa2254ffaee3c3e1172e8e98f800e4105c74988e51c10962616c616e63654f666733def739225d0f93dd2aed457d7b1fd074ec31ff0000", hex(output.encoded())); +} + +TEST(TWAnySingerOntology, Oep4Transfer) { + // https://explorer.ont.io/testnet/tx/710266b8d497e794ecd47e01e269e4aeb6f4ff2b01eaeafc4cd371e062b13757 + auto ownerPrivateKey = + parse_hex("4646464646464646464646464646464646464646464646464646464646464652"); + auto payerPrivateKey = + parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); + auto input = Proto::SigningInput(); + input.set_contract("ff31ec74d01f7b7d45ed2add930f5d2239f7de33"); + input.set_method("transfer"); + input.set_owner_private_key(ownerPrivateKey.data(), ownerPrivateKey.size()); + input.set_payer_private_key(payerPrivateKey.data(), payerPrivateKey.size()); + input.set_to_address("AVY6LfvxauVQAVHDV9hC3ZCv7cQqzfDotH"); + input.set_amount(233); + input.set_gas_price(2500); + input.set_gas_limit(50000); + input.set_nonce(0x1234); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeOntology); + + EXPECT_EQ("00d134120000c40900000000000050c3000000000000fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a4d02e9001496f688657b95be51c11a87b51adfda4ab69e9cbb1457e9d1a61f9aafa798b6c7fbeae35639681d7df653c1087472616e736665726733def739225d0f93dd2aed457d7b1fd074ec31ff00024140bd2923854d7b84b97a107bb3cddf18c8e3dddd2f36b41a1f5f5b23366484daa22871cfb819923fe01e9cb1e9ed16baa2b05c2feb76bcbe2ec125f72701c5e965232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac41406d638653597774ce45812ea2653250806b657b32b7c6ad3e027ddeba91e9a9da4bb5dacd23dafba868cb31bacb38b4a6ff2607682a426c1dc09b05a1e158d6cd2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac", hex(output.encoded())); +} + +} // namespace TW::Ontology::tests diff --git a/tests/Ontology/TWCoinTypeTests.cpp b/tests/chains/Ontology/TWCoinTypeTests.cpp similarity index 94% rename from tests/Ontology/TWCoinTypeTests.cpp rename to tests/chains/Ontology/TWCoinTypeTests.cpp index 30bb745ab50..4bada2c0a47 100644 --- a/tests/Ontology/TWCoinTypeTests.cpp +++ b/tests/chains/Ontology/TWCoinTypeTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Ontology/TransactionTests.cpp b/tests/chains/Ontology/TransactionTests.cpp similarity index 85% rename from tests/Ontology/TransactionTests.cpp rename to tests/chains/Ontology/TransactionTests.cpp index 0c8d4c7b254..ccf7fc14094 100644 --- a/tests/Ontology/TransactionTests.cpp +++ b/tests/chains/Ontology/TransactionTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -16,8 +16,7 @@ #include #include -using namespace TW; -using namespace TW::Ontology; +namespace TW::Ontology::tests { TEST(OntologyTransaction, validity) { std::vector ontContract{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -25,9 +24,9 @@ TEST(OntologyTransaction, validity) { auto fromAddress = Address("AeicEjZyiXKgUeSBbYQHxsU1X3V5Buori5"); auto toAddress = Address("APniYDGozkhUh8Tk7pe35aah2HGJ4fJfVd"); uint64_t amount = 1; - std::list transferParam{fromAddress.data, toAddress.data, amount}; - std::vector args{transferParam}; - auto invokeCode = ParamsBuilder::buildNativeInvokeCode(ontContract, 0x00, "transfer", args); + NeoVmParamValue::ParamList transferParam{fromAddress._data, toAddress._data, amount}; + NeoVmParamValue::ParamArray args{transferParam}; + auto invokeCode = ParamsBuilder::buildNativeInvokeCode(ontContract, 0x00, "transfer", {args}); uint8_t version = 0; uint8_t txType = 0xd1; uint32_t nonce = 1552759011; @@ -50,7 +49,7 @@ TEST(OntologyTransaction, validity) { "6e746f6c6f67792e4e61746976652e496e766f6b6500014140e03a09d85f56d2ceb5817a1f3a430bab9bf0f469" "da38afe4a5b33de258a06236d8e0a59d25918a49825455c99f91de9caf8071e38a589a530519705af9081eca23" "21031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac"; - EXPECT_EQ(520, hex(tx.serialize()).length()); + EXPECT_EQ(520ul, hex(tx.serialize()).length()); EXPECT_EQ(hexTx.substr(0, 20), hex(tx.serialize()).substr(0, 20)); auto signer2 = Signer(PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464652"))); signer2.addSign(tx); @@ -59,7 +58,9 @@ TEST(OntologyTransaction, validity) { hex(result).find("21031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac"); auto verifyPosition2 = hex(result).find("2103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac"); - EXPECT_EQ(450, verifyPosition1); - EXPECT_EQ(654, verifyPosition2); - EXPECT_EQ(724, hex(result).length()); -} \ No newline at end of file + EXPECT_EQ(450ul, verifyPosition1); + EXPECT_EQ(654ul, verifyPosition2); + EXPECT_EQ(724ul, hex(result).length()); +} + +} // namespace TW::Ontology::tests diff --git a/tests/Optimism/TWCoinTypeTests.cpp b/tests/chains/Optimism/TWCoinTypeTests.cpp similarity index 94% rename from tests/Optimism/TWCoinTypeTests.cpp rename to tests/chains/Optimism/TWCoinTypeTests.cpp index 11a0910a505..1cc6ff0e4c3 100644 --- a/tests/Optimism/TWCoinTypeTests.cpp +++ b/tests/chains/Optimism/TWCoinTypeTests.cpp @@ -5,7 +5,7 @@ // file LICENSE at the root of the source code distribution tree. // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -22,7 +22,7 @@ TEST(TWOptimismCoinType, TWCoinType) { ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeOptimism), 18); ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeOptimism)); - assertStringsEqual(symbol, "OETH"); + assertStringsEqual(symbol, "ETH"); assertStringsEqual(txUrl, "https://optimistic.etherscan.io/tx/0x6fd99288be9bf71eb002bb31da10a4fb0fbbb3c45ae73693b212f49c9db7df8f"); assertStringsEqual(accUrl, "https://optimistic.etherscan.io/address/0x1f932361e31d206b4f6b2478123a9d0f8c761031"); assertStringsEqual(id, "optimism"); diff --git a/tests/chains/Osmosis/AddressTests.cpp b/tests/chains/Osmosis/AddressTests.cpp new file mode 100644 index 00000000000..68011e0dc75 --- /dev/null +++ b/tests/chains/Osmosis/AddressTests.cpp @@ -0,0 +1,46 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Cosmos/Address.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include +#include + +namespace TW::Cosmos::tests { + +TEST(OsmosisAddress, Valid) { + EXPECT_TRUE(Address::isValid(TWCoinTypeOsmosis, "osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5")); + EXPECT_TRUE(Address::isValid(TWCoinTypeOsmosis, "osmo18s0hdnsllgcclweu9aymw4ngktr2k0rkvn7jmn")); +} + +TEST(OsmosisAddress, Invalid) { + EXPECT_FALSE(Address::isValid(TWCoinTypeOsmosis, "osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f6")); + EXPECT_FALSE(Address::isValid(TWCoinTypeOsmosis, "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02")); // valid cosmos +} + +TEST(OsmosisAddress, FromPrivateKey) { + auto privateKey = PrivateKey(parse_hex("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af")); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + auto address = Address(TWCoinTypeOsmosis, publicKey); + ASSERT_EQ(address.string(), "osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5"); +} + +TEST(OsmosisAddress, FromPublicKey) { + auto publicKey = PublicKey(parse_hex("02ecef5ce437a302c67f95468de4b31f36e911f467d7e6a52b41c1e13e1d563649"), TWPublicKeyTypeSECP256k1); + auto address = Address(TWCoinTypeOsmosis, publicKey); + ASSERT_EQ(address.string(), "osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5"); +} + +TEST(OsmosisAddress, FromString) { + Address address; + EXPECT_TRUE(Address::decode("osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5", address)); + EXPECT_EQ(address.string(), "osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5"); + EXPECT_EQ(hex(address.getKeyHash()), "dd89a2e267cd96e23cf5a33382f030686f65996f"); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Osmosis/SignerTests.cpp b/tests/chains/Osmosis/SignerTests.cpp new file mode 100644 index 00000000000..91dd335f25e --- /dev/null +++ b/tests/chains/Osmosis/SignerTests.cpp @@ -0,0 +1,59 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Cosmos/Address.h" +#include "Cosmos/Signer.h" +#include "HexCoding.h" +#include "PublicKey.h" +#include "proto/Cosmos.pb.h" +#include "TestUtilities.h" + +#include + +namespace TW::Cosmos::tests { + +TEST(OsmosisSigner, SignTransfer_81B4) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(124703); + input.set_chain_id("osmosis-1"); + input.set_memo(""); + input.set_sequence(0); + + Address fromAddress; + Address toAddress; + EXPECT_TRUE(Address::decode("osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5", fromAddress)); + EXPECT_TRUE(Address::decode("osmo18s0hdnsllgcclweu9aymw4ngktr2k0rkvn7jmn", toAddress)); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address(fromAddress.string()); + message.set_to_address(toAddress.string()); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("uosmo"); + amountOfTx->set_amount("99800"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uosmo"); + amountOfFee->set_amount("200"); + + auto privateKey = parse_hex("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeOsmosis); + + // https://www.mintscan.io/osmosis/txs/81B4F01BDE72AF7FF4536E5D7E66EB218E9FC9ACAA7C5EB5DB237DD0595D5F5F + // curl -H 'Content-Type: application/json' --data-binary '{"tx_bytes": "Co0B...rYVj", "mode": "BROADCAST_MODE_BLOCK"}' https://lcd-osmosis.keplr.app/cosmos/tx/v1beta1/txs + + assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"Co0BCooBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmoKK29zbW8xbWt5Njljbjhla3R3eTA4NDV2ZWM5dXBzZHBoa3R4dDBlbjk3ZjUSK29zbW8xOHMwaGRuc2xsZ2NjbHdldTlheW13NG5na3RyMmswcmt2bjdqbW4aDgoFdW9zbW8SBTk5ODAwEmQKTgpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQLs71zkN6MCxn+VRo3ksx826RH0Z9fmpStBweE+HVY2SRIECgIIARISCgwKBXVvc21vEgMyMDAQwJoMGkAMY//Md5GRUR4lVZhk558hFS3kii9QZYoYKfg4+ac/xgNeyoiEweVDhcmEvlH1orVwjLUOnYs4ly2a/yIurYVj\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); + EXPECT_EQ(hex(output.signature()), "0c63ffcc779191511e25559864e79f21152de48a2f50658a1829f838f9a73fc6035eca8884c1e54385c984be51f5a2b5708cb50e9d8b38972d9aff222ead8563"); + EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.json(), ""); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Osmosis/TWAnyAddressTests.cpp b/tests/chains/Osmosis/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..40793a73acf --- /dev/null +++ b/tests/chains/Osmosis/TWAnyAddressTests.cpp @@ -0,0 +1,28 @@ +// Copyright © 2017-2021 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TWOsmosisAnyAddress, IsValid) { + EXPECT_TRUE(TWAnyAddressIsValid(STRING("osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5").get(), TWCoinTypeOsmosis)); + EXPECT_TRUE(TWAnyAddressIsValid(STRING("osmo18s0hdnsllgcclweu9aymw4ngktr2k0rkvn7jmn").get(), TWCoinTypeOsmosis)); + EXPECT_FALSE(TWAnyAddressIsValid(STRING("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02").get(), TWCoinTypeOsmosis)); +} + +TEST(TWOsmosisAnyAddress, Create) { + auto string = STRING("osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeOsmosis)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "dd89a2e267cd96e23cf5a33382f030686f65996f"); +} diff --git a/tests/chains/Osmosis/TWAnySignerTests.cpp b/tests/chains/Osmosis/TWAnySignerTests.cpp new file mode 100644 index 00000000000..95f230551a8 --- /dev/null +++ b/tests/chains/Osmosis/TWAnySignerTests.cpp @@ -0,0 +1,57 @@ +// Copyright © 2017-2021 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 "Cosmos/Address.h" +#include "HexCoding.h" +#include "proto/Cosmos.pb.h" +#include + +#include "TestUtilities.h" +#include + +namespace TW::Cosmos::tests { + +TEST(TWAnySignerOsmosis, Sign) { + auto privateKey = parse_hex("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af"); + Proto::SigningInput input; + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(124703); + input.set_chain_id("osmosis-1"); + input.set_memo(""); + input.set_sequence(0); + input.set_private_key(privateKey.data(), privateKey.size()); + + Address fromAddress; + Address toAddress; + EXPECT_TRUE(Address::decode("osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5", fromAddress)); + EXPECT_TRUE(Address::decode("osmo18s0hdnsllgcclweu9aymw4ngktr2k0rkvn7jmn", toAddress)); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address(fromAddress.string()); + message.set_to_address(toAddress.string()); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("uosmo"); + amountOfTx->set_amount("99800"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uosmo"); + amountOfFee->set_amount("200"); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeOsmosis); + + // https://www.mintscan.io/osmosis/txs/81B4F01BDE72AF7FF4536E5D7E66EB218E9FC9ACAA7C5EB5DB237DD0595D5F5F + // curl -H 'Content-Type: application/json' --data-binary '{"tx_bytes": "Co0B...rYVj", "mode": "BROADCAST_MODE_BLOCK"}' https://lcd-osmosis.keplr.app/cosmos/tx/v1beta1/txs + assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"Co0BCooBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmoKK29zbW8xbWt5Njljbjhla3R3eTA4NDV2ZWM5dXBzZHBoa3R4dDBlbjk3ZjUSK29zbW8xOHMwaGRuc2xsZ2NjbHdldTlheW13NG5na3RyMmswcmt2bjdqbW4aDgoFdW9zbW8SBTk5ODAwEmQKTgpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQLs71zkN6MCxn+VRo3ksx826RH0Z9fmpStBweE+HVY2SRIECgIIARISCgwKBXVvc21vEgMyMDAQwJoMGkAMY//Md5GRUR4lVZhk558hFS3kii9QZYoYKfg4+ac/xgNeyoiEweVDhcmEvlH1orVwjLUOnYs4ly2a/yIurYVj\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); + EXPECT_EQ(hex(output.signature()), "0c63ffcc779191511e25559864e79f21152de48a2f50658a1829f838f9a73fc6035eca8884c1e54385c984be51f5a2b5708cb50e9d8b38972d9aff222ead8563"); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error(), ""); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Osmosis/TWCoinTypeTests.cpp b/tests/chains/Osmosis/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..d42a4106616 --- /dev/null +++ b/tests/chains/Osmosis/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// Copyright © 2017-2021 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(TWOsmosisCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeOsmosis)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("5A6E50A6F2927E4B8C87BB094D5FBF15F1287429A09111806FC44B3CD86CACA8")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeOsmosis, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeOsmosis, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeOsmosis)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeOsmosis)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeOsmosis), 6); + ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeOsmosis)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeOsmosis)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeOsmosis)); + assertStringsEqual(symbol, "OSMO"); + assertStringsEqual(txUrl, "https://mintscan.io/osmosis/txs/5A6E50A6F2927E4B8C87BB094D5FBF15F1287429A09111806FC44B3CD86CACA8"); + assertStringsEqual(accUrl, "https://mintscan.io/osmosis/account/osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5"); + assertStringsEqual(id, "osmosis"); + assertStringsEqual(name, "Osmosis"); +} diff --git a/tests/POANetwork/TWCoinTypeTests.cpp b/tests/chains/POANetwork/TWCoinTypeTests.cpp similarity index 97% rename from tests/POANetwork/TWCoinTypeTests.cpp rename to tests/chains/POANetwork/TWCoinTypeTests.cpp index 37ca284bc69..76581a52843 100644 --- a/tests/POANetwork/TWCoinTypeTests.cpp +++ b/tests/chains/POANetwork/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Polkadot/AddressTests.cpp b/tests/chains/Polkadot/AddressTests.cpp similarity index 96% rename from tests/Polkadot/AddressTests.cpp rename to tests/chains/Polkadot/AddressTests.cpp index 6cc3fb06368..d2b4b74a230 100644 --- a/tests/Polkadot/AddressTests.cpp +++ b/tests/chains/Polkadot/AddressTests.cpp @@ -11,8 +11,7 @@ #include #include -using namespace TW; -using namespace TW::Polkadot; +namespace TW::Polkadot::tests { TEST(PolkadotAddress, Validation) { // Substrate ed25519 @@ -49,3 +48,5 @@ TEST(PolkadotAddress, FromString) { auto address = Address("15KRsCq9LLNmCxNFhGk55s5bEyazKefunDxUH24GFZwsTxyu"); ASSERT_EQ(address.string(), "15KRsCq9LLNmCxNFhGk55s5bEyazKefunDxUH24GFZwsTxyu"); } + +} // namespace TW::Polkadot::tests diff --git a/tests/chains/Polkadot/SS58AddressTests.cpp b/tests/chains/Polkadot/SS58AddressTests.cpp new file mode 100644 index 00000000000..701938d697b --- /dev/null +++ b/tests/chains/Polkadot/SS58AddressTests.cpp @@ -0,0 +1,146 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Polkadot/SS58Address.h" +#include "HexCoding.h" +#include "PublicKey.h" +#include "TestUtilities.h" + +#include +#include + +using namespace TW; + +namespace TW::Polkadot::tests { + +TEST(SS58Address, IsValid) { + EXPECT_TRUE(SS58Address::isValid("15KRsCq9LLNmCxNFhGk55s5bEyazKefunDxUH24GFZwsTxyu", 0)); + + EXPECT_TRUE(SS58Address::isValid("ZG2d3dH5zfqNchsqReS6x4nBJuJCW7Z6Fh5eLvdA3ZXGkPd", 5)); + EXPECT_FALSE(SS58Address::isValid("ZG2d3dH5zfqNchsqReS6x4nBJuJCW7Z6Fh5eLvdA3ZXGkPd", 6)); + + EXPECT_TRUE(SS58Address::isValid("Fu3r514w83euSVV7q1MyFGWErUR2xDzXS2goHzimUn4S12D", 2)); + EXPECT_FALSE(SS58Address::isValid("Fu3r514w83euSVV7q1MyFGWErUR2xDzXS2goHzimUn4S12D", 5)); + + EXPECT_TRUE(SS58Address::isValid("cEYtw6AVMB27hFUs4gVukajLM7GqxwxUfJkbPY3rNToHMcCgb", 64)); + EXPECT_FALSE(SS58Address::isValid("cEYtw6AVMB27hFUs4gVukajLM7GqxwxUfJkbPY3rNToHMcCgb", 65)); + EXPECT_FALSE(SS58Address::isValid("JCViCkwMdGWKpf7Wogb8EFtDmaYTEZGEg6ah4svUPGnnpc7A", 64)); + + EXPECT_TRUE(SS58Address::isValid("p8EGHjWt7e1MYoD7V6WXvbPZWK9GSJiiK85kv2R7Ur7FisPUL", 172)); + EXPECT_FALSE(SS58Address::isValid("p8EGHjWt7e1MYoD7V6WXvbPZWK9GSJiiK85kv2R7Ur7FisPUL", 171)); + + EXPECT_TRUE(SS58Address::isValid("VDSyeURSP7ykE1zJPJGeqx6GcDZQF2DT3hAKhPMuwM5FuN9HE", 4096)); + EXPECT_FALSE(SS58Address::isValid("VDSyeURSP7ykE1zJPJGeqx6GcDZQF2DT3hAKhPMuwM5FuN9HE", 64)); + + EXPECT_TRUE(SS58Address::isValid("YDTv3GdhXPP3pQMqQtntGVg5hMno4jqanfYUgMPX2rLGJBKX6", 8219)); + EXPECT_FALSE(SS58Address::isValid("YDTv3GdhXPP3pQMqQtntGVg5hMno4jqanfYUgMPX2rLGJBKX6", 322)); +} + +const auto pubkeyString1 = "92fd9c237030356e26cfcc4568dc71055d5ec92dfe0ff903767e00611971bad3"; + +TEST(SS58Address, FromPublicKey) { + auto publicKey = PublicKey(parse_hex(pubkeyString1), TWPublicKeyTypeED25519); + auto addressPolkadot = SS58Address(publicKey, 0); + EXPECT_EQ(addressPolkadot.string(), "14KjL5vGAYJCbKgZJmFKDSjewtBpvaxx9YvRZvi7qmb5s8CC"); + EXPECT_EQ(hex(addressPolkadot.keyBytes()), pubkeyString1); + + auto addressAstar = SS58Address(publicKey, 5); + EXPECT_EQ(addressAstar.string(), "ZG2d3dH5zfqNchsqReS6x4nBJuJCW7Z6Fh5eLvdA3ZXGkPd"); + EXPECT_EQ(hex(addressAstar.keyBytes()), pubkeyString1); + + auto addressParallel = SS58Address(publicKey, 172); + EXPECT_EQ(addressParallel.string(), "p8EGHjWt7e1MYoD7V6WXvbPZWK9GSJiiK85kv2R7Ur7FisPUL"); + EXPECT_EQ(hex(addressParallel.keyBytes()), pubkeyString1); +} + +TEST(SS58Address, FromPublicKeyInvalid) { + auto publicKey = PublicKey(parse_hex(pubkeyString1), TWPublicKeyTypeED25519); + EXPECT_EXCEPTION(SS58Address(publicKey, 32771), "network out of range 32771"); +} + +TEST(SS58Address, FromString) { + auto addressKusama = SS58Address("Fu3r514w83euSVV7q1MyFGWErUR2xDzXS2goHzimUn4S12D", 2); + EXPECT_EQ(addressKusama.string(), "Fu3r514w83euSVV7q1MyFGWErUR2xDzXS2goHzimUn4S12D"); + + auto addressParallel = SS58Address("p8EGHjWt7e1MYoD7V6WXvbPZWK9GSJiiK85kv2R7Ur7FisPUL", 172); + EXPECT_EQ(addressParallel.string(), "p8EGHjWt7e1MYoD7V6WXvbPZWK9GSJiiK85kv2R7Ur7FisPUL"); +} + +TEST(SS58Address, FromStringInvalid) { + EXPECT_EXCEPTION(SS58Address("p8EGHjWt7e1MYoD7V6WXvbPZWK9GSJiiK85kv2R7Ur7FisPUL", 130), "Invalid address string"); +} + +std::map networkData = { + {0x00, "00"}, + {0x01, "01"}, + {0x02, "02"}, + {0x03, "03"}, + {0x04, "04"}, + {0x08, "08"}, + {0x0b, "0b"}, + {0x10, "10"}, + {0x20, "20"}, + {0x23, "23"}, + {0x30, "30"}, + {0x3f, "3f"}, + {0x40, "5000"}, + {0x41, "5040"}, + {0x80, "6000"}, + {0x0100, "4001"}, + {0x0123, "48c1"}, + {0x0200, "4002"}, + {0x0300, "4003"}, + {0x0400, "4004"}, + {0x0800, "4008"}, + {0x0fff, "7fcf"}, + {0x1000, "4010"}, + {0x1003, "40d0"}, + {0x2000, "4020"}, + {0x3000, "4030"}, + {0x3fff, "7fff"}, +}; + +TEST(SS58Address, DecodeNetwork) { + byte networkSize = 0; + uint32_t network = 0; + for (auto& d: networkData) { + std::string input = d.second + std::string("000102030405"); + EXPECT_TRUE(SS58Address::decodeNetwork(parse_hex(input), networkSize, network)); + EXPECT_EQ(network, d.first); + if (d.first < 64) { + EXPECT_EQ(networkSize, 1); + } else { + EXPECT_EQ(networkSize, 2); + } + } + + // 1. byte from invalid range + EXPECT_FALSE(SS58Address::decodeNetwork(parse_hex("ab" "000102030405"), networkSize, network)); + EXPECT_FALSE(SS58Address::decodeNetwork(parse_hex("8000" "000102030405"), networkSize, network)); + + // 2-byte, but decoded network is < 64 + EXPECT_FALSE(SS58Address::decodeNetwork(parse_hex("4000" "000102030405"), networkSize, network)); + EXPECT_FALSE(SS58Address::decodeNetwork(parse_hex("4040" "000102030405"), networkSize, network)); + EXPECT_FALSE(SS58Address::decodeNetwork(parse_hex("4080" "000102030405"), networkSize, network)); + EXPECT_FALSE(SS58Address::decodeNetwork(parse_hex("4100" "000102030405"), networkSize, network)); + EXPECT_FALSE(SS58Address::decodeNetwork(parse_hex("4200" "000102030405"), networkSize, network)); + EXPECT_FALSE(SS58Address::decodeNetwork(parse_hex("4400" "000102030405"), networkSize, network)); + EXPECT_FALSE(SS58Address::decodeNetwork(parse_hex("4800" "000102030405"), networkSize, network)); +} + +TEST(SS58Address, EncodeNetwork) { + Data data; + for (auto& d: networkData) { + EXPECT_TRUE(SS58Address::encodeNetwork(d.first, data)); + EXPECT_EQ(hex(data), d.second); + } + + // network > 16383 + EXPECT_FALSE(SS58Address::encodeNetwork(0x4000, data)); + EXPECT_FALSE(SS58Address::encodeNetwork(0x8000, data)); +} + +} // namespace TW::Polkadot::tests diff --git a/tests/Polkadot/ScaleCodecTests.cpp b/tests/chains/Polkadot/ScaleCodecTests.cpp similarity index 98% rename from tests/Polkadot/ScaleCodecTests.cpp rename to tests/chains/Polkadot/ScaleCodecTests.cpp index efdd07515c8..c31892fddea 100644 --- a/tests/Polkadot/ScaleCodecTests.cpp +++ b/tests/chains/Polkadot/ScaleCodecTests.cpp @@ -12,8 +12,7 @@ #include using namespace TW; -using namespace TW::Polkadot; - +namespace TW::Polkadot::tests { TEST(PolkadotCodec, EncodeCompact) { ASSERT_EQ(hex(encodeCompact(0)), "00"); @@ -76,3 +75,5 @@ TEST(PolkadotCodec, EncodeEra) { ASSERT_EQ(hex(era1), "7200"); ASSERT_EQ(hex(era2), "1100"); } + +} // namespace TW::Polkadot::tests diff --git a/tests/Polkadot/SignerTests.cpp b/tests/chains/Polkadot/SignerTests.cpp similarity index 91% rename from tests/Polkadot/SignerTests.cpp rename to tests/chains/Polkadot/SignerTests.cpp index 5b689e8d5dd..59d51714c23 100644 --- a/tests/Polkadot/SignerTests.cpp +++ b/tests/chains/Polkadot/SignerTests.cpp @@ -7,7 +7,7 @@ #include "Polkadot/Signer.h" #include "Polkadot/Extrinsic.h" #include "Polkadot/Address.h" -#include "SS58Address.h" +#include "Polkadot/SS58Address.h" #include "HexCoding.h" #include "PrivateKey.h" #include "PublicKey.h" @@ -18,10 +18,11 @@ #include -namespace TW::Polkadot { +namespace TW::Polkadot::tests { auto privateKey = PrivateKey(parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")); auto privateKeyIOS = PrivateKey(parse_hex("37932b086586a6675e66e562fe68bd3eeea4177d066619c602fe3efc290ada62")); auto privateKeyThrow2 = PrivateKey(parse_hex("70a794d4f1019c3ce002f33062f45029c4f930a56b3d20ec477f7668c6bbc37f")); + auto privateKeyPolkadot = PrivateKey(parse_hex("298fcced2b497ed48367261d8340f647b3fca2d9415d57c2e3c5ef90482a2266")); auto addressThrow2 = "14Ztd3KJDaB9xyJtRkREtSZDdhLSbm7UUKt8Z7AwSv7q85G2"; auto toPublicKey = PublicKey(parse_hex("0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"), TWPublicKeyTypeED25519); auto genesisHash = parse_hex("91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"); @@ -256,7 +257,8 @@ TEST(PolkadotSigner, SignChill) { input.set_transaction_version(3); auto stakingCall = input.mutable_staking_call(); - [[maybe_unused]] auto &chill = *stakingCall->mutable_chill(); + //auto __attribute__((unused)) &chill = *stakingCall->mutable_chill(); + [[maybe_unused]] auto &chill = *stakingCall->mutable_chill(); //win auto output = Signer::sign(input); ASSERT_EQ(hex(output.encoded()), "9d018488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0088b5e1cd93ba74b82e329f95e1b22660385970182172b2ae280801fdd1ee5652cf7bf319e5e176ccc299dd8eb1e7fccb0ea7717efaf4aacd7640789dd09c1e070000000706"); @@ -309,4 +311,30 @@ TEST(PolkadotSigner, SignUnbond_070957) { ASSERT_EQ(hex(output.encoded()), "b501849dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f783003a762d9dc3f2aba8922c4babf7e6622ca1d74da17ab3f152d8f29b0ffee53c7e5e150915912a9dfd98ef115d272e096543eef9f513207dd606eea97d023a64087503080007020300286bee"); } -} // namespace +TEST(PolkadotSigner, SignChillAndUnbond) { + auto blockHash = parse_hex("0x35ba668bb19453e8da6334cadcef2a27c8d4141bfc8b49e78e853c3d73e1ecd0"); + auto input = Proto::SigningInput(); + + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_block_hash(blockHash.data(), blockHash.size()); + input.set_nonce(6); + input.set_spec_version(9200); + input.set_private_key(privateKeyPolkadot.bytes.data(), privateKeyPolkadot.bytes.size()); + input.set_network(Proto::Network::POLKADOT); + input.set_transaction_version(12); + + auto era = input.mutable_era(); + era->set_block_number(10541373); + era->set_period(64); + + auto stakingCall = input.mutable_staking_call(); + auto chillBond = stakingCall->mutable_chill_and_unbond(); + auto value = store(uint256_t(100500000000)); // 10.05 DOT + chillBond->set_value(value.data(), value.size()); + + auto output = Signer::sign(input); + // https://polkadot.subscan.io/extrinsic/10541383-2 + ASSERT_EQ(hex(output.encoded()), "d10184008361bd08ddca5fda28b5e2aa84dc2621de566e23e089e555a42194c3eaf2da7900c891ba102db672e378945d74cf7f399226a76b43cab502436971599255451597fc2599902e4b62c7ce85ecc3f653c693fef3232be620984b5bb5bcecbbd7b209d50318001a02080706070207004d446617"); +} + +} // namespace TW::Polkadot::tests diff --git a/tests/Polkadot/TWCoinTypeTests.cpp b/tests/chains/Polkadot/TWCoinTypeTests.cpp similarity index 97% rename from tests/Polkadot/TWCoinTypeTests.cpp rename to tests/chains/Polkadot/TWCoinTypeTests.cpp index 82030aaeeec..b35b069a115 100644 --- a/tests/Polkadot/TWCoinTypeTests.cpp +++ b/tests/chains/Polkadot/TWCoinTypeTests.cpp @@ -8,11 +8,10 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include - TEST(TWPolkadotCoinType, TWCoinType) { auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypePolkadot)); diff --git a/tests/Polygon/TWCoinTypeTests.cpp b/tests/chains/Polygon/TWCoinTypeTests.cpp similarity index 97% rename from tests/Polygon/TWCoinTypeTests.cpp rename to tests/chains/Polygon/TWCoinTypeTests.cpp index a1810ade109..3ffc1203d40 100644 --- a/tests/Polygon/TWCoinTypeTests.cpp +++ b/tests/chains/Polygon/TWCoinTypeTests.cpp @@ -5,7 +5,7 @@ // file LICENSE at the root of the source code distribution tree. // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Qtum/TWCoinTypeTests.cpp b/tests/chains/Qtum/TWCoinTypeTests.cpp similarity index 97% rename from tests/Qtum/TWCoinTypeTests.cpp rename to tests/chains/Qtum/TWCoinTypeTests.cpp index bef8e814b59..97814e04ab3 100644 --- a/tests/Qtum/TWCoinTypeTests.cpp +++ b/tests/chains/Qtum/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Qtum/TWQtumAddressTests.cpp b/tests/chains/Qtum/TWQtumAddressTests.cpp similarity index 99% rename from tests/Qtum/TWQtumAddressTests.cpp rename to tests/chains/Qtum/TWQtumAddressTests.cpp index 8d658cf096c..7da7788ce7e 100644 --- a/tests/Qtum/TWQtumAddressTests.cpp +++ b/tests/chains/Qtum/TWQtumAddressTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Ravencoin/TWCoinTypeTests.cpp b/tests/chains/Ravencoin/TWCoinTypeTests.cpp similarity index 97% rename from tests/Ravencoin/TWCoinTypeTests.cpp rename to tests/chains/Ravencoin/TWCoinTypeTests.cpp index f639dfe6bba..9f90f507883 100644 --- a/tests/Ravencoin/TWCoinTypeTests.cpp +++ b/tests/chains/Ravencoin/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Ravencoin/TWRavencoinTransactionTests.cpp b/tests/chains/Ravencoin/TWRavencoinTransactionTests.cpp similarity index 95% rename from tests/Ravencoin/TWRavencoinTransactionTests.cpp rename to tests/chains/Ravencoin/TWRavencoinTransactionTests.cpp index b9c220fdb0d..d3927772a2c 100644 --- a/tests/Ravencoin/TWRavencoinTransactionTests.cpp +++ b/tests/chains/Ravencoin/TWRavencoinTransactionTests.cpp @@ -1,11 +1,10 @@ - -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include "Bitcoin/OutPoint.h" #include "Bitcoin/TransactionBuilder.h" @@ -18,8 +17,7 @@ #include -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { TEST(RavencoinTransaction, SignTransaction) { /* @@ -73,22 +71,23 @@ TEST(RavencoinTransaction, SignTransaction) { signedTx.encode(serialized, Transaction::SegwitFormatMode::NonSegwit); ASSERT_EQ( hex(serialized), - "0100000001445560237d8093da3487eb90bc7ff826fab43cdbe213c034d671ec4eb4827e0c000000006b483045022100d790bdaa3c44eb5e3a422365ca5fc009c4512625222e3378f2f16e7e6ef1732a0220688c1bb995b7ff2f12729e101d7c24b6314430317e7717911fdc35c0d84f2f0d012102138724e702d25b0fdce73372ccea9734f9349442d5a9681a5f4d831036cd9429ffffffff0280f0fa02000000001976a9149451f4546e09fc2e49ef9b5303924712ec2b038e88ac006cdc02000000001976a9145d6e33f3a108bbcc586cbbe90994d5baf5a9cce488ac00000000" - ); + "0100000001445560237d8093da3487eb90bc7ff826fab43cdbe213c034d671ec4eb4827e0c000000006b483045022100d790bdaa3c44eb5e3a422365ca5fc009c4512625222e3378f2f16e7e6ef1732a0220688c1bb995b7ff2f12729e101d7c24b6314430317e7717911fdc35c0d84f2f0d012102138724e702d25b0fdce73372ccea9734f9349442d5a9681a5f4d831036cd9429ffffffff0280f0fa02000000001976a9149451f4546e09fc2e49ef9b5303924712ec2b038e88ac006cdc02000000001976a9145d6e33f3a108bbcc586cbbe90994d5baf5a9cce488ac00000000"); } TEST(RavencoinTransaction, LockScripts) { - // P2PKH + // P2PKH // https://blockbook.ravencoin.org/tx/3717b528eb4925461d9de5a596d2eefe175985740b4fda153255e10135f236a6 - + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("RNoSGCX8SPFscj8epDaJjqEpuZa2B5in88").get(), TWCoinTypeRavencoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a9149451f4546e09fc2e49ef9b5303924712ec2b038e88ac"); // P2SH // https://ravencoin.network/api/tx/f600d07814677d1f60545c8f7f71260238595c4928d6fb87caa0f9dd732e9bb5 - + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("rPWwn5h4QFZNaz1XmY39rc73sdYGGDdmq1").get(), TWCoinTypeRavencoin)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "a914bd92088bb7e82d611a9b94fbb74a0908152b784f87"); } + +} // namespace TW::Bitcoin diff --git a/tests/chains/Ronin/TWAnyAddressTests.cpp b/tests/chains/Ronin/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..8bdcd75d68f --- /dev/null +++ b/tests/chains/Ronin/TWAnyAddressTests.cpp @@ -0,0 +1,58 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "TestUtilities.h" + +#include +#include + +#include "Ronin/Address.h" +#include "Ronin/Entry.h" + +#include + +const auto roninPrefixChecksummed = "ronin:EC49280228b0D05Aa8e8b756503254e1eE7835ab"; + +const auto gTests = { + roninPrefixChecksummed, + "ronin:ec49280228b0d05aa8e8b756503254e1ee7835ab", + "0xEC49280228b0D05Aa8e8b756503254e1eE7835ab", + "ronin:0xEC49280228b0D05Aa8e8b756503254e1eE7835ab", +}; + +TEST(RoninAnyAddress, Validate) { + for (const auto& t: gTests) { + EXPECT_TRUE(TWAnyAddressIsValid(STRING(t).get(), TWCoinTypeRonin)); + } +} + +TEST(RoninAnyAddress, Normalize) { + for (const auto& t: gTests) { + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(STRING(t).get(), TWCoinTypeRonin)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string2.get(), STRING(roninPrefixChecksummed).get())); + + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "ec49280228b0d05aa8e8b756503254e1ee7835ab"); + } +} + +TEST(RoninAnyAddress, Invalid) { + const auto tests = { + "EC49280228b0D05Aa8e8b756503254e1eE7835ab", // no prefix + "ec49280228b0d05aa8e8b756503254e1ee7835ab", // no prefix + "roni:EC49280228b0D05Aa8e8b756503254e1eE7835ab", // wrong prefix + "ronin=EC49280228b0D05Aa8e8b756503254e1eE7835ab", // wrong prefix + "0xronin:EC49280228b0D05Aa8e8b756503254e1eE7835ab", // wrong prefix + "EC49280228b0D05Aa8e8b756503254e1eE7835", // too short + "ronin:EC49280228b0D05Aa8e8b756503254e1eE7835", // too short + "ronin:ec49280228b0d05aa8e8b756503254e1ee7835", // too short + }; + + for (const auto& t: tests) { + EXPECT_FALSE(TWAnyAddressIsValid(STRING(t).get(), TWCoinTypeRonin)); + } +} diff --git a/tests/chains/Ronin/TWAnySignerTests.cpp b/tests/chains/Ronin/TWAnySignerTests.cpp new file mode 100644 index 00000000000..cabaf560b68 --- /dev/null +++ b/tests/chains/Ronin/TWAnySignerTests.cpp @@ -0,0 +1,48 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "HexCoding.h" +#include "proto/Ethereum.pb.h" +#include "uint256.h" +#include "TestUtilities.h" +#include + +#include + +namespace TW::Ronin::tests { + +TEST(TWAnySignerRonin, Sign) { + // https://explorer.roninchain.com/tx/0xf13a2c4421700f8782ca73eaf16bb8baf82bcf093e23570a1ff062cdd8dbf6c3 + Ethereum::Proto::SigningInput input; + auto chainId = store(uint256_t(2020)); + auto nonce = store(uint256_t(0)); + auto gasPrice = store(uint256_t(1000000000)); + auto gasLimit = store(uint256_t(21000)); + auto key = parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646"); + + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + input.set_gas_price(gasPrice.data(), gasPrice.size()); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_private_key(key.data(), key.size()); + input.set_to_address("ronin:c36edf48e21cf395b206352a1819de658fd7f988"); + + auto& transfer = *input.mutable_transaction()->mutable_transfer(); + auto amount = store(uint256_t(276447)); + transfer.set_amount(amount.data(), amount.size()); + + std::string expected = "f86880843b9aca0082520894c36edf48e21cf395b206352a1819de658fd7f988830437df80820feca0442aa06b0d0465bfecf84b28e2ce614a32a1ccc12735dc03a5799517d6659d7aa004e1bf2efa30743f1b6d49dbec2671e9fb5ead1e7da15e352ca1df6fb86a8ba7"; + + // sign test + Ethereum::Proto::SigningOutput output; + + ANY_SIGN(input, TWCoinTypeRonin); + + ASSERT_EQ(hex(output.encoded()), expected); + EXPECT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeRonin)); +} + +} // namespace TW::Ronin::tests diff --git a/tests/Ronin/TWCoinTypeTests.cpp b/tests/chains/Ronin/TWCoinTypeTests.cpp similarity index 92% rename from tests/Ronin/TWCoinTypeTests.cpp rename to tests/chains/Ronin/TWCoinTypeTests.cpp index b36c6610026..3549ef495a0 100644 --- a/tests/Ronin/TWCoinTypeTests.cpp +++ b/tests/chains/Ronin/TWCoinTypeTests.cpp @@ -5,7 +5,7 @@ // file LICENSE at the root of the source code distribution tree. // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -20,7 +20,7 @@ TEST(TWRoninCoinType, TWCoinType) { auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeRonin)); ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeRonin), 18); - ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeRonin)); + ASSERT_EQ(TWBlockchainRonin, TWCoinTypeBlockchain(TWCoinTypeRonin)); assertStringsEqual(symbol, "RON"); assertStringsEqual(txUrl, "https://explorer.roninchain.com/tx/0xc09835aaf9c1cacea8ce322865583c791d3a4dfbd9a3b72f79539db88d3697ab"); diff --git a/tests/chains/SmartBitcoinCash/TWCoinTypeTests.cpp b/tests/chains/SmartBitcoinCash/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..f08f7466371 --- /dev/null +++ b/tests/chains/SmartBitcoinCash/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// Copyright © 2017-2021 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(TWSmartBitcoinCashCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeSmartBitcoinCash)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x6413466b455b17d03c7a8ce2d7f99fec34bcd338628bdd2d0580a21e3197a4d9")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeSmartBitcoinCash, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xFeEc227410E1DF9f3b4e6e2E284DC83051ae468F")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeSmartBitcoinCash, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeSmartBitcoinCash)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeSmartBitcoinCash)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeSmartBitcoinCash), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeSmartBitcoinCash)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeSmartBitcoinCash)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeSmartBitcoinCash)); + assertStringsEqual(symbol, "BCH"); + assertStringsEqual(txUrl, "https://www.smartscan.cash/tx/0x6413466b455b17d03c7a8ce2d7f99fec34bcd338628bdd2d0580a21e3197a4d9"); + assertStringsEqual(accUrl, "https://www.smartscan.cash/address/0xFeEc227410E1DF9f3b4e6e2E284DC83051ae468F"); + assertStringsEqual(id, "smartbch"); + assertStringsEqual(name, "Smart Bitcoin Cash"); +} diff --git a/tests/Solana/AddressTests.cpp b/tests/chains/Solana/AddressTests.cpp similarity index 91% rename from tests/Solana/AddressTests.cpp rename to tests/chains/Solana/AddressTests.cpp index 5af714c46c6..15f011299f9 100644 --- a/tests/Solana/AddressTests.cpp +++ b/tests/chains/Solana/AddressTests.cpp @@ -4,17 +4,18 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "Solana/Address.h" -#include "Solana/Program.h" #include "Base58.h" -#include "PrivateKey.h" #include "HexCoding.h" +#include "PrivateKey.h" +#include "Solana/Address.h" +#include "Solana/Program.h" #include using namespace std; using namespace TW; -using namespace TW::Solana; + +namespace TW::Solana::tests { TEST(SolanaAddress, FromPublicKey) { const auto addressString = "2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"; @@ -51,9 +52,11 @@ TEST(SolanaAddress, isValidOnCurve) { TEST(SolanaAddress, defaultTokenAddress) { const Address serumToken = Address("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"); EXPECT_EQ(Address("HBYC51YrGFAZ8rM7Sj8e9uqKggpSrDYrinQDZzvMtqQp").defaultTokenAddress(serumToken).string(), - "6X4X1Ae24mkoWeCEpktevySVG9jzeCufut5vtUW3wFrD"); + "6X4X1Ae24mkoWeCEpktevySVG9jzeCufut5vtUW3wFrD"); EXPECT_EQ(Address("B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V").defaultTokenAddress(serumToken).string(), - "EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"); + "EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"); EXPECT_EQ(Address("Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ").defaultTokenAddress(serumToken).string(), - "ANVCrmRw7Ww7rTFfMbrjApSPXEEcZpBa6YEiBdf98pAf"); -} \ No newline at end of file + "ANVCrmRw7Ww7rTFfMbrjApSPXEEcZpBa6YEiBdf98pAf"); +} + +} // namespace TW::Solana::tests diff --git a/tests/Solana/ProgramTests.cpp b/tests/chains/Solana/ProgramTests.cpp similarity index 76% rename from tests/Solana/ProgramTests.cpp rename to tests/chains/Solana/ProgramTests.cpp index 0a5c413dafd..f943622e28b 100644 --- a/tests/Solana/ProgramTests.cpp +++ b/tests/chains/Solana/ProgramTests.cpp @@ -4,16 +4,17 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "Solana/Address.h" -#include "Solana/Program.h" #include "Base58.h" #include "PrivateKey.h" +#include "Solana/Address.h" +#include "Solana/Program.h" #include using namespace std; using namespace TW; -using namespace TW::Solana; + +namespace TW::Solana::tests { TEST(SolanaStakeProgram, addressFromValidatorSeed) { auto user = Address("zVSpQnbBZ7dyUWzXhrUQRsTYYNzoAdJWHsHSqhPj3Xu"); @@ -43,7 +44,7 @@ TEST(SolanaStakeProgram, addressFromRecentBlockhash) { TEST(SolanaTokenProgram, defaultTokenAddress) { const Address serumToken = Address("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"); EXPECT_EQ(TokenProgram::defaultTokenAddress(Address("HBYC51YrGFAZ8rM7Sj8e9uqKggpSrDYrinQDZzvMtqQp"), serumToken).string(), - "6X4X1Ae24mkoWeCEpktevySVG9jzeCufut5vtUW3wFrD"); + "6X4X1Ae24mkoWeCEpktevySVG9jzeCufut5vtUW3wFrD"); } TEST(SolanaTokenProgram, findProgramAddress) { @@ -52,21 +53,30 @@ TEST(SolanaTokenProgram, findProgramAddress) { Base58::bitcoin.decode("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"), Base58::bitcoin.decode("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"), }; - Address address = TokenProgram::findProgramAddress(seeds, Address("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL")); - EXPECT_EQ(address.string(), "EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"); + { + Address address = TokenProgram::findProgramAddress(seeds, Address("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL")); + EXPECT_EQ(address.string(), "EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"); + } + { + Address address = TokenProgram::findProgramAddress(seeds, Address("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr")); + EXPECT_EQ(address.string(), "CuS1kE1wvGTmwGk3FYNQK85g4jU7gMySWwFRQQ9LFunp"); + } } TEST(SolanaTokenProgram, createProgramAddress) { + std::vector seeds4 = { + Base58::bitcoin.decode("B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"), + Base58::bitcoin.decode("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"), + Base58::bitcoin.decode("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"), + Data{255}}; { - std::vector seeds = { - Base58::bitcoin.decode("B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"), - Base58::bitcoin.decode("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"), - Base58::bitcoin.decode("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"), - Data{255} - }; - Address address = TokenProgram::createProgramAddress(seeds, Address("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL")); + Address address = TokenProgram::createProgramAddress(seeds4, Address("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL")); EXPECT_EQ(address.string(), "EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"); } + { + Address address = TokenProgram::createProgramAddress(seeds4, Address("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr")); + EXPECT_EQ(address.string(), "CuS1kE1wvGTmwGk3FYNQK85g4jU7gMySWwFRQQ9LFunp"); + } // https://github.com/solana-labs/solana/blob/f25c969ad87e64e6d1fd07d2d37096ac71cf8d06/sdk/program/src/pubkey.rs#L353-L435 { std::vector seeds = {TW::data(""), {1}}; @@ -84,3 +94,5 @@ TEST(SolanaTokenProgram, createProgramAddress) { EXPECT_EQ(address.string(), "GUs5qLUfsEHkcMB9T38vjr18ypEhRuNWiePW2LoK4E3K"); } } + +} // namespace TW::Solana::tests diff --git a/tests/Solana/SignerTests.cpp b/tests/chains/Solana/SignerTests.cpp similarity index 96% rename from tests/Solana/SignerTests.cpp rename to tests/chains/Solana/SignerTests.cpp index f7eb93825e8..875300fb5e2 100644 --- a/tests/Solana/SignerTests.cpp +++ b/tests/chains/Solana/SignerTests.cpp @@ -13,7 +13,7 @@ #include using namespace TW; -using namespace TW::Solana; +namespace TW::Solana::tests { TEST(SolanaSigner, CompiledInstruction) { const auto privateKey0 = @@ -45,13 +45,13 @@ TEST(SolanaSigner, CompiledInstruction) { auto compiledInstruction = CompiledInstruction(instruction, addresses); EXPECT_EQ(compiledInstruction.programIdIndex, 2); - ASSERT_EQ(compiledInstruction.accounts.size(), 5); + ASSERT_EQ(compiledInstruction.accounts.size(), 5ul); EXPECT_EQ(compiledInstruction.accounts[0], 1); EXPECT_EQ(compiledInstruction.accounts[1], 0); EXPECT_EQ(compiledInstruction.accounts[2], 2); EXPECT_EQ(compiledInstruction.accounts[3], 1); EXPECT_EQ(compiledInstruction.accounts[4], 0); - ASSERT_EQ(compiledInstruction.data.size(), 4); + ASSERT_EQ(compiledInstruction.data.size(), 4ul); } TEST(SolanaSigner, CompiledInstructionFindAccount) { @@ -59,10 +59,12 @@ TEST(SolanaSigner, CompiledInstructionFindAccount) { Address address2 = Address(parse_hex("0102030405060708090a0102030405060708090a0102030405060708090a0102")); Address address3 = Address(parse_hex("0102030405060708090a0102030405060708090a0102030405060708090a0103")); Address programId("11111111111111111111111111111111"); + // clang-format off Instruction instruction(programId, std::vector{ AccountMeta(address1, true, false), AccountMeta(address2, false, false), }, Data{1, 2, 3, 4}); + // clang-format on std::vector
addresses = { address1, address2, @@ -335,7 +337,7 @@ TEST(SolanaSigner, SignCreateTokenAccount) { auto tokenAddress = Address("EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"); Solana::Hash recentBlockhash("9ipJh5xfyoyDaiq8trtrdqQeAhQbQkWy2eANizKvx75K"); - auto message = Message::createTokenCreateAccount(signer, TokenInstruction::CreateTokenAccount, signer, token, tokenAddress, recentBlockhash); + auto message = Message::createTokenCreateAccount(signer, signer, token, tokenAddress, recentBlockhash); auto transaction = Transaction(message); std::vector signerKeys; @@ -353,7 +355,7 @@ TEST(SolanaSigner, SignCreateTokenAccount) { EXPECT_EQ(transaction.serialize(), expectedString); } -TEST(SolanaSigner, SignCreateTokenAccountForOther) { +TEST(SolanaSigner, SignCreateTokenAccountForOther_3E6UFV) { const auto privateKeySigner = PrivateKey(parse_hex("4b9d6f57d28b06cbfa1d4cc710953e62d653caf853415c56ffd9d150acdeb7f7")); const auto publicKeySigner = privateKeySigner.getPublicKey(TWPublicKeyTypeED25519); @@ -365,7 +367,7 @@ TEST(SolanaSigner, SignCreateTokenAccountForOther) { auto tokenAddress = Address("67BrwFYt7qUnbAcYBVx7sQ4jeD2KWN1ohP6bMikmmQV3"); Solana::Hash recentBlockhash("HmWyvrif3QfZJnDiRyrojmH9iLr7eMxxqiC9RJWFeunr"); - auto message = Message::createTokenCreateAccount(signer, TokenInstruction::CreateTokenAccount, otherMainAddress, token, tokenAddress, recentBlockhash); + auto message = Message::createTokenCreateAccount(signer, otherMainAddress, token, tokenAddress, recentBlockhash); auto transaction = Transaction(message); std::vector signerKeys; @@ -378,7 +380,7 @@ TEST(SolanaSigner, SignCreateTokenAccountForOther) { EXPECT_EQ(transaction.serialize(), expectedString); } -TEST(SolanaSigner, SignTransferToken) { +TEST(SolanaSigner, SignTransferToken_3vZ67C) { const auto privateKeySigner = PrivateKey(Base58::bitcoin.decode("9YtuoD4sH4h88CVM8DSnkfoAaLY7YeGC2TarDJ8eyMS5")); const auto publicKeySigner = privateKeySigner.getPublicKey(TWPublicKeyTypeED25519); @@ -392,8 +394,8 @@ TEST(SolanaSigner, SignTransferToken) { uint8_t decimals = 6; Solana::Hash recentBlockhash("CNaHfvqePgGYMvtYi9RuUdVxDYttr1zs4TWrTXYabxZi"); - auto message = Message::createTokenTransfer(signer, TokenInstruction::TokenTransfer, token, - senderTokenAddress, recipientTokenAddress, amount, decimals, recentBlockhash); + auto message = Message::createTokenTransfer(signer, token, + senderTokenAddress, recipientTokenAddress, amount, decimals, recentBlockhash); auto transaction = Transaction(message); std::vector signerKeys; @@ -411,3 +413,5 @@ TEST(SolanaSigner, SignTransferToken) { "PGfKqEaH2zZXDMZLcU6LUKdBSzU1GJWJ1CJXtRYCxaCH7k8uok38WSadZfrZw3TGejiau7nSpan2GvbK26hQim24jRe2AupmcYJFrgsdaCt1Aqs5kpGjPqzgj9krgxTZwwob3xgC1NdHK5BcNwhxwRtrCphGEH7zUFpGFrFrHzgpf2KY8FvPiPELQyxzTBuyNtjLjMMreehSKShEjD9Xzp1QeC1pEF8JL6vUKzxMXuveoEYem8q8JiWszYzmTMfDk13JPgv7pXFGMqDV3yNGCLsWccBeSFKN4UKECre6x2QbUEiKGkHkMc4zQwwyD8tGmEMBAGm339qdANssEMNpDeJp2LxLDStSoWShHnotcrH7pUa94xCVvCPPaomF"; EXPECT_EQ(transaction.serialize(), expectedString); } + +} // namespace TW::Solana::tests diff --git a/tests/Solana/TWAnySignerTests.cpp b/tests/chains/Solana/TWAnySignerTests.cpp similarity index 84% rename from tests/Solana/TWAnySignerTests.cpp rename to tests/chains/Solana/TWAnySignerTests.cpp index ae130918f77..8246f8f48ce 100644 --- a/tests/Solana/TWAnySignerTests.cpp +++ b/tests/chains/Solana/TWAnySignerTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -10,13 +10,13 @@ #include "Solana/Address.h" #include "Solana/Program.h" #include "PrivateKey.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include using namespace TW; -using namespace TW::Solana; +namespace TW::Solana::tests { const auto expectedString1 = "3p2kzZ1DvquqC6LApPuxpTg5CCDVPqJFokGSnGhnBHrta4uq7S2EyehV1XNUVXp51D69GxGzQZU" @@ -60,6 +60,26 @@ TEST(TWAnySignerSolana, SignTransferToSelf) { ASSERT_EQ(output.encoded(), expectedString); } +TEST(TWAnySignerSolana, SignTransferWithMemoAndReference) { + const auto privateKey = Base58::bitcoin.decode("AevJ4EWcvQ6dptBDvF2Ri5pU6QSBjkzSGHMfbLFKa746"); + auto input = Solana::Proto::SigningInput(); + + auto& message = *input.mutable_transfer_transaction(); + message.set_recipient("71e8mDsh3PR6gN64zL1HjwuxyKpgRXrPDUJT7XXojsVd"); + message.set_value((uint64_t)10000000L); + message.set_memo("HelloSolanaMemo"); + message.add_references("CuieVDEDtLo7FypA9SbLM9saXFdb1dsshEkyErMqkRQq"); + message.add_references("tFpP7tZUt6zb7YZPpQ11kXNmsc5YzpMXmahGMvCHhqS"); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_recent_blockhash("11111111111111111111111111111111"); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeSolana); + + const auto expectedString = "NfNH76sST3nJ4FmFGTZJBUpJou7DRuHM3YNprT1HeEau699CQF65xNf21Hoi491bbtVKUXfqCJyeZhfTCEnABuXNC1JrhGBeCv2AbQdaS9gpp9j4xHHomhCYdwYaBWFMcKkdMXrx9xHqL9Vkny4HezkwQfb3wGqcaE9XVRdkkNxsoJnVKddRnrQbjhsZGTcKdfmbTghoUeRECNPTm6nZTA1owWF1Dq6mfr6M3GZRh4ucqEquxKsQC2HQwNRrGZahsfyUvwspPWwMt78q5Jpjd9kHqkFDspZL6Pepv4dAA4uHhYDCHeP2bbDiFMBYxxWCVDDtRKSh3H92xUgh1GCSgNcjGdbVfQUhSDPX3k9xuuszPTsVZ2GnsavAsRp6Vf6fFEikBX6pVV9zjW1cx94EepQ2aGEBSsVu4RzX7rJjCLCq87h8cxxf1XnF8mvYGEK7wzF"; + EXPECT_EQ(output.encoded(), expectedString); +} + TEST(TWAnySignerSolana, SignDelegateStakeTransaction_noStakeAccount) { auto privateKey = Base58::bitcoin.decode("AevJ4EWcvQ6dptBDvF2Ri5pU6QSBjkzSGHMfbLFKa746"); auto input = Solana::Proto::SigningInput(); @@ -225,7 +245,7 @@ TEST(TWAnySignerSolana, SignCreateTokenAccount1) { ASSERT_EQ(output.encoded(), expectedString); } -TEST(TWAnySignerSolana, SignCreateTokenAccount2) { +TEST(TWAnySignerSolana, SignCreateTokenAccount2_5KtPn1) { auto privateKeyData = parse_hex("4b9d6f57d28b06cbfa1d4cc710953e62d653caf853415c56ffd9d150acdeb7f7"); ASSERT_EQ(Address(PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519)).string(), "Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ"); @@ -246,7 +266,7 @@ TEST(TWAnySignerSolana, SignCreateTokenAccount2) { ASSERT_EQ(output.encoded(), expectedString); } -TEST(TWAnySignerSolana, SignCreateTokenAccountForOther) { +TEST(TWAnySignerSolana, SignCreateTokenAccountForOther_3E6UFV) { auto privateKeyData = parse_hex("4b9d6f57d28b06cbfa1d4cc710953e62d653caf853415c56ffd9d150acdeb7f7"); ASSERT_EQ(Address(PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519)).string(), "Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ"); @@ -267,7 +287,7 @@ TEST(TWAnySignerSolana, SignCreateTokenAccountForOther) { ASSERT_EQ(output.encoded(), expectedString); } -TEST(TWAnySignerSolana, SignTokenTransfer1) { +TEST(TWAnySignerSolana, SignTokenTransfer1_3vZ67C) { auto privateKeyData = Base58::bitcoin.decode("9YtuoD4sH4h88CVM8DSnkfoAaLY7YeGC2TarDJ8eyMS5"); ASSERT_EQ(Address(PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519)).string(), "B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); @@ -290,7 +310,7 @@ TEST(TWAnySignerSolana, SignTokenTransfer1) { ASSERT_EQ(output.encoded(), expectedString); } -TEST(TWAnySignerSolana, SignTokenTransfer2) { +TEST(TWAnySignerSolana, SignTokenTransfer2_2pMvzp) { auto privateKeyData = Base58::bitcoin.decode("9YtuoD4sH4h88CVM8DSnkfoAaLY7YeGC2TarDJ8eyMS5"); ASSERT_EQ(Address(PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519)).string(), "B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); @@ -313,7 +333,7 @@ TEST(TWAnySignerSolana, SignTokenTransfer2) { ASSERT_EQ(output.encoded(), expectedString); } -TEST(TWAnySignerSolana, SignCreateAndTransferToken) { +TEST(TWAnySignerSolana, SignCreateAndTransferToken_449VaY) { auto privateKeyData = Base58::bitcoin.decode("66ApBuKpo2uSzpjGBraHq7HP8UZMUJzp3um8FdEjkC9c"); ASSERT_EQ(Address(PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519)).string(), "Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ"); @@ -332,11 +352,36 @@ TEST(TWAnySignerSolana, SignCreateAndTransferToken) { ANY_SIGN(input, TWCoinTypeSolana); auto expectedString = - //https://explorer.solana.com/tx/449VaYo48LrkMJF6XVKt9sJwVQN6Seqrmh9erDCLtiuj6BgFG3wpF5TwjNkxgJ7qzNa6NTj3TFsU3h9hKszfkA7w + // https://explorer.solana.com/tx/449VaYo48LrkMJF6XVKt9sJwVQN6Seqrmh9erDCLtiuj6BgFG3wpF5TwjNkxgJ7qzNa6NTj3TFsU3h9hKszfkA7w "3Y2MVz2VVi7aEyC9q1awwdk1ModDBPHRSacKmTYnSgkmbbJeZ62Fub1bVPSHaTy4LUcQpzCQYhHAKtTKXUDYijEeLsMAUqPBEMAq1w8zCdqDpdXy6M4PuwNtYVV1WgqeiEsiMWpPp4BGWKfcziwFbmYueUGituacJq4wTnt92fho8mFi49XW64gEG4iNGScDtJkY7Geq8PKiLh1E9JMJoceiHxKbmxzCmmLTxEHdhySYHcDUSXnXWogZskeZNBMtR9dNjEMkCzEjrxRpBtJPtUNshciY45mDPNmw4j3xyLCBTRikyfFLc5g11r3UgyVD4YokoPRvrEXsgt6W3yjBshropBm6mY2eJYvfY2eZz4Yq8kLcUatCHVKtjcb1mP9Ww57KisJ9bRhipC8sodFaMYhZARMEa4a1u9eH4MyNUATRGNXarwQSBY46PWS3nKP6QBK7Dw7Ppp9MmYkdPcXKaLScbyLF3jKu6dHWMkHw3WdXSsM1wwXjXnWF9LxdwaEVcDmySWybj6aKD9QCWTU5kdncqJU56f7SYNRTN289WdUFGNDmSh56tj2v1"; ASSERT_EQ(output.encoded(), expectedString); } +TEST(TWAnySignerSolana, SignCreateAndTransferTokenWithMemoReferences) { + const auto privateKeyData = Base58::bitcoin.decode("66ApBuKpo2uSzpjGBraHq7HP8UZMUJzp3um8FdEjkC9c"); + EXPECT_EQ(Address(PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519)).string(), "Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ"); + + auto input = Solana::Proto::SigningInput(); + auto& message = *input.mutable_create_and_transfer_token_transaction(); + message.set_recipient_main_address("71e8mDsh3PR6gN64zL1HjwuxyKpgRXrPDUJT7XXojsVd"); + message.set_token_mint_address("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"); + message.set_recipient_token_address("EF6L8yJT1SoRoDCkAZfSVmaweqMzfhxZiptKi7Tgj5XY"); + message.set_sender_token_address("ANVCrmRw7Ww7rTFfMbrjApSPXEEcZpBa6YEiBdf98pAf"); + message.set_amount(2900); + message.set_decimals(6); + message.set_memo("HelloSolanaMemo370"); + message.add_references("CuieVDEDtLo7FypA9SbLM9saXFdb1dsshEkyErMqkRQq"); + message.add_references("tFpP7tZUt6zb7YZPpQ11kXNmsc5YzpMXmahGMvCHhqS"); + input.set_private_key(privateKeyData.data(), privateKeyData.size()); + input.set_recent_blockhash("DMmDdJP41M9mw8Z4586VSvxqGCrqPy5uciF6HsKUVDja"); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeSolana); + + auto expectedString = "FuUw2MoEGPATE38roXAw9mGQhCfdsdpVDdhuf5h8LKc8iWj2HzNS3SteXqyUoZtQ7L1ufLvu7cTMwNzxT8snnVimcknsA52CeN7bgMz1Ad1hRTAr77zE5efzAi8B124kaQ1cBEb6nFMr5Zq4wwDRoJgBaiUaM1U9ZY6GofCKHGMQN7ZNqEFG4fFvPaMXB59dFtiqrtApBGzvDho3nGshyQWZVWfMY44hvVk45FqiGrXuqUwkiJqeRaDhooZdXiFR9ubwJLXo3Ux23ZyijWKXYNsx1Lm5zMFEgRz3kXhzxzb8uzHVSrFYNieXXCQEv1GtErMKeQWuAHcwS3zxC6avTnTWJhTz3kVSXfSTYEg4MF2MBWeGrzKZ7id88ZfbpG4ZwzsDsdUCSMV6YYRNmx9P3B6oC4DL7cbi2g8hwtBdeKojY4G6JMPeg629V9sPyg2KKeYxD3cjhMKAYtrsJEbixep4LZENtdQxmgZFouJVvGy9MVhiTzGEFVwm4G25p5FhWhiS9HxHWVRXpUFHi2K9K2ttoo4Ug39V9f8s9cG1Xb5A4bHhGSuKLeCCBcrBqPWEsuLdVhjxsKJrRBJhyrZ6mpxtDhUWivZa6skmEawTts9rN2aP3dXW3cNch3s3LTXZWXG9QPUARJJPy5QAYsBoR8GunF5FFgHVuEHVpjXAd8ku9f7aoF8RNiMnXAqQHxiM3ug6HZpLHLX8aGoUbJ7vVAnEDLH"; + ASSERT_EQ(output.encoded(), expectedString); +} + TEST(TWAnySignerSolana, SignJSON) { auto json = STRING(R"({"recentBlockhash":"11111111111111111111111111111111","transferTransaction":{"recipient":"EN2sCsJ1WDV8UFqsiTXHcUPUxQ4juE71eCknHYYMifkd","value":"42"}})"); Data keyData = Base58::bitcoin.decode("A7psj2GW7ZMdY4E5hJq14KMeYg7HFjULSsWSrTXZLvYr"); @@ -348,3 +393,5 @@ TEST(TWAnySignerSolana, SignJSON) { ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeSolana)); assertStringsEqual(result, expectedString1); } + +} // namespace TW::Solana::tests diff --git a/tests/Solana/TWCoinTypeTests.cpp b/tests/chains/Solana/TWCoinTypeTests.cpp similarity index 97% rename from tests/Solana/TWCoinTypeTests.cpp rename to tests/chains/Solana/TWCoinTypeTests.cpp index 5bd9998488a..c6225566d1e 100644 --- a/tests/Solana/TWCoinTypeTests.cpp +++ b/tests/chains/Solana/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Solana/TWSolanaAddressTests.cpp b/tests/chains/Solana/TWSolanaAddressTests.cpp similarity index 69% rename from tests/Solana/TWSolanaAddressTests.cpp rename to tests/chains/Solana/TWSolanaAddressTests.cpp index 0ce32caf5db..1cdd42676f2 100644 --- a/tests/Solana/TWSolanaAddressTests.cpp +++ b/tests/chains/Solana/TWSolanaAddressTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -28,8 +28,13 @@ TEST(TWSolanaAddress, HDWallet) { } TEST(TWSolanaProgram, defaultTokenAddress) { - const char* serumToken = "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"; - auto solanaAddress = WRAP(TWSolanaAddress, TWSolanaAddressCreateWithString(WRAPS(TWStringCreateWithUTF8Bytes("B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V")).get())); - auto address1 = WRAPS(TWSolanaAddressDefaultTokenAddress(solanaAddress.get(), WRAPS(TWStringCreateWithUTF8Bytes(serumToken)).get())); - EXPECT_EQ(std::string(TWStringUTF8Bytes(address1.get())), "EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"); + const auto solAddress = STRING("B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); + const auto serumToken = STRING("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"); + + auto solanaAddress = WRAP(TWSolanaAddress, TWSolanaAddressCreateWithString(solAddress.get())); + auto description = WRAPS(TWSolanaAddressDescription(solanaAddress.get())); + auto tokenAddress = WRAPS(TWSolanaAddressDefaultTokenAddress(solanaAddress.get(), serumToken.get())); + + assertStringsEqual(tokenAddress, "EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"); + assertStringsEqual(description, "B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); } diff --git a/tests/Solana/TransactionTests.cpp b/tests/chains/Solana/TransactionTests.cpp similarity index 85% rename from tests/Solana/TransactionTests.cpp rename to tests/chains/Solana/TransactionTests.cpp index 3d1254bd3ff..c6ff726f613 100644 --- a/tests/Solana/TransactionTests.cpp +++ b/tests/chains/Solana/TransactionTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -8,14 +8,12 @@ #include "Solana/Transaction.h" #include "Solana/Program.h" #include "HexCoding.h" -#include "PublicKey.h" #include "BinaryCoding.h" #include -using namespace TW; -using namespace TW::Solana; +namespace TW::Solana { TEST(SolanaTransaction, TransferMessageData) { auto from = Address("6eoo7i1khGhVm8tLBMAdq4ax2FxkKP4G7mCcfHyr3STN"); @@ -66,6 +64,21 @@ TEST(SolanaTransaction, TransferTransactionPayToSelf) { ASSERT_EQ(transaction.serialize(), expectedString); } +TEST(SolanaTransaction, TransferWithMemoAndReferenceTransaction) { + const auto from = Address("zVSpQnbBZ7dyUWzXhrUQRsTYYNzoAdJWHsHSqhPj3Xu"); + const auto to = Address("zVSpQnbBZ7dyUWzXhrUQRsTYYNzoAdJWHsHSqhPj3Xu"); + const Solana::Hash recentBlockhash("11111111111111111111111111111111"); + const auto memo = "HelloSolana73"; + std::vector
references = {Address("GaeTAQZyhVEocTC7iY8GztSyY5cBAJTkAUUA1kLFLMV")}; + auto transaction = Transaction(from, to, 42, recentBlockhash, memo, references); + const Signature signature("3CFWDEK51noPJP4v2t8JZ3qj7kC7kLKyws9akfHMyuJnQ35EtzBptHqvaHfeswiLsvUSxzMVNoj4CuRxWtDD9zB1"); + transaction.signatures.clear(); + transaction.signatures.push_back(signature); + + auto expectedString = "3pzQEdU38uMQgegTyRsRLi23NK4YokgZeSVLXYzFB7HShqZZH8FdBLqj6CeA2d2L8oR9KF2UaJPWbE8YBFmSdaafegoSXJtyj7ciwTjk5ieSXnPXtqH1TEcnMntZATg7gKpeFg6iehqdSUtZuQD1PGmHA1TrzzqLpRSRrc1sqPz8EpSJcQr1Y41B1XCEAfSJDfcuNKrfFrnQaVtRz6tseQfd9uXNYNuR1NQSepWdav5wQiohLUMDiZtxuwb7FQkQ68WE1FDsHmd4JpbWKmDEjz7HFyQY37vf6NBJyX5qWJpFMSg5qGKWvhNCDM32yM4A7HhPeoTWEywE5CXcNmQqdbRt4BzF1A11uqv4etWj"; + EXPECT_EQ(transaction.serialize(), expectedString); +} + TEST(SolanaTransaction, StakeSerializeTransactionV2) { auto signer = Address("zVSpQnbBZ7dyUWzXhrUQRsTYYNzoAdJWHsHSqhPj3Xu"); auto voteAddress = Address("4jpwTqt1qZoR7u6u639z2AngYFGN3nakvKhowcnRZDEC"); @@ -105,11 +118,11 @@ TEST(SolanaTransaction, CreateTokenAccountTransaction) { auto token = Address("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"); auto tokenAddress = Address("EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"); Solana::Hash recentBlockhash("9ipJh5xfyoyDaiq8trtrdqQeAhQbQkWy2eANizKvx75K"); - auto message = Message::createTokenCreateAccount(signer, TokenInstruction::CreateTokenAccount, signer, token, tokenAddress, recentBlockhash); + auto message = Message::createTokenCreateAccount(signer, signer, token, tokenAddress, recentBlockhash); EXPECT_EQ(message.header.numRequiredSignatures, 1); EXPECT_EQ(message.header.numCreditOnlySignedAccounts, 0); EXPECT_EQ(message.header.numCreditOnlyUnsignedAccounts, 5); - ASSERT_EQ(message.accountKeys.size(), 7); + ASSERT_EQ(message.accountKeys.size(), 7ul); EXPECT_EQ(message.accountKeys[0].string(), "B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); EXPECT_EQ(message.accountKeys[1].string(), "EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"); EXPECT_EQ(message.accountKeys[2].string(), "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"); @@ -118,9 +131,9 @@ TEST(SolanaTransaction, CreateTokenAccountTransaction) { EXPECT_EQ(message.accountKeys[5].string(), "SysvarRent111111111111111111111111111111111"); EXPECT_EQ(message.accountKeys[6].string(), "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); EXPECT_EQ(Base58::bitcoin.encode(message.recentBlockhash.bytes), "9ipJh5xfyoyDaiq8trtrdqQeAhQbQkWy2eANizKvx75K"); - ASSERT_EQ(message.instructions.size(), 1); + ASSERT_EQ(message.instructions.size(), 1ul); EXPECT_EQ(message.instructions[0].programId.string(), "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); - ASSERT_EQ(message.instructions[0].accounts.size(), 7); + ASSERT_EQ(message.instructions[0].accounts.size(), 7ul); EXPECT_EQ(message.instructions[0].accounts[0].account.string(), "B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); EXPECT_EQ(message.instructions[0].accounts[1].account.string(), "EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"); EXPECT_EQ(message.instructions[0].accounts[2].account.string(), "B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); @@ -139,7 +152,7 @@ TEST(SolanaTransaction, CreateTokenAccountTransaction) { EXPECT_EQ(transaction.serialize(), expectedString); } -TEST(SolanaTransaction, TransferTokenTransaction) { +TEST(SolanaTransaction, TransferTokenTransaction_3vZ67C) { auto signer = Address("B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); auto token = Address("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"); auto senderTokenAddress = Address("EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"); @@ -147,14 +160,14 @@ TEST(SolanaTransaction, TransferTokenTransaction) { uint64_t amount = 4000; uint8_t decimals = 6; Solana::Hash recentBlockhash("CNaHfvqePgGYMvtYi9RuUdVxDYttr1zs4TWrTXYabxZi"); - auto message = Message::createTokenTransfer(signer, TokenInstruction::TokenTransfer, token, senderTokenAddress, recipientTokenAddress, amount, decimals, recentBlockhash); + auto message = Message::createTokenTransfer(signer, token, senderTokenAddress, recipientTokenAddress, amount, decimals, recentBlockhash); EXPECT_EQ(message.header.numRequiredSignatures, 1); EXPECT_EQ(message.header.numCreditOnlySignedAccounts, 0); EXPECT_EQ(message.header.numCreditOnlyUnsignedAccounts, 2); - ASSERT_EQ(message.accountKeys.size(), 5); - ASSERT_EQ(message.instructions.size(), 1); + ASSERT_EQ(message.accountKeys.size(), 5ul); + ASSERT_EQ(message.instructions.size(), 1ul); EXPECT_EQ(message.instructions[0].programId.string(), "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); - ASSERT_EQ(message.instructions[0].accounts.size(), 4); + ASSERT_EQ(message.instructions[0].accounts.size(), 4ul); auto transaction = Transaction(message); transaction.signatures.clear(); Signature signature("3vZ67CGoRYkuT76TtpP2VrtTPBfnvG2xj6mUTvvux46qbnpThgQDgm27nC3yQVUZrABFjT9Qo7vA74tCjtV5P9Xg"); @@ -166,3 +179,5 @@ TEST(SolanaTransaction, TransferTokenTransaction) { "PGfKqEaH2zZXDMZLcU6LUKdBSzU1GJWJ1CJXtRYCxaCH7k8uok38WSadZfrZw3TGejiau7nSpan2GvbK26hQim24jRe2AupmcYJFrgsdaCt1Aqs5kpGjPqzgj9krgxTZwwob3xgC1NdHK5BcNwhxwRtrCphGEH7zUFpGFrFrHzgpf2KY8FvPiPELQyxzTBuyNtjLjMMreehSKShEjD9Xzp1QeC1pEF8JL6vUKzxMXuveoEYem8q8JiWszYzmTMfDk13JPgv7pXFGMqDV3yNGCLsWccBeSFKN4UKECre6x2QbUEiKGkHkMc4zQwwyD8tGmEMBAGm339qdANssEMNpDeJp2LxLDStSoWShHnotcrH7pUa94xCVvCPPaomF"; EXPECT_EQ(transaction.serialize(), expectedString); } + +} // namespace TW::Solana diff --git a/tests/Stellar/AddressTests.cpp b/tests/chains/Stellar/AddressTests.cpp similarity index 95% rename from tests/Stellar/AddressTests.cpp rename to tests/chains/Stellar/AddressTests.cpp index 9fc5dc06b19..9f55cc55058 100644 --- a/tests/Stellar/AddressTests.cpp +++ b/tests/chains/Stellar/AddressTests.cpp @@ -4,16 +4,17 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "Stellar/Address.h" #include "Bitcoin/Address.h" #include "HexCoding.h" #include "PrivateKey.h" +#include "Stellar/Address.h" #include using namespace std; using namespace TW; -using namespace TW::Stellar; + +namespace TW::Stellar::tests { TEST(StellarAddress, FromPublicKey) { const auto publicKey = PublicKey(parse_hex("0103E20EC6B4A39A629815AE02C0A1393B9225E3B890CAE45B59F42FA29BE9668D"), TWPublicKeyTypeED25519); @@ -35,3 +36,5 @@ TEST(StellarAddress, isValid) { ASSERT_TRUE(Address::isValid(stellarAddress)); ASSERT_FALSE(Address::isValid(bitcoinAddress)); } + +} // namespace TW::Stellar::tests diff --git a/tests/Stellar/TWAnySignerTests.cpp b/tests/chains/Stellar/TWAnySignerTests.cpp similarity index 66% rename from tests/Stellar/TWAnySignerTests.cpp rename to tests/chains/Stellar/TWAnySignerTests.cpp index bbe59a37f43..7ad662a073a 100644 --- a/tests/Stellar/TWAnySignerTests.cpp +++ b/tests/chains/Stellar/TWAnySignerTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include "HexCoding.h" #include "PrivateKey.h" @@ -15,7 +15,8 @@ #include using namespace TW; -using namespace TW::Stellar; + +namespace TW::Stellar::tests { TEST(TWAnySingerStellar, Sign_Payment) { auto key = parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722"); @@ -130,3 +131,62 @@ TEST(TWAnySingerStellar, Sign_Change_Trust_2) { EXPECT_EQ(output.signature(), "AAAAAMpFJQVVMv16RJUPlzQUTlgZOHVurhw3igGacP1305F1AAAnEAH/8MgAAAADAAAAAQAAAAAAAAAAAAAAAGApkAAAAAAAAAAAAQAAAAAAAAAGAAAAAVVTRAAAAAAA6KYahh5gr2D4B3PgY0blxyy+Wdyt2jdgjVjvQlEdn9x//////////wAAAAAAAAABd9ORdQAAAEDMZtN05ZsZB4OKOZSFkQvuRqDIvMME3PYMTAGJPQlO6Ee0nOtaRn2q0uf0IhETSSfqcsK5asAZzNj07tG0SPwM"); } + +TEST(TWAnySingerStellar, Sign_Create_Claimable_Balance_1f1f84) { + auto key = parse_hex("3c0635f8638605aed6e461cf3fa2d508dd895df1a1655ff92c79bfbeaf88d4b9"); + PrivateKey privKey = PrivateKey(key); + PublicKey pubKey = privKey.getPublicKey(TWPublicKeyTypeED25519); + Address addr = Address(pubKey); + EXPECT_EQ(addr.string(), "GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); + + Proto::SigningInput input; + input.set_passphrase(TWStellarPassphrase_Stellar); + input.set_account("GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); + input.set_fee(10000); + input.set_sequence(144098454883270687); + input.mutable_op_create_claimable_balance()->set_amount(90000000); + input.mutable_op_create_claimable_balance()->add_claimants(); + input.mutable_op_create_claimable_balance()->mutable_claimants(0)->set_account("GC6CJDAY54D3O4RHEH33LUTBKDZGVOTR6NHBOTL4PIWI2CDKVRSZZJGJ"); + input.mutable_op_create_claimable_balance()->mutable_claimants(0)->set_predicate(Proto::Predicate_unconditional); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeStellar); + + // https://stellar.expert/explorer/public/tx/1f1f849ff2560901c91226f2fc866ef4ed1c67d672262c1f5829abe2348ac638 + // curl -X POST -F "tx=AAAAAMpF..Bg==" "https://horizon.stellar.org/transactions" + EXPECT_EQ(output.signature(), "AAAAAMpFJQVVMv16RJUPlzQUTlgZOHVurhw3igGacP1305F1AAAnEAH/8MgAAAAfAAAAAAAAAAAAAAABAAAAAAAAAA4AAAAAAAAAAAVdSoAAAAABAAAAAAAAAAC8JIwY7we3cich97XSYVDyarpx804XTXx6LI0IaqxlnAAAAAAAAAAAAAAAAXfTkXUAAABAgms/HPhEP/EYtVr5aWwhKJsn3pIVEZGFnTD2Xd/VPVsn8qogI7RYyjyBxSFPiLAljgGsPaUMfU3WFvyJCWNwBg=="); +} + +TEST(TWAnySingerStellar, Sign_Claim_Claimable_Balance_c1fb3c) { + auto key = parse_hex("3c0635f8638605aed6e461cf3fa2d508dd895df1a1655ff92c79bfbeaf88d4b9"); + PrivateKey privKey = PrivateKey(key); + PublicKey pubKey = privKey.getPublicKey(TWPublicKeyTypeED25519); + Address addr = Address(pubKey); + EXPECT_EQ(addr.string(), "GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); + + Proto::SigningInput input; + input.set_passphrase(TWStellarPassphrase_Stellar); + input.set_account("GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); + input.set_fee(10000); + input.set_sequence(144098454883270689); + const Data balanceIdHash = parse_hex("9c7b794b7b150f3e4c6dcfa260672bbe0c248b360129112e927e0f7ee2f9faf8"); + input.mutable_op_claim_claimable_balance()->set_balance_id(balanceIdHash.data(), balanceIdHash.size()); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeStellar); + + // https://stellar.expert/explorer/public/tx/c1fb3cf348aeb72bb2e1030c1d7f7f9c6c6d1bbab071b3e7c7c1cadafa795e8e + // curl -X POST -F "tx=AAAAAMpF..DQ==" "https://horizon.stellar.org/transactions" + EXPECT_EQ(output.signature(), "AAAAAMpFJQVVMv16RJUPlzQUTlgZOHVurhw3igGacP1305F1AAAnEAH/8MgAAAAhAAAAAAAAAAAAAAABAAAAAAAAAA8AAAAAnHt5S3sVDz5Mbc+iYGcrvgwkizYBKREukn4PfuL5+vgAAAAAAAAAAXfTkXUAAABAWL7dKkR1JuPZGFbDTRDgGBHW/vLPMWNRkAew+wPfGiCnZhpJJDcyX197EDDZMsJ7ungPUyhczRaeQOwZKx4DDQ=="); + + { // negative test: hash wrong size + const Data invalidBalanceIdHash = parse_hex("010203"); + input.mutable_op_claim_claimable_balance()->set_balance_id(invalidBalanceIdHash.data(), invalidBalanceIdHash.size()); + ANY_SIGN(input, TWCoinTypeStellar); + EXPECT_EQ(output.signature(), "AAAAAXfTkXUAAABAFCywEfLs3q5Tv9eZCIcjhkJR0s8J4Us9G5YjVKUSaMoUz/AadC8dM2oQSLhpC5wjrNBi7hevg7jlkPx5/4AJCQ=="); + } +} + +} // namespace TW::Stellar::tests diff --git a/tests/Stellar/TWCoinTypeTests.cpp b/tests/chains/Stellar/TWCoinTypeTests.cpp similarity index 97% rename from tests/Stellar/TWCoinTypeTests.cpp rename to tests/chains/Stellar/TWCoinTypeTests.cpp index 12164f9b2d6..2fc1c59fb4b 100644 --- a/tests/Stellar/TWCoinTypeTests.cpp +++ b/tests/chains/Stellar/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Stellar/TWStellarAddressTests.cpp b/tests/chains/Stellar/TWStellarAddressTests.cpp similarity index 96% rename from tests/Stellar/TWStellarAddressTests.cpp rename to tests/chains/Stellar/TWStellarAddressTests.cpp index 47d1f759c76..97f9fc32f12 100644 --- a/tests/Stellar/TWStellarAddressTests.cpp +++ b/tests/chains/Stellar/TWStellarAddressTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Stellar/TransactionTests.cpp b/tests/chains/Stellar/TransactionTests.cpp similarity index 97% rename from tests/Stellar/TransactionTests.cpp rename to tests/chains/Stellar/TransactionTests.cpp index d9324c4c339..8a7890eac2e 100644 --- a/tests/Stellar/TransactionTests.cpp +++ b/tests/chains/Stellar/TransactionTests.cpp @@ -1,25 +1,23 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" -#include "Stellar/Address.h" #include "Stellar/Signer.h" #include "HexCoding.h" #include "PrivateKey.h" #include -#include #include #include "BinaryCoding.h" #include +namespace TW::Stellar::tests { + using namespace std; -using namespace TW; -using namespace TW::Stellar; TEST(StellarTransaction, sign) { auto words = STRING("indicate rival expand cave giant same grocery burden ugly rose tuna blood"); @@ -143,3 +141,5 @@ TEST(StellarTransaction, signAcreateAccount) { const auto signature = signer.sign(); ASSERT_EQ(signature, "AAAAAAmpZryqzBA+OIlrquP4wvBsIf1H3U+GT/DTP5gZ31yiAAAD6AAAAAAAAAACAAAAAAAAAAIAAAAASZYC0gAAAAEAAAAAAAAAAAAAAADFgLYxeg6zm/f81Po8Gf2rS4m7q79hCV7kUFr27O16rgAAAAAAmJaAAAAAAAAAAAEZ31yiAAAAQNgqNDqbe0X60gyH+1xf2Tv2RndFiJmyfbrvVjsTfjZAVRrS2zE9hHlqPQKpZkGKEFka7+1ElOS+/m/1JDnauQg="); } + +} // namespace TW::Stellar::tests diff --git a/tests/chains/THORChain/SignerTests.cpp b/tests/chains/THORChain/SignerTests.cpp new file mode 100644 index 00000000000..85b6bbf68fc --- /dev/null +++ b/tests/chains/THORChain/SignerTests.cpp @@ -0,0 +1,204 @@ +// Copyright © 2017-2021 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 "proto/Cosmos.pb.h" +#include "THORChain/Signer.h" +#include "HexCoding.h" +#include "Bech32Address.h" +#include "TestUtilities.h" + +#include +#include + +using namespace TW; + + +TEST(THORChainSigner, SignTx_Protobuf_7E480F) { + auto input = Cosmos::Proto::SigningInput(); + input.set_signing_mode(Cosmos::Proto::Protobuf); + input.set_chain_id("thorchain-mainnet-v1"); + input.set_account_number(593); + input.set_sequence(21); + input.set_memo(""); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_thorchain_send_message(); + Bech32Address fromAddress("thor"); + EXPECT_TRUE(Bech32Address::decode("thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r", fromAddress, "thor")); + Bech32Address toAddress("thor"); + EXPECT_TRUE(Bech32Address::decode("thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn", toAddress, "thor")); + message.set_from_address(std::string(fromAddress.getKeyHash().begin(), fromAddress.getKeyHash().end())); + message.set_to_address(std::string(toAddress.getKeyHash().begin(), toAddress.getKeyHash().end())); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("rune"); + amountOfTx->set_amount("38000000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(2500000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("rune"); + amountOfFee->set_amount("200"); + + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + + assertJSONEqual(json, R"( + { + "accountNumber": "593", + "chainId": "thorchain-mainnet-v1", + "fee": { + "amounts": [ + { + "amount": "200", + "denom": "rune" + } + ], + "gas": "2500000" + }, + "messages": [ + { + "thorchainSendMessage": { + "amounts": [ + { + "amount": "38000000", + "denom": "rune" + } + ], + "fromAddress": "FSLnZ9tusZcIsAOAKb+9YHvJvQ4=", + "toAddress": "yoZFn7AFUcffQlQMXnhpGSyDOts=" + } + } + ], + "sequence": "21", + "signingMode": "Protobuf" + } + )"); + + auto privateKey = parse_hex("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = THORChain::Signer::sign(input); + + // https://viewblock.io/thorchain/tx/7E480FA163F6C6AFA17593F214C7BBC218F69AE3BC72366E39042AF381BFE105 + // curl -H 'Content-Type: application/json' --data-binary '{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"ClIKUAoO..89g="}' https:///cosmos/tx/v1beta1/txs + assertJSONEqual(output.serialized(), R"( + { + "mode": "BROADCAST_MODE_BLOCK", + "tx_bytes": "ClIKUAoOL3R5cGVzLk1zZ1NlbmQSPgoUFSLnZ9tusZcIsAOAKb+9YHvJvQ4SFMqGRZ+wBVHH30JUDF54aRksgzrbGhAKBHJ1bmUSCDM4MDAwMDAwEmYKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQPtmX45bPQpL1/OWkK7pBWZzNXZbjExVKfJ6nBJ3jF8dxIECgIIARgVEhIKCwoEcnVuZRIDMjAwEKDLmAEaQKZtS3ATa26OOGvqdKm14ZbHeNfkPtIajXi5MkZ5XaX2SWOeX+YnCPZ9TxF9Jj5cVIo71m55xq4hVL3yDbRe89g=" + } + )"); + EXPECT_EQ(hex(output.signature()), "a66d4b70136b6e8e386bea74a9b5e196c778d7e43ed21a8d78b93246795da5f649639e5fe62708f67d4f117d263e5c548a3bd66e79c6ae2154bdf20db45ef3d8"); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error(), ""); +} + +TEST(THORChainSigner, SignTx_Json_Deprecated) { + auto input = Cosmos::Proto::SigningInput(); + input.set_memo("memo1234"); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address("thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r"); + message.set_to_address("thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn"); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("rune"); + amountOfTx->set_amount("50000000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(2000000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("rune"); + amountOfFee->set_amount("200"); + + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + + assertJSONEqual(json, R"( + { + "fee": { + "amounts": [ + { + "denom": "rune", + "amount": "200" + } + ], + "gas": "2000000" + }, + "memo": "memo1234", + "messages": [ + { + "sendCoinsMessage": { + "fromAddress": "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r", + "toAddress": "thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn", + "amounts": [ + { + "denom": "rune", + "amount": "50000000" + } + ] + } + } + ] + } + )"); + + auto privateKey = parse_hex("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = THORChain::Signer::sign(input); + + assertJSONEqual(output.json(), R"( + { + "mode": "block", + "tx": { + "fee": { + "amount": [ + { + "amount": "200", + "denom": "rune" + } + ], + "gas": "2000000" + }, + "memo": "memo1234", + "msg": [ + { + "type": "thorchain/MsgSend", + "value": { + "amount": [ + { + "amount": "50000000", + "denom": "rune" + } + ], + "from_address": "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r", + "to_address": "thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn" + } + } + ], + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A+2Zfjls9CkvX85aQrukFZnM1dluMTFUp8nqcEneMXx3" + }, + "signature": "12AaNC0v51Rhz8rBf7V7rpI6oksREWrjzba3RK1v1NNlqZq62sG0aXWvStp9zZXe07Pp2FviFBAx+uqWsO30NQ==" + } + ] + } + } + )"); + EXPECT_EQ(hex(output.signature()), "d7601a342d2fe75461cfcac17fb57bae923aa24b11116ae3cdb6b744ad6fd4d365a99abadac1b46975af4ada7dcd95ded3b3e9d85be2141031faea96b0edf435"); +} + +TEST(THORChainSigner, SignJson) { + auto inputJson = R"({"fee":{"amounts":[{"denom":"rune","amount":"200"}],"gas":"2000000"},"memo":"memo1234","messages":[{"sendCoinsMessage":{"fromAddress":"thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r","toAddress":"thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn","amounts":[{"denom":"rune","amount":"50000000"}]}}]})"; + auto privateKey = parse_hex("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e"); + + auto outputJson = THORChain::Signer::signJSON(inputJson, privateKey); + + EXPECT_EQ(R"({"mode":"block","tx":{"fee":{"amount":[{"amount":"200","denom":"rune"}],"gas":"2000000"},"memo":"memo1234","msg":[{"type":"thorchain/MsgSend","value":{"amount":[{"amount":"50000000","denom":"rune"}],"from_address":"thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r","to_address":"thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A+2Zfjls9CkvX85aQrukFZnM1dluMTFUp8nqcEneMXx3"},"signature":"12AaNC0v51Rhz8rBf7V7rpI6oksREWrjzba3RK1v1NNlqZq62sG0aXWvStp9zZXe07Pp2FviFBAx+uqWsO30NQ=="}]}})", outputJson); +} diff --git a/tests/chains/THORChain/SwapTests.cpp b/tests/chains/THORChain/SwapTests.cpp new file mode 100644 index 00000000000..1f0738595e4 --- /dev/null +++ b/tests/chains/THORChain/SwapTests.cpp @@ -0,0 +1,423 @@ +// Copyright © 2017-2021 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 "THORChain/Swap.h" +#include "Bitcoin/Script.h" +#include "Bitcoin/SegwitAddress.h" +#include "Ethereum/Address.h" +#include "Ethereum/ABI/Function.h" +#include "Ethereum/ABI/ParamBase.h" +#include "Ethereum/ABI/ParamAddress.h" +#include "Binance/Address.h" +#include "proto/THORChainSwap.pb.h" +#include "proto/Bitcoin.pb.h" +#include "proto/Ethereum.pb.h" +#include "proto/Binance.pb.h" + +#include "HexCoding.h" +#include "Coin.h" +#include +#include +#include "uint256.h" +#include "TestUtilities.h" + +#include + +namespace TW::THORChainSwap { + +// Addresses for wallet 'isolate dismiss fury ... note' +const auto Address1Btc = "bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8"; +const auto Address1Eth = "0xb9f5771c27664bf2282d98e09d7f50cec7cb01a7"; +const auto Address1Bnb = "bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx"; +const auto Address1Thor = "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r"; +const Data TestKey1Btc = parse_hex("13fcaabaf9e71ffaf915e242ec58a743d55f102cf836968e5bd4881135e0c52c"); +const Data TestKey1Eth = parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904"); +const Data TestKey1Bnb = parse_hex("bcf8b072560dda05122c99390def2c385ec400e1a93df0657a85cf6b57a715da"); +const auto VaultBtc = "bc1q6m9u2qsu8mh8y7v8rr2ywavtj8g5arzlyhcej7"; +const auto VaultEth = "0x1091c4De6a3cF09CdA00AbDAeD42c7c3B69C83EC"; +const auto VaultBnb = "bnb1n9esxuw8ca7ts8l6w66kdh800s09msvul6vlse"; +const auto RouterEth = "0x42A5Ed456650a09Dc10EBc6361A7480fDd61f27B"; + + +TEST(THORChainSwap, SwapBtcEth) { + auto res = Swap::build(Chain::BTC, Chain::ETH, Address1Btc, "ETH", "", Address1Eth, VaultBtc, "", "1000000", "140000000000000000"); + ASSERT_EQ(std::get<1>(res), 0); + ASSERT_EQ(std::get<2>(res), ""); + EXPECT_EQ(hex(std::get<0>(res)), "080110c0843d1801222a62633171366d397532717375386d68387937763872723279776176746a38673561727a6c796863656a372a2a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070386a473d3a4554482e4554483a3078623966353737316332373636346266323238326439386530396437663530636563376362303161373a313430303030303030303030303030303030"); + + auto tx = Bitcoin::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(std::get<0>(res).data(), (int)std::get<0>(res).size())); + + // check fields + EXPECT_EQ(tx.amount(), 1000000); + EXPECT_EQ(tx.to_address(), VaultBtc); + EXPECT_EQ(tx.change_address(), Address1Btc); + EXPECT_EQ(tx.output_op_return(), "=:ETH.ETH:0xb9f5771c27664bf2282d98e09d7f50cec7cb01a7:140000000000000000"); + EXPECT_EQ(tx.coin_type(), 0ul); + EXPECT_EQ(tx.private_key_size(), 0); + EXPECT_FALSE(tx.has_plan()); + + // set few fields before signing + tx.set_byte_fee(20); + EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); + tx.add_private_key(TestKey1Btc.data(), TestKey1Btc.size()); + auto& utxo = *tx.add_utxo(); + Data utxoHash = parse_hex("1234000000000000000000000000000000000000000000000000000000005678"); + utxo.mutable_out_point()->set_hash(utxoHash.data(), utxoHash.size()); + utxo.mutable_out_point()->set_index(0); + utxo.mutable_out_point()->set_sequence(UINT32_MAX); + auto utxoScript = Bitcoin::Script::lockScriptForAddress(Address1Btc, TWCoinTypeBitcoin); + utxo.set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); + utxo.set_amount(50000000); + tx.set_use_max_amount(false); + + // sign and encode resulting input + Bitcoin::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeBitcoin); + EXPECT_EQ(output.error(), 0); + EXPECT_EQ(hex(output.encoded()), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "1234000000000000000000000000000000000000000000000000000000005678" "00000000" "00" "" "ffffffff" + "03" // outputs + "40420f0000000000" "16" "0014d6cbc5021c3eee72798718d447758b91d14e8c5f" + "609deb0200000000" "16" "00140cb9f5c6b62c03249367bc20a90dd2425e6926af" + "0000000000000000" "42" "6a403d3a4554482e4554483a3078623966353737316332373636346266323238326439386530396437663530636563376362303161373a3134303030303030303030" + // witness + "02" + "47" "304402205de19c68b5ea683b9d701d45b09f96658088db76e59ad27bd7b8383ee5d484ec0220245459a4d6d679d8b457564fccc7ecc5831c7ebed49e0366c65ac031e8a5b49201" + "21" "021e582a887bd94d648a9267143eb600449a8d59a0db0653740b1378067a6d0cee" + "00000000" // nLockTime + ); +} + +TEST(THORChainSwap, SwapBtcBnb) { + auto res = Swap::build(Chain::BTC, Chain::BNB, Address1Btc, "BNB", "", Address1Bnb, VaultBtc, "", "200000", "140000000"); + ASSERT_EQ(std::get<1>(res), 0); + ASSERT_EQ(std::get<2>(res), ""); + EXPECT_EQ(hex(std::get<0>(res)), "080110c09a0c1801222a62633171366d397532717375386d68387937763872723279776176746a38673561727a6c796863656a372a2a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070386a41535741503a424e422e424e423a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a313430303030303030"); + + auto tx = Bitcoin::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(std::get<0>(res).data(), (int)std::get<0>(res).size())); + + // check fields + EXPECT_EQ(tx.amount(), 200000); + EXPECT_EQ(tx.to_address(), VaultBtc); + EXPECT_EQ(tx.change_address(), Address1Btc); + EXPECT_EQ(tx.output_op_return(), "SWAP:BNB.BNB:bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx:140000000"); + EXPECT_EQ(tx.coin_type(), 0ul); + EXPECT_EQ(tx.private_key_size(), 0); + EXPECT_FALSE(tx.has_plan()); + + // set few fields before signing + tx.set_byte_fee(80); + EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); + tx.add_private_key(TestKey1Btc.data(), TestKey1Btc.size()); + auto& utxo = *tx.add_utxo(); + Data utxoHash = parse_hex("8eae5c3a4c75058d4e3facd5d72f18a40672bcd3d1f35ebf3094bd6c78da48eb"); + std::reverse(utxoHash.begin(), utxoHash.end()); + utxo.mutable_out_point()->set_hash(utxoHash.data(), utxoHash.size()); + utxo.mutable_out_point()->set_index(0); + utxo.mutable_out_point()->set_sequence(UINT32_MAX - 3); + auto utxoScript = Bitcoin::Script::lockScriptForAddress(Address1Btc, TWCoinTypeBitcoin); + utxo.set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); + utxo.set_amount(450000); + tx.set_use_max_amount(false); + + // sign and encode resulting input + Bitcoin::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeBitcoin); + EXPECT_EQ(output.error(), 0); + EXPECT_EQ(hex(output.encoded()), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "eb48da786cbd9430bf5ef3d1d3bc7206a4182fd7d5ac3f4e8d05754c3a5cae8e" "00000000" "00" "" "fcffffff" + "03" // outputs + "400d030000000000" "16" "0014d6cbc5021c3eee72798718d447758b91d14e8c5f" + "108d030000000000" "16" "00140cb9f5c6b62c03249367bc20a90dd2425e6926af" + "0000000000000000" "42" "6a40535741503a424e422e424e423a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a3134303030303030" + // witness + "02" + "48" "30450221008427ac07af830abbf9f2e1b182096d9faefc9e5b4324786ec68386579b05d02102204fd062817a59255d62aba24b1b0c66bc070d0ddbb70bf130a6159cc057e7f6c801210" + "21" "e582a887bd94d648a9267143eb600449a8d59a0db0653740b1378067a6d0cee" + "00000000" // nLockTime + ); + + // similar real transaction: + // https://blockchair.com/bitcoin/transaction/1cd9056b212b85d9d7d34d0795a746dd8691b8cd34ef56df0aa9622fbdec5f88 + // https://viewblock.io/thorchain/tx/1CD9056B212B85D9D7D34D0795A746DD8691B8CD34EF56DF0AA9622FBDEC5F88 + // https://explorer.binance.org/tx/8D78469069118E9B9546696214CCD46E63D3FA0D7E854C094D63C8F6061278B7 +} + +Data SwapTest_ethAddressStringToData(const std::string& asString) { + if (asString.empty()) { + return Data(); + } + auto address = Ethereum::Address(asString); + Data asData; + asData.resize(20); + std::copy(address.bytes.begin(), address.bytes.end(), asData.data()); + return asData; +} + +TEST(THORChainSwap, SwapEthBnb) { + auto res = Swap::build(Chain::ETH, Chain::BNB, Address1Eth, "BNB", "", Address1Bnb, VaultEth, RouterEth, "50000000000000000", "600003"); + ASSERT_EQ(std::get<1>(res), 0); + ASSERT_EQ(std::get<2>(res), ""); + EXPECT_EQ(hex(std::get<0>(res)), "0a01001201002201002a0100422a30783432413545643435363635306130394463313045426336333631413734383066446436316632374252f30132f0010a07b1a2bc2ec5000012e4011fece7b40000000000000000000000001091c4de6a3cf09cda00abdaed42c7c3b69c83ec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b1a2bc2ec500000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000003e535741503a424e422e424e423a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a3630303030330000"); + + auto tx = Ethereum::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(std::get<0>(res).data(), (int)std::get<0>(res).size())); + + // check fields + EXPECT_EQ(tx.to_address(), RouterEth); + ASSERT_TRUE(tx.transaction().has_contract_generic()); + + Data vaultAddressBin = SwapTest_ethAddressStringToData(VaultEth); + EXPECT_EQ(hex(vaultAddressBin), "1091c4de6a3cf09cda00abdaed42c7c3b69c83ec"); + auto func = Ethereum::ABI::Function("deposit", std::vector>{ + std::make_shared(vaultAddressBin), + std::make_shared(parse_hex("0000000000000000000000000000000000000000")), + std::make_shared(uint256_t(50000000000000000)), + std::make_shared("SWAP:BNB.BNB:bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx:600003") + }); + Data payload; + func.encode(payload); + EXPECT_EQ(hex(payload), "1fece7b4" + "0000000000000000000000001091c4de6a3cf09cda00abdaed42c7c3b69c83ec" + "0000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000b1a2bc2ec50000" + "0000000000000000000000000000000000000000000000000000000000000080" + "000000000000000000000000000000000000000000000000000000000000003e" + "535741503a424e422e424e423a626e6231757334377764686678303863683937" + "7a6475656833783375356d757266727833306a656372783a3630303030330000"); + EXPECT_EQ(hex(TW::data(tx.transaction().contract_generic().amount())), "b1a2bc2ec50000"); + EXPECT_EQ(hex(TW::data(tx.transaction().contract_generic().data())), hex(payload)); + + EXPECT_EQ(hex(TW::data(tx.private_key())), ""); + + // set few fields before signing + auto chainId = store(uint256_t(1)); + tx.set_chain_id(chainId.data(), chainId.size()); + auto nonce = store(uint256_t(3)); + tx.set_nonce(nonce.data(), nonce.size()); + auto gasPrice = store(uint256_t(30000000000)); + tx.set_gas_price(gasPrice.data(), gasPrice.size()); + auto gasLimit = store(uint256_t(80000)); + tx.set_gas_limit(gasLimit.data(), gasLimit.size()); + tx.set_private_key(""); + tx.set_private_key(TestKey1Eth.data(), TestKey1Eth.size()); + + // sign and encode resulting input + Ethereum::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeEthereum); + EXPECT_EQ(hex(output.encoded()), "f90151038506fc23ac00830138809442a5ed456650a09dc10ebc6361a7480fdd61f27b87b1a2bc2ec50000b8e41fece7b40000000000000000000000001091c4de6a3cf09cda00abdaed42c7c3b69c83ec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b1a2bc2ec500000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000003e535741503a424e422e424e423a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a363030303033000025a06ae104be3201baca38315352f81fac70ca4dd47339981914e64e91149813e780a066a3f0b2c44ddf5a96a38481274f623f552a593d723237d6742185f4885c0064"); +} + +TEST(THORChainSwap, SwapBnbBtc) { + auto res = Swap::build(Chain::BNB, Chain::BTC, Address1Bnb, "BTC", "", Address1Btc, VaultBnb, "", "10000000", "10000000"); + ASSERT_EQ(std::get<1>(res), 0); + ASSERT_EQ(std::get<2>(res), ""); + EXPECT_EQ(hex(std::get<0>(res)), "2a40535741503a4254432e4254433a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070383a313030303030303052480a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e421080ade20412220a1499730371c7c77cb81ffa76b566dcef7c1e5dc19c120a0a03424e421080ade204"); + + auto tx = Binance::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(std::get<0>(res).data(), (int)std::get<0>(res).size())); + + // check fields + EXPECT_EQ(tx.memo(), "SWAP:BTC.BTC:bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8:10000000"); + ASSERT_TRUE(tx.has_send_order()); + ASSERT_EQ(tx.send_order().inputs_size(), 1); + ASSERT_EQ(tx.send_order().outputs_size(), 1); + EXPECT_EQ(hex(tx.send_order().inputs(0).address()), "e42be736e933cf8b97c26f33789a3ca6f8348cd1"); + EXPECT_EQ(hex(tx.send_order().outputs(0).address()), "99730371c7c77cb81ffa76b566dcef7c1e5dc19c"); + EXPECT_EQ(hex(TW::data(tx.private_key())), ""); + + // set few fields before signing + tx.set_chain_id("Binance-Chain-Tigris"); + tx.set_private_key(TestKey1Bnb.data(), TestKey1Bnb.size()); + + // sign and encode resulting input + Binance::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeBinance); + EXPECT_EQ(hex(output.encoded()), "8002f0625dee0a4c2a2c87fa0a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e421080ade20412220a1499730371c7c77cb81ffa76b566dcef7c1e5dc19c120a0a03424e421080ade204126a0a26eb5ae9872103ea4b4bc12dc6f36a28d2c9775e01eef44def32cc70fb54f0e4177b659dbc0e191240af2117ebd42e31a9562738e9f8933b3b54b59e6305b5675956525e4edb6a6ac65abea614e90959ae388664e2b36bf720024879b6047e174e3cff95f8f364a4e71a40535741503a4254432e4254433a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070383a3130303030303030"); +} + +TEST(THORChainSwap, SwapBnbEth) { + auto res = Swap::build(Chain::BNB, Chain::ETH, Address1Bnb, "ETH", "", Address1Eth, VaultBnb, "", "27000000", "123456"); + ASSERT_EQ(std::get<1>(res), 0); + ASSERT_EQ(std::get<2>(res), ""); + EXPECT_EQ(hex(std::get<0>(res)), "2a3b3d3a4554482e4554483a3078623966353737316332373636346266323238326439386530396437663530636563376362303161373a31323334353652480a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e4210c0f9ef0c12220a1499730371c7c77cb81ffa76b566dcef7c1e5dc19c120a0a03424e4210c0f9ef0c"); + + auto tx = Binance::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(std::get<0>(res).data(), (int)std::get<0>(res).size())); + + // check fields + EXPECT_EQ(tx.memo(), "=:ETH.ETH:0xb9f5771c27664bf2282d98e09d7f50cec7cb01a7:123456"); + ASSERT_TRUE(tx.has_send_order()); + ASSERT_EQ(tx.send_order().inputs_size(), 1); + ASSERT_EQ(tx.send_order().outputs_size(), 1); + EXPECT_EQ(hex(tx.send_order().inputs(0).address()), "e42be736e933cf8b97c26f33789a3ca6f8348cd1"); + EXPECT_EQ(hex(tx.send_order().outputs(0).address()), "99730371c7c77cb81ffa76b566dcef7c1e5dc19c"); + EXPECT_EQ(hex(TW::data(tx.private_key())), ""); + + // set private key and few other fields + EXPECT_EQ(TW::deriveAddress(TWCoinTypeBinance, PrivateKey(TestKey1Bnb)), Address1Bnb); + tx.set_private_key(TestKey1Bnb.data(), TestKey1Bnb.size()); + tx.set_chain_id("Binance-Chain-Tigris"); + tx.set_account_number(1902570); + tx.set_sequence(12); + // sign and encode resulting input + Binance::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeBinance); + EXPECT_EQ(hex(output.encoded()), "8102f0625dee0a4c2a2c87fa0a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e4210c0f9ef0c12220a1499730371c7c77cb81ffa76b566dcef7c1e5dc19c120a0a03424e4210c0f9ef0c12700a26eb5ae9872103ea4b4bc12dc6f36a28d2c9775e01eef44def32cc70fb54f0e4177b659dbc0e1912409ad3d44f3cc8d5dd2701b0bf3758ef674683533fb63e3e94d39728688c0279f8410395d631075dac62dee74b972c320f5a58e88ab81be6f1bb6a9564468ae1b618ea8f74200c1a3b3d3a4554482e4554483a3078623966353737316332373636346266323238326439386530396437663530636563376362303161373a313233343536"); + + // real transaction: + // https://explorer.binance.org/tx/F0CFDB0D9467E83B5BBF6DF92E4E2D04FE9EFF9B0A1C71D88DCEF566233DCAA2 + // https://viewblock.io/thorchain/tx/F0CFDB0D9467E83B5BBF6DF92E4E2D04FE9EFF9B0A1C71D88DCEF566233DCAA2 + // https://etherscan.io/tx/0x8e5bb7d87e17af86e649e402bc5c182ea8c32ddaca153804679de1184e0d9747 +} + +TEST(THORChainSwap, SwapBnbRune) { + auto res = Swap::build(Chain::BNB, Chain::THOR, Address1Bnb, "RUNE", "", Address1Thor, VaultBnb, "", "4000000", "121065076"); + ASSERT_EQ(std::get<1>(res), 0); + ASSERT_EQ(std::get<2>(res), ""); + EXPECT_EQ(hex(std::get<0>(res)), "2a44535741503a54484f522e52554e453a74686f72317a3533777765376d64366365777a39737177717a6e306161767061756e3067773065786e32723a31323130363530373652480a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e42108092f40112220a1499730371c7c77cb81ffa76b566dcef7c1e5dc19c120a0a03424e42108092f401"); + + auto tx = Binance::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(std::get<0>(res).data(), (int)std::get<0>(res).size())); + + // check fields + EXPECT_EQ(tx.memo(), "SWAP:THOR.RUNE:thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r:121065076"); + ASSERT_TRUE(tx.has_send_order()); + ASSERT_EQ(tx.send_order().inputs_size(), 1); + ASSERT_EQ(tx.send_order().outputs_size(), 1); + EXPECT_EQ(hex(tx.send_order().inputs(0).address()), "e42be736e933cf8b97c26f33789a3ca6f8348cd1"); + EXPECT_EQ(hex(tx.send_order().outputs(0).address()), "99730371c7c77cb81ffa76b566dcef7c1e5dc19c"); + EXPECT_EQ(hex(TW::data(tx.private_key())), ""); + + // set private key and few other fields + EXPECT_EQ(TW::deriveAddress(TWCoinTypeBinance, PrivateKey(TestKey1Bnb)), Address1Bnb); + tx.set_private_key(TestKey1Bnb.data(), TestKey1Bnb.size()); + tx.set_chain_id("Binance-Chain-Tigris"); + tx.set_account_number(1902570); + tx.set_sequence(4); + // sign and encode resulting input + Binance::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeBinance); + EXPECT_EQ(hex(output.encoded()), "8a02f0625dee0a4c2a2c87fa0a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e42108092f40112220a1499730371c7c77cb81ffa76b566dcef7c1e5dc19c120a0a03424e42108092f40112700a26eb5ae9872103ea4b4bc12dc6f36a28d2c9775e01eef44def32cc70fb54f0e4177b659dbc0e191240d91b6655ea4ade62a90cc9b28e43ccd2887dcf1c563e42bbd0d6ae4e825c2c6a1ba7784866810f36b6e098b0c877d1daa48016d0558f7b796b3f0b410107ba2f18ea8f7420041a44535741503a54484f522e52554e453a74686f72317a3533777765376d64366365777a39737177717a6e306161767061756e3067773065786e32723a313231303635303736"); + + // real transaction: + // https://explorer.binance.org/tx/84EE429B35945F0568097527A084532A9DE7BBAB0E6A5562E511CEEFB188DE69 + // https://viewblock.io/thorchain/tx/D582E1473FE229F02F162055833C64F49FB4FF515989A4785ED7898560A448FC +} + +TEST(THORChainSwap, SwapBnbBnbToken) { + auto res = Swap::build( + Chain::BNB, + Chain::BNB, + "bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx", + "BNB", + "TWT-8C2", + "bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx", + "bnb1qefsjm654cdw94ejj8g4s49w7z8te75veslusz", + "", + "10000000", // 0.1 bnb + "5400000000" // 54.0 twt + ); + ASSERT_EQ(std::get<1>(res), 0); + ASSERT_EQ(std::get<2>(res), ""); + EXPECT_EQ(hex(std::get<0>(res)), "2a46535741503a424e422e5457542d3843323a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a3534303030303030303052480a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e421080ade20412220a140653096f54ae1ae2d73291d15854aef08ebcfa8c120a0a03424e421080ade204"); + + auto tx = Binance::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(std::get<0>(res).data(), (int)std::get<0>(res).size())); + + // check fields + EXPECT_EQ(tx.memo(), "SWAP:BNB.TWT-8C2:bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx:5400000000"); + ASSERT_TRUE(tx.has_send_order()); + ASSERT_EQ(tx.send_order().inputs_size(), 1); + ASSERT_EQ(tx.send_order().outputs_size(), 1); + EXPECT_EQ(hex(tx.send_order().inputs(0).address()), "e42be736e933cf8b97c26f33789a3ca6f8348cd1"); + EXPECT_EQ(hex(tx.send_order().outputs(0).address()), "0653096f54ae1ae2d73291d15854aef08ebcfa8c"); + EXPECT_EQ(hex(TW::data(tx.private_key())), ""); + + // set private key and few other fields + const Data privateKey = parse_hex("bcf8b072560dda05122c99390def2c385ec400e1a93df0657a85cf6b57a715da"); + EXPECT_EQ(Binance::Address(PrivateKey(privateKey).getPublicKey(TWPublicKeyTypeSECP256k1)).string(), "bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx"); + tx.set_private_key(privateKey.data(), privateKey.size()); + tx.set_chain_id("Binance-Chain-Tigris"); + tx.set_account_number(1902570); + tx.set_sequence(18); + + // sign and encode resulting input + Binance::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeBinance); + EXPECT_EQ(hex(output.encoded()), "8c02f0625dee0a4c2a2c87fa0a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e421080ade20412220a140653096f54ae1ae2d73291d15854aef08ebcfa8c120a0a03424e421080ade20412700a26eb5ae9872103ea4b4bc12dc6f36a28d2c9775e01eef44def32cc70fb54f0e4177b659dbc0e1912405fd64a0ed5777f5ea4556624bd096f8b20b6d2b510655e4c928db1ec967e6c7025453882ce7e10138ac92f5d6a949acc5382a5539f81347856c67c4bb678d3c418ea8f7420121a46535741503a424e422e5457542d3843323a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a35343030303030303030"); + + // real transaction: + // curl -X GET "http://dataseed1.binance.org/broadcast_tx_sync?tx=0x8c02...3030" + // https://viewblock.io/thorchain/tx/6D1EDC9BD9BFAFEF0F88F95A164191262EA02A0413BF3D9773110AD5676E1523 + // https://explorer.binance.org/tx/6D1EDC9BD9BFAFEF0F88F95A164191262EA02A0413BF3D9773110AD5676E1523 + // https://explorer.binance.org/tx/60C54C9F253B89C36A2788AB66951045E8AC5F5729597CB6C64A13013A7A54CC +} + +TEST(THORChainSwap, Memo) { + EXPECT_EQ(Swap::buildMemo(Chain::BTC, "BTC", "", "btc123", 1234), "SWAP:BTC.BTC:btc123:1234"); + EXPECT_EQ(Swap::buildMemo(Chain::BNB, "BNB", "", "bnb123", 1234), "SWAP:BNB.BNB:bnb123:1234"); + EXPECT_EQ(Swap::buildMemo(Chain::ETH, "ETH", "", "0xaabbccdd", 1234), "=:ETH.ETH:0xaabbccdd:1234"); + EXPECT_EQ(Swap::buildMemo(Chain::ETH, "ETH", "", "0xaabbccdd", 1234), "=:ETH.ETH:0xaabbccdd:1234"); + EXPECT_EQ(Swap::buildMemo(Chain::ETH, "ETH", "0x0000000000000000000000000000000000000000", "0xaabbccdd", 1234), "=:ETH.ETH:0xaabbccdd:1234"); + EXPECT_EQ(Swap::buildMemo(Chain::ETH, "ETH", "0x4B0F1812e5Df2A09796481Ff14017e6005508003", "0xaabbccdd", 1234), "=:ETH.0x4B0F1812e5Df2A09796481Ff14017e6005508003:0xaabbccdd:1234"); + EXPECT_EQ(Swap::buildMemo(Chain::BNB, "BNB", "TWT-8C2", "bnb123", 1234), "SWAP:BNB.TWT-8C2:bnb123:1234"); +} + +TEST(THORChainSwap, WrongFromAddress) { + { + auto res = Swap::build(Chain::BNB, Chain::ETH, "DummyAddress", "ETH", "", Address1Eth, VaultEth, "", "100000", "100000"); + EXPECT_EQ(std::get<1>(res), Proto::ErrorCode::Error_Invalid_from_address); + EXPECT_EQ(std::get<2>(res), "Invalid from address"); + } + { + auto res = Swap::build(Chain::BNB, Chain::ETH, Address1Btc, "ETH", "", Address1Eth, VaultEth, "", "100000", "100000"); + EXPECT_EQ(std::get<1>(res), Proto::ErrorCode::Error_Invalid_from_address); + EXPECT_EQ(std::get<2>(res), "Invalid from address"); + } +} + +TEST(THORChainSwap, WrongToAddress) { + { + auto res = Swap::build(Chain::BNB, Chain::ETH, Address1Bnb, "ETH", "", "DummyAddress", VaultEth, "", "100000", "100000"); + EXPECT_EQ(std::get<1>(res), Proto::ErrorCode::Error_Invalid_to_address); + EXPECT_EQ(std::get<2>(res), "Invalid to address"); + } + { + auto res = Swap::build(Chain::BNB, Chain::ETH, Address1Bnb, "ETH", "", Address1Btc, VaultEth, "", "100000", "100000"); + EXPECT_EQ(std::get<1>(res), Proto::ErrorCode::Error_Invalid_to_address); + EXPECT_EQ(std::get<2>(res), "Invalid to address"); + } +} + +TEST(THORChainSwap, FromRuneNotSupported) { + auto res = Swap::build(Chain::THOR, Chain::BNB, Address1Thor, "BNB", "", Address1Bnb, "", "", "1000", "1000"); + EXPECT_EQ(std::get<1>(res), Proto::ErrorCode::Error_Unsupported_from_chain); + EXPECT_EQ(std::get<2>(res), "Unsupported from chain: 3"); +} + +TEST(THORChainSwap, EthInvalidVault) { + { + auto res = Swap::build(Chain::ETH, Chain::BNB, Address1Eth, "BNB", "", Address1Bnb, "_INVALID_ADDRESS_", RouterEth, "50000000000000000", "600003"); + EXPECT_EQ(std::get<1>(res), Proto::ErrorCode::Error_Invalid_vault_address); + EXPECT_EQ(std::get<2>(res), "Invalid vault address: _INVALID_ADDRESS_"); + } + { + auto res = Swap::build(Chain::ETH, Chain::BNB, Address1Eth, "BNB", "", Address1Bnb, VaultEth, "_INVALID_ADDRESS_", "50000000000000000", "600003"); + EXPECT_EQ(std::get<1>(res), Proto::ErrorCode::Error_Invalid_router_address); + EXPECT_EQ(std::get<2>(res), "Invalid router address: _INVALID_ADDRESS_"); + } +} + +} // namespace diff --git a/tests/THORChain/TWAnyAddressTests.cpp b/tests/chains/THORChain/TWAnyAddressTests.cpp similarity index 96% rename from tests/THORChain/TWAnyAddressTests.cpp rename to tests/chains/THORChain/TWAnyAddressTests.cpp index 28e1618885e..52d9bc560cb 100644 --- a/tests/THORChain/TWAnyAddressTests.cpp +++ b/tests/chains/THORChain/TWAnyAddressTests.cpp @@ -7,7 +7,7 @@ #include #include "HexCoding.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include using namespace TW; diff --git a/tests/THORChain/TWAnySignerTests.cpp b/tests/chains/THORChain/TWAnySignerTests.cpp similarity index 94% rename from tests/THORChain/TWAnySignerTests.cpp rename to tests/chains/THORChain/TWAnySignerTests.cpp index 0f065497660..8e9e45380d7 100644 --- a/tests/THORChain/TWAnySignerTests.cpp +++ b/tests/chains/THORChain/TWAnySignerTests.cpp @@ -10,7 +10,7 @@ #include "proto/Cosmos.pb.h" #include "HexCoding.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include using namespace TW; @@ -33,13 +33,13 @@ TEST(THORChainTWAnySigner, SignTx) { message.set_to_address(toAddress); auto amountOfTx = message.add_amounts(); amountOfTx->set_denom("rune"); - amountOfTx->set_amount(10000000); + amountOfTx->set_amount("10000000"); auto& fee = *input.mutable_fee(); fee.set_gas(200000); auto amountOfFee = fee.add_amounts(); amountOfFee->set_denom("rune"); - amountOfFee->set_amount(2000000); + amountOfFee->set_amount("2000000"); Cosmos::Proto::SigningOutput output; ANY_SIGN(input, TWCoinTypeTHORChain); diff --git a/tests/THORChain/TWCoinTypeTests.cpp b/tests/chains/THORChain/TWCoinTypeTests.cpp similarity index 93% rename from tests/THORChain/TWCoinTypeTests.cpp rename to tests/chains/THORChain/TWCoinTypeTests.cpp index a80328106c6..de7f561af47 100644 --- a/tests/THORChain/TWCoinTypeTests.cpp +++ b/tests/chains/THORChain/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -23,7 +23,7 @@ TEST(TWTHORChainCoinType, TWCoinType) { auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeTHORChain)); ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeTHORChain), 8); - ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeTHORChain)); + ASSERT_EQ(TWBlockchainThorchain, TWCoinTypeBlockchain(TWCoinTypeTHORChain)); ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeTHORChain)); ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeTHORChain)); assertStringsEqual(symbol, "RUNE"); diff --git a/tests/chains/THORChain/TWSwapTests.cpp b/tests/chains/THORChain/TWSwapTests.cpp new file mode 100644 index 00000000000..ea266cb6584 --- /dev/null +++ b/tests/chains/THORChain/TWSwapTests.cpp @@ -0,0 +1,230 @@ +// Copyright © 2017-2021 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Bitcoin/Script.h" +#include "Bitcoin/SegwitAddress.h" +#include "PrivateKey.h" +#include "proto/Binance.pb.h" +#include "proto/Ethereum.pb.h" +#include "proto/THORChainSwap.pb.h" +#include +#include +#include + +#include "HexCoding.h" +#include "uint256.h" +#include "TestUtilities.h" + +#include + +using namespace TW; + +namespace TW::THORChainSwap::tests { + +// clang-format off +const auto Address1Bnb = "bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx"; +const auto Address1Btc = "bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8"; +const auto Address1Eth = "0xb9f5771c27664bf2282d98e09d7f50cec7cb01a7"; +const auto VaultBnb = "bnb1n9esxuw8ca7ts8l6w66kdh800s09msvul6vlse"; +const auto VaultBtc = "bc1q6m9u2qsu8mh8y7v8rr2ywavtj8g5arzlyhcej7"; +const auto VaultEth = "0x1091c4De6a3cF09CdA00AbDAeD42c7c3B69C83EC"; +const auto RouterEth = "0x42A5Ed456650a09Dc10EBc6361A7480fDd61f27B"; +const Data TestKey1Bnb = parse_hex("bcf8b072560dda05122c99390def2c385ec400e1a93df0657a85cf6b57a715da"); +const Data TestKey1Btc = parse_hex("13fcaabaf9e71ffaf915e242ec58a743d55f102cf836968e5bd4881135e0c52c"); +const Data TestKey1Eth = parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904"); + +TEST(TWTHORChainSwap, SwapBtcToEth) { + // prepare swap input + Proto::SwapInput input; + input.set_from_chain(Proto::BTC); + input.set_from_address(Address1Btc); + Proto::Asset toAsset; + toAsset.set_chain(Proto::ETH); + toAsset.set_symbol("ETH"); + toAsset.set_token_id(""); + *input.mutable_to_asset() = toAsset; + input.set_to_address(Address1Eth); + input.set_vault_address(VaultBtc); + input.set_router_address(""); + input.set_from_amount("1000000"); + input.set_to_amount_limit("140000000000000000"); + + // serialize input + const auto inputData_ = input.SerializeAsString(); + EXPECT_EQ(hex(inputData_), "0801122a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070381a0708021203455448222a3078623966353737316332373636346266323238326439386530396437663530636563376362303161372a2a62633171366d397532717375386d68387937763872723279776176746a38673561727a6c796863656a373a07313030303030304212313430303030303030303030303030303030"); + const auto inputTWData_ = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData_.data(), inputData_.size())); + + // invoke swap + const auto outputTWData_ = WRAPD(TWTHORChainSwapBuildSwap(inputTWData_.get())); + const auto outputData = data(TWDataBytes(outputTWData_.get()), TWDataSize(outputTWData_.get())); + EXPECT_EQ(outputData.size(), 178ul); + // parse result in proto + Proto::SwapOutput outputProto; + EXPECT_TRUE(outputProto.ParseFromArray(outputData.data(), static_cast(outputData.size()))); + EXPECT_EQ(outputProto.from_chain(), Proto::BTC); + EXPECT_EQ(outputProto.to_chain(), Proto::ETH); + EXPECT_EQ(outputProto.error().code(), 0); + EXPECT_EQ(outputProto.error().message(), ""); + EXPECT_TRUE(outputProto.has_bitcoin()); + Bitcoin::Proto::SigningInput txInput = outputProto.bitcoin(); + + // tx input: check some fields + EXPECT_EQ(txInput.amount(), 1000000); + EXPECT_EQ(txInput.to_address(), "bc1q6m9u2qsu8mh8y7v8rr2ywavtj8g5arzlyhcej7"); + EXPECT_EQ(txInput.change_address(), "bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8"); + EXPECT_EQ(txInput.output_op_return(), "=:ETH.ETH:0xb9f5771c27664bf2282d98e09d7f50cec7cb01a7:140000000000000000"); + EXPECT_EQ(txInput.coin_type(), 0ul); + + // sign tx input for signed full tx + // set few fields before signing + txInput.set_byte_fee(20); + EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); + txInput.add_private_key(TestKey1Btc.data(), TestKey1Btc.size()); + auto& utxo = *txInput.add_utxo(); + Data utxoHash = parse_hex("1234000000000000000000000000000000000000000000000000000000005678"); + utxo.mutable_out_point()->set_hash(utxoHash.data(), utxoHash.size()); + utxo.mutable_out_point()->set_index(0); + utxo.mutable_out_point()->set_sequence(UINT32_MAX); + auto utxoScript = Bitcoin::Script::lockScriptForAddress(Address1Btc, TWCoinTypeBitcoin); + utxo.set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); + utxo.set_amount(50000000); + txInput.set_use_max_amount(false); + + // sign and encode resulting input + { + Bitcoin::Proto::SigningOutput output; + ANY_SIGN(txInput, TWCoinTypeBitcoin); + EXPECT_EQ(output.error(), 0); + EXPECT_EQ(hex(output.encoded()), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "1234000000000000000000000000000000000000000000000000000000005678" "00000000" "00" "" "ffffffff" + "03" // outputs + "40420f0000000000" "16" "0014d6cbc5021c3eee72798718d447758b91d14e8c5f" + "609deb0200000000" "16" "00140cb9f5c6b62c03249367bc20a90dd2425e6926af" + "0000000000000000" "42" "6a403d3a4554482e4554483a3078623966353737316332373636346266323238326439386530396437663530636563376362303161373a3134303030303030303030" + // witness + "02" + "47" "304402205de19c68b5ea683b9d701d45b09f96658088db76e59ad27bd7b8383ee5d484ec0220245459a4d6d679d8b457564fccc7ecc5831c7ebed49e0366c65ac031e8a5b49201" + "21" "021e582a887bd94d648a9267143eb600449a8d59a0db0653740b1378067a6d0cee" + "00000000" // nLockTime + ); + } +} + +TEST(TWTHORChainSwap, SwapEthBnb) { + // prepare swap input + Proto::SwapInput input; + input.set_from_chain(Proto::ETH); + input.set_from_address(Address1Eth); + Proto::Asset toAsset; + toAsset.set_chain(Proto::BNB); + toAsset.set_symbol("BNB"); + toAsset.set_token_id(""); + *input.mutable_to_asset() = toAsset; + input.set_to_address(Address1Bnb); + input.set_vault_address(VaultEth); + input.set_router_address(RouterEth); + input.set_from_amount("50000000000000000"); + input.set_to_amount_limit("600003"); + + // serialize input + const auto inputData_ = input.SerializeAsString(); + EXPECT_EQ(hex(inputData_), "0802122a3078623966353737316332373636346266323238326439386530396437663530636563376362303161371a0708031203424e42222a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372782a2a307831303931633444653661336346303943644130304162444165443432633763334236394338334543322a3078343241354564343536363530613039446331304542633633363141373438306644643631663237423a1135303030303030303030303030303030304206363030303033"); + const auto inputTWData_ = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData_.data(), inputData_.size())); + + // invoke swap + const auto outputTWData_ = WRAPD(TWTHORChainSwapBuildSwap(inputTWData_.get())); + const auto outputData = data(TWDataBytes(outputTWData_.get()), TWDataSize(outputTWData_.get())); + EXPECT_EQ(outputData.size(), 311ul); + // parse result in proto + Proto::SwapOutput outputProto; + EXPECT_TRUE(outputProto.ParseFromArray(outputData.data(), static_cast(outputData.size()))); + EXPECT_EQ(outputProto.from_chain(), Proto::ETH); + EXPECT_EQ(outputProto.to_chain(), Proto::BNB); + EXPECT_EQ(outputProto.error().code(), 0); + EXPECT_EQ(outputProto.error().message(), ""); + EXPECT_TRUE(outputProto.has_ethereum()); + Ethereum::Proto::SigningInput txInput = outputProto.ethereum(); + + // sign tx input for signed full tx + // set few fields before signing + auto chainId = store(uint256_t(1)); + txInput.set_chain_id(chainId.data(), chainId.size()); + auto nonce = store(uint256_t(3)); + txInput.set_nonce(nonce.data(), nonce.size()); + auto gasPrice = store(uint256_t(30000000000)); + txInput.set_gas_price(gasPrice.data(), gasPrice.size()); + auto gasLimit = store(uint256_t(80000)); + txInput.set_gas_limit(gasLimit.data(), gasLimit.size()); + txInput.set_private_key(""); + txInput.set_private_key(TestKey1Eth.data(), TestKey1Eth.size()); + + // sign and encode resulting input + Ethereum::Proto::SigningOutput output; + ANY_SIGN(txInput, TWCoinTypeEthereum); + EXPECT_EQ(hex(output.encoded()), "f90151038506fc23ac00830138809442a5ed456650a09dc10ebc6361a7480fdd61f27b87b1a2bc2ec50000b8e41fece7b40000000000000000000000001091c4de6a3cf09cda00abdaed42c7c3b69c83ec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b1a2bc2ec500000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000003e535741503a424e422e424e423a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a363030303033000025a06ae104be3201baca38315352f81fac70ca4dd47339981914e64e91149813e780a066a3f0b2c44ddf5a96a38481274f623f552a593d723237d6742185f4885c0064"); +} + +TEST(TWTHORChainSwap, SwapBnbBtc) { + // prepare swap input + Proto::SwapInput input; + input.set_from_chain(Proto::BNB); + input.set_from_address(Address1Bnb); + Proto::Asset toAsset; + toAsset.set_chain(Proto::BTC); + toAsset.set_symbol("BTC"); + toAsset.set_token_id(""); + *input.mutable_to_asset() = toAsset; + input.set_to_address(Address1Btc); + input.set_vault_address(VaultBnb); + input.set_router_address(""); + input.set_from_amount("10000000"); + input.set_to_amount_limit("10000000"); + + // serialize input + const auto inputData_ = input.SerializeAsString(); + EXPECT_EQ(hex(inputData_), "0803122a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372781a0708011203425443222a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070382a2a626e62316e396573787577386361377473386c367736366b64683830307330396d7376756c36766c73653a08313030303030303042083130303030303030"); + const auto inputTWData_ = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData_.data(), inputData_.size())); + + // invoke swap + const auto outputTWData_ = WRAPD(TWTHORChainSwapBuildSwap(inputTWData_.get())); + const auto outputData = data(TWDataBytes(outputTWData_.get()), TWDataSize(outputTWData_.get())); + EXPECT_EQ(outputData.size(), 149ul); + // parse result in proto + Proto::SwapOutput outputProto; + EXPECT_TRUE(outputProto.ParseFromArray(outputData.data(), static_cast(outputData.size()))); + EXPECT_EQ(outputProto.from_chain(), Proto::BNB); + EXPECT_EQ(outputProto.to_chain(), Proto::BTC); + EXPECT_EQ(outputProto.error().code(), 0); + EXPECT_EQ(outputProto.error().message(), ""); + EXPECT_TRUE(outputProto.has_binance()); + Binance::Proto::SigningInput txInput = outputProto.binance(); + + // set few fields before signing + txInput.set_chain_id("Binance-Chain-Tigris"); + txInput.set_private_key(TestKey1Bnb.data(), TestKey1Bnb.size()); + + // sign and encode resulting input + Ethereum::Proto::SigningOutput output; + ANY_SIGN(txInput, TWCoinTypeBinance); + EXPECT_EQ(hex(output.encoded()), "8002f0625dee0a4c2a2c87fa0a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e421080ade20412220a1499730371c7c77cb81ffa76b566dcef7c1e5dc19c120a0a03424e421080ade204126a0a26eb5ae9872103ea4b4bc12dc6f36a28d2c9775e01eef44def32cc70fb54f0e4177b659dbc0e191240af2117ebd42e31a9562738e9f8933b3b54b59e6305b5675956525e4edb6a6ac65abea614e90959ae388664e2b36bf720024879b6047e174e3cff95f8f364a4e71a40535741503a4254432e4254433a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070383a3130303030303030"); +} + +TEST(TWTHORChainSwap, NegativeInvalidInput) { + const auto inputData = parse_hex("00112233"); + const auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData.data(), inputData.size())); + + const auto outputTWData = WRAPD(TWTHORChainSwapBuildSwap(inputTWData.get())); + const auto outputData = data(TWDataBytes(outputTWData.get()), TWDataSize(outputTWData.get())); + EXPECT_EQ(outputData.size(), 39ul); + EXPECT_EQ(hex(outputData), "1a2508021221436f756c64206e6f7420646573657269616c697a6520696e7075742070726f746f"); + EXPECT_EQ(hex(data(std::string("Could not deserialize input proto"))), "436f756c64206e6f7420646573657269616c697a6520696e7075742070726f746f"); +} +// clang-format on + +} // namespace TW::ThorChainSwap::tests diff --git a/tests/chains/Terra/SignerTests.cpp b/tests/chains/Terra/SignerTests.cpp new file mode 100644 index 00000000000..34c01feb560 --- /dev/null +++ b/tests/chains/Terra/SignerTests.cpp @@ -0,0 +1,454 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "HexCoding.h" +#include "Base64.h" +#include "proto/Cosmos.pb.h" +#include "Cosmos/Address.h" +#include "Cosmos/Signer.h" +#include "Cosmos/ProtobufSerialization.h" +#include "uint256.h" +#include "TestUtilities.h" + +#include +#include + +namespace TW::Cosmos::tests { + +TEST(TerraClassicSigner, SignSendTx) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::JSON); + input.set_account_number(1037); + input.set_chain_id("columbus-5"); + input.set_memo(""); + input.set_sequence(2); + + Address fromAddress; + ASSERT_TRUE(Address::decode("terra1hsk6jryyqjfhp5dhc55tc9jtckygx0ep37hdd2", fromAddress)); + Address toAddress; + ASSERT_TRUE(Address::decode("terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", toAddress)); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address(fromAddress.string()); + message.set_to_address(toAddress.string()); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("luna"); + amountOfTx->set_amount("1000000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("luna"); + amountOfFee->set_amount("200"); + + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + EXPECT_EQ(json, R"({"accountNumber":"1037","chainId":"columbus-5","fee":{"amounts":[{"denom":"luna","amount":"200"}],"gas":"200000"},"sequence":"2","messages":[{"sendCoinsMessage":{"fromAddress":"terra1hsk6jryyqjfhp5dhc55tc9jtckygx0ep37hdd2","toAddress":"terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf","amounts":[{"denom":"luna","amount":"1000000"}]}}]})"); + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeTerra); + + assertJSONEqual(output.json(), R"( + { + "mode": "block", + "tx": { + "fee": { + "amount": [ + { + "amount": "200", + "denom": "luna" + } + ], + "gas": "200000" + }, + "memo": "", + "msg": [ + { + "type": "cosmos-sdk/MsgSend", + "value": { + "amount": [ + { + "amount": "1000000", + "denom": "luna" + } + ], + "from_address": "terra1hsk6jryyqjfhp5dhc55tc9jtckygx0ep37hdd2", + "to_address": "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf" + } + } + ], + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F" + }, + "signature": "ofdIsLJzkODcQwLG89eE2g4HOaUmfKPh/08t07ehKPUqRMl4rVonzo73mkOvqtrHWjdtB+6t6R8DGudPpb6bRg==" + } + ] + } + } + )"); + EXPECT_EQ(hex(output.signature()), "a1f748b0b27390e0dc4302c6f3d784da0e0739a5267ca3e1ff4f2dd3b7a128f52a44c978ad5a27ce8ef79a43afaadac75a376d07eeade91f031ae74fa5be9b46"); + EXPECT_EQ(output.serialized(), ""); + EXPECT_EQ(output.error(), ""); +} + +TEST(TerraClassicSigner, SignWasmTransferTxProtobuf_9FF3F0) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(3407705); + input.set_chain_id("columbus-5"); + input.set_memo(""); + input.set_sequence(3); + + Address fromAddress; + ASSERT_TRUE(Address::decode("terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", fromAddress)); + Address toAddress; + ASSERT_TRUE(Address::decode("terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", toAddress)); + const auto tokenContractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76"; // ANC + + auto msg = input.add_messages(); + auto& message = *msg->mutable_wasm_terra_execute_contract_transfer_message(); + message.set_sender_address(fromAddress.string()); + message.set_contract_address(tokenContractAddress); + const auto amount = store(uint256_t(250000), 0); + message.set_amount(amount.data(), amount.size()); + message.set_recipient_address(toAddress.string()); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uluna"); + amountOfFee->set_amount("3000"); + + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + assertJSONEqual(json, R"( + { + "signingMode": "Protobuf", + "accountNumber": "3407705", + "chainId": "columbus-5", + "fee": { + "amounts": [ + { + "denom": "uluna", + "amount": "3000" + } + ], + "gas": "200000" + }, + "sequence": "3", + "messages": [ + { + "wasmTerraExecuteContractTransferMessage": { + "senderAddress": "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", + "contractAddress": "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76", + "amount": "A9CQ", + "recipientAddress": "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp" + } + } + ] + } + )"); + + auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeTerra); + + // https://finder.terra.money/mainnet/tx/9FF3F0A16879254C22EB90D8B4D6195467FE5014381FD36BD3C23CA6698FE94B + // curl -H 'Content-Type: application/json' --data-binary '{"mode": "BROADCAST_MODE_BLOCK","tx_bytes": "CogCCo..wld8"})' https:///cosmos/tx/v1beta1/txs + assertJSONEqual(output.serialized(), R"( + { + "tx_bytes": "CucBCuQBCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBK5AQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2Glt7InRyYW5zZmVyIjp7ImFtb3VudCI6IjI1MDAwMCIsInJlY2lwaWVudCI6InRlcnJhMWpsZ2FxeTludm4yaGY1dDJzcmE5eWN6OHM3N3duZjlsMGttZ2NwIn19EmcKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQNwZjrHsPmJKW/rXOWfukpQ1+lOHOJW3/IlFFnKLNmsABIECgIIARgDEhMKDQoFdWx1bmESBDMwMDAQwJoMGkAaprIEMLPH2HmFdwFGoaipb2GIyhXt6ombz+WMnG2mORBI6gFt0M+IymYgzZz6w1SW52R922yafDnn7yXfutRw", + "mode": "BROADCAST_MODE_BLOCK" + } + )"); + EXPECT_EQ(hex(output.signature()), "1aa6b20430b3c7d87985770146a1a8a96f6188ca15edea899bcfe58c9c6da6391048ea016dd0cf88ca6620cd9cfac35496e7647ddb6c9a7c39e7ef25dfbad470"); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error(), ""); +} + +TEST(TerraClassicSigner, SignWasmTransferTxJson_078E90) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::JSON); + input.set_account_number(3407705); + input.set_chain_id("columbus-5"); + input.set_memo(""); + input.set_sequence(2); + + Address fromAddress; + ASSERT_TRUE(Address::decode("terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", fromAddress)); + Address toAddress; + ASSERT_TRUE(Address::decode("terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", toAddress)); + const auto tokenContractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76"; // ANC + + auto msg = input.add_messages(); + auto& message = *msg->mutable_wasm_terra_execute_contract_transfer_message(); + message.set_sender_address(fromAddress.string()); + message.set_contract_address(tokenContractAddress); + const auto amount = store(250000); + message.set_amount(amount.data(), amount.size()); + message.set_recipient_address(toAddress.string()); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uluna"); + amountOfFee->set_amount("3000"); + + auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeTerra); + + // https://finder.terra.money/mainnet/tx/078E90458061611F6FD8B708882B55FF5C1FFB3FCE61322107A0A0DE39FC0F3E + // curl -H 'Content-Type: application/json' --data-binary '{"mode": "block","tx":{...}}' https:///txs + assertJSONEqual(output.json(), R"( + { + "mode": "block", + "tx": + { + "fee": {"amount":[{"amount": "3000","denom": "uluna"}],"gas": "200000"}, + "memo": "", + "msg": + [ + { + "type": "wasm/MsgExecuteContract", + "value": + { + "sender": "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", + "contract": "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76", + "execute_msg": + { + "transfer": + { + "amount": "250000", + "recipient": "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp" + } + }, + "coins": [] + } + } + ], + "signatures": + [ + { + "pub_key": + { + "type": "tendermint/PubKeySecp256k1", + "value": "A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA" + }, + "signature": "BjETdtbA97Wv1zvcsCV1tM+bdYKC8O3uGTk4mMRv6pBJB2y/Ds7qoS7s/zrkhYak1YChklQetHsI30XRXzGIkg==" + } + ] + } + })"); + EXPECT_EQ(hex(output.signature()), "06311376d6c0f7b5afd73bdcb02575b4cf9b758282f0edee19393898c46fea9049076cbf0eceeaa12eecff3ae48586a4d580a192541eb47b08df45d15f318892"); + EXPECT_EQ(output.serialized(), ""); + EXPECT_EQ(output.error(), ""); +} + +TEST(TerraClassicSigner, SignWasmGeneric_EC4F85) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(3407705); + input.set_chain_id("columbus-5"); + input.set_memo(""); + input.set_sequence(7); + + Address fromAddress; + ASSERT_TRUE(Address::decode("terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", fromAddress)); + Address toAddress; + ASSERT_TRUE(Address::decode("terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", toAddress)); + const auto tokenContractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76"; // ANC + const auto txMessage = R"({"transfer": { "amount": "250000", "recipient": "terra1d7048csap4wzcv5zm7z6tdqem2agyp9647vdyj" } })"; + + auto msg = input.add_messages(); + auto& message = *msg->mutable_wasm_terra_execute_contract_generic(); + message.set_sender_address(fromAddress.string()); + message.set_contract_address(tokenContractAddress); + message.set_execute_msg(txMessage); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uluna"); + amountOfFee->set_amount("3000"); + + auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeTerra); + + // https://finder.terra.money/mainnet/tx/EC4F8532847E4D6AF016E6F6D3F027AE7FB6FF0B533C5132B01382D83B214A6F + // curl -H 'Content-Type: application/json' --data-binary '{"mode": "BROADCAST_MODE_BLOCK","tx_bytes": "Cu4BC...iVt"})' https:///cosmos/tx/v1beta1/txs + assertJSONEqual(output.serialized(), R"( + { + "tx_bytes": "Cu4BCusBCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBLAAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2GmJ7InRyYW5zZmVyIjogeyAiYW1vdW50IjogIjI1MDAwMCIsICJyZWNpcGllbnQiOiAidGVycmExZDcwNDhjc2FwNHd6Y3Y1em03ejZ0ZHFlbTJhZ3lwOTY0N3ZkeWoiIH0gfRJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYBxITCg0KBXVsdW5hEgQzMDAwEMCaDBpAkPsS7xlSng2LMc9KiD1soN5NLaDcUh8I9okPmsdJN3le1B7yxRGNB4aQfhaRl/8Z0r5vitRT0AWuxDasd8wcFw==", + "mode": "BROADCAST_MODE_BLOCK" + } + )"); + + EXPECT_EQ(hex(output.signature()), "90fb12ef19529e0d8b31cf4a883d6ca0de4d2da0dc521f08f6890f9ac74937795ed41ef2c5118d0786907e169197ff19d2be6f8ad453d005aec436ac77cc1c17"); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error(), ""); +} + +TEST(TerraClassicSigner, SignWasmGenericWithCoins_6651FC) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(3407705); + input.set_chain_id("columbus-5"); + input.set_memo(""); + input.set_sequence(9); + + Address fromAddress; + ASSERT_TRUE(Address::decode("terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", fromAddress)); + Address toAddress; + ASSERT_TRUE(Address::decode("terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", toAddress)); + const auto tokenContractAddress = "terra1sepfj7s0aeg5967uxnfk4thzlerrsktkpelm5s"; // ANC Market + const auto txMessage = R"({ "deposit_stable": {} })"; + + auto msg = input.add_messages(); + auto& message = *msg->mutable_wasm_terra_execute_contract_generic(); + message.set_sender_address(fromAddress.string()); + message.set_contract_address(tokenContractAddress); + message.set_execute_msg(txMessage); + + auto amount = message.add_coins(); + amount->set_denom("uusd"); + amount->set_amount("1000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(600000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uluna"); + amountOfFee->set_amount("7000"); + + auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeTerra); + + // https://finder.terra.money/mainnet/tx/6651FCE0EE5C6D6ACB655CC49A6FD5E939FB082862854616EA0642475BCDD0C9 + // curl -H 'Content-Type: application/json' --data-binary '{"mode": "BROADCAST_MODE_BLOCK","tx_bytes": "CrIBCq8B.....0NWg=="})' https:///cosmos/tx/v1beta1/txs + assertJSONEqual(output.serialized(), R"( + { + "tx_bytes": "CrIBCq8BCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBKEAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMXNlcGZqN3MwYWVnNTk2N3V4bmZrNHRoemxlcnJza3RrcGVsbTVzGhh7ICJkZXBvc2l0X3N0YWJsZSI6IHt9IH0qDAoEdXVzZBIEMTAwMBJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYCRITCg0KBXVsdW5hEgQ3MDAwEMDPJBpAGyi7f1ioY8XV6pjFq1s86Om4++CIUnd3rLHif2iopCcYvX0mLkTlQ6NUERg8nWTYgXcj6fOTO/ptgPuAtv0NWg==", + "mode": "BROADCAST_MODE_BLOCK" + } + )"); + + EXPECT_EQ(hex(output.signature()), "1b28bb7f58a863c5d5ea98c5ab5b3ce8e9b8fbe088527777acb1e27f68a8a42718bd7d262e44e543a35411183c9d64d8817723e9f3933bfa6d80fb80b6fd0d5a"); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error(), ""); +} + +TEST(TerraClassicSigner, SignWasmSendTxProtobuf) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(3407705); + input.set_chain_id("columbus-5"); + input.set_memo(""); + input.set_sequence(4); + + Address fromAddress; + ASSERT_TRUE(Address::decode("terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", fromAddress)); + Address toAddress; + ASSERT_TRUE(Address::decode("terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", toAddress)); + const auto tokenContractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76"; // ANC + + auto msg = input.add_messages(); + auto& message = *msg->mutable_wasm_terra_execute_contract_send_message(); + message.set_sender_address(fromAddress.string()); + message.set_contract_address(tokenContractAddress); + const auto amount = store(uint256_t(250000), 0); + message.set_amount(amount.data(), amount.size()); + message.set_recipient_contract_address(toAddress.string()); + const auto msgMsg = Base64::encode(data(std::string(R"({"some_message":{}})"))); + EXPECT_EQ(msgMsg, "eyJzb21lX21lc3NhZ2UiOnt9fQ=="); + message.set_msg(msgMsg); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uluna"); + amountOfFee->set_amount("3000"); + + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + assertJSONEqual(json, R"( + { + "signingMode": "Protobuf", + "accountNumber": "3407705", + "chainId": "columbus-5", + "fee": { + "amounts": [ + { + "denom": "uluna", + "amount": "3000" + } + ], + "gas": "200000" + }, + "sequence": "4", + "messages": [ + { + "wasmTerraExecuteContractSendMessage": { + "senderAddress": "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", + "contractAddress": "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76", + "amount": "A9CQ", + "recipientContractAddress": "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", + "msg": "eyJzb21lX21lc3NhZ2UiOnt9fQ==" + } + } + ] + } + )"); + + auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeTerra); + + // https://finder.terra.money/mainnet/tx/9FF3F0A16879254C22EB90D8B4D6195467FE5014381FD36BD3C23CA6698FE94B + // curl -H 'Content-Type: application/json' --data-binary '{"mode": "BROADCAST_MODE_BLOCK","tx_bytes": "CogCCo..wld8"})' https:///cosmos/tx/v1beta1/txs + assertJSONEqual(output.serialized(), R"( + { + "tx_bytes": "CocCCoQCCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBLZAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2Gnt7InNlbmQiOnsiYW1vdW50IjoiMjUwMDAwIiwiY29udHJhY3QiOiJ0ZXJyYTFqbGdhcXk5bnZuMmhmNXQyc3JhOXljejhzNzd3bmY5bDBrbWdjcCIsIm1zZyI6ImV5SnpiMjFsWDIxbGMzTmhaMlVpT250OWZRPT0ifX0SZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awAEgQKAggBGAQSEwoNCgV1bHVuYRIEMzAwMBDAmgwaQL6NByKeRZsyq5g6CTMdmPqiM77nOe9uLO8FjpetFgkBFiG3Le7ieZZ+4vCMhD1bcFgMwSHibFI/uPil847U/+g=", + "mode": "BROADCAST_MODE_BLOCK" + } + )"); + EXPECT_EQ(hex(output.signature()), "be8d07229e459b32ab983a09331d98faa233bee739ef6e2cef058e97ad1609011621b72deee279967ee2f08c843d5b70580cc121e26c523fb8f8a5f38ed4ffe8"); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error(), ""); +} + +TEST(TerraClassicSigner, SignWasmTerraTransferPayload) { + auto proto = Proto::Message_WasmTerraExecuteContractTransfer(); + proto.set_recipient_address("recipient=address"); + const auto amount = store(uint256_t(250000), 0); + proto.set_amount(amount.data(), amount.size()); + + const auto payload = Protobuf::wasmTerraExecuteTransferPayload(proto); + + assertJSONEqual(payload.dump(), R"( + { + "transfer": + { + "amount": "250000", + "recipient": "recipient=address" + } + } + )"); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/Terra/TWCoinTypeTests.cpp b/tests/chains/Terra/TWCoinTypeTests.cpp similarity index 65% rename from tests/Terra/TWCoinTypeTests.cpp rename to tests/chains/Terra/TWCoinTypeTests.cpp index 4c0a23d8c94..464be13fa31 100644 --- a/tests/Terra/TWCoinTypeTests.cpp +++ b/tests/chains/Terra/TWCoinTypeTests.cpp @@ -1,19 +1,16 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include +namespace TW::Cosmos::tests { -TEST(TWTerraCoinType, TWCoinType) { +TEST(TWTerraCoinType, TWCoinTypeClassic) { auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeTerra)); auto txId = WRAPS(TWStringCreateWithUTF8Bytes("D28D8AFC7CE89F2A22FA2DBF78D2C0A36E549BB830C4D9FA7459E3F723CA7182")); auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeTerra, txId.get())); @@ -21,14 +18,18 @@ TEST(TWTerraCoinType, TWCoinType) { auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeTerra, accId.get())); auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeTerra)); auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeTerra)); + const auto chainId = WRAPS(TWCoinTypeChainId(TWCoinTypeTerra)); ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeTerra), 6); ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeTerra)); ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeTerra)); ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeTerra)); - assertStringsEqual(symbol, "LUNA"); - assertStringsEqual(txUrl, "https://finder.terra.money/tx/tx/D28D8AFC7CE89F2A22FA2DBF78D2C0A36E549BB830C4D9FA7459E3F723CA7182"); - assertStringsEqual(accUrl, "https://finder.terra.money/tx/address/terra16t3gx5rqvz6ru37yzn3shuu20erv4ngmfr59zf"); + assertStringsEqual(chainId, "columbus-5"); + assertStringsEqual(symbol, "LUNC"); + assertStringsEqual(txUrl, "https://finder.terra.money/classic/tx/D28D8AFC7CE89F2A22FA2DBF78D2C0A36E549BB830C4D9FA7459E3F723CA7182"); + assertStringsEqual(accUrl, "https://finder.terra.money/classic/address/terra16t3gx5rqvz6ru37yzn3shuu20erv4ngmfr59zf"); assertStringsEqual(id, "terra"); - assertStringsEqual(name, "Terra"); + assertStringsEqual(name, "Terra Classic"); } + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/TerraV2/SignerTests.cpp b/tests/chains/TerraV2/SignerTests.cpp new file mode 100644 index 00000000000..b1cc0c152b2 --- /dev/null +++ b/tests/chains/TerraV2/SignerTests.cpp @@ -0,0 +1,362 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "HexCoding.h" +#include "Base64.h" +#include "proto/Cosmos.pb.h" +#include "Cosmos/Address.h" +#include "Cosmos/Signer.h" +#include "Cosmos/ProtobufSerialization.h" +#include "uint256.h" +#include "TestUtilities.h" + +#include +#include + +namespace TW::Cosmos::tests { + +TEST(TerraSigner, SignSendTx) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(1037); + input.set_chain_id("phoenix-1"); + input.set_memo(""); + input.set_sequence(1); + + Address fromAddress; + ASSERT_TRUE(Address::decode("terra1hsk6jryyqjfhp5dhc55tc9jtckygx0ep37hdd2", fromAddress)); + Address toAddress; + ASSERT_TRUE(Address::decode("terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", toAddress)); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address(fromAddress.string()); + message.set_to_address(toAddress.string()); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("uluna"); + amountOfTx->set_amount("1000000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uluna"); + amountOfFee->set_amount("30000"); + + { + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + assertJSONEqual(json, R"( + { + "signingMode": "Protobuf", + "accountNumber": "1037", + "chainId": "phoenix-1", + "fee": { + "amounts": [ + { + "denom": "uluna", + "amount": "30000" + } + ], + "gas": "200000" + }, + "sequence": "1", + "messages": [ + { + "sendCoinsMessage": { + "fromAddress": "terra1hsk6jryyqjfhp5dhc55tc9jtckygx0ep37hdd2", + "toAddress": "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", + "amounts": [ + { + "denom": "uluna", + "amount": "1000000" + } + ] + } + } + ] + } + )"); + } + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeTerraV2); + + // similar tx: https://finder.terra.money/mainnet/tx/fbbe73ad2f0db3a13911dc424f8a34370dc4b7e8b66687f536797e68ee200ece + assertJSONEqual(output.serialized(), R"( + { + "tx_bytes": "CpEBCo4BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm4KLHRlcnJhMWhzazZqcnl5cWpmaHA1ZGhjNTV0YzlqdGNreWd4MGVwMzdoZGQyEix0ZXJyYTFqbGdhcXk5bnZuMmhmNXQyc3JhOXljejhzNzd3bmY5bDBrbWdjcBoQCgV1bHVuYRIHMTAwMDAwMBJoClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECVyhuw/N9M1V7u6oACyd0SskCOqmWfK51oYHR/5H6ncUSBAoCCAEYARIUCg4KBXVsdW5hEgUzMDAwMBDAmgwaQPh0C3rjzdixIUiyPx3FlWAxzbKILNAcSRVeQnaTl1vsI5DEfYa2oYlUBLqyilcMCcU/iaJLhex30No2ak0Zn1Q=", + "mode": "BROADCAST_MODE_BLOCK" + } + )"); + EXPECT_EQ(hex(output.signature()), "f8740b7ae3cdd8b12148b23f1dc5956031cdb2882cd01c49155e427693975bec2390c47d86b6a1895404bab28a570c09c53f89a24b85ec77d0da366a4d199f54"); + EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.json(), ""); +} + +TEST(TerraSigner, SignWasmTransferTx) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(3407705); + input.set_chain_id("phoenix-1"); + input.set_memo(""); + input.set_sequence(3); + + Address fromAddress; + ASSERT_TRUE(Address::decode("terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", fromAddress)); + Address toAddress; + ASSERT_TRUE(Address::decode("terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", toAddress)); + const auto tokenContractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76"; + + auto msg = input.add_messages(); + auto& message = *msg->mutable_wasm_execute_contract_transfer_message(); + message.set_sender_address(fromAddress.string()); + message.set_contract_address(tokenContractAddress); + const auto amount = store(uint256_t(250000), 0); + message.set_amount(amount.data(), amount.size()); + message.set_recipient_address(toAddress.string()); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uluna"); + amountOfFee->set_amount("3000"); + + { + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + assertJSONEqual(json, R"( + { + "signingMode": "Protobuf", + "accountNumber": "3407705", + "chainId": "phoenix-1", + "fee": { + "amounts": [ + { + "denom": "uluna", + "amount": "3000" + } + ], + "gas": "200000" + }, + "sequence": "3", + "messages": [ + { + "wasmExecuteContractTransferMessage": { + "senderAddress": "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", + "contractAddress": "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76", + "amount": "A9CQ", + "recipientAddress": "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp" + } + } + ] + } + )"); + } + + auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeTerraV2); + + assertJSONEqual(output.serialized(), R"( + { + "tx_bytes": "CuUBCuIBCiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QSuQEKLHRlcnJhMTh3dWtwODRkcTIyN3d1NG1naDBqbTZuOW5sbmo2cnM4MnBwOXdmEix0ZXJyYTE0ejU2bDBmcDJsc2Y4Nnp5M2h0eTJ6NDdlemtobnRodHI5eXE3NhpbeyJ0cmFuc2ZlciI6eyJhbW91bnQiOiIyNTAwMDAiLCJyZWNpcGllbnQiOiJ0ZXJyYTFqbGdhcXk5bnZuMmhmNXQyc3JhOXljejhzNzd3bmY5bDBrbWdjcCJ9fRJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYAxITCg0KBXVsdW5hEgQzMDAwEMCaDBpAiBGbQaj+jsXE6/FssD3fC77QOxpli9GqsPea+KoNyMIEgVj89Hii+oU1bAEQS4qV0SaE2V6RNy24uCcFTIRbcQ==", + "mode": "BROADCAST_MODE_BLOCK" + } + )"); + EXPECT_EQ(hex(output.signature()), "88119b41a8fe8ec5c4ebf16cb03ddf0bbed03b1a658bd1aab0f79af8aa0dc8c2048158fcf478a2fa85356c01104b8a95d12684d95e91372db8b827054c845b71"); + EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.json(), ""); +} + +TEST(TerraSigner, SignWasmGeneric) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(3407705); + input.set_chain_id("phoenix-1"); + input.set_memo(""); + input.set_sequence(7); + + Address fromAddress; + ASSERT_TRUE(Address::decode("terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", fromAddress)); + const auto tokenContractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76"; + const auto txMessage = R"({"transfer": { "amount": "250000", "recipient": "terra1d7048csap4wzcv5zm7z6tdqem2agyp9647vdyj" } })"; + + auto msg = input.add_messages(); + auto& message = *msg->mutable_wasm_execute_contract_generic(); + message.set_sender_address(fromAddress.string()); + message.set_contract_address(tokenContractAddress); + message.set_execute_msg(txMessage); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uluna"); + amountOfFee->set_amount("3000"); + + auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeTerraV2); + + assertJSONEqual(output.serialized(), R"( + { + "tx_bytes": "CuwBCukBCiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QSwAEKLHRlcnJhMTh3dWtwODRkcTIyN3d1NG1naDBqbTZuOW5sbmo2cnM4MnBwOXdmEix0ZXJyYTE0ejU2bDBmcDJsc2Y4Nnp5M2h0eTJ6NDdlemtobnRodHI5eXE3NhpieyJ0cmFuc2ZlciI6IHsgImFtb3VudCI6ICIyNTAwMDAiLCAicmVjaXBpZW50IjogInRlcnJhMWQ3MDQ4Y3NhcDR3emN2NXptN3o2dGRxZW0yYWd5cDk2NDd2ZHlqIiB9IH0SZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awAEgQKAggBGAcSEwoNCgV1bHVuYRIEMzAwMBDAmgwaQGlYzOoAu/PfyCTSTisGJVW9KWwifxMbCmzy2xwqNg+ZHQkDjVRyUBl7gmbXXLzdOMqtwF1CMauJhlGwmEdzhK4=", + "mode": "BROADCAST_MODE_BLOCK" + } + )"); + + EXPECT_EQ(hex(output.signature()), "6958ccea00bbf3dfc824d24e2b062555bd296c227f131b0a6cf2db1c2a360f991d09038d547250197b8266d75cbcdd38caadc05d4231ab898651b098477384ae"); + EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.json(), ""); +} + +TEST(TerraSigner, SignWasmGenericWithCoins) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(3407705); + input.set_chain_id("phoenix-1"); + input.set_memo(""); + input.set_sequence(9); + + Address fromAddress; + ASSERT_TRUE(Address::decode("terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", fromAddress)); + const auto tokenContractAddress = "terra1sepfj7s0aeg5967uxnfk4thzlerrsktkpelm5s"; + const auto txMessage = R"({ "deposit_stable": {} })"; + + auto msg = input.add_messages(); + auto& message = *msg->mutable_wasm_execute_contract_generic(); + message.set_sender_address(fromAddress.string()); + message.set_contract_address(tokenContractAddress); + message.set_execute_msg(txMessage); + + auto amount = message.add_coins(); + amount->set_denom("uusd"); + amount->set_amount("1000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(600000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uluna"); + amountOfFee->set_amount("7000"); + + auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeTerraV2); + + assertJSONEqual(output.serialized(), R"( + { + "tx_bytes": "CrABCq0BCiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QShAEKLHRlcnJhMTh3dWtwODRkcTIyN3d1NG1naDBqbTZuOW5sbmo2cnM4MnBwOXdmEix0ZXJyYTFzZXBmajdzMGFlZzU5Njd1eG5mazR0aHpsZXJyc2t0a3BlbG01cxoYeyAiZGVwb3NpdF9zdGFibGUiOiB7fSB9KgwKBHV1c2QSBDEwMDASZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awAEgQKAggBGAkSEwoNCgV1bHVuYRIENzAwMBDAzyQaQEDA2foXegF+rslj6o8bX2HPJfn+q/6Ezbq2iAd0SFOTQqS8aAyywQkdZJRToXcaby1HOYL1WvmsMPgrFzChiY4=", + "mode": "BROADCAST_MODE_BLOCK" + } + )"); + + EXPECT_EQ(hex(output.signature()), "40c0d9fa177a017eaec963ea8f1b5f61cf25f9feabfe84cdbab688077448539342a4bc680cb2c1091d649453a1771a6f2d473982f55af9ac30f82b1730a1898e"); + EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.json(), ""); +} + +TEST(TerraSigner, SignWasmSendTx) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(3407705); + input.set_chain_id("phoenix-1"); + input.set_memo(""); + input.set_sequence(4); + + Address fromAddress; + ASSERT_TRUE(Address::decode("terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", fromAddress)); + Address toAddress; + ASSERT_TRUE(Address::decode("terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", toAddress)); + const auto tokenContractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76"; + + auto msg = input.add_messages(); + auto& message = *msg->mutable_wasm_execute_contract_send_message(); + message.set_sender_address(fromAddress.string()); + message.set_contract_address(tokenContractAddress); + const auto amount = store(uint256_t(250000), 0); + message.set_amount(amount.data(), amount.size()); + message.set_recipient_contract_address(toAddress.string()); + const auto msgMsg = Base64::encode(data(std::string(R"({"some_message":{}})"))); + EXPECT_EQ(msgMsg, "eyJzb21lX21lc3NhZ2UiOnt9fQ=="); + message.set_msg(msgMsg); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uluna"); + amountOfFee->set_amount("3000"); + + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + assertJSONEqual(json, R"( + { + "signingMode": "Protobuf", + "accountNumber": "3407705", + "chainId": "phoenix-1", + "fee": { + "amounts": [ + { + "denom": "uluna", + "amount": "3000" + } + ], + "gas": "200000" + }, + "sequence": "4", + "messages": [ + { + "wasmExecuteContractSendMessage": { + "senderAddress": "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", + "contractAddress": "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76", + "amount": "A9CQ", + "recipientContractAddress": "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", + "msg": "eyJzb21lX21lc3NhZ2UiOnt9fQ==" + } + } + ] + } + )"); + + auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input, TWCoinTypeTerraV2); + + assertJSONEqual(output.serialized(), R"( + { + "tx_bytes": "CoUCCoICCiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QS2QEKLHRlcnJhMTh3dWtwODRkcTIyN3d1NG1naDBqbTZuOW5sbmo2cnM4MnBwOXdmEix0ZXJyYTE0ejU2bDBmcDJsc2Y4Nnp5M2h0eTJ6NDdlemtobnRodHI5eXE3Nhp7eyJzZW5kIjp7ImFtb3VudCI6IjI1MDAwMCIsImNvbnRyYWN0IjoidGVycmExamxnYXF5OW52bjJoZjV0MnNyYTl5Y3o4czc3d25mOWwwa21nY3AiLCJtc2ciOiJleUp6YjIxbFgyMWxjM05oWjJVaU9udDlmUT09In19EmcKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQNwZjrHsPmJKW/rXOWfukpQ1+lOHOJW3/IlFFnKLNmsABIECgIIARgEEhMKDQoFdWx1bmESBDMwMDAQwJoMGkBKJbW1GDrv9j2FIckm7MtpDZzP2RjgDjU84oYmOHNHsxEBPLjtt3YAjsKWBCAsjbnbVoJ3s2XFG08nxQXS9xBK", + "mode": "BROADCAST_MODE_BLOCK" + } + )"); + EXPECT_EQ(hex(output.signature()), "4a25b5b5183aeff63d8521c926eccb690d9ccfd918e00e353ce28626387347b311013cb8edb776008ec29604202c8db9db568277b365c51b4f27c505d2f7104a"); + EXPECT_EQ(output.error(), ""); + EXPECT_EQ(output.json(), ""); +} + +TEST(TerraSigner, SignWasmTransferPayload) { + auto proto = Proto::Message_WasmExecuteContractTransfer(); + proto.set_recipient_address("recipient=address"); + const auto amount = store(uint256_t(250000), 0); + proto.set_amount(amount.data(), amount.size()); + + const auto payload = Protobuf::wasmExecuteTransferPayload(proto); + + assertJSONEqual(payload.dump(), R"( + { + "transfer": + { + "amount": "250000", + "recipient": "recipient=address" + } + } + )"); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/TerraV2/TWCoinTypeTests.cpp b/tests/chains/TerraV2/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..ee436963959 --- /dev/null +++ b/tests/chains/TerraV2/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "TestUtilities.h" +#include +#include + +namespace TW::Cosmos::tests { + +TEST(TWTerraCoinType, TWCoinType20) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeTerraV2)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("CFF732C6EBEE06FFA08ABE54EE1657FD53E90FAA81604619E2062C46572A6986")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeTerraV2, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("terra16t3gx5rqvz6ru37yzn3shuu20erv4ngmfr59zf")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeTerraV2, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeTerraV2)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeTerraV2)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeTerraV2), 6); + ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeTerraV2)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeTerraV2)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeTerraV2)); + assertStringsEqual(symbol, "LUNA"); + assertStringsEqual(txUrl, "https://finder.terra.money/mainnet/tx/CFF732C6EBEE06FFA08ABE54EE1657FD53E90FAA81604619E2062C46572A6986"); + assertStringsEqual(accUrl, "https://finder.terra.money/mainnet/address/terra16t3gx5rqvz6ru37yzn3shuu20erv4ngmfr59zf"); + assertStringsEqual(id, "terrav2"); + assertStringsEqual(name, "Terra"); +} + +} // namespace TW::Cosmos::tests + diff --git a/tests/Tezos/AddressTests.cpp b/tests/chains/Tezos/AddressTests.cpp similarity index 78% rename from tests/Tezos/AddressTests.cpp rename to tests/chains/Tezos/AddressTests.cpp index 9a9e5c090cf..38e121ce7df 100644 --- a/tests/Tezos/AddressTests.cpp +++ b/tests/chains/Tezos/AddressTests.cpp @@ -4,20 +4,21 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "Tezos/Address.h" #include "HDWallet.h" #include "HexCoding.h" #include "PrivateKey.h" +#include "Tezos/Address.h" #include "Tezos/Forging.h" #include +#include #include #include -#include using namespace TW; -using namespace TW::Tezos; + +namespace TW::Tezos::tests { TEST(TezosAddress, forge_tz1) { auto input = Address("tz1eZwq8b5cvE2bPKokatLkVMzkxz24z3Don"); @@ -41,10 +42,10 @@ TEST(TezosAddress, forge_tz3) { } TEST(TezosAddress, isInvalid) { - std::array invalidAddresses { - "NmH7tmeJUmHcncBDvpr7aJNEBk7rp5zYsB1qt", // Invalid prefix, valid checksum - "tz1eZwq8b5cvE2bPKokatLkVMzkxz24z3AAAA", // Valid prefix, invalid checksum - "1tzeZwq8b5cvE2bPKokatLkVMzkxz24zAAAAA" // Invalid prefix, invalid checksum + std::array invalidAddresses{ + "NmH7tmeJUmHcncBDvpr7aJNEBk7rp5zYsB1qt", // Invalid prefix, valid checksum + "tz1eZwq8b5cvE2bPKokatLkVMzkxz24z3AAAA", // Valid prefix, invalid checksum + "1tzeZwq8b5cvE2bPKokatLkVMzkxz24zAAAAA" // Invalid prefix, invalid checksum }; for (auto& address : invalidAddresses) { @@ -53,14 +54,13 @@ TEST(TezosAddress, isInvalid) { } TEST(TezosAddress, isValid) { - std::array validAddresses { + std::array validAddresses{ "tz1Yju7jmmsaUiG9qQLoYv35v5pHgnWoLWbt", "tz2PdGc7U5tiyqPgTSgqCDct94qd6ovQwP6u", - "tz3VEZ4k6a4Wx42iyev6i2aVAptTRLEAivNN" - }; + "tz3VEZ4k6a4Wx42iyev6i2aVAptTRLEAivNN"}; for (auto& address : validAddresses) { - ASSERT_TRUE(Address::isValid(address)); + ASSERT_TRUE(Address::isValid(address)); } } @@ -74,15 +74,17 @@ TEST(TezosAddress, deriveOriginatedAddress) { auto operationHash = "oo7VeTEPjEusPKnsHtKcGYbYa7i4RWpcEhUVo3Suugbbs6K62Ro"; auto operationIndex = 0; auto expected = "KT1WrtjtAYQSrUVvSNJPTZTebiUWoopQL5hw"; - + ASSERT_EQ(Address::deriveOriginatedAddress(operationHash, operationIndex), expected); } TEST(TezosAddress, PublicKeyInit) { - Data bytes {1, 254, 21, 124, 200, 1, 23, 39, 147, 108, 89, 47, 133, 108, 144, 113, 211, 156, 244, 172, 218, 223, 166, 215, 100, 53, 228, 97, 156, 157, 197, 111, 99,}; + Data bytes = parse_hex("01fe157cc8011727936c592f856c9071d39cf4acdadfa6d76435e4619c9dc56f63"); const auto publicKey = PublicKey(bytes, TWPublicKeyTypeED25519); auto address = Address(publicKey); auto expected = "tz1cG2jx3W4bZFeVGBjsTxUAG8tdpTXtE8PT"; ASSERT_EQ(address.string(), expected); } + +} // namespace TW::Tezos::tests diff --git a/tests/Tezos/ForgingTests.cpp b/tests/chains/Tezos/ForgingTests.cpp similarity index 57% rename from tests/Tezos/ForgingTests.cpp rename to tests/chains/Tezos/ForgingTests.cpp index 187569f6021..66187be27d2 100644 --- a/tests/Tezos/ForgingTests.cpp +++ b/tests/chains/Tezos/ForgingTests.cpp @@ -1,26 +1,23 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "Tezos/BinaryCoding.h" -#include "Tezos/Address.h" #include "HDWallet.h" #include "HexCoding.h" -#include "PublicKey.h" #include "PrivateKey.h" +#include "PublicKey.h" +#include "Tezos/Address.h" +#include "Tezos/BinaryCoding.h" #include "Tezos/Forging.h" #include "proto/Tezos.pb.h" - #include - +#include #include #include -#include -using namespace TW; -using namespace TW::Tezos; +namespace TW::Tezos::tests { TEST(Forging, ForgeBoolTrue) { auto expected = "ff"; @@ -40,41 +37,41 @@ TEST(Forging, ForgeBoolFalse) { TEST(Forging, ForgeZarithZero) { auto expected = "00"; - + auto output = forgeZarith(0); - + ASSERT_EQ(hex(output), hex(parse_hex(expected))); } TEST(Forging, ForgeZarithTen) { auto expected = "0a"; - + auto output = forgeZarith(10); - + ASSERT_EQ(output, parse_hex(expected)); } TEST(Forging, ForgeZarithTwenty) { auto expected = "14"; - + auto output = forgeZarith(20); - + ASSERT_EQ(output, parse_hex(expected)); } TEST(Forging, ForgeZarithOneHundredFifty) { auto expected = "9601"; - + auto output = forgeZarith(150); - + ASSERT_EQ(output, parse_hex(expected)); } TEST(Forging, ForgeZarithLarge) { auto expected = "bbd08001"; - + auto output = forgeZarith(2107451); - + ASSERT_EQ(hex(output), expected); } @@ -104,19 +101,43 @@ TEST(Forging, forge_tz3) { TEST(Forging, ForgePublicKey) { auto expected = "00311f002e899cdd9a52d96cb8be18ea2bbab867c505da2b44ce10906f511cff95"; - + auto privateKey = PrivateKey(parse_hex("c6377a4cc490dc913fc3f0d9cf67d293a32df4547c46cb7e9e33c3b7b97c64d8")); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); auto output = forgePublicKey(publicKey); - + ASSERT_EQ(hex(output), expected); } +TEST(Forging, ForgeInt32) { + auto expected = "01"; + ASSERT_EQ(hex(forgeInt32(1, 1)), expected); +} + +TEST(Forging, ForgeString) { + auto expected = "087472616e73666572"; + ASSERT_EQ(hex(forgeString("transfer", 1)), expected); +} -TEST(TezosTransaction, forgeTransaction) { +TEST(Forging, ForgeEntrypoint) { + auto expected = "ff087472616e73666572"; + ASSERT_EQ(hex(forgeEntrypoint("transfer")), expected); +} + +TEST(Forging, ForgeMichelsonFA12) { + Tezos::Proto::FA12Parameters data; + data.set_entrypoint("transfer"); + data.set_from("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + data.set_to("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + data.set_value("123"); + auto v = FA12ParameterToMichelson(data); + ASSERT_EQ(hex(forgeMichelson(v)), "07070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555007070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555000bb01"); +} + +TEST(TezosTransaction, forgeTransaction) { auto transactionOperationData = new TW::Tezos::Proto::TransactionOperationData(); - transactionOperationData -> set_amount(1); - transactionOperationData -> set_destination("tz1Yju7jmmsaUiG9qQLoYv35v5pHgnWoLWbt"); + transactionOperationData->set_amount(1); + transactionOperationData->set_destination("tz1Yju7jmmsaUiG9qQLoYv35v5pHgnWoLWbt"); auto transactionOperation = TW::Tezos::Proto::Operation(); transactionOperation.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); @@ -133,11 +154,61 @@ TEST(TezosTransaction, forgeTransaction) { ASSERT_EQ(hex(serialized.begin(), serialized.end()), expected); } +TEST(TezosTransaction, forgeTransactionFA12) { + auto transactionOperationData = new TW::Tezos::Proto::TransactionOperationData(); + transactionOperationData->set_amount(0); + transactionOperationData->set_destination("KT1EwXFWoG9bYebmF4pYw72aGjwEnBWefgW5"); + transactionOperationData->mutable_parameters()->mutable_fa12_parameters()->set_from("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + transactionOperationData->mutable_parameters()->mutable_fa12_parameters()->set_to("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + transactionOperationData->mutable_parameters()->mutable_fa12_parameters()->set_entrypoint("transfer"); + transactionOperationData->mutable_parameters()->mutable_fa12_parameters()->set_value("123"); + + auto transactionOperation = TW::Tezos::Proto::Operation(); + transactionOperation.set_source("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + transactionOperation.set_fee(100000); + transactionOperation.set_counter(2993172); + transactionOperation.set_gas_limit(100000); + transactionOperation.set_storage_limit(0); + transactionOperation.set_kind(TW::Tezos::Proto::Operation::TRANSACTION); + transactionOperation.set_allocated_transaction_operation_data(transactionOperationData); + + auto expected = "6c00fe2ce0cccc0214af521ad60c140c5589b4039247a08d0694d8b601a08d0600000145bd8a65cc48159d8ea60a55df735b7c5ad45f0e00ffff087472616e736665720000005907070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555007070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555000bb01"; + auto serialized = forgeOperation(transactionOperation); + + ASSERT_EQ(hex(serialized), expected); +} + +TEST(TezosTransaction, forgeTransactionFA2) { + auto transactionOperationData = new TW::Tezos::Proto::TransactionOperationData(); + transactionOperationData->set_amount(0); + transactionOperationData->set_destination("KT1DYk1XDzHredJq1EyNkDindiWDqZyekXGj"); + auto& fa2 = *transactionOperationData->mutable_parameters()->mutable_fa2_parameters(); + fa2.set_entrypoint("transfer"); + auto& txObject = *fa2.add_txs_object(); + txObject.set_from("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + auto& tx = *txObject.add_txs(); + tx.set_amount("10"); + tx.set_token_id("0"); + tx.set_to("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + + auto transactionOperation = TW::Tezos::Proto::Operation(); + transactionOperation.set_source("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + transactionOperation.set_fee(100000); + transactionOperation.set_counter(2993173); + transactionOperation.set_gas_limit(100000); + transactionOperation.set_storage_limit(0); + transactionOperation.set_kind(TW::Tezos::Proto::Operation::TRANSACTION); + transactionOperation.set_allocated_transaction_operation_data(transactionOperationData); + auto serialized = forgeOperation(transactionOperation); + auto expected = "6c00fe2ce0cccc0214af521ad60c140c5589b4039247a08d0695d8b601a08d0600000136767f88850bae28bfb9f46b73c5e87ede4de12700ffff087472616e7366657200000066020000006107070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b5550020000003107070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555007070000000a"; + ASSERT_EQ(hex(serialized), expected); +} + TEST(TezosTransaction, forgeReveal) { PublicKey publicKey = parsePublicKey("edpku9ZF6UUAEo1AL3NWy1oxHLL6AfQcGYwA5hFKrEKVHMT3Xx889A"); - + auto revealOperationData = new TW::Tezos::Proto::RevealOperationData(); - revealOperationData -> set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); + revealOperationData->set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); auto revealOperation = TW::Tezos::Proto::Operation(); revealOperation.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); @@ -147,7 +218,7 @@ TEST(TezosTransaction, forgeReveal) { revealOperation.set_storage_limit(257); revealOperation.set_kind(TW::Tezos::Proto::Operation::REVEAL); revealOperation.set_allocated_reveal_operation_data(revealOperationData); - + auto expected = "6b0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80992f001f44e810200429a986c8072a40a1f3a3e2ab5a5819bb1b2fb69993c5004837815b9dc55923e"; auto serialized = forgeOperation(revealOperation); @@ -190,4 +261,6 @@ TEST(TezosTransaction, forgeUndelegate) { auto serialized = forgeOperation(delegateOperation); ASSERT_EQ(hex(serialized.begin(), serialized.end()), expected); -} \ No newline at end of file +} + +} // namespace TW::Tezos::tests diff --git a/tests/Tezos/OperationListTests.cpp b/tests/chains/Tezos/OperationListTests.cpp similarity index 75% rename from tests/Tezos/OperationListTests.cpp rename to tests/chains/Tezos/OperationListTests.cpp index db57ea1da0e..9fb89d9e8a6 100644 --- a/tests/Tezos/OperationListTests.cpp +++ b/tests/chains/Tezos/OperationListTests.cpp @@ -12,8 +12,7 @@ #include -using namespace TW::Tezos; -using namespace TW::Tezos::Proto; +namespace TW::Tezos::tests { TEST(TezosOperationList, ForgeBranch) { auto input = TW::Tezos::OperationList("BMNY6Jkas7BzKb7wDLCFoQ4YxfYoieU7Xmo1ED3Y9Lo3ZvVGdgW"); @@ -27,17 +26,17 @@ TEST(TezosOperationList, ForgeOperationList_TransactionOnly) { auto op_list = TW::Tezos::OperationList(branch); auto key = parsePrivateKey("edsk4bMQMM6HYtMazF3m7mYhQ6KQ1WCEcBuRwh6DTtdnoqAvC3nPCc"); - auto transactionOperationData = new TW::Tezos::Proto::TransactionOperationData(); - transactionOperationData -> set_amount(1); - transactionOperationData -> set_destination("tz1Yju7jmmsaUiG9qQLoYv35v5pHgnWoLWbt"); + auto transactionOperationData = new Proto::TransactionOperationData(); + transactionOperationData->set_amount(1); + transactionOperationData->set_destination("tz1Yju7jmmsaUiG9qQLoYv35v5pHgnWoLWbt"); - auto transactionOperation = TW::Tezos::Proto::Operation(); + auto transactionOperation = Proto::Operation(); transactionOperation.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); transactionOperation.set_fee(1272); transactionOperation.set_counter(30738); transactionOperation.set_gas_limit(10100); transactionOperation.set_storage_limit(257); - transactionOperation.set_kind(TW::Tezos::Proto::Operation::TRANSACTION); + transactionOperation.set_kind(Proto::Operation::TRANSACTION); transactionOperation.set_allocated_transaction_operation_data(transactionOperationData); op_list.addOperation(transactionOperation); @@ -53,10 +52,10 @@ TEST(TezosOperationList, ForgeOperationList_RevealOnly) { auto key = parsePrivateKey("edsk4bMQMM6HYtMazF3m7mYhQ6KQ1WCEcBuRwh6DTtdnoqAvC3nPCc"); PublicKey publicKey = parsePublicKey("edpku9ZF6UUAEo1AL3NWy1oxHLL6AfQcGYwA5hFKrEKVHMT3Xx889A"); - auto revealOperationData = new TW::Tezos::Proto::RevealOperationData(); - revealOperationData -> set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); + auto revealOperationData = new Proto::RevealOperationData(); + revealOperationData->set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); - auto revealOperation = TW::Tezos::Proto::Operation(); + auto revealOperation = Proto::Operation(); revealOperation.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); revealOperation.set_fee(1272); revealOperation.set_counter(30738); @@ -74,18 +73,18 @@ TEST(TezosOperationList, ForgeOperationList_RevealOnly) { TEST(TezosOperationList, ForgeOperationList_Delegation_ClearDelegate) { auto branch = "BLGJfQDFEYZBRLj5GSHskj8NPaRYhk7Kx5WAfdcDucD3q98WdeW"; - auto op_list = TW::Tezos::OperationList(branch); + auto op_list = OperationList(branch); auto key = parsePrivateKey("edsk4bMQMM6HYtMazF3m7mYhQ6KQ1WCEcBuRwh6DTtdnoqAvC3nPCc"); - auto delegationOperationData = new TW::Tezos::Proto::DelegationOperationData(); + auto delegationOperationData = new Proto::DelegationOperationData(); delegationOperationData->set_delegate("tz1gSM6yiwr85jEASZ1q3UekgHEoxYt7wg2M"); - auto delegationOperation = TW::Tezos::Proto::Operation(); + auto delegationOperation = Proto::Operation(); delegationOperation.set_source("tz1RKLoYm4vtLzo7TAgGifMDAkiWhjfyXwP4"); delegationOperation.set_fee(1257); delegationOperation.set_counter(67); delegationOperation.set_gas_limit(10000); delegationOperation.set_storage_limit(0); - delegationOperation.set_kind(TW::Tezos::Proto::Operation::DELEGATION); + delegationOperation.set_kind(Proto::Operation::DELEGATION); delegationOperation.set_allocated_delegation_operation_data(delegationOperationData); op_list.addOperation(delegationOperation); @@ -96,20 +95,20 @@ TEST(TezosOperationList, ForgeOperationList_Delegation_ClearDelegate) { TEST(TezosOperationList, ForgeOperationList_Delegation_AddDelegate) { auto branch = "BLa4GrVQTxUgQWbHv6cF7RXWSGzHGPbgecpQ795R3cLzw4cGfpD"; - auto op_list = TW::Tezos::OperationList(branch); + auto op_list = OperationList(branch); auto key = parsePrivateKey("edsk4bMQMM6HYtMazF3m7mYhQ6KQ1WCEcBuRwh6DTtdnoqAvC3nPCc"); - auto delegationOperationData = new TW::Tezos::Proto::DelegationOperationData(); - delegationOperationData -> set_delegate("tz1dYUCcrorfCoaQCtZaxi1ynGrP3prTZcxS"); + auto delegationOperationData = new Proto::DelegationOperationData(); + delegationOperationData->set_delegate("tz1dYUCcrorfCoaQCtZaxi1ynGrP3prTZcxS"); - auto delegationOperation = TW::Tezos::Proto::Operation(); + auto delegationOperation = Proto::Operation(); delegationOperation.set_source("KT1D5jmrBD7bDa3jCpgzo32FMYmRDdK2ihka"); delegationOperation.set_fee(1257); delegationOperation.set_counter(68); delegationOperation.set_gas_limit(10000); delegationOperation.set_storage_limit(0); - delegationOperation.set_kind(TW::Tezos::Proto::Operation::DELEGATION); + delegationOperation.set_kind(Proto::Operation::DELEGATION); delegationOperation.set_allocated_delegation_operation_data(delegationOperationData); - + op_list.addOperation(delegationOperation); auto expected = "7105102c032807994dd9b5edf219261896a559876ca16cbf9d31dbe3612b89f26e00315b1206ec00b1b1e64cc3b8b93059f58fa2fc39e90944904e00ff00c4650fd609f88c67356e5fe01e37cd3ff654b18c"; auto forged = op_list.forge(key); @@ -118,33 +117,33 @@ TEST(TezosOperationList, ForgeOperationList_Delegation_AddDelegate) { TEST(TezosOperationList, ForgeOperationList_TransactionAndReveal) { auto branch = "BL8euoCWqNCny9AR3AKjnpi38haYMxjei1ZqNHuXMn19JSQnoWp"; - auto op_list = TW::Tezos::OperationList(branch); + auto op_list = OperationList(branch); auto key = parsePrivateKey("edsk4bMQMM6HYtMazF3m7mYhQ6KQ1WCEcBuRwh6DTtdnoqAvC3nPCc"); auto publicKey = parsePublicKey("edpkuNb9N2UHtGeSc2BZCBHN8ETx7E4DwkSfz5Hw3m3tF3dLZTU8qp"); - auto revealOperationData = new TW::Tezos::Proto::RevealOperationData(); - revealOperationData -> set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); + auto revealOperationData = new Proto::RevealOperationData(); + revealOperationData->set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); - auto revealOperation = TW::Tezos::Proto::Operation(); + auto revealOperation = Proto::Operation(); revealOperation.set_source("tz1RKLoYm4vtLzo7TAgGifMDAkiWhjfyXwP4"); revealOperation.set_fee(1272); revealOperation.set_counter(30738); revealOperation.set_gas_limit(10100); revealOperation.set_storage_limit(257); - revealOperation.set_kind(TW::Tezos::Proto::Operation::REVEAL); + revealOperation.set_kind(Proto::Operation::REVEAL); revealOperation.set_allocated_reveal_operation_data(revealOperationData); - - auto transactionOperationData = new TW::Tezos::Proto::TransactionOperationData(); - transactionOperationData -> set_amount(1); - transactionOperationData -> set_destination("tz1gSM6yiwr85jEASZ1q3UekgHEoxYt7wg2M"); - auto transactionOperation = TW::Tezos::Proto::Operation(); + auto transactionOperationData = new Proto::TransactionOperationData(); + transactionOperationData->set_amount(1); + transactionOperationData->set_destination("tz1gSM6yiwr85jEASZ1q3UekgHEoxYt7wg2M"); + + auto transactionOperation = Proto::Operation(); transactionOperation.set_source("tz1RKLoYm4vtLzo7TAgGifMDAkiWhjfyXwP4"); transactionOperation.set_fee(1272); transactionOperation.set_counter(30739); transactionOperation.set_gas_limit(10100); transactionOperation.set_storage_limit(257); - transactionOperation.set_kind(TW::Tezos::Proto::Operation::TRANSACTION); + transactionOperation.set_kind(Proto::Operation::TRANSACTION); transactionOperation.set_allocated_transaction_operation_data(transactionOperationData); op_list.addOperation(revealOperation); @@ -158,18 +157,18 @@ TEST(TezosOperationList, ForgeOperationList_TransactionAndReveal) { TEST(TezosOperationList, ForgeOperationList_RevealWithoutPublicKey) { auto branch = "BL8euoCWqNCny9AR3AKjnpi38haYMxjei1ZqNHuXMn19JSQnoWp"; - auto op_list = TW::Tezos::OperationList(branch); + auto op_list = OperationList(branch); auto key = parsePrivateKey("edsk4bMQMM6HYtMazF3m7mYhQ6KQ1WCEcBuRwh6DTtdnoqAvC3nPCc"); - auto revealOperationData = new TW::Tezos::Proto::RevealOperationData(); + auto revealOperationData = new Proto::RevealOperationData(); - auto revealOperation = TW::Tezos::Proto::Operation(); + auto revealOperation = Proto::Operation(); revealOperation.set_source("tz1RKLoYm4vtLzo7TAgGifMDAkiWhjfyXwP4"); revealOperation.set_fee(1272); revealOperation.set_counter(30738); revealOperation.set_gas_limit(10100); revealOperation.set_storage_limit(257); - revealOperation.set_kind(TW::Tezos::Proto::Operation::REVEAL); + revealOperation.set_kind(Proto::Operation::REVEAL); revealOperation.set_allocated_reveal_operation_data(revealOperationData); op_list.addOperation(revealOperation); @@ -179,3 +178,5 @@ TEST(TezosOperationList, ForgeOperationList_RevealWithoutPublicKey) { ASSERT_EQ(hex(forged.begin(), forged.end()), expected); } + +} // namespace TW::Tezos::tests diff --git a/tests/Tezos/PublicKeyTests.cpp b/tests/chains/Tezos/PublicKeyTests.cpp similarity index 94% rename from tests/Tezos/PublicKeyTests.cpp rename to tests/chains/Tezos/PublicKeyTests.cpp index baac0151631..66c7087dc7e 100644 --- a/tests/Tezos/PublicKeyTests.cpp +++ b/tests/chains/Tezos/PublicKeyTests.cpp @@ -12,8 +12,7 @@ #include -using namespace TW; -using namespace TW::Tezos; +namespace TW::Tezos::tests { TEST(TezosPublicKey, forge) { auto input = parsePublicKey("edpkuAfEJCEatRgFpRGg3gn3FdWniLXBoubARreRwuVZPWufkgDBvR"); @@ -29,3 +28,5 @@ TEST(TezosPublicKey, parse) { auto expected = PublicKey(bytes, TWPublicKeyTypeED25519); ASSERT_EQ(output, expected); } + +} // namespace TW::Tezos::tests diff --git a/tests/Tezos/SignerTests.cpp b/tests/chains/Tezos/SignerTests.cpp similarity index 97% rename from tests/Tezos/SignerTests.cpp rename to tests/chains/Tezos/SignerTests.cpp index 987d3a573c8..92681344646 100644 --- a/tests/Tezos/SignerTests.cpp +++ b/tests/chains/Tezos/SignerTests.cpp @@ -4,17 +4,18 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +#include "Base58.h" +#include "HexCoding.h" +#include "PrivateKey.h" #include "Tezos/BinaryCoding.h" #include "Tezos/OperationList.h" #include "Tezos/Signer.h" -#include "PrivateKey.h" -#include "Base58.h" -#include "HexCoding.h" #include using namespace TW; -using namespace TW::Tezos; + +namespace TW::Tezos::tests { TEST(TezosSigner, SignString) { Data bytesToSign = parse_hex("ffaa"); @@ -23,7 +24,7 @@ TEST(TezosSigner, SignString) { append(expected, bytesToSign); append(expected, expectedSignature); - auto key = PrivateKey(parse_hex("0x2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6f")); + auto key = PrivateKey(parse_hex("0x2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6f")); auto signedBytes = Signer().signData(key, bytesToSign); ASSERT_EQ(signedBytes, expected); @@ -89,3 +90,5 @@ TEST(TezosSigner, SignOperationList) { ASSERT_EQ(hex(signedBytes.begin(), signedBytes.end()), expectedSignedBytes); } + +} // namespace TW::Tezos::tests diff --git a/tests/chains/Tezos/TWAnySignerTests.cpp b/tests/chains/Tezos/TWAnySignerTests.cpp new file mode 100644 index 00000000000..89fac461677 --- /dev/null +++ b/tests/chains/Tezos/TWAnySignerTests.cpp @@ -0,0 +1,127 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "HexCoding.h" +#include "Tezos/BinaryCoding.h" +#include "proto/Tezos.pb.h" +#include "TestUtilities.h" +#include + +#include + +using namespace TW; +namespace TW::Tezos::tests { + +TEST(TWAnySignerTezos, SignFA12) { + // https://ghostnet.tzkt.io/ooTBu7DLbeC7DmVfXEsp896A6WTwimedbsM9QRqUVtqA8Vxt6D3/2993172 + auto key = parse_hex("363265a0b3f06661001cab8b4f3ca8fd97ae70608184979cf7300836f57ec2d6"); + + Proto::SigningInput input; + input.set_private_key(key.data(), key.size()); + auto& operations = *input.mutable_operation_list(); + operations.set_branch("BL8euoCWqNCny9AR3AKjnpi38haYMxjei1ZqNHuXMn19JSQnoWp"); + + auto& transaction = *operations.add_operations(); + auto& txData = *transaction.mutable_transaction_operation_data(); + txData.set_amount(0); + txData.set_destination("KT1EwXFWoG9bYebmF4pYw72aGjwEnBWefgW5"); + txData.mutable_parameters()->mutable_fa12_parameters()->set_entrypoint("transfer"); + txData.mutable_parameters()->mutable_fa12_parameters()->set_from("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + txData.mutable_parameters()->mutable_fa12_parameters()->set_to("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + txData.mutable_parameters()->mutable_fa12_parameters()->set_value("123"); + transaction.set_source("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + transaction.set_fee(100000); + transaction.set_counter(2993172); + transaction.set_gas_limit(100000); + transaction.set_storage_limit(0); + transaction.set_kind(Proto::Operation::TRANSACTION); + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeTezos); + ASSERT_EQ(hex(output.encoded()), "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016c00fe2ce0cccc0214af521ad60c140c5589b4039247a08d0694d8b601a08d0600000145bd8a65cc48159d8ea60a55df735b7c5ad45f0e00ffff087472616e736665720000005907070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555007070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555000bb012914d768155fba2df319a81136e8e3e573b9cadb1676834490c90212615d271da029b6b0531e290e9063bcdb40bea43627af048b18e036f02be2b6b22fc8b307"); +} + +TEST(TWAnySignerTezos, SignFA2) { + // https://ghostnet.tzkt.io/onxLBoPaf23M3A8kHTwncSFG2GVXPfnGXUhkC8BhKj8QDdCEbng + auto key = parse_hex("363265a0b3f06661001cab8b4f3ca8fd97ae70608184979cf7300836f57ec2d6"); + + Proto::SigningInput input; + input.set_private_key(key.data(), key.size()); + auto& operations = *input.mutable_operation_list(); + operations.set_branch("BKvEAX9HXfJZWYfTQbR1C7B3ADoKY6a1aKVRF7qQqvc9hS8Rr3m"); + + auto& transaction = *operations.add_operations(); + + auto* transactionOperationData = transaction.mutable_transaction_operation_data(); + transactionOperationData->set_amount(0); + transactionOperationData->set_destination("KT1DYk1XDzHredJq1EyNkDindiWDqZyekXGj"); + + auto& fa2 = *transactionOperationData->mutable_parameters()->mutable_fa2_parameters(); + fa2.set_entrypoint("transfer"); + auto& txObject = *fa2.add_txs_object(); + txObject.set_from("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + auto& tx = *txObject.add_txs(); + tx.set_amount("10"); + tx.set_token_id("0"); + tx.set_to("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + + transaction.set_source("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + transaction.set_fee(100000); + transaction.set_counter(2993173); + transaction.set_gas_limit(100000); + transaction.set_storage_limit(0); + transaction.set_kind(Proto::Operation::TRANSACTION); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeTezos); + ASSERT_EQ(hex(output.encoded()), "1b1f9345dc9f77bd24b09034d1d2f9a28f02ac837f49db54b8d68341f53dc4b76c00fe2ce0cccc0214af521ad60c140c5589b4039247a08d0695d8b601a08d0600000136767f88850bae28bfb9f46b73c5e87ede4de12700ffff087472616e7366657200000066020000006107070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b5550020000003107070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555007070000000a552d24710d6c59383286700c6c2917b25a6c1fa8b587e593c289dd47704278796792f1e522c1623845ec991e292b0935445e6994850bd03f035a006c5ed93806"); +} + +TEST(TWAnySignerTezos, Sign) { + auto key = parse_hex("2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6f"); + auto revealKey = parse_hex("311f002e899cdd9a52d96cb8be18ea2bbab867c505da2b44ce10906f511cff95"); + + Proto::SigningInput input; + input.set_private_key(key.data(), key.size()); + auto& operations = *input.mutable_operation_list(); + operations.set_branch("BL8euoCWqNCny9AR3AKjnpi38haYMxjei1ZqNHuXMn19JSQnoWp"); + + auto& reveal = *operations.add_operations(); + auto& revealData = *reveal.mutable_reveal_operation_data(); + revealData.set_public_key(revealKey.data(), revealKey.size()); + reveal.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); + reveal.set_fee(1272); + reveal.set_counter(30738); + reveal.set_gas_limit(10100); + reveal.set_storage_limit(257); + reveal.set_kind(Proto::Operation::REVEAL); + + auto& transaction = *operations.add_operations(); + auto& txData = *transaction.mutable_transaction_operation_data(); + txData.set_amount(1); + txData.set_destination("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); + transaction.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); + transaction.set_fee(1272); + transaction.set_counter(30739); + transaction.set_gas_limit(10100); + transaction.set_storage_limit(257); + transaction.set_kind(Proto::Operation::TRANSACTION); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeTezos); + + EXPECT_EQ(hex(output.encoded()), "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016b0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80992f001f44e810200311f002e899cdd9a52d96cb8be18ea2bbab867c505da2b44ce10906f511cff956c0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80993f001f44e810201000081faa75f741ef614b0e35fcc8c90dfa3b0b95721000217034271b815e5f0c0a881342838ce49d7b48cdf507c72b1568c69a10db70c98774cdad1a74df760763e25f760ff13afcbbf3a1f2c833a0beeb9576a579c05"); +} + +TEST(TWAnySignerTezos, SignJSON) { + auto json = STRING(R"({"operationList": {"branch": "BL8euoCWqNCny9AR3AKjnpi38haYMxjei1ZqNHuXMn19JSQnoWp","operations": [{"source": "tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW","fee": 1272,"counter": 30738,"gasLimit": 10100,"storageLimit": 257,"kind": 107,"revealOperationData": {"publicKey": "QpqYbIBypAofOj4qtaWBm7Gy+2mZPFAEg3gVudxVkj4="}},{"source": "tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW","fee": 1272,"counter": 30739,"gasLimit": 10100,"storageLimit": 257,"kind": 108,"transactionOperationData": {"destination": "tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW","amount": 1}}]}})"); + auto key = DATA("2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6f"); + auto result = WRAPS(TWAnySignerSignJSON(json.get(), key.get(), TWCoinTypeTezos)); + + ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeTezos)); + assertStringsEqual(result, "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016b0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80992f001f44e810200429a986c8072a40a1f3a3e2ab5a5819bb1b2fb69993c5004837815b9dc55923e6c0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80993f001f44e810201000081faa75f741ef614b0e35fcc8c90dfa3b0b957210001b86398d5b9be737dca8e4106ea18d70e69b75e92f892fb283546a99152b8d7794b919c0fbf1c31de386069a60014491c0e7505adef5781cead1cfe6608030b"); +} + +} // namespace TW::Tezos::tests diff --git a/tests/Tezos/TWCoinTypeTests.cpp b/tests/chains/Tezos/TWCoinTypeTests.cpp similarity index 97% rename from tests/Tezos/TWCoinTypeTests.cpp rename to tests/chains/Tezos/TWCoinTypeTests.cpp index a8d44065d59..2c33d888f68 100644 --- a/tests/Tezos/TWCoinTypeTests.cpp +++ b/tests/chains/Tezos/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Theta/SignerTests.cpp b/tests/chains/Theta/SignerTests.cpp similarity index 100% rename from tests/Theta/SignerTests.cpp rename to tests/chains/Theta/SignerTests.cpp diff --git a/tests/Theta/TWAnySignerTests.cpp b/tests/chains/Theta/TWAnySignerTests.cpp similarity index 94% rename from tests/Theta/TWAnySignerTests.cpp rename to tests/chains/Theta/TWAnySignerTests.cpp index 92d748c6ebc..1c18296d549 100644 --- a/tests/Theta/TWAnySignerTests.cpp +++ b/tests/chains/Theta/TWAnySignerTests.cpp @@ -5,19 +5,20 @@ // file LICENSE at the root of the source code distribution tree. #include "HexCoding.h" -#include "uint256.h" #include "proto/Theta.pb.h" +#include "uint256.h" #include -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include using namespace TW; -using namespace TW::Theta; + +namespace TW::Theta::tests { TEST(TWAnySignerTheta, Sign) { auto privateKey = parse_hex("93a90ea508331dfdf27fb79757d4250b4e84954927ba0073cd67454ac432c737"); - + Proto::SigningInput input; input.set_chain_id("privatenet"); input.set_to_address("0x9F1233798E905E173560071255140b4A8aBd3Ec6"); @@ -35,3 +36,5 @@ TEST(TWAnySignerTheta, Sign) { ASSERT_EQ(hex(output.encoded()), "02f887c78085e8d4a51000f863f861942e833968e5bb786ae419c4d13189fb081cc43babc70a85e8d4a5101401b8415190868498d587d074d57298f41853d0109d997f15ddf617f471eb8cbb7fff267cb8fe9134ccdef053ec7cabd18070325c9c436efe1abbacd14eb7561d3fc10501d9d8949f1233798e905e173560071255140b4a8abd3ec6c20a14"); } + +} // namespace TW::Thetha::tests diff --git a/tests/Theta/TWCoinTypeTests.cpp b/tests/chains/Theta/TWCoinTypeTests.cpp similarity index 97% rename from tests/Theta/TWCoinTypeTests.cpp rename to tests/chains/Theta/TWCoinTypeTests.cpp index 55ed4e35b38..94107b0898c 100644 --- a/tests/Theta/TWCoinTypeTests.cpp +++ b/tests/chains/Theta/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Theta/TransactionTests.cpp b/tests/chains/Theta/TransactionTests.cpp similarity index 94% rename from tests/Theta/TransactionTests.cpp rename to tests/chains/Theta/TransactionTests.cpp index 19699afcc78..3ff9a4b3926 100644 --- a/tests/Theta/TransactionTests.cpp +++ b/tests/chains/Theta/TransactionTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -10,8 +10,7 @@ #include -using namespace TW; -using namespace TW::Theta; +namespace TW::Theta::tests { TEST(ThetaTransaction, Encode) { const auto from = Ethereum::Address("0x2E833968E5bB786Ae419c4d13189fB081Cc43bab"); @@ -35,3 +34,5 @@ TEST(ThetaTransaction, EncodeWithSignature) { "ccdef053ec7cabd18070325c9c436efe1abbacd14eb7561d3fc10501d9d8949f1233798e905e17356007" "1255140b4a8abd3ec6c20a14"); } + +} // namespace TW::Theta::tests diff --git a/tests/ThunderToken/TWCoinTypeTests.cpp b/tests/chains/ThunderToken/TWCoinTypeTests.cpp similarity index 97% rename from tests/ThunderToken/TWCoinTypeTests.cpp rename to tests/chains/ThunderToken/TWCoinTypeTests.cpp index 3b31872c8c3..c001ea07218 100644 --- a/tests/ThunderToken/TWCoinTypeTests.cpp +++ b/tests/chains/ThunderToken/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/TomoChain/TWCoinTypeTests.cpp b/tests/chains/TomoChain/TWCoinTypeTests.cpp similarity index 97% rename from tests/TomoChain/TWCoinTypeTests.cpp rename to tests/chains/TomoChain/TWCoinTypeTests.cpp index 0cb99362b04..4b6ec3810b4 100644 --- a/tests/TomoChain/TWCoinTypeTests.cpp +++ b/tests/chains/TomoChain/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Tron/AddressTests.cpp b/tests/chains/Tron/AddressTests.cpp similarity index 100% rename from tests/Tron/AddressTests.cpp rename to tests/chains/Tron/AddressTests.cpp diff --git a/tests/Tron/SerializationTests.cpp b/tests/chains/Tron/SerializationTests.cpp similarity index 100% rename from tests/Tron/SerializationTests.cpp rename to tests/chains/Tron/SerializationTests.cpp diff --git a/tests/Tron/SignerTests.cpp b/tests/chains/Tron/SignerTests.cpp similarity index 100% rename from tests/Tron/SignerTests.cpp rename to tests/chains/Tron/SignerTests.cpp diff --git a/tests/Tron/TWAnySignerTests.cpp b/tests/chains/Tron/TWAnySignerTests.cpp similarity index 97% rename from tests/Tron/TWAnySignerTests.cpp rename to tests/chains/Tron/TWAnySignerTests.cpp index 0dc27c18583..ba7f2964e7b 100644 --- a/tests/Tron/TWAnySignerTests.cpp +++ b/tests/chains/Tron/TWAnySignerTests.cpp @@ -8,7 +8,7 @@ #include "proto/Tron.pb.h" #include -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include namespace TW::Tron { diff --git a/tests/Tron/TWCoinTypeTests.cpp b/tests/chains/Tron/TWCoinTypeTests.cpp similarity index 97% rename from tests/Tron/TWCoinTypeTests.cpp rename to tests/chains/Tron/TWCoinTypeTests.cpp index e496477156e..b549e7bae86 100644 --- a/tests/Tron/TWCoinTypeTests.cpp +++ b/tests/chains/Tron/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/VeChain/SignerTests.cpp b/tests/chains/VeChain/SignerTests.cpp similarity index 100% rename from tests/VeChain/SignerTests.cpp rename to tests/chains/VeChain/SignerTests.cpp diff --git a/tests/VeChain/TWAnySignerTests.cpp b/tests/chains/VeChain/TWAnySignerTests.cpp similarity index 90% rename from tests/VeChain/TWAnySignerTests.cpp rename to tests/chains/VeChain/TWAnySignerTests.cpp index 289c725d42b..992e65dbc51 100644 --- a/tests/VeChain/TWAnySignerTests.cpp +++ b/tests/chains/VeChain/TWAnySignerTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -6,12 +6,11 @@ #include "HexCoding.h" #include "proto/VeChain.pb.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include -using namespace TW; -using namespace TW::VeChain; +namespace TW::VeChain::tests { TEST(TWAnySignerVeChain, Sign) { auto input = Proto::SigningInput(); @@ -36,3 +35,5 @@ TEST(TWAnySignerVeChain, Sign) { ASSERT_EQ(hex(output.encoded()), "f86a010101dcdb943535353535353535353535353535353535353535843130303080808252088001c0b841bf8edf9600e645b5abd677cb52f585e7f655d1361075d511b37f707a9f31da6702d28739933b264527a1d05b046f5b74044b88c30c3f5a09d616bd7a4af4901601"); } + +} // namespace TW::VeChain::tests diff --git a/tests/VeChain/TWCoinTypeTests.cpp b/tests/chains/VeChain/TWCoinTypeTests.cpp similarity index 97% rename from tests/VeChain/TWCoinTypeTests.cpp rename to tests/chains/VeChain/TWCoinTypeTests.cpp index cf41c6cba08..60d7e2b709c 100644 --- a/tests/VeChain/TWCoinTypeTests.cpp +++ b/tests/chains/VeChain/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Viacoin/TWCoinTypeTests.cpp b/tests/chains/Viacoin/TWCoinTypeTests.cpp similarity index 97% rename from tests/Viacoin/TWCoinTypeTests.cpp rename to tests/chains/Viacoin/TWCoinTypeTests.cpp index 15ead27fdc1..3fcc86d7e37 100644 --- a/tests/Viacoin/TWCoinTypeTests.cpp +++ b/tests/chains/Viacoin/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Viacoin/TWViacoinAddressTests.cpp b/tests/chains/Viacoin/TWViacoinAddressTests.cpp similarity index 99% rename from tests/Viacoin/TWViacoinAddressTests.cpp rename to tests/chains/Viacoin/TWViacoinAddressTests.cpp index 55ef8fe74a5..d4ea5b115ea 100644 --- a/tests/Viacoin/TWViacoinAddressTests.cpp +++ b/tests/chains/Viacoin/TWViacoinAddressTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Wanchain/TWCoinTypeTests.cpp b/tests/chains/Wanchain/TWCoinTypeTests.cpp similarity index 97% rename from tests/Wanchain/TWCoinTypeTests.cpp rename to tests/chains/Wanchain/TWCoinTypeTests.cpp index f3ba2ce8ee7..cc49f5ae5ae 100644 --- a/tests/Wanchain/TWCoinTypeTests.cpp +++ b/tests/chains/Wanchain/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Waves/AddressTests.cpp b/tests/chains/Waves/AddressTests.cpp similarity index 98% rename from tests/Waves/AddressTests.cpp rename to tests/chains/Waves/AddressTests.cpp index b5a8afe9232..1e99f576d1e 100644 --- a/tests/Waves/AddressTests.cpp +++ b/tests/chains/Waves/AddressTests.cpp @@ -14,7 +14,8 @@ using namespace std; using namespace TW; -using namespace TW::Waves; + +namespace TW::Waves::tests { TEST(WavesAddress, SecureHash) { const auto secureHash = @@ -81,4 +82,6 @@ TEST(WavesAddress, Derive) { ASSERT_EQ(address1, "3PQupTC1yRiHneotFt79LF2pkN6GrGMwEy3"); ASSERT_EQ(address2, "3PEXw52bkS9XuLhttWoKyykZjXqEY8zeLxf"); -} \ No newline at end of file +} + +} // namespace TW::Waves::tests diff --git a/tests/Waves/LeaseTests.cpp b/tests/chains/Waves/LeaseTests.cpp similarity index 60% rename from tests/Waves/LeaseTests.cpp rename to tests/chains/Waves/LeaseTests.cpp index 41de50d1980..53f6e192cbd 100644 --- a/tests/Waves/LeaseTests.cpp +++ b/tests/chains/Waves/LeaseTests.cpp @@ -8,8 +8,8 @@ #include "PrivateKey.h" #include "PublicKey.h" #include "Waves/Address.h" -#include "proto/Waves.pb.h" #include "Waves/Transaction.h" +#include "proto/Waves.pb.h" #include #include @@ -18,23 +18,24 @@ using json = nlohmann::json; using namespace std; using namespace TW; -using namespace TW::Waves; + +namespace TW::Waves::tests { TEST(WavesLease, serialize) { const auto privateKey = - PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); auto input = Proto::SigningInput(); input.set_timestamp(int64_t(1526646497465)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - + auto& message = *input.mutable_lease_message(); message.set_amount(int64_t(100000000)); message.set_fee(int64_t(100000)); message.set_to("3P9DEDP5VbyXQyKtXDUt2crRPn5B7gs6ujc"); auto tx1 = Transaction( - input, - /* pub_key */ - parse_hex("425f57a8cb5439e4e912e66376f7041565d029ae4437dae1a3ebe15649d43461")); + input, + /* pub_key */ + parse_hex("425f57a8cb5439e4e912e66376f7041565d029ae4437dae1a3ebe15649d43461")); auto serialized1 = tx1.serializeToSign(); ASSERT_EQ(hex(serialized1), "080200425f57a8cb5439e4e912e66376f7041565d029ae4437dae1a3ebe15649d4346101574" "fdfcd1bfb19114bd2ac369e32013c70c6d03a4627879cbf0000000005f5e100000000000001" @@ -43,18 +44,18 @@ TEST(WavesLease, serialize) { TEST(WavesLease, CancelSerialize) { const auto privateKey = - PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); auto input = Proto::SigningInput(); input.set_timestamp(int64_t(1568831000826)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - + auto& message = *input.mutable_cancel_lease_message(); message.set_fee(int64_t(100000)); message.set_lease_id("44re3UEDw1QwPFP8dKzfuGHVMNBejUW9NbhxG6b4KJ1T"); auto tx1 = Transaction( - input, - /* pub_key */ - parse_hex("425f57a8cb5439e4e912e66376f7041565d029ae4437dae1a3ebe15649d43461")); + input, + /* pub_key */ + parse_hex("425f57a8cb5439e4e912e66376f7041565d029ae4437dae1a3ebe15649d43461")); auto serialized1 = tx1.serializeToSign(); ASSERT_EQ(hex(serialized1), "090257425f57a8cb5439e4e912e66376f7041565d029ae4437dae1a3ebe15649d" "4346100000000000186a00000016d459d50fa2d8fee08efc97f79bcd97a4d977c" @@ -62,54 +63,54 @@ TEST(WavesLease, CancelSerialize) { } TEST(WavesLease, jsonSerialize) { - const auto privateKey = PrivateKey(parse_hex( - "9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); - const auto publicKeyCurve25519 = - privateKey.getPublicKey(TWPublicKeyTypeCURVE25519); - auto input = Proto::SigningInput(); - input.set_timestamp(int64_t(1568973547102)); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - auto& message = *input.mutable_lease_message(); - message.set_amount(int64_t(100000)); - message.set_fee(int64_t(100000)); - message.set_to("3P9DEDP5VbyXQyKtXDUt2crRPn5B7gs6ujc"); - auto tx1 = Transaction(input, - /* pub_key */ - parse_hex("559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d")); - - auto signature = Signer::sign(privateKey, tx1); - auto json = tx1.buildJson(signature); - - ASSERT_EQ(json["type"], TransactionType::lease); - ASSERT_EQ(json["version"], TransactionVersion::V2); - ASSERT_EQ(json["fee"], int64_t(100000)); - ASSERT_EQ(json["senderPublicKey"], - "6mA8eQjie53kd4jbZrwL3ZhMBqCX6nzit1k55tR2X7zU"); - ASSERT_EQ(json["timestamp"], int64_t(1568973547102)); - ASSERT_EQ(json["proofs"].dump(), - "[\"4opce9e99827upK3m3D3NicnvBqbMLtAJ4Jc8ksTLiScqBgjdqzr9JyXG" - "C1NAGZUbkqJvix9bNrBokrxtGruwmu3\"]"); - ASSERT_EQ(json["recipient"], "3P9DEDP5VbyXQyKtXDUt2crRPn5B7gs6ujc"); - ASSERT_EQ(json["amount"], int64_t(100000)); - ASSERT_EQ(json.dump(), - "{\"amount\":100000,\"fee\":100000,\"proofs\":[" - "\"4opce9e99827upK3m3D3NicnvBqbMLtAJ4Jc8ksTLiScqBgjdqzr9JyXGC1NAGZUbkqJ" - "vix9bNrBokrxtGruwmu3\"],\"recipient\":" - "\"3P9DEDP5VbyXQyKtXDUt2crRPn5B7gs6ujc\",\"senderPublicKey\":" - "\"6mA8eQjie53kd4jbZrwL3ZhMBqCX6nzit1k55tR2X7zU\",\"timestamp\":" - "1568973547102,\"type\":8,\"version\":2}"); + const auto privateKey = PrivateKey(parse_hex( + "9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + const auto publicKeyCurve25519 = + privateKey.getPublicKey(TWPublicKeyTypeCURVE25519); + auto input = Proto::SigningInput(); + input.set_timestamp(int64_t(1568973547102)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto& message = *input.mutable_lease_message(); + message.set_amount(int64_t(100000)); + message.set_fee(int64_t(100000)); + message.set_to("3P9DEDP5VbyXQyKtXDUt2crRPn5B7gs6ujc"); + auto tx1 = Transaction(input, + /* pub_key */ + parse_hex("559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d")); + + auto signature = Signer::sign(privateKey, tx1); + auto json = tx1.buildJson(signature); + + ASSERT_EQ(json["type"], TransactionType::lease); + ASSERT_EQ(json["version"], TransactionVersion::V2); + ASSERT_EQ(json["fee"], int64_t(100000)); + ASSERT_EQ(json["senderPublicKey"], + "6mA8eQjie53kd4jbZrwL3ZhMBqCX6nzit1k55tR2X7zU"); + ASSERT_EQ(json["timestamp"], int64_t(1568973547102)); + ASSERT_EQ(json["proofs"].dump(), + "[\"4opce9e99827upK3m3D3NicnvBqbMLtAJ4Jc8ksTLiScqBgjdqzr9JyXG" + "C1NAGZUbkqJvix9bNrBokrxtGruwmu3\"]"); + ASSERT_EQ(json["recipient"], "3P9DEDP5VbyXQyKtXDUt2crRPn5B7gs6ujc"); + ASSERT_EQ(json["amount"], int64_t(100000)); + ASSERT_EQ(json.dump(), + "{\"amount\":100000,\"fee\":100000,\"proofs\":[" + "\"4opce9e99827upK3m3D3NicnvBqbMLtAJ4Jc8ksTLiScqBgjdqzr9JyXGC1NAGZUbkqJ" + "vix9bNrBokrxtGruwmu3\"],\"recipient\":" + "\"3P9DEDP5VbyXQyKtXDUt2crRPn5B7gs6ujc\",\"senderPublicKey\":" + "\"6mA8eQjie53kd4jbZrwL3ZhMBqCX6nzit1k55tR2X7zU\",\"timestamp\":" + "1568973547102,\"type\":8,\"version\":2}"); } TEST(WavesLease, jsonCancelSerialize) { const auto privateKey = PrivateKey(parse_hex( - "9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + "9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); const auto publicKeyCurve25519 = - privateKey.getPublicKey(TWPublicKeyTypeCURVE25519); + privateKey.getPublicKey(TWPublicKeyTypeCURVE25519); auto input = Proto::SigningInput(); input.set_timestamp(int64_t(1568973547102)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - + auto& message = *input.mutable_cancel_lease_message(); message.set_lease_id("DKhmXrCsBwf6WVhGh8bYVBnjtAXGpk2K4Yd3CW4u1huG"); message.set_fee(int64_t(100000)); @@ -118,7 +119,7 @@ TEST(WavesLease, jsonCancelSerialize) { parse_hex("559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d")); auto signature = Signer::sign(privateKey, tx1); auto json = tx1.buildJson(signature); - + ASSERT_EQ(json["type"], TransactionType::cancelLease); ASSERT_EQ(json["version"], TransactionVersion::V2); ASSERT_EQ(json["fee"], int64_t(100000)); @@ -138,4 +139,4 @@ TEST(WavesLease, jsonCancelSerialize) { "1568973547102,\"type\":9,\"version\":2}"); } - +} // namespace TW::Waves::tests diff --git a/tests/Waves/SignerTests.cpp b/tests/chains/Waves/SignerTests.cpp similarity index 97% rename from tests/Waves/SignerTests.cpp rename to tests/chains/Waves/SignerTests.cpp index bf45d2db69e..95938c16d39 100644 --- a/tests/Waves/SignerTests.cpp +++ b/tests/chains/Waves/SignerTests.cpp @@ -13,7 +13,8 @@ #include using namespace TW; -using namespace TW::Waves; + +namespace TW::Waves::tests { TEST(WavesSigner, SignTransaction) { const auto privateKey = @@ -59,3 +60,5 @@ TEST(WavesSigner, curve25519_pk_to_ed25519) { curve25519_pk_to_ed25519(r.data(), publicKeyCurve25519.data()); EXPECT_EQ(hex(r), "ff84c4bfc095df25b01e48807715856d95af93d88c5b57f30cb0ce567ca4ce56"); } + +} // namespace TW::Waves::tests diff --git a/tests/Waves/TWAnySignerTests.cpp b/tests/chains/Waves/TWAnySignerTests.cpp similarity index 90% rename from tests/Waves/TWAnySignerTests.cpp rename to tests/chains/Waves/TWAnySignerTests.cpp index 9bb037ba987..c814761a4f9 100644 --- a/tests/Waves/TWAnySignerTests.cpp +++ b/tests/chains/Waves/TWAnySignerTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -7,17 +7,16 @@ #include "Base58.h" #include "HexCoding.h" #include "proto/Waves.pb.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include -using namespace TW; -using namespace TW::Waves; +namespace TW::Waves::tests { TEST(TWAnySignerWaves, Sign) { auto input = Proto::SigningInput(); const auto privateKey = Base58::bitcoin.decode("83mqJpmgB5Mko1567sVAdqZxVKsT6jccXt3eFSi4G1zE"); - + input.set_timestamp(int64_t(1559146613)); input.set_private_key(privateKey.data(), privateKey.size()); auto& message = *input.mutable_transfer_message(); @@ -33,3 +32,5 @@ TEST(TWAnySignerWaves, Sign) { ASSERT_EQ(hex(output.signature()), "5d6a77b1fd9b53d9735cd2543ba94215664f2b07d6c7befb081221fcd49f5b6ad6b9ac108582e8d3e74943bdf35fd80d985edf4b4de1fb1c5c427e84d0879f8f"); } + +} // namespace TW::Waves::tests diff --git a/tests/Waves/TWCoinTypeTests.cpp b/tests/chains/Waves/TWCoinTypeTests.cpp similarity index 97% rename from tests/Waves/TWCoinTypeTests.cpp rename to tests/chains/Waves/TWCoinTypeTests.cpp index b9043540b04..6460c3cbff0 100644 --- a/tests/Waves/TWCoinTypeTests.cpp +++ b/tests/chains/Waves/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Waves/TransactionTests.cpp b/tests/chains/Waves/TransactionTests.cpp similarity index 87% rename from tests/Waves/TransactionTests.cpp rename to tests/chains/Waves/TransactionTests.cpp index bfb7998d99f..1e2f1354d8b 100644 --- a/tests/Waves/TransactionTests.cpp +++ b/tests/chains/Waves/TransactionTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -14,19 +14,18 @@ #include #include -using json = nlohmann::json; +namespace TW::Waves::tests { +using json = nlohmann::json; using namespace std; -using namespace TW; -using namespace TW::Waves; TEST(WavesTransaction, serialize) { const auto privateKey = - PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); auto input = Proto::SigningInput(); input.set_timestamp(int64_t(1526641218066)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - + auto& message = *input.mutable_transfer_message(); message.set_amount(int64_t(100000000)); message.set_asset(""); @@ -35,19 +34,18 @@ TEST(WavesTransaction, serialize) { message.set_to("3PLgzJXQiN77G7KgnR1WVa8jBYhF2dmWndx"); message.set_attachment("falafel"); auto tx1 = Transaction( - input, - /* pub_key */ - parse_hex("d528aabec35ca100d87c7b7a128632faf19cd44531819457445113a32a21ef22")); + input, + /* pub_key */ + parse_hex("d528aabec35ca100d87c7b7a128632faf19cd44531819457445113a32a21ef22")); auto serialized1 = tx1.serializeToSign(); ASSERT_EQ(hex(serialized1), "0402d528aabec35ca100d87c7b7a128632faf19cd44531819457445113a32a21ef" "2200000000016372e852120000000005f5e1000000000005f5e1000157cdc9381c" "071beb5abd27738d5cd36cf75f3cbfdd69e8e6bb000766616c6166656c"); - - + auto input2 = Proto::SigningInput(); input2.set_timestamp(int64_t(1)); input2.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - + auto& message2 = *input2.mutable_transfer_message(); message2.set_amount(int64_t(1)); message2.set_asset("DacnEpaUVFRCYk8Fcd1F3cqUZuT4XG7qW9mRyoZD81zq"); @@ -71,11 +69,11 @@ TEST(WavesTransaction, serialize) { TEST(WavesTransaction, failedSerialize) { // 141 bytes attachment const auto privateKey = - PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); auto input = Proto::SigningInput(); input.set_timestamp(int64_t(1526641218066)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - + auto& message = *input.mutable_transfer_message(); message.set_amount(int64_t(100000000)); message.set_asset(""); @@ -93,18 +91,18 @@ TEST(WavesTransaction, failedSerialize) { } TEST(WavesTransaction, jsonSerialize) { - + const auto privateKey = PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); const auto publicKeyCurve25519 = privateKey.getPublicKey(TWPublicKeyTypeCURVE25519); ASSERT_EQ(hex(Data(publicKeyCurve25519.bytes.begin(), publicKeyCurve25519.bytes.end())), "559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d"); const auto address = Address(publicKeyCurve25519); - + auto input = Proto::SigningInput(); input.set_timestamp(int64_t(1526641218066)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - + auto& message = *input.mutable_transfer_message(); message.set_amount(int64_t(10000000)); message.set_asset("DacnEpaUVFRCYk8Fcd1F3cqUZuT4XG7qW9mRyoZD81zq"); @@ -113,10 +111,9 @@ TEST(WavesTransaction, jsonSerialize) { message.set_to(address.string()); message.set_attachment("falafel"); auto tx1 = Transaction( - input, - /* pub_key */ - parse_hex("559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d")); - + input, + /* pub_key */ + parse_hex("559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d")); auto signature = Signer::sign(privateKey, tx1); @@ -128,7 +125,7 @@ TEST(WavesTransaction, jsonSerialize) { ASSERT_EQ(json["senderPublicKey"], "6mA8eQjie53kd4jbZrwL3ZhMBqCX6nzit1k55tR2X7zU"); ASSERT_EQ(json["timestamp"], int64_t(1526641218066)); ASSERT_EQ(json["proofs"].dump(), "[\"5ynN2NUiFHkQzw9bK8R7dZcNfTWMAtcWRJsrMvFFM6dUT3fSnPCCX7CTajNU8bJCB" - "H69vU1mnwfx4zpDtF1SkzKg\"]"); + "H69vU1mnwfx4zpDtF1SkzKg\"]"); ASSERT_EQ(json["recipient"], "3P2uzAzX9XTu1t32GkWw68YFFLwtapWvDds"); ASSERT_EQ(json["assetId"], "DacnEpaUVFRCYk8Fcd1F3cqUZuT4XG7qW9mRyoZD81zq"); ASSERT_EQ(json["feeAssetId"], "DacnEpaUVFRCYk8Fcd1F3cqUZuT4XG7qW9mRyoZD82zq"); @@ -136,3 +133,5 @@ TEST(WavesTransaction, jsonSerialize) { ASSERT_EQ(json["attachment"], "4t2Xazb2SX"); ASSERT_EQ(json.dump(), "{\"amount\":10000000,\"assetId\":\"DacnEpaUVFRCYk8Fcd1F3cqUZuT4XG7qW9mRyoZD81zq\",\"attachment\":\"4t2Xazb2SX\",\"fee\":100000000,\"feeAssetId\":\"DacnEpaUVFRCYk8Fcd1F3cqUZuT4XG7qW9mRyoZD82zq\",\"proofs\":[\"5ynN2NUiFHkQzw9bK8R7dZcNfTWMAtcWRJsrMvFFM6dUT3fSnPCCX7CTajNU8bJCBH69vU1mnwfx4zpDtF1SkzKg\"],\"recipient\":\"3P2uzAzX9XTu1t32GkWw68YFFLwtapWvDds\",\"senderPublicKey\":\"6mA8eQjie53kd4jbZrwL3ZhMBqCX6nzit1k55tR2X7zU\",\"timestamp\":1526641218066,\"type\":4,\"version\":2}"); } + +} // namespace TW::Waves::tests diff --git a/tests/Ripple/AddressTests.cpp b/tests/chains/XRP/AddressTests.cpp similarity index 61% rename from tests/Ripple/AddressTests.cpp rename to tests/chains/XRP/AddressTests.cpp index f73660b0090..591b2842123 100644 --- a/tests/Ripple/AddressTests.cpp +++ b/tests/chains/XRP/AddressTests.cpp @@ -1,28 +1,25 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "Ripple/Address.h" -#include "Ripple/XAddress.h" +#include "XRP/Address.h" +#include "XRP/XAddress.h" #include "HexCoding.h" -#include "PrivateKey.h" #include -using namespace std; -using namespace TW; -using namespace TW::Ripple; +namespace TW::Ripple { TEST(RippleAddress, FromPublicKey) { const auto publicKey = PublicKey(parse_hex("0303E20EC6B4A39A629815AE02C0A1393B9225E3B890CAE45B59F42FA29BE9668D"), TWPublicKeyTypeSECP256k1); const auto address = Address(publicKey); - ASSERT_EQ(string("rnBFvgZphmN39GWzUJeUitaP22Fr9be75H"), address.string()); + ASSERT_EQ(std::string("rnBFvgZphmN39GWzUJeUitaP22Fr9be75H"), address.string()); } TEST(RippleAddress, FromString) { - string classic = "rnBFvgZphmN39GWzUJeUitaP22Fr9be75H"; + std::string classic = "rnBFvgZphmN39GWzUJeUitaP22Fr9be75H"; const auto address = Address(classic); ASSERT_EQ(address.string(), classic); @@ -31,29 +28,31 @@ TEST(RippleAddress, FromString) { TEST(RippleXAddress, FromPublicKey) { const auto publicKey = PublicKey(parse_hex("0303E20EC6B4A39A629815AE02C0A1393B9225E3B890CAE45B59F42FA29BE9668D"), TWPublicKeyTypeSECP256k1); const auto address = XAddress(publicKey, 12345); - ASSERT_EQ(string("X76UnYEMbQfEs3mUqgtjp4zFy9exgThRj7XVZ6UxsdrBptF"), address.string()); + ASSERT_EQ(std::string("X76UnYEMbQfEs3mUqgtjp4zFy9exgThRj7XVZ6UxsdrBptF"), address.string()); } TEST(RippleXAddress, FromString) { - string xAddress = "X76UnYEMbQfEs3mUqgtjp4zFy9exgThRj7XVZ6UxsdrBptF"; - string xAddress2 = "X76UnYEMbQfEs3mUqgtjp4zFy9exgTsM93nriVZAPufrpE3"; + std::string xAddress = "X76UnYEMbQfEs3mUqgtjp4zFy9exgThRj7XVZ6UxsdrBptF"; + std::string xAddress2 = "X76UnYEMbQfEs3mUqgtjp4zFy9exgTsM93nriVZAPufrpE3"; const auto address = XAddress(xAddress); const auto address2 = XAddress(xAddress2); - ASSERT_EQ(address.tag, 12345); + ASSERT_EQ(address.tag, 12345ul); ASSERT_EQ(address.string(), xAddress); - ASSERT_EQ(address2.tag, 0); + ASSERT_EQ(address2.tag, 0ul); ASSERT_EQ(address2.string(), xAddress2); } TEST(RippleAddress, isValid) { - string classicAddress = "r36yxStAh7qgTQNHTzjZvXybCTzUFhrfav"; - string bitcoinAddress = "1Ma2DrB78K7jmAwaomqZNRMCvgQrNjE2QC"; - string xAddress = "XVfvixWZQKkcenFRYApCjpTUyJ4BePTe3jJv7beatUZvQYh"; + std::string classicAddress = "r36yxStAh7qgTQNHTzjZvXybCTzUFhrfav"; + std::string bitcoinAddress = "1Ma2DrB78K7jmAwaomqZNRMCvgQrNjE2QC"; + std::string xAddress = "XVfvixWZQKkcenFRYApCjpTUyJ4BePTe3jJv7beatUZvQYh"; ASSERT_TRUE(Address::isValid(classicAddress)); ASSERT_TRUE(XAddress::isValid(xAddress)); ASSERT_FALSE(Address::isValid(bitcoinAddress)); ASSERT_FALSE(XAddress::isValid(bitcoinAddress)); } + +} // namespace TW::Ripple diff --git a/tests/Ripple/TWAnySignerTests.cpp b/tests/chains/XRP/TWAnySignerTests.cpp similarity index 79% rename from tests/Ripple/TWAnySignerTests.cpp rename to tests/chains/XRP/TWAnySignerTests.cpp index cbfc4f28823..1b9e05728e6 100644 --- a/tests/Ripple/TWAnySignerTests.cpp +++ b/tests/chains/XRP/TWAnySignerTests.cpp @@ -5,14 +5,16 @@ // file LICENSE at the root of the source code distribution tree. #include "HexCoding.h" +#include "proto/Common.pb.h" #include "proto/Ripple.pb.h" #include -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include using namespace TW; -using namespace TW::Ripple; + +namespace TW::Ripple::tests { TEST(TWAnySignerRipple, Sign) { auto key = parse_hex("ba005cd605d8a02e3d5dfd04234cef3a3ee4f76bfbad2722d1fb5af8e12e6764"); @@ -29,4 +31,14 @@ TEST(TWAnySignerRipple, Sign) { ANY_SIGN(input, TWCoinTypeXRP); EXPECT_EQ(hex(output.encoded()), "12000022800000002400000001614000000001ba8140684000000000030d407321026cc34b92cefb3a4537b3edb0b6044c04af27c01583c577823ecc69a9a21119b6744630440220067f20b3eebfc7107dd0bcc72337a236ac3be042c0469f2341d76694a17d4bb9022048393d7ee7dcb729783b33f5038939ddce1bb8337e66d752974626854556bbb681148400b6b6d08d5d495653d73eda6804c249a5148883148132e4e20aecf29090ac428a9c43f230a829220d"); + + // invalid tag + input.set_destination_tag(641505641505); + + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(output.error(), Common::Proto::SigningError::Error_invalid_memo); + EXPECT_EQ(output.encoded(), ""); } + +} // namespace TW::Ripple::tests diff --git a/tests/Ripple/TWCoinTypeTests.cpp b/tests/chains/XRP/TWCoinTypeTests.cpp similarity index 97% rename from tests/Ripple/TWCoinTypeTests.cpp rename to tests/chains/XRP/TWCoinTypeTests.cpp index 5f4249ca2e1..1f1a967a712 100644 --- a/tests/Ripple/TWCoinTypeTests.cpp +++ b/tests/chains/XRP/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Ripple/TWRippleAddressTests.cpp b/tests/chains/XRP/TWRippleAddressTests.cpp similarity index 56% rename from tests/Ripple/TWRippleAddressTests.cpp rename to tests/chains/XRP/TWRippleAddressTests.cpp index 7fd89c52c43..6c21a4a4bc5 100644 --- a/tests/Ripple/TWRippleAddressTests.cpp +++ b/tests/chains/XRP/TWRippleAddressTests.cpp @@ -4,17 +4,17 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include +#include #include TEST(TWRipple, ExtendedKeys) { auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic( - STRING("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal").get(), - STRING("TREZOR").get() - )); + STRING("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal").get(), + STRING("TREZOR").get())); auto xpub = WRAPS(TWHDWalletGetExtendedPublicKey(wallet.get(), TWPurposeBIP44, TWCoinTypeXRP, TWHDVersionXPUB)); auto xprv = WRAPS(TWHDWalletGetExtendedPrivateKey(wallet.get(), TWPurposeBIP44, TWCoinTypeXRP, TWHDVersionXPRV)); @@ -22,3 +22,12 @@ TEST(TWRipple, ExtendedKeys) { assertStringsEqual(xpub, "xpub6D9oDY4gqFBtsFEonh5GTDiUm6nmij373YWzmYdshcnM4AFzdhUf55iZD33vNU2ZqfQJU5wiCJUgisMt2RHKDzhi1PbZfh5Y2NiiYJAQqUn"); assertStringsEqual(xprv, "xprv9zASp2XnzsdbemALgfYG65mkD4xHKGKFgKbPyAEG9HFNBMvr6AAQXHQ5MmqM66EnbJfe9TvYMy1bucz7hSQjG43NVizRZwJJYfLmeKo4nVB"); } + +TEST(TWRipple, XAddress) { + const auto string = STRING("XVfvixWZQKkcenFRYApCjpTUyJ4BePTe3jJv7beatUZvQYh"); + const auto xAddress = WRAP(TWRippleXAddress, TWRippleXAddressCreateWithString(string.get())); + + EXPECT_TRUE(TWRippleXAddressIsValidString(string.get())); + EXPECT_EQ(TWRippleXAddressTag(xAddress.get()), 12345ul); + assertStringsEqual(WRAPS(TWRippleXAddressDescription(xAddress.get())), "XVfvixWZQKkcenFRYApCjpTUyJ4BePTe3jJv7beatUZvQYh"); +} diff --git a/tests/Ripple/TransactionTests.cpp b/tests/chains/XRP/TransactionTests.cpp similarity index 96% rename from tests/Ripple/TransactionTests.cpp rename to tests/chains/XRP/TransactionTests.cpp index 4162ebbfe2a..f9015b684da 100644 --- a/tests/Ripple/TransactionTests.cpp +++ b/tests/chains/XRP/TransactionTests.cpp @@ -4,9 +4,9 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "Ripple/Address.h" -#include "Ripple/Transaction.h" -#include "Ripple/BinaryCoding.h" +#include "XRP/Address.h" +#include "XRP/Transaction.h" +#include "XRP/BinaryCoding.h" #include "HexCoding.h" #include "PrivateKey.h" #include "PublicKey.h" @@ -14,8 +14,8 @@ #include using namespace std; -using namespace TW; -using namespace TW::Ripple; + +namespace TW::Ripple::tests { TEST(RippleTransaction, serializeAmount) { /// From https://github.com/trezor/trezor-core/blob/master/tests/test_apps.ripple.serializer.py @@ -122,5 +122,7 @@ TEST(RippleTransaction, preImage) { /* account */ "81145b812c9d57731e27a2da8b1830195f88ef32a3b6" /* destination */ "8314b5f762798a53d543a014caf8b297cff8f2f937e8" ); - ASSERT_EQ(unsignedTx.size(), 114); + ASSERT_EQ(unsignedTx.size(), 114ul); } + +} // namespace TW::Ripple::tests diff --git a/tests/Zcash/AddressTests.cpp b/tests/chains/Zcash/AddressTests.cpp similarity index 100% rename from tests/Zcash/AddressTests.cpp rename to tests/chains/Zcash/AddressTests.cpp diff --git a/tests/Zcash/TWCoinTypeTests.cpp b/tests/chains/Zcash/TWCoinTypeTests.cpp similarity index 93% rename from tests/Zcash/TWCoinTypeTests.cpp rename to tests/chains/Zcash/TWCoinTypeTests.cpp index 956f05639d3..46deb231511 100644 --- a/tests/Zcash/TWCoinTypeTests.cpp +++ b/tests/chains/Zcash/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -23,7 +23,7 @@ TEST(TWZcashCoinType, TWCoinType) { auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeZcash)); ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeZcash), 8); - ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeZcash)); + ASSERT_EQ(TWBlockchainZcash, TWCoinTypeBlockchain(TWCoinTypeZcash)); ASSERT_EQ(0xbd, TWCoinTypeP2shPrefix(TWCoinTypeZcash)); ASSERT_EQ(0x1c, TWCoinTypeStaticPrefix(TWCoinTypeZcash)); assertStringsEqual(symbol, "ZEC"); diff --git a/tests/Zcash/TWZcashAddressTests.cpp b/tests/chains/Zcash/TWZcashAddressTests.cpp similarity index 99% rename from tests/Zcash/TWZcashAddressTests.cpp rename to tests/chains/Zcash/TWZcashAddressTests.cpp index bbaa4ad932e..d3820059c5a 100644 --- a/tests/Zcash/TWZcashAddressTests.cpp +++ b/tests/chains/Zcash/TWZcashAddressTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include "Zcash/TAddress.h" diff --git a/tests/Zcash/TWZcashTransactionTests.cpp b/tests/chains/Zcash/TWZcashTransactionTests.cpp similarity index 99% rename from tests/Zcash/TWZcashTransactionTests.cpp rename to tests/chains/Zcash/TWZcashTransactionTests.cpp index 96861a4a507..b0acaf0f132 100644 --- a/tests/Zcash/TWZcashTransactionTests.cpp +++ b/tests/chains/Zcash/TWZcashTransactionTests.cpp @@ -5,7 +5,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include "Bitcoin/OutPoint.h" #include "Bitcoin/Script.h" diff --git a/tests/Zelcash/TWCoinTypeTests.cpp b/tests/chains/Zelcash/TWCoinTypeTests.cpp similarity index 92% rename from tests/Zelcash/TWCoinTypeTests.cpp rename to tests/chains/Zelcash/TWCoinTypeTests.cpp index 50bfe25002f..29ff10fe24e 100644 --- a/tests/Zelcash/TWCoinTypeTests.cpp +++ b/tests/chains/Zelcash/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -23,7 +23,7 @@ TEST(TWZelcashCoinType, TWCoinType) { auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeZelcash)); ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeZelcash), 8); - ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeZelcash)); + ASSERT_EQ(TWBlockchainZcash, TWCoinTypeBlockchain(TWCoinTypeZelcash)); ASSERT_EQ(0xbd, TWCoinTypeP2shPrefix(TWCoinTypeZelcash)); ASSERT_EQ(0x1c, TWCoinTypeStaticPrefix(TWCoinTypeZelcash)); assertStringsEqual(symbol, "FLUX"); diff --git a/tests/Zelcash/TWZelcashAddressTests.cpp b/tests/chains/Zelcash/TWZelcashAddressTests.cpp similarity index 99% rename from tests/Zelcash/TWZelcashAddressTests.cpp rename to tests/chains/Zelcash/TWZelcashAddressTests.cpp index 6d2b5807778..f78524d8898 100644 --- a/tests/Zelcash/TWZelcashAddressTests.cpp +++ b/tests/chains/Zelcash/TWZelcashAddressTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Zelcash/TWZelcashTransactionTests.cpp b/tests/chains/Zelcash/TWZelcashTransactionTests.cpp similarity index 99% rename from tests/Zelcash/TWZelcashTransactionTests.cpp rename to tests/chains/Zelcash/TWZelcashTransactionTests.cpp index 90d2b09cb41..74f3413670e 100644 --- a/tests/Zelcash/TWZelcashTransactionTests.cpp +++ b/tests/chains/Zelcash/TWZelcashTransactionTests.cpp @@ -5,7 +5,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include "Bitcoin/OutPoint.h" #include "HexCoding.h" diff --git a/tests/Zilliqa/AddressTests.cpp b/tests/chains/Zilliqa/AddressTests.cpp similarity index 82% rename from tests/Zilliqa/AddressTests.cpp rename to tests/chains/Zilliqa/AddressTests.cpp index e4f0a49e269..440df29db64 100644 --- a/tests/Zilliqa/AddressTests.cpp +++ b/tests/chains/Zilliqa/AddressTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -13,8 +13,7 @@ #include -using namespace TW; -using namespace TW::Zilliqa; +namespace TW::Zilliqa::tests { TEST(ZilliqaAddress, FromPrivateKey) { const auto privateKey = @@ -39,22 +38,19 @@ TEST(ZilliqaAddress, Validation) { TEST(ZilliqaAddress, Checksum) { ASSERT_EQ( checksum(parse_hex("4BAF5FADA8E5DB92C3D3242618C5B47133AE003C")), - "4BAF5faDA8e5Db92C3d3242618c5B47133AE003C" - ); + "4BAF5faDA8e5Db92C3d3242618c5B47133AE003C"); ASSERT_EQ( checksum(parse_hex("448261915A80CDE9BDE7C7A791685200D3A0BF4E")), - "448261915a80cdE9BDE7C7a791685200D3A0bf4E" - ); + "448261915a80cdE9BDE7C7a791685200D3A0bf4E"); ASSERT_EQ( checksum(parse_hex("0xDED02FD979FC2E55C0243BD2F52DF022C40ADA1E")), - "Ded02fD979fC2e55c0243bd2F52df022c40ADa1E" - ); + "Ded02fD979fC2e55c0243bd2F52df022c40ADa1E"); ASSERT_EQ( checksum(parse_hex("0x13F06E60297BEA6A3C402F6F64C416A6B31E586E")), - "13F06E60297bea6A3c402F6f64c416A6b31e586e" - ); + "13F06E60297bea6A3c402F6f64c416A6b31e586e"); ASSERT_EQ( checksum(parse_hex("0x1A90C25307C3CC71958A83FA213A2362D859CF33")), - "1a90C25307C3Cc71958A83fa213A2362D859CF33" - ); + "1a90C25307C3Cc71958A83fa213A2362D859CF33"); } + +} // namespace TW::Zilliqa::tests diff --git a/tests/Zilliqa/SignatureTests.cpp b/tests/chains/Zilliqa/SignatureTests.cpp similarity index 82% rename from tests/Zilliqa/SignatureTests.cpp rename to tests/chains/Zilliqa/SignatureTests.cpp index 386e655cfbf..37684d1b11b 100644 --- a/tests/Zilliqa/SignatureTests.cpp +++ b/tests/chains/Zilliqa/SignatureTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include "HexCoding.h" #include "Data.h" #include @@ -21,9 +21,9 @@ TEST(ZilliqaSignature, Signing) { auto message = "hello schnorr"; auto messageData = WRAPD(TWDataCreateWithBytes((uint8_t *)message, strnlen(message, 13))); - auto signatureData = WRAPD(TWPrivateKeySignSchnorr(privateKey.get(), messageData.get(), TWCurveSECP256k1)); + auto signatureData = WRAPD(TWPrivateKeySignZilliqaSchnorr(privateKey.get(), messageData.get())); auto signature = data(TWDataBytes(signatureData.get()), TWDataSize(signatureData.get())); - ASSERT_TRUE(TWPublicKeyVerifySchnorr(pubKey.get(), signatureData.get(), messageData.get())); + ASSERT_TRUE(TWPublicKeyVerifyZilliqaSchnorr(pubKey.get(), signatureData.get(), messageData.get())); EXPECT_EQ(hex(signature), "d166b1ae7892c5ef541461dc12a50214d0681b63d8037cda29a3fe6af8bb973e4ea94624d85bc0010bdc1b38d05198328fae21254adc2bf5feaf2804d54dba55"); } diff --git a/tests/Zilliqa/SignerTests.cpp b/tests/chains/Zilliqa/SignerTests.cpp similarity index 87% rename from tests/Zilliqa/SignerTests.cpp rename to tests/chains/Zilliqa/SignerTests.cpp index 10f72933b2a..18c192e0572 100644 --- a/tests/Zilliqa/SignerTests.cpp +++ b/tests/chains/Zilliqa/SignerTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -13,8 +13,7 @@ #include -using namespace TW; -using namespace TW::Zilliqa; +namespace TW::Zilliqa::tests { TEST(ZilliqaSigner, PreImage) { auto privateKey = PrivateKey(parse_hex("0E891B9DFF485000C7D1DC22ECF3A583CC50328684321D61947A86E57CF6C638")); @@ -45,7 +44,7 @@ TEST(ZilliqaSigner, PreImage) { ASSERT_EQ(hex(preImage.begin(), preImage.end()), "0881800410041a149ca91eb535fb92fda5094110fdaeb752edb9b03922230a21034ae47910d58b9bde819c3cffa8de4441955508db00aa2540db8e6bf6e99abc1b2a120a10000000000000000000000da475abf00032120a100000000000000000000000003b9aca003801"); - ASSERT_TRUE(pubKey.verifySchnorr(Data(signature.begin(), signature.end()), preImage)); + ASSERT_TRUE(pubKey.verifyZilliqa(Data(signature.begin(), signature.end()), preImage)); } TEST(ZilliqaSigner, Signing) { @@ -105,6 +104,10 @@ TEST(ZilliqaSigner, SigningData) { input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); auto output = Signer::sign(input); - ASSERT_EQ(output.json(), "{\"amount\":\"10000000000000\",\"code\":\"\",\"data\":\"{\\\"_tag\\\":\\\"DelegateStake\\\",\\\"params\\\":[{\\\"type\\\":\\\"ByStr20\\\",\\\"value\\\":\\\"0x122219cCeAb410901e96c3A0e55E46231480341b\\\",\\\"vname\\\":\\\"ssnaddr\\\"}]}\",\"gasLimit\":\"5000\",\"gasPrice\":\"2000000000\",\"nonce\":56,\"pubKey\":\"03fb30b196ce3e976593ecc2da220dca9cdea8c84d2373770042a930b892ac0f5c\",\"signature\":\"437fb5c3ce2c6b01f9d490f670539fae4533c82a21fa7edfe6b23df70d732937e8c578c8d6ed24be9150f5126f7b7c977a467af8947ef92a720908a761a6eb0d\",\"toAddr\":\"43D459eC504C7432959c086B0ac7F7855E984306\",\"version\":65537}"); - ASSERT_EQ(hex(output.signature().begin(), output.signature().end()), "437fb5c3ce2c6b01f9d490f670539fae4533c82a21fa7edfe6b23df70d732937e8c578c8d6ed24be9150f5126f7b7c977a467af8947ef92a720908a761a6eb0d"); + //ASSERT_EQ(output.json(), R"({"amount":"10000000000000","code":"","data":"{\"_tag\":\"DelegateStake\",\"params\":[{\"type\":\"ByStr20\",\"value\":\"0x122219cCeAb410901e96c3A0e55E46231480341b\",\"vname\":\"ssnaddr\"}]}","gasLimit":"5000","gasPrice":"2000000000","nonce":56,"pubKey":"03fb30b196ce3e976593ecc2da220dca9cdea8c84d2373770042a930b892ac0f5c","signature":"437fb5c3ce2c6b01f9d490f670539fae4533c82a21fa7edfe6b23df70d732937e8c578c8d6ed24be9150f5126f7b7c977a467af8947ef92a720908a761a6eb0d","toAddr":"43D459eC504C7432959c086B0ac7F7855E984306","version":65537})"); + ASSERT_EQ(output.json(), "{\"amount\":\"10000000000000\",\"code\":\"\",\"data\":\"{\\\"_tag\\\":\\\"DelegateStake\\\",\\\"params\\\":[{\\\"type\\\":\\\"ByStr20\\\",\\\"value\\\":\\\"0x122219cCeAb410901e96c3A0e55E46231480341b\\\",\\\"vname\\\":\\\"ssnaddr\\\"}]}\",\"gasLimit\":\"5000\",\"gasPrice\":\"2000000000\",\"nonce\":56,\"pubKey\":\"03fb30b196ce3e976593ecc2da220dca9cdea8c84d2373770042a930b892ac0f5c\",\"signature\":\"437fb5c3ce2c6b01f9d490f670539fae4533c82a21fa7edfe6b23df70d732937e8c578c8d6ed24be9150f5126f7b7c977a467af8947ef92a720908a761a6eb0d\",\"toAddr\":\"43D459eC504C7432959c086B0ac7F7855E984306\",\"version\":65537}");//win + + ASSERT_EQ(hex(output.signature().begin(), output.signature().end()), "437fb5c3ce2c6b01f9d490f670539fae4533c82a21fa7edfe6b23df70d732937e8c578c8d6ed24be9150f5126f7b7c977a467af8947ef92a720908a761a6eb0d"); } + +} // namespace TW::Zilliqa::tests \ No newline at end of file diff --git a/tests/Zilliqa/TWAnySignerTests.cpp b/tests/chains/Zilliqa/TWAnySignerTests.cpp similarity index 96% rename from tests/Zilliqa/TWAnySignerTests.cpp rename to tests/chains/Zilliqa/TWAnySignerTests.cpp index e9cbe99aa3f..acf610c7a29 100644 --- a/tests/Zilliqa/TWAnySignerTests.cpp +++ b/tests/chains/Zilliqa/TWAnySignerTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -7,12 +7,11 @@ #include "HexCoding.h" #include "uint256.h" #include "proto/Zilliqa.pb.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include -using namespace TW; -using namespace TW::Zilliqa; +namespace TW::Zilliqa::tests { TEST(TWAnySignerZilliqa, Sign) { auto input = Proto::SigningInput(); @@ -45,3 +44,5 @@ TEST(TWAnySignerZilliqa, SignJSON) { ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeZilliqa)); assertStringsEqual(result, "7b22616d6f756e74223a2231303030303030303030303030222c22636f6465223a22222c2264617461223a22222c226761734c696d6974223a2231222c226761735072696365223a2231303030303030303030222c226e6f6e6365223a322c227075624b6579223a22303366623330623139366365336539373635393365636332646132323064636139636465613863383464323337333737303034326139333062383932616330663563222c227369676e6174757265223a223030316661346466303863313161346137396539366536393339396565343865656563633738323331613738623033353561386361373833633737633133393433366533373933346665636332323532656438646163303065323335653232643138343130343631666238393636383563343237303634323733386564323638222c22746f41646472223a2237464363614366303636613546323645653341466663324544314641393831304465616136333243222c2276657273696f6e223a36353533377d"); } + +} // namespace TW::Zilliqa::tests \ No newline at end of file diff --git a/tests/Zilliqa/TWCoinTypeTests.cpp b/tests/chains/Zilliqa/TWCoinTypeTests.cpp similarity index 97% rename from tests/Zilliqa/TWCoinTypeTests.cpp rename to tests/chains/Zilliqa/TWCoinTypeTests.cpp index 0b1d57ea02b..d0d51a694b2 100644 --- a/tests/Zilliqa/TWCoinTypeTests.cpp +++ b/tests/chains/Zilliqa/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Zilliqa/TWZilliqaAddressTests.cpp b/tests/chains/Zilliqa/TWZilliqaAddressTests.cpp similarity index 97% rename from tests/Zilliqa/TWZilliqaAddressTests.cpp rename to tests/chains/Zilliqa/TWZilliqaAddressTests.cpp index e2e19630cbe..2677af67c45 100644 --- a/tests/Zilliqa/TWZilliqaAddressTests.cpp +++ b/tests/chains/Zilliqa/TWZilliqaAddressTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/chains/ZkSyncV2/TWCoinTypeTests.cpp b/tests/chains/ZkSyncV2/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..95b4854e53e --- /dev/null +++ b/tests/chains/ZkSyncV2/TWCoinTypeTests.cpp @@ -0,0 +1,36 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "TestUtilities.h" +#include +#include + +namespace TW::TWZksync::tests { + +TEST(TWZksyncCoinType, TWCoinType) { + const auto coin = TWCoinTypeZksync; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xb526861291c0335435e3c976e672a464b70762e54d7167409fb4f66e374ed708")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x970978989a51790ee591b2a54f92c7cd9cdc2f88")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "zksync"); + assertStringsEqual(name, "zkSync v2"); + assertStringsEqual(symbol, "ETH"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "280"); + assertStringsEqual(txUrl, "https://zksync2-testnet.zkscan.io/tx/0xb526861291c0335435e3c976e672a464b70762e54d7167409fb4f66e374ed708"); + assertStringsEqual(accUrl, "https://zksync2-testnet.zkscan.io/address/0x970978989a51790ee591b2a54f92c7cd9cdc2f88"); +} + +} // namespace TW::TWZksync::tests diff --git a/tests/xDai/TWCoinTypeTests.cpp b/tests/chains/xDai/TWCoinTypeTests.cpp similarity index 95% rename from tests/xDai/TWCoinTypeTests.cpp rename to tests/chains/xDai/TWCoinTypeTests.cpp index e1754f69233..c49ec7395f1 100644 --- a/tests/xDai/TWCoinTypeTests.cpp +++ b/tests/chains/xDai/TWCoinTypeTests.cpp @@ -8,7 +8,7 @@ // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -30,5 +30,5 @@ TEST(TWxDaiCoinType, TWCoinType) { assertStringsEqual(txUrl, "https://blockscout.com/xdai/mainnet/tx/0x936798a1ef607c9e856d7861b15999c770c06f0887c4fc1f6acbf3bef09899c1"); assertStringsEqual(accUrl, "https://blockscout.com/xdai/mainnet/address/0x12d61a95CF55e18D267C2F1AA67d8e42ae1368f8"); assertStringsEqual(id, "xdai"); - assertStringsEqual(name, "xDai"); + assertStringsEqual(name, "Gnosis Chain"); } diff --git a/tests/common/AnyAddressTests.cpp b/tests/common/AnyAddressTests.cpp new file mode 100644 index 00000000000..bc7dc5af2b2 --- /dev/null +++ b/tests/common/AnyAddressTests.cpp @@ -0,0 +1,34 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "AnyAddress.h" +#include "HexCoding.h" + +#include + +namespace TW::tests { + +constexpr auto ANY_ADDRESS_TEST_ADDRESS = "bc1qcj2vfjec3c3luf9fx9vddnglhh9gawmncmgxhz"; +constexpr auto ANY_ADDRESS_TEST_PUBKEY = "02753f5c275e1847ba4d2fd3df36ad00af2e165650b35fe3991e9c9c46f68b12bc"; + +TEST(AnyAddress, createFromString) { + std::unique_ptr addr(AnyAddress::createAddress(ANY_ADDRESS_TEST_ADDRESS, TWCoinTypeBitcoin)); + EXPECT_EQ(ANY_ADDRESS_TEST_ADDRESS, addr->address); +} + +TEST(AnyAddress, createFromPubKey) { + const Data key = parse_hex(ANY_ADDRESS_TEST_PUBKEY); + PublicKey publicKey(key, TWPublicKeyTypeSECP256k1); + std::unique_ptr addr(AnyAddress::createAddress(publicKey, TWCoinTypeBitcoin)); + EXPECT_EQ(ANY_ADDRESS_TEST_ADDRESS, addr->address); +} + +TEST(AnyAddress, createFromWrongString) { + std::unique_ptr addr(AnyAddress::createAddress("1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaax", TWCoinTypeBitcoin)); + EXPECT_EQ(nullptr, addr); +} + +} // namespace TW::tests diff --git a/tests/common/BCSTests.cpp b/tests/common/BCSTests.cpp new file mode 100644 index 00000000000..fee7c0805df --- /dev/null +++ b/tests/common/BCSTests.cpp @@ -0,0 +1,165 @@ +// Copyright © 2017-2022 Trust Wallet. +// Created by Clément Doumergue +// +// 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 "BCS.h" +#include "HexCoding.h" + +#include + +namespace TW::BCS::tests { + +TEST(BCS, Integral) { + Serializer os; + os << uint32_t(0xAABBCCDD); + ASSERT_EQ(os.bytes, parse_hex("0xDDCCBBAA")); + + os.clear(); + os << int32_t(-305419896); + ASSERT_EQ(os.bytes, parse_hex("0x88A9CBED")); +} + +TEST(BCS, ULEB128) { + Serializer os; + os << uleb128{0x00000001}; + ASSERT_EQ(os.bytes, parse_hex("0x01")); + + os.clear(); + os << uleb128{0x00000080}; + ASSERT_EQ(os.bytes, parse_hex("0x8001")); + + os.clear(); + os << uleb128{0x00004000}; + ASSERT_EQ(os.bytes, parse_hex("0x808001")); + + os.clear(); + os << uleb128{0x00200000}; + ASSERT_EQ(os.bytes, parse_hex("0x80808001")); + + os.clear(); + os << uleb128{0x10000000}; + ASSERT_EQ(os.bytes, parse_hex("0x8080808001")); + + os.clear(); + os << uleb128{0x0000250F}; + ASSERT_EQ(os.bytes, parse_hex("0x8F4A")); +} + +TEST(BCS, String) { + Serializer os; + os << std::string_view("abcd"); + ASSERT_EQ(os.bytes, parse_hex("0x0461626364")); + + os.clear(); + os << std::string_view(""); + ASSERT_EQ(os.bytes, parse_hex("0x00")); +} + +TEST(BCS, Optional) { + Serializer os; + os << std::optional{0xBBCCDD}; + ASSERT_EQ(os.bytes, parse_hex("0x01DDCCBB00")); + + os.clear(); + os << std::optional{}; + ASSERT_EQ(os.bytes, parse_hex("0x00")); + + os.clear(); + os << std::nullopt; + ASSERT_EQ(os.bytes, parse_hex("0x00")); +} + +TEST(BCS, Tuple) { + Serializer os; + os << std::tuple{uint16_t(1), 'a'}; + ASSERT_EQ(os.bytes, parse_hex("0x010061")); + + os.clear(); + os << std::tuple{std::optional{123}, std::string_view("abcd"), uint8_t(0x0E)}; + ASSERT_EQ(os.bytes, parse_hex("0x017b00000004616263640e")); + + os.clear(); + os << std::tuple{}; + ASSERT_EQ(os.bytes, (Data{})); +} + +TEST(BCS, Pair) { + Serializer os; + os << std::pair{uint16_t(1), 'a'}; + ASSERT_EQ(os.bytes, parse_hex("0x010061")); + + os.clear(); + os << std::pair{std::optional{123}, std::string_view("abcd")}; + ASSERT_EQ(os.bytes, parse_hex("0x017b0000000461626364")); +} +/* +struct my_struct { + std::optional first; + std::string_view second; + uint8_t third; +}; + +TEST(BCS, Struct) { + Serializer os; + os << my_struct{{123}, "abcd", 0x0E}; + ASSERT_EQ(os.bytes, parse_hex("0x017b00000004616263640e")); +} +*/ +TEST(BCS, Variant) { + using V = std::variant; + + Serializer os; + os << V{uint32_t(1)}; + ASSERT_EQ(os.bytes, parse_hex("0x0001000000")); + + os.clear(); + os << V{char('a')}; + ASSERT_EQ(os.bytes, parse_hex("0x0161")); + + os.clear(); + os << V{true}; + ASSERT_EQ(os.bytes, parse_hex("0x0201")); +} + +TEST(BCS, Map) { + Serializer os; + os << std::map{{'a', 0}, {'b', 1}, {'c', 2}}; + ASSERT_EQ(os.bytes, parse_hex("0x03610062016302")); +} + +class my_number { +private: + int value; + +public: + explicit my_number(int value) noexcept + : value(value) { + } + + [[nodiscard]] auto get_value() const { + return value; + } +}; + +Serializer& operator<<(Serializer& stream, my_number n) noexcept { + return stream << n.get_value(); +} + +static_assert(CustomSerializable, "my_number does not model the CustomSerializable concept"); + +TEST(BCS, Custom) { + Serializer os; + os << my_number{0xBBCCDD}; + ASSERT_EQ(os.bytes, parse_hex("0xDDCCBB00")); +} + +TEST(BCS, Vector) { + Serializer os; + os << std::vector{1}; + ASSERT_EQ(os.bytes, parse_hex("0101")); +} + +} diff --git a/tests/Base64Tests.cpp b/tests/common/Base64Tests.cpp similarity index 95% rename from tests/Base64Tests.cpp rename to tests/common/Base64Tests.cpp index 9a918d7fd46..64787ffaaf8 100644 --- a/tests/Base64Tests.cpp +++ b/tests/common/Base64Tests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -10,8 +10,7 @@ #include -using namespace TW; -using namespace TW::Base64; +namespace TW::Base64::tests { TEST(Base64, encode) { auto encoded = encode(data("Hello, world!")); @@ -49,7 +48,7 @@ TEST(Base64, decode) { TEST(Base64, UrlFormat) { const std::string const1 = "11003faa8556289975ec991ac9994dfb613abec4ea000d5094e6379080f594e559b330b8"; - + // Encoded string has both special characters auto encoded = encode(parse_hex(const1)); EXPECT_EQ("EQA/qoVWKJl17JkayZlN+2E6vsTqAA1QlOY3kID1lOVZszC4", encoded); @@ -61,3 +60,5 @@ TEST(Base64, UrlFormat) { decoded = decodeBase64Url("EQA_qoVWKJl17JkayZlN-2E6vsTqAA1QlOY3kID1lOVZszC4"); EXPECT_EQ(const1, hex(decoded)); } + +} // namespace TW::Base64::tests diff --git a/tests/BaseEncoding.cpp b/tests/common/BaseEncoding.cpp similarity index 86% rename from tests/BaseEncoding.cpp rename to tests/common/BaseEncoding.cpp index 65763eb404f..48003ec5fad 100644 --- a/tests/BaseEncoding.cpp +++ b/tests/common/BaseEncoding.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,18 +9,15 @@ #include -using namespace TW; -using namespace TW::Base32; +namespace TW::Base32::tests { -void TestBase32Encode(const char* decoded_hex, const char* expected_encoded_in, const char* alphabet_in = nullptr) -{ +void TestBase32Encode(const char* decoded_hex, const char* expected_encoded_in, const char* alphabet_in = nullptr) { auto decoded = parse_hex(std::string(decoded_hex)); auto encoded = encode(decoded, alphabet_in); ASSERT_EQ(std::string(expected_encoded_in), encoded); } -void TestBase32Decode(const char* encoded_in, const char* expected_decoded_hex, const char* alphabet_in = nullptr) -{ +void TestBase32Decode(const char* encoded_in, const char* expected_decoded_hex, const char* alphabet_in = nullptr) { Data decoded; bool res = decode(std::string(encoded_in), decoded, alphabet_in); ASSERT_TRUE(res); @@ -33,7 +30,7 @@ TEST(Base32, Encode) { TestBase32Encode("010203", "AEBAG"); TestBase32Encode("", ""); TestBase32Encode( - "48450c2745890def7da06fc2551f912a14f9fc581c12db6e4d6f73f2fd0b2ad50df3d396", + "48450c2745890def7da06fc2551f912a14f9fc581c12db6e4d6f73f2fd0b2ad50df3d396", "JBCQYJ2FREG667NAN7BFKH4RFIKPT7CYDQJNW3SNN5Z7F7ILFLKQ346TSY"); TestBase32Encode( "3dd160d60673bd9b13adc25dad5d988d0d9f4ccdbe95a2122f9ef28b3ce4e89693074620", @@ -49,7 +46,7 @@ TEST(Base32, Decode) { TestBase32Decode("", ""); TestBase32Decode( "JBCQYJ2FREG667NAN7BFKH4RFIKPT7CYDQJNW3SNN5Z7F7ILFLKQ346TSY", - "48450c2745890def7da06fc2551f912a14f9fc581c12db6e4d6f73f2fd0b2ad50df3d396"); + "48450c2745890def7da06fc2551f912a14f9fc581c12db6e4d6f73f2fd0b2ad50df3d396"); TestBase32Decode( "HXIWBVQGOO6ZWE5NYJO22XMYRUGZ6TGNX2K2EERPT3ZIWPHE5CLJGB2GEA", "3dd160d60673bd9b13adc25dad5d988d0d9f4ccdbe95a2122f9ef28b3ce4e89693074620"); @@ -66,7 +63,9 @@ TEST(Base32, EncodeNimiq) { TEST(Base32, DecodeInvalid) { Data decoded; - ASSERT_FALSE(decode("+-", decoded)); // invalid characters - ASSERT_FALSE(decode("A", decoded)); // invalid odd length + ASSERT_FALSE(decode("+-", decoded)); // invalid characters + ASSERT_FALSE(decode("A", decoded)); // invalid odd length ASSERT_FALSE(decode("ABC", decoded)); // invalid odd length } + +} // namespace TW::Base32::tests diff --git a/tests/Bech32AddressTests.cpp b/tests/common/Bech32AddressTests.cpp similarity index 90% rename from tests/Bech32AddressTests.cpp rename to tests/common/Bech32AddressTests.cpp index ec29247288d..7152fd9ce78 100644 --- a/tests/Bech32AddressTests.cpp +++ b/tests/common/Bech32AddressTests.cpp @@ -104,33 +104,33 @@ TEST(Bech32Address, FromPublicKey) { auto privateKey = PrivateKey(parse_hex("95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832")); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); ASSERT_EQ(hex(publicKey.bytes.begin(), publicKey.bytes.end()), "026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e502"); - auto address = Bech32Address("bnb", HASHER_SHA2_RIPEMD, publicKey); + auto address = Bech32Address("bnb", Hash::HasherSha256ripemd, publicKey); ASSERT_EQ("bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", address.string()); } { auto privateKey = PrivateKey(parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005")); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); ASSERT_EQ(hex(publicKey.bytes.begin(), publicKey.bytes.end()), "0257286ec3f37d33557bbbaa000b27744ac9023aa9967cae75a181d1ff91fa9dc5"); - auto address = Bech32Address("cosmos", HASHER_SHA2_RIPEMD, publicKey); + auto address = Bech32Address("cosmos", Hash::HasherSha256ripemd, publicKey); ASSERT_EQ(address.string(), "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); } { auto privateKey = PrivateKey(parse_hex("e2f88b4974ae763ca1c2db49218802c2e441293a09eaa9ab681779e05d1b7b94")); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); - auto address = Bech32Address("one", HASHER_SHA3K, publicKey); + auto address = Bech32Address("one", Hash::HasherKeccak256, publicKey); ASSERT_EQ(address.string(), "one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe"); } { auto privateKey = PrivateKey(parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f")); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); - auto address = Bech32Address("io", HASHER_SHA3K, publicKey); + auto address = Bech32Address("io", Hash::HasherKeccak256, publicKey); ASSERT_EQ(address.string(), "io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j"); } { const auto privateKey = PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6")); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); ASSERT_EQ(hex(publicKey.bytes.begin(), publicKey.bytes.end()), "02b65744e8bd0ba7666468abaff2aeb862c88a25ed605e0153100aa8f2661c1c3d"); - const auto address = Bech32Address("zil", HASHER_SHA2, publicKey); + const auto address = Bech32Address("zil", Hash::HasherSha256, publicKey); ASSERT_EQ("zil", address.getHrp()); ASSERT_EQ("zil1j8xae6lggm8y63m3y2r7aefu797ze7mhzulnqg", address.string()); } @@ -143,10 +143,10 @@ TEST(Bech32Address, Hashes) { auto publicKey1 = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); ASSERT_EQ("02b65744e8bd0ba7666468abaff2aeb862c88a25ed605e0153100aa8f2661c1c3d", hex(publicKey1.bytes.begin(), publicKey1.bytes.end())); - const auto address1 = Bech32Address("hrp", HASHER_SHA2_RIPEMD, publicKey1); + const auto address1 = Bech32Address("hrp", Hash::HasherSha256ripemd, publicKey1); ASSERT_EQ("hrp186zwn9h0z9fyvwfqs4jl92cw3kexusm4xw6ptp", address1.string()); - const auto address2 = Bech32Address("hrp", HASHER_SHA2, publicKey1); + const auto address2 = Bech32Address("hrp", Hash::HasherSha256, publicKey1); ASSERT_EQ("hrp1j8xae6lggm8y63m3y2r7aefu797ze7mhgfetvu", address2.string()); auto publicKey2 = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); @@ -154,7 +154,7 @@ TEST(Bech32Address, Hashes) { "04b65744e8bd0ba7666468abaff2aeb862c88a25ed605e0153100aa8f2661c1c3d83c307736082c09f1f22328e0fbeab40ddd198cf0f70fcdaa1e5969ca400c098", hex(publicKey2.bytes.begin(), publicKey2.bytes.end())); - const auto address3 = Bech32Address("hrp", HASHER_SHA3K, publicKey2); + const auto address3 = Bech32Address("hrp", Hash::HasherKeccak256, publicKey2); ASSERT_EQ("hrp17hff3s97m5uxpjcdq3nzqxxatt8cmumnsf03su", address3.string()); } @@ -165,10 +165,10 @@ TEST(Bech32Address, Prefixes) { auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); ASSERT_EQ("02b65744e8bd0ba7666468abaff2aeb862c88a25ed605e0153100aa8f2661c1c3d", hex(publicKey.bytes.begin(), publicKey.bytes.end())); - const auto address1 = Bech32Address("hrpone", HASHER_SHA2_RIPEMD, publicKey); + const auto address1 = Bech32Address("hrpone", Hash::HasherSha256ripemd, publicKey); ASSERT_EQ("hrpone186zwn9h0z9fyvwfqs4jl92cw3kexusm47das6p", address1.string()); - const auto address2 = Bech32Address("hrptwo", HASHER_SHA2_RIPEMD, publicKey); + const auto address2 = Bech32Address("hrptwo", Hash::HasherSha256ripemd, publicKey); ASSERT_EQ("hrptwo186zwn9h0z9fyvwfqs4jl92cw3kexusm4qzr8p7", address2.string()); - const auto address3 = Bech32Address("hrpthree", HASHER_SHA2_RIPEMD, publicKey); + const auto address3 = Bech32Address("hrpthree", Hash::HasherSha256ripemd, publicKey); ASSERT_EQ("hrpthree186zwn9h0z9fyvwfqs4jl92cw3kexusm4wuqkvd", address3.string()); } diff --git a/tests/Bech32Tests.cpp b/tests/common/Bech32Tests.cpp similarity index 99% rename from tests/Bech32Tests.cpp rename to tests/common/Bech32Tests.cpp index adb569ec333..9c8b0b32baa 100644 --- a/tests/Bech32Tests.cpp +++ b/tests/common/Bech32Tests.cpp @@ -82,7 +82,7 @@ TEST(Bech32, decode) { auto res = Bech32::decode(td.encoded); if (!td.isValid && !td.isValidM) { EXPECT_EQ(std::get<0>(res), ""); - EXPECT_EQ(std::get<1>(res).size(), 0); + EXPECT_EQ(std::get<1>(res).size(), 0ul); } else { if (td.isValid) { EXPECT_EQ(std::get<2>(res), Bech32::ChecksumVariant::Bech32); diff --git a/tests/BinaryCodingTests.cpp b/tests/common/BinaryCodingTests.cpp similarity index 98% rename from tests/BinaryCodingTests.cpp rename to tests/common/BinaryCodingTests.cpp index 138cbb7bea8..1627a90951c 100644 --- a/tests/BinaryCodingTests.cpp +++ b/tests/common/BinaryCodingTests.cpp @@ -47,10 +47,6 @@ TEST(BinaryCodingTests, varIntSize) { } } -void testEncodeVarInt(uint64_t input, const std::string& expectedEncoded) { - -} - TEST(BinaryCodingTests, encodeAndDecodeVarInt) { vector> tests = { {0, "00"}, diff --git a/tests/CborTests.cpp b/tests/common/CborTests.cpp similarity index 69% rename from tests/CborTests.cpp rename to tests/common/CborTests.cpp index 2c07cc1b1dc..592b0ec9322 100644 --- a/tests/CborTests.cpp +++ b/tests/common/CborTests.cpp @@ -10,21 +10,23 @@ #include -using namespace TW; -using namespace TW::Cbor; +namespace TW::Cbor::tests { + using namespace std; +// clang-format off TEST(Cbor, EncSample1) { EXPECT_EQ( - "8205a26178186461793831", + "8205a26178186461793831", hex(Encode::array({ - Encode::uint(5), - Encode::map({ - make_pair(Encode::string("x"), Encode::uint(100)), - make_pair(Encode::string("y"), Encode::negInt(50)), - }), - }).encoded()) + Encode::uint(5), + Encode::map({ + make_pair(Encode::string("x"), Encode::uint(100)), + make_pair(Encode::string("y"), Encode::negInt(50)), + }), + }) + .encoded()) ); } @@ -84,16 +86,14 @@ TEST(Cbor, EncNegInt) { EXPECT_EQ("-9", Decode(Encode::negInt(9).encoded()).dumpToString()); } - TEST(Cbor, EncString) { EXPECT_EQ("60", hex(Encode::string("").encoded())); EXPECT_EQ("6141", hex(Encode::string("A").encoded())); EXPECT_EQ("656162636465", hex(Encode::string("abcde").encoded())); Data long258(258); EXPECT_EQ( - "590102000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - hex(Encode::bytes(long258).encoded()) - ); + "590102000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + hex(Encode::bytes(long258).encoded())); EXPECT_EQ("\"abcde\"", Decode(Encode::string("abcde").encoded()).dumpToString()); EXPECT_EQ("h\"6162636465\"", Decode(Encode::bytes(parse_hex("6162636465")).encoded()).dumpToString()); @@ -109,6 +109,15 @@ TEST(Cbor, EncTag) { EXPECT_EQ("d94321191234", hex(Encode::tag(0x4321, Encode::uint(0x1234)).encoded())); } +TEST(Cbor, EncNull) { + { + Data cbor = Encode::null().encoded(); + EXPECT_EQ("f6", hex(cbor)); + EXPECT_TRUE(Decode(cbor).isValid()); + EXPECT_EQ("null", Decode(cbor).dumpToString()); + } +} + TEST(Cbor, EncInvalid) { Data invalid = parse_hex("5b99999999999999991234"); // invalid very looong string EXPECT_FALSE(Decode(invalid).isValid()); @@ -122,36 +131,36 @@ TEST(Cbor, EncInvalid) { } TEST(Cbor, DecInt) { - EXPECT_EQ(0, Decode(parse_hex("00")).getValue()); - EXPECT_EQ(1, Decode(parse_hex("01")).getValue()); - EXPECT_EQ(10, Decode(parse_hex("0a")).getValue()); - EXPECT_EQ(23, Decode(parse_hex("17")).getValue()); - EXPECT_EQ(24, Decode(parse_hex("1818")).getValue()); - EXPECT_EQ(25, Decode(parse_hex("1819")).getValue()); - EXPECT_EQ(26, Decode(parse_hex("181a")).getValue()); - EXPECT_EQ(27, Decode(parse_hex("181b")).getValue()); - EXPECT_EQ(28, Decode(parse_hex("181c")).getValue()); - EXPECT_EQ(29, Decode(parse_hex("181d")).getValue()); - EXPECT_EQ(30, Decode(parse_hex("181e")).getValue()); - EXPECT_EQ(31, Decode(parse_hex("181f")).getValue()); - EXPECT_EQ(32, Decode(parse_hex("1820")).getValue()); - EXPECT_EQ(0x3f, Decode(parse_hex("183f")).getValue()); - EXPECT_EQ(0x40, Decode(parse_hex("1840")).getValue()); - EXPECT_EQ(100, Decode(parse_hex("1864")).getValue()); - EXPECT_EQ(0x7f, Decode(parse_hex("187f")).getValue()); - EXPECT_EQ(0x80, Decode(parse_hex("1880")).getValue()); - EXPECT_EQ(0xff, Decode(parse_hex("18ff")).getValue()); - EXPECT_EQ(0x100, Decode(parse_hex("190100")).getValue()); - EXPECT_EQ(1000, Decode(parse_hex("1903e8")).getValue()); - EXPECT_EQ(0x8765, Decode(parse_hex("198765")).getValue()); - EXPECT_EQ(0xffff, Decode(parse_hex("19ffff")).getValue()); - EXPECT_EQ(0x00010000, Decode(parse_hex("1a00010000")).getValue()); - EXPECT_EQ(1000000, Decode(parse_hex("1a000f4240")).getValue()); - EXPECT_EQ(0x00800000, Decode(parse_hex("1a00800000")).getValue()); + EXPECT_EQ(0ul, Decode(parse_hex("00")).getValue()); + EXPECT_EQ(1ul, Decode(parse_hex("01")).getValue()); + EXPECT_EQ(10ul, Decode(parse_hex("0a")).getValue()); + EXPECT_EQ(23ul, Decode(parse_hex("17")).getValue()); + EXPECT_EQ(24ul, Decode(parse_hex("1818")).getValue()); + EXPECT_EQ(25ul, Decode(parse_hex("1819")).getValue()); + EXPECT_EQ(26ul, Decode(parse_hex("181a")).getValue()); + EXPECT_EQ(27ul, Decode(parse_hex("181b")).getValue()); + EXPECT_EQ(28ul, Decode(parse_hex("181c")).getValue()); + EXPECT_EQ(29ul, Decode(parse_hex("181d")).getValue()); + EXPECT_EQ(30ul, Decode(parse_hex("181e")).getValue()); + EXPECT_EQ(31ul, Decode(parse_hex("181f")).getValue()); + EXPECT_EQ(32ul, Decode(parse_hex("1820")).getValue()); + EXPECT_EQ(0x3ful, Decode(parse_hex("183f")).getValue()); + EXPECT_EQ(0x40ul, Decode(parse_hex("1840")).getValue()); + EXPECT_EQ(100ul, Decode(parse_hex("1864")).getValue()); + EXPECT_EQ(0x7ful, Decode(parse_hex("187f")).getValue()); + EXPECT_EQ(0x80ul, Decode(parse_hex("1880")).getValue()); + EXPECT_EQ(0xfful, Decode(parse_hex("18ff")).getValue()); + EXPECT_EQ(0x100ul, Decode(parse_hex("190100")).getValue()); + EXPECT_EQ(1000ul, Decode(parse_hex("1903e8")).getValue()); + EXPECT_EQ(0x8765ul, Decode(parse_hex("198765")).getValue()); + EXPECT_EQ(0xfffful, Decode(parse_hex("19ffff")).getValue()); + EXPECT_EQ(0x00010000ul, Decode(parse_hex("1a00010000")).getValue()); + EXPECT_EQ(1000000ul, Decode(parse_hex("1a000f4240")).getValue()); + EXPECT_EQ(0x00800000ul, Decode(parse_hex("1a00800000")).getValue()); EXPECT_EQ(0x87654321, Decode(parse_hex("1a87654321")).getValue()); EXPECT_EQ(0xffffffff, Decode(parse_hex("1affffffff")).getValue()); - EXPECT_EQ(0x0000000100000000, Decode(parse_hex("1b0000000100000000")).getValue()); - EXPECT_EQ(1000000000000, Decode(parse_hex("1b000000e8d4a51000")).getValue()); + EXPECT_EQ(0x0000000100000000ul, Decode(parse_hex("1b0000000100000000")).getValue()); + EXPECT_EQ(1000000000000ul, Decode(parse_hex("1b000000e8d4a51000")).getValue()); EXPECT_EQ(0x876543210fedcba9, Decode(parse_hex("1b876543210fedcba9")).getValue()); EXPECT_EQ(0xffffffffffffffff, Decode(parse_hex("1bffffffffffffffff")).getValue()); } @@ -165,27 +174,27 @@ TEST(Cbor, DecMinortypeInvalid) { TEST(Cbor, DecArray3) { Decode cbor = Decode(parse_hex("83010203")); - EXPECT_EQ(3, cbor.getArrayElements().size()); + EXPECT_EQ(3ul, cbor.getArrayElements().size()); } TEST(Cbor, DecArrayNested) { Data d1 = parse_hex("8301820203820405"); Decode cbor = Decode(d1); - EXPECT_EQ(3, cbor.getArrayElements().size()); + EXPECT_EQ(3ul, cbor.getArrayElements().size()); - EXPECT_EQ(1, cbor.getArrayElements()[0].getValue()); - EXPECT_EQ(2, cbor.getArrayElements()[1].getArrayElements().size()); - EXPECT_EQ(2, cbor.getArrayElements()[1].getArrayElements()[0].getValue()); - EXPECT_EQ(3, cbor.getArrayElements()[1].getArrayElements()[1].getValue()); - EXPECT_EQ(2, cbor.getArrayElements()[2].getArrayElements().size()); - EXPECT_EQ(4, cbor.getArrayElements()[2].getArrayElements()[0].getValue()); - EXPECT_EQ(5, cbor.getArrayElements()[2].getArrayElements()[1].getValue()); + EXPECT_EQ(1ul, cbor.getArrayElements()[0].getValue()); + EXPECT_EQ(2ul, cbor.getArrayElements()[1].getArrayElements().size()); + EXPECT_EQ(2ul, cbor.getArrayElements()[1].getArrayElements()[0].getValue()); + EXPECT_EQ(3ul, cbor.getArrayElements()[1].getArrayElements()[1].getValue()); + EXPECT_EQ(2ul, cbor.getArrayElements()[2].getArrayElements().size()); + EXPECT_EQ(4ul, cbor.getArrayElements()[2].getArrayElements()[0].getValue()); + EXPECT_EQ(5ul, cbor.getArrayElements()[2].getArrayElements()[1].getValue()); } TEST(Cbor, DecEncoded) { // sometimes getting the encoded version is useful during decoding too Decode cbor = Decode(parse_hex("8301820203820405")); - EXPECT_EQ(3, cbor.getArrayElements().size()); + EXPECT_EQ(3ul, cbor.getArrayElements().size()); EXPECT_EQ("820203", hex(cbor.getArrayElements()[1].encoded())); EXPECT_EQ("820405", hex(cbor.getArrayElements()[2].encoded())); } @@ -199,14 +208,14 @@ TEST(Cbor, DecMemoryref) { // also do some new allocation Decode* dummy = new Decode(parse_hex("5555555555555555")); // work with the child references - EXPECT_EQ(2, elems.size()); - EXPECT_EQ(3, elems[0].getArrayElements().size()); - EXPECT_EQ(3, elems[1].getArrayElements().size()); + EXPECT_EQ(2ul, elems.size()); + EXPECT_EQ(3ul, elems[0].getArrayElements().size()); + EXPECT_EQ(3ul, elems[1].getArrayElements().size()); delete dummy; } TEST(Cbor, GetValue) { - EXPECT_EQ(5, Decode(parse_hex("05")).getValue()); + EXPECT_EQ(5ul, Decode(parse_hex("05")).getValue()); } TEST(Cbor, GetValueInvalid) { @@ -248,13 +257,13 @@ TEST(Cbor, GetStringInvalidTooShort) { TEST(Cbor, ArrayEmpty) { Data cbor = Encode::array({}).encoded(); - + EXPECT_EQ("80", hex(cbor)); EXPECT_TRUE(Decode(cbor).isValid()); EXPECT_EQ("[]", Decode(cbor).dumpToString()); Decode decode(cbor); - EXPECT_EQ(0, decode.getArrayElements().size()); + EXPECT_EQ(0ul, decode.getArrayElements().size()); } TEST(Cbor, Array3) { @@ -263,16 +272,16 @@ TEST(Cbor, Array3) { Encode::uint(2), Encode::uint(3), }).encoded(); - + EXPECT_EQ("83010203", hex(cbor)); EXPECT_TRUE(Decode(cbor).isValid()); EXPECT_EQ("[1, 2, 3]", Decode(cbor).dumpToString()); Decode decode(cbor); - EXPECT_EQ(3, decode.getArrayElements().size()); - EXPECT_EQ(1, decode.getArrayElements()[0].getValue()); - EXPECT_EQ(2, decode.getArrayElements()[1].getValue()); - EXPECT_EQ(3, decode.getArrayElements()[2].getValue()); + EXPECT_EQ(3ul, decode.getArrayElements().size()); + EXPECT_EQ(1ul, decode.getArrayElements()[0].getValue()); + EXPECT_EQ(2ul, decode.getArrayElements()[1].getValue()); + EXPECT_EQ(3ul, decode.getArrayElements()[2].getValue()); } TEST(Cbor, ArrayNested) { @@ -287,21 +296,20 @@ TEST(Cbor, ArrayNested) { Encode::uint(5), }), }).encoded(); - + EXPECT_EQ("8301820203820405", hex(cbor)); EXPECT_TRUE(Decode(cbor).isValid()); - EXPECT_EQ("[1, [2, 3], [4, 5]]", - Decode(cbor).dumpToString()); + EXPECT_EQ("[1, [2, 3], [4, 5]]", Decode(cbor).dumpToString()); Decode decode(cbor); - EXPECT_EQ(3, decode.getArrayElements().size()); - EXPECT_EQ(1, decode.getArrayElements()[0].getValue()); - EXPECT_EQ(2, decode.getArrayElements()[1].getArrayElements().size()); - EXPECT_EQ(2, decode.getArrayElements()[1].getArrayElements()[0].getValue()); - EXPECT_EQ(3, decode.getArrayElements()[1].getArrayElements()[1].getValue()); - EXPECT_EQ(2, decode.getArrayElements()[2].getArrayElements().size()); - EXPECT_EQ(4, decode.getArrayElements()[2].getArrayElements()[0].getValue()); - EXPECT_EQ(5, decode.getArrayElements()[2].getArrayElements()[1].getValue()); + EXPECT_EQ(3ul, decode.getArrayElements().size()); + EXPECT_EQ(1ul, decode.getArrayElements()[0].getValue()); + EXPECT_EQ(2ul, decode.getArrayElements()[1].getArrayElements().size()); + EXPECT_EQ(2ul, decode.getArrayElements()[1].getArrayElements()[0].getValue()); + EXPECT_EQ(3ul, decode.getArrayElements()[1].getArrayElements()[1].getValue()); + EXPECT_EQ(2ul, decode.getArrayElements()[2].getArrayElements().size()); + EXPECT_EQ(4ul, decode.getArrayElements()[2].getArrayElements()[0].getValue()); + EXPECT_EQ(5ul, decode.getArrayElements()[2].getArrayElements()[1].getValue()); } TEST(Cbor, Array25) { @@ -310,28 +318,28 @@ TEST(Cbor, Array25) { elem.push_back(Encode::uint(i)); } Data cbor = Encode::array(elem).encoded(); - + EXPECT_EQ("98190102030405060708090a0b0c0d0e0f101112131415161718181819", hex(cbor)); EXPECT_TRUE(Decode(cbor).isValid()); EXPECT_EQ("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]", - Decode(cbor).dumpToString()); + Decode(cbor).dumpToString()); Decode decode(cbor); - EXPECT_EQ(25, decode.getArrayElements().size()); - for (int i = 1; i <= 25; ++i) { + EXPECT_EQ(25ul, decode.getArrayElements().size()); + for (auto i = 1ul; i <= 25; ++i) { EXPECT_EQ(i, decode.getArrayElements()[i - 1].getValue()); } } TEST(Cbor, MapEmpty) { Data cbor = Encode::map({}).encoded(); - + EXPECT_EQ("a0", hex(cbor)); EXPECT_TRUE(Decode(cbor).isValid()); EXPECT_EQ("{}", Decode(cbor).dumpToString()); Decode decode(cbor); - EXPECT_EQ(0, decode.getMapElements().size()); + EXPECT_EQ(0ul, decode.getMapElements().size()); } TEST(Cbor, Map2Num) { @@ -339,39 +347,39 @@ TEST(Cbor, Map2Num) { make_pair(Encode::uint(1), Encode::uint(2)), make_pair(Encode::uint(3), Encode::uint(4)), }).encoded(); - + EXPECT_EQ("a201020304", hex(cbor)); EXPECT_TRUE(Decode(cbor).isValid()); EXPECT_EQ("{1: 2, 3: 4}", Decode(cbor).dumpToString()); Decode decode(cbor); - EXPECT_EQ(2, decode.getMapElements().size()); - EXPECT_EQ(1, decode.getMapElements()[0].first.getValue()); - EXPECT_EQ(2, decode.getMapElements()[0].second.getValue()); + EXPECT_EQ(2ul, decode.getMapElements().size()); + EXPECT_EQ(1ul, decode.getMapElements()[0].first.getValue()); + EXPECT_EQ(2ul, decode.getMapElements()[0].second.getValue()); } TEST(Cbor, Map2WithArr) { Data cbor = Encode::map({ make_pair(Encode::string("a"), Encode::uint(1)), make_pair(Encode::string("b"), Encode::array({ - Encode::uint(2), + Encode::uint(2), Encode::uint(3), })), }).encoded(); - + EXPECT_EQ("a26161016162820203", hex(cbor)); EXPECT_TRUE(Decode(cbor).isValid()); - EXPECT_EQ("{\"a\": 1, \"b\": [2, 3]}", - Decode(cbor).dumpToString()); + EXPECT_EQ("{\"a\": 1, \"b\": [2, 3]}", + Decode(cbor).dumpToString()); Decode decode(cbor); - EXPECT_EQ(2, decode.getMapElements().size()); + EXPECT_EQ(2ul, decode.getMapElements().size()); EXPECT_EQ("a", decode.getMapElements()[0].first.getString()); - EXPECT_EQ(1, decode.getMapElements()[0].second.getValue()); + EXPECT_EQ(1ul, decode.getMapElements()[0].second.getValue()); EXPECT_EQ("b", decode.getMapElements()[1].first.getString()); - EXPECT_EQ(2, decode.getMapElements()[1].second.getArrayElements().size()); - EXPECT_EQ(2, decode.getMapElements()[1].second.getArrayElements()[0].getValue()); - EXPECT_EQ(3, decode.getMapElements()[1].second.getArrayElements()[1].getValue()); + EXPECT_EQ(2ul, decode.getMapElements()[1].second.getArrayElements().size()); + EXPECT_EQ(2ul, decode.getMapElements()[1].second.getArrayElements()[0].getValue()); + EXPECT_EQ(3ul, decode.getMapElements()[1].second.getArrayElements()[1].getValue()); } TEST(Cbor, MapNested) { @@ -380,16 +388,16 @@ TEST(Cbor, MapNested) { make_pair(Encode::string("b"), Encode::string("c")), })), }).encoded(); - + EXPECT_EQ("a16161a161626163", hex(cbor)); EXPECT_TRUE(Decode(cbor).isValid()); - EXPECT_EQ("{\"a\": {\"b\": \"c\"}}", - Decode(cbor).dumpToString()); + EXPECT_EQ("{\"a\": {\"b\": \"c\"}}", + Decode(cbor).dumpToString()); Decode decode(cbor); - EXPECT_EQ(1, decode.getMapElements().size()); + EXPECT_EQ(1ul, decode.getMapElements().size()); EXPECT_EQ("a", decode.getMapElements()[0].first.getString()); - EXPECT_EQ(1, decode.getMapElements()[0].second.getMapElements().size()); + EXPECT_EQ(1ul, decode.getMapElements()[0].second.getMapElements().size()); EXPECT_EQ("b", decode.getMapElements()[0].second.getMapElements()[0].first.getString()); EXPECT_EQ("c", decode.getMapElements()[0].second.getMapElements()[0].second.getString()); } @@ -397,9 +405,9 @@ TEST(Cbor, MapNested) { TEST(Cbor, MapIndef) { Decode cbor = Decode(parse_hex("bf01020304ff")); EXPECT_EQ("{_ 1: 2, 3: 4}", cbor.dumpToString()); - EXPECT_EQ(2, cbor.getMapElements().size()); - EXPECT_EQ(1, cbor.getMapElements()[0].first.getValue()); - EXPECT_EQ(2, cbor.getMapElements()[0].second.getValue()); + EXPECT_EQ(2ul, cbor.getMapElements().size()); + EXPECT_EQ(1ul, cbor.getMapElements()[0].first.getValue()); + EXPECT_EQ(2ul, cbor.getMapElements()[0].second.getValue()); } TEST(Cbor, MapIsValidInvalidTooShort) { @@ -435,20 +443,20 @@ TEST(Cbor, MapGetInvalidTooShort2) { TEST(Cbor, ArrayIndef) { Data cbor = Encode::indefArray() - .addIndefArrayElem(Encode::uint(1)) - .addIndefArrayElem(Encode::uint(2)) - .closeIndefArray() - .encoded(); - + .addIndefArrayElem(Encode::uint(1)) + .addIndefArrayElem(Encode::uint(2)) + .closeIndefArray() + .encoded(); + EXPECT_EQ("9f0102ff", hex(cbor)); EXPECT_TRUE(Decode(cbor).isValid()); EXPECT_EQ("[_ 1, 2]", - Decode(cbor).dumpToString()); + Decode(cbor).dumpToString()); Decode decode(cbor); - EXPECT_EQ(2, decode.getArrayElements().size()); - EXPECT_EQ(1, decode.getArrayElements()[0].getValue()); - EXPECT_EQ(2, decode.getArrayElements()[1].getValue()); + EXPECT_EQ(2ul, decode.getArrayElements().size()); + EXPECT_EQ(1ul, decode.getArrayElements()[0].getValue()); + EXPECT_EQ(2ul, decode.getArrayElements()[1].getValue()); EXPECT_EQ("[_ 1, 2]", Decode(parse_hex("9f0102ff")).dumpToString()); EXPECT_EQ("", Decode(parse_hex("ff")).dumpToString()); @@ -476,10 +484,10 @@ TEST(Cbor, ArrayInfefErrorCloseNostart) { TEST(Cbor, ArrayInfefErrorResultNoclose) { try { Data cbor = Encode::indefArray() - .addIndefArrayElem(Encode::uint(1)) - .addIndefArrayElem(Encode::uint(2)) - // close is missing, break command not written - .encoded(); + .addIndefArrayElem(Encode::uint(1)) + .addIndefArrayElem(Encode::uint(2)) + // close is missing, break command not written + .encoded(); } catch (exception& ex) { return; } @@ -511,3 +519,5 @@ TEST(Cbor, GetTagElementNotTag) { } FAIL() << "Expected exception"; } +// clang-format on +} // namespace TW::Cbor::tests diff --git a/tests/common/CoinAddressDerivationTests.cpp b/tests/common/CoinAddressDerivationTests.cpp new file mode 100644 index 00000000000..5bd9acd5d63 --- /dev/null +++ b/tests/common/CoinAddressDerivationTests.cpp @@ -0,0 +1,292 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Coin.h" +#include "HexCoding.h" + +#include + +#include +#include + +namespace TW { + +TEST(Coin, DeriveAddress) { + auto dummyKeyData = parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646"); + const auto privateKey = PrivateKey(dummyKeyData); + const auto privateKeyExt = PrivateKey(dummyKeyData, dummyKeyData, dummyKeyData, dummyKeyData, dummyKeyData, dummyKeyData); + + const auto coins = TW::getCoinTypes(); + for (auto& c : coins) { + std::string address; + switch (c) { + default: + address = TW::deriveAddress(c, privateKey); + break; + + case TWCoinTypeCardano: + case TWCoinTypeNEO: + address = TW::deriveAddress(c, privateKeyExt); + break; + } + + switch (c) { + // Ethereum and ... + case TWCoinTypeEthereum: + // ... clones: + case TWCoinTypeArbitrum: + case TWCoinTypeAurora: + case TWCoinTypeAvalancheCChain: + case TWCoinTypeBoba: + case TWCoinTypeCallisto: + case TWCoinTypeCelo: + case TWCoinTypeCronosChain: + case TWCoinTypeECOChain: + case TWCoinTypeEthereumClassic: + case TWCoinTypeEvmos: + case TWCoinTypeFantom: + case TWCoinTypeGoChain: + case TWCoinTypeKavaEvm: + case TWCoinTypeKlaytn: + case TWCoinTypeKuCoinCommunityChain: + case TWCoinTypeMeter: + case TWCoinTypeMetis: + case TWCoinTypeMoonbeam: + case TWCoinTypeMoonriver: + case TWCoinTypeOptimism: + case TWCoinTypeZksync: + case TWCoinTypeOKXChain: + case TWCoinTypePOANetwork: + case TWCoinTypePolygon: + case TWCoinTypeSmartBitcoinCash: + case TWCoinTypeSmartChain: + case TWCoinTypeSmartChainLegacy: + case TWCoinTypeTheta: + case TWCoinTypeThunderToken: + case TWCoinTypeTomoChain: + case TWCoinTypeVeChain: + case TWCoinTypeWanchain: + case TWCoinTypeXDai: + EXPECT_EQ(address, "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); + break; + + case TWCoinTypeKin: + case TWCoinTypeStellar: + EXPECT_EQ(address, "GDXJHJHWN6GRNOAZXON6XH74ZX6NYFAS5B7642RSJQVJTIPA4ZYUQLEB"); + break; + + case TWCoinTypeNEO: + case TWCoinTypeOntology: + EXPECT_EQ(address, "AeicEjZyiXKgUeSBbYQHxsU1X3V5Buori5"); + break; + + case TWCoinTypeTerra: + case TWCoinTypeTerraV2: + EXPECT_EQ(address, "terra1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0ll9rwp"); + break; + + case TWCoinTypeZcash: + case TWCoinTypeZelcash: + EXPECT_EQ(address, "t1b9xfAk3kZp5Qk3rinDPq7zzLkJGHTChDS"); + break; + + case TWCoinTypeAeternity: + EXPECT_EQ(address, "ak_2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5FeVfcw"); + break; + case TWCoinTypeAion: + EXPECT_EQ(address, "0xa0010b0ea04ba4d76ca6e5e9900bacf19bc4402eaec7e36ea7ddd8eed48f60f3"); + break; + case TWCoinTypeAlgorand: + EXPECT_EQ(address, "52J2J5TPRULLQGN3TPVZ77GN7TOBIEXIP7XGUMSMFKM2DYHGOFEOGBP2T4"); + break; + case TWCoinTypeBandChain: + EXPECT_EQ(address, "band1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0q5lp5f"); + break; + case TWCoinTypeBinance: + EXPECT_EQ(address, "bnb1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0mlq0d0"); + break; + case TWCoinTypeBitcoin: + EXPECT_EQ(address, "bc1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z00ppggv"); + break; + case TWCoinTypeBitcoinCash: + EXPECT_EQ(address, "bitcoincash:qz7eyzytkl5z6cg6nw20hd62pyyp22mcfuardfd2vn"); + break; + case TWCoinTypeBitcoinGold: + EXPECT_EQ(address, "btg1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z0eg8day"); + break; + case TWCoinTypeBluzelle: + EXPECT_EQ(address, "bluzelle1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0vrup2s"); + break; + case TWCoinTypeCardano: + EXPECT_EQ(address, "addr1qxzk4wqhh5qmzas4e26aghcvkz8feju6sa43nghfj5xxsly9d2up00gpk9mptj44630sevywnn9e4pmtrx3wn9gvdp7qjhvjl4"); + break; + case TWCoinTypeCosmos: + EXPECT_EQ(address, "cosmos1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0emlrvp"); + break; + case TWCoinTypeCryptoOrg: + EXPECT_EQ(address, "cro1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0pqh6ss"); + break; + case TWCoinTypeDash: + EXPECT_EQ(address, "XsyCV5yojxF4y3bYeEiVYqarvRgsWFELZL"); + break; + case TWCoinTypeDecred: + EXPECT_EQ(address, "Dsp4u8xxTHSZU2ELWTQLQP77xJhgeWrTsGK"); + break; + case TWCoinTypeDigiByte: + EXPECT_EQ(address, "dgb1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z0c69ssz"); + break; + case TWCoinTypeDogecoin: + EXPECT_EQ(address, "DNRTC6GZ5evmM7BZWwPqF54fyDqUqULMyu"); + break; + case TWCoinTypeECash: + EXPECT_EQ(address, "ecash:qz7eyzytkl5z6cg6nw20hd62pyyp22mcfuywezks2y"); + break; + case TWCoinTypeEOS: + EXPECT_EQ(address, "EOS5TrYnZP1RkDSUMzBY4GanCy6AP68kCMdkAb5EACkAwkdgRLShz"); + break; + case TWCoinTypeElrond: + EXPECT_EQ(address, "erd1a6f6fan035ttsxdmn04ellxdlnwpgyhg0lhx5vjv92v6rc8xw9yq83344f"); + break; + case TWCoinTypeEverscale: + EXPECT_EQ(address, "0:ef64d51f95ef17973b737277cfecbd2a8d551141be2f58f5fb362575fc3eb5b0"); + break; + case TWCoinTypeFIO: + EXPECT_EQ(address, "FIO5TrYnZP1RkDSUMzBY4GanCy6AP68kCMdkAb5EACkAwkdgRLShz"); + break; + case TWCoinTypeFilecoin: + EXPECT_EQ(address, "f1qsx7qwiojh5duxbxhbqgnlyx5hmpcf7mcz5oxsy"); + break; + case TWCoinTypeFiro: + EXPECT_EQ(address, "aHzpPjmY132KseS4nkiQTbDahTEXqesY89"); + break; + case TWCoinTypeGroestlcoin: + EXPECT_EQ(address, "grs1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z0jsaf3d"); + break; + case TWCoinTypeHarmony: + EXPECT_EQ(address, "one1nk9x9ajk4rgkzhqjjn7hr6w0k0jg2kj0nmx3dt"); + break; + case TWCoinTypeICON: + EXPECT_EQ(address, "hx4728fc65c31728f0d3538b8783b5394b31a136b9"); + break; + case TWCoinTypeIoTeX: + EXPECT_EQ(address, "io1nk9x9ajk4rgkzhqjjn7hr6w0k0jg2kj0zgdt6h"); + break; + case TWCoinTypeKava: + EXPECT_EQ(address, "kava1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z09wt76x"); + break; + case TWCoinTypeKusama: + EXPECT_EQ(address, "Hy8mqcexg5FMwMYnQvzrUvD723qMxDjMRU9HdNCnTsMAypY"); + break; + case TWCoinTypeLitecoin: + EXPECT_EQ(address, "ltc1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z0tamvsu"); + break; + case TWCoinTypeMonacoin: + EXPECT_EQ(address, "MRBWtGEKHGCHhmyJ1L4CwaWQZJzM5DnVcs"); + break; + case TWCoinTypeNEAR: + EXPECT_EQ(address, "ee93a4f66f8d16b819bb9beb9ffccdfcdc1412e87fee6a324c2a99a1e0e67148"); + break; + case TWCoinTypeNULS: + EXPECT_EQ(address, "NULSd6HgfXT3m5JBGxeCZXHRQbb82FKgZGT8o"); + break; + case TWCoinTypeNano: + EXPECT_EQ(address, "nano_1qepdf4k95dhb5gsmhmq3iddqsxiafwkihunm7irn48jdiwdtnn6pe93k3f6"); + break; + case TWCoinTypeNativeEvmos: + EXPECT_EQ(address, "evmos1nk9x9ajk4rgkzhqjjn7hr6w0k0jg2kj07me7uu"); + break; + case TWCoinTypeNebulas: + EXPECT_EQ(address, "n1XTciu9ZRYt3ni7SxNBmivk9Y6XpP6VrhT"); + break; + case TWCoinTypeNimiq: + EXPECT_EQ(address, "NQ74 D40G N3M0 9EJD ET56 UPLR 02VC X6DU 8G1E"); + break; + case TWCoinTypeOasis: + EXPECT_EQ(address, "oasis1qzw4h3wmyjtrttduqqrs8udggyy2emwdzqmuzwg4"); + break; + case TWCoinTypeOsmosis: + EXPECT_EQ(address, "osmo1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z03qvn6n"); + break; + case TWCoinTypePolkadot: + EXPECT_EQ(address, "16PpFrXrC6Ko3pYcyMAx6gPMp3mFFaxgyYMt4G5brkgNcSz8"); + break; + case TWCoinTypeQtum: + EXPECT_EQ(address, "QdtLm8ccxhuJFF5zCgikpaghbM3thdaGsW"); + break; + case TWCoinTypeRavencoin: + EXPECT_EQ(address, "RSZYjMDCP4q3t7NAFXPPnqEGrMZn971pdB"); + break; + case TWCoinTypeRonin: + EXPECT_EQ(address, "ronin:9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); + break; + case TWCoinTypeSolana: + EXPECT_EQ(address, "H4JcMPicKkHcxxDjkyyrLoQj7Kcibd9t815ak4UvTr9M"); + break; + case TWCoinTypeTHORChain: + EXPECT_EQ(address, "thor1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0luxce7"); + break; + case TWCoinTypeTezos: + EXPECT_EQ(address, "tz1gcEWswVU6dxfNQWbhTgaZrUrNUFwrsT4z"); + break; + case TWCoinTypeTron: + EXPECT_EQ(address, "TQLCsShbQNXMTVCjprY64qZmEA4rBarpQp"); + break; + case TWCoinTypeViacoin: + EXPECT_EQ(address, "via1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z09y9mn2"); + break; + case TWCoinTypeWaves: + EXPECT_EQ(address, "3P2C786D6mBuvyf4WYr6K6Vch5uhi97nBHG"); + break; + case TWCoinTypeXRP: + EXPECT_EQ(address, "rJHMeqKu8Ep7Fazx8MQG6JunaafBXz93YQ"); + break; + case TWCoinTypeZilliqa: + EXPECT_EQ(address, "zil1j2cvtd7j9n7fnxfv2r3neucjw8tp4xz9sp07v4"); + break; + case TWCoinTypeNervos: + EXPECT_EQ(address, "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqtsqfsf77ae0wn5a7795hs2ydv83g6hl4qleywxw"); + break; + case TWCoinTypeAptos: + EXPECT_EQ(address, "0xce2fd04ac9efa74f17595e5785e847a2399d7e637f5e8179244f76191f653276"); + break; + // no default branch here, intentionally, to better notice any missing coins + } + } +} + +int countThreadReady = 0; +std::mutex countThreadReadyMutex; + +void useCoinFromThread() { + const int tryCount = 20; + for (int i = 0; i < tryCount; ++i) { + // perform some operations + TW::validateAddress(TWCoinTypeZilliqa, "zil1j8xae6lggm8y63m3y2r7aefu797ze7mhzulnqg"); + TW::validateAddress(TWCoinTypeEthereum, "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); + const auto coinTypes = TW::getCoinTypes(); + } + countThreadReadyMutex.lock(); + ++countThreadReady; + countThreadReadyMutex.unlock(); +} + +TEST(Coin, InitMultithread) { + const int numThread = 20; + countThreadReady = 0; + std::thread thread[numThread]; + // execute in threads + for (int i = 0; i < numThread; ++i) { + thread[i] = std::thread(useCoinFromThread); + } + // wait for completion + for (int i = 0; i < numThread; ++i) { + thread[i].join(); + } + // check that all completed OK + ASSERT_EQ(countThreadReady, numThread); +} + +} // namespace TW diff --git a/tests/CoinAddressValidationTests.cpp b/tests/common/CoinAddressValidationTests.cpp similarity index 93% rename from tests/CoinAddressValidationTests.cpp rename to tests/common/CoinAddressValidationTests.cpp index 18f31f6ffb2..a1816d6e911 100644 --- a/tests/CoinAddressValidationTests.cpp +++ b/tests/common/CoinAddressValidationTests.cpp @@ -203,8 +203,8 @@ TEST(Coin, validateAddressTron) { } TEST(Coin, validateAddressZcoin) { - EXPECT_TRUE(validateAddress(TWCoinTypeZcoin, "aHzpPjmY132KseS4nkiQTbDahTEXqesY89")); - EXPECT_FALSE(validateAddress(TWCoinTypeZcoin, "xHzpPjmY132KseS4nkiQTbDahTEXqesY89")); + EXPECT_TRUE(validateAddress(TWCoinTypeFiro, "aHzpPjmY132KseS4nkiQTbDahTEXqesY89")); + EXPECT_FALSE(validateAddress(TWCoinTypeFiro, "xHzpPjmY132KseS4nkiQTbDahTEXqesY89")); } TEST(Coin, validateAddressLitecoin) { @@ -324,7 +324,7 @@ TEST(Coin, ValidateAddressBluzelle) { TEST(Coin, ValidateAddresCardano) { // valid V3 address - EXPECT_TRUE(validateAddress(TWCoinTypeCardano, "addr1s3hdtrqgs47l7ue5srga8wmk9dzw279x9e7lxadalt6z0fk64nnn2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59j5lempe")); + EXPECT_TRUE(validateAddress(TWCoinTypeCardano, "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23")); // valid V2 address EXPECT_TRUE(validateAddress(TWCoinTypeCardano, "Ae2tdPwUPEZ6RUCnjGHFqi59k5WZLiv3HoCCNGCW8SYc5H9srdTzn1bec4W")); // valid V1 address @@ -392,4 +392,21 @@ TEST(Coin, ValidateAddresTHORChain) { EXPECT_FALSE(validateAddress(TWCoinTypeTHORChain, "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2s")); } +TEST(Coin, ValidateAddressECash) { + EXPECT_TRUE(validateAddress(TWCoinTypeECash, "ecash:qruxj7zq6yzpdx8dld0e9hfvt7u47zrw9gswqul42q")); + EXPECT_TRUE(validateAddress(TWCoinTypeECash, "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2")); + EXPECT_TRUE(validateAddress(TWCoinTypeECash, "qq07l6rr5lsdm3m80qxw80ku2ex0tj76vvft48qjaw")); + EXPECT_TRUE(validateAddress(TWCoinTypeECash, "qqslmu0jxk4st3ldjyuazfpf5thd6vlgfu395x2elz")); + + ASSERT_EQ(normalizeAddress(TWCoinTypeECash, "qqslmu0jxk4st3ldjyuazfpf5thd6vlgfu395x2elz"), "ecash:qqslmu0jxk4st3ldjyuazfpf5thd6vlgfu395x2elz"); + ASSERT_EQ(normalizeAddress(TWCoinTypeECash, "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2"), "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2"); +} + +TEST(Coin, ValidateAddressEverscale) { + EXPECT_TRUE(validateAddress(TWCoinTypeEverscale, "0:83a0352908060fa87839195d8a763a8d9ab28f8fa41468832b398a719cc6469a")); + EXPECT_FALSE(validateAddress(TWCoinTypeEverscale, "83a0352908060fa87839195d8a763a8d9ab28f8fa41468832b398a719cc6469a")); + + ASSERT_EQ(normalizeAddress(TWCoinTypeEverscale, "0:83A0352908060FA87839195D8A763A8D9AB28F8FA41468832B398A719CC6469A"), "0:83a0352908060fa87839195d8a763a8d9ab28f8fa41468832b398a719cc6469a"); +} + } // namespace TW diff --git a/tests/common/DataTests.cpp b/tests/common/DataTests.cpp new file mode 100644 index 00000000000..dec40018c9c --- /dev/null +++ b/tests/common/DataTests.cpp @@ -0,0 +1,96 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Data.h" +#include "HexCoding.h" + +#include + +using namespace std; +using namespace TW; + +TEST(DataTests, fromVector) { + const Data data = {1, 2, 3}; + EXPECT_EQ(data.size(), 3ul); + EXPECT_EQ(data[1], 2); + EXPECT_EQ(hex(data), "010203"); +} + +TEST(DataTests, fromHex) { + const Data data = parse_hex("01020304"); + EXPECT_EQ(data.size(), 4ul); + EXPECT_EQ(hex(data), "01020304"); +} + +TEST(DataTests, fromString) { + const Data data = TW::data(std::string("ABC")); + EXPECT_EQ(data.size(), 3ul); + EXPECT_EQ(hex(data), "414243"); +} + +TEST(DataTests, fromBytes) { + const std::vector vec = {1, 2, 3}; + const Data data = TW::data(vec.data(), vec.size()); + EXPECT_EQ(data.size(), 3ul); + EXPECT_EQ(hex(data), "010203"); +} + +TEST(DataTests, padLeft) { + Data data = parse_hex("01020304"); + pad_left(data, 10); + EXPECT_EQ(data.size(), 10ul); + EXPECT_EQ(hex(data), "00000000000001020304"); +} + +TEST(DataTests, append) { + Data data1 = parse_hex("01020304"); + const Data data2 = parse_hex("aeaf"); + append(data1, data2); + EXPECT_EQ(data1.size(), 6ul); + EXPECT_EQ(hex(data1), "01020304aeaf"); +} + +TEST(DataTests, appendByte) { + Data data1 = parse_hex("01020304"); + append(data1, 5); + EXPECT_EQ(data1.size(), 5ul); + EXPECT_EQ(hex(data1), "0102030405"); +} + +TEST(DataTests, subData) { + const Data data = parse_hex("0102030405060708090a"); + EXPECT_EQ(data.size(), 10ul); + + EXPECT_EQ(hex(subData(data, 2, 3)), "030405"); + EXPECT_EQ(hex(subData(data, 0, 10)), "0102030405060708090a"); + EXPECT_EQ(hex(subData(data, 3, 1)), "04"); + EXPECT_EQ(hex(subData(data, 3, 0)), ""); + EXPECT_EQ(hex(subData(data, 200, 3)), ""); // index too big + EXPECT_EQ(hex(subData(data, 2, 300)), "030405060708090a"); // length too big + EXPECT_EQ(hex(subData(data, 200, 300)), ""); // index & length too big + + EXPECT_EQ(hex(subData(data, 3)), "0405060708090a"); + EXPECT_EQ(hex(subData(data, 0)), "0102030405060708090a"); + EXPECT_EQ(hex(subData(data, 200)), ""); // index too big +} + +TEST(DataTests, hasPrefix) { + const Data data = parse_hex("0102030405060708090a"); + + const Data prefix11 = parse_hex("010203"); + EXPECT_TRUE(has_prefix(data, prefix11)); + const Data prefix12 = parse_hex("01"); + EXPECT_TRUE(has_prefix(data, prefix12)); + const Data prefix13 = parse_hex("0102030405060708090a"); + EXPECT_TRUE(has_prefix(data, prefix13)); + + const Data prefix21 = parse_hex("020304"); + EXPECT_FALSE(has_prefix(data, prefix21)); + const Data prefix22 = parse_hex("02"); + EXPECT_FALSE(has_prefix(data, prefix22)); + const Data prefix23 = parse_hex("bb"); + EXPECT_FALSE(has_prefix(data, prefix23)); +} diff --git a/tests/EncryptTests.cpp b/tests/common/EncryptTests.cpp similarity index 81% rename from tests/EncryptTests.cpp rename to tests/common/EncryptTests.cpp index 9870f4e2937..74fe4a1f13a 100644 --- a/tests/EncryptTests.cpp +++ b/tests/common/EncryptTests.cpp @@ -12,41 +12,42 @@ #include -using namespace TW::Encrypt; using namespace TW; -const Data key = parse_hex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); +namespace TW::Encrypt::test { + +const Data gKey = parse_hex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); inline void assertHexEqual(const Data& data, const char* expected) { EXPECT_EQ(hex(data), expected); } TEST(Encrypt, paddingSize) { - EXPECT_EQ(paddingSize(0, 16, TWAESPaddingModeZero), 0); - EXPECT_EQ(paddingSize(1, 16, TWAESPaddingModeZero), 15); - EXPECT_EQ(paddingSize(8, 16, TWAESPaddingModeZero), 8); - EXPECT_EQ(paddingSize(15, 16, TWAESPaddingModeZero), 1); - EXPECT_EQ(paddingSize(16, 16, TWAESPaddingModeZero), 0); - EXPECT_EQ(paddingSize(17, 16, TWAESPaddingModeZero), 15); - EXPECT_EQ(paddingSize(24, 16, TWAESPaddingModeZero), 8); - EXPECT_EQ(paddingSize(31, 16, TWAESPaddingModeZero), 1); - EXPECT_EQ(paddingSize(32, 16, TWAESPaddingModeZero), 0); - EXPECT_EQ(paddingSize(0, 16, TWAESPaddingModePKCS7), 16); - EXPECT_EQ(paddingSize(1, 16, TWAESPaddingModePKCS7), 15); - EXPECT_EQ(paddingSize(8, 16, TWAESPaddingModePKCS7), 8); - EXPECT_EQ(paddingSize(15, 16, TWAESPaddingModePKCS7), 1); - EXPECT_EQ(paddingSize(16, 16, TWAESPaddingModePKCS7), 16); - EXPECT_EQ(paddingSize(17, 16, TWAESPaddingModePKCS7), 15); - EXPECT_EQ(paddingSize(24, 16, TWAESPaddingModePKCS7), 8); - EXPECT_EQ(paddingSize(31, 16, TWAESPaddingModePKCS7), 1); - EXPECT_EQ(paddingSize(32, 16, TWAESPaddingModePKCS7), 16); + EXPECT_EQ(paddingSize(0, 16, TWAESPaddingModeZero), 0ul); + EXPECT_EQ(paddingSize(1, 16, TWAESPaddingModeZero), 15ul); + EXPECT_EQ(paddingSize(8, 16, TWAESPaddingModeZero), 8ul); + EXPECT_EQ(paddingSize(15, 16, TWAESPaddingModeZero), 1ul); + EXPECT_EQ(paddingSize(16, 16, TWAESPaddingModeZero), 0ul); + EXPECT_EQ(paddingSize(17, 16, TWAESPaddingModeZero), 15ul); + EXPECT_EQ(paddingSize(24, 16, TWAESPaddingModeZero), 8ul); + EXPECT_EQ(paddingSize(31, 16, TWAESPaddingModeZero), 1ul); + EXPECT_EQ(paddingSize(32, 16, TWAESPaddingModeZero), 0ul); + EXPECT_EQ(paddingSize(0, 16, TWAESPaddingModePKCS7), 16ul); + EXPECT_EQ(paddingSize(1, 16, TWAESPaddingModePKCS7), 15ul); + EXPECT_EQ(paddingSize(8, 16, TWAESPaddingModePKCS7), 8ul); + EXPECT_EQ(paddingSize(15, 16, TWAESPaddingModePKCS7), 1ul); + EXPECT_EQ(paddingSize(16, 16, TWAESPaddingModePKCS7), 16ul); + EXPECT_EQ(paddingSize(17, 16, TWAESPaddingModePKCS7), 15ul); + EXPECT_EQ(paddingSize(24, 16, TWAESPaddingModePKCS7), 8ul); + EXPECT_EQ(paddingSize(31, 16, TWAESPaddingModePKCS7), 1ul); + EXPECT_EQ(paddingSize(32, 16, TWAESPaddingModePKCS7), 16ul); } TEST(Encrypt, AESCBCEncrypt) { auto iv = parse_hex("000102030405060708090A0B0C0D0E0F"); auto data = parse_hex("6bc1bee22e409f96e93d7e117393172a"); - auto encryptResult = AESCBCEncrypt(key, data, iv); + auto encryptResult = AESCBCEncrypt(gKey, data, iv); assertHexEqual(encryptResult, "f58c4c04d6e5f1ba779eabfb5f7bfbd6"); } @@ -70,7 +71,7 @@ TEST(Encrypt, AESCBCDecrypt) { auto iv = parse_hex("000102030405060708090A0B0C0D0E0F"); auto cipher = parse_hex("f58c4c04d6e5f1ba779eabfb5f7bfbd6"); - auto decryptResult = AESCBCDecrypt(key, cipher, iv); + auto decryptResult = AESCBCDecrypt(gKey, cipher, iv); assertHexEqual(decryptResult, "6bc1bee22e409f96e93d7e117393172a"); } @@ -98,7 +99,7 @@ TEST(Encrypt, AESCTREncrypt) { auto iv = parse_hex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"); auto data = parse_hex("6bc1bee22e409f96e93d7e117393172a"); - auto encryptResult = AESCTREncrypt(key, data, iv); + auto encryptResult = AESCTREncrypt(gKey, data, iv); assertHexEqual(encryptResult, "601ec313775789a5b7a7f504bbf3d228"); } @@ -106,7 +107,7 @@ TEST(Encrypt, AESCTRDecrypt) { auto iv = parse_hex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"); auto cipher = parse_hex("601ec313775789a5b7a7f504bbf3d228"); - auto decryptResult = AESCTRDecrypt(key, cipher, iv); + auto decryptResult = AESCTRDecrypt(gKey, cipher, iv); assertHexEqual(decryptResult, "6bc1bee22e409f96e93d7e117393172a"); } @@ -200,3 +201,5 @@ TEST(Encrypt, AESCTRDecryptInvalidKeySize) { } ADD_FAILURE() << "Missed expected exeption"; } + +} // namespace TW::Encrypt::tests diff --git a/tests/common/HDWallet/HDWalletInternalTests.cpp b/tests/common/HDWallet/HDWalletInternalTests.cpp new file mode 100644 index 00000000000..1f27ad22cca --- /dev/null +++ b/tests/common/HDWallet/HDWalletInternalTests.cpp @@ -0,0 +1,156 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "HDWallet.h" +#include "Data.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include +#include +#include "TestUtilities.h" + +#include +#include +#include + +namespace TW::HDWalletInternalTests { + +const auto mnemonic1 = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; + +std::string nodeToHexString(const HDNode& node) { + std::string s; + s += std::to_string(node.depth); + s += "-" + std::to_string(node.child_num); + s += "--" + hex(data(node.chain_code, 32)); + s += "--" + hex(data(node.private_key, 32)); + s += "--" + hex(data(node.private_key_extension, 32)); + s += "--" + hex(data(node.public_key, 33)); + return s; +} + +Data publicKeyFromPrivateKey(const Data& privateKey) { + return PrivateKey(privateKey).getPublicKey(TWPublicKeyTypeSECP256k1).bytes; +} + +TEST(HDWalletInternal, SquareDerivationRoutes) { + /* + Test 'square' derivation routes, result should be the same. + Performing private derivation, then taking the public key yields the same as + taking the public key first, and performing public derivation. + This makes XPUB schemes possible. + + priv_node --priv_deriv.--> priv_child_node + + | | + get_pub get_pub + | | + v v + + pub_node ---pub_deriv.---> pub_key + + */ + + HDWallet wallet = HDWallet(mnemonic1, ""); + const auto derivationPath = DerivationPath("m/84'/0'/0'/1"); + const auto dpLastIndex = 2; + const auto ExpectedFinalPublicKey = "02e0b4765e9012bfbbfe8c714f99beb681acc0a92bdb5acbf2f362c0aff986ad49"; + + // getMasterNode + auto masterNode = HDNode(); + hdnode_from_seed(wallet.getSeed().data(), HDWallet::seedSize, SECP256K1_NAME, &masterNode); + + auto node0 = masterNode; + // getNode + for (auto& index : derivationPath.indices) { + hdnode_private_ckd(&node0, index.derivationIndex()); + } + EXPECT_EQ(nodeToHexString(node0), "4-1--8423e869d887b6b0e7ca5f3bbed6e69234fb5d51aa02e9e8069fbffb2dc3c095--55f85b343359ec33aa3519a40673773d1f52677a044c7185529ce8f5fdb70625--0000000000000000000000000000000000000000000000000000000000000000--000000000000000000000000000000000000000000000000000000000000000000"); + + { + // Route 1 step 1: private node derivation + auto node11 = node0; + EXPECT_EQ(hdnode_private_ckd(&node11, dpLastIndex), 1); + EXPECT_EQ(nodeToHexString(node11), "5-2--53aaec7a112524bc3c8c91b278ccb0cf7e322077dac6a832563eb33149bb0051--512503395481ea0c26fe341bc342c29f4a706be003d12179ec6b65aa8a8c352d--0000000000000000000000000000000000000000000000000000000000000000--000000000000000000000000000000000000000000000000000000000000000000"); + + // Route 1 step 2: public key from private + const auto publicKey = publicKeyFromPrivateKey(data(node11.private_key, 32)); + EXPECT_EQ(hex(publicKey), ExpectedFinalPublicKey); + } + + { + // Route 2 step 1: public node from private (extended public key) + auto node21 = node0; + const auto pub21 = publicKeyFromPrivateKey(data(node21.private_key, 32)); + ::memcpy(node21.public_key, pub21.data(), 33); + EXPECT_EQ(nodeToHexString(node21), "4-1--8423e869d887b6b0e7ca5f3bbed6e69234fb5d51aa02e9e8069fbffb2dc3c095--55f85b343359ec33aa3519a40673773d1f52677a044c7185529ce8f5fdb70625--0000000000000000000000000000000000000000000000000000000000000000--026a940b5b683237037ecb230c402c5e351f38d41f00215e4d36006e9ff6b5cfba"); + + // Route 2 step 2: public node derivation + auto node22 = node21; + EXPECT_EQ(hdnode_public_ckd(&node22, dpLastIndex), 1); + EXPECT_EQ(nodeToHexString(node22), "5-2--53aaec7a112524bc3c8c91b278ccb0cf7e322077dac6a832563eb33149bb0051--0000000000000000000000000000000000000000000000000000000000000000--0000000000000000000000000000000000000000000000000000000000000000--02e0b4765e9012bfbbfe8c714f99beb681acc0a92bdb5acbf2f362c0aff986ad49"); + const auto publicKey = PublicKey(data(node22.public_key, 33), TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(publicKey.bytes), ExpectedFinalPublicKey); + } +} + +TEST(HDWalletInternal, PrivateAndPublicCkdDerivation) { + /* + + PrivateKey1 ----> PrivateKey2 + + | | + v v + + PublicKey1 ----> PublicKey2 + + */ + + const auto PrivateKey1 = "55f85b343359ec33aa3519a40673773d1f52677a044c7185529ce8f5fdb70625"; + const auto PrivateKey2 = "512503395481ea0c26fe341bc342c29f4a706be003d12179ec6b65aa8a8c352d"; + const auto PublicKey1 = "026a940b5b683237037ecb230c402c5e351f38d41f00215e4d36006e9ff6b5cfba"; + const auto PublicKey2 = "02e0b4765e9012bfbbfe8c714f99beb681acc0a92bdb5acbf2f362c0aff986ad49"; + const auto ChainCode0 = "8423e869d887b6b0e7ca5f3bbed6e69234fb5d51aa02e9e8069fbffb2dc3c095"; + const auto dpIndex = 2; + const auto curve = get_curve_by_name(SECP256K1_NAME); + + { // PrivateKey1 -> PublicKey1 + EXPECT_EQ(hex(publicKeyFromPrivateKey(parse_hex(PrivateKey1))), PublicKey1); + } + { // PrivateKey2 -> PublicKey2 + EXPECT_EQ(hex(publicKeyFromPrivateKey(parse_hex(PrivateKey2))), PublicKey2); + } + { // PrivateKey1 -> PrivateKey2 + auto node = HDNode(); + node.depth = 4; + node.child_num = 1; + node.curve = curve; + ::memcpy(node.chain_code, parse_hex(ChainCode0).data(), 32); + ::memcpy(node.private_key, parse_hex(PrivateKey1).data(), 32); + EXPECT_EQ(nodeToHexString(node), "4-1--8423e869d887b6b0e7ca5f3bbed6e69234fb5d51aa02e9e8069fbffb2dc3c095--55f85b343359ec33aa3519a40673773d1f52677a044c7185529ce8f5fdb70625--0000000000000000000000000000000000000000000000000000000000000000--000000000000000000000000000000000000000000000000000000000000000000"); + + EXPECT_EQ(hdnode_private_ckd(&node, dpIndex), 1); + + EXPECT_EQ(nodeToHexString(node), "5-2--53aaec7a112524bc3c8c91b278ccb0cf7e322077dac6a832563eb33149bb0051--512503395481ea0c26fe341bc342c29f4a706be003d12179ec6b65aa8a8c352d--0000000000000000000000000000000000000000000000000000000000000000--000000000000000000000000000000000000000000000000000000000000000000"); + EXPECT_EQ(hex(data(node.private_key, 32)), PrivateKey2); + } + { // PublicKey1 -> PublicKey2 + auto node = HDNode(); + node.depth = 4; + node.child_num = 1; + node.curve = curve; + ::memcpy(node.chain_code, parse_hex(ChainCode0).data(), 32); + ::memcpy(node.public_key, parse_hex(PublicKey1).data(), 33); + EXPECT_EQ(nodeToHexString(node), "4-1--8423e869d887b6b0e7ca5f3bbed6e69234fb5d51aa02e9e8069fbffb2dc3c095--0000000000000000000000000000000000000000000000000000000000000000--0000000000000000000000000000000000000000000000000000000000000000--026a940b5b683237037ecb230c402c5e351f38d41f00215e4d36006e9ff6b5cfba"); + + EXPECT_EQ(hdnode_public_ckd(&node, dpIndex), 1); + + EXPECT_EQ(nodeToHexString(node), "5-2--53aaec7a112524bc3c8c91b278ccb0cf7e322077dac6a832563eb33149bb0051--0000000000000000000000000000000000000000000000000000000000000000--0000000000000000000000000000000000000000000000000000000000000000--02e0b4765e9012bfbbfe8c714f99beb681acc0a92bdb5acbf2f362c0aff986ad49"); + EXPECT_EQ(hex(data(node.public_key, 33)), PublicKey2); + } +} + +} // namespace diff --git a/tests/HDWallet/HDWalletTests.cpp b/tests/common/HDWallet/HDWalletTests.cpp similarity index 58% rename from tests/HDWallet/HDWalletTests.cpp rename to tests/common/HDWallet/HDWalletTests.cpp index 1ad89cb705d..9bc90832bed 100644 --- a/tests/HDWallet/HDWalletTests.cpp +++ b/tests/common/HDWallet/HDWalletTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2021 Trust Wallet. +// 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 @@ -15,43 +15,43 @@ #include "Hash.h" #include "Base58.h" #include "Coin.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include extern std::string TESTS_ROOT; -namespace TW { +namespace TW::HDWalletTests { const auto mnemonic1 = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; -const auto passphrase = "passphrase"; +const auto gPassphrase = "passphrase"; TEST(HDWallet, generate) { { - HDWallet wallet = HDWallet(128, passphrase); + HDWallet wallet = HDWallet(128, gPassphrase); EXPECT_TRUE(Mnemonic::isValid(wallet.getMnemonic())); - EXPECT_EQ(wallet.getPassphrase(), passphrase); - EXPECT_EQ(wallet.getEntropy().size(), 16); + EXPECT_EQ(wallet.getPassphrase(), gPassphrase); + EXPECT_EQ(wallet.getEntropy().size(), 16ul); } { - HDWallet wallet = HDWallet(256, passphrase); + HDWallet wallet = HDWallet(256, gPassphrase); EXPECT_TRUE(Mnemonic::isValid(wallet.getMnemonic())); - EXPECT_EQ(wallet.getPassphrase(), passphrase); - EXPECT_EQ(wallet.getEntropy().size(), 32); + EXPECT_EQ(wallet.getPassphrase(), gPassphrase); + EXPECT_EQ(wallet.getEntropy().size(), 32ul); } } TEST(HDWallet, generateInvalid) { - EXPECT_EXCEPTION(HDWallet(64, passphrase), "Invalid strength"); - EXPECT_EXCEPTION(HDWallet(129, passphrase), "Invalid strength"); - EXPECT_EXCEPTION(HDWallet(512, passphrase), "Invalid strength"); + EXPECT_EXCEPTION(HDWallet(64, gPassphrase), "Invalid strength"); + EXPECT_EXCEPTION(HDWallet(129, gPassphrase), "Invalid strength"); + EXPECT_EXCEPTION(HDWallet(512, gPassphrase), "Invalid strength"); } TEST(HDWallet, createFromMnemonic) { { - HDWallet wallet = HDWallet(mnemonic1, passphrase); + HDWallet wallet = HDWallet(mnemonic1, gPassphrase); EXPECT_EQ(wallet.getMnemonic(), mnemonic1); - EXPECT_EQ(wallet.getPassphrase(), passphrase); + EXPECT_EQ(wallet.getPassphrase(), gPassphrase); EXPECT_EQ(hex(wallet.getEntropy()), "ba5821e8c356c05ba5f025d9532fe0f21f65d594"); EXPECT_EQ(hex(wallet.getSeed()), "143cd5fc27ae46eb423efebc41610473f5e24a80f2ca2e2fa7bf167e537f58f4c68310ae487fce82e25bad29bab2530cf77fd724a5ebfc05a45872773d7ee2d6"); } @@ -67,37 +67,37 @@ TEST(HDWallet, createFromMnemonic) { TEST(HDWallet, entropyLength_createFromMnemonic) { { // 12 words HDWallet wallet = HDWallet("oil oil oil oil oil oil oil oil oil oil oil oil", ""); - EXPECT_EQ(wallet.getEntropy().size(), 16); + EXPECT_EQ(wallet.getEntropy().size(), 16ul); EXPECT_EQ(hex(wallet.getEntropy()), "99d33a674ce99d33a674ce99d33a674c"); } { // 12 words, from https://github.com/trezor/python-mnemonic/blob/master/vectors.json HDWallet wallet = HDWallet("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", ""); - EXPECT_EQ(wallet.getEntropy().size(), 16); + EXPECT_EQ(wallet.getEntropy().size(), 16ul); EXPECT_EQ(hex(wallet.getEntropy()), "00000000000000000000000000000000"); } { // 15 words HDWallet wallet = HDWallet("history step cheap card humble screen raise seek robot slot coral roof spoil wreck caution", ""); - EXPECT_EQ(wallet.getEntropy().size(), 20); + EXPECT_EQ(wallet.getEntropy().size(), 20ul); EXPECT_EQ(hex(wallet.getEntropy()), "6c3aac9b9146ef832c4e18bb3980c0dddd25fc49"); } { // 18 words HDWallet wallet = HDWallet("caught hockey split gun symbol code payment copy broccoli silly shed secret stove tell citizen staff photo high", ""); - EXPECT_EQ(wallet.getEntropy().size(), 24); + EXPECT_EQ(wallet.getEntropy().size(), 24ul); EXPECT_EQ(hex(wallet.getEntropy()), "246d8f48b3fdc65a2869801c791715614d6bbd8a56a0a3ad"); } { // 21 words HDWallet wallet = HDWallet("diary shine country alpha bridge coast loan hungry hip media sell crucial swarm share gospel lake visa coin dizzy physical basket", ""); - EXPECT_EQ(wallet.getEntropy().size(), 28); + EXPECT_EQ(wallet.getEntropy().size(), 28ul); EXPECT_EQ(hex(wallet.getEntropy()), "3d58bcc40381bc59a0c37a6bf14f0d9a3db78a5933e5f4a5ad00d1f1"); } { // 24 words HDWallet wallet = HDWallet("poet spider smile swift roof pilot subject save hand diet ice universe over brown inspire ugly wide economy symbol shove episode patient plug swamp", ""); - EXPECT_EQ(wallet.getEntropy().size(), 32); + EXPECT_EQ(wallet.getEntropy().size(), 32ul); EXPECT_EQ(hex(wallet.getEntropy()), "a73a3732edebbb49f5fdfe68c7b5c0f6e9de3a1d5760faa8c771e384bf4229b6"); } { // 24 words, from https://github.com/trezor/python-mnemonic/blob/master/vectors.json HDWallet wallet = HDWallet("letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless", ""); - EXPECT_EQ(wallet.getEntropy().size(), 32); + EXPECT_EQ(wallet.getEntropy().size(), 32ul); EXPECT_EQ(hex(wallet.getEntropy()), "8080808080808080808080808080808080808080808080808080808080808080"); } } @@ -116,31 +116,31 @@ TEST(HDWallet, createFromSpanishMnemonic) { } TEST(HDWallet, createFromMnemonicInvalid) { - EXPECT_EXCEPTION(HDWallet("THIS IS AN INVALID MNEMONIC", passphrase), "Invalid mnemonic"); - EXPECT_EXCEPTION(HDWallet("", passphrase), "Invalid mnemonic"); + EXPECT_EXCEPTION(HDWallet("THIS IS AN INVALID MNEMONIC", gPassphrase), "Invalid mnemonic"); + EXPECT_EXCEPTION(HDWallet("", gPassphrase), "Invalid mnemonic"); - EXPECT_EXCEPTION(HDWallet("", passphrase, false), "Invalid mnemonic"); - HDWallet walletUnchecked = HDWallet("THIS IS AN INVALID MNEMONIC", passphrase, false); + EXPECT_EXCEPTION(HDWallet("", gPassphrase, false), "Invalid mnemonic"); + HDWallet walletUnchecked = HDWallet("THIS IS AN INVALID MNEMONIC", gPassphrase, false); } TEST(HDWallet, createFromEntropy) { { - HDWallet wallet = HDWallet(parse_hex("ba5821e8c356c05ba5f025d9532fe0f21f65d594"), passphrase); + HDWallet wallet = HDWallet(parse_hex("ba5821e8c356c05ba5f025d9532fe0f21f65d594"), gPassphrase); EXPECT_EQ(wallet.getMnemonic(), mnemonic1); } } TEST(HDWallet, createFromEntropyInvalid) { - EXPECT_EXCEPTION(HDWallet(parse_hex(""), passphrase), "Invalid mnemonic data"); - EXPECT_EXCEPTION(HDWallet(parse_hex("123456"), passphrase), "Invalid mnemonic data"); + EXPECT_EXCEPTION(HDWallet(parse_hex(""), gPassphrase), "Invalid mnemonic data"); + EXPECT_EXCEPTION(HDWallet(parse_hex("123456"), gPassphrase), "Invalid mnemonic data"); } TEST(HDWallet, recreateFromEntropy) { { - HDWallet wallet1 = HDWallet(mnemonic1, passphrase); + HDWallet wallet1 = HDWallet(mnemonic1, gPassphrase); EXPECT_EQ(wallet1.getMnemonic(), mnemonic1); EXPECT_EQ(hex(wallet1.getEntropy()), "ba5821e8c356c05ba5f025d9532fe0f21f65d594"); - HDWallet wallet2 = HDWallet(wallet1.getEntropy(), passphrase); + HDWallet wallet2 = HDWallet(wallet1.getEntropy(), gPassphrase); EXPECT_EQ(wallet2.getMnemonic(), wallet1.getMnemonic()); EXPECT_EQ(wallet2.getEntropy(), wallet1.getEntropy()); EXPECT_EQ(wallet2.getSeed(), wallet1.getSeed()); @@ -152,7 +152,7 @@ TEST(HDWallet, privateKeyFromXPRV) { auto privateKey = HDWallet::getPrivateKeyFromExtended(xprv, TWCoinTypeBitcoinCash, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeBitcoinCash), 0, 0, 3)); ASSERT_TRUE(privateKey); auto publicKey = privateKey->getPublicKey(TWPublicKeyTypeSECP256k1); - auto address = Bitcoin::CashAddress(publicKey); + auto address = Bitcoin::BitcoinCashAddress(publicKey); EXPECT_EQ(hex(publicKey.bytes), "025108168f7e5aad52f7381c18d8f880744dbee21dc02c15abe512da0b1cca7e2f"); EXPECT_EQ(address.string(), "bitcoincash:qp3y0dyg6ya8nt4n3algazn073egswkytqs00z7rz4"); @@ -216,7 +216,7 @@ TEST(HDWallet, privateKeyFromZprv) { const std::string zprv = "zprvAdzGEQ44z4WPLNCRpDaup2RumWxLGgR8PQ9UVsSmJigXsHVDaHK1b6qGM2u9PmxB2Gx264ctAz4yRoN3Xwf1HZmKcn6vmjqwsawF4WqQjfd"; auto privateKey = HDWallet::getPrivateKeyFromExtended(zprv, TWCoinTypeBitcoinCash, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeBitcoin), 0, 0, 5)); auto publicKey = privateKey->getPublicKey(TWPublicKeyTypeSECP256k1); - auto address = Bitcoin::SegwitAddress(publicKey, 0, "bc"); + auto address = Bitcoin::SegwitAddress(publicKey, "bc"); EXPECT_EQ(hex(publicKey.bytes), "022dc3f5a3fcfd2d1cc76d0cb386eaad0e30247ba729da0d8847a2713e444fdafa"); EXPECT_EQ(address.string(), "bc1q5yyq60jepll68hds7exa7kpj20gsvdu0aztw5x"); @@ -253,7 +253,7 @@ TEST(HDWallet, DeriveWithLeadingZerosEth) { } static nlohmann::json getVectors() { - const std::string vectorsJsonPath = std::string(TESTS_ROOT) + "/HDWallet/bip39_vectors.json"; + const std::string vectorsJsonPath = std::string(TESTS_ROOT) + "/common/HDWallet/bip39_vectors.json"; auto vectorsJson = loadJson(vectorsJsonPath)["english"]; return vectorsJson; } @@ -286,4 +286,152 @@ TEST(HDWallet, Bip39Vectors) { } } +TEST(HDWallet, getExtendedPrivateKey) { + const HDWallet wallet = HDWallet(mnemonic1, ""); + const auto purpose = TWPurposeBIP44; + const auto coin = TWCoinTypeBitcoin; + const auto hdVersion = TWHDVersionZPRV; + + // default + const auto extPubKey1 = wallet.getExtendedPrivateKey(purpose, coin, hdVersion); + EXPECT_EQ(extPubKey1, "zprvAcwsTZNaY1f7rfwsy5GseSDStYBrxwtsBZDkb3iyuQUs4NF6n58BuH7Xj54RuaSCWtU5CiQzuYQgFgqr1HokgKcVAeGeXokhJUAJeP3VmvY"); + + // explicitly specify default account=0 + const auto extPubKey2 = wallet.getExtendedPrivateKeyAccount(purpose, coin, TWDerivationDefault, hdVersion, 0); + EXPECT_EQ(extPubKey2, "zprvAcwsTZNaY1f7rfwsy5GseSDStYBrxwtsBZDkb3iyuQUs4NF6n58BuH7Xj54RuaSCWtU5CiQzuYQgFgqr1HokgKcVAeGeXokhJUAJeP3VmvY"); + + // custom account=1 + const auto extPubKey3 = wallet.getExtendedPrivateKeyAccount(purpose, coin, TWDerivationDefault, hdVersion, 1); + EXPECT_EQ(extPubKey3, "zprvAcwsTZNaY1f7sifgNNgdNa4P9mPtyg3zRVgwkx2qF9Sn7F255MzP6Zyumn6bgV5xuoS8ZrDvjzE7APcFSacXdzFYpGvyybb1bnAoh5nHxpn"); +} + +TEST(HDWallet, getExtendedPublicKey) { + const HDWallet wallet = HDWallet(mnemonic1, ""); + const auto purpose = TWPurposeBIP44; + const auto coin = TWCoinTypeBitcoin; + const auto hdVersion = TWHDVersionZPUB; + const auto derivation = TWDerivationDefault; + + // default + const auto extPubKey1 = wallet.getExtendedPublicKey(purpose, coin, hdVersion); + EXPECT_EQ(extPubKey1, "zpub6qwDs4uUNPDR5A2M56ot1aABSa2MNQciYn9MPS8bTk1qwAaFKcSST5S1aLidvPp9twqpaumG7vikR2vHhBXjp5oGgHyMvWK3AtUkfeEgyns"); + + // explicitly specify default account=0 + const auto extPubKey2 = wallet.getExtendedPublicKeyAccount(purpose, coin, derivation, hdVersion, 0); + EXPECT_EQ(extPubKey2, "zpub6qwDs4uUNPDR5A2M56ot1aABSa2MNQciYn9MPS8bTk1qwAaFKcSST5S1aLidvPp9twqpaumG7vikR2vHhBXjp5oGgHyMvWK3AtUkfeEgyns"); + + // custom account=1 + const auto extPubKey3 = wallet.getExtendedPublicKeyAccount(purpose, coin, derivation, hdVersion, 1); + EXPECT_EQ(extPubKey3, "zpub6qwDs4uUNPDR6Ck9UQDdji17hoEPP8mqnicYZLSSoUykz3MDcuJdeNJPd3BozqEafeLZkegWqzAvkgA4JZZ5tTN2rDpGKfk54essyfx1eZP"); +} + +TEST(HDWallet, Derive_XpubPub_vs_PrivPub) { + // Test different routes for deriving address from mnemonic, result should be the same: + // - Direct: mnemonic -> seed -> privateKey -> publicKey -> address + // - Extended Public: mnemonic -> seed -> zpub -> publicKey -> address + // - Extended Private: mnemonic -> seed -> zpriv -> privateKey -> publicKey -> address + + const HDWallet wallet = HDWallet(mnemonic1, ""); + const auto coin = TWCoinTypeBitcoin; + const auto derivPath1 = DerivationPath("m/84'/0'/0'/0/0"); + const auto derivPath2 = DerivationPath("m/84'/0'/0'/0/2"); + const auto expectedPublicKey1 = "02df9ef2a7a5552765178b181e1e1afdefc7849985c7dfe9647706dd4fa40df6ac"; + const auto expectedPublicKey2 = "031e1f64d2f6768dccb6814545b2e2d58e26ad5f91b7cbaffe881ed572c65060db"; + const auto expectedAddress1 = "bc1qpsp72plnsqe6e2dvtsetxtww2cz36ztmfxghpd"; + const auto expectedAddress2 = "bc1q7zddsunzaftf4zlsg9exhzlkvc5374a6v32jf6"; + + // -> privateKey -> publicKey + { + const auto privateKey1 = wallet.getKey(coin, derivPath1); + const auto publicKey1 = privateKey1.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(publicKey1.bytes), expectedPublicKey1); + const auto address1 = Bitcoin::SegwitAddress(publicKey1, "bc"); + EXPECT_EQ(address1.string(), expectedAddress1); + } + { + const auto privateKey2 = wallet.getKey(coin, derivPath2); + const auto publicKey2 = privateKey2.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(publicKey2.bytes), expectedPublicKey2); + const auto address2 = Bitcoin::SegwitAddress(publicKey2, "bc"); + EXPECT_EQ(address2.string(), expectedAddress2); + } + + // zpub -> publicKey + const auto zpub = wallet.getExtendedPublicKey(TWPurposeBIP84, coin, TWHDVersionZPUB); + EXPECT_EQ(zpub, "zpub6rNUNtxSa9Gxvm4Bdxf1MPMwrvkzwDx6vP96Hkzw3jiQKdg3fhXBStxjn12YixQB8h88B3RMSRscRstf9AEVaYr3MAqVBEWBDuEJU4PGaT9"); + + { + const auto publicKey1 = wallet.getPublicKeyFromExtended(zpub, coin, derivPath1); + EXPECT_TRUE(publicKey1.has_value()); + EXPECT_EQ(hex(publicKey1->bytes), expectedPublicKey1); + const auto address1 = Bitcoin::SegwitAddress(publicKey1.value(), "bc"); + EXPECT_EQ(address1.string(), expectedAddress1); + } + { + const auto publicKey2 = wallet.getPublicKeyFromExtended(zpub, coin, derivPath2); + EXPECT_TRUE(publicKey2.has_value()); + EXPECT_EQ(hex(publicKey2->bytes), expectedPublicKey2); + const auto address2 = Bitcoin::SegwitAddress(publicKey2.value(), "bc"); + EXPECT_EQ(address2.string(), expectedAddress2); + } + + // zpriv -> privateKey -> publicKey + const auto zpriv = wallet.getExtendedPrivateKey(TWPurposeBIP84, coin, TWHDVersionZPRV); + EXPECT_EQ(zpriv, "zprvAdP7yPRYjmifiGyiXw7zzFRDJtvWXmEFZADVVNbKVQBRSqLu8ACvu6eFvhrnnw4QwdTD8PUVa48MguwiPTiyfn85zWx9iA5MYy4Eufu5bas"); + + { + const auto privateKey1 = wallet.getPrivateKeyFromExtended(zpriv, coin, derivPath1); + EXPECT_TRUE(privateKey1.has_value()); + const auto publicKey1 = privateKey1->getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(publicKey1.bytes), expectedPublicKey1); + const auto address1 = Bitcoin::SegwitAddress(publicKey1, "bc"); + EXPECT_EQ(address1.string(), expectedAddress1); + } + { + const auto privateKey2 = wallet.getPrivateKeyFromExtended(zpriv, coin, derivPath2); + EXPECT_TRUE(privateKey2.has_value()); + const auto publicKey2 = privateKey2->getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(publicKey2.bytes), expectedPublicKey2); + const auto address2 = Bitcoin::SegwitAddress(publicKey2, "bc"); + EXPECT_EQ(address2.string(), expectedAddress2); + } +} + +TEST(HDWallet, getKeyByCurve) { + const auto derivPath = "m/44'/539'/0'/0/0"; + HDWallet wallet = HDWallet(mnemonic1, ""); + { + const auto privateKey = wallet.getKeyByCurve(TWCurveSECP256k1, DerivationPath(derivPath)); + EXPECT_EQ(hex(privateKey.bytes), "4fb8657d6464adcaa086d6758d7f0b6b6fc026c98dc1671fcc6460b5a74abc62"); + } + { + const auto privateKey = wallet.getKeyByCurve(TWCurveNIST256p1, DerivationPath(derivPath)); + EXPECT_EQ(hex(privateKey.bytes), "a13df52d5a5b438bbf921bbf86276e4347fe8e2f2ed74feaaee12b77d6d26f86"); + } +} + +TEST(HDWallet, getKey) { + const auto derivPath = "m/44'/539'/0'/0/0"; + HDWallet wallet = HDWallet(mnemonic1, ""); + { + const auto privateKey = wallet.getKey(TWCoinTypeBitcoin, DerivationPath(derivPath)); + EXPECT_EQ(hex(privateKey.bytes), "4fb8657d6464adcaa086d6758d7f0b6b6fc026c98dc1671fcc6460b5a74abc62"); + } + { + const auto privateKey = wallet.getKey(TWCoinTypeNEO, DerivationPath(derivPath)); + EXPECT_EQ(hex(privateKey.bytes), "a13df52d5a5b438bbf921bbf86276e4347fe8e2f2ed74feaaee12b77d6d26f86"); + } +} + +TEST(HDWallet, AptosKey) { + const auto derivPath = "m/44'/637'/0'/0'/0'"; + HDWallet wallet = HDWallet(mnemonic1, ""); + { + const auto privateKey = wallet.getKey(TWCoinTypeAptos, DerivationPath(derivPath)); + EXPECT_EQ(hex(privateKey.bytes), "7f2634c0e2414a621e96e39c41d09021700cee12ee43328ed094c5580cd0bd6f"); + EXPECT_EQ(hex(privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes), "633e5c7e355bdd484706436ce1f06fdf280bd7c2229a7f9b6489684412c6967c"); + } +} + + } // namespace diff --git a/tests/HDWallet/bip39_vectors.json b/tests/common/HDWallet/bip39_vectors.json similarity index 100% rename from tests/HDWallet/bip39_vectors.json rename to tests/common/HDWallet/bip39_vectors.json diff --git a/tests/HashTests.cpp b/tests/common/HashTests.cpp similarity index 52% rename from tests/HashTests.cpp rename to tests/common/HashTests.cpp index e665470ddae..c60acec946c 100644 --- a/tests/HashTests.cpp +++ b/tests/common/HashTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -65,4 +65,32 @@ TEST(HashTests, hmac256) { EXPECT_EQ(hex(hmac), expectedHmac); } +TEST(HashTests, allHashEnum) { + const auto tests = { + make_tuple(Hash::HasherSha1, "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12"), + make_tuple(Hash::HasherSha256, "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592"), + make_tuple(Hash::HasherSha512, "07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6"), + make_tuple(Hash::HasherSha512_256, "dd9d67b371519c339ed8dbd25af90e976a1eeefd4ad3d889005e532fc5bef04d"), + make_tuple(Hash::HasherKeccak256, "4d741b6f1eb29cb2a9b9911c82f56fa8d73b04959d3d9d222895df6c0b28aa15"), + make_tuple(Hash::HasherKeccak512, "d135bb84d0439dbac432247ee573a23ea7d3c9deb2a968eb31d47c4fb45f1ef4422d6c531b5b9bd6f449ebcc449ea94d0a8f05f62130fda612da53c79659f609"), + make_tuple(Hash::HasherSha3_256, "69070dda01975c8c120c3aada1b282394e7f032fa9cf32f4cb2259a0897dfc04"), + make_tuple(Hash::HasherSha3_512, "01dedd5de4ef14642445ba5f5b97c15e47b9ad931326e4b0727cd94cefc44fff23f07bf543139939b49128caf436dc1bdee54fcb24023a08d9403f9b4bf0d450"), + make_tuple(Hash::HasherRipemd, "37f332f68db77bd9d7edd4969571ad671cf9dd3b"), + make_tuple(Hash::HasherBlake256, "7576698ee9cad30173080678e5965916adbb11cb5245d386bf1ffda1cb26c9d7"), + make_tuple(Hash::HasherGroestl512, "badc1f70ccd69e0cf3760c3f93884289da84ec13c70b3d12a53a7a8a4a513f99715d46288f55e1dbf926e6d084a0538e4eebfc91cf2b21452921ccde9131718d"), + make_tuple(Hash::HasherSha256d, "6d37795021e544d82b41850edf7aabab9a0ebe274e54a519840c4666f35b3937"), + make_tuple(Hash::HasherSha256ripemd, "0e3397b4abc7a382b3ea2365883c3c7ca5f07600"), + make_tuple(Hash::HasherSha3_256ripemd, "e70a0c74dd1b0c0d5af3c7ccbbe4b488d1b474b5"), + make_tuple(Hash::HasherBlake256d, "4511ab8713d8d580cae73061345df903f603b99e7ec699ddae63c56eea200059"), + make_tuple(Hash::HasherBlake256ripemd, "b4b44de1e854f7f3c0520b654204163f75f704e5"), + make_tuple(Hash::HasherGroestl512d, "1209d229cfc9d7d6711369e2d7f369b0efc1459a9d407cbfc7daf4f54209347f2ee7e3e7522ba5d5ac4e7365445739919e23e2917baee10f23557f3d3fbc696d"), + }; + + for (auto& test: tests) { + const auto hashFunc = get<0>(test); + const auto expected = get<1>(test); + EXPECT_EQ(hex(Hash::hash(hashFunc, brownFox)), expected); + } +} + // More tests in TWHashTests diff --git a/tests/HexCodingTests.cpp b/tests/common/HexCodingTests.cpp similarity index 100% rename from tests/HexCodingTests.cpp rename to tests/common/HexCodingTests.cpp diff --git a/tests/Keystore/Data/empty-accounts.json b/tests/common/Keystore/Data/empty-accounts.json similarity index 100% rename from tests/Keystore/Data/empty-accounts.json rename to tests/common/Keystore/Data/empty-accounts.json diff --git a/tests/Keystore/Data/ethereum-wallet-address-no-0x.json b/tests/common/Keystore/Data/ethereum-wallet-address-no-0x.json similarity index 100% rename from tests/Keystore/Data/ethereum-wallet-address-no-0x.json rename to tests/common/Keystore/Data/ethereum-wallet-address-no-0x.json diff --git a/tests/Keystore/Data/key.json b/tests/common/Keystore/Data/key.json old mode 100755 new mode 100644 similarity index 100% rename from tests/Keystore/Data/key.json rename to tests/common/Keystore/Data/key.json diff --git a/tests/Keystore/Data/key_bitcoin.json b/tests/common/Keystore/Data/key_bitcoin.json old mode 100755 new mode 100644 similarity index 100% rename from tests/Keystore/Data/key_bitcoin.json rename to tests/common/Keystore/Data/key_bitcoin.json diff --git a/tests/Keystore/Data/legacy-mnemonic.json b/tests/common/Keystore/Data/legacy-mnemonic.json similarity index 100% rename from tests/Keystore/Data/legacy-mnemonic.json rename to tests/common/Keystore/Data/legacy-mnemonic.json diff --git a/tests/Keystore/Data/legacy-private-key.json b/tests/common/Keystore/Data/legacy-private-key.json similarity index 100% rename from tests/Keystore/Data/legacy-private-key.json rename to tests/common/Keystore/Data/legacy-private-key.json diff --git a/tests/Keystore/Data/livepeer.json b/tests/common/Keystore/Data/livepeer.json similarity index 100% rename from tests/Keystore/Data/livepeer.json rename to tests/common/Keystore/Data/livepeer.json diff --git a/tests/Keystore/Data/missing-address.json b/tests/common/Keystore/Data/missing-address.json similarity index 100% rename from tests/Keystore/Data/missing-address.json rename to tests/common/Keystore/Data/missing-address.json diff --git a/tests/Keystore/Data/myetherwallet.uu b/tests/common/Keystore/Data/myetherwallet.uu old mode 100755 new mode 100644 similarity index 100% rename from tests/Keystore/Data/myetherwallet.uu rename to tests/common/Keystore/Data/myetherwallet.uu diff --git a/tests/Keystore/Data/pbkdf2.json b/tests/common/Keystore/Data/pbkdf2.json similarity index 100% rename from tests/Keystore/Data/pbkdf2.json rename to tests/common/Keystore/Data/pbkdf2.json diff --git a/tests/Keystore/Data/wallet.json b/tests/common/Keystore/Data/wallet.json old mode 100755 new mode 100644 similarity index 100% rename from tests/Keystore/Data/wallet.json rename to tests/common/Keystore/Data/wallet.json diff --git a/tests/Keystore/Data/watch.json b/tests/common/Keystore/Data/watch.json similarity index 100% rename from tests/Keystore/Data/watch.json rename to tests/common/Keystore/Data/watch.json diff --git a/tests/Keystore/Data/web3j.json b/tests/common/Keystore/Data/web3j.json similarity index 100% rename from tests/Keystore/Data/web3j.json rename to tests/common/Keystore/Data/web3j.json diff --git a/tests/DerivationPathTests.cpp b/tests/common/Keystore/DerivationPathTests.cpp similarity index 51% rename from tests/DerivationPathTests.cpp rename to tests/common/Keystore/DerivationPathTests.cpp index 4b2269ad864..62d21aab939 100644 --- a/tests/DerivationPathTests.cpp +++ b/tests/common/Keystore/DerivationPathTests.cpp @@ -1,10 +1,12 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +#include "Coin.h" #include "DerivationPath.h" +#include #include @@ -12,28 +14,28 @@ namespace TW { TEST(DerivationPath, InitWithIndices) { const auto path = DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeEthereum), 0, 0, 0); - ASSERT_EQ(path.indices[0], DerivationPathIndex(44, /* hardened: */true)); - ASSERT_EQ(path.indices[1], DerivationPathIndex(60, /* hardened: */true)); - ASSERT_EQ(path.indices[2], DerivationPathIndex(0, /* hardened: */true)); - ASSERT_EQ(path.indices[3], DerivationPathIndex(0, /* hardened: */false)); - ASSERT_EQ(path.indices[4], DerivationPathIndex(0, /* hardened: */false)); + ASSERT_EQ(path.indices[0], DerivationPathIndex(44, /* hardened: */ true)); + ASSERT_EQ(path.indices[1], DerivationPathIndex(60, /* hardened: */ true)); + ASSERT_EQ(path.indices[2], DerivationPathIndex(0, /* hardened: */ true)); + ASSERT_EQ(path.indices[3], DerivationPathIndex(0, /* hardened: */ false)); + ASSERT_EQ(path.indices[4], DerivationPathIndex(0, /* hardened: */ false)); } TEST(DerivationPath, InitWithString) { ASSERT_NO_THROW(DerivationPath("m/44'/60'/0'/0/0")); const auto path = DerivationPath("m/44'/60'/0'/0/0"); - ASSERT_EQ(path.indices[0], DerivationPathIndex(44, /* hardened: */true)); - ASSERT_EQ(path.indices[1], DerivationPathIndex(60, /* hardened: */true)); - ASSERT_EQ(path.indices[2], DerivationPathIndex(0, /* hardened: */true)); - ASSERT_EQ(path.indices[3], DerivationPathIndex(0, /* hardened: */false)); - ASSERT_EQ(path.indices[4], DerivationPathIndex(0, /* hardened: */false)); + ASSERT_EQ(path.indices[0], DerivationPathIndex(44, /* hardened: */ true)); + ASSERT_EQ(path.indices[1], DerivationPathIndex(60, /* hardened: */ true)); + ASSERT_EQ(path.indices[2], DerivationPathIndex(0, /* hardened: */ true)); + ASSERT_EQ(path.indices[3], DerivationPathIndex(0, /* hardened: */ false)); + ASSERT_EQ(path.indices[4], DerivationPathIndex(0, /* hardened: */ false)); ASSERT_EQ(path.purpose(), 44); - ASSERT_EQ(path.coin(), 60); - ASSERT_EQ(path.account(), 0); - ASSERT_EQ(path.change(), 0); - ASSERT_EQ(path.address(), 0); + ASSERT_EQ(path.coin(), 60ul); + ASSERT_EQ(path.account(), 0ul); + ASSERT_EQ(path.change(), 0ul); + ASSERT_EQ(path.address(), 0ul); } TEST(DerivationPath, InitInvalid) { @@ -44,13 +46,13 @@ TEST(DerivationPath, InitInvalid) { TEST(DerivationPath, IndexOutOfBounds) { DerivationPath path; - EXPECT_EQ(path.indices.size(), 0); + EXPECT_EQ(path.indices.size(), 0ul); EXPECT_EQ(path.purpose(), TWPurposeBIP44); EXPECT_EQ(path.coin(), TWCoinTypeBitcoin); - EXPECT_EQ(path.account(), 0); - EXPECT_EQ(path.change(), 0); - EXPECT_EQ(path.address(), 0); + EXPECT_EQ(path.account(), 0ul); + EXPECT_EQ(path.change(), 0ul); + EXPECT_EQ(path.address(), 0ul); ASSERT_NO_THROW(path.setPurpose(TWPurposeBIP44)); ASSERT_NO_THROW(path.setCoin(TWCoinTypeBitcoin)); @@ -70,4 +72,26 @@ TEST(DerivationPath, Equal) { ASSERT_EQ(path1, path2); } -} // namespace +TEST(Derivation, derivationPath) { + EXPECT_EQ(TW::derivationPath(TWCoinTypeBitcoin).string(), "m/84'/0'/0'/0/0"); + EXPECT_EQ(TW::derivationPath(TWCoinTypeSolana).string(), "m/44'/501'/0'"); +} + +TEST(Derivation, alternativeDerivation) { + EXPECT_EQ(TW::derivationPath(TWCoinTypeBitcoin).string(), "m/84'/0'/0'/0/0"); + EXPECT_EQ(TW::derivationPath(TWCoinTypeBitcoin, TWDerivationDefault).string(), "m/84'/0'/0'/0/0"); + EXPECT_EQ(TW::derivationPath(TWCoinTypeBitcoin, TWDerivationBitcoinSegwit).string(), "m/84'/0'/0'/0/0"); + EXPECT_EQ(std::string(TW::derivationName(TWCoinTypeBitcoin, TWDerivationBitcoinSegwit)), "segwit"); + EXPECT_EQ(TW::derivationPath(TWCoinTypeBitcoin, TWDerivationBitcoinLegacy).string(), "m/44'/0'/0'/0/0"); + EXPECT_EQ(std::string(TW::derivationName(TWCoinTypeBitcoin, TWDerivationBitcoinLegacy)), "legacy"); + + EXPECT_EQ(TW::derivationPath(TWCoinTypeLitecoin, TWDerivationDefault).string(), "m/84'/2'/0'/0/0"); + EXPECT_EQ(TW::derivationPath(TWCoinTypeLitecoin, TWDerivationLitecoinLegacy).string(), "m/44'/2'/0'/0/0"); + + EXPECT_EQ(TW::derivationPath(TWCoinTypeSolana).string(), "m/44'/501'/0'"); + EXPECT_EQ(TW::derivationPath(TWCoinTypeSolana, TWDerivationDefault).string(), "m/44'/501'/0'"); + EXPECT_EQ(TW::derivationPath(TWCoinTypeSolana, TWDerivationSolanaSolana).string(), "m/44'/501'/0'/0'"); + EXPECT_EQ(std::string(TW::derivationName(TWCoinTypeSolana, TWDerivationSolanaSolana)), "solana"); +} + +} // namespace TW diff --git a/tests/common/Keystore/StoredKeyTests.cpp b/tests/common/Keystore/StoredKeyTests.cpp new file mode 100644 index 00000000000..51cc959968d --- /dev/null +++ b/tests/common/Keystore/StoredKeyTests.cpp @@ -0,0 +1,653 @@ +// Copyright © 2017-2021 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 "Keystore/StoredKey.h" + +#include "Coin.h" +#include "HexCoding.h" +#include "Data.h" +#include "PrivateKey.h" +#include "Mnemonic.h" +#include "Bitcoin/Address.h" + +#include +#include + +extern std::string TESTS_ROOT; + +namespace TW::Keystore::tests { + +using namespace std; + +const auto passwordString = "password"; +const auto gPassword = TW::data(string(passwordString)); +const auto gMnemonic = "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; +const TWCoinType coinTypeBc = TWCoinTypeBitcoin; +const TWCoinType coinTypeBnb = TWCoinTypeBinance; +const TWCoinType coinTypeBsc = TWCoinTypeSmartChain; +const TWCoinType coinTypeEth = TWCoinTypeEthereum; +const TWCoinType coinTypeBscLegacy = TWCoinTypeSmartChainLegacy; + +const std::string testDataPath(const char* subpath) { + return TESTS_ROOT + "/common/Keystore/Data/" + subpath; +} + +TEST(StoredKey, CreateWithMnemonic) { + auto key = StoredKey::createWithMnemonic("name", gPassword, gMnemonic, TWStoredKeyEncryptionLevelDefault); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + const Data& mnemo2Data = key.payload.decrypt(gPassword); + EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(gMnemonic)); + EXPECT_EQ(key.accounts.size(), 0ul); + EXPECT_EQ(key.wallet(gPassword).getMnemonic(), string(gMnemonic)); + + const auto json = key.json(); + EXPECT_EQ(json["name"], "name"); + EXPECT_EQ(json["type"], "mnemonic"); + EXPECT_EQ(json["version"], 3); +} + +TEST(StoredKey, CreateWithMnemonicInvalid) { + try { + auto key = StoredKey::createWithMnemonic("name", gPassword, "_THIS_IS_NOT_A_VALID_MNEMONIC_", TWStoredKeyEncryptionLevelDefault); + } catch (std::invalid_argument&) { + // expedcted exception OK + return; + } + FAIL() << "Missing excpected excpetion"; +} + +TEST(StoredKey, CreateWithMnemonicRandom) { + const auto key = StoredKey::createWithMnemonicRandom("name", gPassword, TWStoredKeyEncryptionLevelDefault); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + // random mnemonic: check only length and validity + const Data& mnemo2Data = key.payload.decrypt(gPassword); + EXPECT_TRUE(mnemo2Data.size() >= 36); + EXPECT_TRUE(Mnemonic::isValid(string(mnemo2Data.begin(), mnemo2Data.end()))); + EXPECT_EQ(key.accounts.size(), 0ul); +} + +TEST(StoredKey, CreateWithMnemonicAddDefaultAddress) { + auto key = StoredKey::createWithMnemonicAddDefaultAddress("name", gPassword, gMnemonic, coinTypeBc); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + + const Data& mnemo2Data = key.payload.decrypt(gPassword); + + EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(gMnemonic)); + EXPECT_EQ(key.accounts.size(), 1ul); + EXPECT_EQ(key.accounts[0].coin, coinTypeBc); + EXPECT_EQ(key.accounts[0].address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); + EXPECT_EQ(key.accounts[0].publicKey, "02df6fc590ab3101bbe0bb5765cbaeab9b5dcfe09ac9315d707047cbd13bc7e006"); + EXPECT_EQ(key.accounts[0].extendedPublicKey, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); + EXPECT_EQ(hex(key.privateKey(coinTypeBc, gPassword).bytes), "d2568511baea8dc347f14c4e0479eb8ebe29eb5f664ed796e755896250ffd11f"); +} + +TEST(StoredKey, CreateWithPrivateKeyAddDefaultAddress) { + const auto privateKey = parse_hex("3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"); + auto key = StoredKey::createWithPrivateKeyAddDefaultAddress("name", gPassword, coinTypeBc, privateKey); + EXPECT_EQ(key.type, StoredKeyType::privateKey); + EXPECT_EQ(key.accounts.size(), 1ul); + EXPECT_EQ(key.accounts[0].coin, coinTypeBc); + EXPECT_EQ(key.accounts[0].address, "bc1q375sq4kl2nv0mlmup3vm8znn4eqwu7mt6hkwhr"); + EXPECT_EQ(hex(key.privateKey(coinTypeBc, gPassword).bytes), hex(privateKey)); + + const auto json = key.json(); + EXPECT_EQ(json["name"], "name"); + EXPECT_EQ(json["type"], "private-key"); + EXPECT_EQ(json["version"], 3); +} + +TEST(StoredKey, CreateWithPrivateKeyAddDefaultAddressInvalid) { + try { + const auto privateKeyInvalid = parse_hex("0001020304"); + auto key = StoredKey::createWithPrivateKeyAddDefaultAddress("name", gPassword, coinTypeBc, privateKeyInvalid); + } catch (std::invalid_argument&) { + // expected exception ok + return; + } + FAIL() << "Missing expected exception"; +} + +TEST(StoredKey, AccountGetCreate) { + auto key = StoredKey::createWithMnemonic("name", gPassword, gMnemonic, TWStoredKeyEncryptionLevelDefault); + EXPECT_EQ(key.accounts.size(), 0ul); + + // not exists + EXPECT_FALSE(key.account(coinTypeBc).has_value()); + EXPECT_EQ(key.accounts.size(), 0ul); + + auto wallet = key.wallet(gPassword); + // not exists, wallet null, not create + EXPECT_FALSE(key.account(coinTypeBc, nullptr).has_value()); + EXPECT_EQ(key.accounts.size(), 0ul); + + // not exists, wallet nonnull, create + std::optional acc3 = key.account(coinTypeBc, &wallet); + EXPECT_TRUE(acc3.has_value()); + EXPECT_EQ(acc3->coin, coinTypeBc); + EXPECT_EQ(key.accounts.size(), 1ul); + + // exists + std::optional acc4 = key.account(coinTypeBc); + EXPECT_TRUE(acc4.has_value()); + EXPECT_EQ(acc4->coin, coinTypeBc); + EXPECT_EQ(key.accounts.size(), 1ul); + + // exists, wallet nonnull, not create + std::optional acc5 = key.account(coinTypeBc, &wallet); + EXPECT_TRUE(acc5.has_value()); + EXPECT_EQ(acc5->coin, coinTypeBc); + EXPECT_EQ(key.accounts.size(), 1ul); + + // exists, wallet null, not create + std::optional acc6 = key.account(coinTypeBc, nullptr); + EXPECT_TRUE(acc6.has_value()); + EXPECT_EQ(acc6->coin, coinTypeBc); + EXPECT_EQ(key.accounts.size(), 1ul); +} + +TEST(StoredKey, AccountGetDoesntChange) { + auto key = StoredKey::createWithMnemonic("name", gPassword, gMnemonic, TWStoredKeyEncryptionLevelDefault); + auto wallet = key.wallet(gPassword); + EXPECT_EQ(key.accounts.size(), 0ul); + + vector coins = {coinTypeBc, coinTypeEth, coinTypeBnb}; + // retrieve multiple accounts, which will be created + vector accounts; + for (auto coin: coins) { + std::optional account = key.account(coin, &wallet); + accounts.push_back(*account); + + // check + ASSERT_TRUE(account.has_value()); + EXPECT_EQ(account->coin, coin); + } + + // Check again; make sure returned references don't change + for (auto i = 0ul; i < accounts.size(); ++i) { + // check + EXPECT_EQ(accounts[i].coin, coins[i]); + } +} + +TEST(StoredKey, AddRemoveAccount) { + auto key = StoredKey::createWithMnemonic("name", gPassword, gMnemonic, TWStoredKeyEncryptionLevelDefault); + EXPECT_EQ(key.accounts.size(), 0ul); + + { + const auto derivationPath = DerivationPath("m/84'/0'/0'/0/0"); + key.addAccount("bc1qaucw06s3agez8tyyk4zj9kt0q2934e3mcewdpf", coinTypeBc, TWDerivationDefault, derivationPath, "", "zpub6rxtad3SPT1C5GUDjPiKQ5oJN5DBeMbdUR7LrdYt12VbU7TBSpGUkdLvfVYGuj1N5edkDoZ3bu1fdN1HprQYfCBdsSH5CaAAygHGsanwtTe"); + EXPECT_EQ(key.accounts.size(), 1ul); + } + { + const auto derivationPath = DerivationPath("m/714'/0'/0'/0/0"); + key.addAccount("bnb1utrnnjym7ustgw7pgyvtmnxay4qmt3ahh276nu", coinTypeBnb, TWDerivationDefault, derivationPath, "", ""); + key.addAccount("0x23b02dC8f67eD6cF8DCa47935791954286ffe7c9", coinTypeBsc, TWDerivationDefault, derivationPath, "", ""); + EXPECT_EQ(key.accounts.size(), 3ul); + } + { + const auto derivationPath = DerivationPath("m/60'/0'/0'/0/0"); + key.addAccount("0xC0d97f61A84A0708225F15d54978D628Fe2C5E62", coinTypeEth, TWDerivationDefault, derivationPath, "", ""); + key.addAccount("0xC0d97f61A84A0708225F15d54978D628Fe2C5E62", coinTypeBscLegacy, TWDerivationDefault, derivationPath, "", ""); + EXPECT_EQ(key.accounts.size(), 5ul); + } + + key.removeAccount(coinTypeBc); + key.removeAccount(coinTypeBnb); + key.removeAccount(coinTypeBsc); + key.removeAccount(coinTypeEth); + key.removeAccount(coinTypeBscLegacy); + EXPECT_EQ(key.accounts.size(), 0ul); +} + +TEST(StoredKey, AddRemoveAccountDerivation) { + auto key = StoredKey::createWithMnemonic("name", Data(), gMnemonic, TWStoredKeyEncryptionLevelDefault); + EXPECT_EQ(key.accounts.size(), 0ul); + + const auto derivationPath = DerivationPath("m/84'/0'/0'/0/0"); + { + key.addAccount("bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny", coinTypeBc, TWDerivationDefault, derivationPath, "", "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); + EXPECT_EQ(key.accounts.size(), 1ul); + } + { + key.addAccount("1NyRyFewhZcWMa9XCj3bBxSXPXyoSg8dKz", coinTypeBc, TWDerivationBitcoinLegacy, derivationPath, "", "xpub6CR52eaUuVb4kXAVyHC2i5ZuqJ37oWNPZFtjXaazFPXZD45DwWBYEBLdrF7fmCR9pgBuCA9Q57zZfyJjDUBDNtWkhWuGHNYKLgDHpqrHsxV"); + EXPECT_EQ(key.accounts.size(), 2ul); + } + + key.removeAccount(coinTypeBc, TWDerivationDefault); + EXPECT_EQ(key.accounts.size(), 1ul); + key.removeAccount(coinTypeBc, TWDerivationDefault); // try 2nd time + EXPECT_EQ(key.accounts.size(), 1ul); + key.removeAccount(coinTypeBc, TWDerivationBitcoinLegacy); + EXPECT_EQ(key.accounts.size(), 0ul); + key.removeAccount(coinTypeBc, TWDerivationBitcoinLegacy); // try 2nd time + EXPECT_EQ(key.accounts.size(), 0ul); +} + +TEST(StoredKey, AddRemoveAccountDerivationPath) { + auto key = StoredKey::createWithMnemonic("name", Data(), gMnemonic, TWStoredKeyEncryptionLevelDefault); + EXPECT_EQ(key.accounts.size(), 0ul); + + const auto derivationPath0 = DerivationPath("m/84'/0'/0'/0/0"); + { + key.addAccount("bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny", coinTypeBc, TWDerivationDefault, derivationPath0, "", "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); + EXPECT_EQ(key.accounts.size(), 1ul); + } + const auto derivationPath1 = DerivationPath("m/84'/0'/0'/1/0"); + { + key.addAccount("bc1qumuzptwdr6jlsqum8jnzz80rdg8nx6x29m2qpu", coinTypeBc, TWDerivationDefault, derivationPath1, "", "zpub6rxtad3SPT1C5GUDjPiKQ5oJN5DBeMbdUR7LrdYt12VbU7TBSpGUkdLvfVYGuj1N5edkDoZ3bu1fdN1HprQYfCBdsSH5CaAAygHGsanwtTe"); + EXPECT_EQ(key.accounts.size(), 2ul); + } + + key.removeAccount(coinTypeBc, derivationPath0); + EXPECT_EQ(key.accounts.size(), 1ul); + key.removeAccount(coinTypeBc, derivationPath0); // try 2nd time + EXPECT_EQ(key.accounts.size(), 1ul); + key.removeAccount(coinTypeBc, derivationPath1); + EXPECT_EQ(key.accounts.size(), 0ul); + key.removeAccount(coinTypeBc, derivationPath1); // try 2nd time + EXPECT_EQ(key.accounts.size(), 0ul); +} + +TEST(StoredKey, FixAddress) { + { + auto key = StoredKey::createWithMnemonic("name", gPassword, gMnemonic, TWStoredKeyEncryptionLevelDefault); + key.fixAddresses(gPassword); + } + { + const auto privateKey = parse_hex("3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"); + auto key = StoredKey::createWithPrivateKeyAddDefaultAddress("name", gPassword, coinTypeBc, privateKey); + key.fixAddresses(gPassword); + } +} + +TEST(StoredKey, WalletInvalid) { + const auto privateKey = parse_hex("3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"); + auto key = StoredKey::createWithPrivateKeyAddDefaultAddress("name", gPassword, coinTypeBc, privateKey); + try { + auto wallet = key.wallet(gPassword); + } catch (std::invalid_argument&) { + // expected exception ok + return; + } + FAIL() << "Missing expected exception"; +} + +TEST(StoredKey, LoadNonexistent) { + ASSERT_THROW(StoredKey::load(testDataPath("nonexistent.json")), invalid_argument); +} + +TEST(StoredKey, LoadLegacyPrivateKey) { + const auto key = StoredKey::load(testDataPath("legacy-private-key.json")); + EXPECT_EQ(key.type, StoredKeyType::privateKey); + EXPECT_EQ(key.id, "3051ca7d-3d36-4a4a-acc2-09e9083732b0"); + EXPECT_EQ(key.accounts[0].coin, TWCoinTypeEthereum); + EXPECT_EQ(hex(key.payload.decrypt(TW::data("testpassword"))), "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"); +} + +TEST(StoredKey, LoadLivepeerKey) { + const auto key = StoredKey::load(testDataPath("livepeer.json")); + EXPECT_EQ(key.type, StoredKeyType::privateKey); + EXPECT_EQ(key.id, "70ea3601-ee21-4e94-a7e4-66255a987d22"); + EXPECT_EQ(key.accounts[0].coin, TWCoinTypeEthereum); + EXPECT_EQ(hex(key.payload.decrypt(TW::data("Radchenko"))), "09b4379d9a41a71d94ee36357bccb4d77b45e7fd9307e2c0f673dd54c0558c73"); +} + +TEST(StoredKey, LoadPBKDF2Key) { + const auto key = StoredKey::load(testDataPath("pbkdf2.json")); + EXPECT_EQ(key.type, StoredKeyType::privateKey); + EXPECT_EQ(key.id, "3198bc9c-6672-5ab3-d995-4942343ae5b6"); + + const auto& payload = key.payload; + ASSERT_TRUE(std::holds_alternative(payload.params.kdfParams)); + EXPECT_EQ(std::get(payload.params.kdfParams).desiredKeyLength, 32ul); + EXPECT_EQ(std::get(payload.params.kdfParams).iterations, 262144ul); + EXPECT_EQ(hex(std::get(payload.params.kdfParams).salt), "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"); + + EXPECT_EQ(hex(payload.decrypt(TW::data("testpassword"))), "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"); +} + +TEST(StoredKey, LoadLegacyMnemonic) { + const auto key = StoredKey::load(testDataPath("legacy-mnemonic.json")); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + EXPECT_EQ(key.id, "629aad29-0b22-488e-a0e7-b4219d4f311c"); + + const auto data = key.payload.decrypt(gPassword); + const auto mnemonic = string(reinterpret_cast(data.data())); + EXPECT_EQ(mnemonic, "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn back"); + + EXPECT_EQ(key.accounts[0].coin, TWCoinTypeEthereum); + EXPECT_EQ(key.accounts[0].derivationPath.string(), "m/44'/60'/0'/0/0"); + EXPECT_EQ(key.accounts[0].address, ""); + EXPECT_EQ(key.accounts[1].coin, coinTypeBc); + EXPECT_EQ(key.accounts[1].derivationPath.string(), "m/84'/0'/0'/0/0"); + EXPECT_EQ(key.accounts[1].address, ""); + EXPECT_EQ(key.accounts[1].extendedPublicKey, "zpub6r97AegwVxVbJeuDAWP5KQgX5y4Q6KyFUrsFQRn8yzSXrnmpwg1ZKHSWwECR1Kiqgr4h93WN5kdS48KC6hVFniuZHqVFXjULZZkCwurqyPn"); +} + +TEST(StoredKey, LoadFromWeb3j) { + const auto key = StoredKey::load(testDataPath("web3j.json")); + EXPECT_EQ(key.type, StoredKeyType::privateKey); + EXPECT_EQ(key.id, "86066d8c-8dba-4d81-afd4-934e2a2b72a2"); + const auto password = parse_hex("2d6eefbfbd4622efbfbdefbfbd516718efbfbdefbfbdefbfbdefbfbd59efbfbd30efbfbdefbfbd3a4348efbfbd2aefbfbdefbfbd49efbfbd27efbfbd0638efbfbdefbfbdefbfbd4cefbfbd6befbfbdefbfbd6defbfbdefbfbd63efbfbd5aefbfbd61262b70efbfbdefbfbdefbfbdefbfbdefbfbdc7aa373163417cefbfbdefbfbdefbfbd44efbfbdefbfbd1d10efbfbdefbfbdefbfbd61dc9e5b124befbfbd11efbfbdefbfbd2fefbfbdefbfbd3d7c574868efbfbdefbfbdefbfbd37043b7b5c1a436471592f02efbfbd18efbfbdefbfbd2befbfbdefbfbd7218efbfbd6a68efbfbdcb8e5f3328773ec48174efbfbd67efbfbdefbfbdefbfbdefbfbdefbfbd2a31efbfbd7f60efbfbdd884efbfbd57efbfbd25efbfbd590459efbfbd37efbfbd2bdca20fefbfbdefbfbdefbfbdefbfbd39450113efbfbdefbfbdefbfbd454671efbfbdefbfbdd49fefbfbd47efbfbdefbfbdefbfbdefbfbd00efbfbdefbfbdefbfbdefbfbd05203f4c17712defbfbd7bd1bbdc967902efbfbdc98a77efbfbd707a36efbfbd12efbfbdefbfbd57c78cefbfbdefbfbdefbfbd10efbfbdefbfbdefbfbde1a1bb08efbfbdefbfbd26efbfbdefbfbd58efbfbdefbfbdc4b1efbfbd295fefbfbd0eefbfbdefbfbdefbfbd0e6eefbfbd"); + const auto data = key.payload.decrypt(password); + EXPECT_EQ(hex(data), "043c5429c7872502531708ec0d821c711691402caf37ef7ba78a8c506f10653b"); +} + +TEST(StoredKey, ReadWallet) { + const auto key = StoredKey::load(testDataPath("key.json")); + + EXPECT_EQ(key.type, StoredKeyType::privateKey); + EXPECT_EQ(key.id, "e13b209c-3b2f-4327-bab0-3bef2e51630d"); + EXPECT_EQ(key.name, "Test Account"); + + const auto header = key.payload; + + EXPECT_EQ(header.params.cipher, "aes-128-ctr"); + EXPECT_EQ(hex(header.encrypted), "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c"); + EXPECT_EQ(hex(header._mac), "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097"); + EXPECT_EQ(hex(header.params.cipherParams.iv), "83dbcc02d8ccb40e466191a123791e0e"); + + ASSERT_TRUE(std::holds_alternative(header.params.kdfParams)); + EXPECT_EQ(std::get(header.params.kdfParams).desiredKeyLength, 32ul); + EXPECT_EQ(std::get(header.params.kdfParams).n, 262144ul); + EXPECT_EQ(std::get(header.params.kdfParams).p, 8ul); + EXPECT_EQ(std::get(header.params.kdfParams).r, 1ul); + EXPECT_EQ(hex(std::get(header.params.kdfParams).salt), "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19"); +} + +TEST(StoredKey, ReadMyEtherWallet) { + ASSERT_NO_THROW(StoredKey::load(testDataPath("myetherwallet.uu"))); +} + +TEST(StoredKey, InvalidPassword) { + const auto key = StoredKey::load(testDataPath("key.json")); + + ASSERT_THROW(key.payload.decrypt(gPassword), DecryptionError); +} + +TEST(StoredKey, EmptyAccounts) { + const auto key = StoredKey::load(testDataPath("empty-accounts.json")); + + ASSERT_NO_THROW(key.payload.decrypt(TW::data("testpassword"))); +} + +TEST(StoredKey, Decrypt) { + const auto key = StoredKey::load(testDataPath("key.json")); + const auto privateKey = key.payload.decrypt(TW::data("testpassword")); + + EXPECT_EQ(hex(privateKey), "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"); +} + +TEST(StoredKey, CreateWallet) { + const auto privateKey = parse_hex("3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"); + const auto key = StoredKey::createWithPrivateKey("name", gPassword, privateKey); + const auto decrypted = key.payload.decrypt(gPassword); + + EXPECT_EQ(hex(decrypted), hex(privateKey)); +} + +TEST(StoredKey, CreateAccounts) { + string mnemonicPhrase = "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; + auto key = StoredKey::createWithMnemonic("name", gPassword, mnemonicPhrase, TWStoredKeyEncryptionLevelDefault); + const auto wallet = key.wallet(gPassword); + + EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->address, "0x494f60cb6Ac2c8F5E1393aD9FdBdF4Ad589507F7"); + EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->publicKey, "04cc32a479080d83fdcf69966713f0aad1bc1dc3ecf873b034894e84259841bc1c9b122717803e68905220ff54952d3f5ea2ab2698ca31f843addf94ae73fae9fd"); + EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->extendedPublicKey, ""); + + EXPECT_EQ(key.account(coinTypeBc, &wallet)->address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); + EXPECT_EQ(key.account(coinTypeBc, &wallet)->publicKey, "02df6fc590ab3101bbe0bb5765cbaeab9b5dcfe09ac9315d707047cbd13bc7e006"); + EXPECT_EQ(key.account(coinTypeBc, &wallet)->extendedPublicKey, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); +} + +TEST(StoredKey, DecodingEthereumAddress) { + const auto key = StoredKey::load(testDataPath("key.json")); + + EXPECT_EQ(key.accounts[0].address, "0x008AeEda4D805471dF9b2A5B0f38A0C3bCBA786b"); +} + +TEST(StoredKey, DecodingBitcoinAddress) { + const auto key = StoredKey::load(testDataPath("key_bitcoin.json")); + + EXPECT_EQ(key.accounts[0].address, "3PWazDi9n1Hfyq9gXFxDxzADNL8RNYyK2y"); +} + +TEST(StoredKey, RemoveAccount) { + auto key = StoredKey::load(testDataPath("legacy-mnemonic.json")); + EXPECT_EQ(key.accounts.size(), 2ul); + key.removeAccount(TWCoinTypeEthereum); + EXPECT_EQ(key.accounts.size(), 1ul); + EXPECT_EQ(key.accounts[0].coin, coinTypeBc); +} + +TEST(StoredKey, MissingAddressFix) { + auto key = StoredKey::load(testDataPath("missing-address.json")); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + + const auto wallet = key.wallet(gPassword); + EXPECT_EQ(wallet.getMnemonic(), "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"); + EXPECT_TRUE(Mnemonic::isValid(wallet.getMnemonic())); + + EXPECT_EQ(key.account(TWCoinTypeBitcoin)->address, ""); + EXPECT_EQ(key.account(TWCoinTypeEthereum)->address, ""); + + key.fixAddresses(gPassword); + + EXPECT_EQ(key.account(TWCoinTypeEthereum, nullptr)->address, "0xA3Dcd899C0f3832DFDFed9479a9d828c6A4EB2A7"); + EXPECT_EQ(key.account(TWCoinTypeEthereum, nullptr)->publicKey, "0448a9ffac8022f1c7eb5253746e24d11d9b6b2737c0aecd48335feabb95a179916b1f3a97bed6740a85a2d11c663d38566acfb08af48a47ce0c835c65c9b23d0d"); + EXPECT_EQ(key.account(coinTypeBc, nullptr)->address, "bc1qpsp72plnsqe6e2dvtsetxtww2cz36ztmfxghpd"); + EXPECT_EQ(key.account(coinTypeBc, nullptr)->publicKey, "02df9ef2a7a5552765178b181e1e1afdefc7849985c7dfe9647706dd4fa40df6ac"); +} + +TEST(StoredKey, MissingAddressReadd) { + auto key = StoredKey::load(testDataPath("missing-address.json")); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + + const auto wallet = key.wallet(gPassword); + EXPECT_EQ(wallet.getMnemonic(), "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"); + EXPECT_TRUE(Mnemonic::isValid(wallet.getMnemonic())); + + EXPECT_EQ(key.account(TWCoinTypeBitcoin)->address, ""); + EXPECT_EQ(key.account(TWCoinTypeEthereum)->address, ""); + + // get accounts, this will also fill addresses as they are empty + const auto btcAccount = key.account(TWCoinTypeBitcoin, &wallet); + const auto ethAccount = key.account(TWCoinTypeEthereum, &wallet); + + EXPECT_TRUE(btcAccount.has_value()); + EXPECT_EQ(btcAccount->address, "bc1qpsp72plnsqe6e2dvtsetxtww2cz36ztmfxghpd"); + EXPECT_TRUE(ethAccount.has_value()); + EXPECT_EQ(ethAccount->address, "0xA3Dcd899C0f3832DFDFed9479a9d828c6A4EB2A7"); +} + +TEST(StoredKey, EtherWalletAddressNo0x) { + auto key = StoredKey::load(testDataPath("ethereum-wallet-address-no-0x.json")); + key.fixAddresses(TW::data("15748c4e3dca6ae2110535576ab0c398cb79d985707c68ee6c9f9df9d421dd53")); + const auto account = key.account(TWCoinTypeEthereum, nullptr); + EXPECT_EQ(account->address, "0xAc1ec44E4f0ca7D172B7803f6836De87Fb72b309"); + EXPECT_EQ(account->publicKey, "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91"); +} + +TEST(StoredKey, CreateMinimalEncryptionParameters) { + const auto key = StoredKey::createWithMnemonic("name", gPassword, gMnemonic, TWStoredKeyEncryptionLevelMinimal); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + const Data& mnemo2Data = key.payload.decrypt(gPassword); + EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(gMnemonic)); + EXPECT_EQ(key.accounts.size(), 0ul); + EXPECT_EQ(key.wallet(gPassword).getMnemonic(), string(gMnemonic)); + + const auto json = key.json(); + + EXPECT_EQ(json["crypto"]["kdf"], "scrypt"); + EXPECT_EQ(json["crypto"]["kdfparams"]["n"], 4096); + + // load it back + const auto key2 = StoredKey::createWithJson(json); + EXPECT_EQ(key2.wallet(gPassword).getMnemonic(), string(gMnemonic)); +} + +TEST(StoredKey, CreateWeakEncryptionParameters) { + const auto key = StoredKey::createWithMnemonic("name", gPassword, gMnemonic, TWStoredKeyEncryptionLevelWeak); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + const Data& mnemo2Data = key.payload.decrypt(gPassword); + EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(gMnemonic)); + EXPECT_EQ(key.accounts.size(), 0ul); + EXPECT_EQ(key.wallet(gPassword).getMnemonic(), string(gMnemonic)); + + const auto json = key.json(); + + EXPECT_EQ(json["crypto"]["kdf"], "scrypt"); + EXPECT_EQ(json["crypto"]["kdfparams"]["n"], 16384); + + // load it back + const auto key2 = StoredKey::createWithJson(json); + EXPECT_EQ(key2.wallet(gPassword).getMnemonic(), string(gMnemonic)); +} + +TEST(StoredKey, CreateStandardEncryptionParameters) { + const auto key = StoredKey::createWithMnemonic("name", gPassword, gMnemonic, TWStoredKeyEncryptionLevelStandard); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + const Data& mnemo2Data = key.payload.decrypt(gPassword); + EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(gMnemonic)); + EXPECT_EQ(key.accounts.size(), 0ul); + EXPECT_EQ(key.wallet(gPassword).getMnemonic(), string(gMnemonic)); + + const auto json = key.json(); + + EXPECT_EQ(json["crypto"]["kdf"], "scrypt"); + EXPECT_EQ(json["crypto"]["kdfparams"]["n"], 262144); + + // load it back + const auto key2 = StoredKey::createWithJson(json); + EXPECT_EQ(key2.wallet(gPassword).getMnemonic(), string(gMnemonic)); +} + +TEST(StoredKey, CreateMultiAccounts) { // Multiple accounts for the same coin + auto key = StoredKey::createWithMnemonic("name", gPassword, gMnemonic, TWStoredKeyEncryptionLevelDefault); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + const Data& mnemo2Data = key.payload.decrypt(gPassword); + EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(gMnemonic)); + EXPECT_EQ(key.wallet(gPassword).getMnemonic(), string(gMnemonic)); + EXPECT_EQ(key.accounts.size(), 0ul); + + const auto expectedBtc1 = "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"; + const auto expectedBtc2 = "1NyRyFewhZcWMa9XCj3bBxSXPXyoSg8dKz"; + const auto expectedSol1 = "HiipoCKL8hX2RVmJTz3vaLy34hS2zLhWWMkUWtw85TmZ"; + const auto wallet = key.wallet(gPassword); + auto expectedAccounts = 0ul; + + { // Create default Bitcoin account + const auto coin = TWCoinTypeBitcoin; + + const auto btc1 = key.account(coin, &wallet); + + EXPECT_TRUE(btc1.has_value()); + EXPECT_EQ(btc1->address, expectedBtc1); + EXPECT_EQ(btc1->derivationPath.string(), "m/84'/0'/0'/0/0"); + EXPECT_EQ(btc1->extendedPublicKey, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); + EXPECT_EQ(key.accounts.size(), ++expectedAccounts); + EXPECT_EQ(key.accounts[expectedAccounts - 1].address, expectedBtc1); + EXPECT_EQ(key.account(coin)->address, expectedBtc1); + EXPECT_EQ(key.getAccounts(coin).size(), 1ul); + EXPECT_EQ(key.getAccounts(coin)[0].address, expectedBtc1); + } + { // Create default Solana account + const auto coin = TWCoinTypeSolana; + + const auto sol1 = key.account(coin, &wallet); + + EXPECT_TRUE(sol1.has_value()); + EXPECT_EQ(sol1->address, expectedSol1); + EXPECT_EQ(sol1->derivationPath.string(), "m/44'/501'/0'"); + EXPECT_EQ(key.accounts.size(), ++expectedAccounts); + EXPECT_EQ(key.accounts[expectedAccounts - 1].address, expectedSol1); + EXPECT_EQ(key.account(coin)->address, expectedSol1); + EXPECT_EQ(key.getAccounts(coin).size(), 1ul); + EXPECT_EQ(key.getAccounts(coin)[0].address, expectedSol1); + } + { // Create alternative P2PK Bitcoin account (different address format) + const auto coin = TWCoinTypeBitcoin; + + const auto btc2 = key.account(coin, TWDerivationBitcoinLegacy, wallet); + + EXPECT_EQ(btc2.address, expectedBtc2); + EXPECT_EQ(btc2.derivationPath.string(), "m/44'/0'/0'/0/0"); + EXPECT_EQ(btc2.extendedPublicKey, "xpub6CR52eaUuVb4kXAVyHC2i5ZuqJ37oWNPZFtjXaazFPXZD45DwWBYEBLdrF7fmCR9pgBuCA9Q57zZfyJjDUBDNtWkhWuGHNYKLgDHpqrHsxV"); + EXPECT_EQ(key.accounts.size(), ++expectedAccounts); + EXPECT_EQ(key.accounts[expectedAccounts - 1].address, expectedBtc2); + EXPECT_EQ(key.account(coin)->address, expectedBtc1); + EXPECT_EQ(key.account(coin, TWDerivationBitcoinLegacy, wallet).address, expectedBtc2); + EXPECT_EQ(key.getAccounts(coin).size(), 2ul); + EXPECT_EQ(key.getAccounts(coin)[0].address, expectedBtc1); + EXPECT_EQ(key.getAccounts(coin)[1].address, expectedBtc2); + } + { // Create alternative Solana account with non-default derivation path (different derivation path and address) + const auto coin = TWCoinTypeSolana; + + const auto sol2 = key.account(coin, TWDerivationSolanaSolana, wallet); + + const auto expectedSol2 = "CgWJeEWkiYqosy1ba7a3wn9HAQuHyK48xs3LM4SSDc1C"; + EXPECT_EQ(sol2.address, expectedSol2); + EXPECT_EQ(sol2.derivationPath.string(), "m/44'/501'/0'/0'"); + EXPECT_EQ(key.accounts.size(), ++expectedAccounts); + EXPECT_EQ(key.accounts[expectedAccounts - 1].address, expectedSol2); + // Now we have 2 Solana addresses, 1st is returned here + EXPECT_EQ(key.account(coin)->address, expectedSol1); + EXPECT_EQ(key.account(coin, TWDerivationSolanaSolana, wallet).address, expectedSol2); + EXPECT_EQ(key.getAccounts(coin).size(), 2ul); + EXPECT_EQ(key.getAccounts(coin)[0].address, expectedSol1); + EXPECT_EQ(key.getAccounts(coin)[1].address, expectedSol2); + } + { // Create CUSTOM account with alternative Bitcoin address. Note: this is not recommended. + const auto coin = TWCoinTypeBitcoin; + const auto customPath = DerivationPath("m/44'/2'/0'/0/0"); + const auto btcPrivateKey = wallet.getKey(coin, customPath); + EXPECT_NE(TW::deriveAddress(coin, btcPrivateKey), expectedBtc1); + const auto btcPublicKey = btcPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + const auto p2pkhBtcAddress = Bitcoin::Address(btcPublicKey, TWCoinTypeP2pkhPrefix(coin)).string(); + const auto expectedBtc3 = "1C43YUWSYTgaoBEsRffAkzF6HruJegEqP5"; + EXPECT_EQ(p2pkhBtcAddress, expectedBtc3); + const auto extendedPublicKey = wallet.getExtendedPublicKey(TW::purpose(coin), coin, TWHDVersionZPUB); + EXPECT_EQ(extendedPublicKey, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); + + key.addAccount(p2pkhBtcAddress, coin, TWDerivationCustom, customPath, hex(btcPublicKey.bytes), extendedPublicKey); + + EXPECT_EQ(key.accounts.size(), ++expectedAccounts); + EXPECT_EQ(key.accounts[expectedAccounts - 1].address, expectedBtc3); + // Now we have 2 Bitcoin addresses, 1st is returned here + EXPECT_EQ(key.account(coin)->address, expectedBtc1); + EXPECT_EQ(key.getAccounts(coin).size(), 3ul); + EXPECT_EQ(key.getAccounts(coin)[0].address, expectedBtc1); + EXPECT_EQ(key.getAccounts(coin)[1].address, expectedBtc2); + EXPECT_EQ(key.getAccounts(coin)[2].address, expectedBtc3); + EXPECT_EQ(key.getAccounts(coin)[2].derivationPath.string(), "m/44'/2'/0'/0/0"); + } +} + +TEST(StoredKey, CreateWithMnemonicAlternativeDerivation) { + const auto coin = TWCoinTypeSolana; + auto key = StoredKey::createWithMnemonicAddDefaultAddress("name", gPassword, gMnemonic, coin); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + + ASSERT_EQ(key.accounts.size(), 1ul); + EXPECT_EQ(key.accounts[0].coin, coin); + EXPECT_EQ(key.accounts[0].address, "HiipoCKL8hX2RVmJTz3vaLy34hS2zLhWWMkUWtw85TmZ"); + EXPECT_EQ(key.accounts[0].publicKey, "f86b18399096c8134dd185f1e72dd7e26528772a2a998abfd81c5f8c547223d0"); + EXPECT_EQ(hex(key.privateKey(coin, gPassword).bytes), "d81b5c525979e487736b69cb84ed8331559de17294f38491b304555c26687e83"); + EXPECT_EQ(hex(key.privateKey(coin, TWDerivationDefault, gPassword).bytes), "d81b5c525979e487736b69cb84ed8331559de17294f38491b304555c26687e83"); + ASSERT_EQ(key.accounts.size(), 1ul); + + // alternative derivation, different keys + EXPECT_EQ(hex(key.privateKey(coin, TWDerivationSolanaSolana, gPassword).bytes), "d49a5fa7f77593534c7afd2ba8dc8e9d8b007bc6ec65fe8df25ffe6fafc57151"); + + ASSERT_EQ(key.accounts.size(), 2ul); + EXPECT_EQ(key.accounts[1].coin, coin); + EXPECT_EQ(key.accounts[1].address, "CgWJeEWkiYqosy1ba7a3wn9HAQuHyK48xs3LM4SSDc1C"); + EXPECT_EQ(key.accounts[1].publicKey, "ad8f57924dce62f9040f93b4f6ce3c3d39afde7e29bcb4013dad59db7913c4c7"); + EXPECT_EQ(hex(key.privateKey(coin, TWDerivationSolanaSolana, gPassword).bytes), "d49a5fa7f77593534c7afd2ba8dc8e9d8b007bc6ec65fe8df25ffe6fafc57151"); +} + +} // namespace TW::Keystore diff --git a/tests/MnemonicTests.cpp b/tests/common/MnemonicTests.cpp similarity index 100% rename from tests/MnemonicTests.cpp rename to tests/common/MnemonicTests.cpp diff --git a/tests/common/NumericLiteralTests.cpp b/tests/common/NumericLiteralTests.cpp new file mode 100644 index 00000000000..07813e2f48e --- /dev/null +++ b/tests/common/NumericLiteralTests.cpp @@ -0,0 +1,14 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "NumericLiteral.h" + +#include + +TEST(UzLitteralOperator, SizetEquality) { + [[maybe_unused]] auto size = 42_uz; + static_assert(std::is_same_v); +} \ No newline at end of file diff --git a/tests/PrivateKeyTests.cpp b/tests/common/PrivateKeyTests.cpp similarity index 71% rename from tests/PrivateKeyTests.cpp rename to tests/common/PrivateKeyTests.cpp index f774f7d8eb6..f969ce40c8e 100644 --- a/tests/PrivateKeyTests.cpp +++ b/tests/common/PrivateKeyTests.cpp @@ -1,19 +1,20 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +#include "Hash.h" +#include "HexCoding.h" #include "PrivateKey.h" #include "PublicKey.h" -#include "HexCoding.h" -#include "Hash.h" #include using namespace TW; using namespace std; +namespace TW::tests { TEST(PrivateKey, CreateValid) { Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); @@ -55,9 +56,9 @@ TEST(PrivateKey, InvalidSECP256k1) { } } -string TestInvalidExtended(const Data& data, const Data& ext, const Data& chainCode) { +string TestInvalidExtended(const Data& data, const Data& ext, const Data& chainCode, const Data& data2, const Data& ext2, const Data& chainCode2) { try { - auto privateKey = PrivateKey(data, ext, chainCode); + auto privateKey = PrivateKey(data, ext, chainCode, data2, ext2, chainCode2); return hex(privateKey.bytes); } catch (invalid_argument& ex) { // expected exception @@ -70,24 +71,40 @@ TEST(PrivateKey, CreateExtendedInvalid) { string res = TestInvalidExtended( parse_hex("deadbeed"), parse_hex("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff"), - parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4") - ); + parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4"), + parse_hex("1111111111111111111111111111111111111111111111111111111111111111"), + parse_hex("1111111111111111111111111111111111111111111111111111111111111111"), + parse_hex("1111111111111111111111111111111111111111111111111111111111111111")); EXPECT_EQ("EXCEPTION: Invalid private key or extended key data", res); } { string res = TestInvalidExtended( parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), parse_hex("deadbeed"), - parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4") - ); + parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4"), + parse_hex("1111111111111111111111111111111111111111111111111111111111111111"), + parse_hex("1111111111111111111111111111111111111111111111111111111111111111"), + parse_hex("1111111111111111111111111111111111111111111111111111111111111111")); EXPECT_EQ("EXCEPTION: Invalid private key or extended key data", res); } { string res = TestInvalidExtended( parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), parse_hex("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff"), - parse_hex("deadbeed") - ); + parse_hex("deadbeed"), + parse_hex("1111111111111111111111111111111111111111111111111111111111111111"), + parse_hex("1111111111111111111111111111111111111111111111111111111111111111"), + parse_hex("1111111111111111111111111111111111111111111111111111111111111111")); + EXPECT_EQ("EXCEPTION: Invalid private key or extended key data", res); + } + { + string res = TestInvalidExtended( + parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), + parse_hex("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff"), + parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4"), + parse_hex("deadbeed"), + parse_hex("1111111111111111111111111111111111111111111111111111111111111111"), + parse_hex("1111111111111111111111111111111111111111111111111111111111111111")); EXPECT_EQ("EXCEPTION: Invalid private key or extended key data", res); } } @@ -105,29 +122,25 @@ TEST(PrivateKey, PublicKey) { const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); EXPECT_EQ( "4870d56d074c50e891506d78faa4fb69ca039cc5f131eb491e166b975880e867", - hex(publicKey.bytes) - ); + hex(publicKey.bytes)); } { const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); EXPECT_EQ( "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1", - hex(publicKey.bytes) - ); + hex(publicKey.bytes)); } { const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); EXPECT_EQ( "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91", - hex(publicKey.bytes) - ); + hex(publicKey.bytes)); } { const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1Extended); EXPECT_EQ( "046d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab918b4fe46ccbf56701fb210d67d91c5779468f6b3fdc7a63692b9b62543f47ae", - hex(publicKey.bytes) - ); + hex(publicKey.bytes)); } } @@ -147,41 +160,60 @@ TEST(PrivateKey, Cleanup) { // Note: it would be good to check the memory area after deletion of the object, but this is not possible } +TEST(PrivateKey, GetType) { + EXPECT_EQ(PrivateKey::getType(TWCurveSECP256k1), TWPrivateKeyTypeDefault); + EXPECT_EQ(PrivateKey::getType(TWCurveNIST256p1), TWPrivateKeyTypeDefault); + EXPECT_EQ(PrivateKey::getType(TWCurveED25519), TWPrivateKeyTypeDefault); + EXPECT_EQ(PrivateKey::getType(TWCurveCurve25519), TWPrivateKeyTypeDefault); + + EXPECT_EQ(PrivateKey::getType(TWCurveED25519ExtendedCardano), TWPrivateKeyTypeCardano); +} + TEST(PrivateKey, PrivateKeyExtended) { // Non-extended: both keys are 32 bytes. auto privateKeyNonext = PrivateKey(parse_hex( - "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5" - )); + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); EXPECT_EQ("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", hex(privateKeyNonext.bytes)); auto publicKeyNonext = privateKeyNonext.getPublicKey(TWPublicKeyTypeED25519); - EXPECT_EQ(32, publicKeyNonext.bytes.size()); - - // Extended keys: private key is 3x32 bytes, public key is 64 bytes - auto privateKeyExt = PrivateKey(parse_hex( - "b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71effbf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4" - )); - EXPECT_EQ("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744", hex(privateKeyExt.bytes)); - EXPECT_EQ("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff", hex(privateKeyExt.extensionBytes)); - EXPECT_EQ("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4", hex(privateKeyExt.chainCodeBytes)); - auto publicKeyExt = privateKeyExt.getPublicKey(TWPublicKeyTypeED25519Extended); - EXPECT_EQ(64, publicKeyExt.bytes.size()); + EXPECT_EQ(32ul, publicKeyNonext.bytes.size()); + + const auto fullkey = + "b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744" + "309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff" + "bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4" + "639aadd8b6499ae39b78018b79255fbd8f585cbda9cbb9e907a72af86afb7a05" + "d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7b" + "ed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a"; + // Extended keys: private key is 2x3x32 bytes, public key is 2x64 bytes + auto privateKeyExt = PrivateKey(parse_hex(fullkey)); + EXPECT_EQ(fullkey, hex(privateKeyExt.bytes)); + EXPECT_EQ("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744", hex(privateKeyExt.key())); + EXPECT_EQ("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff", hex(privateKeyExt.extension())); + EXPECT_EQ("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4", hex(privateKeyExt.chainCode())); + EXPECT_EQ("639aadd8b6499ae39b78018b79255fbd8f585cbda9cbb9e907a72af86afb7a05", hex(privateKeyExt.secondKey())); + EXPECT_EQ("d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7b", hex(privateKeyExt.secondExtension())); + EXPECT_EQ("ed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a", hex(privateKeyExt.secondChainCode())); + + auto publicKeyExt = privateKeyExt.getPublicKey(TWPublicKeyTypeED25519Cardano); + EXPECT_EQ(2 * 64ul, publicKeyExt.bytes.size()); // Try other constructor for extended key - auto privateKeyExtOne = PrivateKey(parse_hex( - "b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71effbf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4" - )); - EXPECT_EQ("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744", hex(privateKeyExtOne.bytes)); - EXPECT_EQ("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff", hex(privateKeyExtOne.extensionBytes)); - EXPECT_EQ("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4", hex(privateKeyExtOne.chainCodeBytes)); + auto privateKeyExtOne = PrivateKey( + parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), + parse_hex("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff"), + parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4"), + parse_hex("639aadd8b6499ae39b78018b79255fbd8f585cbda9cbb9e907a72af86afb7a05"), + parse_hex("d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7b"), + parse_hex("ed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a")); + EXPECT_EQ(fullkey, hex(privateKeyExt.bytes)); } TEST(PrivateKey, PrivateKeyExtendedError) { - // TWPublicKeyTypeED25519Extended pubkey with non-extended private: error + // TWPublicKeyTypeED25519Cardano pubkey with non-extended private: error auto privateKeyNonext = PrivateKey(parse_hex( - "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5" - )); + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); try { - auto publicKeyError = privateKeyNonext.getPublicKey(TWPublicKeyTypeED25519Extended); + auto publicKeyError = privateKeyNonext.getPublicKey(TWPublicKeyTypeED25519Cardano); } catch (invalid_argument& ex) { // expected exception return; @@ -203,8 +235,7 @@ TEST(PrivateKey, getSharedKey) { EXPECT_EQ( "ef2cf705af8714b35c0855030f358f2bee356ff3579cea2607b2025d80133c3a", - hex(derivedKeyData) - ); + hex(derivedKeyData)); } /** @@ -224,12 +255,11 @@ TEST(PrivateKey, getSharedKeyWycherproof) { EXPECT_TRUE(publicKey.isCompressed()); const Data derivedKeyData = privateKey.getSharedKey(publicKey, TWCurveSECP256k1); - + // SHA-256 of encoded x-coordinate `02544dfae22af6af939042b1d85b71a1e49e9a5614123c4d6ad0c8af65baf87d65` EXPECT_EQ( "81165066322732362ca5d3f0991d7f1f7d0aad7ea533276496785d369e35159a", - hex(derivedKeyData) - ); + hex(derivedKeyData)); } TEST(PrivateKey, getSharedKeyBidirectional) { @@ -271,40 +301,29 @@ TEST(PrivateKey, SignSECP256k1) { EXPECT_EQ( "8720a46b5b3963790d94bcc61ad57ca02fd153584315bfa161ed3455e336ba624d68df010ed934b8792c5b6a57ba86c3da31d039f9612b44d1bf054132254de901", - hex(actual) - ); + hex(actual)); } TEST(PrivateKey, SignExtended) { const auto privateKeyExt = PrivateKey(parse_hex( "b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71effbf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4" - )); + "639aadd8b6499ae39b78018b79255fbd8f585cbda9cbb9e907a72af86afb7a05d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7bed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a")); Data messageData = TW::data("hello"); Data hash = Hash::keccak256(messageData); - Data actual = privateKeyExt.sign(hash, TWCurveED25519Extended); + Data actual = privateKeyExt.sign(hash, TWCurveED25519ExtendedCardano); EXPECT_EQ( "375df53b6a4931dcf41e062b1c64288ed4ff3307f862d5c1b1c71964ce3b14c99422d0fdfeb2807e9900a26d491d5e8a874c24f98eec141ed694d7a433a90f08", - hex(actual) - ); + hex(actual)); } TEST(PrivateKey, SignSchnorr) { const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); const Data messageData = TW::data("hello schnorr"); const Data digest = Hash::sha256(messageData); - const auto signature = privateKey.signSchnorr(digest, TWCurveSECP256k1); + const auto signature = privateKey.signZilliqa(digest); EXPECT_EQ(hex(signature), - "b8118ccb99563fe014279c957b0a9d563c1666e00367e9896fe541765246964f64a53052513da4e6dc20fdaf69ef0d95b4ca51c87ad3478986cf053c2dd0b853" - ); -} - -TEST(PrivateKey, SignSchnorrWrongType) { - const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); - const Data messageData = TW::data("hello schnorr"); - const Data digest = Hash::sha256(messageData); - const auto signature = privateKey.signSchnorr(digest, TWCurveNIST256p1); - EXPECT_EQ(signature.size(), 0); + "b8118ccb99563fe014279c957b0a9d563c1666e00367e9896fe541765246964f64a53052513da4e6dc20fdaf69ef0d95b4ca51c87ad3478986cf053c2dd0b853"); } TEST(PrivateKey, SignNIST256p1) { @@ -316,11 +335,10 @@ TEST(PrivateKey, SignNIST256p1) { EXPECT_EQ( "8859e63a0c0cc2fc7f788d7e78406157b288faa6f76f76d37c4cd1534e8d83c468f9fd6ca7dde378df594625dcde98559389569e039282275e3d87c26e36447401", - hex(actual) - ); + hex(actual)); } -int isCanonical(uint8_t by, uint8_t sig[64]) { +int isCanonical([[maybe_unused]] uint8_t by, [[maybe_unused]] uint8_t sig[64]) { return 1; } @@ -333,8 +351,7 @@ TEST(PrivateKey, SignCanonicalSECP256k1) { EXPECT_EQ( "208720a46b5b3963790d94bcc61ad57ca02fd153584315bfa161ed3455e336ba624d68df010ed934b8792c5b6a57ba86c3da31d039f9612b44d1bf054132254de9", - hex(actual) - ); + hex(actual)); } TEST(PrivateKey, SignShortDigest) { @@ -343,14 +360,16 @@ TEST(PrivateKey, SignShortDigest) { Data shortDigest = TW::data("12345"); { Data actual = privateKey.sign(shortDigest, TWCurveSECP256k1); - EXPECT_EQ(actual.size(), 0); + EXPECT_EQ(actual.size(), 0ul); } { Data actual = privateKey.sign(shortDigest, TWCurveNIST256p1); - EXPECT_EQ(actual.size(), 0); + EXPECT_EQ(actual.size(), 0ul); } { Data actual = privateKey.sign(shortDigest, TWCurveSECP256k1, isCanonical); - EXPECT_EQ(actual.size(), 0); + EXPECT_EQ(actual.size(), 0ul); } } + +} // namespace TW::tests diff --git a/tests/PublicKeyTests.cpp b/tests/common/PublicKeyTests.cpp similarity index 57% rename from tests/PublicKeyTests.cpp rename to tests/common/PublicKeyTests.cpp index 7f056f3c8f4..1dae03fb237 100644 --- a/tests/PublicKeyTests.cpp +++ b/tests/common/PublicKeyTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -9,7 +9,7 @@ #include "Hash.h" #include "HexCoding.h" #include "PrivateKey.h" -#include "interface/TWTestUtilities.h" +#include "TestUtilities.h" #include @@ -19,7 +19,7 @@ TEST(PublicKeyTests, CreateFromPrivateSecp256k1) { const Data key = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); auto privateKey = PrivateKey(key); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - EXPECT_EQ(publicKey.bytes.size(), 33); + EXPECT_EQ(publicKey.bytes.size(), 33ul); EXPECT_EQ(hex(publicKey.bytes), "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); EXPECT_EQ(publicKey.isCompressed(), true); EXPECT_TRUE(PublicKey::isValid(publicKey.bytes, TWPublicKeyTypeSECP256k1)); @@ -47,7 +47,7 @@ TEST(PublicKeyTests, CreateBlake) { { auto publicKey = PrivateKey(parse_hex(privateKeyHex)).getPublicKey(TWPublicKeyTypeED25519Blake2b); EXPECT_EQ(hex(publicKey.bytes), publicKeyKeyHex); - EXPECT_EQ(publicKey.bytes.size(), 32); + EXPECT_EQ(publicKey.bytes.size(), 32ul); } { const auto publicKey = PublicKey(parse_hex(publicKeyKeyHex), TWPublicKeyTypeED25519Blake2b); @@ -60,14 +60,14 @@ TEST(PublicKeyTests, CompressedExtended) { auto privateKey = PrivateKey(key); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); EXPECT_EQ(publicKey.type, TWPublicKeyTypeSECP256k1); - EXPECT_EQ(publicKey.bytes.size(), 33); + EXPECT_EQ(publicKey.bytes.size(), 33ul); EXPECT_EQ(publicKey.isCompressed(), true); EXPECT_TRUE(PublicKey::isValid(publicKey.bytes, TWPublicKeyTypeSECP256k1)); EXPECT_EQ(hex(publicKey.bytes), std::string("0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1")); auto extended = publicKey.extended(); EXPECT_EQ(extended.type, TWPublicKeyTypeSECP256k1Extended); - EXPECT_EQ(extended.bytes.size(), 65); + EXPECT_EQ(extended.bytes.size(), 65ul); EXPECT_EQ(extended.isCompressed(), false); EXPECT_TRUE(PublicKey::isValid(extended.bytes, TWPublicKeyTypeSECP256k1Extended)); EXPECT_EQ(hex(extended.bytes), std::string("0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91")); @@ -75,20 +75,20 @@ TEST(PublicKeyTests, CompressedExtended) { auto compressed = extended.compressed(); EXPECT_EQ(compressed.type, TWPublicKeyTypeSECP256k1); EXPECT_TRUE(compressed == publicKey); - EXPECT_EQ(compressed.bytes.size(), 33); + EXPECT_EQ(compressed.bytes.size(), 33ul); EXPECT_EQ(compressed.isCompressed(), true); EXPECT_TRUE(PublicKey::isValid(compressed.bytes, TWPublicKeyTypeSECP256k1)); EXPECT_EQ(hex(compressed.bytes), std::string("0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1")); auto extended2 = extended.extended(); EXPECT_EQ(extended2.type, TWPublicKeyTypeSECP256k1Extended); - EXPECT_EQ(extended2.bytes.size(), 65); + EXPECT_EQ(extended2.bytes.size(), 65ul); EXPECT_EQ(extended2.isCompressed(), false); auto compressed2 = compressed.compressed(); EXPECT_EQ(compressed2.type, TWPublicKeyTypeSECP256k1); EXPECT_TRUE(compressed2 == publicKey); - EXPECT_EQ(compressed2.bytes.size(), 33); + EXPECT_EQ(compressed2.bytes.size(), 33ul); EXPECT_EQ(compressed2.isCompressed(), true); } @@ -97,14 +97,14 @@ TEST(PublicKeyTests, CompressedExtendedNist) { auto privateKey = PrivateKey(key); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1); EXPECT_EQ(publicKey.type, TWPublicKeyTypeNIST256p1); - EXPECT_EQ(publicKey.bytes.size(), 33); + EXPECT_EQ(publicKey.bytes.size(), 33ul); EXPECT_EQ(publicKey.isCompressed(), true); EXPECT_TRUE(PublicKey::isValid(publicKey.bytes, TWPublicKeyTypeNIST256p1)); EXPECT_EQ(hex(publicKey.bytes), std::string("026d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab")); auto extended = publicKey.extended(); EXPECT_EQ(extended.type, TWPublicKeyTypeNIST256p1Extended); - EXPECT_EQ(extended.bytes.size(), 65); + EXPECT_EQ(extended.bytes.size(), 65ul); EXPECT_EQ(extended.isCompressed(), false); EXPECT_TRUE(PublicKey::isValid(extended.bytes, TWPublicKeyTypeNIST256p1Extended)); EXPECT_EQ(hex(extended.bytes), std::string("046d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab918b4fe46ccbf56701fb210d67d91c5779468f6b3fdc7a63692b9b62543f47ae")); @@ -112,20 +112,20 @@ TEST(PublicKeyTests, CompressedExtendedNist) { auto compressed = extended.compressed(); EXPECT_EQ(compressed.type, TWPublicKeyTypeNIST256p1); EXPECT_TRUE(compressed == publicKey); - EXPECT_EQ(compressed.bytes.size(), 33); + EXPECT_EQ(compressed.bytes.size(), 33ul); EXPECT_EQ(compressed.isCompressed(), true); EXPECT_TRUE(PublicKey::isValid(compressed.bytes, TWPublicKeyTypeNIST256p1)); EXPECT_EQ(hex(compressed.bytes), std::string("026d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab")); auto extended2 = extended.extended(); EXPECT_EQ(extended2.type, TWPublicKeyTypeNIST256p1Extended); - EXPECT_EQ(extended2.bytes.size(), 65); + EXPECT_EQ(extended2.bytes.size(), 65ul); EXPECT_EQ(extended2.isCompressed(), false); auto compressed2 = compressed.compressed(); EXPECT_EQ(compressed2.type, TWPublicKeyTypeNIST256p1); EXPECT_TRUE(compressed2 == publicKey); - EXPECT_EQ(compressed2.bytes.size(), 33); + EXPECT_EQ(compressed2.bytes.size(), 33ul); EXPECT_EQ(compressed2.isCompressed(), true); } @@ -134,7 +134,7 @@ TEST(PublicKeyTests, CompressedExtendedED25519) { auto privateKey = PrivateKey(key); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); EXPECT_EQ(publicKey.type, TWPublicKeyTypeED25519); - EXPECT_EQ(publicKey.bytes.size(), 32); + EXPECT_EQ(publicKey.bytes.size(), 32ul); EXPECT_EQ(publicKey.isCompressed(), true); EXPECT_TRUE(PublicKey::isValid(publicKey.bytes, TWPublicKeyTypeED25519)); EXPECT_EQ(hex(publicKey.bytes), std::string("4870d56d074c50e891506d78faa4fb69ca039cc5f131eb491e166b975880e867")); @@ -142,13 +142,13 @@ TEST(PublicKeyTests, CompressedExtendedED25519) { auto extended = publicKey.extended(); EXPECT_EQ(extended.type, TWPublicKeyTypeED25519); EXPECT_TRUE(extended == publicKey); - EXPECT_EQ(extended.bytes.size(), 32); + EXPECT_EQ(extended.bytes.size(), 32ul); EXPECT_EQ(extended.isCompressed(), true); auto compressed = publicKey.compressed(); EXPECT_EQ(compressed.type, TWPublicKeyTypeED25519); EXPECT_TRUE(compressed == publicKey); - EXPECT_EQ(compressed.bytes.size(), 32); + EXPECT_EQ(compressed.bytes.size(), 32ul); EXPECT_EQ(compressed.isCompressed(), true); } @@ -157,8 +157,7 @@ TEST(PublicKeyTests, IsValidWrongType) { } TEST(PublicKeyTests, Verify) { - const auto key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); - const auto privateKey = PrivateKey(key); + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); const char* message = "Hello"; const Data messageData = TW::data(message); @@ -190,19 +189,40 @@ TEST(PublicKeyTests, Verify) { } } -TEST(PublicKeyTests, VerifyEd25519Extended) { - const auto key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); - const auto privateKey = PrivateKey(key); +TEST(PublicKeyTests, VerifyAsDER) { + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); - const Data messageData = TW::data("Hello"); + const char* message = "Hello"; + const Data messageData = TW::data(message); const Data digest = Hash::sha256(messageData); - try { - privateKey.sign(digest, TWCurveED25519Extended); - } catch (const std::invalid_argument&) { - return; // OK, not implemented + const auto signature = privateKey.signAsDER(digest); + EXPECT_EQ(signature.size(), 70ul); + EXPECT_EQ(hex(signature), "304402200f5d5a9e5fc4b82a625312f3be5d3e8ad017d882de86c72c92fcefa924e894c102202071772a14201a3a0debf381b5e8dea39fadb9bcabdc02ee71ab018f55bf717f"); + + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(publicKey.bytes), "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); + + EXPECT_TRUE(publicKey.verifyAsDER(signature, digest)); + + EXPECT_FALSE(publicKey.verify(signature, digest)); + + { // Negative: wrong key type + const auto publicKeyWrong = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1Extended); + EXPECT_FALSE(publicKeyWrong.verifyAsDER(signature, digest)); } - FAIL() << "Missing expected exception"; +} + +TEST(PublicKeyTests, VerifyEd25519Extended) { + const auto privateKey = PrivateKey(parse_hex("e8c8c5b2df13f3abed4e6b1609c808e08ff959d7e6fc3d849e3f2880550b574437aa559095324d78459b9bb2da069da32337e1cc5da78f48e1bd084670107f3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26fae0d152bb611cb9ff34e945e4ff627e6fba81da687a601a879759cd76530b5744424db69a75edd4780a5fbc05d1a3c84ac4166ff8e424808481dd8e77627ce5f5bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + + const auto message = TW::data("Hello"); + const auto digest = Hash::sha256(message); + const auto signature = privateKey.sign(digest, TWCurveED25519ExtendedCardano); + const auto valid = publicKey.verify(signature, digest); + + EXPECT_TRUE(valid); } TEST(PublicKeyTests, VerifySchnorr) { @@ -212,9 +232,9 @@ TEST(PublicKeyTests, VerifySchnorr) { const Data messageData = TW::data("hello schnorr"); const Data digest = Hash::sha256(messageData); - const auto signature = privateKey.signSchnorr(digest, TWCurveSECP256k1); + const auto signature = privateKey.signZilliqa(digest); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - EXPECT_TRUE(publicKey.verifySchnorr(signature, digest)); + EXPECT_TRUE(publicKey.verifyZilliqa(signature, digest)); EXPECT_EQ(hex(signature), "b8118ccb99563fe014279c957b0a9d563c1666e00367e9896fe541765246964f64a53052513da4e6dc20fdaf69ef0d95b4ca51c87ad3478986cf053c2dd0b853"); } @@ -225,18 +245,94 @@ TEST(PublicKeyTests, VerifySchnorrWrongType) { const Data messageData = TW::data("hello schnorr"); const Data digest = Hash::sha256(messageData); - const auto signature = privateKey.signSchnorr(digest, TWCurveSECP256k1); + const auto signature = privateKey.signZilliqa(digest); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1); - EXPECT_FALSE(publicKey.verifySchnorr(signature, digest)); + EXPECT_FALSE(publicKey.verifyZilliqa(signature, digest)); } -TEST(PublicKeyTests, Recover) { +TEST(PublicKeyTests, RecoverRaw) { + { + const auto message = parse_hex("de4e9524586d6fce45667f9ff12f661e79870c4105fa0fb58af976619bb11432"); + const auto signature = parse_hex("00000000000000000000000000000000000000000000000000000000000000020123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"); + { + const auto publicKey = PublicKey::recoverRaw(signature, 1ul, message); + EXPECT_EQ(publicKey.type, TWPublicKeyTypeSECP256k1Extended); + EXPECT_EQ(hex(publicKey.bytes), "0456d8089137b1fd0d890f8c7d4a04d0fd4520a30b19518ee87bd168ea12ed8090329274c4c6c0d9df04515776f2741eeffc30235d596065d718c3973e19711ad0"); + } + { // same data but different recId -> different result + const auto publicKey = PublicKey::recoverRaw(signature, 0ul, message); + EXPECT_EQ(publicKey.type, TWPublicKeyTypeSECP256k1Extended); + EXPECT_EQ(hex(publicKey.bytes), "043fc5bf5fec35b6ffe6fd246226d312742a8c296bfa57dd22da509a2e348529b7ddb9faf8afe1ecda3c05e7b2bda47ee1f5a87e952742b22afca560b29d972fcf"); + } + } + { + const auto message = parse_hex("6468eb103d51c9a683b51818fdb73390151c9973831d2cfb4e9587ad54273155"); + const auto signature = parse_hex("92c336138f7d0231fe9422bb30ee9ef10bf222761fe9e04442e3a11e88880c646487026011dae03dc281bc21c7d7ede5c2226d197befb813a4ecad686b559e58"); + const auto recovered = PublicKey::recoverRaw(signature, 0ul, message); + EXPECT_EQ(hex(recovered.bytes), "0463ade8ebc212b85e7e4278dc3dcb4f9cc18aab912ef5d302b5d1940e772e9e1a9213522efddad487bbd5dd7907e8e776f918e9a5e4cb51893724e9fe76792a4f"); + } +} + +TEST(PublicKeyTests, SignAndRecoverRaw) { + const auto privateKey = PrivateKey(parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + EXPECT_EQ(hex(publicKey.bytes), "0463ade8ebc212b85e7e4278dc3dcb4f9cc18aab912ef5d302b5d1940e772e9e1a9213522efddad487bbd5dd7907e8e776f918e9a5e4cb51893724e9fe76792a4f"); + const auto message = parse_hex("6468eb103d51c9a683b51818fdb73390151c9973831d2cfb4e9587ad54273155"); + + // sign + const auto signature = privateKey.sign(message, TWCurveSECP256k1); + EXPECT_EQ(hex(signature), "92c336138f7d0231fe9422bb30ee9ef10bf222761fe9e04442e3a11e88880c646487026011dae03dc281bc21c7d7ede5c2226d197befb813a4ecad686b559e5800"); + + // revocer + const auto pubkeyRecovered = PublicKey::recoverRaw(signature, signature[64], message); + EXPECT_EQ(hex(pubkeyRecovered.bytes), hex(publicKey.bytes)); + EXPECT_EQ(hex(pubkeyRecovered.bytes), "0463ade8ebc212b85e7e4278dc3dcb4f9cc18aab912ef5d302b5d1940e772e9e1a9213522efddad487bbd5dd7907e8e776f918e9a5e4cb51893724e9fe76792a4f"); +} + +TEST(PublicKeyTests, RecoverRawNegative) { const auto message = parse_hex("de4e9524586d6fce45667f9ff12f661e79870c4105fa0fb58af976619bb11432"); - const auto signature = parse_hex("00000000000000000000000000000000000000000000000000000000000000020123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef80"); - const auto publicKey = PublicKey::recover(signature, message); - EXPECT_EQ(publicKey.type, TWPublicKeyTypeSECP256k1Extended); - EXPECT_EQ(hex(publicKey.bytes), - "0456d8089137b1fd0d890f8c7d4a04d0fd4520a30b19518ee87bd168ea12ed8090329274c4c6c0d9df04515776f2741eeffc30235d596065d718c3973e19711ad0"); + const auto signature = parse_hex("00000000000000000000000000000000000000000000000000000000000000020123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"); + // recid >= 4 + EXPECT_EXCEPTION(PublicKey::recoverRaw(signature, 4ul, message), "Invalid recId"); + // signature too short + EXPECT_EXCEPTION(PublicKey::recoverRaw(parse_hex("00000000000000000000000000000000000000000000000000000000000000020123456789abcdef0123456789abcdef0123456789abcdef0123456789abcd"), 1ul, message), + "signature too short"); + // Digest too short + EXPECT_EXCEPTION(PublicKey::recoverRaw(signature, 1ul, parse_hex("de4e9524586d6fce45667f9ff12f661e79870c4105fa0fb58af976619bb114")), + "digest too short"); +} + +TEST(PublicKeyTests, Recover) { + { + const auto message = parse_hex("de4e9524586d6fce45667f9ff12f661e79870c4105fa0fb58af976619bb11432"); + const auto signature = parse_hex("00000000000000000000000000000000000000000000000000000000000000020123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef80"); + const auto publicKey = PublicKey::recover(signature, message); + EXPECT_EQ(publicKey.type, TWPublicKeyTypeSECP256k1Extended); + EXPECT_EQ(hex(publicKey.bytes), + "0456d8089137b1fd0d890f8c7d4a04d0fd4520a30b19518ee87bd168ea12ed8090329274c4c6c0d9df04515776f2741eeffc30235d596065d718c3973e19711ad0"); + } + + const auto privateKey = PrivateKey(parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + EXPECT_EQ(hex(publicKey.bytes), "0463ade8ebc212b85e7e4278dc3dcb4f9cc18aab912ef5d302b5d1940e772e9e1a9213522efddad487bbd5dd7907e8e776f918e9a5e4cb51893724e9fe76792a4f"); + { + const auto message = parse_hex("6468eb103d51c9a683b51818fdb73390151c9973831d2cfb4e9587ad54273155"); + const auto signature = parse_hex("92c336138f7d0231fe9422bb30ee9ef10bf222761fe9e04442e3a11e88880c646487026011dae03dc281bc21c7d7ede5c2226d197befb813a4ecad686b559e5800"); + const auto recovered = PublicKey::recover(signature, message); + EXPECT_EQ(hex(recovered.bytes), hex(publicKey.bytes)); + } + { // same with v=27 + const auto message = parse_hex("6468eb103d51c9a683b51818fdb73390151c9973831d2cfb4e9587ad54273155"); + const auto signature = parse_hex("92c336138f7d0231fe9422bb30ee9ef10bf222761fe9e04442e3a11e88880c646487026011dae03dc281bc21c7d7ede5c2226d197befb813a4ecad686b559e581b"); + const auto recovered = PublicKey::recover(signature, message); + EXPECT_EQ(hex(recovered.bytes), hex(publicKey.bytes)); + } + { // same with v=35+2 + const auto message = parse_hex("6468eb103d51c9a683b51818fdb73390151c9973831d2cfb4e9587ad54273155"); + const auto signature = parse_hex("92c336138f7d0231fe9422bb30ee9ef10bf222761fe9e04442e3a11e88880c646487026011dae03dc281bc21c7d7ede5c2226d197befb813a4ecad686b559e5825"); + const auto recovered = PublicKey::recover(signature, message); + EXPECT_EQ(hex(recovered.bytes), hex(publicKey.bytes)); + } } TEST(PublicKeyTests, isValidED25519) { diff --git a/tests/interface/TWTestUtilities.cpp b/tests/common/TestUtilities.cpp similarity index 84% rename from tests/interface/TWTestUtilities.cpp rename to tests/common/TestUtilities.cpp index 8ca953eb666..76e34fd35f1 100644 --- a/tests/interface/TWTestUtilities.cpp +++ b/tests/common/TestUtilities.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -15,9 +15,6 @@ using namespace std; string getTestTempDir(void) { // In general, tests should not use hardcoded "/tmp", but TEST_TMPDIR env var. const char* fromEnvironment = getenv("TEST_TMPDIR"); -#ifdef _WIN32 - if (fromEnvironment == NULL || fromEnvironment[0] == '\0') { fromEnvironment = getenv("TMP"); } -#endif if (fromEnvironment == NULL || fromEnvironment[0] == '\0') { return "/tmp"; } return string(fromEnvironment); } diff --git a/tests/interface/TWTestUtilities.h b/tests/common/TestUtilities.h similarity index 95% rename from tests/interface/TWTestUtilities.h rename to tests/common/TestUtilities.h index 1adc36b9ac0..78c0d89cb39 100644 --- a/tests/interface/TWTestUtilities.h +++ b/tests/common/TestUtilities.h @@ -30,10 +30,15 @@ inline void assertHexEqual(const std::shared_ptr& data, const char* expe assertStringsEqual(hex, expected); } + +inline void assertJSONEqual(const nlohmann::json& lhs, const nlohmann::json& rhs) { + ASSERT_EQ(lhs, rhs); +} + inline void assertJSONEqual(const std::string& lhs, const char* expected) { auto lhsJson = nlohmann::json::parse(lhs); auto rhsJson = nlohmann::json::parse(std::string(expected)); - ASSERT_EQ(lhsJson.dump(), rhsJson.dump()); + return assertJSONEqual(lhsJson, rhsJson); } inline std::vector* dataFromTWData(TWData* data) { diff --git a/tests/common/TransactionCompilerTests.cpp b/tests/common/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..e2d26f1d07d --- /dev/null +++ b/tests/common/TransactionCompilerTests.cpp @@ -0,0 +1,437 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include "TransactionCompiler.h" +#include "Coin.h" +#include "proto/Common.pb.h" +#include "proto/Binance.pb.h" +#include "proto/Bitcoin.pb.h" +#include "proto/Ethereum.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include "Bitcoin/Script.h" +#include "Bitcoin/SegwitAddress.h" + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "uint256.h" +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TransactionCompiler, BinanceCompileWithSignatures) { + /// Step 1: Prepare transaction input (protobuf) + const auto coin = TWCoinTypeBinance; + const auto txInputData = TransactionCompiler::buildInput( + coin, + "bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", // from + "bnb1hlly02l6ahjsgxw9wlcswnlwdhg4xhx38yxpd5", // to + "1", // amount + "BNB", // asset + "", // memo + "Binance-Chain-Nile" // testnet chainId + ); + + { + // Check, by parsing + EXPECT_EQ(txInputData.size(), 88ul); + Binance::Proto::SigningInput input; + ASSERT_TRUE(input.ParseFromArray(txInputData.data(), (int)txInputData.size())); + EXPECT_EQ(input.chain_id(), "Binance-Chain-Nile"); + EXPECT_TRUE(input.has_send_order()); + ASSERT_EQ(input.send_order().inputs_size(), 1); + EXPECT_EQ(hex(data(input.send_order().inputs(0).address())), "40c2979694bbc961023d1d27be6fc4d21a9febe6"); + } + + /// Step 2: Obtain preimage hash + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + ASSERT_GT(preImageHashes.size(), 0ul); + + TxCompiler::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); + ASSERT_EQ(preSigningOutput.error(), 0); + + auto preImageHash = data(preSigningOutput.data_hash()); + EXPECT_EQ(hex(preImageHash), "3f3fece9059e714d303a9a1496ddade8f2c38fa78fc4cc2e505c5dbb0ea678d1"); + + // Simulate signature, normally obtained from signature server + const auto publicKeyData = parse_hex("026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e502"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1); + const auto signature = parse_hex("1b1181faec30b60a2ddaa2804c253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5366bb8b041781a6771248550d04094c3d7a504f9e8310679"); + + // Verify signature (pubkey & hash & signature) + { + EXPECT_TRUE(publicKey.verify(signature, preImageHash)); + } + + /// Step 3: Compile transaction info + const Data outputData = TransactionCompiler::compileWithSignatures(coin, txInputData, {signature}, {publicKeyData}); + + const auto ExpectedTx = "b801f0625dee0a462a2c87fa0a1f0a1440c2979694bbc961023d1d27be6fc4d21a9febe612070a03424e421001121f0a14bffe47abfaede50419c577f1074fee6dd1535cd112070a03424e421001126a0a26eb5ae98721026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e50212401b1181faec30b60a2ddaa2804c253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5366bb8b041781a6771248550d04094c3d7a504f9e8310679"; + { + EXPECT_EQ(outputData.size(), 189ul); + Binance::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private keys were not used anywhere up to this point. + Binance::Proto::SigningInput input; + ASSERT_TRUE(input.ParseFromArray(txInputData.data(), (int)txInputData.size())); + auto key = parse_hex("95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832"); + input.set_private_key(key.data(), key.size()); + + Binance::Proto::SigningOutput output; + ANY_SIGN(input, coin); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx); + } +} + +TEST(TransactionCompiler, BitcoinCompileWithSignatures) { + // Test external signining with a Bitcoin transaction with 3 input UTXOs, all used, but only using 2 public keys. + // Three signatures are neeeded. This illustrates that order of UTXOs/hashes is not always the same. + + const auto revUtxoHash0 = parse_hex("07c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa8"); + const auto revUtxoHash1 = parse_hex("d6892a5aa54e3b8fe430efd23f49a8950733aaa9d7c915d9989179f48dd1905e"); + const auto revUtxoHash2 = parse_hex("6021efcf7555f90627364339fc921139dd40a06ccb2cb2a2a4f8f4ea7a2dc74d"); + const auto inPubKey0 = parse_hex("024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382"); + const auto inPubKey1 = parse_hex("0217142f69535e4dad0dc7060df645c55a174cc1bfa5b9eb2e59aad2ae96072dfc"); + const auto inPubKeyHash0 = parse_hex("bd92088bb7e82d611a9b94fbb74a0908152b784f"); + const auto inPubKeyHash1 = parse_hex("6641abedacf9483b793afe1718689cc9420bbb1c"); + + // Test data: Input UTXO infos + struct UtxoInfo { + Data revUtxoHash; + Data publicKey; + long amount; + int index; + }; + std::vector utxoInfos = { + // first + UtxoInfo {revUtxoHash0, inPubKey0, 600'000, 0}, + // second UTXO, with same pubkey + UtxoInfo {revUtxoHash1, inPubKey0, 500'000, 1}, + // third UTXO, with different pubkey + UtxoInfo {revUtxoHash2, inPubKey1, 400'000, 0}, + }; + + // Signature infos, indexed by pubkeyhash+hash + struct SignatureInfo { + Data signature; + Data publicKey; + }; + std::map signatureInfos = { + { + hex(inPubKeyHash0) + "+" + "a296bead4172007be69b21971a790e076388666c162a9505698415f1b003ebd7", + { + parse_hex("304402201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b34902200a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f2a40"), + inPubKey0, + } + }, + { + hex(inPubKeyHash1) + "+" + "505f527f00e15fcc5a2d2416c9970beb57dfdfaca99e572a01f143b24dd8fab6", + { + parse_hex("3044022041294880caa09bb1b653775310fcdd1458da6b8e7d7fae34e37966414fe115820220646397c9d2513edc5974ecc336e9b287de0cdf071c366f3b3dc3ff309213e4e4"), + inPubKey1, + } + }, + { + hex(inPubKeyHash0) + "+" + "60ed6e9371e5ddc72fd88e46a12cb2f68516ebd307c0fd31b1b55cf767272101", + { + parse_hex("30440220764e3d5b3971c4b3e70b23fb700a7462a6fe519d9830e863a1f8388c402ad0b102207e777f7972c636961f92375a2774af3b7a2a04190251bbcb31d19c70927952dc"), + inPubKey0, + } + }, + }; + + const auto coin = TWCoinTypeBitcoin; + const auto ownAddress = "bc1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z00ppggv"; + + // Setup input for Plan + Bitcoin::Proto::SigningInput signingInput; + signingInput.set_coin_type(coin); + signingInput.set_hash_type(TWBitcoinSigHashTypeAll); + signingInput.set_amount(1'200'000); + signingInput.set_use_max_amount(false); + signingInput.set_byte_fee(1); + signingInput.set_to_address("bc1q2dsdlq3343vk29runkgv4yc292hmq53jedfjmp"); + signingInput.set_change_address(ownAddress); + + // process UTXOs + int count = 0; + for (auto& u: utxoInfos) { + const auto publicKey = PublicKey(u.publicKey, TWPublicKeyTypeSECP256k1); + const auto address = Bitcoin::SegwitAddress(publicKey, "bc"); + if (count == 0) EXPECT_EQ(address.string(), ownAddress); + if (count == 1) EXPECT_EQ(address.string(), ownAddress); + if (count == 2) EXPECT_EQ(address.string(), "bc1qveq6hmdvl9yrk7f6lct3s6yue9pqhwcuxedggg"); + + const auto utxoScript = Bitcoin::Script::lockScriptForAddress(address.string(), coin); + if (count == 0) EXPECT_EQ(hex(utxoScript.bytes), "0014bd92088bb7e82d611a9b94fbb74a0908152b784f"); + if (count == 1) EXPECT_EQ(hex(utxoScript.bytes), "0014bd92088bb7e82d611a9b94fbb74a0908152b784f"); + if (count == 2) EXPECT_EQ(hex(utxoScript.bytes), "00146641abedacf9483b793afe1718689cc9420bbb1c"); + + Data keyHash; + EXPECT_TRUE(utxoScript.matchPayToWitnessPublicKeyHash(keyHash)); + if (count == 0) EXPECT_EQ(hex(keyHash), hex(inPubKeyHash0)); + if (count == 1) EXPECT_EQ(hex(keyHash), hex(inPubKeyHash0)); + if (count == 2) EXPECT_EQ(hex(keyHash), hex(inPubKeyHash1)); + + const auto redeemScript = Bitcoin::Script::buildPayToPublicKeyHash(keyHash); + if (count == 0) EXPECT_EQ(hex(redeemScript.bytes), "76a914bd92088bb7e82d611a9b94fbb74a0908152b784f88ac"); + if (count == 1) EXPECT_EQ(hex(redeemScript.bytes), "76a914bd92088bb7e82d611a9b94fbb74a0908152b784f88ac"); + if (count == 2) EXPECT_EQ(hex(redeemScript.bytes), "76a9146641abedacf9483b793afe1718689cc9420bbb1c88ac"); + (*signingInput.mutable_scripts())[hex(keyHash)] = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); + + auto utxo = signingInput.add_utxo(); + utxo->set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); + utxo->set_amount(u.amount); + utxo->mutable_out_point()->set_hash(std::string(u.revUtxoHash.begin(), u.revUtxoHash.end())); + utxo->mutable_out_point()->set_index(u.index); + utxo->mutable_out_point()->set_sequence(UINT32_MAX); + + ++count; + } + EXPECT_EQ(count, 3); + EXPECT_EQ(signingInput.utxo_size(), 3); + + // Plan + Bitcoin::Proto::TransactionPlan plan; + ANY_PLAN(signingInput, plan, coin); + + // At this point plan can be checked, assume it is accepted unmodified + EXPECT_EQ(plan.amount(), 1'200'000); + EXPECT_EQ(plan.fee(), 277); + EXPECT_EQ(plan.change(), 299'723); + ASSERT_EQ(plan.utxos_size(), 3); + // Note that UTXOs happen to be in reverse order compared to the input + EXPECT_EQ(hex(plan.utxos(0).out_point().hash()), hex(revUtxoHash2)); + EXPECT_EQ(hex(plan.utxos(1).out_point().hash()), hex(revUtxoHash1)); + EXPECT_EQ(hex(plan.utxos(2).out_point().hash()), hex(revUtxoHash0)); + + // Extend input with accepted plan + *signingInput.mutable_plan() = plan; + + // Serialize input + const auto txInputData = data(signingInput.SerializeAsString()); + EXPECT_EQ((int)txInputData.size(), 692); + + /// Step 2: Obtain preimage hashes + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + TW::Bitcoin::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), (int)preImageHashes.size())); + + ASSERT_EQ(preSigningOutput.error(), 0); + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[0].data_hash()), "505f527f00e15fcc5a2d2416c9970beb57dfdfaca99e572a01f143b24dd8fab6"); + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[1].data_hash()), "a296bead4172007be69b21971a790e076388666c162a9505698415f1b003ebd7"); + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[2].data_hash()), "60ed6e9371e5ddc72fd88e46a12cb2f68516ebd307c0fd31b1b55cf767272101"); + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[0].public_key_hash()), hex(inPubKeyHash1)); + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[1].public_key_hash()), hex(inPubKeyHash0)); + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[2].public_key_hash()), hex(inPubKeyHash0)); + + // Simulate signatures, normally they are obtained from external source, e.g. a signature server. + std::vector signatureVec; + std::vector pubkeyVec; + for (const auto& h: preSigningOutput.hash_public_keys()) { + const auto& preImageHash = h.data_hash(); + const auto& pubkeyhash = h.public_key_hash(); + + const std::string key = hex(pubkeyhash) + "+" + hex(preImageHash); + const auto sigInfoFind = signatureInfos.find(key); + ASSERT_TRUE(sigInfoFind != signatureInfos.end()); + const auto& sigInfo = std::get<1>(*sigInfoFind); + const auto& publicKeyData = sigInfo.publicKey; + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1); + const auto signature = sigInfo.signature; + + signatureVec.push_back(signature); + pubkeyVec.push_back(publicKeyData); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verifyAsDER(signature, TW::Data(preImageHash.begin(), preImageHash.end()))); + } + + /// Step 3: Compile transaction info + const Data compileWithSignatures = TransactionCompiler::compileWithSignatures(coin, txInputData, signatureVec, pubkeyVec); + + const auto ExpectedTx = "010000000001036021efcf7555f90627364339fc921139dd40a06ccb2cb2a2a4f8f4ea7a2dc74d0000000000ffffffffd6892a5aa54e3b8fe430efd23f49a8950733aaa9d7c915d9989179f48dd1905e0100000000ffffffff07c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa80000000000ffffffff02804f1200000000001600145360df8231ac5965147c9d90ca930a2aafb05232cb92040000000000160014bd92088bb7e82d611a9b94fbb74a0908152b784f02473044022041294880caa09bb1b653775310fcdd1458da6b8e7d7fae34e37966414fe115820220646397c9d2513edc5974ecc336e9b287de0cdf071c366f3b3dc3ff309213e4e401210217142f69535e4dad0dc7060df645c55a174cc1bfa5b9eb2e59aad2ae96072dfc0247304402201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b34902200a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f2a400121024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382024730440220764e3d5b3971c4b3e70b23fb700a7462a6fe519d9830e863a1f8388c402ad0b102207e777f7972c636961f92375a2774af3b7a2a04190251bbcb31d19c70927952dc0121024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb49338200000000"; + { + EXPECT_EQ(compileWithSignatures.size(), 786ul); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(compileWithSignatures.data(), (int)compileWithSignatures.size())); + + EXPECT_EQ(output.encoded().size(), 518ul); + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private keys were not used anywhere up to this point. + Bitcoin::Proto::SigningInput input; + ASSERT_TRUE(input.ParseFromArray(txInputData.data(), (int)txInputData.size())); + + // 2 private keys are needed (despite >2 UTXOs) + auto key0 = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); + auto key1 = parse_hex("7878787878787878787878787878787878787878787878787878787878787878"); + EXPECT_EQ(hex(PrivateKey(key0).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), hex(inPubKey0)); + EXPECT_EQ(hex(PrivateKey(key1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), hex(inPubKey1)); + *input.add_private_key() = std::string(key0.begin(), key0.end()); + *input.add_private_key() = std::string(key1.begin(), key1.end()); + + Bitcoin::Proto::SigningOutput output; + ANY_SIGN(input, coin); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: not enough signatures + const Data outputData = TransactionCompiler::compileWithSignatures(coin, txInputData, {signatureVec[0]}, pubkeyVec); + EXPECT_GT(outputData.size(), 1ul); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + { // Negative: invalid public key + const auto publicKeyBlake = parse_hex("b689ab808542e13f3d2ec56fe1efe43a1660dcadc73ce489fde7df98dd8ce5d9"); + EXPECT_EXCEPTION(TransactionCompiler::compileWithSignatures(coin, txInputData, signatureVec, + {pubkeyVec[0], pubkeyVec[1], publicKeyBlake}), "Invalid public key"); + } + { // Negative: wrong signature (formally valid) + const Data outputData = TransactionCompiler::compileWithSignatures(coin, txInputData, + {parse_hex("415502201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b34902200a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f3b51"), + signatureVec[1], signatureVec[2]}, + pubkeyVec); + EXPECT_EQ(outputData.size(), 2ul); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_signing); + } +} + +TEST(TransactionCompiler, EthereumCompileWithSignatures) { + /// Step 1: Prepare transaction input (protobuf) + const auto coin = TWCoinTypeEthereum; + const auto txInputData0 = TransactionCompiler::buildInput( + coin, + "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F", // from + "0x3535353535353535353535353535353535353535", // to + "1000000000000000000", // amount + "ETH", // asset + "", // memo + "" // chainId + ); + + // Check, by parsing + EXPECT_EQ((int)txInputData0.size(), 61); + Ethereum::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(txInputData0.data(), (int)txInputData0.size())); + EXPECT_EQ(hex(signingInput.chain_id()), "01"); + EXPECT_EQ(signingInput.to_address(), "0x3535353535353535353535353535353535353535"); + ASSERT_TRUE(signingInput.transaction().has_transfer()); + EXPECT_EQ(hex(signingInput.transaction().transfer().amount()), "0de0b6b3a7640000"); + + // Set a few other values + const auto nonce = store(uint256_t(11)); + const auto gasPrice = store(uint256_t(20000000000)); + const auto gasLimit = store(uint256_t(21000)); + signingInput.set_nonce(nonce.data(), nonce.size()); + signingInput.set_gas_price(gasPrice.data(), gasPrice.size()); + signingInput.set_gas_limit(gasLimit.data(), gasLimit.size()); + signingInput.set_tx_mode(Ethereum::Proto::Legacy); + + // Serialize back, this shows how to serialize input protobuf to byte array + const auto txInputData = data(signingInput.SerializeAsString()); + EXPECT_EQ((int)txInputData.size(), 75); + + /// Step 2: Obtain preimage hash + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + ASSERT_GT(preImageHashes.size(), 0ul); + + TxCompiler::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); + ASSERT_EQ(preSigningOutput.error(), 0); + + auto preImageHash = data(preSigningOutput.data_hash()); + EXPECT_EQ(hex(preImageHash), "15e180a6274b2f6a572b9b51823fce25ef39576d10188ecdcd7de44526c47217"); + + // Simulate signature, normally obtained from signature server + const Data publicKeyData = parse_hex("044bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382ce28cab79ad7119ee1ad3ebcdb98a16805211530ecc6cfefa1b88e6dff99232a"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1Extended); + const auto signature = parse_hex("360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07b53bd9d1376e15a191d844db458893b928f3efbfee90c9febf51ab84c9796677900"); + + // Verify signature (pubkey & hash & signature) + { + EXPECT_TRUE(publicKey.verify(signature, preImageHash)); + } + + /// Step 3: Compile transaction info + const Data outputData = TransactionCompiler::compileWithSignatures(coin, txInputData, {signature}, {publicKeyData}); + + const auto ExpectedTx = "f86c0b8504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a0360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07ba053bd9d1376e15a191d844db458893b928f3efbfee90c9febf51ab84c97966779"; + { + EXPECT_EQ(outputData.size(), 183ul); + Ethereum::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.encoded().size(), 110ul); + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private keys were not used anywhere up to this point. + Ethereum::Proto::SigningInput input; + ASSERT_TRUE(input.ParseFromArray(txInputData.data(), (int)txInputData.size())); + auto key = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); + input.set_private_key(key.data(), key.size()); + + Ethereum::Proto::SigningOutput output; + ANY_SIGN(input, coin); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx); + } +} + +TEST(TransactionCompiler, EthereumBuildTransactionInput) { + const auto coin = TWCoinTypeEthereum; + const auto txInputData0 = TransactionCompiler::buildInput( + coin, + "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F", // from + "0x3535353535353535353535353535353535353535", // to + "1000000000000000000", // amount + "ETH", // asset + "Memo", // memo + "05" // chainId + ); + + // Check, by parsing + EXPECT_EQ((int)txInputData0.size(), 61); + Ethereum::Proto::SigningInput input; + ASSERT_TRUE(input.ParseFromArray(txInputData0.data(), (int)txInputData0.size())); + EXPECT_EQ(hex(input.chain_id()), "05"); + EXPECT_EQ(input.to_address(), "0x3535353535353535353535353535353535353535"); + ASSERT_TRUE(input.transaction().has_transfer()); + EXPECT_EQ(hex(input.transaction().transfer().amount()), "0de0b6b3a7640000"); +} + +TEST(TransactionCompiler, EthereumBuildTransactionInputInvalidAddress) { + const auto coin = TWCoinTypeEthereum; + EXPECT_EXCEPTION(TransactionCompiler::buildInput( + coin, + "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F", // from + "__INVALID_ADDRESS__", // to + "1000000000000000000", // amount + "ETH", // asset + "", // memo + "" // chainId + ), "Invalid to address"); +} diff --git a/tests/common/Uint256Tests.cpp b/tests/common/Uint256Tests.cpp new file mode 100644 index 00000000000..a396e1e0318 --- /dev/null +++ b/tests/common/Uint256Tests.cpp @@ -0,0 +1,114 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "uint256.h" +#include "HexCoding.h" + +#include +#include + +#include + +namespace TW { + +// Test data: uint256_t, hex binary representation, string representation +const std::vector> testData = { + {0, "00", "0"}, + {1, "01", "1"}, + {7, "07", "7"}, + {100, "64", "100"}, + {255, "ff", "255"}, + {256, "0100", "256"}, + {65535, "ffff", "65535"}, + {1'000'000, "0f4240", "1000000"}, + { + load(parse_hex("123456789abcdef0")), + "123456789abcdef0", + "1311768467463790320" + }, + { + load(parse_hex("123456789abcdef123456789abcdef")), + "123456789abcdef123456789abcdef", + "94522879700260683142460330790866415" + }, + { + load(parse_hex("1000000000000000000000000000000000000000")), + "1000000000000000000000000000000000000000", + "91343852333181432387730302044767688728495783936" + }, +}; + +TEST(Uint256, storeLoadBasic) { + EXPECT_EQ(hex(store(uint256_t(3))), "03"); + EXPECT_EQ(load(parse_hex("03")), uint256_t(3)); +} + +TEST(Uint256, storeLoadStore) { + for(const auto& testi: testData) { + const uint256_t dataI = std::get<0>(testi); + const char* dataD = std::get<1>(testi); + + const uint256_t i = dataI; + + const Data d = store(i); + EXPECT_EQ(hex(d), dataD); + + const uint256_t i2 = load(d); + EXPECT_EQ(i2, dataI); + + const Data d2 = store(i2); + EXPECT_EQ(hex(d2), dataD); + } +} + +TEST(Uint256, toString) { + for(const auto& testi: testData) { + const uint256_t dataI = std::get<0>(testi); + const char* dataS = std::get<2>(testi); + + EXPECT_EQ(toString(dataI), dataS); + } +} + +TEST(Uint256, storePadding) { + EXPECT_EQ(hex(store(uint256_t(3))), "03"); + EXPECT_EQ(hex(store(uint256_t(1'000'000))), "0f4240"); + + EXPECT_EQ(hex(store(uint256_t(3), 4)), "00000003"); + EXPECT_EQ(hex(store(uint256_t(3), 32)), "0000000000000000000000000000000000000000000000000000000000000003"); + EXPECT_EQ(hex(store(uint256_t(3), 64)), "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003"); + + EXPECT_EQ(hex(store(uint256_t(1'000'000), 32)), "00000000000000000000000000000000000000000000000000000000000f4240"); + EXPECT_EQ(hex(store(uint256_t(1'000'000), 2)), "0f4240"); +} + +TEST(Uint256, loadWithLeadingZero) { + EXPECT_EQ(load(parse_hex("0f4240")), uint256_t(1'000'000)); + EXPECT_EQ(load(parse_hex("000f4240")), uint256_t(1'000'000)); + EXPECT_EQ(load(parse_hex("000000000f4240")), uint256_t(1'000'000)); + EXPECT_EQ(load(parse_hex("0000000000000000000000000000000000000000000000000000000000000003")), uint256_t(3)); + EXPECT_EQ(load(parse_hex("00000000000000000000000000000000000000000000000000000000000f4240")), uint256_t(1'000'000)); +} + +TEST(Uint256, LoadEmpty) { + EXPECT_EQ(load(parse_hex("")), uint256_t(0)); + EXPECT_EQ(load(parse_hex("00")), uint256_t(0)); + EXPECT_EQ(load(parse_hex("0000")), uint256_t(0)); +} + +TEST(Uint256, LoadWithOffset) { + EXPECT_EQ(loadWithOffset(parse_hex("0000000000000000000000000000000000000000000000000000000000000003"), 0), uint256_t(3)); + EXPECT_EQ(loadWithOffset(parse_hex("abcdef0000000000000000000000000000000000000000000000000000000000000003"), 3), uint256_t(3)); + EXPECT_EQ(loadWithOffset(parse_hex("0000000000000000000000000000000000000000000000000000000000000003"), 1), uint256_t(0)); // not enough bytes +} + +TEST(Uint256, loadStringProtobuf) { + const Data data = parse_hex("03"); + const std::string str = std::string(reinterpret_cast(data.data()), data.size()); + EXPECT_EQ(load(str), uint256_t(3)); +} + +} // namespace diff --git a/tests/WalletConsoleTests.cpp b/tests/common/WalletConsoleTests.cpp similarity index 97% rename from tests/WalletConsoleTests.cpp rename to tests/common/WalletConsoleTests.cpp index 3241f2f78a3..e6bdfc9be62 100644 --- a/tests/WalletConsoleTests.cpp +++ b/tests/common/WalletConsoleTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// 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 @@ -6,29 +6,29 @@ #include "../walletconsole/lib/CommandExecutor.h" #include "../walletconsole/lib/WalletConsole.h" -#include "../walletconsole/lib/Util.h" #include #include #include -using namespace TW; -using namespace TW::WalletConsole; -using namespace std; +namespace TW::WalletConsole::tests { -// Test some command execution +using namespace std; static stringstream outputss; static CommandExecutor cmd(outputss); -static int staticInit() { cmd.init(); return 0; } +static int staticInit() { + cmd.init(); + return 0; +} static int dummyStatic = staticInit(); static const string mnemonic1 = "edge defense waste choose enrich upon flee junk siren film clown finish luggage leader kid quick brick print evidence swap drill paddle truly occur"; int countLines(const string& text) { int lines = 0; - for(int i = 0; i < text.length(); ++i) - { - if (text[i] == '\n') ++lines; + for (auto i = 0ul; i < text.length(); ++i) { + if (text[i] == '\n') + ++lines; } return lines; } @@ -41,7 +41,9 @@ TEST(WalletConsole, loopExit) { stringstream inss; stringstream outss; - inss << "coin eth" << endl << "newKey" << endl << "exit" << endl; + inss << "coin eth" << endl + << "newKey" << endl + << "exit" << endl; TW::WalletConsole::WalletConsole console(inss, outss); console.loop(); string res = outss.str(); @@ -90,7 +92,7 @@ TEST(WalletConsole, coin) { } { auto pos = outputss.str().length(); - cmd.executeLine("coin eth"); + cmd.executeLine("coin ethereum"); string res = outputss.str().substr(pos); EXPECT_TRUE(res.find("Set active coin to: ethereum") != string::npos); } @@ -285,7 +287,6 @@ TEST(WalletConsole, dumpdp) { } } - TEST(WalletConsole, dumpXpub) { cmd.executeLine("coin btc"); auto pos1 = outputss.str().length(); @@ -444,7 +445,6 @@ TEST(WalletConsole, fileWriteRead) { string res1 = outputss.str().substr(pos1); EXPECT_TRUE(res1.find("Written to ") != string::npos); - auto pos2 = outputss.str().length(); cmd.executeLine("fileR " + fileName); string res2 = outputss.str().substr(pos2); @@ -458,14 +458,11 @@ TEST(WalletConsole, fileWriteRead) { EXPECT_TRUE(res3.find("already exists, not overwriting") != string::npos); // clean up created file - try - { + try { std::remove(fileName.c_str()); + } catch (...) { } - catch(...) - { - } - + auto pos4 = outputss.str().length(); cmd.executeLine("fileR __NO_SUCH_FILE__"); string res4 = outputss.str().substr(pos4); @@ -485,3 +482,5 @@ TEST(WalletConsole, harmonyAddressDerivation) { EXPECT_TRUE(res1.find("Result") != string::npos); EXPECT_TRUE(res1.find("rror") == string::npos); } + +} // namespace TW::WalletConsole::tests diff --git a/tests/common/algorithm/erase_tests.cpp b/tests/common/algorithm/erase_tests.cpp new file mode 100644 index 00000000000..6a0ca0c41aa --- /dev/null +++ b/tests/common/algorithm/erase_tests.cpp @@ -0,0 +1,27 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "algorithm/erase.h" + +#include "gtest/gtest.h" +#include // std::iota + +TEST(Algorithm, Erase) { + std::vector cnt(10); + std::iota(cnt.begin(), cnt.end(), '0'); + cnt.back() = '3'; + std::size_t nbElementsErased = TW::erase(cnt, '3'); + ASSERT_EQ(cnt.size(), 8ul); + ASSERT_EQ(nbElementsErased, 2ul); +} + +TEST(Algorithm, EraseIf) { + std::vector cnt(10); + std::iota(cnt.begin(), cnt.end(), '0'); + auto erased = TW::erase_if(cnt, [](char x) { return (x - '0') % 2 == 0; }); + ASSERT_EQ(cnt.size(), 5ul); + ASSERT_EQ(erased, 5ul); +} diff --git a/tests/common/algorithm/sort_copy_tests.cpp b/tests/common/algorithm/sort_copy_tests.cpp new file mode 100644 index 00000000000..2603ee27b5a --- /dev/null +++ b/tests/common/algorithm/sort_copy_tests.cpp @@ -0,0 +1,25 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "algorithm/sort_copy.h" + +#include "gtest/gtest.h" + +using namespace TW; + +struct Amount { + int value; +}; + +TEST(SortCopy, IsSorted) { + std::vector data{9, 1, 2, 4, 5}; + const auto sorted = sortCopy(data); + std::vector anotherData{Amount{.value = 9}, Amount{.value = 1}, Amount{.value = 0}}; + auto sortFunctor = [](auto&& lhs, auto&& rhs) { return lhs.value < rhs.value; }; + const auto anotherSorted = sortCopy(anotherData, sortFunctor); + ASSERT_TRUE(std::is_sorted(cbegin(sorted), cend(sorted))); + ASSERT_TRUE(std::is_sorted(cbegin(anotherSorted), cend(anotherSorted), sortFunctor)); +} \ No newline at end of file diff --git a/tests/common/algorithm/to_array_tests.cpp b/tests/common/algorithm/to_array_tests.cpp new file mode 100644 index 00000000000..58d181ad663 --- /dev/null +++ b/tests/common/algorithm/to_array_tests.cpp @@ -0,0 +1,23 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "algorithm/to_array.h" + +#include "gtest/gtest.h" + +using namespace TW; + +TEST(Algorithms, ToArray) { + std::string str{"foo"}; + auto value = TW::to_array(str); + auto expected = std::array{"foo"}; + ASSERT_EQ(value, expected); + + std::vector ints{0, 1, 2}; + auto another_value = TW::to_array(ints); + auto expected_ints = std::array{0, 1, 2}; + ASSERT_EQ(another_value, expected_ints); +} diff --git a/tests/common/memory/memzero_tests.cpp b/tests/common/memory/memzero_tests.cpp new file mode 100644 index 00000000000..47fba2f4697 --- /dev/null +++ b/tests/common/memory/memzero_tests.cpp @@ -0,0 +1,23 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "memory/memzero_wrapper.h" + +#include "gtest/gtest.h" + +struct int_wrapper { + int value; +}; + +TEST(Memory, Memzero) { + int_wrapper obj{.value = 42}; + TW::memzero(&obj); + ASSERT_EQ(obj.value, 0); + obj.value = 42; + ASSERT_EQ(obj.value, 42); + TW::memzero(&obj, sizeof(int_wrapper)); + ASSERT_EQ(obj.value, 0); +} diff --git a/tests/common/operators/equality_comparable_tests.cpp b/tests/common/operators/equality_comparable_tests.cpp new file mode 100644 index 00000000000..134d172968e --- /dev/null +++ b/tests/common/operators/equality_comparable_tests.cpp @@ -0,0 +1,23 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "operators/equality_comparable.h" + +#include "gtest/gtest.h" + +namespace TW::operators::tests { + +struct Amount : equality_comparable { + int value; + friend bool operator==(const Amount& lhs, const Amount& rhs) { return lhs.value == rhs.value; } +}; + +TEST(Operators, EqualityComparable) { + ASSERT_TRUE(Amount{.value = 1} != Amount{.value = 2}); + ASSERT_TRUE(Amount{.value = 1} == Amount{.value = 1}); +} + +} // namespace TW::operators::tests diff --git a/tests/interface/TWAESTests.cpp b/tests/interface/TWAESTests.cpp index 8f1229ae5ca..0ee04dfdc5b 100644 --- a/tests/interface/TWAESTests.cpp +++ b/tests/interface/TWAESTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include diff --git a/tests/interface/TWAccountTests.cpp b/tests/interface/TWAccountTests.cpp new file mode 100644 index 00000000000..5a8bcf4661d --- /dev/null +++ b/tests/interface/TWAccountTests.cpp @@ -0,0 +1,37 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "TestUtilities.h" +#include + +#include + +TEST(TWAccount, Create) { + const auto addressAdd = "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"; + const auto coin = TWCoinTypeBitcoin; + const auto derivationPath = "m/84'/0'/0'/0/0"; + const auto extPubKeyAdd = "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"; + const auto pubKey = "02df6fc590ab3101bbe0bb5765cbaeab9b5dcfe09ac9315d707047cbd13bc7e006"; + + const auto account = WRAP( + TWAccount, + TWAccountCreate( + WRAPS(TWStringCreateWithUTF8Bytes(addressAdd)).get(), + coin, + TWDerivationDefault, + WRAPS(TWStringCreateWithUTF8Bytes(derivationPath)).get(), + WRAPS(TWStringCreateWithUTF8Bytes(pubKey)).get(), + WRAPS(TWStringCreateWithUTF8Bytes(extPubKeyAdd)).get() + ) + ); + + assertStringsEqual(WRAPS(TWAccountAddress(account.get())), addressAdd); + EXPECT_EQ(coin, TWAccountCoin(account.get())); + EXPECT_EQ(TWAccountDerivation(account.get()), TWDerivationDefault); + assertStringsEqual(WRAPS(TWAccountDerivationPath(account.get())), derivationPath); + assertStringsEqual(WRAPS(TWAccountExtendedPublicKey(account.get())), extPubKeyAdd); + assertStringsEqual(WRAPS(TWAccountPublicKey(account.get())), pubKey); +} diff --git a/tests/interface/TWAnyAddressTests.cpp b/tests/interface/TWAnyAddressTests.cpp index ada6aa5ce0f..c0bef30ab93 100644 --- a/tests/interface/TWAnyAddressTests.cpp +++ b/tests/interface/TWAnyAddressTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include "HexCoding.h" #include @@ -32,6 +32,20 @@ TEST(AnyAddress, Data) { auto keyHash = WRAPD(TWAnyAddressData(addr.get())); assertHexEqual(keyHash, "4e5b2e1dc63f6b91cb6cd759936495434c7e972f"); } + // smartBCH + { + auto string = STRING("0x4E5B2e1dc63F6b91cb6Cd759936495434C7e972F"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeSmartBitcoinCash)); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "4e5b2e1dc63f6b91cb6cd759936495434c7e972f"); + } + // KuCoin Community Chain + { + auto string = STRING("0x4E5B2e1dc63F6b91cb6Cd759936495434C7e972F"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeKuCoinCommunityChain)); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "4e5b2e1dc63f6b91cb6cd759936495434c7e972f"); + } // bnb address key hash { auto string = STRING("bnb1hlly02l6ahjsgxw9wlcswnlwdhg4xhx38yxpd5"); @@ -53,7 +67,7 @@ TEST(AnyAddress, Data) { auto witness = WRAPD(TWAnyAddressData(addr.get())); assertHexEqual(witness, "751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"); } - // cashaddr + // bitcoincashaddr { auto string = STRING("bitcoincash:qzxf0wl63ahx6jsxu8uuldcw7n5aatwppvnteraqaw"); auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeBitcoinCash)); @@ -114,10 +128,10 @@ TEST(AnyAddress, Data) { } // cardano { - auto string = STRING("addr1s3xuxwfetyfe7q9u3rfn6je9stlvcgmj8rezd87qjjegdtxm3y3f2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59jz29g8j"); + auto string = STRING("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23"); auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeCardano)); auto pubkey = WRAPD(TWAnyAddressData(addr.get())); - assertHexEqual(pubkey, "0104204dc3393959139f00bc88d33d4b2582fecc237238f2269fc094b286acdb892295206d0b99fdb1cc851fbc8dbcb7d6710fde28df46360c259d482db679c24c4e50b2"); + assertHexEqual(pubkey, "01df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b"); } // neo { @@ -140,4 +154,18 @@ TEST(AnyAddress, Data) { auto pubkey = WRAPD(TWAnyAddressData(addr.get())); assertHexEqual(pubkey, "3b83b07cab54824a59c3d3f2e203a7cd913b7fcdc4439595983e2402c2cf791d"); } + // ecashaddr + { + auto string = STRING("ecash:qzxf0wl63ahx6jsxu8uuldcw7n5aatwppv2xdgx6me"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeECash)); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "8c97bbfa8f6e6d4a06e1f9cfb70ef4e9deadc10b"); + } + // solana + { + auto string = STRING("2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeSolana)); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "18f9d8d877393bbbe8d697a8a2e52879cc7e84f467656d1cce6bab5a8d2637ec"); + } } diff --git a/tests/interface/TWBase32Tests.cpp b/tests/interface/TWBase32Tests.cpp new file mode 100644 index 00000000000..a8582fa23f5 --- /dev/null +++ b/tests/interface/TWBase32Tests.cpp @@ -0,0 +1,67 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "TestUtilities.h" + +#include "Data.h" +#include + +#include + +TEST(TWBase32, InvalidDecode) { + const auto encodedInput = STRING("JBSWY3DPK5XXE3DE======="); + auto result = WRAPD(TWBase32Decode(encodedInput.get())); + ASSERT_EQ(result, nullptr); +} + +TEST(TWBase32, Decode) { + const auto encodedInput = STRING("JBSWY3DPK5XXE3DE"); + auto result = WRAPD(TWBase32Decode(encodedInput.get())); + + ASSERT_NE(result, nullptr); + ASSERT_EQ(TWDataSize(result.get()), 10ul); + + auto data = *reinterpret_cast(result.get()); + std::string str(data.begin(), data.end()); + + ASSERT_EQ(str, "HelloWorld"); +} + +TEST(TWBase32, DecodeWithAlphabet) { + const auto encodedInput = STRING("g52w64jworydimrxov5hmn3gpj2gwyttnzxdmndjo5xxiztsojuxg5dxobzhs6i"); + const auto filecoinAlphabet = STRING("abcdefghijklmnopqrstuvwxyz234567"); + auto result = WRAPD(TWBase32DecodeWithAlphabet(encodedInput.get(), filecoinAlphabet.get())); + + ASSERT_NE(result, nullptr); + ASSERT_EQ(TWDataSize(result.get()), 39ul); + + auto data = *reinterpret_cast(result.get()); + std::string str(data.begin(), data.end()); + + ASSERT_EQ(str, "7uoq6tp427uzv7fztkbsnn64iwotfrristwpryy"); +} + +TEST(TWBase32, Encode) { + TW::Data data{'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd'}; + auto encodedStr = TWBase32Encode(&data); + std::string result = TWStringUTF8Bytes(encodedStr); + + ASSERT_EQ(result, "JBSWY3DPK5XXE3DE"); + + TWStringDelete(encodedStr); +} + +TEST(TWBase32, EncodeWithAlphabet) { + const auto filecoinAlphabet = STRING("abcdefghijklmnopqrstuvwxyz234567"); + TW::Data data{'7', 'u', 'o', 'q', '6', 't', 'p', '4', '2', '7', 'u', 'z', 'v', '7', 'f', + 'z', 't', 'k', 'b', 's', 'n', 'n', '6', '4', 'i', 'w', 'o', 't', 'f', 'r', 'r', 'i', 's', 't', 'w', 'p', 'r', 'y', 'y'}; + auto encodedStr = TWBase32EncodeWithAlphabet(&data, filecoinAlphabet.get()); + std::string result = TWStringUTF8Bytes(encodedStr); + + ASSERT_EQ(result, "g52w64jworydimrxov5hmn3gpj2gwyttnzxdmndjo5xxiztsojuxg5dxobzhs6i"); + + TWStringDelete(encodedStr); +} diff --git a/tests/interface/TWBase58Tests.cpp b/tests/interface/TWBase58Tests.cpp index 79542447036..f2c35aa11fe 100644 --- a/tests/interface/TWBase58Tests.cpp +++ b/tests/interface/TWBase58Tests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include diff --git a/tests/interface/TWBase64Tests.cpp b/tests/interface/TWBase64Tests.cpp new file mode 100644 index 00000000000..fc5332dfdd5 --- /dev/null +++ b/tests/interface/TWBase64Tests.cpp @@ -0,0 +1,56 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "TestUtilities.h" + +#include "Data.h" +#include + +#include + +TEST(TWBase64, Decode) { + const auto encodedInput = STRING("Kyc/YWI="); + auto result = WRAPD(TWBase64Decode(encodedInput.get())); + + ASSERT_EQ(TWDataSize(result.get()), 5ul); + + auto data = *reinterpret_cast(result.get()); + std::string str(data.begin(), data.end()); + + ASSERT_EQ(str, "+\'?ab"); +} + +TEST(TWBase64, Encode) { + TW::Data data{'+', '\'', '?', 'a', 'b'}; + auto encodedStr = TWBase64Encode(&data); + std::string result = TWStringUTF8Bytes(encodedStr); + + ASSERT_EQ(result, "Kyc/YWI="); + + TWStringDelete(encodedStr); +} + +TEST(TWBase64, DecodeUrl) { + const auto encodedInput = STRING("Kyc_YWI="); + auto result = WRAPD(TWBase64DecodeUrl(encodedInput.get())); + + ASSERT_EQ(TWDataSize(result.get()), 5ul); + + auto data = *reinterpret_cast(result.get()); + std::string str(data.begin(), data.end()); + + ASSERT_EQ(str, "+\'?ab"); +} + +TEST(TWBase64, EncodeUrl) { + TW::Data data{'+', '\'', '?', 'a', 'b'}; + auto encodedStr = TWBase64EncodeUrl(&data); + std::string result = TWStringUTF8Bytes(encodedStr); + + ASSERT_EQ(result, "Kyc_YWI="); + + TWStringDelete(encodedStr); +} diff --git a/tests/interface/TWCoinTypeTests.cpp b/tests/interface/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..5fd55733ef8 --- /dev/null +++ b/tests/interface/TWCoinTypeTests.cpp @@ -0,0 +1,162 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "TestUtilities.h" + +#include + +#include + +TEST(TWCoinType, TWPurpose) { + ASSERT_EQ(TWPurposeBIP84, TWCoinTypePurpose(TWCoinTypeBitcoin)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeBitcoinCash)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeBinance)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeCosmos)); + ASSERT_EQ(TWPurposeBIP84, TWCoinTypePurpose(TWCoinTypeDigiByte)); + ASSERT_EQ(TWPurposeBIP84, TWCoinTypePurpose(TWCoinTypeLitecoin)); + ASSERT_EQ(TWPurposeBIP84, TWCoinTypePurpose(TWCoinTypeGroestlcoin)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeIoTeX)); + ASSERT_EQ(TWPurposeBIP84, TWCoinTypePurpose(TWCoinTypeViacoin)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeQtum)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeZilliqa)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeTerra)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeMonacoin)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeKava)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeBandChain)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeBluzelle)); + ASSERT_EQ(TWPurposeBIP1852, TWCoinTypePurpose(TWCoinTypeCardano)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeElrond)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeOasis)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeTHORChain)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeCryptoOrg)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeOsmosis)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeECash)); + + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeAion)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeCallisto)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeDash)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeDecred)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeDogecoin)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeEOS)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeEthereum)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeEthereumClassic)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeGoChain)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeICON)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeKin)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeNULS)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeNano)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeNimiq)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeOntology)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypePOANetwork)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeXRP)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeStellar)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeTezos)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeTheta)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeThunderToken)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeTomoChain)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeTron)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeVeChain)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeWanchain)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeZcash)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeFiro)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeZelcash)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeRavencoin)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeWaves)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeNEO)); +} + +TEST(TWCoinType, TWHDVersion) { + ASSERT_EQ(TWHDVersionZPUB, TWCoinTypeXpubVersion(TWCoinTypeBitcoin)); + ASSERT_EQ(TWHDVersionZPRV, TWCoinTypeXprvVersion(TWCoinTypeBitcoin)); + + ASSERT_EQ(TWHDVersionXPUB, TWCoinTypeXpubVersion(TWCoinTypeBitcoinCash)); + ASSERT_EQ(TWHDVersionXPRV, TWCoinTypeXprvVersion(TWCoinTypeBitcoinCash)); + + ASSERT_EQ(TWHDVersionDPUB, TWCoinTypeXpubVersion(TWCoinTypeDecred)); + ASSERT_EQ(TWHDVersionDPRV, TWCoinTypeXprvVersion(TWCoinTypeDecred)); + + ASSERT_EQ(TWHDVersionDGUB, TWCoinTypeXpubVersion(TWCoinTypeDogecoin)); + ASSERT_EQ(TWHDVersionDGPV, TWCoinTypeXprvVersion(TWCoinTypeDogecoin)); +} + +TEST(TWCoinType, TWPublicKeyType) { + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeBitcoin)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeBitcoinCash)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeBinance)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeCosmos)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeDigiByte)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeLitecoin)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeGroestlcoin)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeIoTeX)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeViacoin)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeQtum)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeZilliqa)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeTerra)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeMonacoin)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeKava)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeBandChain)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeBluzelle)); + ASSERT_EQ(TWPublicKeyTypeED25519Cardano, TWCoinTypePublicKeyType(TWCoinTypeCardano)); + ASSERT_EQ(TWPublicKeyTypeED25519, TWCoinTypePublicKeyType(TWCoinTypeElrond)); + ASSERT_EQ(TWPublicKeyTypeED25519, TWCoinTypePublicKeyType(TWCoinTypeOasis)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeTHORChain)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeCryptoOrg)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeOsmosis)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeECash)); + + ASSERT_EQ(TWPublicKeyTypeED25519, TWCoinTypePublicKeyType(TWCoinTypeAion)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeCallisto)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeDash)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeDecred)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeDogecoin)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeEOS)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeEthereum)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeEthereumClassic)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeGoChain)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeICON)); + ASSERT_EQ(TWPublicKeyTypeED25519, TWCoinTypePublicKeyType(TWCoinTypeKin)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeNULS)); + ASSERT_EQ(TWPublicKeyTypeED25519Blake2b, TWCoinTypePublicKeyType(TWCoinTypeNano)); + ASSERT_EQ(TWPublicKeyTypeED25519, TWCoinTypePublicKeyType(TWCoinTypeNimiq)); + ASSERT_EQ(TWPublicKeyTypeNIST256p1, TWCoinTypePublicKeyType(TWCoinTypeOntology)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypePOANetwork)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeXRP)); + ASSERT_EQ(TWPublicKeyTypeED25519, TWCoinTypePublicKeyType(TWCoinTypeStellar)); + ASSERT_EQ(TWPublicKeyTypeED25519, TWCoinTypePublicKeyType(TWCoinTypeTezos)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeTheta)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeThunderToken)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeTomoChain)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeTron)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeVeChain)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeWanchain)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeZcash)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeFiro)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeZelcash)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeRavencoin)); + ASSERT_EQ(TWPublicKeyTypeCURVE25519, TWCoinTypePublicKeyType(TWCoinTypeWaves)); + ASSERT_EQ(TWPublicKeyTypeNIST256p1, TWCoinTypePublicKeyType(TWCoinTypeNEO)); +} + +TEST(TWCoinType, TWCoinTypeDerivationPath) { + auto res = TWCoinTypeDerivationPath(TWCoinTypeBitcoin); + auto result = *reinterpret_cast(res); + ASSERT_EQ(result, "m/84'/0'/0'/0/0"); + TWStringDelete(res); +} + +TEST(TWCoinType, TWCoinTypeDerivationPathWithDerivation) { + auto res = TWCoinTypeDerivationPathWithDerivation(TWCoinTypeBitcoin, TWDerivationBitcoinLegacy); + auto result = *reinterpret_cast(res); + ASSERT_EQ(result, "m/44'/0'/0'/0/0"); + TWStringDelete(res); +} + +TEST(TWCoinType, TWCoinTypeDerivationPathWithDerivationSolana) { + auto res = TWCoinTypeDerivationPathWithDerivation(TWCoinTypeSolana, TWDerivationSolanaSolana); + auto result = *reinterpret_cast(res); + ASSERT_EQ(result, "m/44'/501'/0'/0'"); + TWStringDelete(res); +} diff --git a/tests/interface/TWDataTests.cpp b/tests/interface/TWDataTests.cpp index 6e648769c57..015c5e12b00 100644 --- a/tests/interface/TWDataTests.cpp +++ b/tests/interface/TWDataTests.cpp @@ -5,14 +5,14 @@ // file LICENSE at the root of the source code distribution tree. #include -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include TEST(TWData, CreateWithHexString) { { const auto data = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); - ASSERT_EQ(TWDataSize(data.get()), 4); + ASSERT_EQ(TWDataSize(data.get()), 4ul); EXPECT_EQ(TWDataBytes(data.get())[0], 0xde); EXPECT_EQ(TWDataBytes(data.get())[1], 0xad); EXPECT_EQ(TWDataBytes(data.get())[2], 0xbe); @@ -22,7 +22,7 @@ TEST(TWData, CreateWithHexString) { { const auto data = WRAPD(TWDataCreateWithHexString(STRING("00").get())); - ASSERT_EQ(TWDataSize(data.get()), 1); + ASSERT_EQ(TWDataSize(data.get()), 1ul); EXPECT_EQ(TWDataBytes(data.get())[0], 0); assertHexEqual(data, "00"); } @@ -60,10 +60,10 @@ TEST(TWData, CreateWithBytes) { } TEST(TWData, CreateWithSize) { - int n = 12; + std::size_t n = 12; const auto data = WRAPD(TWDataCreateWithSize(n)); ASSERT_EQ(TWDataSize(data.get()), n); - for (int i = 0; i < n; ++i) { + for (auto i = 0ul; i < n; ++i) { EXPECT_EQ(TWDataBytes(data.get())[i], 0); } } diff --git a/tests/interface/TWDataVectorTests.cpp b/tests/interface/TWDataVectorTests.cpp new file mode 100644 index 00000000000..36a984c2e2c --- /dev/null +++ b/tests/interface/TWDataVectorTests.cpp @@ -0,0 +1,83 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include "HexCoding.h" +#include "TestUtilities.h" + +#include + +using namespace TW; + + +TEST(TWDataVector, CreateDelete) { + auto vec = TWDataVectorCreate(); + + ASSERT_TRUE(vec != nullptr); + EXPECT_EQ(TWDataVectorSize(vec), 0ul); + + TWDataVectorDelete(vec); +} + +TEST(TWDataVector, CreateWrapAutoDelete) { + auto vec = WRAP(TWDataVector, TWDataVectorCreate()); + + ASSERT_TRUE(vec.get() != nullptr); + EXPECT_EQ(TWDataVectorSize(vec.get()), 0ul); +} + +TEST(TWDataVector, CreateWithData) { + const auto elem1d = parse_hex("deadbeef"); + const auto elem1 = WRAPD(TWDataCreateWithBytes(elem1d.data(), elem1d.size())); + const auto vec = WRAP(TWDataVector, TWDataVectorCreateWithData(elem1.get())); + + ASSERT_TRUE(vec.get() != nullptr); + ASSERT_EQ(TWDataVectorSize(vec.get()), 1ul); + + const auto readElem1 = WRAPD(TWDataVectorGet(vec.get(), 0)); + EXPECT_EQ(hex(*static_cast(readElem1.get())), "deadbeef"); +} + +TEST(TWDataVector, Add) { + const auto vec = WRAP(TWDataVector, TWDataVectorCreate()); + + ASSERT_TRUE(vec.get() != nullptr); + EXPECT_EQ(TWDataVectorSize(vec.get()), 0ul); + + const auto elem1d = parse_hex("deadbeef"); + const auto elem1 = WRAPD(TWDataCreateWithBytes(elem1d.data(), elem1d.size())); + TWDataVectorAdd(vec.get(), elem1.get()); + + ASSERT_EQ(TWDataVectorSize(vec.get()), 1ul); + const auto readElem1 = WRAPD(TWDataVectorGet(vec.get(), 0)); + EXPECT_EQ(hex(*static_cast(readElem1.get())), "deadbeef"); + + const auto elem2d = parse_hex("0202"); + const auto elem2 = WRAPD(TWDataCreateWithBytes(elem2d.data(), elem2d.size())); + TWDataVectorAdd(vec.get(), elem2.get()); + + ASSERT_EQ(TWDataVectorSize(vec.get()), 2ul); + const auto readElem2 = WRAPD(TWDataVectorGet(vec.get(), 1)); + EXPECT_EQ(hex(*static_cast(readElem2.get())), "0202"); +} + +TEST(TWDataVector, Get) { + const auto elem1d = parse_hex("deadbeef"); + const auto elem1 = WRAPD(TWDataCreateWithBytes(elem1d.data(), elem1d.size())); + const auto vec = WRAP(TWDataVector, TWDataVectorCreateWithData(elem1.get())); + + ASSERT_TRUE(vec.get() != nullptr); + ASSERT_EQ(TWDataVectorSize(vec.get()), 1ul); + + { // Get element + const auto readElem1 = WRAPD(TWDataVectorGet(vec.get(), 0)); + EXPECT_EQ(hex(*static_cast(readElem1.get())), "deadbeef"); + } + { // Get with bad index + const auto readElem = TWDataVectorGet(vec.get(), 666); + EXPECT_EQ(readElem, nullptr); + } +} diff --git a/tests/interface/TWDerivationPathTests.cpp b/tests/interface/TWDerivationPathTests.cpp new file mode 100644 index 00000000000..15db6b2645e --- /dev/null +++ b/tests/interface/TWDerivationPathTests.cpp @@ -0,0 +1,64 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "TestUtilities.h" +#include "TrustWalletCore/TWDerivation.h" +#include "TrustWalletCore/TWPurpose.h" +#include +#include + +#include + +TEST(TWDerivationPath, CreateWithString) { + const auto derivationPath = STRING("m/84'/1'/2'/3/4"); + + const auto path = WRAP(TWDerivationPath, TWDerivationPathCreateWithString(derivationPath.get())); + + EXPECT_EQ(5u, TWDerivationPathIndicesCount(path.get())); + EXPECT_EQ(TWPurposeBIP84, TWDerivationPathPurpose(path.get())); + EXPECT_EQ(1u, TWDerivationPathCoin(path.get())); + EXPECT_EQ(2u, TWDerivationPathAccount(path.get())); + EXPECT_EQ(3u, TWDerivationPathChange(path.get())); + EXPECT_EQ(4u, TWDerivationPathAddress(path.get())); + assertStringsEqual(WRAPS(TWDerivationPathDescription(path.get())), "m/84'/1'/2'/3/4"); + + const auto index0 = WRAP(TWDerivationPathIndex, TWDerivationPathIndexAt(path.get(), 0)); + const auto index3 = WRAP(TWDerivationPathIndex, TWDerivationPathIndexAt(path.get(), 3)); + + EXPECT_EQ(NULL, TWDerivationPathIndexAt(path.get(), 10)); + EXPECT_EQ(TWPurposeBIP84, TWDerivationPathIndexValue(index0.get())); + EXPECT_TRUE(TWDerivationPathIndexHardened(index0.get())); + + EXPECT_EQ(3u, TWDerivationPathIndexValue(index3.get())); + EXPECT_FALSE(TWDerivationPathIndexHardened(index3.get())); + assertStringsEqual(WRAPS(TWDerivationPathIndexDescription(index0.get())), "84'"); + + const auto path2 = WRAP(TWDerivationPath, TWDerivationPathCreateWithString(STRING("m/44'/501'").get())); + + EXPECT_EQ(2u, TWDerivationPathIndicesCount(path2.get())); + EXPECT_EQ(TWPurposeBIP44, TWDerivationPathPurpose(path2.get())); + EXPECT_EQ(501u, TWDerivationPathCoin(path2.get())); + EXPECT_EQ(0u, TWDerivationPathAccount(path2.get())); + EXPECT_EQ(0u, TWDerivationPathChange(path2.get())); + EXPECT_EQ(0u, TWDerivationPathAddress(path2.get())); +} + +TEST(TWDerivationPath, CreateWithCoin) { + + const auto path = WRAP(TWDerivationPath, TWDerivationPathCreate(TWPurposeBIP44, 60, 0, 0, 0)); + + EXPECT_EQ(5u, TWDerivationPathIndicesCount(path.get())); + EXPECT_EQ(TWPurposeBIP44, TWDerivationPathPurpose(path.get())); + EXPECT_EQ(60u, TWDerivationPathCoin(path.get())); + EXPECT_EQ(0u, TWDerivationPathAccount(path.get())); + EXPECT_EQ(0u, TWDerivationPathChange(path.get())); + EXPECT_EQ(0u, TWDerivationPathAddress(path.get())); + assertStringsEqual(WRAPS(TWDerivationPathDescription(path.get())), "m/44'/60'/0'/0/0"); + + const auto index = WRAP(TWDerivationPathIndex, TWDerivationPathIndexCreate(44, true)); + const auto description = WRAPS(TWDerivationPathIndexDescription(index.get())); + assertStringsEqual(description, "44'"); +} diff --git a/tests/interface/TWHDWalletTests.cpp b/tests/interface/TWHDWalletTests.cpp index a3ca7f87b91..63fc42fec3c 100644 --- a/tests/interface/TWHDWalletTests.cpp +++ b/tests/interface/TWHDWalletTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include "Coin.h" @@ -16,6 +16,7 @@ #include #include #include +#include #include #include "HexCoding.h" @@ -23,9 +24,11 @@ #include #include +using namespace TW; + const auto wordsStr = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; -const auto words = STRING(wordsStr); -const auto passphrase = STRING("TREZOR"); +const auto gWords = STRING(wordsStr); +const auto gPassphrase = STRING("TREZOR"); const auto seedHex = "7ae6f661157bda6492f6162701e570097fc726b6235011ea5ad09bf04986731ed4d92bc43cbdee047b60ea0dd1b1fa4274377c9bf5bd14ab1982c272d8076f29"; const auto entropyHex = "ba5821e8c356c05ba5f025d9532fe0f21f65d594"; @@ -46,31 +49,31 @@ inline void assertEntropyEq(const std::shared_ptr& wallet, const cha } TEST(HDWallet, CreateFromMnemonic) { - const auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + const auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); assertMnemonicEq(wallet, wordsStr); assertEntropyEq(wallet, entropyHex); assertSeedEq(wallet, seedHex); } TEST(HDWallet, CreateFromEntropy) { - const auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithEntropy(DATA(entropyHex).get(), passphrase.get())); + const auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithEntropy(DATA(entropyHex).get(), gPassphrase.get())); assertMnemonicEq(wallet, wordsStr); assertSeedEq(wallet, seedHex); assertEntropyEq(wallet, entropyHex); } TEST(HDWallet, Generate) { - const auto wallet = WRAP(TWHDWallet, TWHDWalletCreate(128, passphrase.get())); + const auto wallet = WRAP(TWHDWallet, TWHDWalletCreate(128, gPassphrase.get())); EXPECT_TRUE(TWMnemonicIsValid(WRAPS(TWHDWalletMnemonic(wallet.get())).get())); } TEST(HDWallet, SeedWithExtraSpaces) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); assertSeedEq(wallet, "7ae6f661157bda6492f6162701e570097fc726b6235011ea5ad09bf04986731ed4d92bc43cbdee047b60ea0dd1b1fa4274377c9bf5bd14ab1982c272d8076f29"); } TEST(HDWallet, CreateFromMnemonicNoPassword) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), STRING("").get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), STRING("").get())); assertSeedEq(wallet, "354c22aedb9a37407adc61f657a6f00d10ed125efa360215f36c6919abd94d6dbc193a5f9c495e21ee74118661e327e84a5f5f11fa373ec33b80897d4697557d"); } @@ -85,7 +88,7 @@ TEST(HDWallet, CreateFromMnemonicInvalid) { } TEST(HDWallet, MasterPrivateKey) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), STRING("").get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), STRING("").get())); auto key1 = WRAP(TWPrivateKey, TWHDWalletGetMasterKey(wallet.get(), TWCurveSECP256k1)); auto hexKey1 = WRAPD(TWPrivateKeyData(key1.get())); @@ -99,7 +102,7 @@ TEST(HDWallet, MasterPrivateKey) { TEST(HDWallet, Derive) { const auto derivationPath = TW::derivationPath(TWCoinTypeEthereum); - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto key0 = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeEthereum)); auto publicKey0 = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(key0.get(), false)); @@ -109,7 +112,7 @@ TEST(HDWallet, Derive) { } TEST(HDWallet, DeriveBitcoinNonextended) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeBitcoin)); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(key.get(), false)); auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); @@ -119,7 +122,7 @@ TEST(HDWallet, DeriveBitcoinNonextended) { } TEST(HDWallet, DeriveBitcoinExtended) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeBitcoin)); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(key.get(), true)); auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); @@ -131,13 +134,13 @@ TEST(HDWallet, DeriveBitcoinExtended) { } TEST(HDWallet, DeriveAddressBitcoin) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto address = WRAP(TWString, TWHDWalletGetAddressForCoin(wallet.get(), TWCoinTypeBitcoin)); assertStringsEqual(address, "bc1qumwjg8danv2vm29lp5swdux4r60ezptzz7ce85"); } TEST(HDWallet, DeriveEthereum) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeEthereum)); auto key2 = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeSmartChain)); @@ -154,7 +157,7 @@ TEST(HDWallet, DeriveEthereum) { } TEST(HDWallet, DeriveAddressEthereum) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto address = WRAP(TWString, TWHDWalletGetAddressForCoin(wallet.get(), TWCoinTypeEthereum)); assertStringsEqual(address, "0x27Ef5cDBe01777D62438AfFeb695e33fC2335979"); } @@ -176,7 +179,7 @@ TEST(HDWallet, DeriveCosmos) { } TEST(HDWallet, DeriveNimiq) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeNimiq)); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519(key.get())); auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); @@ -185,7 +188,7 @@ TEST(HDWallet, DeriveNimiq) { } TEST(HDWallet, DeriveTezos) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeTezos)); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519(key.get())); auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); @@ -194,7 +197,7 @@ TEST(HDWallet, DeriveTezos) { } TEST(HDWallet, DeriveDoge) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeDogecoin)); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(key.get(), true)); auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); @@ -209,7 +212,7 @@ TEST(HDWallet, DeriveDoge) { } TEST(HDWallet, DeriveZilliqa) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeZilliqa)); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(key.get(), true)); auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); @@ -241,7 +244,7 @@ TEST(HDWallet, DeriveFIO) { } TEST(HDWallet, DeriveAlgorand) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto privateKey = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeAlgorand)); auto privateKeyData = WRAPD(TWPrivateKeyData(privateKey.get())); auto address = WRAPS(TWCoinTypeDeriveAddress(TWCoinTypeAlgorand, privateKey.get())); @@ -250,7 +253,7 @@ TEST(HDWallet, DeriveAlgorand) { } TEST(HDWallet, DeriveElrond) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto privateKey = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeElrond)); auto privateKeyData = WRAPD(TWPrivateKeyData(privateKey.get())); auto address = WRAPS(TWCoinTypeDeriveAddress(TWCoinTypeElrond, privateKey.get())); @@ -260,7 +263,7 @@ TEST(HDWallet, DeriveElrond) { } TEST(HDWallet, DeriveBinance) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeBinance)); auto key2 = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeSmartChain)); auto keyData = WRAPD(TWPrivateKeyData(key.get())); @@ -274,7 +277,7 @@ TEST(HDWallet, DeriveBinance) { } TEST(HDWallet, DeriveAvalancheCChain) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeAvalancheCChain)); auto keyData = WRAPD(TWPrivateKeyData(key.get())); @@ -283,6 +286,17 @@ TEST(HDWallet, DeriveAvalancheCChain) { assertHexEqual(keyData, expectedETH); } +TEST(HDWallet, DeriveCardano) { + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); + auto privateKey = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeCardano)); + auto privateKeyData = WRAPD(TWPrivateKeyData(privateKey.get())); + EXPECT_EQ(TWDataSize(privateKeyData.get()), 192ul); + auto address = WRAPS(TWCoinTypeDeriveAddress(TWCoinTypeCardano, privateKey.get())); + + assertHexEqual(privateKeyData, "f8a3b8ad30e62c369b939336c2035aba26d1ffad135e6f346f2a370517a14952e73d20aeadf906bc8b531900fb6c3ed4a05b16973c10ae24650b68b26fae4ee5d97418ba7f3b2707fae963041ff5f174195d1578da09478ad2d17a1ecc00cad478a8ca3be214870accd41f008d70e3b4b59b5981ca933d6d3f389ad317a14952166d8fd329ae3fab4712da739efc2ded9b3eef2b1a8e225dd3dddeb4f065a729b297d9fa76b8852eef235c25aac8f0ff6209ab7251f2a84c83b3b5f1161f7c59"); + assertStringsEqual(address, "addr1q9zz5nj4rqdvteauvdtc834f2vtzyrdrrdmnwdyp4s6huuz5fp33p9cq4xwpqtgguaxknzurmglv58yn9wrv6angpvvq9u36ya"); +} + TEST(HDWallet, ExtendedKeys) { auto words = STRING("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"); auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), STRING("").get())); @@ -309,6 +323,21 @@ TEST(HDWallet, ExtendedKeys) { assertStringsEqual(emptyPub, ""); } +TEST(HDWallet, ExtendedKeysCustomAccount) { + auto words = STRING("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), STRING("").get())); + + auto zprv0 = WRAPS(TWHDWalletGetExtendedPrivateKeyAccount(wallet.get(), TWPurposeBIP84, TWCoinTypeBitcoin, TWDerivationBitcoinSegwit, TWHDVersionZPRV, 0)); + assertStringsEqual(zprv0, "zprvAdG4iTXWBoARxkkzNpNh8r6Qag3irQB8PzEMkAFeTRXxHpbF9z4QgEvBRmfvqWvGp42t42nvgGpNgYSJA9iefm1yYNZKEm7z6qUWCroSQnE"); + auto zprv1 = WRAPS(TWHDWalletGetExtendedPrivateKeyAccount(wallet.get(), TWPurposeBIP84, TWCoinTypeBitcoin, TWDerivationBitcoinSegwit, TWHDVersionZPRV, 1)); + assertStringsEqual(zprv1, "zprvAdG4iTXWBoAS2cCGuaGevCvH54GCunrvLJb2hoWCSuE3D9LS42XVg3c6sPm64w6VMq3w18vJf8nF3cBA2kUMkyWHsq6enWVXivzw42UrVHG"); + + auto zpub0 = WRAPS(TWHDWalletGetExtendedPublicKeyAccount(wallet.get(), TWPurposeBIP84, TWCoinTypeBitcoin, TWDerivationBitcoinSegwit, TWHDVersionZPUB, 0)); + assertStringsEqual(zpub0, "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs"); + auto zpub1 = WRAPS(TWHDWalletGetExtendedPublicKeyAccount(wallet.get(), TWPurposeBIP84, TWCoinTypeBitcoin, TWDerivationBitcoinSegwit, TWHDVersionZPUB, 1)); + assertStringsEqual(zpub1, "zpub6rFR7y4Q2AijF6Gk1bofHLs1d66hKFamhXWdWBup1Em25wfabZqkDqvaieV63fDQFaYmaatCG7jVNUpUiM2hAMo6SAVHcrUpSnHDpNzucB7"); +} + TEST(HDWallet, PublicKeyFromX) { auto xpub = STRING("xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj"); auto xpubAddr2 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeBitcoinCash, STRING("m/44'/145'/0'/0/2").get())); @@ -420,8 +449,75 @@ TEST(HDWallet, MultipleThreads) { } TEST(HDWallet, GetDerivedKey) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); const auto privateKey = WRAP(TWPrivateKey, TWHDWalletGetDerivedKey(wallet.get(), TWCoinTypeBitcoin, 0, 0, 0)); const auto privateKeyData = WRAPD(TWPrivateKeyData(privateKey.get())); assertHexEqual(privateKeyData, "1901b5994f075af71397f65bd68a9fff8d3025d65f5a2c731cf90f5e259d6aac"); } + +TEST(HDWallet, GetKeyByCurve) { + const auto derivPath = STRING("m/44'/539'/0'/0/0"); + + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), STRING("").get())); + const auto privateKey1 = WRAP(TWPrivateKey, TWHDWalletGetKeyByCurve(wallet.get(), TWCurveSECP256k1, derivPath.get())); + const auto privateKeyData1 = WRAPD(TWPrivateKeyData(privateKey1.get())); + assertHexEqual(privateKeyData1, "4fb8657d6464adcaa086d6758d7f0b6b6fc026c98dc1671fcc6460b5a74abc62"); + + const auto privateKey2 = WRAP(TWPrivateKey, TWHDWalletGetKeyByCurve(wallet.get(), TWCurveNIST256p1, derivPath.get())); + const auto privateKeyData2 = WRAPD(TWPrivateKeyData(privateKey2.get())); + assertHexEqual(privateKeyData2, "a13df52d5a5b438bbf921bbf86276e4347fe8e2f2ed74feaaee12b77d6d26f86"); +} + +TEST(TWHDWallet, Derive_XpubPub_vs_PrivPub) { + // Test different routes for deriving address from mnemonic, result should be the same: + // - Direct: mnemonic -> seed -> privateKey -> publicKey -> address + // - Extended Public: mnemonic -> seed -> zpub -> publicKey -> address + + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), STRING("").get())); + const auto coin = TWCoinTypeBitcoin; + const auto derivPath1 = STRING("m/84'/0'/0'/0/0"); + const auto derivPath2 = STRING("m/84'/0'/0'/0/2"); + const auto expectedPublicKey1 = "02df9ef2a7a5552765178b181e1e1afdefc7849985c7dfe9647706dd4fa40df6ac"; + const auto expectedPublicKey2 = "031e1f64d2f6768dccb6814545b2e2d58e26ad5f91b7cbaffe881ed572c65060db"; + const auto expectedAddress1 = "bc1qpsp72plnsqe6e2dvtsetxtww2cz36ztmfxghpd"; + const auto expectedAddress2 = "bc1q7zddsunzaftf4zlsg9exhzlkvc5374a6v32jf6"; + + // -> privateKey -> publicKey + { + const auto privateKey1 = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), coin, derivPath1.get())); + const auto publicKey1 = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey1.get(), true)); + const auto publicKey1Data = WRAPD(TWPublicKeyData(publicKey1.get())); + EXPECT_EQ(hex(TW::data(TWDataBytes(publicKey1Data.get()), TWDataSize(publicKey1Data.get()))), expectedPublicKey1); + const auto address1 = WRAP(TWSegwitAddress, TWSegwitAddressCreateWithPublicKey(TWHRPBitcoin, publicKey1.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(WRAPS(TWSegwitAddressDescription(address1.get())).get())), expectedAddress1); + } + { + const auto privateKey2 = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), coin, derivPath2.get())); + const auto publicKey2 = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey2.get(), true)); + const auto publicKey2Data = WRAPD(TWPublicKeyData(publicKey2.get())); + EXPECT_EQ(hex(TW::data(TWDataBytes(publicKey2Data.get()), TWDataSize(publicKey2Data.get()))), expectedPublicKey2); + const auto address2 = WRAP(TWSegwitAddress, TWSegwitAddressCreateWithPublicKey(TWHRPBitcoin, publicKey2.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(WRAPS(TWSegwitAddressDescription(address2.get())).get())), expectedAddress2); + } + + // zpub -> publicKey + const auto zpub = WRAPS(TWHDWalletGetExtendedPublicKey(wallet.get(), TWPurposeBIP84, coin, TWHDVersionZPUB)); + EXPECT_EQ(std::string(TWStringUTF8Bytes(zpub.get())), "zpub6rNUNtxSa9Gxvm4Bdxf1MPMwrvkzwDx6vP96Hkzw3jiQKdg3fhXBStxjn12YixQB8h88B3RMSRscRstf9AEVaYr3MAqVBEWBDuEJU4PGaT9"); + + { + const auto publicKey1 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(zpub.get(), coin, derivPath1.get())); + EXPECT_NE(publicKey1.get(), nullptr); + const auto publicKey1Data = WRAPD(TWPublicKeyData(publicKey1.get())); + EXPECT_EQ(hex(TW::data(TWDataBytes(publicKey1Data.get()), TWDataSize(publicKey1Data.get()))), expectedPublicKey1); + const auto address1 = WRAP(TWSegwitAddress, TWSegwitAddressCreateWithPublicKey(TWHRPBitcoin, publicKey1.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(WRAPS(TWSegwitAddressDescription(address1.get())).get())), expectedAddress1); + } + { + const auto publicKey2 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(zpub.get(), coin, derivPath2.get())); + EXPECT_NE(publicKey2.get(), nullptr); + const auto publicKey2Data = WRAPD(TWPublicKeyData(publicKey2.get())); + EXPECT_EQ(hex(TW::data(TWDataBytes(publicKey2Data.get()), TWDataSize(publicKey2Data.get()))), expectedPublicKey2); + const auto address2 = WRAP(TWSegwitAddress, TWSegwitAddressCreateWithPublicKey(TWHRPBitcoin, publicKey2.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(WRAPS(TWSegwitAddressDescription(address2.get())).get())), expectedAddress2); + } +} diff --git a/tests/interface/TWHRPTests.cpp b/tests/interface/TWHRPTests.cpp index 562d95ec3e1..a24bda956ff 100644 --- a/tests/interface/TWHRPTests.cpp +++ b/tests/interface/TWHRPTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -34,6 +34,7 @@ TEST(TWHRP, StringForHRP) { ASSERT_STREQ(stringForHRP(TWHRPOasis), "oasis"); ASSERT_STREQ(stringForHRP(TWHRPTHORChain), "thor"); ASSERT_STREQ(stringForHRP(TWHRPCryptoOrg), "cro"); + ASSERT_STREQ(stringForHRP(TWHRPOsmosis), "osmo"); } TEST(TWHRP, HRPForString) { @@ -59,6 +60,8 @@ TEST(TWHRP, HRPForString) { ASSERT_EQ(hrpForString("thor"), TWHRPTHORChain); ASSERT_EQ(hrpForString("bluzelle"), TWHRPBluzelle); ASSERT_EQ(hrpForString("cro"), TWHRPCryptoOrg); + ASSERT_EQ(hrpForString("osmo"), TWHRPOsmosis); + ASSERT_EQ(hrpForString("ecash"), TWHRPECash); } TEST(TWHPR, HPRByCoinType) { @@ -83,6 +86,8 @@ TEST(TWHPR, HPRByCoinType) { ASSERT_EQ(TWHRPOasis, TWCoinTypeHRP(TWCoinTypeOasis)); ASSERT_EQ(TWHRPTHORChain, TWCoinTypeHRP(TWCoinTypeTHORChain)); ASSERT_EQ(TWHRPCryptoOrg, TWCoinTypeHRP(TWCoinTypeCryptoOrg)); + ASSERT_EQ(TWHRPOsmosis, TWCoinTypeHRP(TWCoinTypeOsmosis)); + ASSERT_EQ(TWHRPECash, TWCoinTypeHRP(TWCoinTypeECash)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeAion)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeCallisto)); @@ -110,7 +115,7 @@ TEST(TWHPR, HPRByCoinType) { ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeVeChain)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeWanchain)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeZcash)); - ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeZcoin)); + ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeFiro)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeZelcash)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeRavencoin)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeWaves)); diff --git a/tests/interface/TWHashTests.cpp b/tests/interface/TWHashTests.cpp index 5ead9c659fd..e67d0aeb36f 100644 --- a/tests/interface/TWHashTests.cpp +++ b/tests/interface/TWHashTests.cpp @@ -9,7 +9,7 @@ #include -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include using namespace std; @@ -176,38 +176,6 @@ TEST(TWHashTests, Groestl512) { } } -TEST(TWHashTests, XXHash64) { - auto tests = { - make_tuple(string(""), string("99e9d85137db46ef"), 0), - make_tuple(brownFox, string("bc71da1f362d240b"), 0), - make_tuple(brownFoxDot, string("73ad51577033ad44"), 0), - make_tuple(string("123456789ABCDEF123456789ABCDEF123456789ABCDEF123456789ABCDEF123456789ABCDEF123456789ABCDEF"), - string("fd84b6962fcb8d09"), 0), - make_tuple(string("123456789ABCDEF123456789ABCDEF123456789ABCDEF123456789ABCDEF123456789ABCDEF123456789ABCDEF123456789ABCDEF"), - string("7ea0f7af4b4f9cf4"), 1), - }; - for (auto &test: tests) { - const auto inData = WRAPD(TWDataCreateWithBytes(reinterpret_cast(get<0>(test).c_str()), get<0>(test).length())); - const auto hash = WRAPD(TWHashXXHash64(inData.get(), get<2>(test))); - EXPECT_EQ(hex(data(TWDataBytes(hash.get()), TWDataSize(hash.get()))), get<1>(test)); - } -} - -TEST(TWHashTests, XXHash64Concat) { - auto tests = { - make_tuple(string(""), string("99e9d85137db46ef4bbea33613baafd5")), - make_tuple(brownFox, string("bc71da1f362d240bdbc6d2dab69150df")), - make_tuple(brownFoxDot, string("73ad51577033ad44269e8e5ef42d32d2")), - make_tuple(string("Balances"), string("c2261276cc9d1f8598ea4b6a74b15c2f")), - make_tuple(string("FreeBalance"), string("6482b9ade7bc6657aaca787ba1add3b4")), - }; - for (auto &test: tests) { - const auto inData = WRAPD(TWDataCreateWithBytes(reinterpret_cast(get<0>(test).c_str()), get<0>(test).length())); - const auto hash = WRAPD(TWHashTwoXXHash64Concat(inData.get())); - EXPECT_EQ(hex(data(TWDataBytes(hash.get()), TWDataSize(hash.get()))), get<1>(test)); - } -} - TEST(TWHashTests, SHA256SHA256) { auto tests = { make_tuple(string(""), string("5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456")), diff --git a/tests/interface/TWMnemonicTests.cpp b/tests/interface/TWMnemonicTests.cpp index 6ed7d3b1a9c..df507c553a9 100644 --- a/tests/interface/TWMnemonicTests.cpp +++ b/tests/interface/TWMnemonicTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include diff --git a/tests/interface/TWPBKDF2Tests.cpp b/tests/interface/TWPBKDF2Tests.cpp new file mode 100644 index 00000000000..57c48c73083 --- /dev/null +++ b/tests/interface/TWPBKDF2Tests.cpp @@ -0,0 +1,22 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "TestUtilities.h" + +#include + +#include + +TEST(TWPBKDF2, Sha256_sha512) { + auto password = DATA("50617373776f7264"); // Password + auto salt = DATA("4e61436c"); // NaCl + + auto sha256Result = WRAPD(TWPBKDF2HmacSha256(password.get(), salt.get(), 80000, 128)); + assertHexEqual(sha256Result, "4ddcd8f60b98be21830cee5ef22701f9641a4418d04c0414aeff08876b34ab56a1d425a1225833549adb841b51c9b3176a272bdebba1d078478f62b397f33c8d62aae85a11cdde829d89cb6ffd1ab0e63a981f8747d2f2f9fe5874165c83c168d2eed1d2d5ca4052dec2be5715623da019b8c0ec87dc36aa751c38f9893d15c3"); + + auto sha512Result = WRAPD(TWPBKDF2HmacSha512(password.get(), salt.get(), 80000, 128)); + assertHexEqual(sha512Result, "e6337d6fbeb645c794d4a9b5b75b7b30dac9ac50376a91df1f4460f6060d5addb2c1fd1f84409abacc67de7eb4056e6bb06c2d82c3ef4ccd1bded0f675ed97c65c33d39f81248454327aa6d03fd049fc5cbb2b5e6dac08e8ace996cdc960b1bd4530b7e754773d75f67a733fdb99baf6470e42ffcb753c15c352d4800fb6f9d6"); +} diff --git a/tests/interface/TWPrivateKeyTests.cpp b/tests/interface/TWPrivateKeyTests.cpp index d6fe4ea8f36..2ce9d6be10c 100644 --- a/tests/interface/TWPrivateKeyTests.cpp +++ b/tests/interface/TWPrivateKeyTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include "PrivateKey.h" #include "PublicKey.h" @@ -167,7 +167,7 @@ TEST(TWPrivateKeyTests, SignAsDER) { const auto data = WRAPD(TWDataCreateWithBytes((uint8_t *)message, strlen(message))); const auto hash = WRAPD(TWHashKeccak256(data.get())); - auto actual = WRAPD(TWPrivateKeySignAsDER(privateKey.get(), hash.get(), TWCurveSECP256k1)); + auto actual = WRAPD(TWPrivateKeySignAsDER(privateKey.get(), hash.get())); ASSERT_EQ(TW::hex(*((TW::Data*)actual.get())), "30450221008720a46b5b3963790d94bcc61ad57ca02fd153584315bfa161ed3455e336ba6202204d68df010ed934b8792c5b6a57ba86c3da31d039f9612b44d1bf054132254de9"); diff --git a/tests/interface/TWPublicKeyTests.cpp b/tests/interface/TWPublicKeyTests.cpp index b0ec026e983..64d712cc96a 100644 --- a/tests/interface/TWPublicKeyTests.cpp +++ b/tests/interface/TWPublicKeyTests.cpp @@ -1,10 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include "PublicKey.h" #include "PrivateKey.h" @@ -49,20 +49,20 @@ TEST(TWPublicKeyTests, CompressedExtended) { const auto privateKey = WRAP(TWPrivateKey, new TWPrivateKey{ key }); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); EXPECT_EQ(TWPublicKeyKeyType(publicKey.get()), TWPublicKeyTypeSECP256k1); - EXPECT_EQ(publicKey.get()->impl.bytes.size(), 33); + EXPECT_EQ(publicKey.get()->impl.bytes.size(), 33ul); EXPECT_EQ(TWPublicKeyIsCompressed(publicKey.get()), true); EXPECT_TRUE(TWPublicKeyIsValid(publicKey.get(), TWPublicKeyTypeSECP256k1)); auto extended = WRAP(TWPublicKey, TWPublicKeyUncompressed(publicKey.get())); EXPECT_EQ(TWPublicKeyKeyType(extended.get()), TWPublicKeyTypeSECP256k1Extended); - EXPECT_EQ(extended.get()->impl.bytes.size(), 65); + EXPECT_EQ(extended.get()->impl.bytes.size(), 65ul); EXPECT_EQ(TWPublicKeyIsCompressed(extended.get()), false); EXPECT_TRUE(TWPublicKeyIsValid(extended.get(), TWPublicKeyTypeSECP256k1Extended)); auto compressed = WRAP(TWPublicKey, TWPublicKeyCompressed(extended.get())); //EXPECT_TRUE(compressed == publicKey.get()); EXPECT_EQ(TWPublicKeyKeyType(compressed.get()), TWPublicKeyTypeSECP256k1); - EXPECT_EQ(compressed.get()->impl.bytes.size(), 33); + EXPECT_EQ(compressed.get()->impl.bytes.size(), 33ul); EXPECT_EQ(TWPublicKeyIsCompressed(compressed.get()), true); EXPECT_TRUE(TWPublicKeyIsValid(compressed.get(), TWPublicKeyTypeSECP256k1)); } @@ -81,6 +81,23 @@ TEST(TWPublicKeyTests, Verify) { ASSERT_TRUE(TWPublicKeyVerify(publicKey.get(), signature.get(), digest.get())); } +TEST(TWPublicKeyTests, VerifyAsDER) { + const PrivateKey key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + const auto privateKey = WRAP(TWPrivateKey, new TWPrivateKey{ key }); + + const char* message = "Hello"; + auto messageData = WRAPD(TWDataCreateWithBytes((const uint8_t*)message, strlen(message))); + auto digest = WRAPD(TWHashKeccak256(messageData.get())); + + auto signature = WRAPD(TWPrivateKeySignAsDER(privateKey.get(), digest.get())); + + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), false)); + + ASSERT_TRUE(TWPublicKeyVerifyAsDER(publicKey.get(), signature.get(), digest.get())); + + ASSERT_FALSE(TWPublicKeyVerify(publicKey.get(), signature.get(), digest.get())); +} + TEST(TWPublicKeyTests, VerifyEd25519) { const PrivateKey key(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); const auto privateKey = WRAP(TWPrivateKey, new TWPrivateKey{ key }); diff --git a/tests/interface/TWStoredKeyTests.cpp b/tests/interface/TWStoredKeyTests.cpp index ff498e4f6cb..6cfa96c04c8 100644 --- a/tests/interface/TWStoredKeyTests.cpp +++ b/tests/interface/TWStoredKeyTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -14,6 +14,7 @@ #include "../src/HexCoding.h" #include +#include #include @@ -40,7 +41,7 @@ struct std::shared_ptr createDefaultStoredKey() { } TEST(TWStoredKey, loadPBKDF2Key) { - const auto filename = WRAPS(TWStringCreateWithUTF8Bytes((TESTS_ROOT + "/Keystore/Data/pbkdf2.json").c_str())); + const auto filename = WRAPS(TWStringCreateWithUTF8Bytes((TESTS_ROOT + "/common/Keystore/Data/pbkdf2.json").c_str())); const auto key = WRAP(TWStoredKey, TWStoredKeyLoad(filename.get())); const auto keyId = WRAPS(TWStoredKeyIdentifier(key.get())); EXPECT_EQ(string(TWStringUTF8Bytes(keyId.get())), "3198bc9c-6672-5ab3-d995-4942343ae5b6"); @@ -56,7 +57,7 @@ TEST(TWStoredKey, createWallet) { const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); - const auto key = WRAP(TWStoredKey, TWStoredKeyCreate(name.get(), password.get())); + const auto key = WRAP(TWStoredKey, TWStoredKeyCreateLevel(name.get(), password.get(), TWStoredKeyEncryptionLevelDefault)); const auto name2 = WRAPS(TWStoredKeyName(key.get())); EXPECT_EQ(string(TWStringUTF8Bytes(name2.get())), "name"); const auto mnemonic = WRAPS(TWStoredKeyDecryptMnemonic(key.get(), password.get())); @@ -108,27 +109,73 @@ TEST(TWStoredKey, addressAddRemove) { const auto accountAddress = WRAPS(TWAccountAddress(accountCoin.get())); EXPECT_EQ(string(TWStringUTF8Bytes(accountAddress.get())), "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); - EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 1); + EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 1ul); const auto accountIdx = WRAP(TWAccount, TWStoredKeyAccount(key.get(), 0)); TWStoredKeyRemoveAccountForCoin(key.get(), coin); - EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 0); + EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 0ul); const auto addressAdd = "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"; const auto derivationPath = "m/84'/0'/0'/0/0"; const auto extPubKeyAdd = "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"; + const auto pubKey = "02df6fc590ab3101bbe0bb5765cbaeab9b5dcfe09ac9315d707047cbd13bc7e006"; TWStoredKeyAddAccount(key.get(), WRAPS(TWStringCreateWithUTF8Bytes(addressAdd)).get(), TWCoinTypeBitcoin, WRAPS(TWStringCreateWithUTF8Bytes(derivationPath)).get(), + WRAPS(TWStringCreateWithUTF8Bytes(pubKey)).get(), WRAPS(TWStringCreateWithUTF8Bytes(extPubKeyAdd)).get()); - EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 1); + EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 1ul); // invalid account index EXPECT_EQ(TWStoredKeyAccount(key.get(), 1001), nullptr); } +TEST(TWStoredKey, addressAddRemoveDerivationPath) { + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + + const auto coin = TWCoinTypeBitcoin; + const auto key = createAStoredKey(coin, password.get()); + + const auto wallet = WRAP(TWHDWallet, TWStoredKeyWallet(key.get(), password.get())); + const auto accountCoin = WRAP(TWAccount, TWStoredKeyAccountForCoin(key.get(), coin, wallet.get())); + const auto accountAddress = WRAPS(TWAccountAddress(accountCoin.get())); + EXPECT_EQ(string(TWStringUTF8Bytes(accountAddress.get())), "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); + + EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 1ul); + const auto accountIdx = WRAP(TWAccount, TWStoredKeyAccount(key.get(), 0)); + + const auto derivationPath0 = "m/84'/0'/0'/0/0"; + const auto derivationPath1 = "m/84'/0'/0'/1/0"; + + TWStoredKeyRemoveAccountForCoinDerivationPath(key.get(), coin, WRAPS(TWStringCreateWithUTF8Bytes(derivationPath1)).get()); + EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 1ul); + + TWStoredKeyRemoveAccountForCoinDerivationPath(key.get(), coin, WRAPS(TWStringCreateWithUTF8Bytes(derivationPath0)).get()); + EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 0ul); +} + +TEST(TWStoredKey, addressAddDerivation) { + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + + const auto coin = TWCoinTypeBitcoin; + const auto key = createAStoredKey(coin, password.get()); + const auto wallet = WRAP(TWHDWallet, TWStoredKeyWallet(key.get(), password.get())); + + const auto accountCoin1 = WRAP(TWAccount, TWStoredKeyAccountForCoinDerivation(key.get(), coin, TWDerivationDefault, wallet.get())); + const auto accountAddress1 = WRAPS(TWAccountAddress(accountCoin1.get())); + EXPECT_EQ(string(TWStringUTF8Bytes(accountAddress1.get())), "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); + + const auto accountCoin2 = WRAP(TWAccount, TWStoredKeyAccountForCoinDerivation(key.get(), coin, TWDerivationBitcoinLegacy, wallet.get())); + const auto accountAddress2 = WRAPS(TWAccountAddress(accountCoin2.get())); + EXPECT_EQ(string(TWStringUTF8Bytes(accountAddress2.get())), "1NyRyFewhZcWMa9XCj3bBxSXPXyoSg8dKz"); + + EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 2ul); +} + TEST(TWStoredKey, exportJSON) { const auto key = createDefaultStoredKey(); const auto json = WRAPD(TWStoredKeyExportJSON(key.get())); @@ -155,7 +202,7 @@ TEST(TWStoredKey, storeAndImportJSON) { Data json(length); size_t idx = 0; // read the slow way, ifs.read gave some false warnings with codacy - while (!ifs.eof() && idx < length) { char c = ifs.get(); json[idx++] = (uint8_t)c; } + while (!ifs.eof() && idx < static_cast(length)) { char c = ifs.get(); json[idx++] = (uint8_t)c; } const auto key2 = WRAP(TWStoredKey, TWStoredKeyImportJSON(WRAPD(TWDataCreateWithData(&json)).get())); const auto name2 = WRAPS(TWStoredKeyName(key2.get())); @@ -203,11 +250,11 @@ TEST(TWStoredKey, removeAccountForCoin) { ASSERT_NE(WRAP(TWAccount, TWStoredKeyAccountForCoin(key.get(), TWCoinTypeEthereum, wallet.get())).get(), nullptr); ASSERT_NE(WRAP(TWAccount, TWStoredKeyAccountForCoin(key.get(), TWCoinTypeBitcoin, wallet.get())).get(), nullptr); - ASSERT_EQ(TWStoredKeyAccountCount(key.get()), 2); + ASSERT_EQ(TWStoredKeyAccountCount(key.get()), 2ul); TWStoredKeyRemoveAccountForCoin(key.get(), TWCoinTypeBitcoin); - ASSERT_EQ(TWStoredKeyAccountCount(key.get()), 1); + ASSERT_EQ(TWStoredKeyAccountCount(key.get()), 1ul); ASSERT_NE(WRAP(TWAccount, TWStoredKeyAccountForCoin(key.get(), TWCoinTypeEthereum, nullptr)).get(), nullptr); ASSERT_EQ(WRAP(TWAccount, TWStoredKeyAccountForCoin(key.get(), TWCoinTypeBitcoin, nullptr)).get(), nullptr); @@ -221,7 +268,41 @@ TEST(TWStoredKey, getWalletPasswordInvalid) { const auto invalidString = WRAPS(TWStringCreateWithUTF8Bytes("_THIS_IS_INVALID_PASSWORD_")); const auto passwordInvalid = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(invalidString.get())), TWStringSize(invalidString.get()))); - auto key = WRAP(TWStoredKey, TWStoredKeyCreate (name.get(), password.get())); + auto key = WRAP(TWStoredKey, TWStoredKeyCreate(name.get(), password.get())); ASSERT_NE(WRAP(TWHDWallet, TWStoredKeyWallet(key.get(), password.get())).get(), nullptr); ASSERT_EQ(WRAP(TWHDWallet, TWStoredKeyWallet(key.get(), passwordInvalid.get())).get(), nullptr); } + +TEST(TWStoredKey, encryptionParameters) { + const auto key = createDefaultStoredKey(); + const auto params = WRAPS(TWStoredKeyEncryptionParameters(key.get())); + + nlohmann::json jsonParams = nlohmann::json::parse(string(TWStringUTF8Bytes(params.get()))); + + // compare some specific parameters + EXPECT_EQ(jsonParams["kdfparams"]["n"], 16384); + EXPECT_EQ(std::string(jsonParams["cipherparams"]["iv"]).length(), 32ul); + + // compare all keys, except dynamic ones (like cipherparams/iv) + jsonParams["cipherparams"] = {}; + jsonParams["ciphertext"] = ""; + jsonParams["kdfparams"]["salt"] = ""; + jsonParams["mac"] = ""; + const auto params2 = jsonParams.dump(); + assertJSONEqual(params2, R"( + { + "cipher": "aes-128-ctr", + "cipherparams": null, + "ciphertext": "", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 16384, + "p": 4, + "r": 8, + "salt": "" + }, + "mac": "" + } + )"); +} diff --git a/tests/interface/TWStringTests.cpp b/tests/interface/TWStringTests.cpp index 17d800b6782..e79755ba76d 100644 --- a/tests/interface/TWStringTests.cpp +++ b/tests/interface/TWStringTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include diff --git a/tests/interface/TWTransactionCompilerTests.cpp b/tests/interface/TWTransactionCompilerTests.cpp new file mode 100644 index 00000000000..fa97c94b719 --- /dev/null +++ b/tests/interface/TWTransactionCompilerTests.cpp @@ -0,0 +1,397 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include +#include +#include "proto/Binance.pb.h" +#include "proto/Bitcoin.pb.h" +#include "proto/Ethereum.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include "Bitcoin/Script.h" +#include "Bitcoin/SegwitAddress.h" + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "uint256.h" +#include + +#include "TestUtilities.h" +#include + +#include +#include +#include + +using namespace TW; + +TEST(TWTransactionCompiler, ExternalSignatureSignBinance) { + /// Step 1: Prepare transaction input (protobuf) + const auto coin = TWCoinTypeBinance; + const auto txInputData = WRAPD(TWTransactionCompilerBuildInput( + coin, + STRING("bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2").get(), // from + STRING("bnb1hlly02l6ahjsgxw9wlcswnlwdhg4xhx38yxpd5").get(), // to + STRING("1").get(), // amount + STRING("BNB").get(), // asset + STRING("").get(), // memo + STRING("Binance-Chain-Nile").get() // testnet chainId + )); + + { + // Check, by parsing + EXPECT_EQ((int)TWDataSize(txInputData.get()), 88); + Binance::Proto::SigningInput input; + ASSERT_TRUE(input.ParseFromArray(TWDataBytes(txInputData.get()), (int)TWDataSize(txInputData.get()))); + EXPECT_EQ(input.chain_id(), "Binance-Chain-Nile"); + EXPECT_TRUE(input.has_send_order()); + ASSERT_EQ(input.send_order().inputs_size(), 1); + EXPECT_EQ(hex(data(input.send_order().inputs(0).address())), "40c2979694bbc961023d1d27be6fc4d21a9febe6"); + } + + /// Step 2: Obtain preimage hash + const auto preImageHashes = WRAPD(TWTransactionCompilerPreImageHashes(coin, txInputData.get())); + auto preImageHash = data(TWDataBytes(preImageHashes.get()), TWDataSize(preImageHashes.get())); + + TxCompiler::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHash.data(), int(preImageHash.size()))); + ASSERT_EQ(preSigningOutput.error(), 0); + const auto preImageHashData = data(preSigningOutput.data_hash()); + + EXPECT_EQ(hex(preImageHashData), "3f3fece9059e714d303a9a1496ddade8f2c38fa78fc4cc2e505c5dbb0ea678d1"); + + // Simulate signature, normally obtained from signature server + const auto publicKeyData = parse_hex("026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e502"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1); + const auto signature = parse_hex("1b1181faec30b60a2ddaa2804c253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5366bb8b041781a6771248550d04094c3d7a504f9e8310679"); + + // Verify signature (pubkey & hash & signature) + { + EXPECT_TRUE(publicKey.verify(signature, preImageHashData)); + } + + /// Step 3: Compile transaction info + const auto outputData = WRAPD(TWTransactionCompilerCompileWithSignatures( + coin, + txInputData.get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&signature)).get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&publicKeyData)).get()) + ); + + const auto ExpectedTx = "b801f0625dee0a462a2c87fa0a1f0a1440c2979694bbc961023d1d27be6fc4d21a9febe612070a03424e421001121f0a14bffe47abfaede50419c577f1074fee6dd1535cd112070a03424e421001126a0a26eb5ae98721026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e50212401b1181faec30b60a2ddaa2804c253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5366bb8b041781a6771248550d04094c3d7a504f9e8310679"; + { + EXPECT_EQ(TWDataSize(outputData.get()), 189ul); + Binance::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), (int)TWDataSize(outputData.get()))); + + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private keys were not used anywhere up to this point. + Binance::Proto::SigningInput input; + ASSERT_TRUE(input.ParseFromArray(TWDataBytes(txInputData.get()), (int)TWDataSize(txInputData.get()))); + auto key = parse_hex("95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832"); + input.set_private_key(key.data(), key.size()); + + Binance::Proto::SigningOutput output; + ANY_SIGN(input, coin); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx); + } +} + +TEST(TWTransactionCompiler, ExternalSignatureSignEthereum) { + /// Step 1: Prepare transaction input (protobuf) + const auto coin = TWCoinTypeEthereum; + const auto txInputData0 = WRAPD(TWTransactionCompilerBuildInput( + coin, + STRING("0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F").get(), // from + STRING("0x3535353535353535353535353535353535353535").get(), // to + STRING("1000000000000000000").get(), // amount + STRING("ETH").get(), // asset + STRING("").get(), // memo + STRING("").get() // chainId + )); + + // Check, by parsing + EXPECT_EQ((int)TWDataSize(txInputData0.get()), 61); + Ethereum::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(TWDataBytes(txInputData0.get()), (int)TWDataSize(txInputData0.get()))); + EXPECT_EQ(hex(signingInput.chain_id()), "01"); + EXPECT_EQ(signingInput.to_address(), "0x3535353535353535353535353535353535353535"); + ASSERT_TRUE(signingInput.transaction().has_transfer()); + EXPECT_EQ(hex(signingInput.transaction().transfer().amount()), "0de0b6b3a7640000"); + + // Set a few other values + const auto nonce = store(uint256_t(11)); + const auto gasPrice = store(uint256_t(20000000000)); + const auto gasLimit = store(uint256_t(21000)); + signingInput.set_nonce(nonce.data(), nonce.size()); + signingInput.set_gas_price(gasPrice.data(), gasPrice.size()); + signingInput.set_gas_limit(gasLimit.data(), gasLimit.size()); + signingInput.set_tx_mode(Ethereum::Proto::Legacy); + + // Serialize back, this shows how to serialize SigningInput protobuf to byte array + const auto txInputDataData = data(signingInput.SerializeAsString()); + const auto txInputData = WRAPD(TWDataCreateWithBytes(txInputDataData.data(), txInputDataData.size())); + EXPECT_EQ((int)TWDataSize(txInputData.get()), 75); + + /// Step 2: Obtain preimage hash + const auto preImageHashes = WRAPD(TWTransactionCompilerPreImageHashes(coin, txInputData.get())); + auto preImageHash = data(TWDataBytes(preImageHashes.get()), TWDataSize(preImageHashes.get())); + + TxCompiler::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHash.data(), int(preImageHash.size()))); + ASSERT_EQ(preSigningOutput.error(), 0); + const auto preImageHashData = data(preSigningOutput.data_hash()); + EXPECT_EQ(hex(preImageHashData), "15e180a6274b2f6a572b9b51823fce25ef39576d10188ecdcd7de44526c47217"); + + // Simulate signature, normally obtained from signature server + const auto publicKeyData = parse_hex("044bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382ce28cab79ad7119ee1ad3ebcdb98a16805211530ecc6cfefa1b88e6dff99232a"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1Extended); + const auto signature = parse_hex("360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07b53bd9d1376e15a191d844db458893b928f3efbfee90c9febf51ab84c9796677900"); + + // Verify signature (pubkey & hash & signature) + { + EXPECT_TRUE(publicKey.verify(signature, preImageHashData)); + } + + /// Step 3: Compile transaction info + const auto outputData = WRAPD(TWTransactionCompilerCompileWithSignatures( + coin, + txInputData.get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&signature)).get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&publicKeyData)).get()) + ); + + const auto ExpectedTx = "f86c0b8504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a0360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07ba053bd9d1376e15a191d844db458893b928f3efbfee90c9febf51ab84c97966779"; + { + EXPECT_EQ(TWDataSize(outputData.get()), 183ul); + Ethereum::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), (int)TWDataSize(outputData.get()))); + + EXPECT_EQ(output.encoded().size(), 110ul); + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private keys were not used anywhere up to this point. + Ethereum::Proto::SigningInput input; + ASSERT_TRUE(input.ParseFromArray(TWDataBytes(txInputData.get()), (int)TWDataSize(txInputData.get()))); + auto key = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); + input.set_private_key(key.data(), key.size()); + + Ethereum::Proto::SigningOutput output; + ANY_SIGN(input, coin); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx); + } +} + +// data conversion utility +Data dataFromTWData(std::shared_ptr data) { + return TW::data(TWDataBytes(data.get()), TWDataSize(data.get())); +} + +TEST(TWTransactionCompiler, ExternalSignatureSignBitcoin) { + // Test external signining with a Bitcoin transaction with 3 input UTXOs, all used, but only using 2 public keys. + // Three signatures are neeeded. This illustrates that order of UTXOs/hashes is not always the same. + + const auto revUtxoHash0 = parse_hex("07c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa8"); + const auto revUtxoHash1 = parse_hex("d6892a5aa54e3b8fe430efd23f49a8950733aaa9d7c915d9989179f48dd1905e"); + const auto revUtxoHash2 = parse_hex("6021efcf7555f90627364339fc921139dd40a06ccb2cb2a2a4f8f4ea7a2dc74d"); + const auto inPubKey0 = parse_hex("024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382"); + const auto inPubKey1 = parse_hex("0217142f69535e4dad0dc7060df645c55a174cc1bfa5b9eb2e59aad2ae96072dfc"); + const auto inPubKeyHash0 = parse_hex("bd92088bb7e82d611a9b94fbb74a0908152b784f"); + const auto inPubKeyHash1 = parse_hex("6641abedacf9483b793afe1718689cc9420bbb1c"); + + // Input UTXO infos + struct UtxoInfo { + Data revUtxoHash; + Data publicKey; + long amount; + int index; + }; + std::vector utxoInfos = { + // first + UtxoInfo {revUtxoHash0, inPubKey0, 600'000, 0}, + // second UTXO, with same pubkey + UtxoInfo {revUtxoHash1, inPubKey0, 500'000, 1}, + // third UTXO, with different pubkey + UtxoInfo {revUtxoHash2, inPubKey1, 400'000, 0}, + }; + + // Signature infos, indexed by pubkeyhash+hash + struct SignatureInfo { + Data signature; + Data publicKey; + }; + std::map signatureInfos = { + { + hex(inPubKeyHash0) + "+" + "a296bead4172007be69b21971a790e076388666c162a9505698415f1b003ebd7", + { + parse_hex("304402201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b34902200a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f2a40"), + inPubKey0, + } + }, + { + hex(inPubKeyHash1) + "+" + "505f527f00e15fcc5a2d2416c9970beb57dfdfaca99e572a01f143b24dd8fab6", + { + parse_hex("3044022041294880caa09bb1b653775310fcdd1458da6b8e7d7fae34e37966414fe115820220646397c9d2513edc5974ecc336e9b287de0cdf071c366f3b3dc3ff309213e4e4"), + inPubKey1, + } + }, + { + hex(inPubKeyHash0) + "+" + "60ed6e9371e5ddc72fd88e46a12cb2f68516ebd307c0fd31b1b55cf767272101", + { + parse_hex("30440220764e3d5b3971c4b3e70b23fb700a7462a6fe519d9830e863a1f8388c402ad0b102207e777f7972c636961f92375a2774af3b7a2a04190251bbcb31d19c70927952dc"), + inPubKey0, + } + }, + }; + + const auto coin = TWCoinTypeBitcoin; + const auto ownAddress = "bc1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z00ppggv"; + + // Setup input for Plan + Bitcoin::Proto::SigningInput signingInput; + signingInput.set_coin_type(coin); + signingInput.set_hash_type(TWBitcoinSigHashTypeAll); + signingInput.set_amount(1'200'000); + signingInput.set_use_max_amount(false); + signingInput.set_byte_fee(1); + signingInput.set_to_address("bc1q2dsdlq3343vk29runkgv4yc292hmq53jedfjmp"); + signingInput.set_change_address(ownAddress); + + // process UTXOs + int count = 0; + for (auto& u: utxoInfos) { + const auto publicKey = PublicKey(u.publicKey, TWPublicKeyTypeSECP256k1); + const auto address = Bitcoin::SegwitAddress(publicKey, "bc"); + if (count == 0) EXPECT_EQ(address.string(), ownAddress); + if (count == 1) EXPECT_EQ(address.string(), ownAddress); + if (count == 2) EXPECT_EQ(address.string(), "bc1qveq6hmdvl9yrk7f6lct3s6yue9pqhwcuxedggg"); + + const auto utxoScript = Bitcoin::Script::lockScriptForAddress(address.string(), coin); + if (count == 0) EXPECT_EQ(hex(utxoScript.bytes), "0014bd92088bb7e82d611a9b94fbb74a0908152b784f"); + if (count == 1) EXPECT_EQ(hex(utxoScript.bytes), "0014bd92088bb7e82d611a9b94fbb74a0908152b784f"); + if (count == 2) EXPECT_EQ(hex(utxoScript.bytes), "00146641abedacf9483b793afe1718689cc9420bbb1c"); + + Data keyHash; + EXPECT_TRUE(utxoScript.matchPayToWitnessPublicKeyHash(keyHash)); + if (count == 0) EXPECT_EQ(hex(keyHash), hex(inPubKeyHash0)); + if (count == 1) EXPECT_EQ(hex(keyHash), hex(inPubKeyHash0)); + if (count == 2) EXPECT_EQ(hex(keyHash), hex(inPubKeyHash1)); + + const auto redeemScript = Bitcoin::Script::buildPayToPublicKeyHash(keyHash); + if (count == 0) EXPECT_EQ(hex(redeemScript.bytes), "76a914bd92088bb7e82d611a9b94fbb74a0908152b784f88ac"); + if (count == 1) EXPECT_EQ(hex(redeemScript.bytes), "76a914bd92088bb7e82d611a9b94fbb74a0908152b784f88ac"); + if (count == 2) EXPECT_EQ(hex(redeemScript.bytes), "76a9146641abedacf9483b793afe1718689cc9420bbb1c88ac"); + (*signingInput.mutable_scripts())[hex(keyHash)] = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); + + auto utxo = signingInput.add_utxo(); + utxo->set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); + utxo->set_amount(u.amount); + utxo->mutable_out_point()->set_hash(std::string(u.revUtxoHash.begin(), u.revUtxoHash.end())); + utxo->mutable_out_point()->set_index(u.index); + utxo->mutable_out_point()->set_sequence(UINT32_MAX); + + ++count; + } + EXPECT_EQ(count, 3); + EXPECT_EQ(signingInput.utxo_size(), 3); + + // Plan + Bitcoin::Proto::TransactionPlan plan; + ANY_PLAN(signingInput, plan, coin); + + // Plan is checked, assume it is accepted + EXPECT_EQ(plan.amount(), 1'200'000); + EXPECT_EQ(plan.fee(), 277); + EXPECT_EQ(plan.change(), 299'723); + ASSERT_EQ(plan.utxos_size(), 3); + // Note that UTXOs happen to be in reverse order compared to the input + EXPECT_EQ(hex(plan.utxos(0).out_point().hash()), hex(revUtxoHash2)); + EXPECT_EQ(hex(plan.utxos(1).out_point().hash()), hex(revUtxoHash1)); + EXPECT_EQ(hex(plan.utxos(2).out_point().hash()), hex(revUtxoHash0)); + + // Extend input with accepted plan + *signingInput.mutable_plan() = plan; + + // Serialize signingInput + const auto txInputDataData = data(signingInput.SerializeAsString()); + const auto txInputData = WRAPD(TWDataCreateWithBytes(txInputDataData.data(), txInputDataData.size())); + EXPECT_EQ((int)TWDataSize(txInputData.get()), 692); + + /// Step 2: Obtain preimage hashes + const auto preImageHashes = WRAPD(TWTransactionCompilerPreImageHashes(coin, txInputData.get())); + auto preImageHash = data(TWDataBytes(preImageHashes.get()), TWDataSize(preImageHashes.get())); + Bitcoin::Proto::PreSigningOutput preOutput; + ASSERT_TRUE(preOutput.ParseFromArray(preImageHash.data(), (int)preImageHash.size())); + ASSERT_EQ(preOutput.hash_public_keys_size(), 3); + auto hashes = preOutput.hash_public_keys(); + EXPECT_EQ(hex(hashes[0].data_hash()), "505f527f00e15fcc5a2d2416c9970beb57dfdfaca99e572a01f143b24dd8fab6"); + EXPECT_EQ(hex(hashes[1].data_hash()), "a296bead4172007be69b21971a790e076388666c162a9505698415f1b003ebd7"); + EXPECT_EQ(hex(hashes[2].data_hash()), "60ed6e9371e5ddc72fd88e46a12cb2f68516ebd307c0fd31b1b55cf767272101"); + EXPECT_EQ(hex(hashes[0].public_key_hash()), hex(inPubKeyHash1)); + EXPECT_EQ(hex(hashes[1].public_key_hash()), hex(inPubKeyHash0)); + EXPECT_EQ(hex(hashes[2].public_key_hash()), hex(inPubKeyHash0)); + + // Simulate signatures, normally obtained from signature server. + auto signatureVec = WRAP(TWDataVector, TWDataVectorCreate()); + auto pubkeyVec = WRAP(TWDataVector, TWDataVectorCreate()); + for (const auto& h: hashes) { + const auto& preImageHash_ = TW::data(h.data_hash()); + const auto& pubkeyhash = h.public_key_hash(); + + const std::string key = hex(pubkeyhash) + "+" + hex(preImageHash_); + const auto sigInfoFind = signatureInfos.find(key); + ASSERT_TRUE(sigInfoFind != signatureInfos.end()); + const auto& sigInfo = std::get<1>(*sigInfoFind); + const auto& publicKeyData = sigInfo.publicKey; + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1); + const auto signature = sigInfo.signature; + + TWDataVectorAdd(signatureVec.get(), WRAPD(TWDataCreateWithBytes(signature.data(), signature.size())).get()); + TWDataVectorAdd(pubkeyVec.get(), WRAPD(TWDataCreateWithBytes(publicKeyData.data(), publicKeyData.size())).get()); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verifyAsDER(signature, preImageHash_)); + } + /// Step 3: Compile transaction info + const auto outputData = WRAPD(TWTransactionCompilerCompileWithSignatures( + coin, txInputData.get(), signatureVec.get(), pubkeyVec.get() + )); + + const auto ExpectedTx = "010000000001036021efcf7555f90627364339fc921139dd40a06ccb2cb2a2a4f8f4ea7a2dc74d0000000000ffffffffd6892a5aa54e3b8fe430efd23f49a8950733aaa9d7c915d9989179f48dd1905e0100000000ffffffff07c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa80000000000ffffffff02804f1200000000001600145360df8231ac5965147c9d90ca930a2aafb05232cb92040000000000160014bd92088bb7e82d611a9b94fbb74a0908152b784f02473044022041294880caa09bb1b653775310fcdd1458da6b8e7d7fae34e37966414fe115820220646397c9d2513edc5974ecc336e9b287de0cdf071c366f3b3dc3ff309213e4e401210217142f69535e4dad0dc7060df645c55a174cc1bfa5b9eb2e59aad2ae96072dfc0247304402201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b34902200a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f2a400121024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382024730440220764e3d5b3971c4b3e70b23fb700a7462a6fe519d9830e863a1f8388c402ad0b102207e777f7972c636961f92375a2774af3b7a2a04190251bbcb31d19c70927952dc0121024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb49338200000000"; + { + EXPECT_EQ(TWDataSize(outputData.get()), 786ul); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), (int)TWDataSize(outputData.get()))); + + EXPECT_EQ(output.encoded().size(), 518ul); + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private keys were not used anywhere up to this point. + Bitcoin::Proto::SigningInput input; + ASSERT_TRUE(input.ParseFromArray(TWDataBytes(txInputData.get()), (int)TWDataSize(txInputData.get()))); + + // 2 private keys are needed (despite >2 UTXOs) + auto key0 = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); + auto key1 = parse_hex("7878787878787878787878787878787878787878787878787878787878787878"); + EXPECT_EQ(hex(PrivateKey(key0).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), hex(inPubKey0)); + EXPECT_EQ(hex(PrivateKey(key1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), hex(inPubKey1)); + *input.add_private_key() = std::string(key0.begin(), key0.end()); + *input.add_private_key() = std::string(key1.begin(), key1.end()); + + Bitcoin::Proto::SigningOutput output; + ANY_SIGN(input, coin); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx); + } +} diff --git a/tests/main.cpp b/tests/main.cpp index d75eab4a6bc..136462e0cce 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -1,11 +1,17 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. - #include +#ifdef WIN32 +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +#include std::string TESTS_ROOT; int main(int argc, char **argv) { @@ -20,8 +26,36 @@ int main(int argc, char **argv) { std::cerr << "Please specify the tests root folder. '" << TESTS_ROOT << "' is not a valid directory." << std::endl; exit(1); } + std::cout<<"TESTS_ROOT: "< +#include + + +std::string TESTS_ROOT; + +int main(int argc, char** argv) { + // current path + auto path = std::filesystem::current_path(); + // executable path + path.append(argv[0]); + // normalize + path = std::filesystem::canonical(path); + std::cout<<"normaliz path: "< /tmp/llvm-gcov.sh #!/bin/bash -exec /usr/bin/llvm-cov-11 gcov "\$@" +exec /usr/bin/llvm-cov-14 gcov "\$@" EOF sudo chmod 755 /tmp/llvm-gcov.sh } diff --git a/tools/dependencies-version b/tools/dependencies-version new file mode 100644 index 00000000000..38f12b7c4a0 --- /dev/null +++ b/tools/dependencies-version @@ -0,0 +1,7 @@ +#!/bin/bash + +export GTEST_VERSION=1.11.0 +export CHECK_VERSION=0.15.2 +export JSON_VERSION=3.10.2 +export PROTOBUF_VERSION=3.19.2 +export SWIFT_PROTOBUF_VERSION=1.18.0 diff --git a/tools/download-dependencies b/tools/download-dependencies new file mode 100644 index 00000000000..bd138ca5904 --- /dev/null +++ b/tools/download-dependencies @@ -0,0 +1,61 @@ +#!/bin/bash + +set -e + +ROOT="$PWD" +PREFIX="${PREFIX:-$ROOT/build/local}" + +# Load dependencies version +BASE_DIR=$(cd `dirname $0`; pwd) +source ${BASE_DIR}/dependencies-version + +function download_gtest() { + echo "Downloading gtest..." + GTEST_DIR="$ROOT/build/local/src/gtest" + mkdir -p "$GTEST_DIR" + cd "$GTEST_DIR" + if [ ! -f release-$GTEST_VERSION.tar.gz ]; then + curl -fSsOL https://github.com/google/googletest/archive/release-$GTEST_VERSION.tar.gz + fi + tar xzf release-$GTEST_VERSION.tar.gz +} + +function download_libcheck() { + echo "Downloading libcheck..." + CHECK_DIR="$ROOT/build/local/src/check" + mkdir -p "$CHECK_DIR" + cd "$CHECK_DIR" + if [ ! -f check-$CHECK_VERSION.tar.gz ]; then + curl -fSsOL https://github.com/libcheck/check/releases/download/$CHECK_VERSION/check-$CHECK_VERSION.tar.gz + fi + tar xzf check-$CHECK_VERSION.tar.gz +} + +function download_nolhmann_json() { + echo "Downloading nolhmann_json..." + JSON_DIR="$ROOT/build/local/json" + mkdir -p "$JSON_DIR" + cd "$JSON_DIR" + if [ ! -f include.zip ]; then + curl -fSsOL https://github.com/nlohmann/json/releases/download/v$JSON_VERSION/include.zip + fi + unzip -qq -d "$PREFIX" -o include.zip +} + +function download_protobuf() { + echo "Downloading protobuf..." + PROTOBUF_DIR="$ROOT/build/local/src/protobuf" + mkdir -p "$PROTOBUF_DIR" + cd "$PROTOBUF_DIR" + if [ ! -f protobuf-java-$PROTOBUF_VERSION.tar.gz ]; then + curl -fSsOL https://github.com/protocolbuffers/protobuf/releases/download/v$PROTOBUF_VERSION/protobuf-java-$PROTOBUF_VERSION.tar.gz + fi + tar xzf protobuf-java-$PROTOBUF_VERSION.tar.gz +} + +download_gtest +download_libcheck +download_nolhmann_json +download_protobuf + +echo "done." diff --git a/tools/doxygen_convert_comments b/tools/doxygen_convert_comments new file mode 100644 index 00000000000..91870ffb466 --- /dev/null +++ b/tools/doxygen_convert_comments @@ -0,0 +1,28 @@ +#!/bin/bash + +SWIFT_PARAMETER_PATTERN='s/\\param\s+([^\s]+)/\- Parameter $1:/g' +SWIFT_RETURN_PATTERN='s/\\return/\- Returns:/g' +SWIFT_NOTE_PATTERN='s/\\note/\- Note:/g' +SWIFT_SEE_PATTERN='s/\\see/\- SeeAlso:/g' +SWIFT_FOLDER_PATH="swift/Sources/Generated/*.swift" +SWIFT_FOLDER_PATH_BAK="swift/Sources/Generated/*.bak" + +function process_swift_comments() { + perl -pi.bak -e "$SWIFT_PARAMETER_PATTERN" "$1" + perl -pi.bak -e "$SWIFT_RETURN_PATTERN" "$1" + perl -pi.bak -e "$SWIFT_NOTE_PATTERN" "$1" + perl -pi.bak -e "$SWIFT_SEE_PATTERN" "$1" +} + + +function swift_convert() { + echo "Processing swift conversion" + + for d in $SWIFT_FOLDER_PATH ; do + process_swift_comments $d + done + + rm -rf $SWIFT_FOLDER_PATH_BAK +} + +swift_convert diff --git a/tools/generate-files b/tools/generate-files index 5df2a69f23e..aa694add2cc 100755 --- a/tools/generate-files +++ b/tools/generate-files @@ -49,16 +49,21 @@ codegen/bin/coins # Generate interface code codegen/bin/codegen +# Convert doxygen comments to appropriate format +tools/doxygen_convert_comments + # Generate Java, C++ and Swift Protobuf files if [ -x "$(command -v protoc-gen-swift)" ]; then - "$PROTOC" -I=$PREFIX/include -I=src/proto --cpp_out=src/proto --java_out=jni/java --swift_out=swift/Sources/Generated/Protobuf --swift_opt=Visibility=Public src/proto/*.proto + "$PROTOC" -I=$PREFIX/include -I=src/proto --cpp_out=src/proto --java_out=lite:jni/java --swift_out=swift/Sources/Generated/Protobuf --swift_opt=Visibility=Public src/proto/*.proto else - "$PROTOC" -I=$PREFIX/include -I=src/proto --cpp_out=src/proto --java_out=jni/java src/proto/*.proto + "$PROTOC" -I=$PREFIX/include -I=src/proto --cpp_out=src/proto --java_out=lite:jni/java src/proto/*.proto fi -# Generate internal message protocol Protobuf files -- not every time +# Generate internal message protocol Protobuf files "$PROTOC" -I=$PREFIX/include -I=src/Tron/Protobuf --cpp_out=src/Tron/Protobuf src/Tron/Protobuf/*.proto "$PROTOC" -I=$PREFIX/include -I=src/Zilliqa/Protobuf --cpp_out=src/Zilliqa/Protobuf src/Zilliqa/Protobuf/*.proto +"$PROTOC" -I=$PREFIX/include -I=src/Cosmos/Protobuf --cpp_out=src/Cosmos/Protobuf src/Cosmos/Protobuf/*.proto +"$PROTOC" -I=$PREFIX/include -I=tests/chains/Cosmos/Protobuf --cpp_out=tests/chains/Cosmos/Protobuf tests/chains/Cosmos/Protobuf/*.proto # Generate Proto interface file "$PROTOC" -I=$PREFIX/include -I=src/proto --plugin=$PREFIX/bin/protoc-gen-c-typedef --c-typedef_out include/TrustWalletCore src/proto/*.proto diff --git a/tools/gtest.patch b/tools/gtest.patch deleted file mode 100644 index 83138001530..00000000000 --- a/tools/gtest.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- CMakeLists.txt 2019-10-03 23:52:15.000000000 +0900 -+++ CMakeLists.txt.patched 2021-01-29 10:16:41.000000000 +0900 -@@ -1,7 +1,7 @@ - # Note: CMake support is community-based. The maintainers do not use CMake - # internally. - --cmake_minimum_required(VERSION 2.8.8) -+cmake_minimum_required(VERSION 2.8.12) - - if (POLICY CMP0048) - cmake_policy(SET CMP0048 NEW) diff --git a/tools/gtest_mock.patch b/tools/gtest_mock.patch deleted file mode 100644 index 2b9dfb81c62..00000000000 --- a/tools/gtest_mock.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- googlemock/CMakeLists.txt 2019-10-03 23:52:15.000000000 +0900 -+++ googlemock/CMakeLists.txt.patched 2021-01-29 10:28:32.000000000 +0900 -@@ -42,7 +42,7 @@ - cmake_policy(SET CMP0048 NEW) - project(gmock VERSION ${GOOGLETEST_VERSION} LANGUAGES CXX C) - endif() --cmake_minimum_required(VERSION 2.6.4) -+cmake_minimum_required(VERSION 2.8.12) - - if (COMMAND set_up_hermetic_build) - set_up_hermetic_build() diff --git a/tools/gtest_test.patch b/tools/gtest_test.patch deleted file mode 100644 index 09b0d659c79..00000000000 --- a/tools/gtest_test.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- googletest/CMakeLists.txt 2019-10-03 23:52:15.000000000 +0900 -+++ googletest/CMakeLists.txt.patched 2021-01-29 10:24:45.000000000 +0900 -@@ -53,7 +53,7 @@ - cmake_policy(SET CMP0048 NEW) - project(gtest VERSION ${GOOGLETEST_VERSION} LANGUAGES CXX C) - endif() --cmake_minimum_required(VERSION 2.6.4) -+cmake_minimum_required(VERSION 2.8.12) - - if (POLICY CMP0063) # Visibility - cmake_policy(SET CMP0063 NEW) diff --git a/tools/install-android-dependencies b/tools/install-android-dependencies new file mode 100644 index 00000000000..7f3d5c64b88 --- /dev/null +++ b/tools/install-android-dependencies @@ -0,0 +1,8 @@ +#!/bin/bash + +set -e + +$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --verbose "cmake;3.18.1" "ndk;23.1.7779620" +$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "system-images;android-26;google_apis;x86" + +echo -e "y\ny\ny\ny\ny\n" | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses diff --git a/tools/install-dependencies b/tools/install-dependencies index 041930f4800..3a04f8434bd 100755 --- a/tools/install-dependencies +++ b/tools/install-dependencies @@ -6,6 +6,13 @@ ROOT="$PWD" PREFIX="${PREFIX:-$ROOT/build/local}" echo "PREFIX: $PREFIX" +CMAKE=cmake +MAKE=make + +# Load dependencies version +BASE_DIR=$(cd `dirname $0`; pwd) +source ${BASE_DIR}/dependencies-version + # Setup up folders export PATH="$PREFIX/bin":$PATH export LDFLAGS="-L$PREFIX/lib" @@ -14,78 +21,57 @@ export DYLD_LIBRARY_PATH="$PREFIX/lib" export LD_LIBRARY_PATH="$PREFIX/lib" export LD_RUN_PATH="$PREFIX/lib" -# Download Google Test -export GTEST_VERSION=1.10.0 -GTEST_DIR="$ROOT/build/local/src/gtest" -mkdir -p "$GTEST_DIR" -cd "$GTEST_DIR" -if [ ! -f release-$GTEST_VERSION.tar.gz ]; then - curl -fSsOL https://github.com/google/googletest/archive/release-$GTEST_VERSION.tar.gz -fi -tar xzf release-$GTEST_VERSION.tar.gz - -# Build gtest -cd googletest-release-$GTEST_VERSION -echo "patching cmake minimum version" -cp $ROOT/tools/*.patch . -patch CMakeLists.txt gtest.patch -patch googletest/CMakeLists.txt gtest_test.patch -patch googlemock/CMakeLists.txt gtest_mock.patch - -cmake -DCMAKE_INSTALL_PREFIX:PATH=$PREFIX -H. -make -j4 -make install -make clean - -# Download Check -export CHECK_VERSION=0.15.2 -CHECK_DIR="$ROOT/build/local/src/check" -mkdir -p "$CHECK_DIR" -cd "$CHECK_DIR" -if [ ! -f check-$CHECK_VERSION.tar.gz ]; then - curl -fSsOL https://github.com/libcheck/check/releases/download/$CHECK_VERSION/check-$CHECK_VERSION.tar.gz -fi -tar xzf check-$CHECK_VERSION.tar.gz - -# Build Check -cd check-$CHECK_VERSION -./configure --prefix="$PREFIX" -make -j4 -make install -make clean - -# Download Nlohmann JSON -export JSON_VERSION=3.10.2 -JSON_DIR="$ROOT/build/local/json" -mkdir -p "$JSON_DIR" -cd "$JSON_DIR" -if [ ! -f include.zip ]; then - curl -fSsOL https://github.com/nlohmann/json/releases/download/v$JSON_VERSION/include.zip -fi -unzip -d "$PREFIX" -o include.zip - -# Download Protobuf sources -export PROTOBUF_VERSION=3.14.0 -PROTOBUF_DIR="$ROOT/build/local/src/protobuf" -mkdir -p "$PROTOBUF_DIR" -cd "$PROTOBUF_DIR" -if [ ! -f protobuf-java-$PROTOBUF_VERSION.tar.gz ]; then - curl -fSsOL https://github.com/protocolbuffers/protobuf/releases/download/v$PROTOBUF_VERSION/protobuf-java-$PROTOBUF_VERSION.tar.gz -fi -tar xzf protobuf-java-$PROTOBUF_VERSION.tar.gz - -# Build Protobuf -cd protobuf-$PROTOBUF_VERSION -./configure --prefix="$PREFIX" -make -j4 -make install -# after install, cleanup to save space (docker) -make clean -"$PREFIX/bin/protoc" --version - -if [[ -x "$(command -v swift)" && `uname` == "Darwin" ]]; then - # Download Swift Protobuf sources - export SWIFT_PROTOBUF_VERSION=1.18.0 +function download_dependencies() { + ${BASE_DIR}/download-dependencies +} + +function build_gtest() { + # Build gtest + GTEST_DIR="$ROOT/build/local/src/gtest" + cd ${GTEST_DIR}/googletest-release-$GTEST_VERSION + $CMAKE -DCMAKE_INSTALL_PREFIX:PATH=$PREFIX -H. + $MAKE -j4 + $MAKE install + $MAKE clean +} + +function build_libcheck() { + # Build Check + CHECK_DIR="$ROOT/build/local/src/check" + cd ${CHECK_DIR}/check-$CHECK_VERSION + $CMAKE -DCMAKE_INSTALL_PREFIX:PATH=$PREFIX -H. + $MAKE -j4 + $MAKE install + $MAKE clean +} + +function build_protobuf() { + # Build Protobuf + PROTOBUF_DIR="$ROOT/build/local/src/protobuf" + cd ${PROTOBUF_DIR}/protobuf-$PROTOBUF_VERSION + + ./configure --prefix="$PREFIX" + $MAKE -j4 + $MAKE install + + # after install, cleanup to save space (docker) + make clean + "$PREFIX/bin/protoc" --version + + # Protobuf plugins + cd "$ROOT/protobuf-plugin" + $CMAKE -H. -Bbuild -DCMAKE_INSTALL_PREFIX=$PREFIX + $MAKE -Cbuild -j12 + $MAKE -Cbuild install + rm -rf build + + if [[ -x "$(command -v swift)" && `uname` == "Darwin" ]]; then + build_swift_plugin + fi +} + +function build_swift_plugin() { + # Download Swift Protobuf sources SWIFT_PROTOBUF_DIR="$ROOT/build/local/src/swift-protobuf" mkdir -p "$SWIFT_PROTOBUF_DIR" cd "$SWIFT_PROTOBUF_DIR" @@ -99,13 +85,12 @@ if [[ -x "$(command -v swift)" && `uname` == "Darwin" ]]; then swift build --static-swift-stdlib -c release cp -f "$SWIFT_PROTOBUF_DIR/swift-protobuf-$SWIFT_PROTOBUF_VERSION/.build/release/protoc-gen-swift" "$PREFIX/bin" | true $PREFIX/bin/protoc-gen-swift --version -fi - -# Protobuf plugins -cd "$ROOT/protobuf-plugin" -cmake -H. -Bbuild -DCMAKE_INSTALL_PREFIX=$PREFIX -make -Cbuild -j12 -make -Cbuild install -rm -rf build +} + +download_dependencies + +build_gtest +build_libcheck +build_protobuf cd "$ROOT" diff --git a/tools/install-wasm-dependencies b/tools/install-wasm-dependencies new file mode 100644 index 00000000000..dcf17caa686 --- /dev/null +++ b/tools/install-wasm-dependencies @@ -0,0 +1,15 @@ +#!/bin/bash + +set -e + +emsdk_version=3.1.22 + +git clone https://github.com/emscripten-core/emsdk.git + +cd emsdk + + +./emsdk install $emsdk_version && ./emsdk activate $emsdk_version + +curl -fSsOL https://github.com/emscripten-ports/boost/releases/download/boost-1.75.0/boost-headers-1.75.0.zip +unzip boost-headers-1.75.0.zip -d upstream/emscripten/system/include diff --git a/tools/ios-build b/tools/ios-build index bf5fa47251a..e1b3945ecfc 100755 --- a/tools/ios-build +++ b/tools/ios-build @@ -23,7 +23,7 @@ init() { # build destination archivePath build() { echo "Building scheme: $1, destination: $2, path: $3" - xcodebuild archive -project swift/${FRAMEWORK}.xcodeproj -scheme "$1" -destination "$2" -archivePath "$BUILD_FOLDER/$3".xcarchive SKIP_INSTALL=NO | xcpretty + xcodebuild archive -project swift/${FRAMEWORK}.xcodeproj -scheme "$1" -destination "$2" -archivePath "$BUILD_FOLDER/$3".xcarchive SKIP_INSTALL=NO | xcbeautify } build_ios_arm64() { diff --git a/tools/ios-doc b/tools/ios-doc new file mode 100644 index 00000000000..9c004897a18 --- /dev/null +++ b/tools/ios-doc @@ -0,0 +1,22 @@ +#!/bin/bash + +# https://developer.apple.com/documentation/xcode/distributing-documentation-to-external-developers + +set -e +set -o pipefail + +pushd swift +mkdir -p build && rm -rf build/*.doccarchive + +export DOCC_JSON_PRETTYPRINT="YES" +xcodebuild -workspace TrustWalletCore.xcworkspace -derivedDataPath build/docsData -scheme WalletCore -destination 'platform=iOS Simulator,name=iPhone 13 Pro Max' -parallelizeTargets docbuild | xcbeautify + +pushd build + +mv `find docsData/Build/Products -type d -name "*.doccarchive"` . + +echo "Zipping docc archive..." +zip -rq WalletCore.doccarchive.zip WalletCore.doccarchive + +popd # build +popd # swift diff --git a/tools/ios-release b/tools/ios-release index 1499240cbff..85a70879c09 100755 --- a/tools/ios-release +++ b/tools/ios-release @@ -37,8 +37,9 @@ popd # upload to release download_url=$(wc_upload_asset ${release_url} ${filename}) +echo "download_url is $download_url" download_url=$(tr -d '"' <<< $download_url) -echo "download_url is ${download_url}" +echo "trimmed download_url is ${download_url}" # create podspec podspec_name=TrustWalletCore diff --git a/tools/ios-test b/tools/ios-test index 4839435c61a..62d09d61417 100755 --- a/tools/ios-test +++ b/tools/ios-test @@ -3,9 +3,15 @@ # This script runs the iOS tests. set -e +set -o pipefail pushd swift -xcodegen -pod install -fastlane scan --workspace TrustWalletCore.xcworkspace --scheme TrustWalletCore --sdk iphonesimulator --device='iPhone 13' --clean + +xcodegen && pod install +xcodebuild -workspace TrustWalletCore.xcworkspace \ + -scheme WalletCore \ + -sdk iphonesimulator \ + -destination "platform=iOS Simulator,name=iPhone 13" \ + test | xcbeautify + popd diff --git a/tools/ios-xcframework b/tools/ios-xcframework index c2cb0191073..d5d667f4b14 100755 --- a/tools/ios-xcframework +++ b/tools/ios-xcframework @@ -5,8 +5,10 @@ set -e -patch build/local/src/protobuf/protobuf-3.14.0/src/google/protobuf/stubs/common.cc swift/protobuf.patch +echo "Building Docc..." +tools/ios-doc +echo "Building xcframework..." pushd swift fastlane ios xcframework popd diff --git a/tools/ios-xcframework-release b/tools/ios-xcframework-release index a7843f6a69d..0ce859a8cc1 100755 --- a/tools/ios-xcframework-release +++ b/tools/ios-xcframework-release @@ -1,5 +1,6 @@ #!/bin/bash +set -o pipefail set -e # load release library code @@ -16,13 +17,19 @@ pushd ${base_dir}/../swift/build # archive xcframeworks core_zip_filename="WalletCore.xcframework.zip" +core_dsyms_filename="WalletCore.xcframework.dSYM.zip" protobuf_zip_filename="SwiftProtobuf.xcframework.zip" +protobuf_dsyms_filename="SwiftProtobuf.xcframework.dSYM.zip" -rm -rf ${core_zip_filename} ${protobuf_zip_filename} +rm -rf ${core_zip_filename} ${core_dsyms_filename} ${protobuf_zip_filename} ${protobuf_dsyms_filename} + +zip -r ${core_dsyms_filename} WalletCore.dSYMs zip -r ${core_zip_filename} WalletCore.xcframework core_hash=$(/usr/bin/shasum -a 256 ${core_zip_filename} | awk '{printf $1}') +zip -r ${protobuf_dsyms_filename} SwiftProtobuf.dSYMs + zip -r ${protobuf_zip_filename} SwiftProtobuf.xcframework protobuf_hash=$(/usr/bin/shasum -a 256 ${protobuf_zip_filename} | awk '{printf $1}') @@ -44,9 +51,8 @@ let package = Package( name: "WalletCore", platforms: [.iOS(.v13)], products: [ - .library( - name: "WalletCore", targets: ["WalletCore", "SwiftProtobuf"] - ) + .library(name: "WalletCore", targets: ["WalletCore"]), + .library(name: "SwiftProtobuf", targets: ["SwiftProtobuf"]) ], dependencies: [], targets: [ @@ -64,8 +70,19 @@ let package = Package( ) EOF -echo "Package.swift generated." - +echo "${package_swift} generated." cat $package_swift +package_swift_download_url=$(wc_upload_asset ${release_url} ${package_swift}) +echo "${package_swift} url is: ${package_swift_download_url}" + +protobuf_dsyms_url=$(wc_upload_asset ${release_url} ${protobuf_dsyms_filename}) +echo "protobuf dsyms url is: ${protobuf_dsyms_url}" + +core_dsyms_url=$(wc_upload_asset ${release_url} ${core_dsyms_filename}) +echo "core dsyms url is: ${core_dsyms_url}" + +docc_url=$(wc_upload_asset ${release_url} WalletCore.doccarchive.zip) +echo "docc url is: ${docc_url}" + popd diff --git a/tools/library b/tools/library index 58af6624a2c..46891c22dc9 100755 --- a/tools/library +++ b/tools/library @@ -11,7 +11,11 @@ wc_read_version() { wc_release_url() { tag="$1" - id=$(curl "https://api.github.com/repos/trustwallet/wallet-core/releases/tags/${tag}" | jq ".id") + id=$(curl -u ${GITHUB_USER}:${GITHUB_TOKEN} "https://api.github.com/repos/trustwallet/wallet-core/releases/tags/${tag}" | jq ".id") + if [[ $id == "null" ]]; then + echo "Failed to get release id for tag ${tag}" + exit 22 + fi release_url="https://uploads.github.com/repos/trustwallet/wallet-core/releases/${id}" echo ${release_url} } @@ -21,6 +25,6 @@ wc_upload_asset() { filename="$2" upload_url="${release_url}/assets?name=${filename}" - download_url=$(curl -u ${GITHUB_USER}:${GITHUB_TOKEN} -X POST -H "Content-Type: application/octet-stream" --data-binary @${filename} ${upload_url} | jq ".browser_download_url") + download_url=$(curl --progress-bar --retry 3 -u ${GITHUB_USER}:${GITHUB_TOKEN} -X POST -H "Content-Type: application/octet-stream" --data-binary @${filename} ${upload_url} | jq ".browser_download_url") echo ${download_url} } diff --git a/tools/lint-all b/tools/lint-all index e3e1eb781c8..c2afc4bc026 100755 --- a/tools/lint-all +++ b/tools/lint-all @@ -4,4 +4,5 @@ set -e -find src \( -name "*.cpp" -o -name "*.h" \) -not -path "./src/proto/*" -not -path "./src/Tron/Protobuf/*" -exec clang-tidy-11 -quiet -p=build '{}' \; +clang-tidy --version +find src \( -name "*.cpp" -o -name "*.h" \) -not -path "./src/proto/*" -not -path "./src/Tron/Protobuf/*" -exec clang-tidy -quiet -p=build '{}' \; diff --git a/tools/pvs-studio-analyze b/tools/pvs-studio-analyze new file mode 100644 index 00000000000..c219fbefc52 --- /dev/null +++ b/tools/pvs-studio-analyze @@ -0,0 +1,25 @@ +#!/bin/bash +# +# This script generate a build folder with pvs studio enabled and run an analyze +# You need to have ninja, pvs-studio-analyzer and llvm installed +# If you are on macOS set CC/CXX to clang/clang++ from brew + +base_dir=$( + cd $(dirname $0) + pwd +) +src_dir=${base_dir}/.. + +mkdir -p "${src_dir}"/build_pvs_studio +cd "${src_dir}"/build_pvs_studio + +if [[ "$OSTYPE" == "linux-gnu"* ]]; then + cmake -GNinja -DTW_ENABLE_PVS_STUDIO=ON -DTW_IDE_VSCODE=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ ../ +elif [[ "$OSTYPE" == "darwin"* ]]; then + llvm_bin="$(brew --prefix)/opt/llvm/bin" + cmake -GNinja -DTW_ENABLE_PVS_STUDIO=ON -DTW_IDE_VSCODE=ON -DCMAKE_C_COMPILER=$llvm_bin/clang -DCMAKE_CXX_COMPILER=$llvm_bin/clang++ ../ +fi + +ninja TrustWalletCore.analyze + +cd - diff --git a/tools/pvs-studio/config.cfg b/tools/pvs-studio/config.cfg new file mode 100644 index 00000000000..4823855e789 --- /dev/null +++ b/tools/pvs-studio/config.cfg @@ -0,0 +1,11 @@ +exclude-path=*/boost/* +exclude-path=*/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform* +exclude-path=*/src/proto* +exclude-path=*/src/Cosmos/Protobuf* +exclude-path=*/build/local/include* +exclude-path=*/build/local/* +exclude-path=*/build/local/src/google/protobuf/* +exclude-path=*/opt/homebrew/opt/llvm* +exclude-path=*/src/Zilliqa/Protobuf* +exclude-path=*/src/Tron/Protobuf* +exclude-path=*/opt/homebrew/Cellar/llvm* diff --git a/tools/wasm-build b/tools/wasm-build new file mode 100644 index 00000000000..2fa483726a2 --- /dev/null +++ b/tools/wasm-build @@ -0,0 +1,22 @@ +#!/bin/bash + +set -e + +base_dir=$(cd `dirname $0`; pwd) +src_dir=${base_dir}/.. + +if [[ -z ${EMSDK} ]]; then + source ${src_dir}/emsdk/emsdk_env.sh +fi + +build_folder=${src_dir}/wasm-build +boost_dir=${EMSDK}/upstream/emscripten/system/include +pushd ${src_dir} + +# cmake +cmake -Bwasm-build -DBoost_INCLUDE_DIR=${boost_dir} -DTW_COMPILE_WASM=ON -DCMAKE_TOOLCHAIN_FILE=${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake + +# make +make -j8 -Cwasm-build + +popd diff --git a/tools/wasm-set-version b/tools/wasm-set-version new file mode 100644 index 00000000000..ca3828fd0c1 --- /dev/null +++ b/tools/wasm-set-version @@ -0,0 +1,13 @@ +#!/bin/bash + +base_dir=$(cd `dirname $0`; pwd) +src_dir=${base_dir}/.. +package_json=${src_dir}/wasm/package.json +version="$1" + +if [ -z "${version}" ]; then + version=`git describe --long --tags | cut -f 1 -d "-"` +fi + +new_package_json=$(jq --arg tag "${version}" '.version = $tag' ${package_json}) +echo ${new_package_json} | jq . > ${package_json} diff --git a/tools/windows-build-and-test.ps1 b/tools/windows-build-and-test.ps1 new file mode 100644 index 00000000000..815b73ab1ca --- /dev/null +++ b/tools/windows-build-and-test.ps1 @@ -0,0 +1,25 @@ +# Build Wallet Core + +# Run after installing `dependencies` and `generating` code +# > powershell .\tools\windows-build-and-test.ps1 + +# Builds Wallet Core in static release mode, to build the console wallet +# Builds Wallet Core in dynamic release and debug mode, to build a DLL for applications + + + +# Fail if any commands fails +$ErrorActionPreference = "Stop" + +#Set the position of the toolchain = $toolsPath +$toolsPath = $PSScriptRoot + +echo "#### Generating files... ####" +& $toolsPath\windows-generate.ps1 + + +echo "#### Building... ####" +& $toolsPath\windows-build.ps1 + +& $toolsPath\windows-tests.ps1 + diff --git a/tools/windows-build.ps1 b/tools/windows-build.ps1 index e80121b8a60..da19f546275 100644 --- a/tools/windows-build.ps1 +++ b/tools/windows-build.ps1 @@ -1,24 +1,13 @@ - -# Build Wallet Core - -# Run after installing dependencies and generating code -# > powershell .\tools\windows-build.ps1 - -# Builds Wallet Core in static release mode, to build the console wallet -# Builds Wallet Core in dynamic release and debug mode, to build a DLL for applications - -$ErrorActionPreference = "Stop" +# Load dependencies version +. $PSScriptRoot\windows-dependencies-version.ps1 $root = $pwd $prefix = Join-Path $pwd "build\local" $install = Join-Path $pwd "build\install" -$cmakeGenerator = "Visual Studio 17 2022" -$cmakePlatform = "x64" -$cmakeToolset = "v143" if (Test-Path -Path $install -PathType Container) { - Remove-Item –Path $install -Recurse + Remove-Item ¨CPath $install -Recurse } cd build @@ -36,7 +25,7 @@ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } $libInstall = Join-Path $install "lib\TrustWalletCore.lib" -Remove-Item –Path $libInstall # Replaced with the shared lib afterwards +Remove-Item ¨CPath $libInstall # Replaced with the shared lib afterwards cd .. if (-not(Test-Path -Path "shared" -PathType Container)) { @@ -59,7 +48,7 @@ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } $cppInclude = Join-Path $install "include\WalletCore" -Remove-Item –Path $cppInclude -Recurse # Useless from shared library +Remove-Item ¨CPath $cppInclude -Recurse # Useless from shared library cd .. cd $root diff --git a/tools/windows-dependencies-version.ps1 b/tools/windows-dependencies-version.ps1 new file mode 100644 index 00000000000..00e6f353683 --- /dev/null +++ b/tools/windows-dependencies-version.ps1 @@ -0,0 +1,14 @@ + +$gtestVersion="1.11.0" +$checkVersion="0.15.2" +$jsonVersion="3.10.2" +$boostVersion = "1.80.0" +$protobufVersion="3.19.2" +$SWIFT_PROTOBUF_VERSION="1.18.0" + + + +$cmakeGenerator = "Visual Studio 16 2019" +$cmakePlatform = "x64" +$cmakeToolset = "v142" + diff --git a/tools/windows-dependencies.ps1 b/tools/windows-dependencies.ps1 index 3e92b23f098..8549e7f3e5c 100644 --- a/tools/windows-dependencies.ps1 +++ b/tools/windows-dependencies.ps1 @@ -12,188 +12,147 @@ # Downloads and builds Protobuf in static debug and release mode, with dynamic C runtime # Builds the Wallet Core protobuf plugins in release mode + $ErrorActionPreference = "Stop" $root = $pwd $prefix = Join-Path $pwd "build\local" $include = Join-Path $prefix "include" -$cmakeGenerator = "Visual Studio 17 2022" -$cmakePlatform = "x64" -$cmakeToolset = "v143" +# Load dependencies version +. $PSScriptRoot\windows-dependencies-version.ps1 -# GoogleTest -$gtestVersion = "1.11.0" -$gtestDir = Join-Path $prefix "src\gtest" -$gtestZip = "googletest-release-$gtestVersion.zip" -$gtestUrl = "https://github.com/google/googletest/archive/refs/tags/release-$gtestVersion.zip" - -# Download and extract -if (-not(Test-Path -Path $gtestDir -PathType Container)) { - mkdir $gtestDir | Out-Null -} -cd $gtestDir -if (-not(Test-Path -Path $gtestZip -PathType Leaf)) { - Invoke-WebRequest -Uri $gtestUrl -OutFile $gtestZip -} -if (Test-Path -Path googletest-release-$gtestVersion -PathType Container) { - Remove-Item –Path googletest-release-$gtestVersion -Recurse -} -Expand-Archive -LiteralPath $gtestZip -DestinationPath $gtestDir - -# Build debug and release libraries -cd googletest-release-$gtestVersion -mkdir build_msvc | Out-Null -cd build_msvc -cmake -G $cmakeGenerator -A $cmakePlatform -T $cmakeToolset "-DCMAKE_INSTALL_PREFIX=$prefix" "-DCMAKE_DEBUG_POSTFIX=d" "-DCMAKE_BUILD_TYPE=Release" "-Dgtest_force_shared_crt=ON" .. -if ($LASTEXITCODE -ne 0) { - exit $LASTEXITCODE -} -cmake --build . --target INSTALL --config Debug -if ($LASTEXITCODE -ne 0) { - exit $LASTEXITCODE -} -cmake --build . --target INSTALL --config Release -if ($LASTEXITCODE -ne 0) { - exit $LASTEXITCODE -} -# Check -$checkVersion = "0.15.2" -$checkDir = Join-Path $prefix "src\check" -$checkZip = "check-$checkVersion.zip" -$checkUrl = "https://codeload.github.com/libcheck/check/zip/refs/tags/$checkVersion" - -# Download and extract -if (-not(Test-Path -Path $checkDir -PathType Container)) { - mkdir $checkDir | Out-Null -} -cd $checkDir -if (-not(Test-Path -Path $checkZip -PathType Leaf)) { - Invoke-WebRequest -Uri $checkUrl -OutFile $checkZip -} -if (Test-Path -Path check-$checkVersion -PathType Container) { - Remove-Item –Path check-$checkVersion -Recurse -} -Expand-Archive -LiteralPath $checkZip -DestinationPath $checkDir - -# Build debug and release libraries -cd check-$checkVersion -mkdir build_msvc | Out-Null -cd build_msvc -cmake -G $cmakeGenerator -A $cmakePlatform -T $cmakeToolset "-DCMAKE_INSTALL_PREFIX=$prefix" "-DCMAKE_DEBUG_POSTFIX=d" "-DCMAKE_BUILD_TYPE=Release" .. -if ($LASTEXITCODE -ne 0) { - exit $LASTEXITCODE -} -cmake --build . --target INSTALL --config Debug -if ($LASTEXITCODE -ne 0) { - exit $LASTEXITCODE -} -cmake --build . --target INSTALL --config Release -if ($LASTEXITCODE -ne 0) { - exit $LASTEXITCODE -} -# Nlohmann JSON -$jsonVersion = "3.10.4" -$jsonDir = Join-Path $prefix "include\nlohmann" -$jsonUrl = "https://github.com/nlohmann/json/releases/download/v$jsonVersion/json.hpp" -$jsonFile = Join-Path $jsonDir "json.hpp" -if (Test-Path -Path $jsonFile -PathType Leaf) { - Remove-Item –Path $jsonFile -} -if (-not(Test-Path -Path $jsonDir -PathType Container)) { - mkdir $jsonDir | Out-Null -} -Invoke-WebRequest -Uri $jsonUrl -OutFile $jsonFile - -# Boost -$boostVersion = "1.77.0" -$boostVersionU = $boostVersion.Replace(".", "_") -$boostDir = Join-Path $prefix "src\boost" -$boostZip = "boost_$boostVersionU.zip" -$boostUrl = "https://nchc.dl.sourceforge.net/project/boost/boost/$boostVersion/$boostZip" - -# Download and extract -if (-not(Test-Path -Path $boostDir -PathType Container)) { - mkdir $boostDir | Out-Null -} -cd $boostDir -if (-not(Test-Path -Path $boostZip -PathType Leaf)) { - Invoke-WebRequest -Uri $boostUrl -OutFile $boostZip -} -if (Test-Path -Path boost_$boostVersionU -PathType Container) { - Remove-Item –Path boost_$boostVersionU -Recurse -} -# Expand-Archive -LiteralPath $boostZip -DestinationPath $boostDir -$boostZipPath = Join-Path $boostDir $boostZip -Add-Type -Assembly "System.IO.Compression.Filesystem" -[System.IO.Compression.ZipFile]::ExtractToDirectory($boostZipPath, $boostDir) -if (-not(Test-Path -Path $include -PathType Container)) { - mkdir $include | Out-Null -} -$boostInclude = Join-Path $include "boost" -if (Test-Path -Path $boostInclude -PathType Container) { - Remove-Item –Path $boostInclude -Recurse -} -$boostSrcInclude = Join-Path $boostDir "boost_$boostVersionU\boost" -move $boostSrcInclude $boostInclude - -# Protobuf -$protobufVersion = "3.19.1" -$protobufDir = Join-Path $prefix "src\protobuf" -$protobufZip = "protobuf-cpp-$protobufVersion.zip" -$protobufUrl = "https://github.com/protocolbuffers/protobuf/releases/download/v$protobufVersion/$protobufZip" - -# Download and extract -if (-not(Test-Path -Path $protobufDir -PathType Container)) { - mkdir $protobufDir | Out-Null -} -cd $protobufDir -if (-not(Test-Path -Path $protobufZip -PathType Leaf)) { - Invoke-WebRequest -Uri $protobufUrl -OutFile $protobufZip -} -if (Test-Path -Path protobuf-$protobufVersion -PathType Container) { - Remove-Item –Path protobuf-$protobufVersion -Recurse -} -Expand-Archive -LiteralPath $protobufZip -DestinationPath $protobufDir - -# Build debug and release libraries -cd protobuf-$protobufVersion -mkdir build_msvc | Out-Null -cd build_msvc -$protobufCMake = Get-Content ..\cmake\CMakeLists.txt # Bugfix -$protobufCMake = $protobufCMake.Replace("set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded$<$:Debug>)", - "if (MSVC AND protobuf_MSVC_STATIC_RUNTIME)`r`nset(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded$<$:Debug>)`r`nendif()") # Bugfix -$protobufCMake | Out-File -encoding UTF8 ..\cmake\CMakeLists.txt # Bugfix -cmake -G $cmakeGenerator -A $cmakePlatform -T $cmakeToolset "-DCMAKE_INSTALL_PREFIX=$prefix" "-DCMAKE_BUILD_TYPE=Release" "-Dprotobuf_WITH_ZLIB=OFF" "-Dprotobuf_MSVC_STATIC_RUNTIME=OFF" "-Dprotobuf_BUILD_TESTS=OFF" "-Dprotobuf_BUILD_SHARED_LIBS=OFF" ../cmake -if ($LASTEXITCODE -ne 0) { - exit $LASTEXITCODE -} -cmake --build . --target INSTALL --config Debug -if ($LASTEXITCODE -ne 0) { - exit $LASTEXITCODE + +function download_dependencies +{ + & $PSScriptRoot\windows-download-dependencies.ps1 } -cmake --build . --target INSTALL --config Release -if ($LASTEXITCODE -ne 0) { - exit $LASTEXITCODE + +# GoogleTest +function build_gtest +{ + # Build debug and release libraries + $gtest_dir = Join-Path $root "build\local\src\gtest\googletest-release-$gtestVersion" + cd $gtest_dir + if (Test-Path -Path build_msvc -PathType Container) + { + Remove-Item ¨CPath build_msvc -Recurse + } + mkdir build_msvc | Out-Null + cd build_msvc + cmake -G $cmakeGenerator -A $cmakePlatform -T $cmakeToolset "-DCMAKE_INSTALL_PREFIX=$prefix" "-DCMAKE_DEBUG_POSTFIX=d" "-DCMAKE_BUILD_TYPE=Release" "-Dgtest_force_shared_crt=ON" .. + if ($LASTEXITCODE -ne 0) + { + exit $LASTEXITCODE + } + cmake --build . --target INSTALL --config Debug + if ($LASTEXITCODE -ne 0) + { + exit $LASTEXITCODE + } + cmake --build . --target INSTALL --config Release + if ($LASTEXITCODE -ne 0) + { + exit $LASTEXITCODE + } +} + +#check +function build_libcheck +{ + # Build debug and release libraries + $check_dir = Join-Path $root "build\local\src\check\check-$checkVersion" + cd $check_dir + if (-not(Test-Path -Path $check_dir\build_msvc -PathType Container)) + { + mkdir build_msvc | Out-Null + } + cd build_msvc + cmake -G $cmakeGenerator -A $cmakePlatform -T $cmakeToolset "-DCMAKE_INSTALL_PREFIX=$prefix" "-DCMAKE_DEBUG_POSTFIX=d" "-DCMAKE_BUILD_TYPE=Release" .. + if ($LASTEXITCODE -ne 0) + { + exit $LASTEXITCODE + } + cmake --build . --target INSTALL --config Debug + if ($LASTEXITCODE -ne 0) + { + exit $LASTEXITCODE + } + cmake --build . --target INSTALL --config Release + if ($LASTEXITCODE -ne 0) + { + exit $LASTEXITCODE + } +} + +#protobuf +function build_protobuf +{ + # Build debug and release libraries + $protobuf_dir = Join-Path $root "build\local\src\protobuf\protobuf-$protobufVersion" + cd $protobuf_dir + if (Test-Path -Path build_msvc -PathType Container) + { + Remove-Item ¨CPath build_msvc -Recurse + } + mkdir build_msvc | Out-Null + cd build_msvc + $protobufCMake = Get-Content ..\cmake\CMakeLists.txt # Bugfix + $protobufCMake = $protobufCMake.Replace("set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded$<$:Debug>)", + "if (MSVC AND protobuf_MSVC_STATIC_RUNTIME)`r`nset(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded$<$:Debug>)`r`nendif()") # Bugfix + $protobufCMake | Out-File -encoding UTF8 ..\cmake\CMakeLists.txt # Bugfix + cmake -G $cmakeGenerator -A $cmakePlatform -T $cmakeToolset "-DCMAKE_INSTALL_PREFIX=$prefix" "-DCMAKE_BUILD_TYPE=Release" "-Dprotobuf_WITH_ZLIB=OFF" "-Dprotobuf_MSVC_STATIC_RUNTIME=OFF" "-Dprotobuf_BUILD_TESTS=OFF" "-Dprotobuf_BUILD_SHARED_LIBS=OFF" ../cmake + if ($LASTEXITCODE -ne 0) + { + exit $LASTEXITCODE + } + cmake --build . --target INSTALL --config Debug + if ($LASTEXITCODE -ne 0) + { + exit $LASTEXITCODE + } + cmake --build . --target INSTALL --config Release + if ($LASTEXITCODE -ne 0) + { + exit $LASTEXITCODE + } + + build_swift_plugin } # Protobuf plugins -$pluginSrc = Join-Path $root "protobuf-plugin" -cd $pluginSrc -if (Test-Path -Path build -PathType Container) { - Remove-Item –Path build -Recurse -} -mkdir build | Out-Null -cd build -cmake -G $cmakeGenerator -A $cmakePlatform -T $cmakeToolset "-DCMAKE_INSTALL_PREFIX=$prefix" "-DCMAKE_BUILD_TYPE=Release" .. -if ($LASTEXITCODE -ne 0) { - exit $LASTEXITCODE -} -cmake --build . --target INSTALL --config Release -if ($LASTEXITCODE -ne 0) { - exit $LASTEXITCODE -} +function build_swift_plugin +{ + $pluginSrc = Join-Path $root "protobuf-plugin" + cd $pluginSrc + if (Test-Path -Path build -PathType Container) + { + Remove-Item ¨CPath build -Recurse + } + mkdir build | Out-Null + cd build + cmake -G $cmakeGenerator -A $cmakePlatform -T $cmakeToolset "-DCMAKE_INSTALL_PREFIX=$prefix" "-DCMAKE_BUILD_TYPE=Release" .. + if ($LASTEXITCODE -ne 0) + { + exit $LASTEXITCODE + } + cmake --build . --target INSTALL --config Release + if ($LASTEXITCODE -ne 0) + { + exit $LASTEXITCODE + } +} + + + +download_dependencies + +build_gtest +build_libcheck +build_protobuf +echo "`n`n" +echo "--------------------done...." cd $root diff --git a/tools/windows-download-dependencies.ps1 b/tools/windows-download-dependencies.ps1 new file mode 100644 index 00000000000..90010186637 --- /dev/null +++ b/tools/windows-download-dependencies.ps1 @@ -0,0 +1,162 @@ + +# Downloads and builds dependencies for Windows + +#This script downloads third-party dependencies .By tools\Windows-dependencies.Ps1 calls. + + + +$ErrorActionPreference = "Stop" + + +$root = $pwd +$prefix = Join-Path $pwd "build\local" +$include = Join-Path $prefix "include" + +# Load dependencies version +. $PSScriptRoot\windows-dependencies-version.ps1 + + + +# GoogleTest +function download_gtest +{ + Write-Host "Downloading gtest..." + $gtestDir = Join-Path $prefix "src\gtest" + $gtestZip = "googletest-release-$gtestVersion.zip" + $gtestUrl = "https://github.com/google/googletest/archive/refs/tags/release-$gtestVersion.zip" + # Download and extract + if (-not(Test-Path -Path $gtestDir -PathType Container)) + { + mkdir $gtestDir | Out-Null + } + cd $gtestDir + if (-not(Test-Path -Path $gtestZip -PathType Leaf)) + { + Invoke-WebRequest -Uri $gtestUrl -OutFile $gtestZip + } + if (Test-Path -Path googletest-release-$gtestVersion -PathType Container) + { + Remove-Item ¨CPath googletest-release-$gtestVersion -Recurse + } + Expand-Archive -LiteralPath $gtestZip -DestinationPath $gtestDir +} +# Check +function download_libcheck +{ + Write-Host "Downloading libcheck..." + $checkDir = Join-Path $prefix "src\check" + $checkZip = "check-$checkVersion.zip" + $checkUrl = "https://codeload.github.com/libcheck/check/zip/refs/tags/$checkVersion" + + # Download and extract + if (-not(Test-Path -Path $checkDir -PathType Container)) + { + mkdir $checkDir | Out-Null + } + cd $checkDir + if (-not(Test-Path -Path $checkZip -PathType Leaf)) + { + Invoke-WebRequest -Uri $checkUrl -OutFile $checkZip + } + if (Test-Path -Path check-$checkVersion -PathType Container) + { + Remove-Item ¨CPath check-$checkVersion -Recurse + } + Expand-Archive -LiteralPath $checkZip -DestinationPath $checkDir + +} + +# Nlohmann JSON +function download_nolhmann_json +{ + + Write-Host "Downloading Nlohmann JSON..." + $jsonDir = Join-Path $prefix "include\nlohmann" + $jsonUrl = "https://github.com/nlohmann/json/releases/download/v$jsonVersion/json.hpp" + $jsonFile = Join-Path $jsonDir "json.hpp" + if (Test-Path -Path $jsonFile -PathType Leaf) { + Remove-Item ¨CPath $jsonFile + } + if (-not(Test-Path -Path $jsonDir -PathType Container)) { + mkdir $jsonDir | Out-Null + } + Invoke-WebRequest -Uri $jsonUrl -OutFile $jsonFile +} + +# Boost +function download_and_move_include_boost +{ + Write-Host "Downloading boost..." + + $boostVersionU = $boostVersion.Replace(".", "_") + $boostDir = Join-Path $prefix "src\boost" + $boostZip = "boost_$boostVersionU.zip" + $boostUrl = "https://nchc.dl.sourceforge.net/project/boost/boost/$boostVersion/$boostZip" + + # Download and extract + if (-not(Test-Path -Path $boostDir -PathType Container)) + { + mkdir $boostDir | Out-Null + } + cd $boostDir + if (-not(Test-Path -Path $boostZip -PathType Leaf)) + { + Invoke-WebRequest -Uri $boostUrl -OutFile $boostZip + } + if (Test-Path -Path boost_$boostVersionU -PathType Container) + { + Remove-Item ¨CPath boost_$boostVersionU -Recurse + } + + # Expand-Archive -LiteralPath $boostZip -DestinationPath $boostDir + $boostZipPath = Join-Path $boostDir $boostZip + Add-Type -Assembly "System.IO.Compression.Filesystem" + [System.IO.Compression.ZipFile]::ExtractToDirectory($boostZipPath, $boostDir) + + #move include + if (-not(Test-Path -Path $include -PathType Container)) + { + mkdir $include | Out-Null + } + $boostInclude = Join-Path $include "boost" + if (Test-Path -Path $boostInclude -PathType Container) + { + Remove-Item ¨CPath $boostInclude -Recurse + } + $boostSrcInclude = Join-Path $boostDir "boost_$boostVersionU\boost" + move $boostSrcInclude $boostInclude + +} + +function download_protobuf +{ + Write-Host "Downloading Protobuf..." + + # Protobuf + $protobufVersion = "3.19.2" + $protobufDir = Join-Path $prefix "src\protobuf" + $protobufZip = "protobuf-cpp-$protobufVersion.zip" + $protobufUrl = "https://github.com/protocolbuffers/protobuf/releases/download/v$protobufVersion/$protobufZip" + + # Download and extract + if (-not(Test-Path -Path $protobufDir -PathType Container)) { + mkdir $protobufDir | Out-Null + } + cd $protobufDir + if (-not(Test-Path -Path $protobufZip -PathType Leaf)) { + Invoke-WebRequest -Uri $protobufUrl -OutFile $protobufZip + } + if (Test-Path -Path protobuf-$protobufVersion -PathType Container) { + Remove-Item ¨CPath protobuf-$protobufVersion -Recurse + } + Expand-Archive -LiteralPath $protobufZip -DestinationPath $protobufDir +} + + +download_gtest +download_libcheck +download_nolhmann_json +download_protobuf +download_and_move_include_boost + +cd $root diff --git a/tools/windows-doxygen_convert_comments.ps1 b/tools/windows-doxygen_convert_comments.ps1 new file mode 100644 index 00000000000..0e7242ebe7f --- /dev/null +++ b/tools/windows-doxygen_convert_comments.ps1 @@ -0,0 +1,30 @@ +#perlÐèÒªÏÂÔØ + + +$SWIFT_PARAMETER_PATTERN = 's/\\param\s+([^\s]+)/\- Parameter $1:/g' +$SWIFT_RETURN_PATTERN = 's/\\return/\- Returns:/g' +$SWIFT_NOTE_PATTERN = 's/\\note/\- Note:/g' +$SWIFT_SEE_PATTERN = 's/\\see/\- SeeAlso:/g' +$SWIFT_FOLDER_PATH = "swift\Sources\Generated\*.swift" +$SWIFT_FOLDER_PATH_BAK = "swift\Sources\Generated\*.bak" + +function process_swift_comments($path) +{ + perl '-pi.bak' -e $SWIFT_PARAMETER_PATTERN $path + perl '-pi.bak' -e $SWIFT_RETURN_PATTERN $path + perl '-pi.bak' -e $SWIFT_NOTE_PATTERN $path + perl '-pi.bak' -e $SWIFT_SEE_PATTERN $path +} + +function swift_convert +{ + echo "Processing swift convertion..." + + foreach($path in Get-ChildItem $SWIFT_FOLDER_PATH) + { + process_swift_comments($path) + } + Remove-Item $SWIFT_FOLDER_PATH_BAK + +} +swift_convert \ No newline at end of file diff --git a/tools/windows-generate.ps1 b/tools/windows-generate.ps1 index 55c9e3085bd..a9a3b9c6f66 100644 --- a/tools/windows-generate.ps1 +++ b/tools/windows-generate.ps1 @@ -9,6 +9,10 @@ $ErrorActionPreference = "Stop" +#Set the position of the toolchain = $toolsPath +$toolsPath = Join-Path $pwd "tools" + + $root = $pwd $prefix = Join-Path $pwd "build\local" $bin = Join-Path $prefix "bin" @@ -22,13 +26,13 @@ protoc.exe --version # Clean if (Test-Path -Path "swift\Sources\Generated" -PathType Container) { - Remove-Item –Path "swift\Sources\Generated" -Recurse + Remove-Item ¨CPath "swift\Sources\Generated" -Recurse } if (Test-Path -Path "jni\java\wallet\core\jni" -PathType Container) { - Remove-Item –Path "jni\java\wallet\core\jni" -Recurse + Remove-Item ¨CPath "jni\java\wallet\core\jni" -Recurse } if (Test-Path -Path "jni\cpp\generated" -PathType Container) { - Remove-Item –Path "jni\cpp\generated" -Recurse + Remove-Item ¨CPath "jni\cpp\generated" -Recurse } mkdir swift\Sources\Generated\Protobuf | Out-Null @@ -46,8 +50,11 @@ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +# Convert doxygen comments to appropriate format +& $toolsPath\windows-doxygen_convert_comments.ps1 + # Generate Java, C++ and Swift Protobuf files -protoc.exe "-I=$include" -I=src/proto --cpp_out=src/proto --java_out=jni/java src/proto/*.proto +protoc.exe "-I=$include" -I=src/proto --cpp_out=src/proto --java_out=lite:jni/java src/proto/*.proto if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } @@ -62,6 +69,15 @@ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +protoc.exe "-I=$include" -I=src/Cosmos/Protobuf --cpp_out=src/Cosmos/Protobuf src/Cosmos/Protobuf/*.proto +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} +protoc.exe "-I=$include" -I=tests/chains/Cosmos/Protobuf --cpp_out=tests/chains/Cosmos/Protobuf tests/chains/Cosmos/Protobuf/*.proto +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} + # Generate Proto interface file protoc.exe "-I=$include" -I=src/proto "--plugin=$bin\protoc-gen-c-typedef.exe" --c-typedef_out include/TrustWalletCore src/proto/*.proto if ($LASTEXITCODE -ne 0) { @@ -71,3 +87,5 @@ protoc.exe "-I=$include" -I=src/proto "--plugin=$bin\protoc-gen-swift-typealias. if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + +echo "done..." \ No newline at end of file diff --git a/tools/windows-replace-file.pl b/tools/windows-replace-file.pl new file mode 100644 index 00000000000..c357c93c27e --- /dev/null +++ b/tools/windows-replace-file.pl @@ -0,0 +1,241 @@ +#!/usr/bin/perl + +if ($#ARGV+1 != 1 ) { + + print "Enter the command: -e or -r,\n"; + print " -e : extract command is used to extract the modified source code to a tools file.\n"; + print " -r : replace command is used to replace the source code file.\n"; + exit; +} + +$op=$ARGV[0]; + +@trezor_crypto_file = ( + "trezor-crypto/crypto/rand.c", + "trezor-crypto/crypto/base32.c", + "trezor-crypto/crypto/base58.c", + "trezor-crypto/crypto/bignum.c", + "trezor-crypto/crypto/blake2b.c", + "trezor-crypto/crypto/blake2s.c", + "trezor-crypto/crypto/cardano.c", + "trezor-crypto/crypto/shamir.c", + "trezor-crypto/crypto/sha3.c", + "trezor-crypto/crypto/tests/test_check.c", + "trezor-crypto/crypto/tests/CMakeLists.txt", + "trezor-crypto/crypto/monero/base58.c", + "trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-donna-portable.h", + "trezor-crypto/include/TrezorCrypto/groestl_internal.h", + "trezor-crypto/include/TrezorCrypto/nist256p1.h", + "trezor-crypto/include/TrezorCrypto/rand.h", + "trezor-crypto/include/TrezorCrypto/aes.h", + "trezor-crypto/include/TrezorCrypto/endian.h", + "trezor-crypto/CMakeLists.txt" +); + +@src_file = ( + "src/Base32.h", + "src/BinaryCoding.h", + "src/HDWallet.cpp", + "src/Aptos/MoveTypes.cpp", + "src/Everscale/Cell.h", + "src/Everscale/Cell.cpp", + "src/Everscale/CellSlice.h", + "src/NULS/Address.cpp", + "src/Ethereum/ABI/ParamFactory.cpp", + "src/interface/TWBitcoinScript.cpp", + "src/Keystore/EncryptionParameters.cpp" +); + +@tests_file = ( + "tests/chains/Bitcoin/MessageSignerTests.cpp", + "tests/chains/Cardano/SigningTests.cpp", + "tests/chains/Cardano/TWCardanoAddressTests.cpp", + "tests/chains/Polkadot/SignerTests.cpp", + "tests/chains/Zilliqa/SignerTests.cpp", + "tests/chains/Aptos/MoveTypesTests.cpp", + "tests/common/BCSTests.cpp", + "tests/CMakeLists.txt", + "tests/main.cpp" +); + + +@include_file = ( + "include/TrustWalletCore/TWBase.h", + "include/TrustWalletCore/TWString.h", + "include/TrustWalletCore/TWAnySigner.h" +); + +@walletconsole_file = ( + "walletconsole/CMakeLists.txt", + "walletconsole/lib/CMakeLists.txt" +); + +@cmake_file = ( + "cmake/Protobuf.cmake", + "cmake/CompilerWarnings.cmake" + +); + +@wallet_cmakeLists_file = ( + "CMakeLists.txt" +); + +@protobuf_plugin_file = ( + "protobuf-plugin/CMakeLists.txt" +); + +@windows_tools_file =( + "tools/windows-build.ps1", + "tools/windows-build-and-test.ps1", + "tools/windows-dependencies.ps1", + "tools/windows-dependencies-version.ps1", + "tools/windows-download-dependencies.ps1", + "tools/windows-doxygen_convert_comments.ps1", + "tools/windows-generate.ps1", + "tools/windows-replace-file.p1", + "tools/windows-samples.ps1", + "tools/windows-tests.ps1", +); +#ÍêÕû·¾¶ +sub addFullPath +{ + @pathAndCMD = @_; + + $prefixPathName = $_[0]; + + for($a = 1;$a < $#pathAndCMD + 1;$a = $a + 1) + { + $pathAndCMD[$a] = join( "", $pathAndCMD[0] ,$pathAndCMD[$a] ); + } + return @pathAndCMD; + +} + +#¿½±´Îļþ +sub copyFile +{ + #my($walletPath,$toolsPath) = @_; + my($fromPath,$toPath) = @_; + + use File::Copy; + for($a = 1;$a < @$fromPath;$a = $a + 1) + { +=pod + print "\n"; + print "fromPath = ",$fromPath->[$a],"\n"; + print "toPath = ",$toPath->[$a],"\n"; + print "\n"; +=cut + copy $fromPath->[$a] , $toPath->[$a] or warn 'copy failed.'; + } + +} + +#path--> /wallet-core-win +use Cwd; +$TWdir = getcwd; + +$TWdir = join( "", $TWdir ,"/" ); + +#path--> tools/windows-replace/ +$toolsDir = join( "", $TWdir,"tools/windows-replace/" ); + +#trezor_cryptoÎļþ +@wallet_trezor_crypto_path = addFullPath($TWdir,@trezor_crypto_file); +@tools_trezor_crypto_path = addFullPath($toolsDir,@trezor_crypto_file); + +#src +@wallet_src_path = addFullPath($TWdir,@src_file); +@tools_src_path = addFullPath($toolsDir,@src_file); + +#tests +@wallet_tests_path = addFullPath($TWdir,@tests_file); +@tools_tests_path = addFullPath($toolsDir,@tests_file); + +#include +@wallet_include_path = addFullPath($TWdir,@include_file); +@tools_include_path = addFullPath($toolsDir,@include_file); + +#walletconsole +@wallet_walletconsole_path = addFullPath($TWdir,@walletconsole_file); +@tools_walletconsole_path = addFullPath($toolsDir,@walletconsole_file); + +#protobuf_plugin +@wallet_protobuf_plugin_path = addFullPath($TWdir,@protobuf_plugin_file); +@tools_protobuf_plugin_path = addFullPath($toolsDir,@protobuf_plugin_file); + +#cmake +@wallet_cmake_path = addFullPath($TWdir,@cmake_file); +@tools_cmake_path = addFullPath($toolsDir,@cmake_file); + +# wallet_cmakeLists +@wallet_cmakeLists_path = addFullPath($TWdir,@wallet_cmakeLists_file); +@tools_wallet_cmakeLists_path = addFullPath($toolsDir,@wallet_cmakeLists_file); + + + +if ($op eq "-e") +{ + + #copy trezor_cryptoÎļþ + copyFile(\@wallet_trezor_crypto_path ,\@tools_trezor_crypto_path); + + #copy src + copyFile(\@wallet_src_path ,\@tools_src_path); + + #copy test + copyFile(\@wallet_tests_path ,\@tools_tests_path); + + #copy include + copyFile(\@wallet_include_path ,\@tools_include_path); + + #copy walletconsole + copyFile(\@wallet_walletconsole_path ,\@tools_walletconsole_path); + + #copy protobuf_plugin_ + copyFile(\@wallet_protobuf_plugin_path ,\@tools_protobuf_plugin_path); + + #copy cmake + copyFile(\@wallet_cmake_path ,\@tools_cmake_path); + + #copy cmakeLists + copyFile(\@wallet_cmakeLists_path ,\@tools_wallet_cmakeLists_path); +} +elsif($op eq "-r") +{ + #toolsÌæ»»Ô´Âë + #copy trezor_cryptoÎļþ + copyFile(\@tools_trezor_crypto_path,\@wallet_trezor_crypto_path ); + + #copy src + copyFile(\@tools_src_path,\@wallet_src_path ); + + #copy test + copyFile(\@tools_tests_path,\@wallet_tests_path ); + + #copy include + copyFile(\@tools_include_path,\@wallet_include_path); + + #copy walletconsole + copyFile(\@tools_walletconsole_path,\@wallet_walletconsole_path ); + + #copy protobuf_plugin_ + copyFile(\@tools_protobuf_plugin_path,\@wallet_protobuf_plugin_path); + + #copy cmake + copyFile(\@tools_cmake_path,\@wallet_cmake_path ); + + #copy cmakeLists + copyFile(\@tools_wallet_cmakeLists_path,\@wallet_cmakeLists_path); +} +else{ + + print "Enter the command: -e or -r,\n"; + print " -e : extract command is used to extract the modified source code to a tools file.\n"; + print " -r : replace command is used to replace the source code file.\n"; + exit; +} + + + + diff --git a/tools/windows-replace/.vs/CMake Overview b/tools/windows-replace/.vs/CMake Overview new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tools/windows-replace/.vs/ProjectSettings.json b/tools/windows-replace/.vs/ProjectSettings.json new file mode 100644 index 00000000000..940645c5d23 --- /dev/null +++ b/tools/windows-replace/.vs/ProjectSettings.json @@ -0,0 +1,3 @@ +{ + "CurrentProjectSetting": "x64-Debug" +} \ No newline at end of file diff --git a/tools/windows-replace/.vs/slnx.sqlite b/tools/windows-replace/.vs/slnx.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..7185ef315c6350688c1b12c1d8d6aa7d4fb15ac2 GIT binary patch literal 90112 zcmeHw4Rl+_b>;&B;vesUq9{nBC_$7c5|SQ?e-M&o2?QaD68}Mp`XNm}01px@5TF6j zk`ianN=_U*UdQ$6cAKqxnxuQ0pXQ`{(xxZbZQ5tK@16I9_W;n8WyLvl21mLC?!7Z}=bJn8=G}R3COtD=sEOWuxw0bEydlQT7!1rY zua{w%cKE*q{wtqO_+nCiz+Z#r`+8s7nZd9ZGcs_mqRuAnRp-Z?ZpUf+`)!X{ziKg> zXN-@xz0x+@c51yWDF5FK0#>y2`d-%JS>O%VX9}g9cr|@(B~vbriuppRP%D&6%hK1< z#p8vdIF?%;Yo){`XH$u}ly_`$Ge| ziPmOL3M*o37v96O-$wWHkd(RQ4#l!Q4_wh9$QxqGk-uT-k z8#9*}8BevSxywEv;nD=+SZ)dMolBia&3dP2$0ic9=e(y==e&vexv8;9$aW$%IR{q3 z|M~IpgK{p@mGXUsoLJGOR&*p4Y=qg=@ziW;GMP$yFDNNerCOnOZD~MdKo-7w5@@&i z=}`m%U`QrLQ##RTYCKgJDaJ?uPM%53CQl}22ZE7^Pc^s!S0%~uwPNww%$iUv( zZ$0&`U^!mS3fMnq%U9N~fTENv7uQxw&3Z1o@A{C#;#r1%wM_aIu{0}`N@67`WG{)! z&8ADetWnXa?`4hRb@S2mxyCf$d(EXU`5u_JlOFfr66tY`sTzMf^gXUo4CO05u2H1z zGdI!WS6-G&Aq*97d zdfr@NMXc6@mDMFL4jyx36EKp(c!i$G+bo_TpWy~aJW(kY^IJxuFeJn5ao%%5A-*A*w%2lg+Gl{Xu4p}Uo6t6T{qa!a*3l*_cTgG9# zQD$w~evMk%95hYYsD=}UR#)ePa5*C;G$ue_zCL2McrL@pcdb71X?metbaP70_@_~~ z$n}w@K#j>Z$ZdnVy-p>%+O^Z7reYD5)&`q5Fu0y_1LIl&?|a~+NgAY+^+8&LR->Km z`VP`4)^w3Z(XDkA$>zG3-^5I0-8QI?bz~gF`4oO`VM_4kM)BzelV$$#2(d0ddW8kdtfZ<(i^5sGWjjt$;95BF*vb{9~S zE6#A==KcrwQTReX6ak6=MSvne5ugZA1SkR&0g3=cfFhtr;N6!^?abka58$2fSbYsE zEycR4e)+@W-v*+}f|+nQp36u1XeN;31A$zekB7x9s^{XN$Z%W$Sx6MK{%A1E2g1=1 zAC3+Cc_Ea|@nSR*5dHB;EGhy!6AEUd!!bTM9LVzFY&6FUK_S40a>KE3CNBhY;T#{! zgfh8EF2oOqqCq|!jOBPJBFx9a!F*5*1|!jIkRSF(La_)Kk&6bxd^nJgfKoifM~1_p zyci9M!f=p}XY#}GOgzhr@f;NAkAIv(eyiG?vZs(U>sIhvV5O zFT`_xK9~{np=dleoXLYyCLbP-gk!uvBSyjS99RHV;j@8YE+2~IL@^%cbMasxn2qH4 zcq}47PM|5oA|XDQj|@jM`Aps)i}AU9HWrBo;{0$dV#h}0ofe_Rq7z|_ru{a! zDOp}C)dKx(CWa3sE22;nb5oW1)f|2a_=f_qp`bq)^aetQ{EGz~z1!hH% zqq2d)&|n1b|IOTc8SaPN3*3jePjm0({zC8Q^cO{dB0v$K2v7tl0u%v?07ZZzKoOt_ zPy{Ffw+R7HyD?;_KZ-Id=0$jJA}iW98I3-J@;H_BC|KKOlaZH{H4j!E>M$F9wt5z$ zLQUA*rYLG2mATttFm@Z{XAn$wsDO$47{fi!eS!NJ_wsG356y-mKoOt_Py{Ff6ak6= zMSvne5ugZA1SkR&fm=YJ+j!Wp&Z8!#F5{4)>Cph=X1Mond@!KRZS1${9sN57-f{sa4S_Wx=Bi2a8Boc)M>pWR~n zzU^7t=WQRiJ#M>dyW4iew#E7*>zA#cus&kFY@M_YS+`ml%RgBD%JTb`CoI=37cJA4 zxTV)(Gyl;1y!j8zzhS;^zHFW_hs_=B|J?p!`|r2^r}i7|+4iID2cZJ=LlK||Py}uZ z0-NtPGaiH0oLMUrbIaD}|Lac@eVOi#Wi!*sSk2|Nn!@V6%gjV#IjUM6 zyr)_&q0X_FcWv)kGBZAd&77PNE{o%ZYOOk0yINaB@g1LczO-o(a)q*rg^WYt^;~F` zL>2d+H#0{dM==Lm9>5cCxpJu_@T=ttc^hr;(!7#S*Eut@4HPRvDK~g2t+2PBH8XpF zT>@j^slU~LMz(vw%mhGou~LQ?tBdd!e@(2EgrY{pbH>c{VDY6~K`3biJLb(yKU76$ zms;xPIRw>a7R0J1O?TSN^g)`riujIlC0V()S}Tt#hIY@Q&}OcIDa-KeTK4i_c1jWU z%(MbY6?>*7D>Fh>j0_KEPb=v*Pf3l7nO2hu*E1=pS1$=Gh00)76?9EV7=j?AWshs1 z)o7naw$;2+h8+*eY9?DxnVDTsk*rY338k_opRHq(BvSDQeT-U z-zVZr@^w=pN={qi629jP#bV)JA)xR*!`L8J^K`j*S;&=Zs$fe@N+8#<&W}p`kwPt7 zE|gUM=7{9Y3FtRfmFo#h?Y$xtO4tZCrQEKN+^*#na8Z@r6KvJ4s<4S@yqD+TorNn=#1L|h0qJb)LT;r&?{O^93UQxtm#%}j_it6a5owo+KFVP<`b z&URiZC@B_;>0%)(4qn=$$hr=qZ7{roOvCmZkPskj$V=7VwO?`$V$N|PTPs(tVPQTc z!%aSEKu(N|^(wp_130pQLntkcDRwE+&VDn~1&s+CPo%91)3Z-%;nN~4zLhJYD%jeG z{TjO*V38NHbxHSLsr#H9T@Z>zkzi0ywns{qx(^V{3dMRdPp?!`s&)xh4cAuGBt5&O zRvjsngvzyKIR~9#@RBO_cqNENV1$H0p)T0GOXE|jde6>QK4Mnt>+LZ!Ljchgc&)p9 zrOMaH0I*sOv@06DJEWFYG^(&|M-lp+R%c(gnK`V(*FbSOTP^{Ja7BSm0Q*VkE%0wOGgG=mS4zdQkmGaW zDqQhkRTjqztv5t*bb+#mr1-v)1IuW3xgp(dF#z(ql^IBRUP=W;2t}WJxr5 zxZD%`b*g(i%}h+IS}xbBwTiH+)7rMl%93cpsZNsgU_{$NtF?O`Rqf;m*JoLS2cW>?@+*rtg4T+&#a zEU&DB6I2$2N(tI0SR+#rUb^%t!cH6X2}nuYT48orr5Rzhl2uq93v^Sk10KoGt%!pwIYrQ81{ffN zRk@n{YH{sify@$d8o0F`a7S_Zidacsg27Bl*kyu|A0+Z6T4C=n0&Gx_ep;zWJKH34 z2}laF!yuVEcwZG+$G-P3R$TwT+x9TSz2f}8&We57_3xZP$5z+94u}0Q$CUl2uDhK3 z?BBQlo$X=gcKb8Vzj1!i`5EU$?q%+4u7{j+jyvr4+n%z0+BwFB90B{Cw!d@Uum@Z- zuB3gtE6Dw|qtCV1<*|Lyd57aE`;*Ss9RFyyyI9*VJHPI9ai6z6!F`hR+W*M*uiOXi zzi0o5qvp8V@e%G}%VU*rnx7ueroA7k%fC)j@1 zPq|6g-@Bf4eaiKO^JexUye)SIG!u#dMSvne5ugZA1SkTx4FQ|cV<40FyDi2}1B4it zw;Ch(H)#C zTZ{oia}aRBY3wm5fxj~y#(sU?^H>cM;G1(B`wZHkUK$xVdsYH@v(3iNVD3u5ZW{9< z)9oo_D}lL5v{?$qO{nH)$MLHabUW=b?lP!>wo~{^4X%yVe?dU)Bq>e~qMgu|eO&p0 z!LpPC9+;J9-=kQLbZ3xkwtoccs11H4I*kVm4dJ6>EqF(Jj7JUY2Da|RwLHuPZf54l zZ8bz6j$k(UOB2AlW4keKXdSdVjP0q0FVtKPVcq25R2(a<1f+&hAP1ph_+5_9M6p#! zuqlG-N`NU$a3MjZP%|z;A|=720Q#9GVB|;tTrWs8_4#ooBb;LW%fGzPIe8R z8u)v-&;Ka*VXlYsa1O4W{a5xC_J{0CTnGCUJYn!B>>sk9Vc*Msiv0xK-G7k%&+IR= zkFsU90M8vPu?y@Bdm5fUILgM^5Zlf2Y(Kl3?P59B#u{BeasAl!Pp+>+1?Yz&KoOt_ zPy{Ff6ak6=MSvne5ugbCVj<9OG#HleHW?YivdL&O+=agvmykLZ3B7=H{yft3IYQ48 zx`p?DAdTKZ=wYOhLr4R0LWc>B5gH{lg47l!G(>2S&;ZhYKhk|e zNc#qn?%@eNNaz8izWqpheMol=Ahq@rx{uI4LiZB7htOU^cN6L*bQexvy+Qt&JIb;SON?NxYw|9;nihsk4*meZH-wwgK_vY@`r6tT5lQQvIx!JJnA ze$JB3)M>y)b;n{0vE#x{(-DJy@msU7K7Zb6+GdcK*3;)YOnVI4#r5>rt)_sX#qxT3 zfi?9&w#u6|XF5&&4W*>#N#&%sXy!JV`V6{d_R%z9;ew&E&^|TWq5@Mhq$Fjr{q!`+ zm8>%+r-(>da8FK>y2$UpOw=ouO^%Zv^1CjlInypfec?TMiu_iW-jidR-}pAmNyz}^ z9hMV1dy>c1pX7a&l*`m@P!{3sqoiE<-IQdD+Sx}43fiUkP@>Cp0PcDk-aa|jiaUIC zm+2_ry6&4McWyE9QeN66`PdPX87{7C7UiRN>@dX*>nzJh4--#Nm!UB&c|Rg@h}2kl zZzN9OP!{I{!$hdOBN8LOls7}7q$Q>IK_VoXvPf?WOSnnzeuP@!W(!Ih(t92O;xDt`C#0MjpNIA*dzJ0%GzAk#brTzA8{iZ3c>N<=2?ORFHD^@lx@3(vQnI<&ZtoLTa799ra z5`TM_*OaIyZ(ihY-`s7AX%w`}{Oz4?({6M-Wuc$jL|R5!>gPI$x9Ar8Ik(hR^~?Px zjEglIU^E)Q_5U{RafW-Gd-5%*AT5L<$ze*cf_|A!pUGu-#MufhBNpXENn{YUNwx60kc zjdO>%0d5OtVt>H?E&EsO=iur453moiH8#Uevvqo9{y8Z^v1^6A;ue;vo zy6Vci=3FB#zpKY(cmCMZ$l57s9sBy%zTE*w zMMx~7MObh`1hQCJ>$&gbJK%H(i6v-pxZ!*U1a~+T~D{+$FpLD~44#=67II*w9+;D~iawa8C9NvJO35gT_lc(Hpa07D2 zB~IwM``vI>19DEuoJTIW;cN!vj7gl}pKo=;Nesw2DRBZXeA*3XE+FTG#PRV-UIs$SM5{F;;oEwfoK+Z9VbMV}_8_qdE&QXbT;F}&d zoNIubJ0;Hkk3Q;#;|q{;MCP2k#|=jnAmK+cfNX*=wO;{}j2C~@}Q`5iYLC4d}W;_UhU z=iG390CEn>oU2c`;m`o&9FRFPB{!THfSmmjr}v`S4F?7wXF%p0e##9e1t4dy#MwQQ zbHm{P$k{D%yqRBf!=V7k@zyzT9sqJSNt|6*2i$N50CGAc&W@vlZa4q{Ic|y5{hKf1 z`u`#Jw;0@ePuZJ~dzJfV?q#w=-*;d)z!$h@xu>}=aepfB1NceY-;eC)r`r(_cl>L$ zDpFHH9Q^OHUQhFr)WbRy*)NDtbP92xM#TEJUgqC^jHxP|6V#2Bn5WiYnyI3eSmW%iQc8ihWqU<->s`Fz`x8t<^{kBJ}U$q#` zGsef;UTK?cJN2_^Zkp@c69LnwJPW+x`b?pe6R)PPtz^o@Q88aA6>5cYX<7POx_BHq zQr@x2(bQS*g`3J_$z}J#XKJ!pf5|&=L0{~W?|PTZ;z@#;Ct901 zDXfUCjbk@3uAXuOlTdNVo17XS$GT2VB@^kC?>$GH77x!G-pAL3Oi^sCdgE`GY|LC@ zWIWZP<}Ukygi8~MW4R^3cP@1zHS3+89h*qZp7Wkgo%1H<=cdLcA=`=6a_AeUEDtL-|UNYZPhw%uVz- zxj|dm+~hgB9!JXE*kRtBzbM^WTPelgb=D|~s`Dz0rvBf$lh0jSg;7!&sgxp=o;O!m zf$5a6vbyBO!DDW00!C68uh0{Do5eHaGu+^aCo08ae#=M{hGdvsZr1t9v6*@G%A@G0 zi8St|$mEK_HAdk`CN~Tus`QLlsX|9<8u@Q9R*T2yGdysZ_@R=Sl)e6!P49zxp~eFh zfwqxu!t*50Pz{!gs$DE@#Aq#suig*GJ42&t(|-uGL3AO)r#-ZceEg|1|0rxjym~ zs4>|FxouFl*QrETyLMXCR4k&>+F(=U|cKUeGi;8NrQBLaf_Tl9{mjwj~F=e*gq3hA*mWtfIeUN%}hfo{X~U8I}Hcle}F%kypNp5LVR zr)E9kMr67#C0MW1`lj2=o&yIA=ZNQ2?}a&inibI8XxTKL{y( z*H0KMp6+hL4Uyz2+a~{M)8%nN=4xC{=DcN|c19?!K|3~hr*x4d_y5fM8E(?~e#eg- zQ`Ugxe)AVizr57HXHs6|f*y8-L3C7`R~l|Hl5^hX@xYr%dq;$-ZVo<{JLp}n1EYRY z@_R`|x`Ja8942()+9mJt*{O-9QtLez<|9*+^`DZJ_1|du^k56~^eV^GgKym2rYwm0 z_uudBU+nk7pXouhnotjIt!tO+X{Gu#G$m?J*LB7wC*dyi6x??iD4A7X47F7p|K}%gZ!ohXZXr4+Z8W-@C7FA`{q$ z4>Vk(F;P7M{_4_?k$RzKsMHH?ZN#kSp_*F1#L5?}x^vyaW%f#;zSzvA&guh4b3)~!SZj3^S|?JIESGBH)tgCyHx9uL zh&qIHQ3o@I2=Xf5@-WB}YS1l=1M zJu0q>rJPWzZD4fg#zwcyYD1$NQZ4x&?B*<3EYbUa?f$k zY+NAKpa@U|C;}7#iU37`B0v$K2v7tl0u%v?07c;Ehk(h*7;H$aNGwRqNZOH@kQkA) z8I6n$pZ_;l0u214ABq4)fFeKh26>+sF zWFf77lOwHlIr0hwyutx*07AJJ>d&~pMJ515x3{jV4O=p;Tlw2wvb0^)TUefM|Er%{ zx$?$a#9hJp*7LR74!3@zu%BDGxS!cuSGcq<+hXf&*XkYnwu3E~3!|%ML%5$|dBlt4 zSGlGA>3Wr+X{kPB*2jK%*oPsk1TVwZHQ>G6syJ54!Zvj{jDkEb6suxg;iOPaRVw8Q zNNSZejexwMUxU}%@OsgZXhEnZ1$gbPxQ;NfRx6W|*VjtJZh2r)xzwCHnDjbQs43Ya z>cm@oXScZ?)b90d@_8 z&BDqR$=U{#B*fo^f8Iq9>rG3W4Pn_>Ts)H>j%VUoUX16meApig@j@^Z$?7$uYyi4+6aB5_?|k&Uq6?qZ3F292he zSW-+eG2L|2do{*8hIA$S6a!j8K-9LQyjwwvLq zNk53=oVX>|LYjc!-H)9Y$-lheM1m5%&shrOh{$T8dAF_OKa z+)g5OM#w{`@QmgS7{k`rbGbB>4>$`mKcSbcIIDIp;Nt<4R% z7Bo`5NSSdgr|SHIQU_JTo^JQl*%x>gdb|O9c}2Cod`4-hy|ALZs=6}2q`cbRvWV#e z(WY0Hl;u~>u}^c%$+QPNO>Td{)zlK5I@8|b^7-BPy4~k?HMVD)X?NAtbrxLbsju%W z9M{Gka4!pKmT7ONE&75h5b${xv^Ix?WI*V6>i=+n%7ylGz0(l#cHUZlBv* z>+YyvI2%Wfn=!&B;*s3NEh^X-a@c`-qD4#?k&K{;VD!OrMzA}CPxgLm7sEXwG9D_y zfOLHC%{Wo%B5&NMf>ABql$}hinPFvRtQO2t!tmi}O&*&ABN9ZF zAx5-E)J57X(a&+=_fB{&jY`v_<_S|bataI2o6L|Ax`$I5F;x<#qaF#{(s^<#Se-^Y zB8@{!_6rf!W`R)M9w`QuW+@^Rig+&XtW)|MntcJ%sF9V<7$F>KY6zB*NaPt#+ye`}xWU;QtxFO8xN1`Gxa1`Gxa1`Gxa z1`Gxa1`Gxa1`Gxa2L4k9;$%J}uH8^VY_Ow7kIapeB7kKq60$c9X{K9F~6-5;@%8M$>9W%VacLil`-$HjGys~Qa)CPQl5ZC6y*4v_EYTylA zpr+2<=nlAR7J35Z?xoeUYCzU3bu~iZh}veKd&E+(Ry8m6*ATmL4Lcj*bAx@f)?Jge z#NQ!oNzT}s0w1JtXU#0ks#_4x^BD=XjUKG(61UF}cHEq?Svff|CO#=C6fkmR&Uh|X z;fKWeUG;9LloJdA-hi*wZPCByBz+~Pzp8K6SL*lb&+6CdpZupp8|e%N3fx zFc>fxFc>fxFc>fxFc>fxFc>fxI06HM`7+eaYI>xIBXwshFo#9z(nKJHMe5Qnz=uWZ z(jb7}^$+e&as2*2PmnnM4gDtY|F71EXg_JYwDYwETCQeR_o$oI>8ftqZ@a;^%vNaA ztnXXzu&%aNS(7Y3TAs38WLaoA)}krzDz_-hlqpJ_{E2*zyjng%PL+O;9+x&t^QBzL zB<>e)7FUUtqTT$R`4RJ4bG3P}@K51!;ap+9FdB+C{tN~T2LAUL7@j9^NoBD4QLVcr z(A}IdY_h;5mU)_5U_X^^<4(eH>84lR#mzld;D(nq)z`wNHq^NZjGx^l%9$u|X-t_$ z&jQ&0rrRW0#|T^sOVXqG6L5T5hn~dG9xrgIWuT1D$bMv6NTC*=YhjbCyCSlNhN|4?@pfC~>>(kQ+&*8k zue$=W2Zs`nj?rC$k*T3N^lVOo8*Zc>Yef@4 z^d2ykIS@BB{%m!jE6^>k83XVjb1#7}NxRcMGnw=uy8qPj?qZMVFK`24K6EuU!3 zi=LT;GjDRWkUikKi=Ea_;9_y??jnv&6u6wS_8oPdF5kjd{7rCo=|=R$+PA0cE_!AH z)_#f0=fV3FcN;q%$F6Vn*5W-IyNjI_heryX+v?o4-Oh!%eefNa#>X3#`CH&t)LkLD zu{cqC^qx(kBYIE2tL0i2&7|O#;m=@^9 zt+n0u;tYhd?a@H7z?vgZkU49$wZH8~eV_Sz@fI;fkS)4pi+P;I92l0UKj&nAlWaQ&Y}a{m+S{}H6K^?d!G84;D0?5A|^UpPVHO#PeYf*Z=)V z<~?2iC#4IqaF^+R{oik#kW+@**YQ1G|0fdd+tYP7VEPj6d%FHlAhCP8)QKk}rRVGa zIC2O6lk5LJBvJcPzGv(ISVB^JqV7oQO)Ap!^?wX$yFb7F?{%C|ScZZt;s0R$uSaI^ z=hy$*vBJb~4gS>nUnLI`?dtb*{cj_kDwwRhwy=_>>+$;ELYl7U>wkqlG4^QvFViQ+ zKezsu$h7!p*8d`@agW#k<_saO4B;AgN2Ne!&>pS-c|uFi*8ipqc&bu*wEoBM|B=3% zgMY@K!GOVl!GOVl!GOVl!GOVl!GOVl!GOVl!GOWQ|I-YNM`k6rOs=WPtC>+=Saa;G zipptK)AI`*lXFLnA31*fxWdunC*+JCH9miQ&V+FjMnh2UxPttVqetQQ|49Eg2mg#e zg8_p9g8_p9g8_p9g8_p9g8_p9g8_p9g8_qqzaaxWuPAi=e}K~u{0%E;RLNk#V8CF& zV8CF&V8CF&V8CF&V8CF&V8CGD{~81Fe6G@oe*j(ozsteT02qG;0|o;I0|o;I0|o;I z0|o;I0|o;I0|o;I0|o>Cg$$(fWlDGc|MdI+FFF0o|H29yDjN(K3>XX;3>XX;3>XX; z3>XX;3>XX;3>XX;3`8-|2lY~9C61HZ|FAoN@%?`kO~$FgfWd&lfWd&lfWd&lfWd&l zfWd&lfWd&lz~794(D(nw{(pb7rZuW#FkmoXFkmoXFkmoXFkmoXFkmoXFkmoXFwo9G zX#M{yr~ld>Y#bO27z`K;7z`K;7z`K;7z`K;7z`K;7z`K;7!3SP7?AilvK=7x|Nn*4 zfBBnK%&3OJfWd&lfWd&lfWd&lfWd&lfWd&lfWd&lfWbhJ0a;M!`XBHA$Mp&Z8)z_K zFkmoXFkmoXFkmoXFkmoXFkmoXFkmoXFz|O|0Kfk?*8hLUmNn{RFkmoXFkmoXFkmoX zFkmoXFkmoXFkmoXFc4zESpSE(Fpdlc3;J!N%Nq4E7%&(x7%&(x7%&(x7%&(x7%&(x7%&(x7zi<7hC`$u!U47VA+^wUne}Ju zRLchCbEQbWSbAIPBQ}~}GOI!%f2Qd<^cy;sI~_+pT7PX5;;l%KB)QEsTrP{R9kcY~ zyafxFjC}v;ztBOlsH!QPnqN|0RdZrVX=%v``88D~#pRAl zoO@MP;q>WIrv+mi6AFrQM~yAYb&M_;Q#4`J7{~bh(YXcVCX637vS4)EF>y@yTF^bN zcFdB2dCS*4KX}E5)AA~XPYbk5O8x@fr&p9t%P*>^{{O3dSs$i+Yo1)zr?&W>>+)t^ z^VU5R->t|iI&gWf#(8`G0_6+yD~s~WD{87|v7`K=qDn_qReKY3d7<0Jm>3&uj1h8K zY;EzqC+BUx>Yn2?Rmq!a8FBie7l!?X^}#)-q`bJj8qw$7YCoZO*f8i_JH>Tw-gIqW z%_V0}%d2>I@|^u2-SQV`UrR~5lG2ju5=T|mR9uU&({6Pyj$yhtPms%E zYNy=xX5RdJH(u(#cywO5_w-(KFW&bT>RtxDC%@PcHFP@~Q&y{gK>n(`TN6JWCoRx<3cTRjWaU^kc~m#MX*``?(?Fm$2sjSqc! zrK1mgeMwRF(bIF9V@_2y+`eiY<%N}VrdO9#luysE%rApmT_w3Gbrs%iEsn9VTFlLr z%WSoU{~Vq-_ldnj)?G6tuj0AqK2}fv@#xi}q#V?pLhiG(*sM}jSXnZ?8dG20F{oSR zhgq5O)1a$ZYey$Ml2@WUl3!OoFRyso5_`f+ryf1!9W#OI!u(Q4O`)T-w5qhE(2+GY z?66y9?UrD_PL#_mpzP^+(@PKR+&1^Byt0iof80CE{Z}bV^~{>ut%`#crlJF82Bmg% zzr%TplX?x^b;r$l(|4smos@RnU#24T08A$C3EiqWP7d~2xQWZPg+H#$TQKwX36u5g zyyB<7TkzZSHAhj+>Zy)O$BeR?{DP7iGUlKI!%&=qHH|*+R`X#J)4VuME|Y4DSC{71 z)->FA&8~Ozj{oI#%iiYMM^E#pI|kOb{btc4ng@wY*$G4BGO@Ps`?5TrCS2c^<;knQ z=E2urcxvO(Q+8?*eMrZOg&%jz=uk6La#Ds|W`A2^DN z=2tm#M}dk#>`u8+JX7wZ6uFGAopR2Dc_*X^)1SHIj=ZXqZr+FfxcBHNS5jVBI->~I zS(P)Ys%eYkRT(_#S5;RMRzpYKHhq?fDLn@mHG$HP<&F9H;@I2few$abW&9(`$fNWu z-?h@1Uw9IpMX_6j$04giR6@sU#@S_ zH|gi-XXq>SWx7vq)a&*6`iXkAUan8o3-n|4vHD1Tm_Aq^p!bCgjX#3{g8_p9g8_p9 zg8_p9g8_p9g8_p9g8_qq|EC!+^9ZGw;K5j?reZm05SA$^SlaDa z4jhQ(fB{%0Cu7;aKbA>JSoZ6OWnv1 zURdfnmYRm8s$yxgVQIBuX|Z6bC|JremXd^}C}L?gV<`w&@;sI%6VEG(3EBeb>v4-* zrLR}>wRsw6+iUaLdRuQ(ud^O!dD+seT&IkXcgm&GYm!U+K|I$IC#ISoGM^xPE}SOB zni~0Ad57s;@C{gtH16F0ym8w&VKtAAj-{l!(s5!%ILeDk^2@WP4)zOep8vUG^YJ_0RG-kc@GmKou}fZKk~hoF z!l{nJX<3DXnuRvkUs+^X1vqn5)L@g{nDJSKDGfqf)n8d&)ztj5lFFoAFoHhIxKu6(;}HB^8wBPjifdx2XO9XG$u_DopyTJ6!>I zn#?H6D(v@{7sQ59;{QZJ@JgewZ=KLK?Pzs8>_kyhSWyOZLQO$o$f+XXXih@3kR!U3 z#kUA;wMVy7w7jm|z~Yt*ZLNPrf!(@p_4%u+8Z0^5sVerbYRfMCqIw_A$Aj>`6D+TD zuces3ir1*u$nB0tz2*pQV~%PU3VI0XPN6OTDCOLllaF>3slxep>zt!D3T<Z-HMVw9Wa; zODo8Sh4Yn1asHwUFy`>y<S5ih*XmX3 zEPa5wUEK(N07!dT-=ux1uF_YiKdAxr3-ui>pv~8|YY(eC_3!kL^oiP8`s@F>6~IVo zFkmoXFkmoXFkmoXFkmoXFkmoXFz{c+K!1J`aukgkGbU%kh@y(}^869gD=Mq=3rfMM zJl(=4BJ!?fYMRccqVkfe>QQ6HPS44jIy9coL7lvpwDHNP{cWMbCm{L`uNOZA;bmU? z8#WxT{q+|edC@e@!NR#Z%F`&Tj{ zTPIe(-8_k#BkV&#jWlBy-{q+Zd@iWm;cGV=&Lr&XF)QYqd;m4of(r|N3#H&nMSo@_ z$M9((Es6C9QyO8i>CI=LC_7778%z~6J_Ym&es#g)Yr*drOuhItCF1)46^2#1s&M+Z z`aAlw`n|Bi->9Fed-R!lA^7tr>lW=7?Gx=)ZI^b7c8RuD^Jz}4RGXj;)nYYH{YHIT zeMY@Uy;|L%u237)8ES!=t@eiujX#3{g8_p9g8_p9g8_p9g8_p9g8_qq|5q7^N$;hA z-*nhtdQ4h$P+H8;=%Ar7L!yI*#0-uO8XS`v9h4d~C^~3ROiFZ6N{l@^$R0B=I%r_b zfastBG0D+E$ua$-gZjrLMF%Ct^otJa7n2wrlo-=DI;d|2SG@5P_Gz0I!KSvqJy*;H9AO*u|)^jVyw|Y))-54kR?Wm z4pL&|=pZ>piVl)u#ONR~#vC1FjuE1Rgcv?Lh>tNv2bp@IUWyXd#bsI5f92v#Z*h9A z{-icX7t~7I-PTuZtE~ZRU%A|JK>A9$Od2RAny)h_3X6Hc^p@#tQw}6D{{C+=p#N$* zE++x`J>EL^GS4z+y{FObcQ(3R^-iy=$xTlC7FIg)s~vXOkkK*Q-Z7HBqTEg*rnjfd zD32Et(h^V;$+vYe%eOtt1eWED@{$u~1appv7RnSS%E*Wl6DB1fj^yWG+~^6oovzkE zGdX9u=MW|0SD)NRN=QgRYjXpx1&y={v(V!W*vl)b?Xb^S zslBkG9PDkd9a^=$Wf999qD`+XDT8fg?BGV2X%Bds-2Q;8sUVwsEd1{+ZR?a(a7gq z*b23X$S}OB^}(u!$F6itfnAl#3msLoT0Vbz*yW6jwjr@n!rURKZC0?QTYSwYyK4hZ zpS#}gtZnwzdlou9b#Ur$Zd~H7Bi$-IPMHNKqs6u;^ z4rRAr$ym+zk`jgtL2F9Lpa{=A{5U3Jh=gB(krA(TwHM~YwrxcY7(k&eMEh*i0F8`6hDnDQ5zWsO&RDKX3HeE=Ej8F#nIi422ptt? z$AWux(KWkBg{#6Y+1s1lQ4>G8Y0emCgr_69ul9++A2 z9GYndg`G{!b**s6gxi64dH6jf)9zkY+t^y?cDA?zom7iz>-2bg5UJh=$}Mg7E$UhH z=9Yk`+3W9w%0^e9zS-B*O*SpTXgw{rJwcZxvkOir9p4w+KDW2l-BH1CHjW%OV}wn_ zBe{#`fnZmMaH@JLm zc$$dn_Ziu;m{6G9Rl~Fwn-e@z+WL!9Le+58Rvc`c+GZFw@POCkTm(UWG9$wVn#+XdSYllU_tob~3eQhLx4ES};oq!-u0ad29}hNDx(q7||Y47iqIZKb(c% zJK2GNE1xTYtRyt#ZaHOdrSXK@@Ft@2lN=QjTZSLR@YjOJm9ycCj z?8p>ZzxHsbVn@xwNyl|-k4<%rsMiu#V{6oHCnMJ+CKRU}VE{3uoOlSaV;&Z-GG4%{ zmM^&&bQ7obRUeb9r1zy}@h$!V@jAZJ^r>2F`^2`;y3v}b`_N6c;ntnpbgT2o%U;Wu zri;cUhaNKUI?CDPY6(5o1w*nJM-hv}$f$V01*3-^)H9%r9NYk_TS)`B{Ql-zC%(gm zj)q1wKqwpB;gJmK@+fqCOdCWwv`B@CM7Ep+yS$BF;( zZj?#pZf7?IW3^eFPCFyZ<%m6*>l(ue02|txXGHQRHuOHNs_B7&SI8g-10~?FPIkikug7laF+R3w3NKJiQ0Uu)Qrd zNlK^yt4SUi^zaDJR#3SAhTEA~e-6JofQ_7JV`F%nV0K^t2#$#M)jYKThTE1{l#Hf+ zFcX1NVKWi2cTuG}Q6;(K4AC(Gog+fw>A~XsiBXoL&T9}J+W&|JvY0S`L{&EI_Pn-F zUt&2rdl8$OLW+eKlo&NVg<_GL6*)cfJk`F2XBO9xauN3_oKu%~XU0_Y(lY#+EV`Ir zlCVN>P?tq`npzq?wI1lzQA?jpF!u(RwjHn4HaE3Eww*mY1TQgTLMIuw$7pc7>R@QK zdOIz@Jz7g`LnpbomvyOm7pu8U`$D(Z?Q;dX(3>@~7p&YJYaG6sk8Y4mdryrEUDy;d zv<5EwPrSGY3|JTt@wpBcJ%7z_ocMUR}&C73LND=9j3LYH9Rgf3;Y%}6D;_5V|k zw!ud#vU+c)!#^aN&(NjNV;D&j`HTdn)L@R8_`xINv=j+1klF?Yn=&k;s5?*S%HSiE z9+fDv`1Gi&8FTGoLQYEPjajG+a>omurXHb4@-#%|+~6IK8g*wTK<57iuj62sIg9Z+ z4!@3Tvks9Gra^Zc7kn@YO$HJ31nJtLYw4_aL_d^-VsuQ6duHf5ejo|WHFRvy{r@N6 z{r`W_f7HLxztBI1UjTSZ->bi@Kd(OxzX9;DexH7)eye^1d>e3ueu;j8zCk}5eg|Nc zev00zx9E%DmjGOPjXq1S)XU(v01EZV`h+7}35=@-0|o;I0|o;I0|o;I0|o;I0|o;I z0|o;I1OFuq2)u&Qu}H&WC>BGo7>q?K7K5-z!NQKkKr9Aek&H!uERwM3heaY5eX&Tu zA|8u4Ec##(i$!lNVzB6ig^q=Wg^Gm@3o8~DEEFtcEF>&MEX-I4Snybo{r`>M|NAfL zZ-$x%0|o;I0|o;I0|o;I0|o;I0|o;I0|o;}nF0LUc>i~P|BtNywJ$mSkp542`~R8# zf&QlcD!loBQhx+?1Ka^`{jbt5)z8=0!yEt8^d)+;-T-g=Pt>dQQr!V>`p3a8ff@QB zJxTAQtGcNDq5Z1;02vy81_K5I1_K5I1_K5I1_K5I1_K5I1_K5I1_S>q42Xh)7xo0w z(g+M4N?^zk0)qz=NKGX$Xb^#v6ascTfq?@F3>ZKlIhjEJ{sfYe2=wbmATg0Z-@XJA z5(vb{6Nrl=(5DZ9*jNI+dlQI>A<(NA0bM7cX#`Z2fXzn0Y9(N?5Kt5XvP?jd2#6v9 zvzdS(5a4+NCX*m2ieiSgNY%4&i)_>X1vY?h;R}F|^mp}r`YT`scuL=;KcL?Q-v8Ud z4se-%A^85ErLTrx2v`E1|0nBp`aFF$`2CmaQ}pBXWAxE_mh`*ypmeU(B#o1F@h$OA z(I<{EA2dH`KFd7YJW%*vctW^Ps27gmU*d1){rn7m9G_@9WO~o^sOf6c8q*xp0Q3WT z7F~?yp;+!e`5nPYZ9;+xnUIp2jMyLkl>-jNac2nJYD*pL)>H@kiZ!w-mzGRtTsdvqC_=1 zVVl66-kUFQ`Q5pr^dcS1E(_pzXd4F!WQ1a(62x$)3%&X3S$dIGx9NeU!&fCBh{q>E zJUD4kieO0;d2q#%|h+1;-d*GY2Eh}seC~u}`Y-R|>T*6ecXa<{U0hwp=Vp%RzqE_1OZnWPY5kXUadiowYftIl+QrpY zJ~y%7F#&QFB@dxJ0wLA{0X_>Fs*3a^+O?K6!{>siPyarB`Xu!UEcFNa`@6{5&*xgz z@jjY!Vsy?vk~3yKJYTh++DA=P154{%S<@15|5ViBxRbWG0aB-xSm0+_VAsTI{ONry z9ORFz9%+K%G;A@eMs(j;%nvfb4~{^0fF_Pr0!tUTGFwPoJ~=vWiwSo+7(tDm1z8OX zVi(}U%;qj)HwRhu_GEgyi0uu=h7aYl8s^8&huC!^nrQ5S(V8>`W8<-y z)!^&{-_6%87}rRmqihl#bHm!I(PZbH<*kj4@M*2T#a)ZH`Kp_nG*_9MIM?ZIZOmFk z6Ve)oC0s-jHhUpX`>{1Uk|DnSfNxTR|J+iU6UGU;)&7KYUng^fup#j zyga2`DIZvllTTdOMcE5GO%7$37L_Wc$)%+1`Yy_@?=&$fd)ky~%Cv#gaPpyUNDe(U zNyWVs|B)6^;@qY>OTF6_XvH5@y8@%@XfYYlRji}MOwF%yPJvxYXH+_h#}zBZ@x_pP z!_e9;a<4Tlh{%0G7r8I!kh^1$Lvh4AaPG-2n!6sQyY4d0w=7#Wd_K)IAzJ16km>Zw zit37jeg#T_S^)d_1PYuqvLiaOlWI@~KU-Z<@Gnm}D;-rcN~`mR<|%oD z^FW`bym>T_yy!gU(L5Zp@vl}ntE%&>OA4K!!E{Gu^_*ifk5!IMITkW-PdbTa(4ill z)L8~pr3u3(C=&)wz{$tg(BwJM<<~?c4;DOj$XI1;zp*&w*ts<2i0G7aBT~{5b5nDb z+`hRu-RKi(x(>bK#E5iLW|S9>Oc|++j30?p4Vwe0pwFd7wOiaA(`<{=IWymxUs+t` zguf-O(X+cKVRl3bj`A60Ba%lbBjQJp`poL0KC>cH!6vga3af|p8>S5FI}E2AGn1wp z9?iqdh;)UeFdd{NrYUI&X*k)jGib7LDy|p)W7FCUnrvoCRY^ffX-V}QXOUw{KJ*$> z>XcMV>VQ;8z2w;HE>c%_nL5RuVo4d0f>UQ#(bOGgx~ibc)sD*Y{)zpS{xSWrve}hg zB&ZBe&^NuW(l@3rNpM0J2~G%45T72e#K**w1k<}nFg-j$Y;3F&8xxBYjH#drI?!Gb zp1@g%r(L+iDry4UWu5ME!Q4}sDx0SrN2e7Bv?p! zrCpR)8kLNcr=-DpAQA4@Fs_fMj{^xgw0E%np~M|uV)1xeS_9Ry$fNTxXF0u-*-^&G z$s><}|M>Fs5=bs1Wq5Ja6HZAy{P>u6d6j1&Y!WfO(dC63m18z;Z+piaP1!R?CXP&l z5$N(JO(i*Yd`6s_o0dMJxYLIhMJ=8Z{qQm*eSErO3dxI~2+!p7k9Gbj@x3_sskC+l zt6E!HV9PlF@l_S&><6jq&fOO(4IA@7uHOiPTeo2n$h8~r!}1yD9S8FCb60{~ zdG2J8E7q?9xoka--+IoeAbscHEH{{Uys*&=@wnk4udzqkJ{JTXWEC_+uB}j zkM^wgg!YJbpLVl$owf~rQSf~2Ty33px^{{d(7alMwm_@VW@_cy6m7CLR?C9l862R+ zYhVFU532{&Z`IG#_tkys9(A|6OTACsp>9{VsaxRJ2iK`9)g`J|tykx%Gu3i+iaJ>x zt7fS~)d6a}s;Q#wuytaDV zJljlLxowJVvTdv_%Qnu1*Yt^2Hdth=qdtoK=WShriZS+`g> zSl3xsT9;V8)_Uta>r898b&7Seb*weZI@CJA8gJFCqUEsVpygZ3XO{OZ`z(7byDhsc z_gQvWwp+GYwpcb;)>&3smRP)&ddob^OiQ_Cie<88tR>4b)H1*lZ_zBGa#%U2e5-t> zyszw2_9(lRUCMpR4rRNtP1&MsP}V6cl_iQ-saNJHGnH~>iZWRlt7Iudl>th;qA8+$ zSUxC!D}N@xFYlB0$h+lT@_q6SdAqz#-Xd?1*U2m8C9+qpm*>ed<#Kt7JXszqXURk5 z0dl;o$)a>vIw*ZBeFl3(?vwUNyQN*yebNqTyR=Q(B5jb?Nh_r#l2@vi=1DW9a%qY* zSsE*4NkgRpQoN)|qIg(5D1Iw`CcZE36ZeR_#a-fk;tp}UxJ}$5ZV=arE5#+ESF9K3 zi8IA=af&!u94lstL&X7Nyr_wy`7l_KzBPYle&4*$yvMxTyvux_d53wsd7F8Qd4qYK zd8K)Y*=w#h&oj?7mz$@UC!5Ebv&=)y1I+Pe4NPE%g@eMk!e_$!!aiY-uv^$A+$Zc1 zwhP;YEy4z2ov>0^B6z_vHBXo+lnYaY$--D6OBgB)5aI<*5c$LWLH=9*GyZ*kAHRp+ z&F|vx<9G1e`EC3begnUbU&$}wy?i}CkDn>=CKLJqeL(Pi^ghA&(0c^mMeh=P2fah^ zZS*$5x6oSz-$ZW`+>iDX+=uoNd;`5fa4*_R@OAV$!Pn4h1Ybq35_|={LhxnuGQmA) z55bqvO9Wp;FA{tKy+H7J^gO}m&~pTzMb8p^20cS?H`-0`Y4kL~r_fUbpF~d*d;&c| z@Nx7w!N<^J1Rq6@65NG$5!{J(5_|+bLhxbqFu{k=Lj)g04-$L;JwWh&bU(rS(0v5& zMfVcC2i-&PZge-nyU<+(??iVJyaU}q@OE@N!5wG^!Q0Sn1aC#R61)Z7LhxpEGr^nC zO$2X5Hxj%7-9T_V+D`C#bUnfA&~*f_Mb{F%23#dSp?5SXA)e8))8Eb))G7e zok4I7T0?L(T1~JGwzb3goQ_T>xC*TzxDu@-cp5s5;Hl_Tf-BGpf~TNU2rfs<2`)p+ z2rflS2`)iP2)3eDf&mmD=tq8nKI9{~7%e8)f?5bRqh^9$l|hhPJ0Ah-}MBv_B?3A&M+U>&L>Sc_^2E*;CM8i;5amn;8--4;21Q9 z;Ak|OU@pofI0}s-n1gZ%jzl8~W}|F^StyI(2sDCVCdwo@91SNp3=JcgfieiDqjZ94 zD2?DyG?d^FG=$(_G?-v2N+mc54I-F=QV80So!~$;kl+9`fM7C8CfFbKCzyni2=+t$ z2qvOLf_+h6f(a;rU_6Q^7>D8r_Cb9J#-dn)y-{z1F(`&0{KykQ9q9x$q!Cn+O3;RE z1g*$Q(1I)k;Y9;M8Oa1CBoP#mNYIST1O+4zM(|PYQG&a;T?BV>I|)9*Jwotd?qPxtaSsuEkb98e1Ka}y@8|9(cprBk z!F#!T3EsoqL-20yZi07lcM-gkyOZD@+#LjO=WZvsgWEyyHtsfpw{o`_yOiK1+$97r<}N0$r6U z*K%tKp23|#a1FPH;A(C)!8Wdq;OX4y*oS`ADlAv7#PYP$usrotELW_+@|07sT)rI3 zWy`Q!x)jSLOR#Kh#WE1U((lL8=fiUGVl1ak!?L9X%jRY*yDl982u{_}fET>P$vZ4aZ@^UQ8%CIag#q#*$ zu`DUUa_Uqpi;JULpjsv(AL`B5*g8BGZSS~Dw;|hGt z$MUgoT#kcK~QtblmJnc-aObJra?3?vCpgn`x@Eb=XIW&a zvz!E$f(px2OTOh8ORgo;G6ZY|36@@9|1~Lxlz%GUfVJQQ%l;| zuN)&=WM2BW^fTBGK9@d}-jZIEUXY%Wc7g@rcIigxYWR)G3&3Y&E!Ys2N-dH{S|H7p zs--fpBIHTqr5tIPlq&U?;=qm|Nu2nb_@nrh_=)%)SQ1_qpA{b$9}@2tZxycxTf)WS zCh=@>wYWkIh)rNkaEi0V6U5`iLh)E}4A>LWM7x+M_7=f=#{4^26uvWmVg85tZS(8q z7tK$DP2qm?9p;YSGN%&g$6l@Fog;#{0+ z!Ze{sm;@GvY$07p5&8+Sf=w`kjo}ymd;S3b5&sUq7d&Kk^N;cm@OSbzbDYQ{lg%V! zu$m+aA_X%Auu_N6h5Tz0fqM| zyhq_(3hz*Oo5EWZ-lVXf!afRbP}oc1bqcRhc$LB{6kevV2ZMrMqVOVx7brYW;W-M= zQh0{KZVFFRc#6W46rP~)IEBY3JW62~g`E^0q3|$;hbTNq;QnL1H;Tj58Q@D!4l@zv7xPrpv z6t+^hjKZZ9E}?KSg)JClbPY@)D{!UhWGQ8<^vdJ5-IIGe&*6waiu zj>1|BXHZx}VKs#|3a3+8MPVg{(ySI7s1V3jd_=6NMis{6OJ*3g1!qmclm_zNYXMg)b=_pzsBS&nbLH;Zq8qQ23a_ zM-=`+;X?`^PQ*Wn<(5!;RXuZDO^wCIttfPxQ4>j z6t1FhC53GiuAp!^g{>4Wqi`vOODJ4SVGD(eC|pS40t)9-*i2y)g^d(8P&kjmxfIq@ zIETX77zFMt3TIMSM`106GbpT~u$n>}1!58qh)F;oCINw%1bAW+;E73qCnf=&m;`uY z65xqRfF~vao|pu9ViMqqNq{FN0e&&w%Z^wCOvEB!A{GG?u?U!mMZiQX0w!V+FcFJ@ ziC6?o#3EoK76B8n2$+aPz(gzpCSnmV5sQF{SOiSOB48pG0TZzZn21HdL@WX(Vi7PA zi-3t(1Wd#tU?LU)6R`-Gh(*9eECMEC5ik*pfQeWHOvEB!A{GG?u?U!mMZi>wt9U#X zC0INw#}!x5N7TTH6)2i*1{2>uqac1-{(ow>8-o+FZ7|um-QN zmDmccYptu42jxTZ&+>QD1SwY>z%j307b(VXnR(b~)G$zi#l5e!G69wjaJ?$kqqziP{>lLCn?s+9Iu1tJ0=vvFahP zHN37qsotV)RoAP2buQQva@8c&YWvCdvF!!Ts`2Vi>c{Fk>I>?_V28L?y+B<9_J>7k zty-l{Qzxp!)l@ZByo$U_-dudaZSoAjTbO07uohd#fmI>FDuFHGbIS*om%)Z`zvTwYm6nZQJy>d4XmNtopxBaU8E471 z30v3V~q?e^?`CIs{{000Pj+o%~Bl6M$eTT?P z3$!17N5a2G-!ddGEg+n{v_M}X^3np%$x93LDLO#neTv9S3pgh)Eda?&3y4QvS^$!l z7JwhKa>+{zI43VH06%2mmllBJr3D~)X@T}Jr-e7r zJ~JKYVCGvG5UnBBWB3~i$Wg_>G@?T(13NJ7xg`LbvVJCA^*vXs}b}}c0oyJK8b5hvOoD{Y*Cxz|IN#Sbdq;M^BQn;2mDO}B*6s~4Y3Rj`43ENi@c_oqCh`fTx z%Zc1dj*~}eb9dk!Gler_TW9|qmm^;E5%pG9`b4OUs+!0nVcZAcKJHiU)jip%Vh#ukm;=HB z=72DXIUqQh1A>z|AWUKo2sO+Bp@umiOkxfQbC?6d9Oi&9i8&z5WDW>3nFGQk=73Pe z91yCQ1HvTcfH0joAWUZt2$PrtLNRkdIG#Bm9M2pOj%N-C$1?|nV&;G_l{p|3GY5o8 z%mJa0IUp1=2ZTcAfG~+UARNaW5RPLG2zkr_VKQ?-n8X|qjzyD5s~t<^L?VwNEjXUJ z9t>ix2Vp>=SJs8AX4~8<= zgQ3jzU?_7v7|L7^hBDWKLCp0amAM|IGS`DaD3xeGh-g26IUWpPjt2v%;{kVoIUe+5 zjtBji<3T^_cmUV?k@S6-<3S(hc+iJA9`s?32YsmH0mSb^;%m(DKx2*v8go3*nB##) z9SL zV2%erFvo)f%<{r-eyh*Z!@QZx0%zyE6nL&FLOHB z%bX7OGN*&R%;{h+b2@m1IUT&poDN=PP6w}Wug!6xQRa0_!L*urf=$esU<-35*utC%HZf;{3z;*)Cgx1AnK=_|X3hkgxXpy+O+;=aas!d) z5qU0=>xn#vNa{SWjyVskW6lF@%z0ona~@dDoCj7j=YclnJa9VKMshh_;AON5iGWEwsM^MH(6uxQ4@i$xO_jaV$g;$$p5 zSTta<5DQ`ssB_~(VhvbOiw}u0V7?0<&d0)u#XKxd!lDL?xmXY@!0b8ra5fgRu$YO( z3@oa#sKTNWixaSzjzt9)<`BrSBcL!=qhjw76;j6e$1qg=M-HPA*2m<^IHM${Z-wg zZ-o8-X6pCbEH<-^gEi@W;46L&tl3VM=UKnEmcaYb3(XDY9Qi4^+pplde7SVGa#Mhv{S3N1(uZtTkKeE6x!I!u#E4VC8m&z?)yQ zybi18xfZ0{qbyU7l@D8QR<5%=sWe#5R5C5o2=X6r10PI_wtvB zK5L`$jO8%sifLYv$cR9!c0Xrud}9F`wCx)8?CjL_bgXi-0(HP zYGsNg%`(br6R+f-<|)^dwZLWu0hHR0A7`5Hg0GX8H#N8VN8sNe^|!cc-DzkLPSxTH zG@ulGgue?%c9l;yxt9gt*TLO&Xy5=o&$Q4TnC|l|fu94O=3YL<*WBa?ULMe%L2`Qr z{Ub6+O6Dh#3|be!&nHKx?`MNP;Hs@{ZUsdX@yQam&+lpWqP{wx0^2H(V)1!Gd;ak_ zT1%_1rP=RBady5CXCKsPda#&$mT8~x5@N$k=p9u;OnV8v!b{Lopeu%#(9T4#2rVij zwLK$SZ$6Dy!ku4N=X3l0$l89<(($4auAkfx0bme`v3y1tH`8$gnk}&1 zMQy8Zi5tO25otI&L@iieNP@s$s6z@a5x=vNV1;4?& zu&BK~aGBiU$cl5n2Xo~99mC*ZGjyp+*NLOUB0)@;SKnjTb6S_ue+()>#60wjY#%QRNSxeRoFqq?Oh1u zd=(M*%ZRfB@qD(ajOAL;T327rFHy-wKV0k9ELyl zamSYCJ_@D&M_B3)F(c3#B;5zN?eGM`y^o{NX@z?)l=59%gW$Zty@O|&I(PeI#JwGs z>Mcx6tKaQxA)}FdGpt*4`=jP3ZeM%JZ$!**+}^M(UvH1}T6FqXyPV&+S8&0#&?L}z zxtDQWTt1&`IkzW?pJ0mE_N2+(bc);8oYla+)R)hVyykOx>)^tRxSWVYq<#Ew>4gsY zv2g>}p2r+EG+_}`}YH4x%;ObMj`GUO@&Y#5XfHx0; z<0o*o1)c!DRb*GW7cTV#*vtkOAIA&@FBZa$DRk*E+${?{UYBoqVRIcc2Sk4q_b9x< z$e8hPwhNaU9FlOp6Q7gcRxa^YvhYVAGLB1;l2SAf{S}l=40^) z(H=S$O%9bMqy?rl$f500|L)yUnAqtMhO%3Z;t2go5oM3TC=|CEfsc zeS2ot;Uhmh9&p!&IAF1^!6N`F*y`r4##t{RiK}qrtAc7?8B%lX=u!u_tv%-}f@)qK zQjQ=QiTry2M=@X!da%aD@Y{EsbvOJRHnsf^*rl0R1q)tq-2S{xaM-I2QiU z7&_qDI9pd^OM`0x+)mC4YH=oxLOTby4$nowC{E~{Yw5JuzB3hfMo8y69AOPkO?w}= z8Xx2SpF0ZsT$8$-Zsn5@+r4R3B0mmA&Kx9iR>MjQpNOcMu(VR~(n`fkD-|!TRJ^oO z@zP4gODh#GtyI)%eIqb z$eumT?1XH|<5l^AI2zq?rzwulMjf`)X~YSFO|%FnAW`sUI46@@cDB=vCyAd33P$gN zQ-HUzY3MT5!Xu=r$LzA*W!$~_44iw|4l;FNmuov-hGnCUmbnx@0g-K7=G%D(&Y{aa zTAayz9*)rY1}yWKqLEi=W+%n-IbkV-Tc_0Y;YWl8)3|e)rYs-4>B)&Yp9((j?K_&x ziHw4GEtwse72T3#RxCd}EPt}!$V|p`$IV1$ba|y3w=k(V-V~xL;;b^_?1YH3>Fwp? zba>+pSl5ou=3RIvDtgnYs`Wvb9p4}wUO0-60TC0?AEE1R?EEGf%EQwFTThu}{VGmO z#H;N>e1Do!Sy49CG22;|KfBaXF7Af##dwUu7ITemuQ^@62tv3s@uad~%y|DP!Vnr# zga7ba18w~_~eC# z!#NP#$)f{2Ac))Gs*d%*<>g3)I44oxf#F>JS=a$!2fPvgMBk_WOZ{2>9^QO^rv5{H z*L@xB4x=w1MC;pf}_u)S=%(srUP#rlKwaqDLA`QL2&89etd zvQ4)oSUKGer%IoTPg_c)ICy`5n%dj;k?k({&VRH`u>kD9H|E>T7s0z}7kHeH zFvkkNz#HhtgzHU*z=y#DKH>BDF8YKY&gi8n^g!{ekIWwcOR^b2r{vX_r->fwAKBv5=Jm*X~XWltO(T5P# z)zt^Nn-I-TOiptjM1*(8+>LmJ9uG`$H=q|@L%9!76d4=i-j4_`|J?f!9mbgsdW#W- z+hy)L^n+tFz+H<7#zX<`8bmboycYUiCG;5g9xe3Uh(>3ornz&P-@6duJPUUPYxmu;mSLs|%!fI-MXE#!a}0;XUPvQG=ytA&6`7=-NBLUw5( zU>XJ?16oMG76K+>5VAuHxkwA?Rs2S|?T9FgJ&0)M*@lR+*o_G1$+)eEfX`WO3nCbQ zjc{Gm59VgLPAzmZMI&&8Y*PG2xei2-N`z}iL{n}_#Usa98vyG|-d5gw_ zUPu0{_>Ge{HNQV;es54em{25tM1=E+nvncPi}$J)`fEgZ4kW+QLVu~DR}fL%mk}XD`+YUmLSJ*=UJ7@D0S4{GQEL^$b1?xzS_>zj!1SRnTy3c?tJd_zO`GBgn&U#AGC zk^d7B?FaWD!r7UHsVVfq!TklNq%aWY4amY&lotO^if{^RVG>GiTNdYR^T z8c`5?6P@;f!o@6x3_9&|DFW>Nrx0M4fA<6r)r7^R+2js$g#*(J1OseZ2VFR&Go^sh zNd<%_6c8LofNK+vF)%rCMD;n009KX^jH+Nn1&35Hi~#3YB8tyQSoI01KFUlAI&l%) zA;o`uPyvyHDmb8m{VLd}g1rcEt=2uN&u$g$Qo&9I92-zUzZ%r1f*mTjNCmwr*sg*e z6>L*MHv(MDa0~MZOu(ak5smsqZnNqIhW#R1yQOhQBJ=I&i}%?0rvHAtbsC*8d>8Np zb_cxNn7$9LE_8t-V#KurZ$KCJ2mUCyEpU@DB0& zbo75SjQ@WCGXdAay8kyymkDK36rNNMOM@`NyGYt1wMpy1-oH+&kYp(Z?EMXnzrcLJ zZ@||7X~!e*O#ch;RR7~JlKz0>yf`DhDg9phmGo1`RgTkOS08nZJ5n41*vo$u#^Tq3 zH9gqLg9SX=xL*fW?w8q4+GcD=Y$4k|TOYh3-)38DbK6$Kv+g|e8|L3GSYN|;1CAI% zhJA)U!&XC^VXeXKIOym#tTx^x6hP^(cIlEG)C+hGNR zYowEoh?MPk&hcHxH^kS(Ux;6al?0v=9|60BPl*Ar?ynb9U~Pcs?B9ab0B*FOv>$*s z4pzhL!kh4h|3l&~ahupImcbhUN$>{1EB5c(zhS=v-dK1qysK~&-csnZZ?reri|is; z^Zyns`Jb@e19tpY;a6a>e~B;vR`>rSye_-|HvEqX-xTf^J}%rOTmzQ*Gr}lX=5K|! z`D=wV>+9CXte>%d94zlI0ZaU?@LqlnyzTI;%Ee$wK$*aR!s?ElL8g7t^+ZvWk2 zA#kO22CM`Iz-qtNT4I%~CU|fE7nY~M?*2~8EnuDhZp&qsd3Z;EpXDO3xnFOo0$cqI z3kMeWuO|H$yo(7d;f)`PlE0J`;y)T7X0H$p`<-Y9Z759J^X^CH1q#} z?R}w{1AF=hOt+b?f_b5Brg~EfSjm6i{FM0?^St>H^Vh)||5NZT{=32MVbZ)0-pGi+ zTlky7THkHXFxyOTm|itKWBLwQ;eXEbVX(Zv)HDk-9lOBhzS&d;Hup)!w~enDp972g ztKhwjS$Jb(o3YtgX1Ls#VmxBp1@`v!U}>KOHum2)d;{$3-!{Brcn<9A?=swBxY6)l zu(r?Pe_?=WSNO8TYtXl?MjpNhaSP9Vf}xKJ+{ZQaF(ovU`>2AlxLX;zITaRlGg>%4 z6&7_vSqF={p)7_)-I)BaB0r?an-uv$Mc$~$8x;8gMZTZqaJ>K%3$%16Ght$ZqH6@0 zSWwZm3|)~46ARSuJpy-LL+{l>-^0*3fqS>+caEW}1nylLx>^gpilHk7?yTl_B||4& z+!YL+5ny6LE#Dc2P7B;+n%`-LVgeV}{9+8961Yn>zf%ldEO3`-eit)zA{Ay9Xdd&a zFtfn;o~N=N7K39vA6MitMb0WRs>m6Z!cl>nW@vpTca)(C0cI0u`hX_2&WqoAq@>{p%I3H0vFa$NDB=zG$_E_0?ilDkQO?~(0&2t z7icJ;16t^QhV}?B$3Q~??bSl}Ftk&Ec?KE^XqOhclc7EV<{D@ypnfg1kD*=x<{M}z zpo_H7UWPVj!h8b_-6C*38rr6zZVhc^2sE}Z)G5Gx1Jwn;PKMUIxXlc;Wx{*|4F%Mp zp>{1^8$*79Yt>K-LmM+WKSLW_+(w2Vaoh6*VnuVlhZ0}ag;xB?C3YbZ~P zm&^Rp1ujQJ*&52yP^N}5D8f9_nV&=8(lnH+p%e`zYe=F9bsfylCUByL>{_Ue`B`CE zJI&9^{LBJp(NK~WYG!^0fir2ysD&Ds9}!@M56TN5P75Ur{Z%0Uqx!*21I7dMC75YY z(Onopy}{7y0?ah1@m|-^9~gQ~fSCpw3h4J*=xYqU zD!@zw4F&XD4gE%o_bNlb6v(eN^eZj&m$cuwU|Bpoda}sNEac|`%rekazne*Z&d^H& z`I&}(s)fG9&~pOJGEfabFKFm_E#7ks{YW4`(a^IR`mu)oiy_GOM+|*WAkS#%X$}2Q zLqE{a_Zb4+?=kd*K%UajlUnE#>}Y{S^yqa57SUt!aYa6+$Zsq1TZ(*Ck&h_yVMRWq z$OjeqfFkc#(nga(nGxQOG{F~w0Nf( zilxE~0_LAeVrGzB@uf_hV&W1i+!r%(l8F;c%rh~^#BnB$F)>R;U6i?Im}@%82y26O z30tYN_$U)oOiVH{!NfQf@)#3Gm^jSDC=(-09AaXarX3a{%o(Q6pb%otAaxE20p=WH zVvvc0OdMchKNI_y*h>|63wxMzHxs*<*vZ5I6a7r|F|mV*il|s^4a2=p74ldS(-~u%qei%0aX5E(ip@n-1To==LrUMq! zFu2C4%kXi*a2vSj50*TGmUf>2$5xt3riB`Z8>HW1_`9XIU z({FeD7T(>z5uWyMfff179X42N@6+O~u+H8&c$$6`#>)e+y50tOBfdh+7nAL8!V3G3 z+3&I62Ji1*ZlAOZwjbO6!!~Ct7rt#hWpO6`ySdwVkFnFp!&}fE{t14Jujf9`?cwst ztK{Rfn2EbhZ6+Q^7wP+ea$VcZSZlL&7E7ec5mzHy=X^Y|G>^&yZ7u3 z1Vdn6bZBJs@R9L}$*H5$Gtt>&$LHox6xX)GfxO4?I3}ZNcTx52i}cm>i`TY5^jX7U znrq|S))OZN8uspKpE$AUuy@m;aJ0Q;Pv5pJJuN#9w})JZdX6{lZrRj+Xu5HB_o1oc zU4AU{YQrdK<3rTQAUp@HEN*THHh3F?!GQ6hMkQKO${EO z*X#DUU7k=gOsq9{Bd(_5P?O8&^)$FW&F)ZhgR9xu916NzoeC)YQ;iwRQug{>f7sW zIo@~7d(_vo*;O1`k0oz23}eYPWBA2IcQyNl;VEO-1r_&3!i_$+$LsPmgnjNt=dh>I z8*~qc0~=dnuHnWer>i;CINa=Z2AVuUci82Ogc`lxVXw#I3weUUklQ#}pJl>{=#}jFSEFvCXFzk**B7w#RuQ%-Ucsx)F4-_HdjtqyOJp$o? z#|4>0)?g-^4WSh?an1#T!@fvUFdU4yogq&X{DqtWcPJQc@HGq%`=Ao8=Ei`hIXoN* zhh5%g;0fN}XbgBg(C!TlO^{7{GiGBlm>`?t=0G#_i$HS#%HQA$G&!3=`iAK~+|cA| zbcUK;fuPsb9B>A`p~gU-9zQzaX*v;}nIAiG#N{r&%ARPeU-` z3^g_ehnt7J%^vTtvnd3fJ<`+=2}gXMaL5BapwSm_H+ekG%}^7s4@hkEg`gYwyiLJ| zh%@YThXR2}*ctJ7Ly=IZ(dG0ug+q~skT(EFXF~`M%|>UF(+7ueAOr`l$LV%AH-tPs zpR3X5YY0T3ufmbt8XA!+7kzG3OQ9Upt01pMVfZixKE(>jcFf7^ znN;%IHgjn01)>H*@EQ<}T6cUT5Us3IBL!xolQIqMg|7;ZVjSwKo0$zlHkHmAmV6yp z`N69jl~te#)|4G>{evABRW4Fg;z6)ho4UF>Echa3dM2YX*w$c@ln?+)xsipqp$XVM zZQ_58%r1O#79R|(l@Co$9vX|RS%XhSCWEuXYu3z0N5|Hz*#@r;1r9~}C$~VfKFUlb z{eoAGT#lMhBAOVyNEKbTt~fk9J|)vM*2u-{)?tiw>u3act7{BOae8Ybws8jk@=2Mp zLlZ-bV&I12x;}W4OW!kVnNc!?OcvrQAG%sE@7mGQ+g{0>ZPWPNXBXI{w9ZE(GnGnP z&w|mgZ!juXS83VQ;iIj}sv4FVW?YBQ7J+&-7pTd?7s?j0(LU>i-yLHdu)OQwfjB0J zPjfL5reD{K7j1lJtp(rwP|C?lw>Sr;4eHq2M?-^PJ{DaE#onN3ppks#1r#tRVAIXm z{Mr9+*~D*#{V!p2w+qOO*Yi)Y!(${c4PSYlicYU%?X0%QLSO5Q%qcw-teCrcHdoG7 zY0b%W+9U8rSfr}A0aGsIpGZuZC4gx&w2=Bh|0W;nFLTg6)Nmz5m($2wBm8$(PH-+k zr>-7!4)Bj9>fm9pp^|DKRPv~)gZO*+N0-M(V=)>VK}m@cJ5-BT@HYO_a4o&?-C1Bb z0(U3@Z(|tl4_r+lxQuv$9=MGNdl~|srjXkO7cF1J>4M8x#0&SaaH9b?G!4#RAlTS6 zj5izUHE{n}0k62Omrpw9oDDUwb6%3X8hi`+Ili(OK33ZoF0OJ^gHIkm1o4W;1EJ#d zDylb#?jzx0G$b6KDRxxSSO*o~;;agA*YXF@9U7g2*ISi{DdiAQ#P44SD9$JYuPT1u zf>#s_S&D^H@J{0icss2nfK#}a_>-3i#punwNWF`w_Y3GPQ}5@gcOmtD4!u`V@1LNz zqX2Lo_blMyz}QSg$fse?fV)x51DwM>4Ywqu2E)LDf zfW#ZP2Nu$s93GyDLo+mOmSXp^&bY?U-IyM@euPlb)YhZRP3dc*8%>>+PhR4Q6$MGYTRk;QXyJ%?`+(}@VnTIfG+a)O+eoyey zI6mz^5`f*|HUai`3NI5F`@b&zR(eHxNqUy9jrT3E)c(43xAX<+4(T@O7FY@JI_aEr zxpWFv0i1#r0)o;$sbAU#Hs2d1pVR;=0G7Zz?HQ6oN`e&t--I{%f93cotp4`{$9KR2 z{2NOx0~Xl>48zRAZpTG1yU^xX=V*d2{#7_+M~)*E*3~n@dI5hFe+z2{ydeGvp3r|= zd;q@scbE8C@sltEag%tx_-^>_-=*Rl%tIW8)%NzoTtpANjqiu&^DbC*uT(66Z~wv9 zmPO9~XLwfsYx~dO>wiDAf7kwq{XSTO?~Cx<{uA&v!43BF_OtdF*p*M&N9;rP-S93! zC#>`DvpemTcG;e7Pli?giS19Y_QA`x=WS2J8vhU5?zMf{_F3EQwwqyv|MRx9wiwKE zOxZ?YegECIUR$TlZ}Zulu)4o&%eE!kEH)zi3D)+183@;ZIs-ZbIs-ZbIs-ZbIs-Zb z|DQ8}D_>d5*rIRN61M1@HIFU&W_gk=`eu28E&68pE?e}?@*TG5o8@t~=$qv+w&fH^vx1yi@sUv*`jZjTDIt$ zrIIcBW~pF{zFErIqHmTmw&gYeL*hi09%7BX+K*8E@>ZI1TJYWTLdm?7h41_X{Ykt zvH`XTTv9)pwex&ZFFces@LbaNOR4BNMa8yDsOY|!imfN9*m8o3E?Dp!6guaq*nFId zO~5TRlyOhtBx3S*E8LjZ;8e}<@ddytB^4pQ-_162HRKNTu+7fr{bv zR7BQM5nfA0XblynW-5$6Dhyr}{BJx|{JM#Xmm8`0nVX8ITvR;KK*e{QR6JHs#iMmp ze7cs3`5G$bR#P!lO~qgp6$dM+I8Z^w{&Fhzl~F-TQE)GnQ1N0h6)zM~@w`mMbA?p= zWEB<97Etj_J{3>rQSoRl6_4al@o+X34`osDU?vq0WKeN`Iu)NxqvF<76y)U;{KVg( zWZVx+xDPvU9~5yvWXJtM8}1JXxZiKZ{XPrs_a@NDo6Yh7w7slZ8gJ2g89~{!O|8Z065Om%`e9<5C#L z{X1agf4}5~w*bmu%%28t0dS5#ft|q5VZ8r6$G05!!Ak$1g}1&x0`GmFbDVKp43__6 zj<91teABO6eG45%`C0HiKa=1=)c#}p_rO~JoA$5RKWD!kM)dE8Z}(je z>*dYDcLO3|zdvB#X78}C2fO@Qdl`JUF9WReO}4jfuiIV)oBn5EC4g_iH~a2}@%(MJ zkJ#R4I|uI~oUk2*(fpuor)?X2t8cB%Wvha*e3s1t-|71cyc7RRu;G6iR@Z+}xJUS+ z@G0R|czgdm*zundW`!{!BM_0~UBPOvl3vz<`(m7!gx|Lt+MCOw0fbiWz`WF#|9xW&p;;48Xvc0T>xG07GL2 zU~J3)42~Iq(J=!sJZ1pK#|*##nE@CfGXO(m24IZL01T2DfKf67Fid6u#>ou8K$!s; zDKh{=Wd>lZ%m56Q8Gz9;129}>0LIG8Q44xT)(K7=ud}aW~&kVo-ngJL=GXO(q24D=$01Tp&pp|hH z&1BChCbwO}WcS5PZavB57I^ax<9ETEcPKmI%{!Ev;mtdgo8ZkmlpXNq9m;li^A2U( z43i$P^9DDJshNCaiphs3nS5x1$p^=oyl0HbuN+}=_%IZlv7#QNgYgzyZF3f&F|r z1N-S4BXGBGw?}1je%SFR0_aOA_a{DV8BnsutY`JK}Arc zV#rR#L9phBm@ZSsk%5YBJQ@~QyG6MATx>Z=q}QYu z=;~~DNuPvg{qKSm+KxzjVVv)SH~ljulj9Gtn%k4`9RKr+^uSa2RFT+Uv%g?}0^XGWoc$K~u3sG9 zln>Z<*jwS-cO~%UI|FMrUKoLI-)$CrLZy%`Sge1tz6|feKV-ejdOLi*?kf0N-C_8y{Z{Kb zcprY1RkFNoc@^HDf6VfqmQPzgVtJ3{lw|@cuK#ofbOv+=bOv+=bO!z}GvK&53GV3O z2RHRaHC;81ld3z+7p*Q??Kq*jLwtF4RkdSYbqD!XRfAQIIn^EDODkI|9miGo5T9RB zSRo#xZn)bw@(pD*j#(ubZssdWYDyeY)%^lrTwGf0m{Hx&^TkD_Mak3Xh9^y?CwYkz zJ2^=_N@G|#(_MVUYL_^rI*;&W-ZpVkbspwdH8hD6sxzH0Yu+r5tIjkC^*Y9=ljGn< z-C5_U7mp~x#teQ{QI+Gc8l29{g?`yFs<`2%y)17_o;Z>Og8=v$K4ufMMe&g8e4i~j zKiM&?#y-qv7IYOjBC30o&&&_!OJVAU2XiBQW*)gJ&k<519OAQcx8^#6s(Y9($T^?pjw`wEu$dAaX<|=LFM)oN&BfA@;CC? zGC3_v`&17DpI^xRTcNmDO@Ox*loUAjC<(y*e|GM4uDDwX=I^j&S7nR4ROkC`wp^RI zQ*~Zu-IlXW98jI7#m>A=v0rt@t?e1@VxQ`a3H}_veFr#^S^kta*=xT@bzNMOTqE|X zQBRmNGBO<7)k4klb!ltU#2z(xJ}<2%&9O}lp5u#Ct5d~pHFz#9wJ25GsydI^l2enV zE$D>K#!n`b8_URzjxO-9qQ^M7u9#dWcB=7@mOR2BrS+wb zjjDS;pIcH=;@F_N_wfbA)y3j^HS<1OYG$fqof=H|ob0@8ajg=}y=_a&N^`7Hf?@cT zTA5bqXja`X^63?s6%L>3et}Of%P4brRrm9JW@&b*!=t*N<5!gwmN=SJ_fPnY;;dpv zqw0Q^PcO-Jhy62EL5FW6->FO#8s;E%6wO?ODs^GCvB;DsbaqB+%7g1G}-gONzY(gT_wSBcB2cb06xD8(K$LIeS+durbpZ{MiT?TUkv(kig zNE(v%fDOP__|o5c$s^TEl~R$E3w8i@m?!wF;|<5}9IrTDbo>|C0zBq;(D8N0mmQyT z{JZ0$jt{}i!FwE6I%1A_$F$=JSOgq!>~w60F9WtX*1#75YaC@TmyqR1c32&}_?Gww zm{a(<_#9XUJRv?RepCD!*am!Bybb0TZWOPD)dSDK3IxZ*NpS@11NOrA1G~izSc$+3 z76MgbF|5y*4mJX2_+r4HVD{mc_Lsm);QRK+?GM@Swcicj0Qi*sV^DGZr!$~4pfjK| zpfjK|pfjK|pfjK|@V|orG=VlpsJ*{AOzr*6A!_e$4pMu6bAa0Wn}?{qzu8Fb{Y@`Y zdw_Ym_hCRjp@|h z-|#rK_cwfu+WQ;cNA3L$hpD~4VU*hY8%C(Tzu^$I_cshvdw)YDfORm0slC4;MD6_z zL2B=BFj0GdgOS?%8w}Lm-@sFQfBw_d-k-mN+WYhGr}qB*Wz^oEKTYlZ`8c)r=VR2~ zpFc(I{rQWjy+40~+WYhK)ZU+;r}qB*9JTl7=cv6ue~jAu^OMxxpC708{`?VY@6R8m z_Wt}ZwfE=4)ZU*DQhR@Xh}!$}gVf%iKiGox;}1}Ke||r;_viOfdw;%<+WYf_+WT{F zQ+t2zC2H@_y-4l-xfiItKlePf_vfCY_Ws;YsJ%b;EVcLNo}u>s+|$(FpZfu|_vap^ z_Ws->)ZU+anA-bu4^ex6?m=qr&pklx{ki+8y+8LgYVXf|h1&abcTszP?oMj&&wY~G z`*XK4d;eRQz5g}L-v2$!-v1o4_rHqS`(MfI{ZBG`|Lx4)e+#qs?_~D=o0z?S2ebEY zXZHSnX79hA+54|z_Wrrd-ann%`*V&IkYtylJK4 z4GR^oCsFYSGZn9ysCd;##V-w1yv&;nyp_c1_}}(30xR|ZPI_5-UV0j={~wm_h1LJr zYWX)v=fUG+c44zLus$#E^b>mLJafl0?OjQ0D%VxZO0>~O+c{;M3B4sn_N|3k1A|6O1~aGUsV zu)^Sb;d_FoV1z#|M#KZ~ZvQqI->(; CN=KOaW-Hj#(*24Azk3~TlO09GBmAKvNz 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 -?QRo^RQHRw*wO{R3yHx_V8rq?$ zU$54v8da@gSi4cICgMd#A>MUZczXU=ejwkLughc5c6eGIl@H4!@-X%@+%Naa-LUVt zRkq8GvR=<4iS^u|$`7g##<)@7Ql>cP>hx`ZQC-M`*IrpBX=pA27Zz-)DSJzQ_0{`6tFd%0DvxLH>d9 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 + $ + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}/jni/cpp + ${CMAKE_CURRENT_SOURCE_DIR}/build/local/include + ) + +if (TW_UNIT_TESTS AND NOT (WIN32 AND NOT TW_STATIC_LIBRARY)) + add_subdirectory(tests) +endif () + +if (TW_BUILD_EXAMPLES AND NOT (WIN32 AND NOT TW_STATIC_LIBRARY)) + add_subdirectory(walletconsole/lib) + add_subdirectory(walletconsole) +endif () + +if (TW_ENABLE_PVS_STUDIO) + tw_add_pvs_studio_target(TrustWalletCore) +endif () + +if (TW_ENABLE_CLANG_TIDY) + tw_add_clang_tidy_target(TrustWalletCore) +endif () + +if (NOT ANDROID AND TW_UNITY_BUILD) + set_target_properties(TrustWalletCore PROPERTIES UNITY_BUILD ON) + file(GLOB_RECURSE PROTOBUF_SOURCE_FILES CONFIGURE_DEPENDS src/Cosmos/Protobuf/*.pb.cc src/proto/*.pb.cc) + foreach(file ${PROTOBUF_SOURCE_FILES}) + set_property(SOURCE ${file} PROPERTY SKIP_UNITY_BUILD_INCLUSION ON) + endforeach() + message(STATUS "Unity build activated") +endif() + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/swift/cpp.xcconfig.in ${CMAKE_CURRENT_SOURCE_DIR}/swift/cpp.xcconfig @ONLY) + +if(WIN32 AND NOT TW_STATIC_LIBRARY) + install(TARGETS TrustWalletCore RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +endif() + +install(TARGETS TrustWalletCore + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + ) + +install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/WalletCore + FILES_MATCHING PATTERN "*.h" +) + +install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/tools/windows-replace/cmake/CompilerWarnings.cmake b/tools/windows-replace/cmake/CompilerWarnings.cmake new file mode 100644 index 00000000000..d7d54fd984e --- /dev/null +++ b/tools/windows-replace/cmake/CompilerWarnings.cmake @@ -0,0 +1,95 @@ +macro(target_enable_asan target) + message("-- ASAN Enabled, Configuring...") + target_compile_options(${target} PUBLIC + $<$,$>:-fsanitize=address -fno-omit-frame-pointer> + $<$,$>:-fsanitize=address -fno-omit-frame-pointer>) + target_link_options(${target} PUBLIC + $<$,$>:-fsanitize=address -fno-omit-frame-pointer> + $<$,$>:-fsanitize=address -fno-omit-frame-pointer>) +endmacro() + +macro(target_enable_coverage target) + message(STATUS "Code coverage ON") + # This option is used to compile and link code instrumented for coverage analysis. + # The option is a synonym for -fprofile-arcs -ftest-coverage (when compiling) and -lgcov (when linking). + # See the documentation for those options for more details. + # https://gcc.gnu.org/onlinedocs/gcc-9.3.0/gcc/Instrumentation-Options.html + if (TW_IDE_CLION) + message(STATUS "Code coverage for Clion ON") + target_compile_options(${target} PUBLIC + $<$,$>:-fprofile-instr-generate -fcoverage-mapping> + $<$,$>:-fprofile-instr-generate -fcoverage-mapping> + $<$,$>:-fprofile-instr-generate -fcoverage-mapping>) + target_link_options(${target} PUBLIC + $<$,$>:-fprofile-instr-generate -fcoverage-mapping> + $<$,$>:-fprofile-instr-generate -fcoverage-mapping> + $<$,$>:-fprofile-instr-generate -fcoverage-mapping>) + else() + target_compile_options(${target} PUBLIC + $<$,$>:--coverage> + $<$,$>:--coverage>) + target_link_options(${target} PUBLIC + $<$,$>:--coverage> + $<$,$>:--coverage>) + endif () +endmacro() + +add_library(tw_error_settings INTERFACE) +add_library(tw::error_settings ALIAS tw_error_settings) + +add_library(tw_defaults_features INTERFACE) +add_library(tw::defaults_features ALIAS tw_defaults_features) + +add_library(tw_optimize_settings INTERFACE) +add_library(tw::optimize_settings ALIAS tw_optimize_settings) + +if(NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "MSVC")) + target_compile_options( + tw_error_settings + INTERFACE + -Wall + -Wextra # reasonable and standard + -Wfatal-errors # short error report + -Wshadow # warn the user if a variable declaration shadows one from a + -Wshorten-64-to-32 + -Wno-nullability-completeness + # parent context + -Wnon-virtual-dtor # warn the user if a class with virtual functions has a + # non-virtual destructor. This helps catch hard to track down memory errors + -Wcast-align # warn for potential performance problem casts + #-Wunused # warn on anything being unused + -Woverloaded-virtual # warn if you overload (not override) a virtual + # function + -Wnull-dereference # warn if a null dereference is detected + -Wdouble-promotion # warn if float is implicit promoted to double + -Wformat=2 # warn on security issues around functions that format output + ) +endif () + + + + +if (TW_WARNINGS_AS_ERRORS) + target_compile_options( + tw_error_settings + INTERFACE + -Werror + ) +endif () + +target_compile_features(tw_defaults_features INTERFACE cxx_std_20) + +target_compile_options(tw_optimize_settings INTERFACE + $<$,$>:-O0 -g> + $<$,$>:-O0 -g> + $<$,$>:-O2> + $<$,$>:-O2> + ) + +function(set_project_warnings project_name) + target_link_libraries(${project_name} INTERFACE tw::error_settings tw::defaults_features tw::optimize_settings) + + if (NOT TARGET ${project_name}) + message(AUTHOR_WARNING "${project_name} is not a target, thus no compiler warning were added.") + endif () +endfunction() diff --git a/tools/windows-replace/cmake/Protobuf.cmake b/tools/windows-replace/cmake/Protobuf.cmake new file mode 100644 index 00000000000..891d734ba24 --- /dev/null +++ b/tools/windows-replace/cmake/Protobuf.cmake @@ -0,0 +1,220 @@ +# 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. + +set(protobuf_SOURCE_DIR ${CMAKE_SOURCE_DIR}/build/local/src/protobuf/protobuf-3.19.2) +set(protobuf_source_dir ${CMAKE_SOURCE_DIR}/build/local/src/protobuf/protobuf-3.19.2) + +# sort + uniq -u +# https://github.com/protocolbuffers/protobuf/blob/master/cmake/libprotobuf.cmake +# https://github.com/protocolbuffers/protobuf/blob/master/cmake/libprotobuf-lite.cmake + +set(protobuf_SOURCE_FILES + ${protobuf_source_dir}/src/google/protobuf/any.cc + ${protobuf_source_dir}/src/google/protobuf/any.pb.cc + ${protobuf_source_dir}/src/google/protobuf/any_lite.cc + ${protobuf_source_dir}/src/google/protobuf/api.pb.cc + ${protobuf_source_dir}/src/google/protobuf/arena.cc + ${protobuf_source_dir}/src/google/protobuf/arenastring.cc + ${protobuf_source_dir}/src/google/protobuf/compiler/importer.cc + ${protobuf_source_dir}/src/google/protobuf/compiler/parser.cc + ${protobuf_source_dir}/src/google/protobuf/descriptor.cc + ${protobuf_source_dir}/src/google/protobuf/descriptor.pb.cc + ${protobuf_source_dir}/src/google/protobuf/descriptor_database.cc + ${protobuf_source_dir}/src/google/protobuf/duration.pb.cc + ${protobuf_source_dir}/src/google/protobuf/dynamic_message.cc + ${protobuf_source_dir}/src/google/protobuf/empty.pb.cc + ${protobuf_source_dir}/src/google/protobuf/extension_set.cc + ${protobuf_source_dir}/src/google/protobuf/extension_set_heavy.cc + ${protobuf_source_dir}/src/google/protobuf/field_mask.pb.cc + ${protobuf_source_dir}/src/google/protobuf/generated_enum_util.cc + ${protobuf_source_dir}/src/google/protobuf/generated_message_bases.cc + ${protobuf_source_dir}/src/google/protobuf/generated_message_reflection.cc + ${protobuf_source_dir}/src/google/protobuf/generated_message_table_driven.cc + ${protobuf_source_dir}/src/google/protobuf/generated_message_table_driven_lite.cc + ${protobuf_source_dir}/src/google/protobuf/generated_message_tctable_full.cc + ${protobuf_source_dir}/src/google/protobuf/generated_message_tctable_lite.cc + ${protobuf_source_dir}/src/google/protobuf/generated_message_util.cc + ${protobuf_source_dir}/src/google/protobuf/implicit_weak_message.cc + ${protobuf_source_dir}/src/google/protobuf/inlined_string_field.cc + ${protobuf_source_dir}/src/google/protobuf/io/coded_stream.cc + ${protobuf_source_dir}/src/google/protobuf/io/gzip_stream.cc + ${protobuf_source_dir}/src/google/protobuf/io/io_win32.cc + ${protobuf_source_dir}/src/google/protobuf/io/printer.cc + ${protobuf_source_dir}/src/google/protobuf/io/strtod.cc + ${protobuf_source_dir}/src/google/protobuf/io/tokenizer.cc + ${protobuf_source_dir}/src/google/protobuf/io/zero_copy_stream.cc + ${protobuf_source_dir}/src/google/protobuf/io/zero_copy_stream_impl.cc + ${protobuf_source_dir}/src/google/protobuf/io/zero_copy_stream_impl_lite.cc + ${protobuf_source_dir}/src/google/protobuf/map.cc + ${protobuf_source_dir}/src/google/protobuf/map_field.cc + ${protobuf_source_dir}/src/google/protobuf/message.cc + ${protobuf_source_dir}/src/google/protobuf/message_lite.cc + ${protobuf_source_dir}/src/google/protobuf/parse_context.cc + ${protobuf_source_dir}/src/google/protobuf/reflection_ops.cc + ${protobuf_source_dir}/src/google/protobuf/repeated_field.cc + ${protobuf_source_dir}/src/google/protobuf/repeated_ptr_field.cc + ${protobuf_source_dir}/src/google/protobuf/service.cc + ${protobuf_source_dir}/src/google/protobuf/source_context.pb.cc + ${protobuf_source_dir}/src/google/protobuf/struct.pb.cc + ${protobuf_source_dir}/src/google/protobuf/stubs/bytestream.cc + ${protobuf_source_dir}/src/google/protobuf/stubs/common.cc + ${protobuf_source_dir}/src/google/protobuf/stubs/int128.cc + ${protobuf_source_dir}/src/google/protobuf/stubs/status.cc + ${protobuf_source_dir}/src/google/protobuf/stubs/statusor.cc + ${protobuf_source_dir}/src/google/protobuf/stubs/stringpiece.cc + ${protobuf_source_dir}/src/google/protobuf/stubs/stringprintf.cc + ${protobuf_source_dir}/src/google/protobuf/stubs/structurally_valid.cc + ${protobuf_source_dir}/src/google/protobuf/stubs/strutil.cc + ${protobuf_source_dir}/src/google/protobuf/stubs/substitute.cc + ${protobuf_source_dir}/src/google/protobuf/stubs/time.cc + ${protobuf_source_dir}/src/google/protobuf/text_format.cc + ${protobuf_source_dir}/src/google/protobuf/timestamp.pb.cc + ${protobuf_source_dir}/src/google/protobuf/type.pb.cc + ${protobuf_source_dir}/src/google/protobuf/unknown_field_set.cc + ${protobuf_source_dir}/src/google/protobuf/util/delimited_message_util.cc + ${protobuf_source_dir}/src/google/protobuf/util/field_comparator.cc + ${protobuf_source_dir}/src/google/protobuf/util/field_mask_util.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/datapiece.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/default_value_objectwriter.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/error_listener.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/field_mask_utility.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/json_escaping.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/json_objectwriter.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/json_stream_parser.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/object_writer.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/proto_writer.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/protostream_objectsource.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/protostream_objectwriter.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/type_info.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/utility.cc + ${protobuf_source_dir}/src/google/protobuf/util/json_util.cc + ${protobuf_source_dir}/src/google/protobuf/util/message_differencer.cc + ${protobuf_source_dir}/src/google/protobuf/util/time_util.cc + ${protobuf_source_dir}/src/google/protobuf/util/type_resolver_util.cc + ${protobuf_source_dir}/src/google/protobuf/wire_format.cc + ${protobuf_source_dir}/src/google/protobuf/wire_format_lite.cc + ${protobuf_source_dir}/src/google/protobuf/wrappers.pb.cc +) + +set(protobuf_HEADER_FILES + ${protobuf_source_dir}/src/google/protobuf/any.h + ${protobuf_source_dir}/src/google/protobuf/any.pb.h + ${protobuf_source_dir}/src/google/protobuf/api.pb.h + ${protobuf_source_dir}/src/google/protobuf/arena.h + ${protobuf_source_dir}/src/google/protobuf/arena_impl.h + ${protobuf_source_dir}/src/google/protobuf/arenastring.h + ${protobuf_source_dir}/src/google/protobuf/compiler/importer.h + ${protobuf_source_dir}/src/google/protobuf/compiler/parser.h + ${protobuf_source_dir}/src/google/protobuf/descriptor.h + ${protobuf_source_dir}/src/google/protobuf/descriptor.pb.h + ${protobuf_source_dir}/src/google/protobuf/descriptor_database.h + ${protobuf_source_dir}/src/google/protobuf/duration.pb.h + ${protobuf_source_dir}/src/google/protobuf/dynamic_message.h + ${protobuf_source_dir}/src/google/protobuf/empty.pb.h + ${protobuf_source_dir}/src/google/protobuf/explicitly_constructed.h + ${protobuf_source_dir}/src/google/protobuf/extension_set.h + ${protobuf_source_dir}/src/google/protobuf/extension_set_inl.h + ${protobuf_source_dir}/src/google/protobuf/field_access_listener.h + ${protobuf_source_dir}/src/google/protobuf/field_mask.pb.h + ${protobuf_source_dir}/src/google/protobuf/generated_enum_reflection.h + ${protobuf_source_dir}/src/google/protobuf/generated_enum_util.h + ${protobuf_source_dir}/src/google/protobuf/generated_message_bases.h + ${protobuf_source_dir}/src/google/protobuf/generated_message_reflection.h + ${protobuf_source_dir}/src/google/protobuf/generated_message_table_driven.h + ${protobuf_source_dir}/src/google/protobuf/generated_message_table_driven_lite.h + ${protobuf_source_dir}/src/google/protobuf/generated_message_tctable_decl.h + ${protobuf_source_dir}/src/google/protobuf/generated_message_tctable_impl.h + ${protobuf_source_dir}/src/google/protobuf/generated_message_tctable_impl.inc + ${protobuf_source_dir}/src/google/protobuf/generated_message_util.h + ${protobuf_source_dir}/src/google/protobuf/has_bits.h + ${protobuf_source_dir}/src/google/protobuf/implicit_weak_message.h + ${protobuf_source_dir}/src/google/protobuf/inlined_string_field.h + ${protobuf_source_dir}/src/google/protobuf/io/coded_stream.h + ${protobuf_source_dir}/src/google/protobuf/io/gzip_stream.h + ${protobuf_source_dir}/src/google/protobuf/io/io_win32.h + ${protobuf_source_dir}/src/google/protobuf/io/printer.h + ${protobuf_source_dir}/src/google/protobuf/io/strtod.h + ${protobuf_source_dir}/src/google/protobuf/io/tokenizer.h + ${protobuf_source_dir}/src/google/protobuf/io/zero_copy_stream.h + ${protobuf_source_dir}/src/google/protobuf/io/zero_copy_stream_impl.h + ${protobuf_source_dir}/src/google/protobuf/io/zero_copy_stream_impl_lite.h + ${protobuf_source_dir}/src/google/protobuf/map.h + ${protobuf_source_dir}/src/google/protobuf/map_entry.h + ${protobuf_source_dir}/src/google/protobuf/map_entry_lite.h + ${protobuf_source_dir}/src/google/protobuf/map_field.h + ${protobuf_source_dir}/src/google/protobuf/map_field_inl.h + ${protobuf_source_dir}/src/google/protobuf/map_field_lite.h + ${protobuf_source_dir}/src/google/protobuf/map_type_handler.h + ${protobuf_source_dir}/src/google/protobuf/message.h + ${protobuf_source_dir}/src/google/protobuf/message_lite.h + ${protobuf_source_dir}/src/google/protobuf/metadata.h + ${protobuf_source_dir}/src/google/protobuf/metadata_lite.h + ${protobuf_source_dir}/src/google/protobuf/parse_context.h + ${protobuf_source_dir}/src/google/protobuf/port.h + ${protobuf_source_dir}/src/google/protobuf/reflection.h + ${protobuf_source_dir}/src/google/protobuf/reflection_ops.h + ${protobuf_source_dir}/src/google/protobuf/repeated_field.h + ${protobuf_source_dir}/src/google/protobuf/repeated_ptr_field.h + ${protobuf_source_dir}/src/google/protobuf/service.h + ${protobuf_source_dir}/src/google/protobuf/source_context.pb.h + ${protobuf_source_dir}/src/google/protobuf/struct.pb.h + ${protobuf_source_dir}/src/google/protobuf/stubs/bytestream.h + ${protobuf_source_dir}/src/google/protobuf/stubs/callback.h + ${protobuf_source_dir}/src/google/protobuf/stubs/casts.h + ${protobuf_source_dir}/src/google/protobuf/stubs/common.h + ${protobuf_source_dir}/src/google/protobuf/stubs/hash.h + ${protobuf_source_dir}/src/google/protobuf/stubs/logging.h + ${protobuf_source_dir}/src/google/protobuf/stubs/macros.h + ${protobuf_source_dir}/src/google/protobuf/stubs/map_util.h + ${protobuf_source_dir}/src/google/protobuf/stubs/mutex.h + ${protobuf_source_dir}/src/google/protobuf/stubs/once.h + ${protobuf_source_dir}/src/google/protobuf/stubs/platform_macros.h + ${protobuf_source_dir}/src/google/protobuf/stubs/port.h + ${protobuf_source_dir}/src/google/protobuf/stubs/status.h + ${protobuf_source_dir}/src/google/protobuf/stubs/stl_util.h + ${protobuf_source_dir}/src/google/protobuf/stubs/stringpiece.h + ${protobuf_source_dir}/src/google/protobuf/stubs/strutil.h + ${protobuf_source_dir}/src/google/protobuf/stubs/template_util.h + ${protobuf_source_dir}/src/google/protobuf/text_format.h + ${protobuf_source_dir}/src/google/protobuf/timestamp.pb.h + ${protobuf_source_dir}/src/google/protobuf/type.pb.h + ${protobuf_source_dir}/src/google/protobuf/unknown_field_set.h + ${protobuf_source_dir}/src/google/protobuf/util/delimited_message_util.h + ${protobuf_source_dir}/src/google/protobuf/util/field_comparator.h + ${protobuf_source_dir}/src/google/protobuf/util/field_mask_util.h + ${protobuf_source_dir}/src/google/protobuf/util/json_util.h + ${protobuf_source_dir}/src/google/protobuf/util/message_differencer.h + ${protobuf_source_dir}/src/google/protobuf/util/time_util.h + ${protobuf_source_dir}/src/google/protobuf/util/type_resolver.h + ${protobuf_source_dir}/src/google/protobuf/util/type_resolver_util.h + ${protobuf_source_dir}/src/google/protobuf/wire_format.h + ${protobuf_source_dir}/src/google/protobuf/wire_format_lite.h + ${protobuf_source_dir}/src/google/protobuf/wrappers.pb.h +) + +#file(GLOB_RECURSE protobuf_HEADER_FILES ${protobuf_SOURCE_DIR}/src/**/*.h) +include_directories(${protobuf_source_dir}/src) + +add_library(protobuf ${protobuf_SOURCE_FILES} ${protobuf_HEADER_FILES}) +set_target_properties( + protobuf + PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON + IMPORTED_CONFIGURATIONS Release + INCLUDE_DIRECTORIES ${protobuf_source_dir}/src + PUBLIC_HEADER "${protobuf_HEADER_FILES}" + LINK_FLAGS -no-undefined +) + +target_compile_options(protobuf PRIVATE -DHAVE_PTHREAD=1 -Wno-inconsistent-missing-override -Wno-shorten-64-to-32 -Wno-invalid-noreturn) + +install(TARGETS protobuf + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/protobuf +) +set(Protobuf_LIBRARIES protobuf) \ No newline at end of file diff --git a/tools/windows-replace/include/TrustWalletCore/TWAnySigner.h b/tools/windows-replace/include/TrustWalletCore/TWAnySigner.h new file mode 100644 index 00000000000..90276f3dea8 --- /dev/null +++ b/tools/windows-replace/include/TrustWalletCore/TWAnySigner.h @@ -0,0 +1,50 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +#pragma once + +#include "TWBase.h" +#include "TWCoinType.h" +#include "TWData.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +/// Represents a signer to sign transactions for any blockchain. +struct TWAnySigner; + +/// Signs a transaction specified by the signing input and coin type. +/// +/// \param input The serialized data of a signing input (e.g. TW.Bitcoin.Proto.SigningInput). +/// \param coin The given coin type to sign the transaction for. +/// \return The serialized data of a `SigningOutput` proto object. (e.g. TW.Bitcoin.Proto.SigningOutput). +TW_EXTERN +extern TWData *_Nonnull TWAnySignerSign(TWData *_Nonnull input, enum TWCoinType coin); + +/// Signs a transaction specified by the JSON representation of signing input, coin type and a private key, returning the JSON representation of the signing output. +/// +/// \param json JSON representation of a signing input +/// \param key The private key to sign with. +/// \param coin The given coin type to sign the transaction for. +/// \return The JSON representation of a `SigningOutput` proto object. +TW_EXTERN +extern TWString *_Nonnull TWAnySignerSignJSON(TWString *_Nonnull json, TWData *_Nonnull key, enum TWCoinType coin); + +/// Check if AnySigner supports signing JSON representation of signing input. +/// +/// \param coin The given coin type to sign the transaction for. +/// \return true if AnySigner supports signing JSON representation of signing input for a given coin. +TW_EXTERN +extern bool TWAnySignerSupportsJSON(enum TWCoinType coin); + +/// Plans a transaction (for UTXO chains only). +/// +/// \param input The serialized data of a signing input +/// \param coin The given coin type to plan the transaction for. +/// \return The serialized data of a `TransactionPlan` proto object. +TW_EXTERN +extern TWData *_Nonnull TWAnySignerPlan(TWData *_Nonnull input, enum TWCoinType coin); + +TW_EXTERN_C_END diff --git a/tools/windows-replace/include/TrustWalletCore/TWBase.h b/tools/windows-replace/include/TrustWalletCore/TWBase.h new file mode 100644 index 00000000000..db2f782dd9a --- /dev/null +++ b/tools/windows-replace/include/TrustWalletCore/TWBase.h @@ -0,0 +1,99 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#if !defined(TW_EXTERN_C_BEGIN) +#if defined(__cplusplus) +#define TW_EXTERN_C_BEGIN extern "C" { +#define TW_EXTERN_C_END } +#else +#define TW_EXTERN_C_BEGIN +#define TW_EXTERN_C_END +#endif +#endif + +#ifdef _WIN32 +#ifdef TW_STATIC_LIBRARY +#define TW_EXTERN +#else +#ifdef TW_EXPORT_LIBRARY +#define TW_EXTERN __declspec(dllexport) +#else +#define TW_EXTERN __declspec(dllimport) +#endif +#endif +#else +#define TW_EXTERN extern +#endif + +// Marker for default visibility +#ifdef _MSC_VER + #define TW_VISIBILITY_DEFAULT +#else + #define TW_VISIBILITY_DEFAULT __attribute__((visibility("default"))) +#endif + +// Marker for exported classes +#define TW_EXPORT_CLASS + +// Marker for exported structs +#define TW_EXPORT_STRUCT + +// Marker for exported enums +#define TW_EXPORT_ENUM(...) + +// Marker for exported functions +#define TW_EXPORT_FUNC TW_EXTERN + +// Marker for exported methods +#define TW_EXPORT_METHOD TW_EXTERN + +// Marker for exported properties +#define TW_EXPORT_PROPERTY TW_EXTERN + +// Marker for exported static methods +#define TW_EXPORT_STATIC_METHOD TW_EXTERN + +// Marker for exported static properties +#define TW_EXPORT_STATIC_PROPERTY TW_EXTERN + +// Marker for discardable result (static) method +#define TW_METHOD_DISCARDABLE_RESULT + +// Marker for Protobuf types to be serialized across the interface +#define PROTO(x) TWData * + +#ifdef _MSC_VER +#define TW_HAS_FEATURE(feature) 0 +#else +#define TW_HAS_FEATURE(feature) __has_feature(feature) +#endif + +#if TW_HAS_FEATURE(assume_nonnull) +#define TW_ASSUME_NONNULL_BEGIN _Pragma("clang assume_nonnull begin") +#define TW_ASSUME_NONNULL_END _Pragma("clang assume_nonnull end") +#else +#define TW_ASSUME_NONNULL_BEGIN +#define TW_ASSUME_NONNULL_END +#endif + + +#if !TW_HAS_FEATURE(nullability) +#ifndef _Nullable +#define _Nullable +#endif +#ifndef _Nonnull +#define _Nonnull +#endif +#ifndef _Null_unspecified +#define _Null_unspecified +#endif +#endif + +#include +#include +#include +#include + diff --git a/tools/windows-replace/include/TrustWalletCore/TWString.h b/tools/windows-replace/include/TrustWalletCore/TWString.h new file mode 100644 index 00000000000..4ad92566126 --- /dev/null +++ b/tools/windows-replace/include/TrustWalletCore/TWString.h @@ -0,0 +1,73 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "TWBase.h" + +TW_EXTERN_C_BEGIN + +typedef const void TWData; + +/// Defines a resizable string. +/// +/// The implementantion of these methods should be language-specific to minimize translation +/// overhead. For instance it should be a `jstring` for Java and an `NSString` for Swift. Create +/// allocates memory, the delete call should be called at the end to release memory. +typedef const void TWString; + +/// Creates a TWString from a null-terminated UTF8 byte array. It must be deleted at the end. +/// +/// \param bytes a null-terminated UTF8 byte array. +TW_EXTERN +TWString* _Nonnull TWStringCreateWithUTF8Bytes(const char* _Nonnull bytes) TW_VISIBILITY_DEFAULT; + +/// Creates a string from a raw byte array and size. It must be deleted at the end. +/// +/// \param bytes a raw byte array. +/// \param size the size of the byte array. +TW_EXTERN +TWString* _Nonnull TWStringCreateWithRawBytes(const uint8_t* _Nonnull bytes, size_t size) TW_VISIBILITY_DEFAULT; + +/// Creates a hexadecimal string from a block of data. It must be deleted at the end. +/// +/// \param data a block of data. +TW_EXTERN +TWString* _Nonnull TWStringCreateWithHexData(TWData* _Nonnull data) TW_VISIBILITY_DEFAULT; + +/// Returns the string size in bytes. +/// +/// \param string a TWString pointer. +TW_EXTERN +size_t TWStringSize(TWString* _Nonnull string) TW_VISIBILITY_DEFAULT; + +/// Returns the byte at the provided index. +/// +/// \param string a TWString pointer. +/// \param index the index of the byte. +TW_EXTERN +char TWStringGet(TWString* _Nonnull string, size_t index) TW_VISIBILITY_DEFAULT; + +/// Returns the raw pointer to the string's UTF8 bytes (null-terminated). +/// +/// \param string a TWString pointer. +TW_EXTERN +const char* _Nonnull TWStringUTF8Bytes(TWString* _Nonnull string) TW_VISIBILITY_DEFAULT; + +/// Deletes a string created with a `TWStringCreate*` method and frees the memory. +/// +/// \param string a TWString pointer. +TW_EXTERN +void TWStringDelete(TWString* _Nonnull string) TW_VISIBILITY_DEFAULT; + +/// Determines whether two string blocks are equal. +/// +/// \param lhs a TWString pointer. +/// \param rhs another TWString pointer. +TW_EXTERN +bool TWStringEqual(TWString* _Nonnull lhs, TWString* _Nonnull rhs) TW_VISIBILITY_DEFAULT; + +TW_EXTERN_C_END diff --git a/tools/windows-replace/powerShell/windows-bootstrap.ps1 b/tools/windows-replace/powerShell/windows-bootstrap.ps1 new file mode 100644 index 00000000000..0b29e2d3d1c --- /dev/null +++ b/tools/windows-replace/powerShell/windows-bootstrap.ps1 @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +# > powershell .\windows-bootstrap + +# Ported from the `bootstrap.sh` bash script +# Initializes the workspace with dependencies, then performs full build + +# Fail if any commands fails +$ErrorActionPreference = "Stop" + +#Set the position of the toolchain = $toolsPath +$toolsPath = Join-Path $PSScriptRoot "tools" + +Write-Host "#### Initializing workspace with dependencies ... ####" +& $toolsPath\windows-dependencies.ps1 + +Write-Host "#### Building and running tests ... ####" +& $toolsPath\windows-build-and-test.ps1 + + diff --git a/tools/windows-replace/powerShell/windows-build-and-test.ps1 b/tools/windows-replace/powerShell/windows-build-and-test.ps1 new file mode 100644 index 00000000000..815b73ab1ca --- /dev/null +++ b/tools/windows-replace/powerShell/windows-build-and-test.ps1 @@ -0,0 +1,25 @@ +# Build Wallet Core + +# Run after installing `dependencies` and `generating` code +# > powershell .\tools\windows-build-and-test.ps1 + +# Builds Wallet Core in static release mode, to build the console wallet +# Builds Wallet Core in dynamic release and debug mode, to build a DLL for applications + + + +# Fail if any commands fails +$ErrorActionPreference = "Stop" + +#Set the position of the toolchain = $toolsPath +$toolsPath = $PSScriptRoot + +echo "#### Generating files... ####" +& $toolsPath\windows-generate.ps1 + + +echo "#### Building... ####" +& $toolsPath\windows-build.ps1 + +& $toolsPath\windows-tests.ps1 + diff --git a/tools/windows-replace/powerShell/windows-build.ps1 b/tools/windows-replace/powerShell/windows-build.ps1 new file mode 100644 index 00000000000..da19f546275 --- /dev/null +++ b/tools/windows-replace/powerShell/windows-build.ps1 @@ -0,0 +1,54 @@ +# Load dependencies version +. $PSScriptRoot\windows-dependencies-version.ps1 + +$root = $pwd +$prefix = Join-Path $pwd "build\local" +$install = Join-Path $pwd "build\install" + + +if (Test-Path -Path $install -PathType Container) { + Remove-Item ¨CPath $install -Recurse +} + +cd build + +if (-not(Test-Path -Path "static" -PathType Container)) { + mkdir "static" | Out-Null +} +cd static +cmake -G $cmakeGenerator -A $cmakePlatform -T $cmakeToolset "-DCMAKE_PREFIX_PATH=$prefix" "-DCMAKE_INSTALL_PREFIX=$install" "-DCMAKE_BUILD_TYPE=Release" "-DTW_STATIC_LIBRARY=ON" ../.. +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} +cmake --build . --target INSTALL --config Release +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} +$libInstall = Join-Path $install "lib\TrustWalletCore.lib" +Remove-Item ¨CPath $libInstall # Replaced with the shared lib afterwards +cd .. + +if (-not(Test-Path -Path "shared" -PathType Container)) { + mkdir "shared" | Out-Null +} +cd shared +cmake -G $cmakeGenerator -A $cmakePlatform -T $cmakeToolset "-DCMAKE_PREFIX_PATH=$prefix" "-DCMAKE_INSTALL_PREFIX=$install" "-DCMAKE_BUILD_TYPE=Release" "-DCMAKE_DEBUG_POSTFIX=d" "-DTW_STATIC_LIBRARY=OFF" ../.. +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} +cmake --build . --target INSTALL --config Debug +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} +$pdbBuild = Join-Path $root "build\shared\Debug\TrustWalletCored.pdb" +$pdbInstall = Join-Path $install "bin\TrustWalletCored.pdb" +copy $pdbBuild $pdbInstall +cmake --build . --target INSTALL --config Release +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} +$cppInclude = Join-Path $install "include\WalletCore" +Remove-Item ¨CPath $cppInclude -Recurse # Useless from shared library +cd .. + +cd $root diff --git a/tools/windows-replace/powerShell/windows-dependencies-version.ps1 b/tools/windows-replace/powerShell/windows-dependencies-version.ps1 new file mode 100644 index 00000000000..00e6f353683 --- /dev/null +++ b/tools/windows-replace/powerShell/windows-dependencies-version.ps1 @@ -0,0 +1,14 @@ + +$gtestVersion="1.11.0" +$checkVersion="0.15.2" +$jsonVersion="3.10.2" +$boostVersion = "1.80.0" +$protobufVersion="3.19.2" +$SWIFT_PROTOBUF_VERSION="1.18.0" + + + +$cmakeGenerator = "Visual Studio 16 2019" +$cmakePlatform = "x64" +$cmakeToolset = "v142" + diff --git a/tools/windows-replace/powerShell/windows-dependencies.ps1 b/tools/windows-replace/powerShell/windows-dependencies.ps1 new file mode 100644 index 00000000000..8549e7f3e5c --- /dev/null +++ b/tools/windows-replace/powerShell/windows-dependencies.ps1 @@ -0,0 +1,158 @@ + +# Downloads and builds dependencies for Windows +# Run this PowerShell script from the repository root folder +# > powershell .\tools\windows-dependencies.ps1 + +# Ported from the `install-dependencies` bash script + +# GoogleTest +# Check +# Downloads the single-header Nlohmann JSON library +# Downloads boost library, only require the headers +# Downloads and builds Protobuf in static debug and release mode, with dynamic C runtime +# Builds the Wallet Core protobuf plugins in release mode + + +$ErrorActionPreference = "Stop" + +$root = $pwd +$prefix = Join-Path $pwd "build\local" +$include = Join-Path $prefix "include" + +# Load dependencies version +. $PSScriptRoot\windows-dependencies-version.ps1 + + + + +function download_dependencies +{ + & $PSScriptRoot\windows-download-dependencies.ps1 +} + +# GoogleTest +function build_gtest +{ + # Build debug and release libraries + $gtest_dir = Join-Path $root "build\local\src\gtest\googletest-release-$gtestVersion" + cd $gtest_dir + if (Test-Path -Path build_msvc -PathType Container) + { + Remove-Item ¨CPath build_msvc -Recurse + } + mkdir build_msvc | Out-Null + cd build_msvc + cmake -G $cmakeGenerator -A $cmakePlatform -T $cmakeToolset "-DCMAKE_INSTALL_PREFIX=$prefix" "-DCMAKE_DEBUG_POSTFIX=d" "-DCMAKE_BUILD_TYPE=Release" "-Dgtest_force_shared_crt=ON" .. + if ($LASTEXITCODE -ne 0) + { + exit $LASTEXITCODE + } + cmake --build . --target INSTALL --config Debug + if ($LASTEXITCODE -ne 0) + { + exit $LASTEXITCODE + } + cmake --build . --target INSTALL --config Release + if ($LASTEXITCODE -ne 0) + { + exit $LASTEXITCODE + } +} + +#check +function build_libcheck +{ + # Build debug and release libraries + $check_dir = Join-Path $root "build\local\src\check\check-$checkVersion" + cd $check_dir + if (-not(Test-Path -Path $check_dir\build_msvc -PathType Container)) + { + mkdir build_msvc | Out-Null + } + cd build_msvc + cmake -G $cmakeGenerator -A $cmakePlatform -T $cmakeToolset "-DCMAKE_INSTALL_PREFIX=$prefix" "-DCMAKE_DEBUG_POSTFIX=d" "-DCMAKE_BUILD_TYPE=Release" .. + if ($LASTEXITCODE -ne 0) + { + exit $LASTEXITCODE + } + cmake --build . --target INSTALL --config Debug + if ($LASTEXITCODE -ne 0) + { + exit $LASTEXITCODE + } + cmake --build . --target INSTALL --config Release + if ($LASTEXITCODE -ne 0) + { + exit $LASTEXITCODE + } +} + +#protobuf +function build_protobuf +{ + # Build debug and release libraries + $protobuf_dir = Join-Path $root "build\local\src\protobuf\protobuf-$protobufVersion" + cd $protobuf_dir + if (Test-Path -Path build_msvc -PathType Container) + { + Remove-Item ¨CPath build_msvc -Recurse + } + mkdir build_msvc | Out-Null + cd build_msvc + $protobufCMake = Get-Content ..\cmake\CMakeLists.txt # Bugfix + $protobufCMake = $protobufCMake.Replace("set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded$<$:Debug>)", + "if (MSVC AND protobuf_MSVC_STATIC_RUNTIME)`r`nset(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded$<$:Debug>)`r`nendif()") # Bugfix + $protobufCMake | Out-File -encoding UTF8 ..\cmake\CMakeLists.txt # Bugfix + cmake -G $cmakeGenerator -A $cmakePlatform -T $cmakeToolset "-DCMAKE_INSTALL_PREFIX=$prefix" "-DCMAKE_BUILD_TYPE=Release" "-Dprotobuf_WITH_ZLIB=OFF" "-Dprotobuf_MSVC_STATIC_RUNTIME=OFF" "-Dprotobuf_BUILD_TESTS=OFF" "-Dprotobuf_BUILD_SHARED_LIBS=OFF" ../cmake + if ($LASTEXITCODE -ne 0) + { + exit $LASTEXITCODE + } + cmake --build . --target INSTALL --config Debug + if ($LASTEXITCODE -ne 0) + { + exit $LASTEXITCODE + } + cmake --build . --target INSTALL --config Release + if ($LASTEXITCODE -ne 0) + { + exit $LASTEXITCODE + } + + build_swift_plugin +} + +# Protobuf plugins +function build_swift_plugin +{ + $pluginSrc = Join-Path $root "protobuf-plugin" + cd $pluginSrc + if (Test-Path -Path build -PathType Container) + { + Remove-Item ¨CPath build -Recurse + } + mkdir build | Out-Null + cd build + cmake -G $cmakeGenerator -A $cmakePlatform -T $cmakeToolset "-DCMAKE_INSTALL_PREFIX=$prefix" "-DCMAKE_BUILD_TYPE=Release" .. + if ($LASTEXITCODE -ne 0) + { + exit $LASTEXITCODE + } + cmake --build . --target INSTALL --config Release + if ($LASTEXITCODE -ne 0) + { + exit $LASTEXITCODE + } +} + + + +download_dependencies + +build_gtest +build_libcheck +build_protobuf +echo "`n`n" +echo "--------------------done...." + +cd $root diff --git a/tools/windows-replace/powerShell/windows-download-dependencies.ps1 b/tools/windows-replace/powerShell/windows-download-dependencies.ps1 new file mode 100644 index 00000000000..90010186637 --- /dev/null +++ b/tools/windows-replace/powerShell/windows-download-dependencies.ps1 @@ -0,0 +1,162 @@ + +# Downloads and builds dependencies for Windows + +#This script downloads third-party dependencies .By tools\Windows-dependencies.Ps1 calls. + + + +$ErrorActionPreference = "Stop" + + +$root = $pwd +$prefix = Join-Path $pwd "build\local" +$include = Join-Path $prefix "include" + +# Load dependencies version +. $PSScriptRoot\windows-dependencies-version.ps1 + + + +# GoogleTest +function download_gtest +{ + Write-Host "Downloading gtest..." + $gtestDir = Join-Path $prefix "src\gtest" + $gtestZip = "googletest-release-$gtestVersion.zip" + $gtestUrl = "https://github.com/google/googletest/archive/refs/tags/release-$gtestVersion.zip" + # Download and extract + if (-not(Test-Path -Path $gtestDir -PathType Container)) + { + mkdir $gtestDir | Out-Null + } + cd $gtestDir + if (-not(Test-Path -Path $gtestZip -PathType Leaf)) + { + Invoke-WebRequest -Uri $gtestUrl -OutFile $gtestZip + } + if (Test-Path -Path googletest-release-$gtestVersion -PathType Container) + { + Remove-Item ¨CPath googletest-release-$gtestVersion -Recurse + } + Expand-Archive -LiteralPath $gtestZip -DestinationPath $gtestDir +} +# Check +function download_libcheck +{ + Write-Host "Downloading libcheck..." + $checkDir = Join-Path $prefix "src\check" + $checkZip = "check-$checkVersion.zip" + $checkUrl = "https://codeload.github.com/libcheck/check/zip/refs/tags/$checkVersion" + + # Download and extract + if (-not(Test-Path -Path $checkDir -PathType Container)) + { + mkdir $checkDir | Out-Null + } + cd $checkDir + if (-not(Test-Path -Path $checkZip -PathType Leaf)) + { + Invoke-WebRequest -Uri $checkUrl -OutFile $checkZip + } + if (Test-Path -Path check-$checkVersion -PathType Container) + { + Remove-Item ¨CPath check-$checkVersion -Recurse + } + Expand-Archive -LiteralPath $checkZip -DestinationPath $checkDir + +} + +# Nlohmann JSON +function download_nolhmann_json +{ + + Write-Host "Downloading Nlohmann JSON..." + $jsonDir = Join-Path $prefix "include\nlohmann" + $jsonUrl = "https://github.com/nlohmann/json/releases/download/v$jsonVersion/json.hpp" + $jsonFile = Join-Path $jsonDir "json.hpp" + if (Test-Path -Path $jsonFile -PathType Leaf) { + Remove-Item ¨CPath $jsonFile + } + if (-not(Test-Path -Path $jsonDir -PathType Container)) { + mkdir $jsonDir | Out-Null + } + Invoke-WebRequest -Uri $jsonUrl -OutFile $jsonFile +} + +# Boost +function download_and_move_include_boost +{ + Write-Host "Downloading boost..." + + $boostVersionU = $boostVersion.Replace(".", "_") + $boostDir = Join-Path $prefix "src\boost" + $boostZip = "boost_$boostVersionU.zip" + $boostUrl = "https://nchc.dl.sourceforge.net/project/boost/boost/$boostVersion/$boostZip" + + # Download and extract + if (-not(Test-Path -Path $boostDir -PathType Container)) + { + mkdir $boostDir | Out-Null + } + cd $boostDir + if (-not(Test-Path -Path $boostZip -PathType Leaf)) + { + Invoke-WebRequest -Uri $boostUrl -OutFile $boostZip + } + if (Test-Path -Path boost_$boostVersionU -PathType Container) + { + Remove-Item ¨CPath boost_$boostVersionU -Recurse + } + + # Expand-Archive -LiteralPath $boostZip -DestinationPath $boostDir + $boostZipPath = Join-Path $boostDir $boostZip + Add-Type -Assembly "System.IO.Compression.Filesystem" + [System.IO.Compression.ZipFile]::ExtractToDirectory($boostZipPath, $boostDir) + + #move include + if (-not(Test-Path -Path $include -PathType Container)) + { + mkdir $include | Out-Null + } + $boostInclude = Join-Path $include "boost" + if (Test-Path -Path $boostInclude -PathType Container) + { + Remove-Item ¨CPath $boostInclude -Recurse + } + $boostSrcInclude = Join-Path $boostDir "boost_$boostVersionU\boost" + move $boostSrcInclude $boostInclude + +} + +function download_protobuf +{ + Write-Host "Downloading Protobuf..." + + # Protobuf + $protobufVersion = "3.19.2" + $protobufDir = Join-Path $prefix "src\protobuf" + $protobufZip = "protobuf-cpp-$protobufVersion.zip" + $protobufUrl = "https://github.com/protocolbuffers/protobuf/releases/download/v$protobufVersion/$protobufZip" + + # Download and extract + if (-not(Test-Path -Path $protobufDir -PathType Container)) { + mkdir $protobufDir | Out-Null + } + cd $protobufDir + if (-not(Test-Path -Path $protobufZip -PathType Leaf)) { + Invoke-WebRequest -Uri $protobufUrl -OutFile $protobufZip + } + if (Test-Path -Path protobuf-$protobufVersion -PathType Container) { + Remove-Item ¨CPath protobuf-$protobufVersion -Recurse + } + Expand-Archive -LiteralPath $protobufZip -DestinationPath $protobufDir +} + + +download_gtest +download_libcheck +download_nolhmann_json +download_protobuf +download_and_move_include_boost + +cd $root diff --git a/tools/windows-replace/powerShell/windows-doxygen_convert_comments.ps1 b/tools/windows-replace/powerShell/windows-doxygen_convert_comments.ps1 new file mode 100644 index 00000000000..487cffdc465 --- /dev/null +++ b/tools/windows-replace/powerShell/windows-doxygen_convert_comments.ps1 @@ -0,0 +1,31 @@ +#perl Download http://strawberryperl.com/releases.html + + + +$SWIFT_PARAMETER_PATTERN = 's/\\param\s+([^\s]+)/\- Parameter $1:/g' +$SWIFT_RETURN_PATTERN = 's/\\return/\- Returns:/g' +$SWIFT_NOTE_PATTERN = 's/\\note/\- Note:/g' +$SWIFT_SEE_PATTERN = 's/\\see/\- SeeAlso:/g' +$SWIFT_FOLDER_PATH = "swift\Sources\Generated\*.swift" +$SWIFT_FOLDER_PATH_BAK = "swift\Sources\Generated\*.bak" + +function process_swift_comments($path) +{ + perl '-pi.bak' -e $SWIFT_PARAMETER_PATTERN $path + perl '-pi.bak' -e $SWIFT_RETURN_PATTERN $path + perl '-pi.bak' -e $SWIFT_NOTE_PATTERN $path + perl '-pi.bak' -e $SWIFT_SEE_PATTERN $path +} + +function swift_convert +{ + echo "Processing swift convertion..." + + foreach($path in Get-ChildItem $SWIFT_FOLDER_PATH) + { + process_swift_comments($path) + } + Remove-Item $SWIFT_FOLDER_PATH_BAK + +} +swift_convert \ No newline at end of file diff --git a/tools/windows-replace/powerShell/windows-generate.ps1 b/tools/windows-replace/powerShell/windows-generate.ps1 new file mode 100644 index 00000000000..a9a3b9c6f66 --- /dev/null +++ b/tools/windows-replace/powerShell/windows-generate.ps1 @@ -0,0 +1,91 @@ + +# Generate protobuf headers +# Requires Ruby to be installed, and added into the system PATH environment variable + +# Ported from `generate-files` + +# Run after installing dependencies +# > powershell .\tools\windows-generate.ps1 + +$ErrorActionPreference = "Stop" + +#Set the position of the toolchain = $toolsPath +$toolsPath = Join-Path $pwd "tools" + + +$root = $pwd +$prefix = Join-Path $pwd "build\local" +$bin = Join-Path $prefix "bin" +$include = Join-Path $prefix "include" + +$env:Path = $bin + ";" + $env:Path + +where.exe ruby +where.exe protoc +protoc.exe --version + +# Clean +if (Test-Path -Path "swift\Sources\Generated" -PathType Container) { + Remove-Item ¨CPath "swift\Sources\Generated" -Recurse +} +if (Test-Path -Path "jni\java\wallet\core\jni" -PathType Container) { + Remove-Item ¨CPath "jni\java\wallet\core\jni" -Recurse +} +if (Test-Path -Path "jni\cpp\generated" -PathType Container) { + Remove-Item ¨CPath "jni\cpp\generated" -Recurse +} + +mkdir swift\Sources\Generated\Protobuf | Out-Null +mkdir swift\Sources\Generated\Enums | Out-Null + +# Generate coins info from registry.json +ruby.exe codegen\bin\coins +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} + +# Generate interface code +ruby.exe codegen\bin\codegen +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} + +# Convert doxygen comments to appropriate format +& $toolsPath\windows-doxygen_convert_comments.ps1 + +# Generate Java, C++ and Swift Protobuf files +protoc.exe "-I=$include" -I=src/proto --cpp_out=src/proto --java_out=lite:jni/java src/proto/*.proto +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} + +# Generate internal message protocol Protobuf files -- not every time +protoc.exe "-I=$include" -I=src/Tron/Protobuf --cpp_out=src/Tron/Protobuf src/Tron/Protobuf/*.proto +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} +protoc.exe "-I=$include" -I=src/Zilliqa/Protobuf --cpp_out=src/Zilliqa/Protobuf src/Zilliqa/Protobuf/*.proto +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} + +protoc.exe "-I=$include" -I=src/Cosmos/Protobuf --cpp_out=src/Cosmos/Protobuf src/Cosmos/Protobuf/*.proto +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} +protoc.exe "-I=$include" -I=tests/chains/Cosmos/Protobuf --cpp_out=tests/chains/Cosmos/Protobuf tests/chains/Cosmos/Protobuf/*.proto +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} + +# Generate Proto interface file +protoc.exe "-I=$include" -I=src/proto "--plugin=$bin\protoc-gen-c-typedef.exe" --c-typedef_out include/TrustWalletCore src/proto/*.proto +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} +protoc.exe "-I=$include" -I=src/proto "--plugin=$bin\protoc-gen-swift-typealias.exe" --swift-typealias_out swift/Sources/Generated/Protobuf src/proto/*.proto +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} + +echo "done..." \ No newline at end of file diff --git a/tools/windows-replace/powerShell/windows-replace-file.pl b/tools/windows-replace/powerShell/windows-replace-file.pl new file mode 100644 index 00000000000..c357c93c27e --- /dev/null +++ b/tools/windows-replace/powerShell/windows-replace-file.pl @@ -0,0 +1,241 @@ +#!/usr/bin/perl + +if ($#ARGV+1 != 1 ) { + + print "Enter the command: -e or -r,\n"; + print " -e : extract command is used to extract the modified source code to a tools file.\n"; + print " -r : replace command is used to replace the source code file.\n"; + exit; +} + +$op=$ARGV[0]; + +@trezor_crypto_file = ( + "trezor-crypto/crypto/rand.c", + "trezor-crypto/crypto/base32.c", + "trezor-crypto/crypto/base58.c", + "trezor-crypto/crypto/bignum.c", + "trezor-crypto/crypto/blake2b.c", + "trezor-crypto/crypto/blake2s.c", + "trezor-crypto/crypto/cardano.c", + "trezor-crypto/crypto/shamir.c", + "trezor-crypto/crypto/sha3.c", + "trezor-crypto/crypto/tests/test_check.c", + "trezor-crypto/crypto/tests/CMakeLists.txt", + "trezor-crypto/crypto/monero/base58.c", + "trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-donna-portable.h", + "trezor-crypto/include/TrezorCrypto/groestl_internal.h", + "trezor-crypto/include/TrezorCrypto/nist256p1.h", + "trezor-crypto/include/TrezorCrypto/rand.h", + "trezor-crypto/include/TrezorCrypto/aes.h", + "trezor-crypto/include/TrezorCrypto/endian.h", + "trezor-crypto/CMakeLists.txt" +); + +@src_file = ( + "src/Base32.h", + "src/BinaryCoding.h", + "src/HDWallet.cpp", + "src/Aptos/MoveTypes.cpp", + "src/Everscale/Cell.h", + "src/Everscale/Cell.cpp", + "src/Everscale/CellSlice.h", + "src/NULS/Address.cpp", + "src/Ethereum/ABI/ParamFactory.cpp", + "src/interface/TWBitcoinScript.cpp", + "src/Keystore/EncryptionParameters.cpp" +); + +@tests_file = ( + "tests/chains/Bitcoin/MessageSignerTests.cpp", + "tests/chains/Cardano/SigningTests.cpp", + "tests/chains/Cardano/TWCardanoAddressTests.cpp", + "tests/chains/Polkadot/SignerTests.cpp", + "tests/chains/Zilliqa/SignerTests.cpp", + "tests/chains/Aptos/MoveTypesTests.cpp", + "tests/common/BCSTests.cpp", + "tests/CMakeLists.txt", + "tests/main.cpp" +); + + +@include_file = ( + "include/TrustWalletCore/TWBase.h", + "include/TrustWalletCore/TWString.h", + "include/TrustWalletCore/TWAnySigner.h" +); + +@walletconsole_file = ( + "walletconsole/CMakeLists.txt", + "walletconsole/lib/CMakeLists.txt" +); + +@cmake_file = ( + "cmake/Protobuf.cmake", + "cmake/CompilerWarnings.cmake" + +); + +@wallet_cmakeLists_file = ( + "CMakeLists.txt" +); + +@protobuf_plugin_file = ( + "protobuf-plugin/CMakeLists.txt" +); + +@windows_tools_file =( + "tools/windows-build.ps1", + "tools/windows-build-and-test.ps1", + "tools/windows-dependencies.ps1", + "tools/windows-dependencies-version.ps1", + "tools/windows-download-dependencies.ps1", + "tools/windows-doxygen_convert_comments.ps1", + "tools/windows-generate.ps1", + "tools/windows-replace-file.p1", + "tools/windows-samples.ps1", + "tools/windows-tests.ps1", +); +#ÍêÕû·¾¶ +sub addFullPath +{ + @pathAndCMD = @_; + + $prefixPathName = $_[0]; + + for($a = 1;$a < $#pathAndCMD + 1;$a = $a + 1) + { + $pathAndCMD[$a] = join( "", $pathAndCMD[0] ,$pathAndCMD[$a] ); + } + return @pathAndCMD; + +} + +#¿½±´Îļþ +sub copyFile +{ + #my($walletPath,$toolsPath) = @_; + my($fromPath,$toPath) = @_; + + use File::Copy; + for($a = 1;$a < @$fromPath;$a = $a + 1) + { +=pod + print "\n"; + print "fromPath = ",$fromPath->[$a],"\n"; + print "toPath = ",$toPath->[$a],"\n"; + print "\n"; +=cut + copy $fromPath->[$a] , $toPath->[$a] or warn 'copy failed.'; + } + +} + +#path--> /wallet-core-win +use Cwd; +$TWdir = getcwd; + +$TWdir = join( "", $TWdir ,"/" ); + +#path--> tools/windows-replace/ +$toolsDir = join( "", $TWdir,"tools/windows-replace/" ); + +#trezor_cryptoÎļþ +@wallet_trezor_crypto_path = addFullPath($TWdir,@trezor_crypto_file); +@tools_trezor_crypto_path = addFullPath($toolsDir,@trezor_crypto_file); + +#src +@wallet_src_path = addFullPath($TWdir,@src_file); +@tools_src_path = addFullPath($toolsDir,@src_file); + +#tests +@wallet_tests_path = addFullPath($TWdir,@tests_file); +@tools_tests_path = addFullPath($toolsDir,@tests_file); + +#include +@wallet_include_path = addFullPath($TWdir,@include_file); +@tools_include_path = addFullPath($toolsDir,@include_file); + +#walletconsole +@wallet_walletconsole_path = addFullPath($TWdir,@walletconsole_file); +@tools_walletconsole_path = addFullPath($toolsDir,@walletconsole_file); + +#protobuf_plugin +@wallet_protobuf_plugin_path = addFullPath($TWdir,@protobuf_plugin_file); +@tools_protobuf_plugin_path = addFullPath($toolsDir,@protobuf_plugin_file); + +#cmake +@wallet_cmake_path = addFullPath($TWdir,@cmake_file); +@tools_cmake_path = addFullPath($toolsDir,@cmake_file); + +# wallet_cmakeLists +@wallet_cmakeLists_path = addFullPath($TWdir,@wallet_cmakeLists_file); +@tools_wallet_cmakeLists_path = addFullPath($toolsDir,@wallet_cmakeLists_file); + + + +if ($op eq "-e") +{ + + #copy trezor_cryptoÎļþ + copyFile(\@wallet_trezor_crypto_path ,\@tools_trezor_crypto_path); + + #copy src + copyFile(\@wallet_src_path ,\@tools_src_path); + + #copy test + copyFile(\@wallet_tests_path ,\@tools_tests_path); + + #copy include + copyFile(\@wallet_include_path ,\@tools_include_path); + + #copy walletconsole + copyFile(\@wallet_walletconsole_path ,\@tools_walletconsole_path); + + #copy protobuf_plugin_ + copyFile(\@wallet_protobuf_plugin_path ,\@tools_protobuf_plugin_path); + + #copy cmake + copyFile(\@wallet_cmake_path ,\@tools_cmake_path); + + #copy cmakeLists + copyFile(\@wallet_cmakeLists_path ,\@tools_wallet_cmakeLists_path); +} +elsif($op eq "-r") +{ + #toolsÌæ»»Ô´Âë + #copy trezor_cryptoÎļþ + copyFile(\@tools_trezor_crypto_path,\@wallet_trezor_crypto_path ); + + #copy src + copyFile(\@tools_src_path,\@wallet_src_path ); + + #copy test + copyFile(\@tools_tests_path,\@wallet_tests_path ); + + #copy include + copyFile(\@tools_include_path,\@wallet_include_path); + + #copy walletconsole + copyFile(\@tools_walletconsole_path,\@wallet_walletconsole_path ); + + #copy protobuf_plugin_ + copyFile(\@tools_protobuf_plugin_path,\@wallet_protobuf_plugin_path); + + #copy cmake + copyFile(\@tools_cmake_path,\@wallet_cmake_path ); + + #copy cmakeLists + copyFile(\@tools_wallet_cmakeLists_path,\@wallet_cmakeLists_path); +} +else{ + + print "Enter the command: -e or -r,\n"; + print " -e : extract command is used to extract the modified source code to a tools file.\n"; + print " -r : replace command is used to replace the source code file.\n"; + exit; +} + + + + diff --git a/tools/windows-replace/powerShell/windows-samples.ps1 b/tools/windows-replace/powerShell/windows-samples.ps1 new file mode 100644 index 00000000000..4ed82a3a656 --- /dev/null +++ b/tools/windows-replace/powerShell/windows-samples.ps1 @@ -0,0 +1,28 @@ + +# Build Samples + +# Load dependencies version +. $PSScriptRoot\windows-dependencies-version.ps1 + +$ErrorActionPreference = "Stop" + +$root = $pwd +$prefix = Join-Path $pwd "build\local" +$install = Join-Path $pwd "build\install" + + +cd samples\cpp +if (-not(Test-Path -Path "build" -PathType Container)) { + mkdir "build" | Out-Null +} +cd build +cmake -G $cmakeGenerator -A $cmakePlatform -T $cmakeToolset "-DCMAKE_BUILD_TYPE=Release" "-DWALLET_CORE=$root" .. +cmake --build . --config Release +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} +$dllInstall = Join-Path $install "bin\TrustWalletCore.dll" +$dllCopy = Join-Path $root "samples\cpp\build\Release\TrustWalletCore.dll" +copy $dllInstall $dllCopy + +cd $root diff --git a/tools/windows-replace/powerShell/windows-tests.ps1 b/tools/windows-replace/powerShell/windows-tests.ps1 new file mode 100644 index 00000000000..d3883de0bb2 --- /dev/null +++ b/tools/windows-replace/powerShell/windows-tests.ps1 @@ -0,0 +1,14 @@ +# Run tests + +$ErrorActionPreference = "Stop" + +.\build\static\trezor-crypto\crypto\tests\Release\TrezorCryptoTests.exe +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} + +.\build\static\tests\Release\tests.exe tests +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} + diff --git a/tools/windows-replace/protobuf-plugin/CMakeLists.txt b/tools/windows-replace/protobuf-plugin/CMakeLists.txt new file mode 100644 index 00000000000..2d8c3a722fb --- /dev/null +++ b/tools/windows-replace/protobuf-plugin/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.2 FATAL_ERROR) +project(TrustWalletCoreProtobufPlugin) + +set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14" CACHE STRING "Minimum OS X deployment version" FORCE) +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if ("$ENV{PREFIX}" STREQUAL "") + set(PREFIX "${CMAKE_SOURCE_DIR}/../build/local") +else() + set(PREFIX "$ENV{PREFIX}") +endif() + +include_directories(${PREFIX}/include) +link_directories(${PREFIX}/lib) + +find_package(Protobuf REQUIRED PATH ${PREFIX}/lib/pkgconfig) +include_directories(${Protobuf_INCLUDE_DIRS}) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +add_executable(protoc-gen-c-typedef c_typedef.cc ${PROTO_SRCS} ${PROTO_HDRS}) +if (WIN32) + target_link_libraries(protoc-gen-c-typedef ${Protobuf_LIBRARIES} ${Protobuf_PROTOC_LIBRARIES}) +else() + target_link_libraries(protoc-gen-c-typedef protobuf -lprotoc -pthread) +endif() + +add_executable(protoc-gen-swift-typealias swift_typealias.cc ${PROTO_SRCS} ${PROTO_HDRS}) +if (WIN32) + target_link_libraries(protoc-gen-swift-typealias ${Protobuf_LIBRARIES} ${Protobuf_PROTOC_LIBRARIES}) +else() + target_link_libraries(protoc-gen-swift-typealias protobuf -lprotoc -pthread) +endif() + +install(TARGETS protoc-gen-c-typedef protoc-gen-swift-typealias DESTINATION bin) diff --git a/tools/windows-replace/readme.md b/tools/windows-replace/readme.md new file mode 100644 index 00000000000..5bab8dbaba3 --- /dev/null +++ b/tools/windows-replace/readme.md @@ -0,0 +1,33 @@ +# windows-replace folder + +The windows-replace folder manages modified files. + +used when there are no powershell scripts in the tools directory: + +```perl .\tools\windows-replace\windows-dragon-king.pl -spouting``` + +Place Windows dependencies in the tools folder and project home directory. + + + +```perl .\tools\windows-replace\windows-dragon-king.pl -suction``` + +Reclaim the files in the tools folder and project home directory. + +#Replace the file +```perl .\tools\windows-replace-file.pl -e ``` + +Replace the modified file with the source file. + +#Build + + powershell ```.\windows-bootstrap``` + + Or, broken up in smaller steps: + + powershell ```.\tools\windows-dependencies.ps1``` + + This script builds TrustWallet and runs the tests: + + powershell ```.\tools\windows-build-and-test.ps1``` + diff --git a/tools/windows-replace/src/Aptos/MoveTypes.cpp b/tools/windows-replace/src/Aptos/MoveTypes.cpp new file mode 100644 index 00000000000..4df1a932c5f --- /dev/null +++ b/tools/windows-replace/src/Aptos/MoveTypes.cpp @@ -0,0 +1,152 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include + +namespace TW::Aptos { + +Aptos::ModuleId::ModuleId(Address accountAddress, Identifier name) noexcept + : mAccountAddress(accountAddress), mName(std::move(name)) { +} + +Data ModuleId::accessVector() const noexcept { + BCS::Serializer serializer; + serializer << static_cast(gCodeTag) << mAccountAddress << mName; + return serializer.bytes; +} + +std::string ModuleId::string() const noexcept { + std::stringstream ss; + ss << mAccountAddress.string() << "::" << mName; + return ss.str(); +} + +std::string ModuleId::shortString() const noexcept { + std::stringstream ss; + ss << "0x" << mAccountAddress.shortString() << "::" << mName; + return ss.str(); +} + +#ifdef _MSC_VER +#pragma optimize( "", off ) +#endif +Data StructTag::serialize(bool withResourceTag) const noexcept { + BCS::Serializer serializer; + if (withResourceTag) + { + serializer << gResourceTag; + } + serializer << mAccountAddress << mModule << mName << mTypeParams; + return serializer.bytes; +} +#ifdef _MSC_VER +#pragma optimize("", on) +#endif + +StructTag::StructTag(Address accountAddress, Identifier module, Identifier name, std::vector typeParams) noexcept + : mAccountAddress(accountAddress), mModule(std::move(module)), mName(std::move(name)), mTypeParams(std::move(typeParams)) { +} +std::string StructTag::string() const noexcept { + std::stringstream ss; + ss << "0x" << mAccountAddress.shortString() << "::" << mModule << "::" << mName; + if (!mTypeParams.empty()) { + ss << "<"; + ss << TypeTagToString(*mTypeParams.begin()); + std::for_each(begin(mTypeParams) + 1, end(mTypeParams), [&ss](auto&& cur) { + ss << ", " << TypeTagToString(cur); + }); + ss << ">"; + } + return ss.str(); +} + +BCS::Serializer& operator<<(BCS::Serializer& stream, Bool) noexcept { + stream << Bool::value; + return stream; +} +BCS::Serializer& operator<<(BCS::Serializer& stream, U8) noexcept { + stream << U8::value; + return stream; +} +BCS::Serializer& operator<<(BCS::Serializer& stream, U64) noexcept { + stream << U64::value; + return stream; +} +BCS::Serializer& operator<<(BCS::Serializer& stream, U128) noexcept { + stream << U128::value; + return stream; +} +BCS::Serializer& operator<<(BCS::Serializer& stream, TAddress) noexcept { + stream << TAddress::value; + return stream; +} +BCS::Serializer& operator<<(BCS::Serializer& stream, TSigner) noexcept { + stream << TSigner::value; + return stream; +} + +BCS::Serializer& operator<<(BCS::Serializer& stream, const StructTag& st) noexcept { + auto res = st.serialize(); + stream.add_bytes(begin(res), end(res)); + return stream; +} + +BCS::Serializer& operator<<(BCS::Serializer& stream, const TStructTag& st) noexcept { + stream << TStructTag::value; + auto res = st.st.serialize(false); + stream.add_bytes(begin(res), end(res)); + return stream; +} + +BCS::Serializer& operator<<(BCS::Serializer& stream, const Vector& t) noexcept { + stream << Vector::value; + for (auto&& cur: t.tags) { + stream << cur; + } + return stream; +} + +BCS::Serializer& operator<<(BCS::Serializer& stream, const TypeTag& t) noexcept { + std::visit([&stream](auto&& arg) { stream << arg; }, t.tags); + return stream; +} + +BCS::Serializer& operator<<(BCS::Serializer& stream, const ModuleId& module) noexcept { + stream << module.address() << module.name(); + return stream; +} +std::string TypeTagToString(const TypeTag& typeTag) noexcept { + auto visit_functor = [](const TypeTag::TypeTagVariant& value) -> std::string { + if (std::holds_alternative(value)) { + return "bool"; + } else if (std::holds_alternative(value)) { + return "u8"; + } else if (std::holds_alternative(value)) { + return "u64"; + } else if (std::holds_alternative(value)) { + return "u128"; + } else if (std::holds_alternative(value)) { + return "address"; + } else if (std::holds_alternative(value)) { + return "signer"; + } else if (auto* vectorData = std::get_if(&value); vectorData != nullptr && !vectorData->tags.empty()) { + std::stringstream ss; + ss << "vector<" << TypeTagToString(*vectorData->tags.begin()) << ">"; + return ss.str(); + } else if (auto* structData = std::get_if(&value); structData) { + return structData->string(); + } else if (auto* tStructData = std::get_if(&value); tStructData) { + return tStructData->st.string(); + } else { + return ""; + } + }; + + return std::visit(visit_functor, typeTag.tags); +} + +} // namespace TW::Aptos diff --git a/tools/windows-replace/src/Base32.h b/tools/windows-replace/src/Base32.h new file mode 100644 index 00000000000..7c9f5e9cb3e --- /dev/null +++ b/tools/windows-replace/src/Base32.h @@ -0,0 +1,68 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Data.h" + +#include + +#include + +namespace TW::Base32 { + +/// Decode Base32 string, return bytes as Data +/// alphabet: Optional alphabet, if missing, default ALPHABET_RFC4648 +inline bool decode(const std::string& encoded_in, Data& decoded_out, const char* alphabet_in = nullptr) { + size_t inLen = encoded_in.size(); + // obtain output length first + size_t outLen = base32_decoded_length(inLen); + //win +#ifdef _MSC_VER + uint8_t *buf = (uint8_t *)_alloca(outLen); +#else + uint8_t buf[outLen]; +#endif + + if (alphabet_in == nullptr) { + alphabet_in = BASE32_ALPHABET_RFC4648; + } + // perform the base32 decode + uint8_t* retval = base32_decode(encoded_in.data(), inLen, buf, outLen, alphabet_in); + if (retval == nullptr) { + return false; + } + decoded_out.assign(buf, buf + outLen); + return true; +} + +/// Encode bytes in Data to Base32 string +/// alphabet: Optional alphabet, if missing, default ALPHABET_RFC4648 +inline std::string encode(const Data& val, const char* alphabet = nullptr) { + size_t inLen = val.size(); + // obtain output length first, reserve for terminator + size_t outLen = base32_encoded_length(inLen) + 1; + //win +#ifdef _MSC_VER + char *buf = (char *)_alloca(outLen); +#else + char buf[outLen]; +#endif + if (alphabet == nullptr) { + alphabet = BASE32_ALPHABET_RFC4648; + } + // perform the base32 encode + char* retval = base32_encode(val.data(), inLen, buf, outLen, alphabet); + if (retval == nullptr) { + // return empty string if failed + return std::string(); + } + // make sure there is a terminator ath the end + buf[outLen - 1] = '\0'; + return std::string(buf); +} + +} // namespace TW::Base32 diff --git a/tools/windows-replace/src/BinaryCoding.h b/tools/windows-replace/src/BinaryCoding.h new file mode 100644 index 00000000000..88c11ebab31 --- /dev/null +++ b/tools/windows-replace/src/BinaryCoding.h @@ -0,0 +1,120 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Data.h" + +#include +#include +#include +#include +#include + +//win +#ifdef _MSC_VER +#define _Nonnull +#endif + +namespace TW { + +/// Encodes a 16-bit little-endian value into the provided buffer. +inline void encode16LE(uint16_t val, std::vector& data) { + data.push_back(static_cast(val)); + data.push_back(static_cast(val >> 8)); +} + +/// Decodes a 16-bit little-endian value from the provided buffer. +inline uint16_t decode16LE(const uint8_t* _Nonnull src) { + return static_cast((src[0]) | ((uint16_t)(src[1]) << 8)); +} + +/// Encodes a 32-bit little-endian value into the provided buffer. +inline void encode32LE(uint32_t val, std::vector& data) { + data.push_back(static_cast(val)); + data.push_back(static_cast((val >> 8))); + data.push_back(static_cast((val >> 16))); + data.push_back(static_cast((val >> 24))); +} + +/// Decodes a 32-bit little-endian value from the provided buffer. +inline uint32_t decode32LE(const uint8_t* _Nonnull src) { + // clang-format off + return static_cast(src[0]) + | (static_cast(src[1]) << 8) + | (static_cast(src[2]) << 16) + | (static_cast(src[3]) << 24); + // clang-format on +} + +/// Encodes a 64-bit little-endian value into the provided buffer. +void encode64LE(uint64_t val, std::vector& data); + +/// Decodes a 64-bit little-endian value from the provided buffer. +uint64_t decode64LE(const uint8_t* _Nonnull src); + +/// Returns the number of bytes it would take to serialize the provided value +/// as a variable-length integer (varint). +uint8_t varIntSize(uint64_t value); + +/// Encodes a value as a variable-length integer. +/// +/// A variable-length integer (varint) is an encoding for integers up to a max +/// value of 2^64-1 that uses a variable number of bytes depending on the value +/// being encoded. It produces fewer bytes for smaller numbers as opposed to a +/// fixed-size encoding. Little endian byte order is used. +/// +/// \returns the number of bytes written. +uint8_t encodeVarInt(uint64_t size, std::vector& data); + +/// Decodes an integer as a variable-length integer. See encodeVarInt(). +/// +/// \returns a tuple with a success indicator and the decoded integer. +std::tuple decodeVarInt(const Data& in, size_t& indexInOut); + +/// Encodes a 16-bit big-endian value into the provided buffer. +inline void encode16BE(uint16_t val, std::vector& data) { + data.push_back(static_cast(val >> 8)); + data.push_back(static_cast(val)); +} + +/// Decodes a 16-bit big-endian value from the provided buffer. +inline uint16_t decode16BE(const uint8_t* _Nonnull src) { + return static_cast((src[1]) | ((uint16_t)(src[0]) << 8)); +} + +/// Encodes a 32-bit big-endian value into the provided buffer. +inline void encode32BE(uint32_t val, std::vector& data) { + data.push_back(static_cast((val >> 24))); + data.push_back(static_cast((val >> 16))); + data.push_back(static_cast((val >> 8))); + data.push_back(static_cast(val)); +} + +/// Decodes a 32-bit big-endian value from the provided buffer. +inline uint32_t decode32BE(const uint8_t* _Nonnull src) { + // clang-format off + return static_cast(src[3]) + | (static_cast(src[2]) << 8) + | (static_cast(src[1]) << 16) + | (static_cast(src[0]) << 24); + // clang-format on +} + +/// Encodes a 64-bit big-endian value into the provided buffer. +void encode64BE(uint64_t val, std::vector& data); + +/// Decodes a 64-bit big-endian value from the provided buffer. +uint64_t decode64BE(const uint8_t* _Nonnull src); + +/// Encodes an ASCII string prefixed by the length (varInt) +void encodeString(const std::string& str, std::vector& data); + +/// Decodes an ASCII string prefixed by its length (varInt) +/// \returns a tuple with a success indicator and the decoded string. +std::tuple decodeString(const Data& in, size_t& indexInOut); + +} // namespace TW diff --git a/tools/windows-replace/src/Ethereum/ABI/ParamFactory.cpp b/tools/windows-replace/src/Ethereum/ABI/ParamFactory.cpp new file mode 100644 index 00000000000..e82ce201246 --- /dev/null +++ b/tools/windows-replace/src/Ethereum/ABI/ParamFactory.cpp @@ -0,0 +1,206 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "ParamFactory.h" +#include "ParamAddress.h" +#include "HexCoding.h" + +#include +#include + +using namespace std; +using namespace boost::algorithm; +using json = nlohmann::json; + +namespace TW::Ethereum::ABI { + +static int parseBitSize(const std::string& type) { + int size = stoi(type); + if (size < 8 || size > 256 || size % 8 != 0 || + size == 8 || size == 16 || size == 32 || size == 64 || size == 256) { + throw invalid_argument("invalid bit size"); + } + return size; +} + +static std::shared_ptr makeUInt(const std::string& type) { + auto bits = parseBitSize(type); + return make_shared(bits); +} + +static std::shared_ptr makeInt(const std::string& type) { + auto bits = parseBitSize(type); + return make_shared(bits); +} + +static bool isArrayType(const std::string& type) { + return ends_with(type, "[]") && type.length() >= 3; +} + +static std::string getArrayElemType(const std::string& arrayType) { + if (isArrayType(arrayType)) { + return arrayType.substr(0, arrayType.length() - 2); + } + return ""; +} + +std::shared_ptr ParamFactory::make(const std::string& type) { + shared_ptr param; + if (isArrayType(type)) { + auto elemType = getArrayElemType(type); + auto elemParam = make(elemType); + if (!elemParam) { + return param; + } + param = make_shared(elemParam); + } else if (type == "address") { + param = make_shared(); + } else if (type == "uint8") { + param = make_shared(); + } else if (type == "uint16") { + param = make_shared(); + } else if (type == "uint32") { + param = make_shared(); + } else if (type == "uint64") { + param = make_shared(); + } else if (type == "uint256" || type == "uint") { + param = make_shared(); + } else if (type == "int8") { + param = make_shared(); + } else if (type == "int16") { + param = make_shared(); + } else if (type == "int32") { + param = make_shared(); + } else if (type == "int64") { + param = make_shared(); + } else if (type == "int256" || type == "int") { + param = make_shared(); + } else if (starts_with(type, "uint")) { + param = makeUInt(type.substr(4, type.size() - 1)); + } else if (starts_with(type, "int")) { + param = makeInt(type.substr(3, type.size() - 1)); + } else if (type == "bool") { + param = make_shared(); + } else if (type == "bytes") { + param = make_shared(); + } else if (starts_with(type, "bytes")) { + auto bits = stoi(type.substr(5, type.size() - 1)); + param = make_shared(bits); + } else if (type == "string") { + param = make_shared(); + } + return param; +} + +std::string joinArrayElems(const std::vector& strings) { + auto array = json::array(); + for (const auto& string : strings) { + // parse to prevent quotes on simple values + auto value = json::parse(string, nullptr, false); + if (value.is_discarded()) { + // fallback + value = json(string); + } + array.push_back(value); + } + return array.dump(); +} + +std::shared_ptr ParamFactory::makeNamed(const std::string& name, const std::string& type) { + auto param = make(type); + if (!param) { + return nullptr; + } + return std::make_shared(name, param); +} + +bool ParamFactory::isPrimitive(const std::string& type) { + if (starts_with(type, "address")) { + return true; + } else if (starts_with(type, "uint")) { + return true; + } else if (starts_with(type, "int")) { + return true; + } else if (starts_with(type, "bool")) { + return true; + } else if (starts_with(type, "bytes")) { + return true; + } else if (starts_with(type, "string")) { + return true; + } + return false; +} + +std::string ParamFactory::getValue(const std::shared_ptr& param, const std::string& type) { + std::string result = ""; + if (isArrayType(type)) { + auto values = getArrayValue(param, type); + result = joinArrayElems(values); + } else if (type == "address") { + auto value = dynamic_pointer_cast(param); + result = hexEncoded(value->getData()); + } else if (type == "uint8") { + //result = boost::lexical_cast((uint)dynamic_pointer_cast(param)->getVal()); + result = boost::lexical_cast((unsigned int)dynamic_pointer_cast(param)->getVal());//win + + } else if (type == "uint16") { + result = boost::lexical_cast(dynamic_pointer_cast(param)->getVal()); + } else if (type == "uint32") { + result = boost::lexical_cast(dynamic_pointer_cast(param)->getVal()); + } else if (type == "uint64") { + result = boost::lexical_cast(dynamic_pointer_cast(param)->getVal()); + } else if (type == "uint256" || type == "uint") { + result = boost::lexical_cast(dynamic_pointer_cast(param)->getVal()); + } else if (type == "int8") { + result = boost::lexical_cast((int)dynamic_pointer_cast(param)->getVal()); + } else if (type == "int16") { + result = boost::lexical_cast(dynamic_pointer_cast(param)->getVal()); + } else if (type == "int32") { + result = boost::lexical_cast(dynamic_pointer_cast(param)->getVal()); + } else if (type == "int64") { + result = boost::lexical_cast(dynamic_pointer_cast(param)->getVal()); + } else if (type == "int256" || type == "int") { + result = boost::lexical_cast(dynamic_pointer_cast(param)->getVal()); + } else if (starts_with(type, "uint")) { + auto value = dynamic_pointer_cast(param); + result = boost::lexical_cast(value->getVal()); + } else if (starts_with(type, "int")) { + auto value = dynamic_pointer_cast(param); + result = boost::lexical_cast(value->getVal()); + } else if (type == "bool") { + auto value = dynamic_pointer_cast(param); + result = value->getVal() ? "true" : "false"; + } else if (type == "bytes") { + auto value = dynamic_pointer_cast(param); + result = hexEncoded(value->getVal()); + } else if (starts_with(type, "bytes")) { + auto value = dynamic_pointer_cast(param); + result = hexEncoded(value->getVal()); + } else if (type == "string") { + auto value = dynamic_pointer_cast(param); + result = value->getVal(); + } + return result; +} + +std::vector ParamFactory::getArrayValue(const std::shared_ptr& param, const std::string& type) { + if (!isArrayType(type)) { + return std::vector(); + } + auto array = dynamic_pointer_cast(param); + if (!array) { + return std::vector(); + } + auto elemType = getArrayElemType(type); + auto elems = array->getVal(); + std::vector values(elems.size()); + for (auto i = 0ul; i < elems.size(); ++i) { + values[i] = getValue(elems[i], elemType); + } + return values; +} + +} // namespace TW::Ethereum::ABI diff --git a/tools/windows-replace/src/Everscale/Cell.cpp b/tools/windows-replace/src/Everscale/Cell.cpp new file mode 100644 index 00000000000..8d1349937c9 --- /dev/null +++ b/tools/windows-replace/src/Everscale/Cell.cpp @@ -0,0 +1,405 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Cell.h" + +#include +#include +#include +#include + +#include "../BinaryCoding.h" + +#include +#include + +#ifdef _MSC_VER +#include +typedef SSIZE_T ssize_t; +#endif + +using namespace TW; + +namespace TW::Everscale { + +constexpr static uint32_t BOC_MAGIC = 0xb5ee9c72; + +uint16_t computeBitLen(const Data& data, bool aligned) { + auto bitLen = static_cast(data.size() * 8); + if (aligned) { + return bitLen; + } + + for (auto i = static_cast(data.size() - 1); i >= 0; --i) { + if (data[i] == 0) { + bitLen -= 8; + } else { + auto skip = 1; + uint8_t mask = 1; + while ((data[i] & mask) == 0) { + skip += 1; + mask <<= 1; + } + bitLen -= skip; + break; + } + } + return bitLen; +} + +struct Reader { + const uint8_t* _Nonnull buffer; + size_t bufferLen; + size_t offset = 0; + + explicit Reader(const uint8_t* _Nonnull buffer, size_t len) noexcept + : buffer(buffer), bufferLen(len) { + } + + void require(size_t bytes) const { + if (offset + bytes > bufferLen) { + throw std::runtime_error("unexpected eof"); + } + } + + void advance(size_t bytes) { + offset += bytes; + } + + const uint8_t* _Nonnull data() const { + return buffer + offset; + } + + size_t readNextUint(uint8_t len) { + const auto* _Nonnull p = data(); + advance(len); + + switch (len) { + case 1: + return static_cast(*p); + case 2: + return static_cast(decode16BE(p)); + case 3: + return static_cast(p[2]) | (static_cast(p[1]) << 8) | (static_cast(p[0]) << 16); + case 4: + return static_cast(decode32BE(p)); + default: + // Unreachable in valid cells + return 0; + } + } +}; + +std::shared_ptr Cell::fromBase64(const std::string& encoded) { + // `Hash::base64` trims \0 bytes from the end of the _decoded_ data, so + // raw transform is used here + using namespace boost::archive::iterators; + using It = transform_width, 8, 6>; + Data boc(It(begin(encoded)), It(end(encoded))); + return Cell::deserialize(boc.data(), boc.size()); +} + +std::shared_ptr Cell::deserialize(const uint8_t* _Nonnull data, size_t len) { + Reader reader(data, len); + + // Parse header + reader.require(6); + // 1. Magic + if (reader.readNextUint(sizeof(BOC_MAGIC)) != BOC_MAGIC) { + throw std::runtime_error("unknown magic"); + } + // 2. Flags + struct Flags { + uint8_t refSize : 3; + uint8_t : 2; // unused + bool hasCacheBits : 1; + bool hasCrc : 1; + bool indexIncluded : 1; + }; + + static_assert(sizeof(Flags) == 1, "flags must be represented as 1 byte"); + const auto flags = reinterpret_cast(reader.data())[0]; + const auto refSize = flags.refSize; + const auto offsetSize = reader.data()[1]; + reader.advance(2); + + // 3. Counters and root index + reader.require(refSize * 3 + offsetSize + refSize); + const auto cellCount = reader.readNextUint(refSize); + const auto rootCount = reader.readNextUint(refSize); + if (rootCount != 1) { + throw std::runtime_error("unsupported root count"); + } + if (rootCount > cellCount) { + throw std::runtime_error("root count is greater than cell count"); + } + const auto absent_count = reader.readNextUint(refSize); + if (absent_count > 0) { + throw std::runtime_error("absent cells are not supported"); + } + + reader.readNextUint(offsetSize); // total cell size + + const auto rootIndex = reader.readNextUint(refSize); + + // 4. Cell offsets (skip if specified) + if (flags.indexIncluded) { + reader.advance(cellCount * offsetSize); + } + + // 5. Deserialize cells + struct IntermediateCell { + uint16_t bitLen; + Data data; + std::vector references; + }; + + std::vector intermediate{}; + intermediate.reserve(cellCount); + + for (size_t i = 0; i < cellCount; ++i) { + struct Descriptor { + uint8_t refCount : 3; + bool exotic : 1; + bool storeHashes : 1; + uint8_t level : 3; + }; + + static_assert(sizeof(Descriptor) == 1, "cell descriptor must be represented as 1 byte"); + + reader.require(2); + const auto d1 = reinterpret_cast(reader.data())[0]; + if (d1.level != 0) { + throw std::runtime_error("non-zero level is not supported"); + } + if (d1.exotic) { + throw std::runtime_error("exotic cells are not supported"); + } + if (d1.refCount == 7 && d1.storeHashes) { + throw std::runtime_error("absent cells are not supported"); + } + if (d1.refCount > 4) { + throw std::runtime_error("invalid ref count"); + } + const auto d2 = reader.data()[1]; + const auto byteLen = (d2 >> 1) + (d2 & 0b1); + reader.advance(2); + + // Skip stored hashes + if (d1.storeHashes) { + reader.advance(sizeof(uint16_t) + Hash::sha256Size); + } + + reader.require(byteLen); + Data cellData(byteLen); + std::memcpy(cellData.data(), reader.data(), byteLen); + reader.advance(byteLen); + + std::vector references{}; + references.reserve(refSize * d1.refCount); + reader.require(refSize * d1.refCount); + for (size_t r = 0; r < d1.refCount; ++r) { + const auto index = reader.readNextUint(refSize); + if (index > cellCount || index <= i) { + throw std::runtime_error("invalid child index"); + } + + references.push_back(index); + } + + const auto bitLen = computeBitLen(cellData, (d2 & 0b1) == 0); + intermediate.emplace_back( + IntermediateCell{ + .bitLen = bitLen, + .data = std::move(cellData), + .references = std::move(references), + }); + } + + std::unordered_map doneCells{}; + + size_t index = cellCount; + for (auto it = intermediate.rbegin(); it != intermediate.rend(); ++it, --index) { + auto& raw = *it; + + Cell::Refs references{}; + for (size_t r = 0; r < raw.references.size(); ++r) { + const auto child = doneCells.find(raw.references[r]); + if (child == doneCells.end()) { + throw std::runtime_error("child cell not found"); + } + references[r] = child->second; + } + + auto cell = std::make_shared(raw.bitLen, std::move(raw.data), raw.references.size(), std::move(references)); + cell->finalize(); + doneCells.emplace(index - 1, cell); + } + + const auto root = doneCells.find(rootIndex); + if (root == doneCells.end()) { + throw std::runtime_error("root cell not found"); + } + return std::move(root->second); +} + +class SerializationContext { +public: + static SerializationContext build(const Cell& cell) { + SerializationContext ctx{}; + fillContext(cell, ctx); + return ctx; + } + + void encode(Data& os) const { + os.reserve(os.size() + HEADER_SIZE + cellsSize); + + const auto cellCount = static_cast(reversedCells.size()); + + // Write header + encode32BE(BOC_MAGIC, os); + os.push_back(REF_SIZE); + os.push_back(OFFSET_SIZE); + encode16BE(static_cast(cellCount), os); + encode16BE(1, os); // root count + encode16BE(0, os); // absent cell count + encode16BE(static_cast(cellsSize), os); + encode16BE(0, os); // root cell index + + // Write cells + size_t i = cellCount - 1; + while (true) { + const auto& cell = *reversedCells[i]; + + // Write cell data + const auto [d1, d2] = cell.getDescriptorBytes(); + os.push_back(d1); + os.push_back(d2); + os.insert(os.end(), cell.data.begin(), cell.data.end()); + + // Write cell references + for (const auto& child : cell.references) { + if (child == nullptr) { + break; + } + + // Map cell hash to index (which must be presented) + const auto it = indices.find(child->hash); + assert(it != indices.end()); + + encode16BE(cellCount - it->second - 1, os); + } + + if (i == 0) { + break; + } else { + --i; + } + } + } + +private: + // uint16_t will be enough for wallet transactions (e.g. 64k is the size of the whole elector) + using ref_t = uint16_t; + using offset_t = uint16_t; + + constexpr static uint8_t REF_SIZE = sizeof(ref_t); + constexpr static uint8_t OFFSET_SIZE = sizeof(offset_t); + constexpr static size_t HEADER_SIZE = + /*magic*/ sizeof(BOC_MAGIC) + + /*ref_size*/ 1 + + /*offset_size*/ 1 + + /*cell_count*/ REF_SIZE + + /*root_count*/ REF_SIZE + + /*absent_count*/ REF_SIZE + + /*data_size*/ OFFSET_SIZE + + /*root_cell_index*/ REF_SIZE; + + size_t cellsSize = 0; + ref_t index = 0; + std::map indices{}; + std::vector reversedCells{}; + + static void fillContext(const Cell& cell, SerializationContext& ctx) { + if (ctx.indices.find(cell.hash) != ctx.indices.end()) { + return; + } + + for (const auto& ref : cell.references) { + if (ref == nullptr) { + break; + } + fillContext(*ref, ctx); + } + + ctx.indices.insert(std::make_pair(cell.hash, ctx.index++)); + ctx.reversedCells.emplace_back(&cell); + ctx.cellsSize += cell.serializedSize(REF_SIZE); + } +}; + +void Cell::serialize(Data& os) const { + assert(finalized); + const auto ctx = SerializationContext::build(*this); + ctx.encode(os); +} + +void Cell::finalize() { + if (finalized) { + return; + } + + if (bitLen > Cell::MAX_BITS || refCount > Cell::MAX_REFS) { + throw std::invalid_argument("invalid cell"); + } + + // Finalize child cells and update current cell depth + // NOTE: Use before context creation to reduce stack size + for (const auto& ref : references) { + if (ref == nullptr) { + break; + } + ref->finalize(); + depth = std::max(depth, static_cast(ref->depth + 1)); + } + + // Compute cell hash + const auto dataSize = std::min(data.size(), static_cast((bitLen + 7) / 8)); + + Data normalized{}; + normalized.reserve(/* descriptor bytes */ 2 + dataSize + refCount * (sizeof(uint16_t) + Hash::sha256Size)); + + // Write descriptor bytes + const auto [d1, d2] = getDescriptorBytes(); + normalized.push_back(d1); + normalized.push_back(d2); + std::copy(data.begin(), std::next(data.begin(), static_cast(dataSize)), std::back_inserter(normalized)); + + // Write all children depths + for (const auto& ref : references) { + if (ref == nullptr) { + break; + } + encode16BE(ref->depth, normalized); + } + + // Write all children hashes + for (const auto& ref : references) { + if (ref == nullptr) { + break; + } + std::copy(ref->hash.begin(), ref->hash.end(), std::back_inserter(normalized)); + } + + // Done + const auto computedHash = Hash::sha256(normalized); + assert(computedHash.size() == Hash::sha256Size); + + std::copy(computedHash.begin(), computedHash.end(), hash.begin()); + finalized = true; +} + +} // namespace TW::Everscale diff --git a/tools/windows-replace/src/Everscale/Cell.h b/tools/windows-replace/src/Everscale/Cell.h new file mode 100644 index 00000000000..8ff98f09c28 --- /dev/null +++ b/tools/windows-replace/src/Everscale/Cell.h @@ -0,0 +1,70 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include +#include +#include + +#include + +#include "Data.h" +#include "../Hash.h" + +//win +#ifdef _MSC_VER +#define _Nonnull +#endif +namespace TW::Everscale { + +class Cell { +public: + constexpr static uint16_t MAX_BITS = 1023; + constexpr static uint8_t MAX_REFS = 4; + + using Ref = std::shared_ptr; + using Refs = std::array; + using CellHash = std::array; + + bool finalized = false; + uint16_t bitLen = 0; + std::vector data{}; + uint8_t refCount = 0; + Refs references{}; + + uint16_t depth = 0; + CellHash hash{}; + + Cell() = default; + + Cell(uint16_t bitLen, std::vector data, uint8_t refCount, Refs references) + : bitLen(bitLen), data(std::move(data)), refCount(refCount), references(std::move(references)) {} + + // Deserialize from Base64 + static std::shared_ptr fromBase64(const std::string& encoded); + + // Deserialize from BOC representation + static std::shared_ptr deserialize(const uint8_t* _Nonnull data, size_t len); + + // Serialize to binary stream + void serialize(Data& os) const; + + // Compute cell depth and hash + void finalize(); + + [[nodiscard]] inline std::pair getDescriptorBytes() const noexcept { + const uint8_t d1 = refCount; + const uint8_t d2 = (static_cast(bitLen >> 2) & 0b11111110) | (bitLen % 8 != 0); + return std::pair{d1, d2}; + } + + [[nodiscard]] inline size_t serializedSize(uint8_t refSize) const noexcept { + return 2 + (bitLen + 7) / 8 + refCount * refSize; + } +}; + +} // namespace TW::Everscale diff --git a/tools/windows-replace/src/Everscale/CellSlice.h b/tools/windows-replace/src/Everscale/CellSlice.h new file mode 100644 index 00000000000..f7446f1482f --- /dev/null +++ b/tools/windows-replace/src/Everscale/CellSlice.h @@ -0,0 +1,36 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include +#include +#include + +#include "Cell.h" +//win +#ifdef _MSC_VER +#define _Nonnull +#endif + +namespace TW::Everscale { + +class CellSlice { +public: + const Cell* _Nonnull cell; + uint16_t dataOffset = 0; + + explicit CellSlice(const Cell* _Nonnull cell) noexcept + : cell(cell) {} + + uint32_t getNextU32(); + Data getNextBytes(uint8_t bytes); + +private: + void require(uint16_t bits) const; +}; + +} // namespace TW::Everscale diff --git a/tools/windows-replace/src/HDWallet.cpp b/tools/windows-replace/src/HDWallet.cpp new file mode 100644 index 00000000000..068de7f20e5 --- /dev/null +++ b/tools/windows-replace/src/HDWallet.cpp @@ -0,0 +1,390 @@ +// Copyright © 2017-2021 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 "HDWallet.h" + +#include "Base58.h" +#include "BinaryCoding.h" +#include "Bitcoin/CashAddress.h" +#include "Bitcoin/SegwitAddress.h" +#include "Coin.h" +#include "Mnemonic.h" +#include "memory/memzero_wrapper.h" + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace TW; + +namespace { + +uint32_t fingerprint(HDNode* node, Hash::Hasher hasher); +std::string serialize(const HDNode* node, uint32_t fingerprint, uint32_t version, bool use_public, Hash::Hasher hasher); +bool deserialize(const std::string& extended, TWCurve curve, Hash::Hasher hasher, HDNode* node); +HDNode getNode(const HDWallet& wallet, TWCurve curve, const DerivationPath& derivationPath); +HDNode getMasterNode(const HDWallet& wallet, TWCurve curve); + +const char* curveName(TWCurve curve); +} // namespace + +const int MnemonicBufLength = Mnemonic::MaxWords * (BIP39_MAX_WORD_LENGTH + 3) + 20; // some extra slack + +HDWallet::HDWallet(int strength, const std::string& passphrase) + : passphrase(passphrase) { + char buf[MnemonicBufLength]; + //win + if (!random_init()) { + throw std::runtime_error("Failed to initialize random number generator"); + } + const char* mnemonic_chars = mnemonic_generate(strength, buf, MnemonicBufLength); + if (mnemonic_chars == nullptr) { + //win + random_release(); + throw std::invalid_argument("Invalid strength"); + } + mnemonic = mnemonic_chars; + TW::memzero(buf, MnemonicBufLength); + updateSeedAndEntropy(); +} + +HDWallet::HDWallet(const std::string& mnemonic, const std::string& passphrase, const bool check) + : mnemonic(mnemonic), passphrase(passphrase) { + if (mnemonic.length() == 0 || + (check && !Mnemonic::isValid(mnemonic))) { + throw std::invalid_argument("Invalid mnemonic"); + } + //win + if (!random_init()) { + throw std::runtime_error("Failed to initialize random number generator"); + } + updateSeedAndEntropy(check); +} + +HDWallet::HDWallet(const Data& entropy, const std::string& passphrase) + : passphrase(passphrase) { + char buf[MnemonicBufLength]; + const char* mnemonic_chars = mnemonic_from_data(entropy.data(), static_cast(entropy.size()), buf, MnemonicBufLength); + if (mnemonic_chars == nullptr) { + throw std::invalid_argument("Invalid mnemonic data"); + } + mnemonic = mnemonic_chars; + TW::memzero(buf, MnemonicBufLength); + //win + if (!random_init()) { + throw std::runtime_error("Failed to initialize random number generator"); + } + updateSeedAndEntropy(); +} + +HDWallet::~HDWallet() { + random_release();//win + std::fill(seed.begin(), seed.end(), 0); + std::fill(mnemonic.begin(), mnemonic.end(), 0); + std::fill(passphrase.begin(), passphrase.end(), 0); +} + +void HDWallet::updateSeedAndEntropy(bool check) { + assert(!check || Mnemonic::isValid(mnemonic)); // precondition + + // generate seed from mnemonic + mnemonic_to_seed(mnemonic.c_str(), passphrase.c_str(), seed.data(), nullptr); + + // generate entropy bits from mnemonic + Data entropyRaw((Mnemonic::MaxWords * Mnemonic::BitsPerWord) / 8); + // entropy is truncated to fully bytes, 4 bytes for each 3 words (=33 bits) + auto entropyBytes = mnemonic_to_bits(mnemonic.c_str(), entropyRaw.data()) / 33 * 4; + // copy to truncate + entropy = data(entropyRaw.data(), entropyBytes); + assert(!check || entropy.size() > 10); +} + +PrivateKey HDWallet::getMasterKey(TWCurve curve) const { + auto node = getMasterNode(*this, curve); + auto data = Data(node.private_key, node.private_key + PrivateKey::_size); + return PrivateKey(data); +} + +PrivateKey HDWallet::getMasterKeyExtension(TWCurve curve) const { + auto node = getMasterNode(*this, curve); + auto data = Data(node.private_key_extension, node.private_key_extension + PrivateKey::_size); + return PrivateKey(data); +} + +PrivateKey HDWallet::getKey(TWCoinType coin, TWDerivation derivation) const { + const auto path = TW::derivationPath(coin, derivation); + return getKey(coin, path); +} + +DerivationPath HDWallet::cardanoStakingDerivationPath(const DerivationPath& path) { + DerivationPath stakingPath = path; + stakingPath.indices[3].value = 2; + stakingPath.indices[4].value = 0; + return stakingPath; +} + +PrivateKey HDWallet::getKey(TWCoinType coin, const DerivationPath& derivationPath) const { + const auto curve = TWCoinTypeCurve(coin); + return getKeyByCurve(curve, derivationPath); +} + +PrivateKey HDWallet::getKeyByCurve(TWCurve curve, const DerivationPath& derivationPath) const { + const auto privateKeyType = PrivateKey::getType(curve); + auto node = getNode(*this, curve, derivationPath); + switch (privateKeyType) { + case TWPrivateKeyTypeCardano: { + if (derivationPath.indices.size() < 4 || derivationPath.indices[3].value > 1) { + // invalid derivation path + return PrivateKey(Data(PrivateKey::cardanoKeySize)); + } + const DerivationPath stakingPath = cardanoStakingDerivationPath(derivationPath); + + auto pkData = Data(node.private_key, node.private_key + PrivateKey::_size); + auto extData = Data(node.private_key_extension, node.private_key_extension + PrivateKey::_size); + auto chainCode = Data(node.chain_code, node.chain_code + PrivateKey::_size); + + // repeat with staking path + const auto node2 = getNode(*this, curve, stakingPath); + auto pkData2 = Data(node2.private_key, node2.private_key + PrivateKey::_size); + auto extData2 = Data(node2.private_key_extension, node2.private_key_extension + PrivateKey::_size); + auto chainCode2 = Data(node2.chain_code, node2.chain_code + PrivateKey::_size); + + TW::memzero(&node); + return PrivateKey(pkData, extData, chainCode, pkData2, extData2, chainCode2); + } + + case TWPrivateKeyTypeDefault: + default: + // default path + auto data = Data(node.private_key, node.private_key + PrivateKey::_size); + TW::memzero(&node); + return PrivateKey(data); + } +} + +std::string HDWallet::getRootKey(TWCoinType coin, TWHDVersion version) const { + const auto curve = TWCoinTypeCurve(coin); + auto node = getMasterNode(*this, curve); + return serialize(&node, 0, version, false, base58Hasher(coin)); +} + +std::string HDWallet::deriveAddress(TWCoinType coin) const { + return deriveAddress(coin, TWDerivationDefault); +} + +std::string HDWallet::deriveAddress(TWCoinType coin, TWDerivation derivation) const { + const auto derivationPath = TW::derivationPath(coin, derivation); + return TW::deriveAddress(coin, getKey(coin, derivationPath), derivation); +} + +std::string HDWallet::getExtendedPrivateKeyAccount(TWPurpose purpose, TWCoinType coin, TWDerivation derivation, TWHDVersion version, uint32_t account) const { + if (version == TWHDVersionNone) { + return ""; + } + + const auto curve = TWCoinTypeCurve(coin); + const auto path = TW::derivationPath(coin, derivation); + auto derivationPath = DerivationPath({DerivationPathIndex(purpose, true), DerivationPathIndex(path.coin(), true)}); + auto node = getNode(*this, curve, derivationPath); + auto fingerprintValue = fingerprint(&node, publicKeyHasher(coin)); + hdnode_private_ckd(&node, account + 0x80000000); + return serialize(&node, fingerprintValue, version, false, base58Hasher(coin)); +} + +std::string HDWallet::getExtendedPublicKeyAccount(TWPurpose purpose, TWCoinType coin, TWDerivation derivation, TWHDVersion version, uint32_t account) const { + if (version == TWHDVersionNone) { + return ""; + } + + const auto curve = TWCoinTypeCurve(coin); + const auto path = TW::derivationPath(coin, derivation); + auto derivationPath = DerivationPath({DerivationPathIndex(purpose, true), DerivationPathIndex(path.coin(), true)}); + auto node = getNode(*this, curve, derivationPath); + auto fingerprintValue = fingerprint(&node, publicKeyHasher(coin)); + hdnode_private_ckd(&node, account + 0x80000000); + hdnode_fill_public_key(&node); + return serialize(&node, fingerprintValue, version, true, base58Hasher(coin)); +} + +std::optional HDWallet::getPublicKeyFromExtended(const std::string& extended, TWCoinType coin, const DerivationPath& path) { + const auto curve = TW::curve(coin); + const auto hasher = TW::base58Hasher(coin); + + auto node = HDNode{}; + if (!deserialize(extended, curve, hasher, &node)) { + return {}; + } + if (node.curve->params == nullptr) { + return {}; + } + hdnode_public_ckd(&node, path.change()); + hdnode_public_ckd(&node, path.address()); + hdnode_fill_public_key(&node); + + // These public key type are not applicable. Handled above, as node.curve->params is null + assert(curve != TWCurveED25519 && curve != TWCurveED25519Blake2bNano && curve != TWCurveED25519ExtendedCardano && curve != TWCurveCurve25519); + TWPublicKeyType keyType = TW::publicKeyType(coin); + if (curve == TWCurveSECP256k1) { + auto pubkey = PublicKey(Data(node.public_key, node.public_key + 33), TWPublicKeyTypeSECP256k1); + if (keyType == TWPublicKeyTypeSECP256k1Extended) { + return pubkey.extended(); + } else { + return pubkey; + } + } else if (curve == TWCurveNIST256p1) { + auto pubkey = PublicKey(Data(node.public_key, node.public_key + 33), TWPublicKeyTypeNIST256p1); + if (keyType == TWPublicKeyTypeNIST256p1Extended) { + return pubkey.extended(); + } else { + return pubkey; + } + } + return {}; +} + +std::optional HDWallet::getPrivateKeyFromExtended(const std::string& extended, TWCoinType coin, const DerivationPath& path) { + const auto curve = TW::curve(coin); + const auto hasher = TW::base58Hasher(coin); + + auto node = HDNode{}; + if (!deserialize(extended, curve, hasher, &node)) { + return {}; + } + hdnode_private_ckd(&node, path.change()); + hdnode_private_ckd(&node, path.address()); + + return PrivateKey(Data(node.private_key, node.private_key + 32)); +} + +namespace { + +uint32_t fingerprint(HDNode* node, Hash::Hasher hasher) { + hdnode_fill_public_key(node); + auto digest = Hash::hash(hasher, node->public_key, 33); + return ((uint32_t)digest[0] << 24) + (digest[1] << 16) + (digest[2] << 8) + digest[3]; +} + +std::string serialize(const HDNode* node, uint32_t fingerprint, uint32_t version, bool use_public, Hash::Hasher hasher) { + Data node_data; + node_data.reserve(78); + + encode32BE(version, node_data); + node_data.push_back(static_cast(node->depth)); + encode32BE(fingerprint, node_data); + encode32BE(node->child_num, node_data); + node_data.insert(node_data.end(), node->chain_code, node->chain_code + 32); + if (use_public) { + node_data.insert(node_data.end(), node->public_key, node->public_key + 33); + } else { + node_data.push_back(0); + node_data.insert(node_data.end(), node->private_key, node->private_key + 32); + } + + return Base58::bitcoin.encodeCheck(node_data, hasher); +} + +bool deserialize(const std::string& extended, TWCurve curve, Hash::Hasher hasher, HDNode* node) { + TW::memzero(node); + const char* curveNameStr = curveName(curve); + if (curveNameStr == nullptr || ::strlen(curveNameStr) == 0) { + return false; + } + node->curve = get_curve_by_name(curveNameStr); + assert(node->curve != nullptr); + + const auto node_data = Base58::bitcoin.decodeCheck(extended, hasher); + if (node_data.size() != 78) { + return false; + } + + uint32_t version = decode32BE(node_data.data()); + if (TWHDVersionIsPublic(static_cast(version))) { + std::copy(node_data.begin() + 45, node_data.begin() + 45 + 33, node->public_key); + } else if (TWHDVersionIsPrivate(static_cast(version))) { + if (node_data[45]) { // invalid data + return false; + } + std::copy(node_data.begin() + 46, node_data.begin() + 46 + 32, node->private_key); + } else { + return false; // invalid version + } + node->depth = node_data[4]; + node->child_num = decode32BE(node_data.data() + 9); + std::copy(node_data.begin() + 13, node_data.begin() + 13 + 32, node->chain_code); + return true; +} + +HDNode getNode(const HDWallet& wallet, TWCurve curve, const DerivationPath& derivationPath) { + const auto privateKeyType = PrivateKey::getType(curve); + auto node = getMasterNode(wallet, curve); + for (auto& index : derivationPath.indices) { + switch (privateKeyType) { + case TWPrivateKeyTypeCardano: + hdnode_private_ckd_cardano(&node, index.derivationIndex()); + break; + case TWPrivateKeyTypeDefault: + default: + hdnode_private_ckd(&node, index.derivationIndex()); + break; + } + } + return node; +} + +HDNode getMasterNode(const HDWallet& wallet, TWCurve curve) { + const auto privateKeyType = PrivateKey::getType(curve); + HDNode node; + switch (privateKeyType) { + case TWPrivateKeyTypeCardano: { + // Derives the root Cardano HDNode from a passphrase and the entropy encoded in + // a BIP-0039 mnemonic using the Icarus derivation (V2) scheme + const auto entropy = wallet.getEntropy(); + uint8_t secret[CARDANO_SECRET_LENGTH]; + secret_from_entropy_cardano_icarus((const uint8_t*)"", 0, entropy.data(), int(entropy.size()), secret, nullptr); + hdnode_from_secret_cardano(secret, &node); + TW::memzero(secret, CARDANO_SECRET_LENGTH); + break; + } + case TWPrivateKeyTypeDefault: + default: + hdnode_from_seed(wallet.getSeed().data(), HDWallet::seedSize, curveName(curve), &node); + break; + } + return node; +} + +const char* curveName(TWCurve curve) { + switch (curve) { + case TWCurveSECP256k1: + return SECP256K1_NAME; + case TWCurveED25519: + return ED25519_NAME; + case TWCurveED25519Blake2bNano: + return ED25519_BLAKE2B_NANO_NAME; + case TWCurveED25519ExtendedCardano: + return ED25519_CARDANO_NAME; + case TWCurveNIST256p1: + return NIST256P1_NAME; + case TWCurveCurve25519: + return CURVE25519_NAME; + case TWCurveNone: + default: + return ""; + } +} + +} // namespace diff --git a/tools/windows-replace/src/Keystore/EncryptionParameters.cpp b/tools/windows-replace/src/Keystore/EncryptionParameters.cpp new file mode 100644 index 00000000000..d3e08dd6ebe --- /dev/null +++ b/tools/windows-replace/src/Keystore/EncryptionParameters.cpp @@ -0,0 +1,160 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "EncryptionParameters.h" + +#include "../Hash.h" + +#include +#include +#include +#include + +using namespace TW; + +namespace TW::Keystore { + +template +static Data computeMAC(Iter begin, Iter end, const Data& key) { + auto data = Data(); + data.reserve((end - begin) + key.size()); + data.insert(data.end(), begin, end); + append(data, key); + return Hash::keccak256(data); +} + +// ----------------- +// Encoding/Decoding +// ----------------- + +namespace CodingKeys { +static const auto encrypted = "ciphertext"; +static const auto cipher = "cipher"; +static const auto cipherParams = "cipherparams"; +static const auto kdf = "kdf"; +static const auto kdfParams = "kdfparams"; +static const auto mac = "mac"; +} // namespace CodingKeys + +EncryptionParameters::EncryptionParameters(const nlohmann::json& json) { + cipher = json[CodingKeys::cipher].get(); + cipherParams = AESParameters(json[CodingKeys::cipherParams]); + + auto kdf = json[CodingKeys::kdf].get(); + if (kdf == "scrypt") { + kdfParams = ScryptParameters(json[CodingKeys::kdfParams]); + } else if (kdf == "pbkdf2") { + kdfParams = PBKDF2Parameters(json[CodingKeys::kdfParams]); + } +} + +nlohmann::json EncryptionParameters::json() const { + nlohmann::json j; + j[CodingKeys::cipher] = cipher; + j[CodingKeys::cipherParams] = cipherParams.json(); + + if (auto* scryptParams = std::get_if(&kdfParams); scryptParams) { + j[CodingKeys::kdf] = "scrypt"; + j[CodingKeys::kdfParams] = scryptParams->json(); + } else if (auto* pbkdf2Params = std::get_if(&kdfParams); pbkdf2Params) { + j[CodingKeys::kdf] = "pbkdf2"; + j[CodingKeys::kdfParams] = pbkdf2Params->json(); + } + + return j; +} + +EncryptedPayload::EncryptedPayload(const Data& password, const Data& data, const EncryptionParameters& params) + : params(std::move(params)), _mac() { + auto scryptParams = std::get(this->params.kdfParams); + auto derivedKey = Data(scryptParams.desiredKeyLength); + scrypt(reinterpret_cast(password.data()), password.size(), scryptParams.salt.data(), + scryptParams.salt.size(), scryptParams.n, scryptParams.r, scryptParams.p, derivedKey.data(), + scryptParams.desiredKeyLength); + + aes_encrypt_ctx ctx; + auto result = aes_encrypt_key128(derivedKey.data(), &ctx); + assert(result == EXIT_SUCCESS); + if (result == EXIT_SUCCESS) { + Data iv = this->params.cipherParams.iv; + encrypted = Data(data.size()); + aes_ctr_encrypt(data.data(), encrypted.data(), static_cast(data.size()), iv.data(), aes_ctr_cbuf_inc, &ctx); + + _mac = computeMAC(derivedKey.end() - 16, derivedKey.end(), encrypted); + } +} + +EncryptedPayload::~EncryptedPayload() { + std::fill(encrypted.begin(), encrypted.end(), 0); + std::fill(_mac.begin(), _mac.end(), 0); +} + +Data EncryptedPayload::decrypt(const Data& password) const { + auto derivedKey = Data(); + auto mac = Data(); + + if (auto* scryptParams = std::get_if(¶ms.kdfParams); scryptParams) { + derivedKey.resize(scryptParams->defaultDesiredKeyLength); + scrypt(password.data(), password.size(), scryptParams->salt.data(), + scryptParams->salt.size(), scryptParams->n, scryptParams->r, scryptParams->p, derivedKey.data(), + scryptParams->defaultDesiredKeyLength); + mac = computeMAC(derivedKey.end() - 16, derivedKey.end(), encrypted); + } else if (auto* pbkdf2Params = std::get_if(¶ms.kdfParams); pbkdf2Params) { + derivedKey.resize(pbkdf2Params->defaultDesiredKeyLength); + pbkdf2_hmac_sha256(password.data(), static_cast(password.size()), pbkdf2Params->salt.data(), + static_cast(pbkdf2Params->salt.size()), pbkdf2Params->iterations, derivedKey.data(), + pbkdf2Params->defaultDesiredKeyLength); + mac = computeMAC(derivedKey.end() - 16, derivedKey.end(), encrypted); + } else { + throw DecryptionError::unsupportedKDF; + } + + if (mac != _mac) { + throw DecryptionError::invalidPassword; + } + + Data decrypted(encrypted.size()); + Data iv = params.cipherParams.iv; + if (params.cipher == "aes-128-ctr") { + aes_encrypt_ctx ctx; + //win + //auto __attribute__((unused)) result = aes_encrypt_key(derivedKey.data(), 16, &ctx); + [[maybe_unused]] auto result = aes_encrypt_key(derivedKey.data(), 16, &ctx); + assert(result != EXIT_FAILURE); + + aes_ctr_decrypt(encrypted.data(), decrypted.data(), static_cast(encrypted.size()), iv.data(), + aes_ctr_cbuf_inc, &ctx); + } else if (params.cipher == "aes-128-cbc") { + aes_decrypt_ctx ctx; + //win + //auto __attribute__((unused)) result = aes_decrypt_key(derivedKey.data(), 16, &ctx); + [[maybe_unused]] auto result = aes_decrypt_key(derivedKey.data(), 16, &ctx); + assert(result != EXIT_FAILURE); + + for (auto i = 0ul; i < encrypted.size(); i += 16) { + aes_cbc_decrypt(encrypted.data() + i, decrypted.data() + i, 16, iv.data(), &ctx); + } + } else { + throw DecryptionError::unsupportedCipher; + } + + return decrypted; +} + +EncryptedPayload::EncryptedPayload(const nlohmann::json& json) { + params = EncryptionParameters(json); + encrypted = parse_hex(json[CodingKeys::encrypted].get()); + _mac = parse_hex(json[CodingKeys::mac].get()); +} + +nlohmann::json EncryptedPayload::json() const { + nlohmann::json j = params.json(); + j[CodingKeys::encrypted] = hex(encrypted); + j[CodingKeys::mac] = hex(_mac); + return j; +} + +} // namespace TW::Keystore diff --git a/tools/windows-replace/src/NULS/Address.cpp b/tools/windows-replace/src/NULS/Address.cpp new file mode 100644 index 00000000000..494131dcb1e --- /dev/null +++ b/tools/windows-replace/src/NULS/Address.cpp @@ -0,0 +1,85 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +#include "Address.h" + +#include + +#include "../Base58.h" +#include "../BinaryCoding.h" +#include "../HexCoding.h" + +using namespace TW; + +namespace TW::NULS { + +const std::string Address::prefix("NULSd"); +const std::array Address::mainnetId = {0x01, 0x00}; + +bool Address::isValid(const std::string& string) { + if (string.empty()) { + return false; + } + if (string.length() <= prefix.length()) { + return false; + } + + std::string address = string.substr(prefix.length(), string.length() - prefix.length()); + Data decoded = Base58::bitcoin.decode(address); + if (decoded.size() != size) { + return false; + } + + // Check Xor + uint8_t checkSum = 0x00; + for (int i = 0; i < 23; ++i) { + checkSum ^= decoded[i]; + } + + return decoded[23] == checkSum; +} + +Address::Address(const TW::PublicKey& publicKey) { + // Main-Net chainID + bytes[0] = mainnetId[0]; + bytes[1] = mainnetId[1]; + // Address Type + bytes[2] = addressType; + //ecdsa_get_pubkeyhash(publicKey.bytes.data(), HASHER_SHA2_RIPEMD, bytes.begin() + 3); + ecdsa_get_pubkeyhash(publicKey.bytes.data(), HASHER_SHA2_RIPEMD, &bytes[3]);//win + bytes[23] = checksum(bytes); +} + +Address::Address(const std::string& string) { + if (false == isValid(string)) { + throw std::invalid_argument("Invalid address string"); + } + std::string address = string.substr(prefix.length(), string.length() - prefix.length()); + const auto decoded = Base58::bitcoin.decode(address); + std::copy(decoded.begin(), decoded.end(), bytes.begin()); +} + +uint16_t Address::chainID() const { + return decode16LE(bytes.data()); +} + +uint8_t Address::type() const { + return bytes[2]; +} + +std::string Address::string() const { + return prefix + Base58::bitcoin.encode(&bytes[0], &bytes[0] + bytes.size());//win + //return prefix + Base58::bitcoin.encode(bytes.begin(), bytes.end()); +} + +uint8_t Address::checksum(std::array& byteArray) const { + uint8_t checkSum = 0x00; + for (int i = 0; i < 23; ++i) { + checkSum ^= byteArray[i]; + } + return checkSum; +} + +} // namespace TW::NULS diff --git a/tools/windows-replace/src/interface/TWBitcoinScript.cpp b/tools/windows-replace/src/interface/TWBitcoinScript.cpp new file mode 100644 index 00000000000..8b685973143 --- /dev/null +++ b/tools/windows-replace/src/interface/TWBitcoinScript.cpp @@ -0,0 +1,165 @@ +// Copyright © 2017-2021 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include + +#include "../Bitcoin/Script.h" +#include "../Bitcoin/SigHashType.h" + +#include + +struct TWBitcoinScript *_Nonnull TWBitcoinScriptCreate() { + auto* script = new TWBitcoinScript{}; + return script; +} + +struct TWBitcoinScript *TWBitcoinScriptCreateWithData(TWData *data) { + auto* script = new TWBitcoinScript{}; + script->impl.bytes.resize(TWDataSize(data)); + TWDataCopyBytes(data, 0, TWDataSize(data), script->impl.bytes.data()); + return script; +} + +struct TWBitcoinScript *_Nonnull TWBitcoinScriptCreateWithBytes(uint8_t *_Nonnull bytes, size_t size) { + auto* script = new TWBitcoinScript{}; + std::copy(bytes, bytes + size, std::back_inserter(script->impl.bytes)); + return script; +} + +struct TWBitcoinScript *TWBitcoinScriptCreateCopy(const struct TWBitcoinScript *script) { + auto* newScript = new TWBitcoinScript{}; + newScript->impl.bytes = script->impl.bytes; + return newScript; +} + +void TWBitcoinScriptDelete(struct TWBitcoinScript *script) { + delete script; +} + +size_t TWBitcoinScriptSize(const struct TWBitcoinScript *script) { + return script->impl.bytes.size(); +} + +TWData *TWBitcoinScriptData(const struct TWBitcoinScript *script) { + return TWDataCreateWithBytes(&script->impl.bytes[0], script->impl.bytes.size()); +} + +TWData *TWBitcoinScriptScriptHash(const struct TWBitcoinScript *_Nonnull script) { + auto result = script->impl.hash(); + return TWDataCreateWithBytes(result.data(), result.size()); +} + +bool TWBitcoinScriptIsPayToScriptHash(const struct TWBitcoinScript *script) { + return script->impl.isPayToScriptHash(); +} + +bool TWBitcoinScriptIsPayToWitnessScriptHash(const struct TWBitcoinScript *script) { + return script->impl.isPayToWitnessScriptHash(); +} + +bool TWBitcoinScriptIsPayToWitnessPublicKeyHash(const struct TWBitcoinScript *script) { + return script->impl.isPayToWitnessPublicKeyHash(); +} + +bool TWBitcoinScriptIsWitnessProgram(const struct TWBitcoinScript *script) { + return script->impl.isWitnessProgram(); +} + +bool TWBitcoinScriptEqual(const struct TWBitcoinScript *_Nonnull lhs, const struct TWBitcoinScript *_Nonnull rhs) { + return lhs->impl.bytes == rhs->impl.bytes; +} + +TWData *TWBitcoinScriptMatchPayToPubkey(const struct TWBitcoinScript *script) { + std::vector data; + if (script->impl.matchPayToPublicKey(data)) { + return TWDataCreateWithBytes(data.data(), data.size()); + } + return nullptr; +} + +TWData *TWBitcoinScriptMatchPayToPubkeyHash(const struct TWBitcoinScript *script) { + std::vector data; + if (script->impl.matchPayToPublicKeyHash(data)) { + return TWDataCreateWithBytes(data.data(), data.size()); + } + return nullptr; +} + +TWData *_Nullable TWBitcoinScriptMatchPayToScriptHash(const struct TWBitcoinScript *script) { + std::vector data; + if (script->impl.matchPayToScriptHash(data)) { + return TWDataCreateWithBytes(data.data(), data.size()); + } + return nullptr; +} + +TWData *_Nullable TWBitcoinScriptMatchPayToWitnessPublicKeyHash(const struct TWBitcoinScript *script) { + std::vector data; + if (script->impl.matchPayToWitnessPublicKeyHash(data)) { + return TWDataCreateWithBytes(data.data(), data.size()); + } + return nullptr; +} + +TWData *_Nullable TWBitcoinScriptMatchPayToWitnessScriptHash(const struct TWBitcoinScript *script) { + std::vector data; + if (script->impl.matchPayToWitnessScriptHash(data)) { + return TWDataCreateWithBytes(data.data(), data.size()); + } + return nullptr; +} + +TWData *TWBitcoinScriptEncode(const struct TWBitcoinScript *script) { + auto result = std::vector{}; + script->impl.encode(result); + return TWDataCreateWithBytes(result.data(), result.size()); +} + +struct TWBitcoinScript *TWBitcoinScriptBuildPayToPublicKey(TWData *pubkey) { + auto* v = reinterpret_cast*>(pubkey); + auto script = TW::Bitcoin::Script::buildPayToPublicKey(*v); + return new TWBitcoinScript{ script };//win + //return new TWBitcoinScript{ .impl = script }; +} + +struct TWBitcoinScript *TWBitcoinScriptBuildPayToPublicKeyHash(TWData *hash) { + auto* v = reinterpret_cast*>(hash); + auto script = TW::Bitcoin::Script::buildPayToPublicKeyHash(*v); + return new TWBitcoinScript{ script };//win + //return new TWBitcoinScript{ .impl = script }; +} + +struct TWBitcoinScript *TWBitcoinScriptBuildPayToScriptHash(TWData *scriptHash) { + auto* v = reinterpret_cast*>(scriptHash); + auto script = TW::Bitcoin::Script::buildPayToScriptHash(*v); + return new TWBitcoinScript{ script };//win + //return new TWBitcoinScript{ .impl = script }; +} + +struct TWBitcoinScript *TWBitcoinScriptBuildPayToWitnessPubkeyHash(TWData *hash) { + auto* v = reinterpret_cast*>(hash); + auto script = TW::Bitcoin::Script::buildPayToWitnessPublicKeyHash(*v); + return new TWBitcoinScript{ script };//win + //return new TWBitcoinScript{ .impl = script }; +} + +struct TWBitcoinScript *TWBitcoinScriptBuildPayToWitnessScriptHash(TWData *scriptHash) { + auto* v = reinterpret_cast*>(scriptHash); + auto script = TW::Bitcoin::Script::buildPayToWitnessScriptHash(*v); + return new TWBitcoinScript{ script };//win + //return new TWBitcoinScript{ .impl = script }; +} + +struct TWBitcoinScript *_Nonnull TWBitcoinScriptLockScriptForAddress(TWString *_Nonnull address, enum TWCoinType coin) { + auto* s = reinterpret_cast(address); + auto script = TW::Bitcoin::Script::lockScriptForAddress(*s, coin); + //return new TWBitcoinScript{ .impl = script }; + return new TWBitcoinScript{ script };//win +} + +uint32_t TWBitcoinScriptHashTypeForCoin(enum TWCoinType coinType) { + return TW::Bitcoin::hashTypeForCoin(coinType); +} diff --git a/tools/windows-replace/tests/CMakeLists.txt b/tools/windows-replace/tests/CMakeLists.txt new file mode 100644 index 00000000000..fbeed4ace8a --- /dev/null +++ b/tools/windows-replace/tests/CMakeLists.txt @@ -0,0 +1,69 @@ +# 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. + +enable_testing() + +# Prevent overriding the parent project's compiler/linker +# settings on Windows +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + +if(WIN32) + find_package(GTest REQUIRED) +else() + # Add googletest directly to our build. This defines + # the gtest and gtest_main targets. + add_subdirectory(${CMAKE_SOURCE_DIR}/build/local/src/gtest/googletest-release-1.11.0 + ${CMAKE_CURRENT_BINARY_DIR}/googletest-build + EXCLUDE_FROM_ALL) + set(GTEST_LIBRARIES gtest) + set(GTEST_MAIN_LIBRARIES gtest_main) +endif() + +# Note: Protobuf is defined in included CMake +##find_library(Protobuf REQUIRED PATH ${CMAKE_SOURCE_DIR}/build/local/lib/pkgconfig NO_DEFAULT_PATH) +##include_directories(${Protobuf_INCLUDE_DIRS}) + +# Test executable +file(GLOB_RECURSE test_sources *.cpp **/*.cpp **/*.cc) +if("${CMAKE_CXX_COMPILER_ID}" MATCHES "MSVC") + list(FILTER test_sources EXCLUDE REGEX ".*CMakeCXXCompilerId\\.cpp$") +endif() + +add_executable(tests ${test_sources}) +target_link_libraries(tests ${GTEST_MAIN_LIBRARIES} TrezorCrypto TrustWalletCore walletconsolelib ${Protobuf_LIBRARIES} Boost::boost) +target_include_directories(tests PRIVATE ${CMAKE_SOURCE_DIR}/src) +target_include_directories(tests PRIVATE ${CMAKE_SOURCE_DIR}/tests/common) + +if(NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "MSVC")) + target_compile_options(tests PRIVATE "-Wall") +endif() + +if (NOT ANDROID AND TW_UNITY_BUILD) + set_target_properties(tests PROPERTIES UNITY_BUILD ON) +endif() + +set_target_properties(tests + PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON +) + +option(CODE_COVERAGE "Enable coverage reporting" OFF) +if(CODE_COVERAGE AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + # Add required flags (GCC & LLVM/Clang) + target_compile_options(tests INTERFACE + -O0 # no optimization + -g # generate debug info + --coverage # sets all required flags + ) + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.13) + target_link_options(tests INTERFACE --coverage) + else() + target_link_libraries(tests INTERFACE --coverage) + endif() +endif() + +add_test(NAME example_test COMMAND tests) diff --git a/tools/windows-replace/tests/chains/Aptos/MoveTypesTests.cpp b/tools/windows-replace/tests/chains/Aptos/MoveTypesTests.cpp new file mode 100644 index 00000000000..45c7ec321e7 --- /dev/null +++ b/tools/windows-replace/tests/chains/Aptos/MoveTypesTests.cpp @@ -0,0 +1,60 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include +#include + +namespace TW::Aptos::tests { + +TEST(AptosMoveTypes, ModuleId) { + ModuleId module(gAddressOne, "coin"); + ASSERT_EQ(module.address(), gAddressOne); + ASSERT_EQ(module.name(), "coin"); + ASSERT_EQ(hex(module.accessVector()), "00000000000000000000000000000000000000000000000000000000000000000104636f696e"); + ASSERT_EQ(module.string(), "0x0000000000000000000000000000000000000000000000000000000000000001::coin"); + ASSERT_EQ(module.shortString(), "0x1::coin"); +} + +/* +TEST(AptosMoveTypes, StructTag) { + auto functorTest = [](T value, const std::string expectedHex) { + TypeTag t{.tags = value}; + StructTag st(gAddressOne, "abc", "abc", std::vector{{t}}); + ASSERT_EQ(st.moduleID().name(), "abc"); + ASSERT_EQ(st.moduleID().address(), gAddressOne); + ASSERT_EQ(hex(st.serialize()), expectedHex); + }; + functorTest(Bool{}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630100"); + functorTest(U8{}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630101"); + functorTest(U64{}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630102"); + functorTest(U128{}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630103"); + functorTest(TAddress{}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630104"); + functorTest(TSigner{}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630105"); + functorTest(Vector{.tags = std::vector{{TypeTag{.tags = U8{}}}}}, "0100000000000000000000000000000000000000000000000000000000000000010361626303616263010601"); + StructTag stInner(gAddressOne, "foo", "bar", std::vector{{U8{}}}); + functorTest(TStructTag{stInner}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630107000000000000000000000000000000000000000000000000000000000000000103666f6f036261720101"); +} +*/ +TEST(AptosMoveTypes, TypeTagDisplay) { + auto functorTest = [](const TypeTag &value, const std::string& expected) { + ASSERT_EQ(TypeTagToString(value), expected); + }; + functorTest(TypeTag{.tags = Bool{}}, "bool"); + functorTest(TypeTag{.tags = U8{}}, "u8"); + functorTest(TypeTag{.tags = U64{}}, "u64"); + functorTest(TypeTag{.tags = U128{}}, "u128"); + functorTest(TypeTag{.tags = TAddress{}}, "address"); + functorTest(TypeTag{.tags = TSigner{}}, "signer"); + TypeTag t{.tags = TypeTag::TypeTagVariant(Vector{.tags = {{U8{}}}})}; + functorTest(t, "vector"); + StructTag st(gAddressOne, "foo", "bar", std::vector{{U8{}}}); + TypeTag anotherT{.tags = TypeTag::TypeTagVariant(st)}; + functorTest(anotherT, "0x1::foo::bar"); + functorTest(gTransferTag, "0x1::aptos_coin::AptosCoin"); +} + +} // namespace TW::Aptos::tests diff --git a/tools/windows-replace/tests/chains/Bitcoin/MessageSignerTests.cpp b/tools/windows-replace/tests/chains/Bitcoin/MessageSignerTests.cpp new file mode 100644 index 00000000000..df617721300 --- /dev/null +++ b/tools/windows-replace/tests/chains/Bitcoin/MessageSignerTests.cpp @@ -0,0 +1,167 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Bitcoin/MessageSigner.h" +#include +#include "Bitcoin/Address.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "Base64.h" +#include "Coin.h" +#include "Data.h" +#include "TestUtilities.h" + +#include +#include + +#include +#include + +namespace TW::Bitcoin::MessageSignerTests { + +const auto gPrivateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + +TEST(BitcoinMessageSigner, VerifyMessage) { + EXPECT_TRUE(MessageSigner::verifyMessage( + "1B8Qea79tsxmn4dTiKKRVvsJpHwL2fMQnr", + "test signature", + "H+3L5IbSVcejp4S2VwLXCxLEMQAWDvKbE8lQyq0ocdvyM1aoEudkzN/S/qLI3vnNOFY6V13BXWSFrPr3OjGa5Dk=" + )); + EXPECT_TRUE(MessageSigner::verifyMessage( + "1HZwkjkeaoZfTSaJxDw6aKkxp45agDiEzN", + "This is an example of a signed message.", + "G39Qf0XrZHICWbz3r5gOkcgTRw3vM4leGjiR3refr/K1OezcKmmXaLn4zc8ji2rjbBUIMrIhH/jc5Z2qEEz7qVk=" + )); + EXPECT_TRUE(MessageSigner::verifyMessage( + "1H8X4u6CVZRTLLNbUQTKAnc5vCkqWMpwfF", + "compressed key", + "IKUI9v2xbHogJe8HKXI2M5KEhMKaW6fjNxtyEy27Mf+3/e1ht4jZoc85e4F8stPsxt4Xcg8Yr42S28O6L/Qx9fE=" + )); + EXPECT_TRUE(MessageSigner::verifyMessage( + "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X", + "test signature", + "ILH5K7JQLaRGaKGXXH5mYM6FIIy9IWyY4JUPI+PHYY4WaupxUbg+zy0bhBCrDuehy9x4WidwjkRR1GSLnWvOXBo=" + )); + EXPECT_TRUE(MessageSigner::verifyMessage( + "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X", + "another text", + "H7vrF2C+TlFiHyegAw3QLv6SK0myuEEXUOgfx0+Qio1YVDuSa6p/OHpoQVlUt3F8QJdbdZN9M1h/fYEAnEz16V0=" + )); + EXPECT_TRUE(MessageSigner::verifyMessage( + "1E4T9JZ3mq6cdgiRJEWzHqDXb9t322fE6d", + "test signature", + "HLH5K7JQLaRGaKGXXH5mYM6FIIy9IWyY4JUPI+PHYY4WaupxUbg+zy0bhBCrDuehy9x4WidwjkRR1GSLnWvOXBo=" + )); +} + +TEST(BitcoinMessageSigner, SignAndVerify) { + const auto pubKey = gPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(pubKey.bytes), "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); + const auto address = Address(pubKey, TW::p2pkhPrefix(TWCoinTypeBitcoin)).string(); + EXPECT_EQ(address, "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X"); + + { + const auto msg = "test signature"; + const auto signature = MessageSigner::signMessage(gPrivateKey, address, msg); + EXPECT_EQ(signature, "ILH5K7JQLaRGaKGXXH5mYM6FIIy9IWyY4JUPI+PHYY4WaupxUbg+zy0bhBCrDuehy9x4WidwjkRR1GSLnWvOXBo="); + + EXPECT_TRUE(MessageSigner::verifyMessage(address, msg, signature)); + } + { + const auto msg = "another text"; + const auto signature = MessageSigner::signMessage(gPrivateKey, address, msg); + EXPECT_EQ(signature, "H7vrF2C+TlFiHyegAw3QLv6SK0myuEEXUOgfx0+Qio1YVDuSa6p/OHpoQVlUt3F8QJdbdZN9M1h/fYEAnEz16V0="); + + EXPECT_TRUE(MessageSigner::verifyMessage(address, msg, signature)); + } + + // uncompressed + const auto pubKeyUncomp = gPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + const auto keyHash = pubKeyUncomp.hash(Data{TW::p2pkhPrefix(TWCoinTypeBitcoin)}, Hash::HasherSha256ripemd); + const auto addressUncomp = Address(keyHash).string(); + EXPECT_EQ(addressUncomp, "1E4T9JZ3mq6cdgiRJEWzHqDXb9t322fE6d"); + { + const auto msg = "test signature"; + const auto signature = MessageSigner::signMessage(gPrivateKey, addressUncomp, msg, false); + EXPECT_EQ(signature, "HLH5K7JQLaRGaKGXXH5mYM6FIIy9IWyY4JUPI+PHYY4WaupxUbg+zy0bhBCrDuehy9x4WidwjkRR1GSLnWvOXBo="); + + EXPECT_TRUE(MessageSigner::verifyMessage(addressUncomp, msg, signature)); + } +} + +TEST(BitcoinMessageSigner, SignNegative) { + const auto address = Address(gPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1), TW::p2pkhPrefix(TWCoinTypeBitcoin)).string(); + EXPECT_EQ(address, "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X"); + const auto msg = "test signature"; + // Use invalid address + EXPECT_EXCEPTION(MessageSigner::signMessage(gPrivateKey, "__THIS_IS_NOT_A_VALID_ADDRESS__", msg), "Address is not valid (legacy) address"); + // Use invalid address, not legacy + EXPECT_EXCEPTION(MessageSigner::signMessage(gPrivateKey, "bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8", msg), "Address is not valid (legacy) address"); + // Use valid, but not matching key + EXPECT_EXCEPTION(MessageSigner::signMessage(gPrivateKey, "1B8Qea79tsxmn4dTiKKRVvsJpHwL2fMQnr", msg), "Address does not match key"); +} + +TEST(BitcoinMessageSigner, VerifyNegative) { + const auto sig = parse_hex("1fedcbe486d255c7a3a784b65702d70b12c43100160ef29b13c950caad2871dbf23356a812e764ccdfd2fea2c8def9cd38563a575dc15d6485acfaf73a319ae439"); + // Baseline positive + EXPECT_TRUE(MessageSigner::verifyMessage( + "1B8Qea79tsxmn4dTiKKRVvsJpHwL2fMQnr", + "test signature", + sig + )); + + // Provide non-matching address + EXPECT_FALSE(MessageSigner::verifyMessage( + "1HZwkjkeaoZfTSaJxDw6aKkxp45agDiEzN", + "test signature", + sig + )); + // Signature too short + EXPECT_EXCEPTION(MessageSigner::verifyMessage( + "1HZwkjkeaoZfTSaJxDw6aKkxp45agDiEzN", + "test signature", + parse_hex("1fedcbe486d255c7a3a784b65702d70b12c43100160ef29b13c950caad2871dbf23356a812e764ccdfd2fea2c8def9cd38563a575dc15d6485acfaf73a319ae4") + ), "signature too short"); + // Invalid address + EXPECT_EXCEPTION(MessageSigner::verifyMessage( + "__THIS_IS_NOT_A_VALID_ADDRESS__", + "test signature", + parse_hex("1fedcbe486d255c7a3a784b65702d70b12c43100160ef29b13c950caad2871dbf23356a812e764ccdfd2fea2c8def9cd38563a575dc15d6485acfaf73a319ae4") + ), "Input address invalid"); + EXPECT_EXCEPTION(MessageSigner::verifyMessage( + "bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8", + "test signature", + parse_hex("1fedcbe486d255c7a3a784b65702d70b12c43100160ef29b13c950caad2871dbf23356a812e764ccdfd2fea2c8def9cd38563a575dc15d6485acfaf73a319ae4") + ), "Input address invalid"); +} + +TEST(BitcoinMessageSigner, MessageToHash) { + EXPECT_EQ(hex(MessageSigner::messageToHash("Hello, world!")), "02d6c0643e40b0db549cbbd7eb47dcab71a59d7017199ebde6b272f28fbbf95f"); + EXPECT_EQ(hex(MessageSigner::messageToHash("test signature")), "8e81cc5bca9862d8b7f22be1f7cb762b49121cf4e1611c27906a041f9a9eb21f"); +} + +TEST(TWBitcoinMessageSigner, VerifyMessage) { + EXPECT_TRUE(TWBitcoinMessageSignerVerifyMessage( + STRING("1B8Qea79tsxmn4dTiKKRVvsJpHwL2fMQnr").get(), + STRING("test signature").get(), + STRING("H+3L5IbSVcejp4S2VwLXCxLEMQAWDvKbE8lQyq0ocdvyM1aoEudkzN/S/qLI3vnNOFY6V13BXWSFrPr3OjGa5Dk=").get() + )); +} + +TEST(TWBitcoinMessageSigner, SignAndVerify) { + const auto privKeyData = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get())); + const auto address = STRING("19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X"); + const auto message = STRING("test signature"); + + const auto signature = WRAPS(TWBitcoinMessageSignerSignMessage(privateKey.get(), address.get(), message.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(signature.get())), "ILH5K7JQLaRGaKGXXH5mYM6FIIy9IWyY4JUPI+PHYY4WaupxUbg+zy0bhBCrDuehy9x4WidwjkRR1GSLnWvOXBo="); + + EXPECT_TRUE(TWBitcoinMessageSignerVerifyMessage(address.get(), message.get(), signature.get())); +} + +} // namespace TW::Bitcoin diff --git a/tools/windows-replace/tests/chains/Bitcoin/TestUtilities.h b/tools/windows-replace/tests/chains/Bitcoin/TestUtilities.h new file mode 100644 index 00000000000..78c0d89cb39 --- /dev/null +++ b/tools/windows-replace/tests/chains/Bitcoin/TestUtilities.h @@ -0,0 +1,88 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include +#include + +#include +#include +#include + +#include + +#define WRAP(type, x) std::shared_ptr(x, type##Delete) +#define WRAPD(x) std::shared_ptr(x, TWDataDelete) +#define WRAPS(x) std::shared_ptr(x, TWStringDelete) +#define STRING(x) std::shared_ptr(TWStringCreateWithUTF8Bytes(x), TWStringDelete) +#define DATA(x) std::shared_ptr(TWDataCreateWithHexString(STRING(x).get()), TWDataDelete) + +inline void assertStringsEqual(const std::shared_ptr& string, const char* expected) { + ASSERT_STREQ(TWStringUTF8Bytes(string.get()), expected); +} + +inline void assertHexEqual(const std::shared_ptr& data, const char* expected) { + auto hex = WRAPS(TWStringCreateWithHexData(data.get())); + assertStringsEqual(hex, expected); +} + + +inline void assertJSONEqual(const nlohmann::json& lhs, const nlohmann::json& rhs) { + ASSERT_EQ(lhs, rhs); +} + +inline void assertJSONEqual(const std::string& lhs, const char* expected) { + auto lhsJson = nlohmann::json::parse(lhs); + auto rhsJson = nlohmann::json::parse(std::string(expected)); + return assertJSONEqual(lhsJson, rhsJson); +} + +inline std::vector* dataFromTWData(TWData* data) { + return const_cast*>(reinterpret_cast*>(data)); +} + +/// Return a writable temp dir which can be used to create files during testing +std::string getTestTempDir(void); + +#define ANY_SIGN(input, coin) \ + {\ + auto inputData = input.SerializeAsString();\ + auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData.data(), inputData.size()));\ + auto outputTWData = WRAPD(TWAnySignerSign(inputTWData.get(), coin));\ + output.ParseFromArray(TWDataBytes(outputTWData.get()), static_cast(TWDataSize(outputTWData.get())));\ + } +#define ANY_PLAN(input, output, coin) \ + {\ + auto inputData = input.SerializeAsString();\ + auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData.data(), inputData.size()));\ + auto outputTWData = WRAPD(TWAnySignerPlan(inputTWData.get(), coin));\ + output.ParseFromArray(TWDataBytes(outputTWData.get()), static_cast(TWDataSize(outputTWData.get())));\ + } +#define DUMP_PROTO(input) \ + { \ + std::string json; \ + google::protobuf::util::MessageToJsonString(input, &json); \ + std::cout<<"dump proto: "< + +#include "Cbor.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "uint256.h" +#include "TestUtilities.h" +#include + +#include +#include + +#ifdef _MSC_VER + typedef unsigned int uint; +#endif + +using namespace TW; +using namespace std; + +namespace TW::Cardano::SigningTests { + +const auto privateKeyTest1 = "089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e"; +const auto ownAddress1 = "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23"; +const auto sundaeTokenPolicy = "9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77"; + +TEST(CardanoSigning, SelectInputs) { + const auto inputs = std::vector({ + TxInput{{parse_hex("0001"), 0}, "ad01", 700, {}}, + TxInput{{parse_hex("0002"), 1}, "ad02", 900, {}}, + TxInput{{parse_hex("0003"), 2}, "ad03", 300, {}}, + TxInput{{parse_hex("0004"), 3}, "ad04", 600, {}}, + }); + + { // 2 + const auto s1 = Signer::selectInputsWithTokens(inputs, 1500, {}); + ASSERT_EQ(s1.size(), 2ul); + EXPECT_EQ(s1[0].amount, 900ul); + EXPECT_EQ(s1[1].amount, 700ul); + } + { // all + const auto s1 = Signer::selectInputsWithTokens(inputs, 10000, {}); + ASSERT_EQ(s1.size(), 4ul); + EXPECT_EQ(s1[0].amount, 900ul); + EXPECT_EQ(s1[1].amount, 700ul); + EXPECT_EQ(s1[2].amount, 600ul); + EXPECT_EQ(s1[3].amount, 300ul); + } + { // 3 + const auto s1 = Signer::selectInputsWithTokens(inputs, 2000, {}); + ASSERT_EQ(s1.size(), 3ul); + } + { // 1 + const auto s1 = Signer::selectInputsWithTokens(inputs, 500, {}); + ASSERT_EQ(s1.size(), 1ul); + } + { // at least 0 is returned + const auto s1 = Signer::selectInputsWithTokens(inputs, 0, {}); + ASSERT_EQ(s1.size(), 1ul); + } +} + +Proto::SigningInput createSampleInput(uint64_t amount, int utxoCount = 10, + const std::string& alternateToAddress = "", bool omitPrivateKey = false) { + const std::string toAddress = (alternateToAddress.length() > 0) ? alternateToAddress : "addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5"; + + Proto::SigningInput input; + if (utxoCount >= 1) { + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767"); + utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_output_index(1); + utxo1->set_address(ownAddress1); + utxo1->set_amount(1500000); + } + if (utxoCount >= 2) { + auto* utxo2 = input.add_utxos(); + const auto txHash2 = parse_hex("554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af0"); + utxo2->mutable_out_point()->set_tx_hash(txHash2.data(), txHash2.size()); + utxo2->mutable_out_point()->set_output_index(0); + utxo2->set_address(ownAddress1); + utxo2->set_amount(6500000); + } + + if (!omitPrivateKey) { + const auto privateKeyData = parse_hex(privateKeyTest1); + input.add_private_key(privateKeyData.data(), privateKeyData.size()); + } + input.mutable_transfer_message()->set_to_address(toAddress); + input.mutable_transfer_message()->set_change_address(ownAddress1); + input.mutable_transfer_message()->set_amount(amount); + input.mutable_transfer_message()->set_use_max_amount(false); + input.set_ttl(53333333); + return input; +} + +TEST(CardanoSigning, Plan) { + auto input = createSampleInput(7000000); + + { + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.utxos.size(), 2ul); + EXPECT_EQ(plan.availableAmount, 8000000ul); + EXPECT_EQ(plan.amount, 7000000ul); + EXPECT_EQ(plan.fee, 170196ul); + EXPECT_EQ(plan.change, 829804ul); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + EXPECT_EQ(plan.error, Common::Proto::OK); + } + { // very small target amount + input.mutable_transfer_message()->set_amount(1); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.utxos.size(), 1ul); + EXPECT_EQ(plan.availableAmount, 6500000ul); + EXPECT_EQ(plan.amount, 1ul); + EXPECT_EQ(plan.fee, 168435ul); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + } + { // small target amount + input.mutable_transfer_message()->set_amount(2000000); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.utxos.size(), 1ul); + EXPECT_EQ(plan.availableAmount, 6500000ul); + EXPECT_EQ(plan.amount, 2000000ul); + EXPECT_EQ(plan.fee, 168611ul); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + } + { // small target amount requested, but max amount + input.mutable_transfer_message()->set_amount(2000000); + input.mutable_transfer_message()->set_use_max_amount(true); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.utxos.size(), 2ul); + EXPECT_EQ(plan.availableAmount, 8000000ul); + EXPECT_EQ(plan.amount, 7832667ul); + EXPECT_EQ(plan.fee, 167333ul); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + } +} + +TEST(CardanoSigning, PlanForceFee) { + auto requestedAmount = 6500000ul; + auto availableAmount = 8000000ul; + auto input = createSampleInput(requestedAmount); + + { + auto fee = 170147ul; + input.mutable_transfer_message()->set_force_fee(fee); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.availableAmount, availableAmount); + EXPECT_EQ(plan.amount, requestedAmount); + EXPECT_EQ(plan.fee, fee); + EXPECT_EQ(plan.change, availableAmount - requestedAmount - fee); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + EXPECT_EQ(plan.error, Common::Proto::OK); + } + { // tiny fee + auto fee = 100ul; + input.mutable_transfer_message()->set_force_fee(fee); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.availableAmount, availableAmount); + EXPECT_EQ(plan.amount, requestedAmount); + EXPECT_EQ(plan.fee, fee); + EXPECT_EQ(plan.change, availableAmount - requestedAmount - fee); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + } + { // large fee + auto fee = 1200000ul; + input.mutable_transfer_message()->set_force_fee(fee); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.availableAmount, availableAmount); + EXPECT_EQ(plan.amount, requestedAmount); + EXPECT_EQ(plan.fee, fee); + EXPECT_EQ(plan.change, availableAmount - requestedAmount - fee); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + } + { // very large fee, larger than possible, truncated + auto fee = 3000000ul; + input.mutable_transfer_message()->set_force_fee(fee); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.availableAmount, availableAmount); + EXPECT_EQ(plan.amount, requestedAmount); + EXPECT_EQ(plan.fee, 1500000ul); + EXPECT_EQ(plan.change, 0ul); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + } + { // force fee and max amount: fee is used, amount is max, change 0 + auto fee = 160000ul; + input.mutable_transfer_message()->set_force_fee(fee); + input.mutable_transfer_message()->set_use_max_amount(true); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.availableAmount, availableAmount); + EXPECT_EQ(plan.amount, 7840000ul); + EXPECT_EQ(plan.fee, fee); + EXPECT_EQ(plan.change, 0ul); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + } +} + +TEST(CardanoSigning, PlanMissingPrivateKey) { + auto input = createSampleInput(7000000, 10, "", true); + + auto signer = Signer(input); + const auto plan = signer.doPlan(); + + EXPECT_EQ(plan.utxos.size(), 2ul); + EXPECT_EQ(plan.availableAmount, 8000000ul); + EXPECT_EQ(plan.amount, 7000000ul); + EXPECT_EQ(plan.fee, 170196ul); + EXPECT_EQ(plan.change, 829804ul); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + EXPECT_EQ(plan.error, Common::Proto::OK); +} + +TEST(CardanoSigning, SignTransfer1) { + const auto input = createSampleInput(7000000); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a40082825820554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af000825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd51a006acfc082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a000ca96c021a000298d4031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058407cf591599852b5f5e007fdc241062405c47e519266c0d884b0767c1d4f5eacce00db035998e53ed10ca4ba5ce4aac8693798089717ce6cf4415f345cc764200ef6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "9b5b15e133cd73ccaa85307d2986aebc846505118a2eb4e6111e6b4b67d1f389"); + + { + const auto decode = Cbor::Decode(encoded); + ASSERT_TRUE(decode.isValid()); + EXPECT_EQ(decode.dumpToString(), "[{0: [[h\"554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af0\", 0], [h\"f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767\", 1]], 1: [[h\"01558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd5\", 7000000], [h\"01df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\", 829804]], 2: 170196, 3: 53333333}, {0: [[h\"6d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df290\", h\"7cf591599852b5f5e007fdc241062405c47e519266c0d884b0767c1d4f5eacce00db035998e53ed10ca4ba5ce4aac8693798089717ce6cf4415f345cc764200e\"]]}, null]"); + EXPECT_EQ(decode.getArrayElements().size(), 3ul); + } +} + +TEST(CardanoSigning, PlanAndSignTransfer1) { + uint amount = 6000000; + auto input = createSampleInput(amount); + + { + // run plan and check result + auto signer = Signer(input); + const auto plan = signer.doPlan(); + + EXPECT_EQ(plan.availableAmount, 8000000ul); + EXPECT_EQ(plan.amount, amount); + EXPECT_EQ(plan.fee, 170196ul); + EXPECT_EQ(plan.change, 8000000 - amount - 170196); + ASSERT_EQ(plan.utxos.size(), 2ul); + EXPECT_EQ(plan.utxos[0].amount, 6500000ul); + EXPECT_EQ(plan.utxos[1].amount, 1500000ul); + + // perform sign with default plan + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a40082825820554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af000825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd51a005b8d8082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a001bebac021a000298d4031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058404abc749ffaffcf2f87970e4f1983c5e44b352ee1515b60017fc65e581d42b3a6ed146d5eb35d04a770460b0541a25afd5aedfd027fdaded82686f43454196a0cf6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "3852f809245d7000ad0c5ccb1357e5d333b0dd25158924581e4c7049ec68c564"); + } + + // set different plan, with one input only + input.mutable_plan()->set_amount(amount); + input.mutable_plan()->set_available_amount(6500000); + input.mutable_plan()->set_fee(165489); + input.mutable_plan()->set_change(17191988); + *(input.mutable_plan()->add_utxos()) = input.utxos(0); + input.mutable_plan()->set_error(Common::Proto::OK); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a40081825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd51a005b8d8082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a01065434021a00028671031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058408311a058035d75545a47b844fea401aa9c23e99fe7bc8136b554396eef135d4cd93062c5df38e613185c21bb1c98b881d1e0fd1024d3539b163c8e14d1a6e40df6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "e319c0bfc99cdb79d64f00b7e8fb8bfbf29fa70554c84f101e92b7dfed172448"); +} + +TEST(CardanoSigning, PlanAndSignMaxAmount) { + auto input = createSampleInput(7000000); + input.mutable_transfer_message()->set_use_max_amount(true); + + { + // run plan and check result + auto signer = Signer(input); + const auto plan = signer.doPlan(); + + EXPECT_EQ(plan.availableAmount, 8000000ul); + EXPECT_EQ(plan.amount, 8000000 - 167333ul); + EXPECT_EQ(plan.fee, 167333ul); + EXPECT_EQ(plan.change, 0ul); + ASSERT_EQ(plan.utxos.size(), 2ul); + EXPECT_EQ(plan.utxos[0].amount, 1500000ul); + EXPECT_EQ(plan.utxos[1].amount, 6500000ul); + } + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a40082825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701825820554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af000018182583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd51a0077845b021a00028da5031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058403e64473e08adc863953c0e9f820b658dda0b8a423d6172fdccff73fcd5559956c9df8ed93ff67405331d368a0c11fd18c69781046384946582e1555e9e8ec70bf6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "ca0f1e12f20c95011da7d686d206a1eb98df94accd74c4df4ef403c5ce836057"); +} + +TEST(CardanoSigning, SignNegative) { + { // plan with error + auto input = createSampleInput(7000000); + const auto error = Common::Proto::Error_invalid_memo; + input.mutable_plan()->set_error(error); + auto signer = Signer(input); + const auto output = signer.sign(); + EXPECT_EQ(output.error(), error); + } + { // zero requested amount + auto input = createSampleInput(0); + auto signer = Signer(input); + const auto output = signer.sign(); + EXPECT_EQ(output.error(), Common::Proto::Error_zero_amount_requested); + } + { // no utxo + auto input = createSampleInput(7000000, 0); + auto signer = Signer(input); + const auto output = signer.sign(); + EXPECT_EQ(output.error(), Common::Proto::Error_missing_input_utxos); + } + { // low balance + auto input = createSampleInput(7000000000); + auto signer = Signer(input); + const auto output = signer.sign(); + EXPECT_EQ(output.error(), Common::Proto::Error_low_balance); + } + { // missing private key + auto input = createSampleInput(7000000, 10, "", true); + auto signer = Signer(input); + const auto output = signer.sign(); + EXPECT_EQ(output.error(), Common::Proto::Error_missing_private_key); + } +} + +TEST(CardanoSigning, SignTransfer_0db1ea) { + const auto amount = 1100000ul; + + Proto::SigningInput input; + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("81b935447bb994567f041d181b628a0afbcd747d0199c9ff4cd895686bbee8c6"); + utxo1->mutable_out_point()->set_tx_hash(std::string(txHash1.begin(), txHash1.end())); + utxo1->mutable_out_point()->set_output_index(0); + utxo1->set_address(ownAddress1); + utxo1->set_amount(1000000); + auto* utxo2 = input.add_utxos(); + const auto txHash2 = parse_hex("3a9068a273cc2af59b45593b78973841d972d01802abe992c55dbeecdffc561b"); + utxo2->mutable_out_point()->set_tx_hash(std::string(txHash2.begin(), txHash2.end())); + utxo2->mutable_out_point()->set_output_index(0); + utxo2->set_address(ownAddress1); + utxo2->set_amount(1800000); + + const auto privateKeyData1 = parse_hex(privateKeyTest1); + input.add_private_key(privateKeyData1.data(), privateKeyData1.size()); + input.mutable_transfer_message()->set_to_address("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + input.mutable_transfer_message()->set_change_address(ownAddress1); + input.mutable_transfer_message()->set_amount(amount); + auto fee = 170147ul; + input.mutable_transfer_message()->set_use_max_amount(false); + input.mutable_transfer_message()->set_force_fee(fee); // use force fee feature here + input.set_ttl(54675589); + + { + // run plan and check result + auto signer = Signer(input); + const auto plan = signer.doPlan(); + + EXPECT_EQ(plan.availableAmount, 2800000ul); + EXPECT_EQ(plan.amount, amount); + EXPECT_EQ(plan.fee, fee); + EXPECT_EQ(plan.change, 2800000ul - amount - fee); + EXPECT_EQ(plan.utxos.size(), 2ul); + } + + // set plan with specific fee, to match the real transaction + input.mutable_plan()->set_amount(amount); + input.mutable_plan()->set_available_amount(2800000); + input.mutable_plan()->set_fee(fee); + input.mutable_plan()->set_change(2800000 - amount - fee); + *(input.mutable_plan()->add_utxos()) = input.utxos(0); + *(input.mutable_plan()->add_utxos()) = input.utxos(1); + input.mutable_plan()->set_error(Common::Proto::OK); + + auto signer = Signer(input); + const auto output = signer.sign(); + + // https://cardanoscan.io/transaction/0db1ea8c5c5828bbd027fcef3da02a63b86899db670ad7bb0630cefbe35944fa + // curl -d '{"txHash":"0db1ea..44fa","txBody":"83a400..06f6"}' -H "Content-Type: application/json" https:///api/txs/submit + EXPECT_EQ(output.error(), Common::Proto::OK); + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a4008282582081b935447bb994567f041d181b628a0afbcd747d0199c9ff4cd895686bbee8c6008258203a9068a273cc2af59b45593b78973841d972d01802abe992c55dbeecdffc561b000182825839018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a34681a0010c8e082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a001757fd021a000298a3031a03424885a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058406300b52aaff1e26067a3e0a48ae26f4f068765f46f934fabeab872c1d25535fc94893ec72feacd787f0174fbabd8933727d9a2b319b406e7a855843b0c051806f6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "0db1ea8c5c5828bbd027fcef3da02a63b86899db670ad7bb0630cefbe35944fa"); +} + +TEST(CardanoSigning, SignTransferFromLegacy) { + Proto::SigningInput input; + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767"); + utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_output_index(1); + utxo1->set_address("Ae2tdPwUPEZMRgecV9jV2e9RdbrmnWu7YgRie4de16xLdkWhy6q7ypmRhgn"); + utxo1->set_amount(1500000); + auto* utxo2 = input.add_utxos(); + const auto txHash2 = parse_hex("554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af0"); + utxo2->mutable_out_point()->set_tx_hash(txHash2.data(), txHash2.size()); + utxo2->mutable_out_point()->set_output_index(0); + utxo2->set_address("Ae2tdPwUPEZMRgecV9jV2e9RdbrmnWu7YgRie4de16xLdkWhy6q7ypmRhgn"); + utxo2->set_amount(6500000); + + const auto privateKeyData = parse_hex("c031e942f6bf2b2864700e7da20964ee6bb6d716345ce2e24d8c00e6500b574411111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"); + { + const auto privKey = PrivateKey(privateKeyData); + const auto pubKey = privKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto addr = AddressV2(pubKey); + EXPECT_EQ(addr.string(), "Ae2tdPwUPEZMRgecV9jV2e9RdbrmnWu7YgRie4de16xLdkWhy6q7ypmRhgn"); + } + input.add_private_key(privateKeyData.data(), privateKeyData.size()); + input.mutable_transfer_message()->set_to_address("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5"); + input.mutable_transfer_message()->set_change_address(ownAddress1); + input.mutable_transfer_message()->set_amount(7000000); + input.mutable_transfer_message()->set_use_max_amount(false); + input.set_ttl(53333333); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_address); + EXPECT_EQ(hex(output.encoded()), ""); +} + +TEST(CardanoSigning, SignTransferToLegacy) { + const auto toAddressLegacy = "DdzFFzCqrhssmYoG5Eca1bKZFdGS8d6iag1mU4wbLeYcSPVvBNF2wRG8yhjzQqErbg63N6KJA4DHqha113tjKDpGEwS5x1dT2KfLSbSJ"; + EXPECT_FALSE(AddressV3::isValid(toAddressLegacy)); // not V3 + EXPECT_TRUE(AddressV3::isValidLegacy(toAddressLegacy)); + + const auto input = createSampleInput(7000000, 10, toAddressLegacy); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + EXPECT_EQ(hex(output.encoded()), "83a40082825820554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af000825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701018282584c82d818584283581c6aebd89cf88271c3ee76339930d8956b03f018b2f4871522f88eb8f9a101581e581c692a37dae3bc63dfc3e1463f12011f26655ab1d1e0f4ed4b8fc63708001ad8a9555b1a006acfc082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a000ca627021a00029c19031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840db9becdc733f4c08c0e7abc29b5cc6469f9339d32f565df8bf77455439ae1f949facc9b831754e74d3fbb42e99647eedd6c28de1461d18c315485f5d24b5b90af6"); + EXPECT_EQ(hex(data(output.tx_id())), "f9b713e9987ec1377ac223f50d63c7a5e155915302de43f40d7b2627accabf69"); +} + +TEST(CardanoSigning, SignTransferToInvalid) { + const auto input = createSampleInput(7000000, 10, "__INVALID_ADDRESS__"); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_address); + EXPECT_EQ(hex(output.encoded()), ""); +} + +TEST(CardanoSigning, SignTransferToken) { + Proto::SigningInput input; + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767"); + utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_output_index(1); + utxo1->set_address(ownAddress1); + utxo1->set_amount(8051373); + // some token, to be preserved + auto* token3 = utxo1->add_token_amount(); + token3->set_policy_id(sundaeTokenPolicy); + token3->set_asset_name("CUBY"); + const auto tokenAmount3 = store(uint256_t(3000000)); + token3->set_amount(tokenAmount3.data(), tokenAmount3.size()); + + auto* utxo2 = input.add_utxos(); + const auto txHash2 = parse_hex("f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767"); + utxo2->mutable_out_point()->set_tx_hash(txHash2.data(), txHash2.size()); + utxo2->mutable_out_point()->set_output_index(2); + utxo2->set_address(ownAddress1); + utxo2->set_amount(2000000); + // some SUNDAE token, to be transferred + auto* token1 = utxo2->add_token_amount(); + token1->set_policy_id(sundaeTokenPolicy); + token1->set_asset_name("SUNDAE"); + const auto tokenAmount1 = store(uint256_t(80996569)); + token1->set_amount(tokenAmount1.data(), tokenAmount1.size()); + // some other token, to be preserved + auto* token2 = utxo2->add_token_amount(); + token2->set_policy_id(sundaeTokenPolicy); + token2->set_asset_name("CUBY"); + const auto tokenAmount2 = store(uint256_t(2000000)); + token2->set_amount(tokenAmount2.data(), tokenAmount2.size()); + + const auto privateKeyData = parse_hex(privateKeyTest1); + input.add_private_key(privateKeyData.data(), privateKeyData.size()); + input.mutable_transfer_message()->set_to_address("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5"); + input.mutable_transfer_message()->set_change_address("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + input.mutable_transfer_message()->set_amount(1500000); + auto* toToken = input.mutable_transfer_message()->mutable_token_amount()->add_token(); + toToken->set_policy_id(sundaeTokenPolicy); + toToken->set_asset_name("SUNDAE"); + const auto toTokenAmount = store(uint256_t(20000000)); + toToken->set_amount(toTokenAmount.data(), toTokenAmount.size()); + input.mutable_transfer_message()->set_use_max_amount(false); + input.set_ttl(53333333); + + { // check min ADA amount, set it + const auto bundleProtoData = data(input.transfer_message().token_amount().SerializeAsString()); + const auto minAdaAmount = TWCardanoMinAdaAmount(&bundleProtoData); + EXPECT_EQ(minAdaAmount, 1444443ul); + input.mutable_transfer_message()->set_amount(minAdaAmount); + } + + { + // run plan and check result + auto signer = Signer(input); + const auto plan = signer.doPlan(); + + EXPECT_EQ(plan.availableAmount, 10051373ul); + EXPECT_EQ(plan.amount, 1444443ul); + EXPECT_EQ(plan.fee, 174601ul); + EXPECT_EQ(plan.change, 8432329ul); + EXPECT_EQ(plan.utxos.size(), 2ul); + EXPECT_EQ(plan.availableTokens.size(), 2ul); + EXPECT_EQ(plan.availableTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_CUBY"), 5000000); + EXPECT_EQ(plan.availableTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 80996569); + EXPECT_EQ(plan.outputTokens.size(), 1ul); + EXPECT_EQ(plan.outputTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_CUBY"), 0); + EXPECT_EQ(plan.outputTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 20000000); + EXPECT_EQ(plan.changeTokens.size(), 2ul); + EXPECT_EQ(plan.changeTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_CUBY"), 5000000); + EXPECT_EQ(plan.changeTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 60996569); + } + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a40082825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76702018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd5821a00160a5ba1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a14653554e4441451a01312d00825839018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468821a0080aac9a1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a244435542591a004c4b404653554e4441451a03a2bbd9021a0002aa09031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840d90dcfbd190cbe59c42094e59eeb49b3de9d80a85b786cc311f932c5c9302d1c8c6c577b22aa70ff7955c139c700ea918f8cb425c3ba43a27980e1d238e4e908f6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "201c537693b005b64a0f0528e366ec67a84be0119ed4363b547f141f2a7770c2"); + + { + // also test proto toProto / fromProto + const Proto::TransactionPlan planProto = Signer::plan(input); + const auto plan2 = TransactionPlan::fromProto(planProto); + EXPECT_EQ(plan2.amount, 1444443ul); + EXPECT_EQ(plan2.change, 8432329ul); + } +} + +TEST(CardanoSigning, SignTransferToken_1dd248) { + Proto::SigningInput input; + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("f2d2b11c8c07c5c646f5b5af20fddf2f0a174743c6a1b13cca27e28a6ca34710"); + utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_output_index(0); + utxo1->set_address(ownAddress1); + utxo1->set_amount(1500000); + // some token + auto* token3 = utxo1->add_token_amount(); + token3->set_policy_id(sundaeTokenPolicy); + token3->set_asset_name("SUNDAE"); + const auto tokenAmount3 = store(uint256_t(20000000)); + token3->set_amount(tokenAmount3.data(), tokenAmount3.size()); + + auto* utxo2 = input.add_utxos(); + const auto txHash2 = parse_hex("6975fcf7bbca745c85f50777f956219868fd9cad14ba496fed1371252e8df60f"); + utxo2->mutable_out_point()->set_tx_hash(txHash2.data(), txHash2.size()); + utxo2->mutable_out_point()->set_output_index(0); + utxo2->set_address(ownAddress1); + utxo2->set_amount(10258890); + + const auto privateKeyData = parse_hex(privateKeyTest1); + input.add_private_key(privateKeyData.data(), privateKeyData.size()); + input.mutable_transfer_message()->set_to_address("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); // Test + input.mutable_transfer_message()->set_change_address(ownAddress1); + input.mutable_transfer_message()->set_amount(1600000); + auto* toToken = input.mutable_transfer_message()->mutable_token_amount()->add_token(); + toToken->set_policy_id(sundaeTokenPolicy); + toToken->set_asset_name("SUNDAE"); + const auto toTokenAmount = store(uint256_t(11000000)); + toToken->set_amount(toTokenAmount.data(), toTokenAmount.size()); + input.mutable_transfer_message()->set_use_max_amount(false); + input.set_ttl(61232158); + + { // check min ADA amount + const auto bundleProtoData = data(input.transfer_message().token_amount().SerializeAsString()); + EXPECT_EQ(TWCardanoMinAdaAmount(&bundleProtoData), 1444443ul); + EXPECT_GT(input.transfer_message().amount(), TWCardanoMinAdaAmount(&bundleProtoData)); + } + + { + // run plan and check result + auto signer = Signer(input); + const auto plan = signer.doPlan(); + + EXPECT_EQ(plan.availableAmount, 11758890ul); + EXPECT_EQ(plan.amount, 11758890 - 9984729 - 174161ul); + EXPECT_EQ(plan.fee, 174161ul); + EXPECT_EQ(plan.change, 9984729ul); + EXPECT_EQ(plan.utxos.size(), 2ul); + EXPECT_EQ(plan.availableTokens.size(), 1ul); + EXPECT_EQ(plan.availableTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 20000000); + EXPECT_EQ(plan.outputTokens.size(), 1ul); + EXPECT_EQ(plan.outputTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 11000000); + EXPECT_EQ(plan.changeTokens.size(), 1ul); + EXPECT_EQ(plan.changeTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 9000000); + } + + // set plan with specific fee, to match the real transaction + input.mutable_plan()->set_available_amount(11758890); + input.mutable_plan()->set_amount(1600000); + input.mutable_plan()->set_fee(174102); + input.mutable_plan()->set_change(9984788); + *(input.mutable_plan()->add_available_tokens()) = input.utxos(0).token_amount(0); + *(input.mutable_plan()->add_output_tokens()) = input.utxos(0).token_amount(0); + input.mutable_plan()->mutable_output_tokens(0)->set_amount(toTokenAmount.data(), toTokenAmount.size()); + *(input.mutable_plan()->add_change_tokens()) = input.utxos(0).token_amount(0); + const auto changeTokenAmount = store(uint256_t(9000000)); + input.mutable_plan()->mutable_change_tokens(0)->set_amount(changeTokenAmount.data(), changeTokenAmount.size()); + *(input.mutable_plan()->add_utxos()) = input.utxos(1); + *(input.mutable_plan()->add_utxos()) = input.utxos(0); + input.mutable_plan()->set_error(Common::Proto::OK); + + auto signer = Signer(input); + const auto output = signer.sign(); + + // https://cardanoscan.io/transaction/1dd24872d93d3b5091b98e19b9f920cd0c4369e4c5ca178e898152c52f00c162 + // curl -d '{"txHash":"1dd248..c162","txBody":"83a400..08f6"}' -H "Content-Type: application/json" https:///api/txs/submit + EXPECT_EQ(output.error(), Common::Proto::OK); + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a400828258206975fcf7bbca745c85f50777f956219868fd9cad14ba496fed1371252e8df60f00825820f2d2b11c8c07c5c646f5b5af20fddf2f0a174743c6a1b13cca27e28a6ca34710000182825839018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468821a00186a00a1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a14653554e4441451a00a7d8c082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b821a00985b14a1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a14653554e4441451a00895440021a0002a816031a03a6541ea100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840c8cdee32bfd584f55cf334b4ec6f734635144736d48f882e647a7a6283f230bc5a67d4dd66a9e523e0c29c812ed1e3589febbcf96547a1fc6d061a7ccfb81308f6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "1dd24872d93d3b5091b98e19b9f920cd0c4369e4c5ca178e898152c52f00c162"); +} + +TEST(CardanoSigning, SignTransferTokenMaxAmount_620b71) { + Proto::SigningInput input; + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("46964521ad00d9b3f3d41f77c07e1b3093848048dbdf2d95cf900e15cdac0d7f"); + utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_output_index(0); + utxo1->set_address(ownAddress1); + utxo1->set_amount(2170871); + // some token + auto* token1 = utxo1->add_token_amount(); + token1->set_policy_id(sundaeTokenPolicy); + token1->set_asset_name("SUNDAE"); + const auto tokenAmount1 = store(uint256_t(20000000)); + token1->set_amount(tokenAmount1.data(), tokenAmount1.size()); + + const auto privateKeyData = parse_hex(privateKeyTest1); + input.add_private_key(privateKeyData.data(), privateKeyData.size()); + input.mutable_transfer_message()->set_to_address("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5"); + input.mutable_transfer_message()->set_change_address(ownAddress1); + input.mutable_transfer_message()->set_amount(666); // doesn't matter, max is used + auto* toToken = input.mutable_transfer_message()->mutable_token_amount()->add_token(); + toToken->set_policy_id(sundaeTokenPolicy); + toToken->set_asset_name("SUNDAE"); + const auto toTokenAmount = store(uint256_t(666)); // doesn't matter, max is used + input.mutable_transfer_message()->set_use_max_amount(true); + input.set_ttl(61085916); + + { + // run plan and check result + auto signer = Signer(input); + const auto plan = signer.doPlan(); + + EXPECT_EQ(plan.availableAmount, 2170871ul); + EXPECT_EQ(plan.amount, 2170871 - 167730ul); + EXPECT_EQ(plan.fee, 167730ul); + EXPECT_EQ(plan.change, 0ul); + EXPECT_EQ(plan.utxos.size(), 1ul); + EXPECT_EQ(plan.availableTokens.size(), 1ul); + EXPECT_EQ(plan.availableTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 20000000); + EXPECT_EQ(plan.outputTokens.size(), 1ul); + EXPECT_EQ(plan.outputTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 20000000); + EXPECT_EQ(plan.changeTokens.size(), 0ul); + } + + // set plan with specific fee, to match the real transaction + input.mutable_plan()->set_available_amount(2170871); + input.mutable_plan()->set_amount(1998526); + input.mutable_plan()->set_fee(172345); + input.mutable_plan()->set_change(0); + *(input.mutable_plan()->add_available_tokens()) = input.utxos(0).token_amount(0); + *(input.mutable_plan()->add_output_tokens()) = input.utxos(0).token_amount(0); + *(input.mutable_plan()->add_utxos()) = input.utxos(0); + input.mutable_plan()->set_error(Common::Proto::OK); + + auto signer = Signer(input); + const auto output = signer.sign(); + + // https://cardanoscan.io/transaction/620b719338efb419b0e1417bfbe01fc94a62d5669a4b8cbbf4e32ecc1ca3b872 + // curl -d '{"txHash":"620b71..b872","txBody":"83a400..08f6"}' -H "Content-Type: application/json" https:///api/txs/submit + EXPECT_EQ(output.error(), Common::Proto::OK); + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a4008182582046964521ad00d9b3f3d41f77c07e1b3093848048dbdf2d95cf900e15cdac0d7f00018182583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd5821a001e7ebea1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a14653554e4441451a01312d00021a0002a139031a03a418dca100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840e1d1565cd747b20b0f10a92f068f3d5faebdee92b4b4a4b96ce14736d975e17d1446f7f51e64997a0bb38e0151dc738468161d574d6cfcd8040e4455ff46bc08f6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "620b719338efb419b0e1417bfbe01fc94a62d5669a4b8cbbf4e32ecc1ca3b872"); +} + +TEST(CardanoSigning, SignTransferTwoTokens) { + auto input = createSampleInput(7000000); + input.mutable_transfer_message()->set_amount(1500000); + auto* token1 = input.mutable_transfer_message()->mutable_token_amount()->add_token(); + token1->set_policy_id(sundaeTokenPolicy); + token1->set_asset_name("SUNDAE"); + const auto tokenAmount1 = store(uint256_t(40000000)); + token1->set_amount(tokenAmount1.data(), tokenAmount1.size()); + auto* token2 = input.mutable_transfer_message()->mutable_token_amount()->add_token(); + token2->set_policy_id(sundaeTokenPolicy); + token2->set_asset_name("CUBY"); + const auto tokenAmount2 = store(uint256_t(2000000)); + token2->set_amount(tokenAmount2.data(), tokenAmount2.size()); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_requested_token_amount); + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(output.encoded()), ""); +} + +TEST(CardanoSigning, SignMessageWithKey) { + // test case from cardano-crypto.js + + const auto privateKey = PrivateKey(parse_hex( + "d809b1b4b4c74734037f76aace501730a3fe2fca30b5102df99ad3f7c0103e48" + "d54cde47e9041b31f3e6873d700d83f7a937bea746dadfa2c5b0a6a92502356c" + "69272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000" + "1111111111111111111111111111111111111111111111111111111111111111" + "1111111111111111111111111111111111111111111111111111111111111111" + "1111111111111111111111111111111111111111111111111111111111111111")); + + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + EXPECT_EQ(hex(publicKey.bytes), + "e6f04522f875c1563682ca876ddb04c2e2e3ae718e3ff9f11c03dd9f9dccf698" + "69272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000" + "857eed804ff087b97f87848f6493e87257a8c5203cb9f422f6e7a7d8a4d299f3" + "1111111111111111111111111111111111111111111111111111111111111111"); + + const auto sampleMessageStr = "Hello world"; + const auto sampleMessage = data(sampleMessageStr); + + const auto signature = privateKey.sign(sampleMessage, TWCurveED25519ExtendedCardano); + + const auto sampleRightSignature = "1096ddcfb2ad21a4c0d861ef3fabe18841e8de88105b0d8e36430d7992c588634ead4100c32b2800b31b65e014d54a8238bdda63118d829bf0bcf1b631e86f0e"; + EXPECT_EQ(hex(signature), sampleRightSignature); +} + +TEST(CardanoSigning, AnySignTransfer1) { + const auto input = createSampleInput(7000000); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeCardano); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a40082825820554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af000825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd51a006acfc082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a000ca96c021a000298d4031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058407cf591599852b5f5e007fdc241062405c47e519266c0d884b0767c1d4f5eacce00db035998e53ed10ca4ba5ce4aac8693798089717ce6cf4415f345cc764200ef6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "9b5b15e133cd73ccaa85307d2986aebc846505118a2eb4e6111e6b4b67d1f389"); +} + +TEST(CardanoSigning, AnyPlan1) { + const auto input = createSampleInput(7000000); + + Proto::TransactionPlan plan; + ANY_PLAN(input, plan, TWCoinTypeCardano); + + EXPECT_EQ(plan.error(), Common::Proto::OK); + EXPECT_EQ(plan.amount(), 7000000ul); + EXPECT_EQ(plan.available_amount(), 8000000ul); + EXPECT_EQ(plan.fee(), 170196ul); + EXPECT_EQ(plan.change(), 829804ul); + ASSERT_EQ(plan.utxos_size(), 2); + EXPECT_EQ(plan.utxos(0).amount(), 6500000ul); + EXPECT_EQ(plan.utxos(1).amount(), 1500000ul); + + EXPECT_EQ(hex(plan.SerializeAsString()), "0880a4e80310c09fab0318d4b10a20ecd2324292010a220a20554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af01267616464723171383034336d356865656179646e76746d6d6b7975686536717635686176766873663064323671336a7967737370786c796670796b3679716b77307968747976747230666c656b6a3834753634617a38326375666d716e36357a6473796c7a6b323318a0dd8c034293010a240a20f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76710011267616464723171383034336d356865656179646e76746d6d6b7975686536717635686176766873663064323671336a7967737370786c796670796b3679716b77307968747976747230666c656b6a3834753634617a38326375666d716e36357a6473796c7a6b323318e0c65b"); + + { + // also test fromProto + const auto plan2 = TransactionPlan::fromProto(plan); + EXPECT_EQ(plan2.amount, plan.amount()); + EXPECT_EQ(plan2.change, plan.change()); + } +} + +} // namespace TW::Cardano::tests \ No newline at end of file diff --git a/tools/windows-replace/tests/chains/Cardano/TWCardanoAddressTests.cpp b/tools/windows-replace/tests/chains/Cardano/TWCardanoAddressTests.cpp new file mode 100644 index 00000000000..f610ffd1bba --- /dev/null +++ b/tools/windows-replace/tests/chains/Cardano/TWCardanoAddressTests.cpp @@ -0,0 +1,74 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include +#include +#include +#include +#include +#include +#include +#include "TestUtilities.h" +#include "PrivateKey.h" + +#include + +TEST(TWCardano, AddressFromPublicKey) { + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA( + "b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71effbf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4" + "639aadd8b6499ae39b78018b79255fbd8f585cbda9cbb9e907a72af86afb7a05d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7bed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a" + ).get())); + ASSERT_NE(nullptr, privateKey.get()); + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519Cardano(privateKey.get())); + ASSERT_NE(nullptr, publicKey.get()); + ASSERT_EQ(128ul, publicKey.get()->impl.bytes.size()); + auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeCardano)); + auto addressString = WRAPS(TWAnyAddressDescription(address.get())); + assertStringsEqual(addressString, "addr1qx4z6twzknkkux0hhp0kq6hvdfutczp56g56y5em8r8mgvxalp7nkkk25vuspleke2zltaetmlwrfxv7t049cq9jmwjswmfw6t"); + + auto address2 = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(STRING("addr1qx4z6twzknkkux0hhp0kq6hvdfutczp56g56y5em8r8mgvxalp7nkkk25vuspleke2zltaetmlwrfxv7t049cq9jmwjswmfw6t").get(), TWCoinTypeCardano)); + ASSERT_NE(nullptr, address2.get()); + auto address2String = WRAPS(TWAnyAddressDescription(address2.get())); + assertStringsEqual(address2String, "addr1qx4z6twzknkkux0hhp0kq6hvdfutczp56g56y5em8r8mgvxalp7nkkk25vuspleke2zltaetmlwrfxv7t049cq9jmwjswmfw6t"); + + ASSERT_TRUE(TWAnyAddressEqual(address.get(), address2.get())); +} + +TEST(TWCardano, AddressFromWallet) { + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic( + STRING("cost dash dress stove morning robust group affair stomach vacant route volume yellow salute laugh").get(), + STRING("").get() + )); + auto privateKey = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeCardano)); + auto privateKeyData = WRAPD(TWPrivateKeyData(privateKey.get())); + EXPECT_EQ(TWDataSize(privateKeyData.get()), 192ul); + + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519Cardano(privateKey.get())); + auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); + EXPECT_EQ(TWDataSize(publicKeyData.get()), 128ul); + assertHexEqual(publicKeyData, "fafa7eb4146220db67156a03a5f7a79c666df83eb31abbfbe77c85e06d40da3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26faf4b8d5201961e68f2e177ba594101f513ee70fe70a41324e8ea8eb787ffda6f4bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276"); + + auto address = WRAPS(TWCoinTypeDeriveAddress(TWCoinTypeCardano, privateKey.get())); + assertStringsEqual(address, "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); +} + +/* +TEST(TWCardano, GetStakingKey) { + { + auto stakingAddress = WRAPS(TWCardanoGetStakingAddress(STRING("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23").get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(stakingAddress.get())), "stake1u80jysjtdzqt88jt4jx93h5lumfr67d273r4vwyasfa2pxcwxllmx"); + } + { // negative case: cannot get staking address from non-base address + auto stakingAddress = WRAPS(TWCardanoGetStakingAddress(STRING("stake1u95zuevxqjvpdh83r08ywq9xal6nxl48fgm0wvngyenvs4qh0hqf9").get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(stakingAddress.get())), ""); + } + { // negative case: cannot get staking address from invalid address, should not throw + auto stakingAddress = WRAPS(TWCardanoGetStakingAddress(STRING("__THIS_IS_NOT_A_VALID_CARDANO_ADDRESS__").get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(stakingAddress.get())), ""); + } +} +*/ diff --git a/tools/windows-replace/tests/chains/Polkadot/SignerTests.cpp b/tools/windows-replace/tests/chains/Polkadot/SignerTests.cpp new file mode 100644 index 00000000000..59d51714c23 --- /dev/null +++ b/tools/windows-replace/tests/chains/Polkadot/SignerTests.cpp @@ -0,0 +1,340 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Polkadot/Signer.h" +#include "Polkadot/Extrinsic.h" +#include "Polkadot/Address.h" +#include "Polkadot/SS58Address.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "proto/Polkadot.pb.h" +#include "uint256.h" + +#include +#include + + +namespace TW::Polkadot::tests { + auto privateKey = PrivateKey(parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")); + auto privateKeyIOS = PrivateKey(parse_hex("37932b086586a6675e66e562fe68bd3eeea4177d066619c602fe3efc290ada62")); + auto privateKeyThrow2 = PrivateKey(parse_hex("70a794d4f1019c3ce002f33062f45029c4f930a56b3d20ec477f7668c6bbc37f")); + auto privateKeyPolkadot = PrivateKey(parse_hex("298fcced2b497ed48367261d8340f647b3fca2d9415d57c2e3c5ef90482a2266")); + auto addressThrow2 = "14Ztd3KJDaB9xyJtRkREtSZDdhLSbm7UUKt8Z7AwSv7q85G2"; + auto toPublicKey = PublicKey(parse_hex("0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"), TWPublicKeyTypeED25519); + auto genesisHash = parse_hex("91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"); + auto controller1 = "14xKzzU1ZYDnzFj7FgdtDAYSMJNARjDc2gNw4XAFDgr4uXgp"; + +TEST(PolkadotSigner, SignTransfer_9fd062) { + auto toAddress = Address("13ZLCqJNPsRZYEbwjtZZFpWt9GyFzg5WahXCVWKpWdUJqrQ5"); + + auto input = Proto::SigningInput(); + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + auto blockHash = parse_hex("0x5d2143bb808626d63ad7e1cda70fa8697059d670a992e82cd440fbb95ea40351"); + input.set_block_hash(blockHash.data(), blockHash.size()); + input.set_nonce(3); + input.set_spec_version(26); + { + PublicKey publicKey = privateKeyThrow2.getPublicKey(TWPublicKeyTypeED25519); + Address address = Address(publicKey); + EXPECT_EQ(address.string(), addressThrow2); + } + input.set_private_key(privateKeyThrow2.bytes.data(), privateKeyThrow2.bytes.size()); + input.set_network(Proto::Network::POLKADOT); + input.set_transaction_version(5); + + // era: for blockhash and block number, use curl -H "Content-Type: application/json" -H "Accept: text/plain" https:///transaction/material?noMeta=true + auto era = input.mutable_era(); + era->set_block_number(3541050); + era->set_period(64); + + auto balanceCall = input.mutable_balance_call(); + auto transfer = balanceCall->mutable_transfer(); + auto value = store(uint256_t(2000000000)); // 0.2 + transfer->set_to_address(toAddress.string()); + transfer->set_value(value.data(), value.size()); + + auto extrinsic = Extrinsic(input); + auto preimage = extrinsic.encodePayload(); + EXPECT_EQ(hex(preimage), "05007120f76076bcb0efdf94c7219e116899d0163ea61cb428183d71324eb33b2bce0300943577a5030c001a0000000500000091b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c35d2143bb808626d63ad7e1cda70fa8697059d670a992e82cd440fbb95ea40351"); + + auto output = Signer::sign(input); + // https://polkadot.subscan.io/extrinsic/0x9fd06208a6023e489147d8d93f0182b0cb7e45a40165247319b87278e08362d8 + EXPECT_EQ(hex(output.encoded()), "3502849dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f7830073e59cef381aedf56d7af076bafff9857ffc1e3bd7d1d7484176ff5b58b73f1211a518e1ed1fd2ea201bd31869c0798bba4ffe753998c409d098b65d25dff801a5030c0005007120f76076bcb0efdf94c7219e116899d0163ea61cb428183d71324eb33b2bce0300943577"); +} + +TEST(PolkadotSigner, SignTransferDOT) { + + auto blockHash = parse_hex("0x343a3f4258fd92f5ca6ca5abdf473d86a78b0bcd0dc09c568ca594245cc8c642"); + auto toAddress = SS58Address(toPublicKey, TWSS58AddressTypePolkadot); + + auto input = Proto::SigningInput(); + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_block_hash(blockHash.data(), blockHash.size()); + + input.set_nonce(0); + input.set_spec_version(17); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_network(Proto::Network::POLKADOT); + input.set_transaction_version(3); + + auto& era = *input.mutable_era(); + era.set_block_number(927699); + era.set_period(8); + + auto balanceCall = input.mutable_balance_call(); + auto& transfer = *balanceCall->mutable_transfer(); + auto value = store(uint256_t(12345)); + transfer.set_to_address(toAddress.string()); + transfer.set_value(value.data(), value.size()); + + auto extrinsic = Extrinsic(input); + auto preimage = extrinsic.encodePayload(); + auto output = Signer::sign(input); + + ASSERT_EQ(hex(preimage), "05008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c032000000110000000300000091b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3343a3f4258fd92f5ca6ca5abdf473d86a78b0bcd0dc09c568ca594245cc8c642"); + ASSERT_EQ(hex(output.encoded()), "29028488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee003d91a06263956d8ce3ce5c55455baefff299d9cb2bb3f76866b6828ee4083770b6c03b05d7b6eb510ac78d047002c1fe5c6ee4b37c9c5a8b09ea07677f12e50d3200000005008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0"); +} + +TEST(PolkadotSigner, SignTransfer_72dd5b) { + + auto blockHash = parse_hex("7d5fa17b70251d0806f26156b1b698dfd09e040642fa092595ce0a78e9e84fcd"); + + auto input = Proto::SigningInput(); + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_block_hash(blockHash.data(), blockHash.size()); + + input.set_nonce(1); + input.set_spec_version(28); + input.set_private_key(privateKeyIOS.bytes.data(), privateKeyIOS.bytes.size()); + input.set_network(Proto::Network::POLKADOT); + input.set_transaction_version(6); + + auto& era = *input.mutable_era(); + era.set_block_number(3910736); + era.set_period(64); + + auto balanceCall = input.mutable_balance_call(); + auto& transfer = *balanceCall->mutable_transfer(); + auto value = store(uint256_t(10000000000)); + transfer.set_to_address("13ZLCqJNPsRZYEbwjtZZFpWt9GyFzg5WahXCVWKpWdUJqrQ5"); + transfer.set_value(value.data(), value.size()); + + auto extrinsic = Extrinsic(input); + auto preimage = extrinsic.encodePayload(); + auto output = Signer::sign(input); + + ASSERT_EQ(hex(preimage), "0500007120f76076bcb0efdf94c7219e116899d0163ea61cb428183d71324eb33b2bce0700e40b5402050104001c0000000600000091b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c37d5fa17b70251d0806f26156b1b698dfd09e040642fa092595ce0a78e9e84fcd"); + ASSERT_EQ(hex(output.encoded()), "410284008d96660f14babe708b5e61853c9f5929bc90dd9874485bf4d6dc32d3e6f22eaa0038ec4973ab9773dfcbf170b8d27d36d89b85c3145e038d68914de83cf1f7aca24af64c55ec51ba9f45c5a4d74a9917dee380e9171108921c3e5546e05be15206050104000500007120f76076bcb0efdf94c7219e116899d0163ea61cb428183d71324eb33b2bce0700e40b5402"); +} + +TEST(PolkadotSigner, SignBond_8da66d) { + auto input = Proto::SigningInput(); + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + auto blockHash = parse_hex("0xf1eee612825f29abd3299b486e401299df2faa55b7ce1e34bf2243bd591905fc"); + input.set_block_hash(blockHash.data(), blockHash.size()); + input.set_nonce(0); + input.set_spec_version(26); + { + PublicKey publicKey = privateKeyThrow2.getPublicKey(TWPublicKeyTypeED25519); + Address address = Address(publicKey); + EXPECT_EQ(address.string(), addressThrow2); + } + input.set_private_key(privateKeyThrow2.bytes.data(), privateKeyThrow2.bytes.size()); + input.set_network(Proto::Network::POLKADOT); + input.set_transaction_version(5); + + // era: for blockhash and block number, use curl -H "Content-Type: application/json" -H "Accept: text/plain" https:///transaction/material?noMeta=true + auto era = input.mutable_era(); + era->set_block_number(3540912); + era->set_period(64); + + auto stakingCall = input.mutable_staking_call(); + auto bond = stakingCall->mutable_bond(); + auto value = store(uint256_t(11000000000)); // 1.1 + bond->set_controller(addressThrow2); // myself + bond->set_value(value.data(), value.size()); + bond->set_reward_destination(Proto::RewardDestination::STASH); + + auto output = Signer::sign(input); + // https://polkadot.subscan.io/extrinsic/0x8da66d3fe0f592cff714ec107289370365117a1abdb72a19ac91181fdcf62bba + ASSERT_EQ(hex(output.encoded()), "3d02849dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f783009025843bc49c1c4fbc99dbbd290c92f9879665d55b02f110abfb4800f0e7630877d2cffd853deae7466c22fbc8616a609e1b92615bb365ea8adccba5ef7624050503000007009dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f7830700aea68f0201"); +} + +TEST(PolkadotSigner, SignBondAndNominate_4955314_2) { + + auto key = parse_hex("7f44b19b391a8015ca4c7d94097b3695867a448d1391e7f3243f06987bdb6858"); + auto input = Proto::SigningInput(); + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_block_hash(genesisHash.data(), genesisHash.size()); + input.set_nonce(4); + input.set_spec_version(30); + input.set_private_key(key.data(), key.size()); + input.set_network(Proto::Network::POLKADOT); + input.set_transaction_version(7); + + auto stakingCall = input.mutable_staking_call(); + auto bondnom = stakingCall->mutable_bond_and_nominate(); + auto value = store(uint256_t(10000000000)); // 1 DOT + bondnom->set_controller("13ZLCqJNPsRZYEbwjtZZFpWt9GyFzg5WahXCVWKpWdUJqrQ5"); + bondnom->set_value(value.data(), value.size()); + bondnom->set_reward_destination(Proto::RewardDestination::STASH); + bondnom->add_nominators("1zugcavYA9yCuYwiEYeMHNJm9gXznYjNfXQjZsZukF1Mpow"); + bondnom->add_nominators("15oKi7HoBQbwwdQc47k71q4sJJWnu5opn1pqoGx4NAEYZSHs"); + + auto output = Signer::sign(input); + // https://polkadot.subscan.io/extrinsic/4955314-2 + ASSERT_EQ(hex(output.encoded()), "6103840036092fac541e0e5feda19e537c679b487566d7101141c203ac8322c27e5f076a00a8b1f859d788f11a958e98b731358f89cf3fdd41a667ea992522e8d4f46915f4c03a1896f2ac54bdc5f16e2ce8a2a3bf233d02aad8192332afd2113ed6688e0d0010001a02080700007120f76076bcb0efdf94c7219e116899d0163ea61cb428183d71324eb33b2bce0700e40b540201070508002c2a55b5ffdca266bd0207df97565b03255f70783ca1a349be5ed9f44589c36000d44533a4d21fd9d6f5d57c8cd05c61a6f23f9131cec8ae386b6b437db399ec3d"); +} + +TEST(PolkadotSigner, SignNominate_452522) { + auto input = Proto::SigningInput(); + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + auto blockHash = parse_hex("0x211787d016e39007ac054547737a10542620013e73648b3134541d536cb44e2c"); + input.set_block_hash(blockHash.data(), blockHash.size()); + input.set_nonce(1); + input.set_spec_version(26); + input.set_private_key(privateKeyThrow2.bytes.data(), privateKeyThrow2.bytes.size()); + input.set_network(Proto::Network::POLKADOT); + input.set_transaction_version(5); + + // era: for blockhash and block number, use curl -H "Content-Type: application/json" -H "Accept: text/plain" https:///transaction/material?noMeta=true + auto era = input.mutable_era(); + era->set_block_number(3540945); + era->set_period(64); + + auto stakingCall = input.mutable_staking_call(); + auto nominate = stakingCall->mutable_nominate(); + + nominate->add_nominators(controller1); + nominate->add_nominators("1REAJ1k691g5Eqqg9gL7vvZCBG7FCCZ8zgQkZWd4va5ESih"); + + auto output = Signer::sign(input); + // https://polkadot.subscan.io/extrinsic/0x4525224b7d8f3e58de3a54a9fbfd071401c2b737f314c972a2bb087a0ff508a6 + ASSERT_EQ(hex(output.encoded()), "a502849dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f78300d73ff0dc456704743f70173a56e6c13e88a6e1dddb38a23552a066e44fb64e2c9d8a5e9a76afb9489b8540365f668bddd34b7d9c8dbdc4600e6316080e55a30315010400070508aee72821ca00e62304e4f0d858122a65b87c8df4f0eae224ae064b951d39f610127a30e486492921e58f2564b36ab1ca21ff630672f0e76920edd601f8f2b89a"); +} + +TEST(PolkadotSigner, SignNominate2) { + auto blockHash = parse_hex("d22a6b2e3e61325050718bd04a14da9efca1f41c9f0a525c375d36106e25af68"); + auto input = Proto::SigningInput(); + + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_block_hash(blockHash.data(), blockHash.size()); + input.set_nonce(0); + input.set_spec_version(17); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_network(Proto::Network::POLKADOT); + input.set_transaction_version(3); + + auto stakingCall = input.mutable_staking_call(); + auto& nominate = *stakingCall->mutable_nominate(); + // payload size larger than 256, will be hashed + nominate.add_nominators("1zugcabYjgfQdMLC3cAzQ8tJZMo45tMnGpivpAzpxB4CZyK"); + nominate.add_nominators("1REAJ1k691g5Eqqg9gL7vvZCBG7FCCZ8zgQkZWd4va5ESih"); + nominate.add_nominators("1WG3jyNqniQMRZGQUc7QD2kVLT8hkRPGMSqAb5XYQM1UDxN"); + nominate.add_nominators("16QFrtU6kDdBjxY8qEKz5EEfuDkHxqG8pix3wSGKQzRcuWHo"); + nominate.add_nominators("14ShUZUYUR35RBZW6uVVt1zXDxmSQddkeDdXf1JkMA6P721N"); + nominate.add_nominators("15MUBwP6dyVw5CXF9PjSSv7SdXQuDSwjX86v1kBodCSWVR7c"); + + auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.encoded()), "a1048488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee00135bbc68b67fffadaf7e98b6402c4fc60382765f543225083a024b0e0ff8071d4ec4ddd67a65828113cc76f3208765608be010d2fcfdcd47e8fe342872704c000000000705182c2a55b5a116a4c88aff57e8f2b70ba72dda72dda4b78630e16ad0ca69006f18127a30e486492921e58f2564b36ab1ca21ff630672f0e76920edd601f8f2b89a1650c532ed1a8641e8922aa24ade0ff411d03edd9ed1c6b7fe42f1a801cee37ceee9d5d071a418b51c02b456d5f5cefd6231041ad59b0e8379c59c11ba4a2439984e16482c99cfad1436111e321a86d87d0fac203bf64538f888e45d793b5413c08d5de7a5d97bea2c7ddf516d0635bddc43f326ae2f80e2595b49d4a08c4619"); +} + +TEST(PolkadotSigner, SignChill) { + auto blockHash = parse_hex("1d4a1ecc8b1c37bf0ba5d3e0bf14ec5402fbb035eeaf6d8042c07ca5f8c57429"); + auto input = Proto::SigningInput(); + + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_block_hash(blockHash.data(), blockHash.size()); + input.set_nonce(0); + input.set_spec_version(17); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_network(Proto::Network::POLKADOT); + input.set_transaction_version(3); + + auto stakingCall = input.mutable_staking_call(); + //auto __attribute__((unused)) &chill = *stakingCall->mutable_chill(); + [[maybe_unused]] auto &chill = *stakingCall->mutable_chill(); //win + auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.encoded()), "9d018488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0088b5e1cd93ba74b82e329f95e1b22660385970182172b2ae280801fdd1ee5652cf7bf319e5e176ccc299dd8eb1e7fccb0ea7717efaf4aacd7640789dd09c1e070000000706"); +} + +TEST(PolkadotSigner, SignWithdraw) { + auto blockHash = parse_hex("7b4d1d1e2573eabcc90a3e96058eb0d8d21d7a0b636e8030d152d9179a345dda"); + auto input = Proto::SigningInput(); + + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_block_hash(blockHash.data(), blockHash.size()); + input.set_nonce(0); + input.set_spec_version(17); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_network(Proto::Network::POLKADOT); + input.set_transaction_version(3); + + auto stakingCall = input.mutable_staking_call(); + auto& withdraw = *stakingCall->mutable_withdraw_unbonded(); + withdraw.set_slashing_spans(10); + + auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.encoded()), "ad018488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee002e49bf0dec9bef01dd3bd25419e2147dc983613d0860108f889f9ff2d062c5e3267e309e2dbc35dd2fc2b877b57d86a5f12cbeb8217485be32be3c34d2507d0e00000007030a000000"); +} + +TEST(PolkadotSigner, SignUnbond_070957) { + auto input = Proto::SigningInput(); + + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + auto blockHash = parse_hex("0x53040c71c6061bd256346b81fcb3545c13b5c34c7cd0c2c25f00aa6e564b16d5"); + input.set_block_hash(blockHash.data(), blockHash.size()); + input.set_nonce(2); + input.set_spec_version(26); + input.set_private_key(privateKeyThrow2.bytes.data(), privateKeyThrow2.bytes.size()); + input.set_network(Proto::Network::POLKADOT); + input.set_transaction_version(5); + + auto era = input.mutable_era(); + era->set_block_number(3540983); + era->set_period(64); + + auto stakingCall = input.mutable_staking_call(); + auto unbond = stakingCall->mutable_unbond(); + auto value = store(uint256_t(4000000000)); + unbond->set_value(value.data(), value.size()); + + auto output = Signer::sign(input); + // https://polkadot.subscan.io/extrinsic/0x070957ab697adbe11f7d72a1314d0a81d272a747d2e6880818073317125f980a + ASSERT_EQ(hex(output.encoded()), "b501849dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f783003a762d9dc3f2aba8922c4babf7e6622ca1d74da17ab3f152d8f29b0ffee53c7e5e150915912a9dfd98ef115d272e096543eef9f513207dd606eea97d023a64087503080007020300286bee"); +} + +TEST(PolkadotSigner, SignChillAndUnbond) { + auto blockHash = parse_hex("0x35ba668bb19453e8da6334cadcef2a27c8d4141bfc8b49e78e853c3d73e1ecd0"); + auto input = Proto::SigningInput(); + + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_block_hash(blockHash.data(), blockHash.size()); + input.set_nonce(6); + input.set_spec_version(9200); + input.set_private_key(privateKeyPolkadot.bytes.data(), privateKeyPolkadot.bytes.size()); + input.set_network(Proto::Network::POLKADOT); + input.set_transaction_version(12); + + auto era = input.mutable_era(); + era->set_block_number(10541373); + era->set_period(64); + + auto stakingCall = input.mutable_staking_call(); + auto chillBond = stakingCall->mutable_chill_and_unbond(); + auto value = store(uint256_t(100500000000)); // 10.05 DOT + chillBond->set_value(value.data(), value.size()); + + auto output = Signer::sign(input); + // https://polkadot.subscan.io/extrinsic/10541383-2 + ASSERT_EQ(hex(output.encoded()), "d10184008361bd08ddca5fda28b5e2aa84dc2621de566e23e089e555a42194c3eaf2da7900c891ba102db672e378945d74cf7f399226a76b43cab502436971599255451597fc2599902e4b62c7ce85ecc3f653c693fef3232be620984b5bb5bcecbbd7b209d50318001a02080706070207004d446617"); +} + +} // namespace TW::Polkadot::tests diff --git a/tools/windows-replace/tests/chains/Zilliqa/SignerTests.cpp b/tools/windows-replace/tests/chains/Zilliqa/SignerTests.cpp new file mode 100644 index 00000000000..18c192e0572 --- /dev/null +++ b/tools/windows-replace/tests/chains/Zilliqa/SignerTests.cpp @@ -0,0 +1,113 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "Zilliqa/Address.h" +#include "Zilliqa/Signer.h" +#include "proto/Zilliqa.pb.h" +#include "uint256.h" + +#include + +namespace TW::Zilliqa::tests { + +TEST(ZilliqaSigner, PreImage) { + auto privateKey = PrivateKey(parse_hex("0E891B9DFF485000C7D1DC22ECF3A583CC50328684321D61947A86E57CF6C638")); + auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + ASSERT_EQ(hex(pubKey.bytes), "034ae47910d58b9bde819c3cffa8de4441955508db00aa2540db8e6bf6e99abc1b"); + + auto amount = uint256_t(15000000000000); + auto gasPrice = uint256_t(1000000000); + auto amountData = store(amount); + auto gasData = store(gasPrice); + auto toAddress = Address(parse_hex("0x9Ca91EB535Fb92Fda5094110FDaEB752eDb9B039")); + + auto input = Proto::SigningInput(); + auto& tx = *input.mutable_transaction(); + auto& transfer = *tx.mutable_transfer(); + transfer.set_amount(amountData.data(), amountData.size()); + + input.set_version(65537); + input.set_nonce(4); + input.set_to(toAddress.string()); + input.set_gas_price(gasData.data(), gasData.size()); + input.set_gas_limit(uint64_t(1)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + Address address; + auto preImage = Signer::getPreImage(input, address); + auto signature = Signer::sign(input).signature(); + + ASSERT_EQ(hex(preImage.begin(), preImage.end()), "0881800410041a149ca91eb535fb92fda5094110fdaeb752edb9b03922230a21034ae47910d58b9bde819c3cffa8de4441955508db00aa2540db8e6bf6e99abc1b2a120a10000000000000000000000da475abf00032120a100000000000000000000000003b9aca003801"); + + ASSERT_TRUE(pubKey.verifyZilliqa(Data(signature.begin(), signature.end()), preImage)); +} + +TEST(ZilliqaSigner, Signing) { + auto privateKey = PrivateKey(parse_hex("0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748")); + auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + + // 1 ZIL + auto amount = uint256_t(1000000000000); + auto gasPrice = uint256_t(1000000000); + auto amountData = store(amount); + auto gasData = store(gasPrice); + auto toAddress = Address(parse_hex("0x7FCcaCf066a5F26Ee3AFfc2ED1FA9810Deaa632C")); + + auto input = Proto::SigningInput(); + auto& tx = *input.mutable_transaction(); + auto& transfer = *tx.mutable_transfer(); + transfer.set_amount(amountData.data(), amountData.size()); + + input.set_version(65537); + input.set_nonce(2); + input.set_to(toAddress.string()); + input.set_gas_price(gasData.data(), gasData.size()); + input.set_gas_limit(uint64_t(1)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.signature().begin(), output.signature().end()), "001fa4df08c11a4a79e96e69399ee48eeecc78231a78b0355a8ca783c77c139436e37934fecc2252ed8dac00e235e22d18410461fb896685c4270642738ed268"); + ASSERT_EQ(output.json(), R"({"amount":"1000000000000","code":"","data":"","gasLimit":"1","gasPrice":"1000000000","nonce":2,"pubKey":"03fb30b196ce3e976593ecc2da220dca9cdea8c84d2373770042a930b892ac0f5c","signature":"001fa4df08c11a4a79e96e69399ee48eeecc78231a78b0355a8ca783c77c139436e37934fecc2252ed8dac00e235e22d18410461fb896685c4270642738ed268","toAddr":"7FCcaCf066a5F26Ee3AFfc2ED1FA9810Deaa632C","version":65537})"); +} + +TEST(ZilliqaSigner, SigningData) { + // https://viewblock.io/zilliqa/tx/0x6228b3d7e69fc3481b84fd00e892cec359a41654f58948ff7b1b932396b00ad9 + auto privateKey = PrivateKey(parse_hex("0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748")); + auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + + // 10 ZIL + auto amount = uint256_t(10000000000000); + auto gasPrice = uint256_t(2000000000); + auto amountData = store(amount); + auto gasData = store(gasPrice); + + std::string json = "{\"_tag\":\"DelegateStake\",\"params\":[{\"type\":\"ByStr20\",\"value\":\"0x122219cCeAb410901e96c3A0e55E46231480341b\",\"vname\":\"ssnaddr\"}]}"; + auto jsonData = Data(json.begin(), json.end()); + + auto input = Proto::SigningInput(); + auto& tx = *input.mutable_transaction(); + auto& raw = *tx.mutable_raw_transaction(); + raw.set_amount(amountData.data(), amountData.size()); + raw.set_data(jsonData.data(), jsonData.size()); + + input.set_version(65537); + input.set_nonce(56); + input.set_to("zil1g029nmzsf36r99vupp4s43lhs40fsscx3jjpuy"); + input.set_gas_price(gasData.data(), gasData.size()); + input.set_gas_limit(uint64_t(5000)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto output = Signer::sign(input); + //ASSERT_EQ(output.json(), R"({"amount":"10000000000000","code":"","data":"{\"_tag\":\"DelegateStake\",\"params\":[{\"type\":\"ByStr20\",\"value\":\"0x122219cCeAb410901e96c3A0e55E46231480341b\",\"vname\":\"ssnaddr\"}]}","gasLimit":"5000","gasPrice":"2000000000","nonce":56,"pubKey":"03fb30b196ce3e976593ecc2da220dca9cdea8c84d2373770042a930b892ac0f5c","signature":"437fb5c3ce2c6b01f9d490f670539fae4533c82a21fa7edfe6b23df70d732937e8c578c8d6ed24be9150f5126f7b7c977a467af8947ef92a720908a761a6eb0d","toAddr":"43D459eC504C7432959c086B0ac7F7855E984306","version":65537})"); + ASSERT_EQ(output.json(), "{\"amount\":\"10000000000000\",\"code\":\"\",\"data\":\"{\\\"_tag\\\":\\\"DelegateStake\\\",\\\"params\\\":[{\\\"type\\\":\\\"ByStr20\\\",\\\"value\\\":\\\"0x122219cCeAb410901e96c3A0e55E46231480341b\\\",\\\"vname\\\":\\\"ssnaddr\\\"}]}\",\"gasLimit\":\"5000\",\"gasPrice\":\"2000000000\",\"nonce\":56,\"pubKey\":\"03fb30b196ce3e976593ecc2da220dca9cdea8c84d2373770042a930b892ac0f5c\",\"signature\":\"437fb5c3ce2c6b01f9d490f670539fae4533c82a21fa7edfe6b23df70d732937e8c578c8d6ed24be9150f5126f7b7c977a467af8947ef92a720908a761a6eb0d\",\"toAddr\":\"43D459eC504C7432959c086B0ac7F7855E984306\",\"version\":65537}");//win + + ASSERT_EQ(hex(output.signature().begin(), output.signature().end()), "437fb5c3ce2c6b01f9d490f670539fae4533c82a21fa7edfe6b23df70d732937e8c578c8d6ed24be9150f5126f7b7c977a467af8947ef92a720908a761a6eb0d"); +} + +} // namespace TW::Zilliqa::tests \ No newline at end of file diff --git a/tools/windows-replace/tests/common/BCSTests.cpp b/tools/windows-replace/tests/common/BCSTests.cpp new file mode 100644 index 00000000000..fee7c0805df --- /dev/null +++ b/tools/windows-replace/tests/common/BCSTests.cpp @@ -0,0 +1,165 @@ +// Copyright © 2017-2022 Trust Wallet. +// Created by Clément Doumergue +// +// 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 "BCS.h" +#include "HexCoding.h" + +#include + +namespace TW::BCS::tests { + +TEST(BCS, Integral) { + Serializer os; + os << uint32_t(0xAABBCCDD); + ASSERT_EQ(os.bytes, parse_hex("0xDDCCBBAA")); + + os.clear(); + os << int32_t(-305419896); + ASSERT_EQ(os.bytes, parse_hex("0x88A9CBED")); +} + +TEST(BCS, ULEB128) { + Serializer os; + os << uleb128{0x00000001}; + ASSERT_EQ(os.bytes, parse_hex("0x01")); + + os.clear(); + os << uleb128{0x00000080}; + ASSERT_EQ(os.bytes, parse_hex("0x8001")); + + os.clear(); + os << uleb128{0x00004000}; + ASSERT_EQ(os.bytes, parse_hex("0x808001")); + + os.clear(); + os << uleb128{0x00200000}; + ASSERT_EQ(os.bytes, parse_hex("0x80808001")); + + os.clear(); + os << uleb128{0x10000000}; + ASSERT_EQ(os.bytes, parse_hex("0x8080808001")); + + os.clear(); + os << uleb128{0x0000250F}; + ASSERT_EQ(os.bytes, parse_hex("0x8F4A")); +} + +TEST(BCS, String) { + Serializer os; + os << std::string_view("abcd"); + ASSERT_EQ(os.bytes, parse_hex("0x0461626364")); + + os.clear(); + os << std::string_view(""); + ASSERT_EQ(os.bytes, parse_hex("0x00")); +} + +TEST(BCS, Optional) { + Serializer os; + os << std::optional{0xBBCCDD}; + ASSERT_EQ(os.bytes, parse_hex("0x01DDCCBB00")); + + os.clear(); + os << std::optional{}; + ASSERT_EQ(os.bytes, parse_hex("0x00")); + + os.clear(); + os << std::nullopt; + ASSERT_EQ(os.bytes, parse_hex("0x00")); +} + +TEST(BCS, Tuple) { + Serializer os; + os << std::tuple{uint16_t(1), 'a'}; + ASSERT_EQ(os.bytes, parse_hex("0x010061")); + + os.clear(); + os << std::tuple{std::optional{123}, std::string_view("abcd"), uint8_t(0x0E)}; + ASSERT_EQ(os.bytes, parse_hex("0x017b00000004616263640e")); + + os.clear(); + os << std::tuple{}; + ASSERT_EQ(os.bytes, (Data{})); +} + +TEST(BCS, Pair) { + Serializer os; + os << std::pair{uint16_t(1), 'a'}; + ASSERT_EQ(os.bytes, parse_hex("0x010061")); + + os.clear(); + os << std::pair{std::optional{123}, std::string_view("abcd")}; + ASSERT_EQ(os.bytes, parse_hex("0x017b0000000461626364")); +} +/* +struct my_struct { + std::optional first; + std::string_view second; + uint8_t third; +}; + +TEST(BCS, Struct) { + Serializer os; + os << my_struct{{123}, "abcd", 0x0E}; + ASSERT_EQ(os.bytes, parse_hex("0x017b00000004616263640e")); +} +*/ +TEST(BCS, Variant) { + using V = std::variant; + + Serializer os; + os << V{uint32_t(1)}; + ASSERT_EQ(os.bytes, parse_hex("0x0001000000")); + + os.clear(); + os << V{char('a')}; + ASSERT_EQ(os.bytes, parse_hex("0x0161")); + + os.clear(); + os << V{true}; + ASSERT_EQ(os.bytes, parse_hex("0x0201")); +} + +TEST(BCS, Map) { + Serializer os; + os << std::map{{'a', 0}, {'b', 1}, {'c', 2}}; + ASSERT_EQ(os.bytes, parse_hex("0x03610062016302")); +} + +class my_number { +private: + int value; + +public: + explicit my_number(int value) noexcept + : value(value) { + } + + [[nodiscard]] auto get_value() const { + return value; + } +}; + +Serializer& operator<<(Serializer& stream, my_number n) noexcept { + return stream << n.get_value(); +} + +static_assert(CustomSerializable, "my_number does not model the CustomSerializable concept"); + +TEST(BCS, Custom) { + Serializer os; + os << my_number{0xBBCCDD}; + ASSERT_EQ(os.bytes, parse_hex("0xDDCCBB00")); +} + +TEST(BCS, Vector) { + Serializer os; + os << std::vector{1}; + ASSERT_EQ(os.bytes, parse_hex("0101")); +} + +} diff --git a/tools/windows-replace/tests/main.cpp b/tools/windows-replace/tests/main.cpp new file mode 100644 index 00000000000..136462e0cce --- /dev/null +++ b/tools/windows-replace/tests/main.cpp @@ -0,0 +1,61 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +#include + +#ifdef WIN32 +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +#include +std::string TESTS_ROOT; + +int main(int argc, char **argv) { + if (argc < 2) { + std::cerr << "Please specify the tests root folder." << std::endl; + exit(1); + } + + TESTS_ROOT = argv[1]; + struct stat s; + if (stat(TESTS_ROOT.c_str(), &s) != 0 || (s.st_mode & S_IFDIR) == 0) { + std::cerr << "Please specify the tests root folder. '" << TESTS_ROOT << "' is not a valid directory." << std::endl; + exit(1); + } + std::cout<<"TESTS_ROOT: "< +#include + + +std::string TESTS_ROOT; + +int main(int argc, char** argv) { + + // current path + auto path = std::filesystem::current_path(); + // executable path + path.append(argv[0]); + // normalize + path = std::filesystem::canonical(path); + std::cout<<"normaliz path: "< + $ + PRIVATE + src +) + +install( + TARGETS TrezorCrypto + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/tools/windows-replace/trezor-crypto/crypto/base32.c b/tools/windows-replace/trezor-crypto/crypto/base32.c new file mode 100644 index 00000000000..d1cb294c77f --- /dev/null +++ b/tools/windows-replace/trezor-crypto/crypto/base32.c @@ -0,0 +1,245 @@ +/** + * Copyright (c) 2017 Saleem Rashid + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, E1PRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include + +#include + +const char *BASE32_ALPHABET_RFC4648 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ23456789"; + +static inline void base32_5to8(const uint8_t *in, uint8_t length, uint8_t *out); +static inline bool base32_8to5(const uint8_t *in, uint8_t length, uint8_t *out, + const char *alphabet); +static inline void base32_8to5_raw(const uint8_t *in, uint8_t length, + uint8_t *out); + +static inline int base32_encode_character(uint8_t decoded, + const char *alphabet); +static inline int base32_decode_character(char encoded, const char *alphabet); + +char *base32_encode(const uint8_t *in, size_t inlen, char *out, size_t outlen, + const char *alphabet) { + size_t length = base32_encoded_length(inlen); + if (outlen <= length) { + return NULL; + } + + base32_encode_unsafe(in, inlen, (uint8_t *)out); + + for (size_t i = 0; i < length; i++) { + int ret = base32_encode_character(out[i], alphabet); + + if (ret == -1) { + return false; + } else { + out[i] = ret; + } + } + + out[length] = '\0'; + return &out[length]; +} + +uint8_t *base32_decode(const char *in, size_t inlen, uint8_t *out, + size_t outlen, const char *alphabet) { + size_t length = base32_decoded_length(inlen); + if (outlen < length) { + return NULL; + } + + if (!base32_decode_unsafe((uint8_t *)in, inlen, (uint8_t *)out, alphabet)) { + return NULL; + } + + return &out[length]; +} + +void base32_encode_unsafe(const uint8_t *in, size_t inlen, uint8_t *out) { + uint8_t remainder = inlen % 5; + size_t limit = inlen - remainder; + + size_t i = 0, j = 0; + for (i = 0, j = 0; i < limit; i += 5, j += 8) { + base32_5to8(&in[i], 5, &out[j]); + } + + if (remainder) base32_5to8(&in[i], remainder, &out[j]); +} + +bool base32_decode_unsafe(const uint8_t *in, size_t inlen, uint8_t *out, + const char *alphabet) { + uint8_t remainder = inlen % 8; + size_t limit = inlen - remainder; + + size_t i = 0, j = 0; + for (i = 0, j = 0; i < limit; i += 8, j += 5) { + if (!base32_8to5(&in[i], 8, &out[j], alphabet)) { + return false; + } + } + + if (remainder && !base32_8to5(&in[i], remainder, &out[j], alphabet)) { + return false; + } + + return true; +} + +size_t base32_encoded_length(size_t inlen) { + uint8_t remainder = inlen % 5; + + return (inlen / 5) * 8 + (remainder * 8 + 4) / 5; +} + +size_t base32_decoded_length(size_t inlen) { + uint8_t remainder = inlen % 8; + + return (inlen / 8) * 5 + (remainder * 5) / 8; +} + +void base32_5to8(const uint8_t *in, uint8_t length, uint8_t *out) { + if (length >= 1) { + out[0] = (in[0] >> 3); + out[1] = (in[0] & 7) << 2; + } + + if (length >= 2) { + out[1] |= (in[1] >> 6); + out[2] = (in[1] >> 1) & 31; + out[3] = (in[1] & 1) << 4; + } + + if (length >= 3) { + out[3] |= (in[2] >> 4); + out[4] = (in[2] & 15) << 1; + } + + if (length >= 4) { + out[4] |= (in[3] >> 7); + out[5] = (in[3] >> 2) & 31; + out[6] = (in[3] & 3) << 3; + } + + if (length >= 5) { + out[6] |= (in[4] >> 5); + out[7] = (in[4] & 31); + } +} + +bool base32_8to5(const uint8_t *in, uint8_t length, uint8_t *out, + const char *alphabet) { + if (length == 1 || length == 3 || length == 6 || length > 8) { + return false; + } + + if (alphabet) { + #ifdef _MSC_VER + uint8_t *decoded = _alloca(length); + #else + uint8_t decoded[length]; + #endif + memset(decoded, 0, length); //win memset(decoded, 0, sizeof(decoded)); + + for (size_t i = 0; i < length; i++) { + int ret = base32_decode_character(in[i], alphabet); + + if (ret == -1) { + return false; + } else { + decoded[i] = ret; + } + } + + base32_8to5_raw(decoded, length, out); + } else { + base32_8to5_raw(in, length, out); + } + + return true; +} + +void base32_8to5_raw(const uint8_t *in, uint8_t length, uint8_t *out) { + if (length >= 2) { + out[0] = (in[0] << 3); + out[0] |= (in[1] >> 2); + } + + if (length >= 4) { + out[1] = (in[1] & 3) << 6; + out[1] |= (in[2] << 1); + out[1] |= (in[3] >> 4); + } + + if (length >= 5) { + out[2] = (in[3] & 15) << 4; + out[2] |= (in[4] >> 1); + } + + if (length >= 7) { + out[3] = (in[4] & 1) << 7; + out[3] |= (in[5] << 2); + out[3] |= (in[6] >> 3); + } + + if (length >= 8) { + out[4] = (in[6] & 7) << 5; + out[4] |= (in[7] & 31); + } +} + +int base32_encode_character(uint8_t decoded, const char *alphabet) { + if (decoded >> 5) { + return -1; + } + + if (alphabet == BASE32_ALPHABET_RFC4648) { + if (decoded < 26) { + return 'A' + decoded; + } else { + return '2' - 26 + decoded; + } + } + + return alphabet[decoded]; +} + +int base32_decode_character(char encoded, const char *alphabet) { + if (alphabet == BASE32_ALPHABET_RFC4648) { + if (encoded >= 'A' && encoded <= 'Z') { + return encoded - 'A'; + } else if (encoded >= 'a' && encoded <= 'z') { + return encoded - 'a'; + } else if (encoded >= '2' && encoded <= '7') { + return encoded - '2' + 26; + } else { + return -1; + } + } + + const char *occurrence = strchr(alphabet, encoded); + + if (occurrence) { + return occurrence - alphabet; + } else { + return -1; + } +} diff --git a/tools/windows-replace/trezor-crypto/crypto/base58.c b/tools/windows-replace/trezor-crypto/crypto/base58.c new file mode 100644 index 00000000000..4c870cd50e5 --- /dev/null +++ b/tools/windows-replace/trezor-crypto/crypto/base58.c @@ -0,0 +1,294 @@ +/** + * Copyright (c) 2012-2014 Luke Dashjr + * Copyright (c) 2013-2014 Pavol Rusnak + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +const char b58digits_ordered[] = + "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; +const int8_t b58digits_map[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, + 8, -1, -1, -1, -1, -1, -1, -1, 9, 10, 11, 12, 13, 14, 15, 16, -1, 17, 18, + 19, 20, 21, -1, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, -1, + -1, -1, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, -1, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, -1, -1, -1, -1, +}; + +typedef uint64_t b58_maxint_t; +typedef uint32_t b58_almostmaxint_t; +#define b58_almostmaxint_bits (sizeof(b58_almostmaxint_t) * 8) +const b58_almostmaxint_t b58_almostmaxint_mask = + ((((b58_maxint_t)1) << b58_almostmaxint_bits) - 1); + +// Decodes a null-terminated Base58 string `b58` to binary and writes the result +// at the end of the buffer `bin` of size `*binszp`. On success `*binszp` is set +// to the number of valid bytes at the end of the buffer. +bool b58tobin(void *bin, size_t *binszp, const char *b58) { + size_t binsz = *binszp; + + if (binsz == 0) { + return false; + } + + const unsigned char *b58u = (const unsigned char *)b58; + unsigned char *binu = bin; + size_t outisz = + (binsz + sizeof(b58_almostmaxint_t) - 1) / sizeof(b58_almostmaxint_t); + //win + #ifdef _MSC_VER + b58_almostmaxint_t *outi = _alloca(sizeof(b58_almostmaxint_t) * outisz); +#else + b58_almostmaxint_t outi[outisz]; +#endif +//win + b58_maxint_t t = 0; + b58_almostmaxint_t c = 0; + size_t i = 0, j = 0; + uint8_t bytesleft = binsz % sizeof(b58_almostmaxint_t); + b58_almostmaxint_t zeromask = + bytesleft ? (b58_almostmaxint_mask << (bytesleft * 8)) : 0; + unsigned zerocount = 0; + + size_t b58sz = strlen(b58); + + memzero(outi, sizeof(b58_almostmaxint_t) * outisz);//win + + // Leading zeros, just count + for (i = 0; i < b58sz && b58u[i] == '1'; ++i) ++zerocount; + + for (; i < b58sz; ++i) { + if (b58u[i] & 0x80) + // High-bit set on invalid digit + return false; + if (b58digits_map[b58u[i]] == -1) + // Invalid base58 digit + return false; + c = (unsigned)b58digits_map[b58u[i]]; + for (j = outisz; j--;) { + t = ((b58_maxint_t)outi[j]) * 58 + c; + c = t >> b58_almostmaxint_bits; + outi[j] = t & b58_almostmaxint_mask; + } + if (c) + // Output number too big (carry to the next int32) + return false; + if (outi[0] & zeromask) + // Output number too big (last int32 filled too far) + return false; + } + + j = 0; + if (bytesleft) { + for (i = bytesleft; i > 0; --i) { + *(binu++) = (outi[0] >> (8 * (i - 1))) & 0xff; + } + ++j; + } + + for (; j < outisz; ++j) { + for (i = sizeof(*outi); i > 0; --i) { + *(binu++) = (outi[j] >> (8 * (i - 1))) & 0xff; + } + } + + // locate the most significant byte + binu = bin; + for (i = 0; i < binsz; ++i) { + if (binu[i]) break; + } + + // prepend the correct number of null-bytes + if (zerocount > i) { + /* result too large */ + return false; + } + *binszp = binsz - i + zerocount; + + return true; +} + +int b58check(const void *bin, size_t binsz, HasherType hasher_type, + const char *base58str) { + unsigned char buf[32] = {0}; + const uint8_t *binc = bin; + unsigned i = 0; + if (binsz < 4) return -4; + hasher_Raw(hasher_type, bin, binsz - 4, buf); + if (memcmp(&binc[binsz - 4], buf, 4)) return -1; + + // Check number of zeros is correct AFTER verifying checksum (to avoid + // possibility of accessing base58str beyond the end) + for (i = 0; binc[i] == '\0' && base58str[i] == '1'; ++i) { + } // Just finding the end of zeros, nothing to do in loop + if (binc[i] == '\0' || base58str[i] == '1') return -3; + + return binc[0]; +} + +bool b58enc(char *b58, size_t *b58sz, const void *data, size_t binsz) { + const uint8_t *bin = data; + int carry = 0; + size_t i = 0, j = 0, high = 0, zcount = 0; + size_t size = 0; + + while (zcount < binsz && !bin[zcount]) ++zcount; + + size = (binsz - zcount) * 138 / 100 + 1; + #ifdef _MSC_VER + uint8_t *buf = _alloca(size); +#else + uint8_t buf[size]; +#endif + memzero(buf, size); + + + for (i = zcount, high = size - 1; i < binsz; ++i, high = j) { + for (carry = bin[i], j = size - 1; (j > high) || carry; --j) { + carry += 256 * buf[j]; + buf[j] = carry % 58; + carry /= 58; + if (!j) { + // Otherwise j wraps to maxint which is > high + break; + } + } + } + + for (j = 0; j < size && !buf[j]; ++j) + ; + + if (*b58sz <= zcount + size - j) { + *b58sz = zcount + size - j + 1; + return false; + } + + if (zcount) memset(b58, '1', zcount); + for (i = zcount; j < size; ++i, ++j) b58[i] = b58digits_ordered[buf[j]]; + b58[i] = '\0'; + *b58sz = i + 1; + + return true; +} + +int base58_encode_check(const uint8_t *data, int datalen, + HasherType hasher_type, char *str, int strsize) { + if (datalen > 128) { + return 0; + } + +#ifdef _MSC_VER + uint8_t *buf = _alloca((size_t)datalen + 32); +#else + uint8_t buf[datalen + 32]; +#endif + memset(buf, 0, (size_t)datalen + 32);//win memset(buf, 0, (size_t)datalen + 32); + uint8_t *hash = buf + datalen; + memcpy(buf, data, datalen); + hasher_Raw(hasher_type, data, datalen, hash); + size_t res = strsize; + bool success = b58enc(str, &res, buf, datalen + 4); + memzero(buf, (size_t)datalen + 32); //win memzero(buf, (size_t)datalen + 32); + return success ? res : 0; +} + +int base58_decode_check(const char *str, HasherType hasher_type, uint8_t *data, + int datalen) { + if (datalen > 128) { + return 0; + } + #ifdef _MSC_VER + uint8_t *d = _alloca((size_t)datalen + 4); +#else + uint8_t d[datalen + 4]; + #endif + memset(d, 0, (size_t)datalen + 4); //win memset(d, 0, sizeof(d)); + size_t res = datalen + 4; + if (b58tobin(d, &res, str) != true) { + return 0; + } + uint8_t *nd = d + datalen + 4 - res; + if (b58check(nd, res, hasher_type, str) < 0) { + return 0; + } + memcpy(data, nd, res - 4); + return res - 4; +} + +#if USE_GRAPHENE +int b58gphcheck(const void *bin, size_t binsz, const char *base58str) { + unsigned char buf[32] = {0}; + const uint8_t *binc = bin; + unsigned i = 0; + if (binsz < 4) return -4; + ripemd160(bin, binsz - 4, buf); // No double SHA256, but a single RIPEMD160 + if (memcmp(&binc[binsz - 4], buf, 4)) return -1; + + // Check number of zeros is correct AFTER verifying checksum (to avoid + // possibility of accessing base58str beyond the end) + for (i = 0; binc[i] == '\0' && base58str[i] == '1'; ++i) { + } // Just finding the end of zeros, nothing to do in loop + if (binc[i] == '\0' || base58str[i] == '1') return -3; + + return binc[0]; +} + +int base58gph_encode_check(const uint8_t *data, int datalen, char *str, + int strsize) { + if (datalen > 128) { + return 0; + } + uint8_t buf[datalen + 32]; + memset(buf, 0, sizeof(buf)); + uint8_t *hash = buf + datalen; + memcpy(buf, data, datalen); + ripemd160(data, datalen, hash); // No double SHA256, but a single RIPEMD160 + size_t res = strsize; + bool success = b58enc(str, &res, buf, datalen + 4); + memzero(buf, sizeof(buf)); + return success ? res : 0; +} + +int base58gph_decode_check(const char *str, uint8_t *data, int datalen) { + if (datalen > 128) { + return 0; + } + uint8_t d[datalen + 4]; + memset(d, 0, sizeof(d)); + size_t res = datalen + 4; + if (b58tobin(d, &res, str) != true) { + return 0; + } + uint8_t *nd = d + datalen + 4 - res; + if (b58gphcheck(nd, res, str) < 0) { + return 0; + } + memcpy(data, nd, res - 4); + return res - 4; +} +#endif diff --git a/tools/windows-replace/trezor-crypto/crypto/bignum.c b/tools/windows-replace/trezor-crypto/crypto/bignum.c new file mode 100644 index 00000000000..be739c611e5 --- /dev/null +++ b/tools/windows-replace/trezor-crypto/crypto/bignum.c @@ -0,0 +1,1841 @@ +/** + * Copyright (c) 2013-2014 Tomas Dzetkulic + * Copyright (c) 2013-2014 Pavol Rusnak + * Copyright (c) 2015 Jochen Hoenicke + * Copyright (c) 2016 Alex Beregszaszi + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include + +#include +#include +#include +#include + +#include +#include + +/* + This library implements 256-bit numbers arithmetic. + + An unsigned 256-bit number is represented by a bignum256 structure, that is an + array of nine 32-bit values called limbs. Limbs are digits of the number in + the base 2**29 representation in the little endian order. This means that + bignum256 x; + represents the value + sum([x[i] * 2**(29*i) for i in range(9)). + + A limb of a bignum256 is *normalized* iff it's less than 2**29. + A bignum256 is *normalized* iff every its limb is normalized. + A number is *fully reduced modulo p* iff it is less than p. + A number is *partly reduced modulo p* iff is is less than 2*p. + The number p is usually a prime number such that 2^256 - 2^224 <= p <= 2^256. + + All functions except bn_fast_mod expect that all their bignum256 inputs are + normalized. (The function bn_fast_mod allows the input number to have the + most significant limb unnormalized). All bignum256 outputs of all functions + are guaranteed to be normalized. + + A number can be partly reduced with bn_fast_mod, a partly reduced number can + be fully reduced with bn_mod. + + A function has *constant control flow with regard to its argument* iff the + order in which instructions of the function are executed doesn't depend on the + value of the argument. + A function has *constant memory access flow with regard to its argument* iff + the memory addresses that are acessed and the order in which they are accessed + don't depend on the value of the argument. + A function *has contant control (memory access) flow* iff it has constant + control (memory access) flow with regard to all its arguments. + + The following function has contant control flow with regard to its arugment + n, however is doesn't have constant memory access flow with regard to it: + void (int n, int *a) } + a[0] = 0; + a[n] = 0; // memory address reveals the value of n + } + + Unless stated otherwise all functions are supposed to have both constant + control flow and constant memory access flow. + */ + +#define BN_MAX_DECIMAL_DIGITS \ + 79 // floor(log(2**(LIMBS * BITS_PER_LIMB), 10)) + 1 + +// out_number = (bignum256) in_number +// Assumes in_number is a raw bigendian 256-bit number +// Guarantees out_number is normalized +void bn_read_be(const uint8_t *in_number, bignum256 *out_number) { + uint32_t temp = 0; + + for (int i = 0; i < BN_LIMBS - 1; i++) { + uint32_t limb = read_be(in_number + (BN_LIMBS - 2 - i) * 4); + + temp |= limb << (BN_EXTRA_BITS * i); + out_number->val[i] = temp & BN_LIMB_MASK; + + temp = limb >> (32 - BN_EXTRA_BITS * (i + 1)); + } + + out_number->val[BN_LIMBS - 1] = temp; +} + +// out_number = (256BE) in_number +// Assumes in_number < 2**256 +// Guarantess out_number is a raw bigendian 256-bit number +void bn_write_be(const bignum256 *in_number, uint8_t *out_number) { + uint32_t temp = in_number->val[BN_LIMBS - 1]; + for (int i = BN_LIMBS - 2; i >= 0; i--) { + uint32_t limb = in_number->val[i]; + + temp = (temp << (BN_BITS_PER_LIMB - BN_EXTRA_BITS * i)) | + (limb >> (BN_EXTRA_BITS * i)); + write_be(out_number + (BN_LIMBS - 2 - i) * 4, temp); + + temp = limb; + } +} + +// out_number = (bignum256) in_number +// Assumes in_number is a raw little endian 256-bit number +// Guarantees out_number is normalized +void bn_read_le(const uint8_t *in_number, bignum256 *out_number) { + uint32_t temp = 0; + for (int i = 0; i < BN_LIMBS - 1; i++) { + uint32_t limb = read_le(in_number + i * 4); + + temp |= limb << (BN_EXTRA_BITS * i); + out_number->val[i] = temp & BN_LIMB_MASK; + temp = limb >> (32 - BN_EXTRA_BITS * (i + 1)); + } + + out_number->val[BN_LIMBS - 1] = temp; +} + +// out_number = (256LE) in_number +// Assumes in_number < 2**256 +// Guarantess out_number is a raw little endian 256-bit number +void bn_write_le(const bignum256 *in_number, uint8_t *out_number) { + uint32_t temp = in_number->val[BN_LIMBS - 1]; + + for (int i = BN_LIMBS - 2; i >= 0; i--) { + uint32_t limb = in_number->val[i]; + temp = (temp << (BN_BITS_PER_LIMB - BN_EXTRA_BITS * i)) | + (limb >> (BN_EXTRA_BITS * i)); + write_le(out_number + i * 4, temp); + temp = limb; + } +} + +// out_number = (bignum256) in_number +// Guarantees out_number is normalized +void bn_read_uint32(uint32_t in_number, bignum256 *out_number) { + out_number->val[0] = in_number & BN_LIMB_MASK; + out_number->val[1] = in_number >> BN_BITS_PER_LIMB; + for (uint32_t i = 2; i < BN_LIMBS; i++) out_number->val[i] = 0; +} + +// out_number = (bignum256) in_number +// Guarantees out_number is normalized +void bn_read_uint64(uint64_t in_number, bignum256 *out_number) { + out_number->val[0] = in_number & BN_LIMB_MASK; + out_number->val[1] = (in_number >>= BN_BITS_PER_LIMB) & BN_LIMB_MASK; + out_number->val[2] = in_number >> BN_BITS_PER_LIMB; + for (uint32_t i = 3; i < BN_LIMBS; i++) out_number->val[i] = 0; +} +//win +#ifdef _MSC_VER +#include +uint32_t __forceinline bn_clz(uint32_t value) +{ + unsigned long leading_zero = 0; + if (_BitScanReverse(&leading_zero, value)) + return 31 - leading_zero; + else + return 32; +} +#else +#define bn_clz __builtin_clz +#endif + +// Returns the bitsize of x +// Assumes x is normalized +// The function doesn't have neither constant control flow nor constant memory +// access flow +int bn_bitcount(const bignum256 *x) { + for (int i = BN_LIMBS - 1; i >= 0; i--) { + uint32_t limb = x->val[i]; + if (limb != 0) { + // __builtin_clz returns the number of leading zero bits starting at the + // most significant bit position + return i * BN_BITS_PER_LIMB + (32 - bn_clz(limb)); //win return i * BN_BITS_PER_LIMB + (32 - __builtin_clz(limb)); + } + } + return 0; +} + +// Returns the number of decimal digits of x; if x is 0, returns 1 +// Assumes x is normalized +// The function doesn't have neither constant control flow nor constant memory +// access flow +unsigned int bn_digitcount(const bignum256 *x) { + bignum256 val = {0}; + bn_copy(x, &val); + + unsigned int digits = 1; + for (unsigned int i = 0; i < BN_MAX_DECIMAL_DIGITS; i += 3) { + uint32_t limb = 0; + + bn_divmod1000(&val, &limb); + + if (limb >= 100) { + digits = i + 3; + } else if (limb >= 10) { + digits = i + 2; + } else if (limb >= 1) { + digits = i + 1; + } + } + + memzero(&val, sizeof(val)); + + return digits; +} + +// x = 0 +// Guarantees x is normalized +void bn_zero(bignum256 *x) { + for (int i = 0; i < BN_LIMBS; i++) { + x->val[i] = 0; + } +} + +// x = 1 +// Guarantees x is normalized +void bn_one(bignum256 *x) { + x->val[0] = 1; + for (int i = 1; i < BN_LIMBS; i++) { + x->val[i] = 0; + } +} + +// Returns x == 0 +// Assumes x is normalized +int bn_is_zero(const bignum256 *x) { + uint32_t result = 0; + for (int i = 0; i < BN_LIMBS; i++) { + result |= x->val[i]; + } + return !result; +} + +// Returns x == 1 +// Assumes x is normalized +int bn_is_one(const bignum256 *x) { + uint32_t result = x->val[0] ^ 1; + for (int i = 1; i < BN_LIMBS; i++) { + result |= x->val[i]; + } + return !result; +} + +// Returns x < y +// Assumes x, y are normalized +int bn_is_less(const bignum256 *x, const bignum256 *y) { + uint32_t res1 = 0; + uint32_t res2 = 0; + for (int i = BN_LIMBS - 1; i >= 0; i--) { + res1 = (res1 << 1) | (x->val[i] < y->val[i]); + res2 = (res2 << 1) | (x->val[i] > y->val[i]); + } + return res1 > res2; +} + +// Returns x == y +// Assumes x, y are normalized +int bn_is_equal(const bignum256 *x, const bignum256 *y) { + uint32_t result = 0; + for (int i = 0; i < BN_LIMBS; i++) { + result |= x->val[i] ^ y->val[i]; + } + return !result; +} + +// res = cond if truecase else falsecase +// Assumes cond is either 0 or 1 +// Works properly even if &res == &truecase or &res == &falsecase or +// &truecase == &falsecase or &res == &truecase == &falsecase +void bn_cmov(bignum256 *res, volatile uint32_t cond, const bignum256 *truecase, + const bignum256 *falsecase) { + assert((int)(cond == 1) | (cond == 0)); + + uint32_t tmask = -cond; // tmask = 0xFFFFFFFF if cond else 0x00000000 + uint32_t fmask = ~tmask; // fmask = 0x00000000 if cond else 0xFFFFFFFF + + for (int i = 0; i < BN_LIMBS; i++) { + res->val[i] = (truecase->val[i] & tmask) | (falsecase->val[i] & fmask); + } +} + +// x = -x % prime if cond else x, +// Explicitly x = (3 * prime - x if x > prime else 2 * prime - x) if cond else +// else (x if x > prime else x + prime) +// Assumes x is normalized and partly reduced +// Assumes cond is either 1 or 0 +// Guarantees x is normalized +// Assumes prime is normalized and +// 0 < prime < 2**260 == 2**(BITS_PER_LIMB * LIMBS - 1) +void bn_cnegate(volatile uint32_t cond, bignum256 *x, const bignum256 *prime) { + assert((int)(cond == 1) | (cond == 0)); + + uint32_t tmask = -cond; // tmask = 0xFFFFFFFF if cond else 0x00000000 + uint32_t fmask = ~tmask; // fmask = 0x00000000 if cond else 0xFFFFFFFF + + bn_mod(x, prime); + // x < prime + + uint32_t acc1 = 1; + uint32_t acc2 = 0; + + for (int i = 0; i < BN_LIMBS; i++) { + acc1 += (BN_BASE - 1) + 2 * prime->val[i] - x->val[i]; + // acc1 neither overflows 32 bits nor underflows 0 + // Proof: + // acc1 + (BASE - 1) + 2 * prime[i] - x[i] + // >= (BASE - 1) - x >= (2**BITS_PER_LIMB - 1) - (2**BITS_PER_LIMB - 1) + // == 0 + // acc1 + (BASE - 1) + 2 * prime[i] - x[i] + // <= acc1 + (BASE - 1) + 2 * prime[i] + // <= (2**(32 - BITS_PER_LIMB) - 1) + 2 * (2**BITS_PER_LIMB - 1) + + // (2**BITS_PER_LIMB - 1) + // == 7 + 3 * 2**29 < 2**32 + + acc2 += prime->val[i] + x->val[i]; + // acc2 doesn't overflow 32 bits + // Proof: + // acc2 + prime[i] + x[i] + // <= 2**(32 - BITS_PER_LIMB) - 1 + 2 * (2**BITS_PER_LIMB - 1) + // == 2**(32 - BITS_PER_LIMB) + 2**(BITS_PER_LIMB + 1) - 2 + // == 2**30 + 5 < 2**32 + + // x = acc1 & LIMB_MASK if cond else acc2 & LIMB_MASK + x->val[i] = ((acc1 & tmask) | (acc2 & fmask)) & BN_LIMB_MASK; + + acc1 >>= BN_BITS_PER_LIMB; + // acc1 <= 7 == 2**(32 - BITS_PER_LIMB) - 1 + // acc1 == 2**(BITS_PER_LIMB * (i + 1)) + 2 * prime[:i + 1] - x[:i + 1] + // >> BITS_PER_LIMB * (i + 1) + + acc2 >>= BN_BITS_PER_LIMB; + // acc2 <= 7 == 2**(32 - BITS_PER_LIMB) - 1 + // acc2 == prime[:i + 1] + x[:i + 1] >> BITS_PER_LIMB * (i + 1) + } + + // assert(acc1 == 1); // assert prime <= 2**260 + // assert(acc2 == 0); + + // clang-format off + // acc1 == 1 + // Proof: + // acc1 == 2**(BITS_PER_LIMB * LIMBS) + 2 * prime[:LIMBS] - x[:LIMBS] >> BITS_PER_LIMB * LIMBS + // == 2**(BITS_PER_LIMB * LIMBS) + 2 * prime - x >> BITS_PER_LIMB * LIMBS + // <= 2**(BITS_PER_LIMB * LIMBS) + 2 * prime >> BITS_PER_LIMB * LIMBS + // <= 2**(BITS_PER_LIMB * LIMBS) + 2 * (2**(BITS_PER_LIMB * LIMBS - 1) - 1) >> BITS_PER_LIMB * LIMBS + // <= 2**(BITS_PER_LIMB * LIMBS) + 2**(BITS_PER_LIMB * LIMBS) - 2 >> BITS_PER_LIMB * LIMBS + // == 1 + + // acc1 == 2**(BITS_PER_LIMB * LIMBS) + 2 * prime[:LIMBS] - x[:LIMBS] >> BITS_PER_LIMB * LIMBS + // == 2**(BITS_PER_LIMB * LIMBS) + 2 * prime - x >> BITS_PER_LIMB * LIMBS + // >= 2**(BITS_PER_LIMB * LIMBS) + 0 >> BITS_PER_LIMB * LIMBS + // == 1 + + // acc2 == 0 + // Proof: + // acc2 == prime[:LIMBS] + x[:LIMBS] >> BITS_PER_LIMB * LIMBS + // == prime + x >> BITS_PER_LIMB * LIMBS + // <= 2 * prime - 1 >> BITS_PER_LIMB * LIMBS + // <= 2 * (2**(BITS_PER_LIMB * LIMBS - 1) - 1) - 1 >> 261 + // == 2**(BITS_PER_LIMB * LIMBS) - 3 >> BITS_PER_LIMB * LIMBS + // == 0 + // clang-format on +} + +// x <<= 1 +// Assumes x is normalized, x < 2**260 == 2**(LIMBS*BITS_PER_LIMB - 1) +// Guarantees x is normalized +void bn_lshift(bignum256 *x) { + for (int i = BN_LIMBS - 1; i > 0; i--) { + x->val[i] = ((x->val[i] << 1) & BN_LIMB_MASK) | + (x->val[i - 1] >> (BN_BITS_PER_LIMB - 1)); + } + x->val[0] = (x->val[0] << 1) & BN_LIMB_MASK; +} + +// x >>= 1, i.e. x = floor(x/2) +// Assumes x is normalized +// Guarantees x is normalized +// If x is partly reduced (fully reduced) modulo prime, +// guarantess x will be partly reduced (fully reduced) modulo prime +void bn_rshift(bignum256 *x) { + for (int i = 0; i < BN_LIMBS - 1; i++) { + x->val[i] = + (x->val[i] >> 1) | ((x->val[i + 1] & 1) << (BN_BITS_PER_LIMB - 1)); + } + x->val[BN_LIMBS - 1] >>= 1; +} + +// Sets i-th least significant bit (counting from zero) +// Assumes x is normalized and 0 <= i < 261 == LIMBS*BITS_PER_LIMB +// Guarantees x is normalized +// The function has constant control flow but not constant memory access flow +// with regard to i +void bn_setbit(bignum256 *x, uint16_t i) { + assert(i < BN_LIMBS * BN_BITS_PER_LIMB); + x->val[i / BN_BITS_PER_LIMB] |= (1u << (i % BN_BITS_PER_LIMB)); +} + +// clears i-th least significant bit (counting from zero) +// Assumes x is normalized and 0 <= i < 261 == LIMBS*BITS_PER_LIMB +// Guarantees x is normalized +// The function has constant control flow but not constant memory access flow +// with regard to i +void bn_clearbit(bignum256 *x, uint16_t i) { + assert(i < BN_LIMBS * BN_BITS_PER_LIMB); + x->val[i / BN_BITS_PER_LIMB] &= ~(1u << (i % BN_BITS_PER_LIMB)); +} + +// returns i-th least significant bit (counting from zero) +// Assumes x is normalized and 0 <= i < 261 == LIMBS*BITS_PER_LIMB +// The function has constant control flow but not constant memory access flow +// with regard to i +uint32_t bn_testbit(const bignum256 *x, uint16_t i) { + assert(i < BN_LIMBS * BN_BITS_PER_LIMB); + return (x->val[i / BN_BITS_PER_LIMB] >> (i % BN_BITS_PER_LIMB)) & 1; +} + +// res = x ^ y +// Assumes x, y are normalized +// Guarantees res is normalized +// Works properly even if &res == &x or &res == &y or &res == &x == &y +void bn_xor(bignum256 *res, const bignum256 *x, const bignum256 *y) { + for (int i = 0; i < BN_LIMBS; i++) { + res->val[i] = x->val[i] ^ y->val[i]; + } +} + +// x = x / 2 % prime +// Explicitly x = x / 2 if is_even(x) else (x + prime) / 2 +// Assumes x is normalized, x + prime < 261 == LIMBS * BITS_PER_LIMB +// Guarantees x is normalized +// If x is partly reduced (fully reduced) modulo prime, +// guarantess x will be partly reduced (fully reduced) modulo prime +// Assumes prime is an odd number and normalized +void bn_mult_half(bignum256 *x, const bignum256 *prime) { + // x = x / 2 if is_even(x) else (x + prime) / 2 + + uint32_t x_is_odd_mask = + -(x->val[0] & 1); // x_is_odd_mask = 0xFFFFFFFF if is_odd(x) else 0 + + uint32_t acc = (x->val[0] + (prime->val[0] & x_is_odd_mask)) >> 1; + // acc < 2**BITS_PER_LIMB + // Proof: + // acc == x[0] + prime[0] & x_is_odd_mask >> 1 + // <= (2**(BITS_PER_LIMB) - 1) + (2**(BITS_PER_LIMB) - 1) >> 1 + // == 2**(BITS_PER_LIMB + 1) - 2 >> 1 + // < 2**(BITS_PER_LIMB) + + for (int i = 0; i < BN_LIMBS - 1; i++) { + uint32_t temp = (x->val[i + 1] + (prime->val[i + 1] & x_is_odd_mask)); + // temp < 2**(BITS_PER_LIMB + 1) + // Proof: + // temp == x[i + 1] + val[i + 1] & x_is_odd_mask + // <= (2**(BITS_PER_LIMB) - 1) + (2**(BITS_PER_LIMB) - 1) + // < 2**(BITS_PER_LIMB + 1) + + acc += (temp & 1) << (BN_BITS_PER_LIMB - 1); + // acc doesn't overflow 32 bits + // Proof: + // acc + (temp & 1 << BITS_PER_LIMB - 1) + // <= 2**(BITS_PER_LIMB + 1) + 2**(BITS_PER_LIMB - 1) + // <= 2**30 + 2**28 < 2**32 + + x->val[i] = acc & BN_LIMB_MASK; + acc >>= BN_BITS_PER_LIMB; + acc += temp >> 1; + // acc < 2**(BITS_PER_LIMB + 1) + // Proof: + // acc + (temp >> 1) + // <= (2**(32 - BITS_PER_LIMB) - 1) + (2**(BITS_PER_LIMB + 1) - 1 >> 1) + // == 7 + 2**(BITS_PER_LIMB) - 1 < 2**(BITS_PER_LIMB + 1) + + // acc == x[:i+2]+(prime[:i+2] & x_is_odd_mask) >> BITS_PER_LIMB * (i+1) + } + x->val[BN_LIMBS - 1] = acc; + + // assert(acc >> BITS_PER_LIMB == 0); + // acc >> BITS_PER_LIMB == 0 + // Proof: + // acc + // == x[:LIMBS] + (prime[:LIMBS] & x_is_odd_mask) >> BITS_PER_LIMB*LIMBS + // == x + (prime & x_is_odd_mask) >> BITS_PER_LIMB * LIMBS + // <= x + prime >> BITS_PER_LIMB * LIMBS + // <= 2**(BITS_PER_LIMB * LIMBS) - 1 >> BITS_PER_LIMB * LIMBS + // == 0 +} + +// x = x * k % prime +// Assumes x is normalized, 0 <= k <= 8 = 2**(32 - BITS_PER_LIMB) +// Assumes prime is normalized and 2^256 - 2^224 <= prime <= 2^256 +// Guarantees x is normalized and partly reduced modulo prime +void bn_mult_k(bignum256 *x, uint8_t k, const bignum256 *prime) { + assert(k <= 8); + + for (int i = 0; i < BN_LIMBS; i++) { + x->val[i] = k * x->val[i]; + // x[i] doesn't overflow 32 bits + // k * x[i] <= 2**(32 - BITS_PER_LIMB) * (2**BITS_PER_LIMB - 1) + // < 2**(32 - BITS_PER_LIMB) * 2**BITS_PER_LIMB == 2**32 + } + + bn_fast_mod(x, prime); +} + +// Reduces partly reduced x modulo prime +// Explicitly x = x if x < prime else x - prime +// Assumes x is partly reduced modulo prime +// Guarantees x is fully reduced modulo prime +// Assumes prime is nonzero and normalized +void bn_mod(bignum256 *x, const bignum256 *prime) { + uint32_t x_less_prime = bn_is_less(x, prime); + + bignum256 temp = {0}; + bn_subtract(x, prime, &temp); + bn_cmov(x, x_less_prime, x, &temp); + + memzero(&temp, sizeof(temp)); +} + +// Auxiliary function for bn_multiply +// res = k * x +// Assumes k and x are normalized +// Guarantees res is normalized 18 digit little endian number in base 2**29 +void bn_multiply_long(const bignum256 *k, const bignum256 *x, + uint32_t res[2 * BN_LIMBS]) { + // Uses long multiplication in base 2**29, see + // https://en.wikipedia.org/wiki/Multiplication_algorithm#Long_multiplication + + uint64_t acc = 0; + + // compute lower half + for (int i = 0; i < BN_LIMBS; i++) { + for (int j = 0; j <= i; j++) { + acc += k->val[j] * (uint64_t)x->val[i - j]; + // acc doesn't overflow 64 bits + // Proof: + // acc <= acc + sum([k[j] * x[i-j] for j in range(i)]) + // <= (2**(64 - BITS_PER_LIMB) - 1) + + // LIMBS * (2**BITS_PER_LIMB - 1) * (2**BITS_PER_LIMB - 1) + // == (2**35 - 1) + 9 * (2**29 - 1) * (2**29 - 1) + // <= 2**35 + 9 * 2**58 < 2**64 + } + + res[i] = acc & BN_LIMB_MASK; + acc >>= BN_BITS_PER_LIMB; + // acc <= 2**35 - 1 == 2**(64 - BITS_PER_LIMB) - 1 + } + + // compute upper half + for (int i = BN_LIMBS; i < 2 * BN_LIMBS - 1; i++) { + for (int j = i - BN_LIMBS + 1; j < BN_LIMBS; j++) { + acc += k->val[j] * (uint64_t)x->val[i - j]; + // acc doesn't overflow 64 bits + // Proof: + // acc <= acc + sum([k[j] * x[i-j] for j in range(i)]) + // <= (2**(64 - BITS_PER_LIMB) - 1) + // LIMBS * (2**BITS_PER_LIMB - 1) * (2**BITS_PER_LIMB - 1) + // == (2**35 - 1) + 9 * (2**29 - 1) * (2**29 - 1) + // <= 2**35 + 9 * 2**58 < 2**64 + } + + res[i] = acc & (BN_BASE - 1); + acc >>= BN_BITS_PER_LIMB; + // acc < 2**35 == 2**(64 - BITS_PER_LIMB) + } + + res[2 * BN_LIMBS - 1] = acc; +} + +// Auxiliary function for bn_multiply +// Assumes 0 <= d <= 8 == LIMBS - 1 +// Assumes res is normalized and res < 2**(256 + 29*d + 31) +// Guarantess res in normalized and res < 2 * prime * 2**(29*d) +// Assumes prime is normalized, 2**256 - 2**224 <= prime <= 2**256 +void bn_multiply_reduce_step(uint32_t res[2 * BN_LIMBS], const bignum256 *prime, + uint32_t d) { + // clang-format off + // Computes res = res - (res // 2**(256 + BITS_PER_LIMB * d)) * prime * 2**(BITS_PER_LIMB * d) + + // res - (res // 2**(256 + BITS_PER_LIMB * d)) * prime * 2**(BITS_PER_LIMB * d) < 2 * prime * 2**(BITS_PER_LIMB * d) + // Proof: + // res - res // (2**(256 + BITS_PER_LIMB * d)) * 2**(BITS_PER_LIMB * d) * prime + // == res - res // (2**(256 + BITS_PER_LIMB * d)) * 2**(BITS_PER_LIMB * d) * (2**256 - (2**256 - prime)) + // == res - res // (2**(256 + BITS_PER_LIMB * d)) * 2**(BITS_PER_LIMB * d) * 2**256 + res // (2**(256 + BITS_PER_LIMB * d)) * 2**(BITS_PER_LIMB * d) * (2**256 - prime) + // == (res % 2**(256 + BITS_PER_LIMB * d)) + res // (2**256 + BITS_PER_LIMB * d) * 2**(BITS_PER_LIMB * d) * (2**256 - prime) + // <= (2**(256 + 29*d + 31) % 2**(256 + 29*d)) + (2**(256 + 29*d + 31) - 1) / (2**256 + 29*d) * 2**(29*d) * (2**256 - prime) + // <= 2**(256 + 29*d) + 2**(256 + 29*d + 31) / (2**256 + 29*d) * 2**(29*d) * (2**256 - prime) + // == 2**(256 + 29*d) + 2**31 * 2**(29*d) * (2**256 - prime) + // == 2**(29*d) * (2**256 + 2**31 * (2*256 - prime)) + // <= 2**(29*d) * (2**256 + 2**31 * 2*224) + // <= 2**(29*d) * (2**256 + 2**255) + // <= 2**(29*d) * 2 * (2**256 - 2**224) + // <= 2 * prime * 2**(29*d) + // clang-format on + + uint32_t coef = + (res[d + BN_LIMBS - 1] >> (256 - (BN_LIMBS - 1) * BN_BITS_PER_LIMB)) + + (res[d + BN_LIMBS] << ((BN_LIMBS * BN_BITS_PER_LIMB) - 256)); + + // coef == res // 2**(256 + BITS_PER_LIMB * d) + + // coef < 2**31 + // Proof: + // coef == res // 2**(256 + BITS_PER_LIMB * d) + // < 2**(256 + 29 * d + 31) // 2**(256 + 29 * d) + // == 2**31 + + const int shift = 31; + uint64_t acc = 1ull << shift; + + for (int i = 0; i < BN_LIMBS; i++) { + acc += (((uint64_t)(BN_BASE - 1)) << shift) + res[d + i] - + prime->val[i] * (uint64_t)coef; + // acc neither overflow 64 bits nor underflow zero + // Proof: + // acc + ((BASE - 1) << shift) + res[d + i] - prime[i] * coef + // >= ((BASE - 1) << shift) - prime[i] * coef + // == 2**shift * (2**BITS_PER_LIMB - 1) - (2**BITS_PER_LIMB - 1) * + // (2**31 - 1) + // == (2**shift - 2**31 + 1) * (2**BITS_PER_LIMB - 1) + // == (2**31 - 2**31 + 1) * (2**29 - 1) + // == 2**29 - 1 > 0 + // acc + ((BASE - 1) << shift) + res[d + i] - prime[i] * coef + // <= acc + ((BASE - 1) << shift) + res[d+i] + // <= (2**(64 - BITS_PER_LIMB) - 1) + 2**shift * (2**BITS_PER_LIMB - 1) + // + (2*BITS_PER_LIMB - 1) + // == (2**(64 - BITS_PER_LIMB) - 1) + (2**shift + 1) * + // (2**BITS_PER_LIMB - 1) + // == (2**35 - 1) + (2**31 + 1) * (2**29 - 1) + // <= 2**35 + 2**60 + 2**29 < 2**64 + + res[d + i] = acc & BN_LIMB_MASK; + acc >>= BN_BITS_PER_LIMB; + // acc <= 2**(64 - BITS_PER_LIMB) - 1 == 2**35 - 1 + + // acc == (1 << BITS_PER_LIMB * (i + 1) + shift) + res[d : d + i + 1] + // - coef * prime[:i + 1] >> BITS_PER_LIMB * (i + 1) + } + + // acc += (((uint64_t)(BASE - 1)) << shift) + res[d + LIMBS]; + // acc >>= BITS_PER_LIMB; + // assert(acc <= 1ul << shift); + + // clang-format off + // acc == 1 << shift + // Proof: + // acc + // == (1 << BITS_PER_LIMB * (LIMBS + 1) + shift) + res[d : d + LIMBS + 1] - coef * prime[:LIMBS] >> BITS_PER_LIMB * (LIMBS + 1) + // == (1 << BITS_PER_LIMB * (LIMBS + 1) + shift) + res[d : d + LIMBS + 1] - coef * prime >> BITS_PER_LIMB * (LIMBS + 1) + + // == (1 << BITS_PER_LIMB * (LIMBS + 1) + shift) + (res[d : d + LIMBS + 1] - coef * prime) >> BITS_PER_LIMB * (LIMBS + 1) + // <= (1 << BITS_PER_LIMB * (LIMBS + 1) + shift) + (res[:d] + BASE**d * res[d : d + LIMBS + 1] - BASE**d * coef * prime)//BASE**d >> BITS_PER_LIMB * (LIMBS + 1) + // <= (1 << BITS_PER_LIMB * (LIMBS + 1) + shift) + (res - BASE**d * coef * prime) // BASE**d >> BITS_PER_LIMB * (LIMBS + 1) + // == (1 << BITS_PER_LIMB * (LIMBS + 1) + shift) + (2 * prime * BASE**d) // BASE**d >> BITS_PER_LIMB * (LIMBS + 1) + // <= (1 << 321) + 2 * 2**256 >> 290 + // == 1 << 31 == 1 << shift + + // == (1 << BITS_PER_LIMB * (LIMBS + 1) + shift) + res[d : d + LIMBS + 1] - coef * prime[:LIMBS + 1] >> BITS_PER_LIMB * (LIMBS + 1) + // >= (1 << BITS_PER_LIMB * (LIMBS + 1) + shift) + 0 >> BITS_PER_LIMB * (LIMBS + 1) + // == 1 << shift + // clang-format on + + res[d + BN_LIMBS] = 0; +} + +// Auxiliary function for bn_multiply +// Partly reduces res and stores both in x and res +// Assumes res in normalized and res < 2**519 +// Guarantees x is normalized and partly reduced modulo prime +// Assumes prime is normalized, 2**256 - 2**224 <= prime <= 2**256 +void bn_multiply_reduce(bignum256 *x, uint32_t res[2 * BN_LIMBS], + const bignum256 *prime) { + for (int i = BN_LIMBS - 1; i >= 0; i--) { + // res < 2**(256 + 29*i + 31) + // Proof: + // if i == LIMBS - 1: + // res < 2**519 + // == 2**(256 + 29 * 8 + 31) + // == 2**(256 + 29 * (LIMBS - 1) + 31) + // else: + // res < 2 * prime * 2**(29 * (i + 1)) + // <= 2**256 * 2**(29*i + 29) < 2**(256 + 29*i + 31) + bn_multiply_reduce_step(res, prime, i); + } + + for (int i = 0; i < BN_LIMBS; i++) { + x->val[i] = res[i]; + } +} + +// x = k * x % prime +// Assumes k, x are normalized, k * x < 2**519 +// Guarantees x is normalized and partly reduced modulo prime +// Assumes prime is normalized, 2**256 - 2**224 <= prime <= 2**256 +void bn_multiply(const bignum256 *k, bignum256 *x, const bignum256 *prime) { + uint32_t res[2 * BN_LIMBS] = {0}; + + bn_multiply_long(k, x, res); + bn_multiply_reduce(x, res, prime); + + memzero(res, sizeof(res)); +} + +// Partly reduces x modulo prime +// Assumes limbs of x except the last (the most significant) one are normalized +// Assumes prime is normalized and 2^256 - 2^224 <= prime <= 2^256 +// Guarantees x is normalized and partly reduced modulo prime +void bn_fast_mod(bignum256 *x, const bignum256 *prime) { + // Computes x = x - (x // 2**256) * prime + + // x < 2**((LIMBS - 1) * BITS_PER_LIMB + 32) == 2**264 + + // x - (x // 2**256) * prime < 2 * prime + // Proof: + // x - (x // 2**256) * prime + // == x - (x // 2**256) * (2**256 - (2**256 - prime)) + // == x - ((x // 2**256) * 2**256) + (x // 2**256) * (2**256 - prime) + // == (x % prime) + (x // 2**256) * (2**256 - prime) + // <= prime - 1 + (2**264 // 2**256) * (2**256 - prime) + // <= 2**256 + 2**8 * 2**224 == 2**256 + 2**232 + // < 2 * (2**256 - 2**224) + // <= 2 * prime + + // x - (x // 2**256 - 1) * prime < 2 * prime + // Proof: + // x - (x // 2**256) * prime + prime + // == x - (x // 2**256) * (2**256 - (2**256 - prime)) + prime + // == x - ((x//2**256) * 2**256) + (x//2**256) * (2**256 - prime) + prime + // == (x % prime) + (x // 2**256) * (2**256 - prime) + prime + // <= 2 * prime - 1 + (2**264 // 2**256) * (2**256 - prime) + // <= 2 * prime + 2**8 * 2**224 == 2**256 + 2**232 + 2**256 - 2**224 + // < 2 * (2**256 - 2**224) + // <= 2 * prime + + uint32_t coef = + x->val[BN_LIMBS - 1] >> (256 - ((BN_LIMBS - 1) * BN_BITS_PER_LIMB)); + + // clang-format off + // coef == x // 2**256 + // 0 <= coef < 2**((LIMBS - 1) * BITS_PER_LIMB + 32 - 256) == 256 + // Proof: + //* Let x[[a : b] be the number consisting of a-th to (b-1)-th bit of the number x. + // x[LIMBS - 1] >> (256 - ((LIMBS - 1) * BITS_PER_LIMB)) + // == x[[(LIMBS - 1) * BITS_PER_LIMB : (LIMBS - 1) * BITS_PER_LIMB + 32]] >> (256 - ((LIMBS - 1) * BITS_PER_LIMB)) + // == x[[256 - ((LIMBS - 1) * BITS_PER_LIMB) + (LIMBS - 1) * BITS_PER_LIMB : (LIMBS - 1) * BITS_PER_LIMB + 32]] + // == x[[256 : (LIMBS - 1) * BITS_PER_LIMB + 32]] + // == x[[256 : 264]] == x // 2**256 + // clang-format on + + const int shift = 8; + uint64_t acc = 1ull << shift; + + for (int i = 0; i < BN_LIMBS; i++) { + acc += (((uint64_t)(BN_BASE - 1)) << shift) + x->val[i] - + prime->val[i] * (uint64_t)coef; + // acc neither overflows 64 bits nor underflows 0 + // Proof: + // acc + (BASE - 1 << shift) + x[i] - prime[i] * coef + // >= (BASE - 1 << shift) - prime[i] * coef + // >= 2**shift * (2**BITS_PER_LIMB - 1) - (2**BITS_PER_LIMB - 1) * 255 + // == (2**shift - 255) * (2**BITS_PER_LIMB - 1) + // == (2**8 - 255) * (2**29 - 1) == 2**29 - 1 >= 0 + // acc + (BASE - 1 << shift) + x[i] - prime[i] * coef + // <= acc + ((BASE - 1) << shift) + x[i] + // <= (2**(64 - BITS_PER_LIMB) - 1) + 2**shift * (2**BITS_PER_LIMB - 1) + // + (2**32 - 1) + // == (2**35 - 1) + 2**8 * (2**29 - 1) + 2**32 + // < 2**35 + 2**37 + 2**32 < 2**64 + + x->val[i] = acc & BN_LIMB_MASK; + acc >>= BN_BITS_PER_LIMB; + // acc <= 2**(64 - BITS_PER_LIMB) - 1 == 2**35 - 1 + + // acc == (1 << BITS_PER_LIMB * (i + 1) + shift) + x[:i + 1] + // - coef * prime[:i + 1] >> BITS_PER_LIMB * (i + 1) + } + + // assert(acc == 1 << shift); + + // clang-format off + // acc == 1 << shift + // Proof: + // acc + // == (1 << BITS_PER_LIMB * LIMBS + shift) + x[:LIMBS] - coef * prime[:LIMBS] >> BITS_PER_LIMB * LIMBS + // == (1 << BITS_PER_LIMB * LIMBS + shift) + (x - coef * prime) >> BITS_PER_LIMB * LIMBS + // <= (1 << BITS_PER_LIMB * LIMBS + shift) + (2 * prime) >> BITS_PER_LIMB * LIMBS + // <= (1 << BITS_PER_LIMB * LIMBS + shift) + 2 * 2**256 >> BITS_PER_LIMB * LIMBS + // <= 2**269 + 2**257 >> 2**261 + // <= 1 << 8 == 1 << shift + + // acc + // == (1 << BITS_PER_LIMB * LIMBS + shift) + x[:LIMBS] - coef * prime[:LIMBS] >> BITS_PER_LIMB * LIMBS + // >= (1 << BITS_PER_LIMB * LIMBS + shift) + 0 >> BITS_PER_LIMB * LIMBS + // == (1 << BITS_PER_LIMB * LIMBS + shift) + 0 >> BITS_PER_LIMB * LIMBS + // <= 1 << 8 == 1 << shift + // clang-format on +} + +// res = x**e % prime +// Assumes both x and e are normalized, x < 2**259 +// Guarantees res is normalized and partly reduced modulo prime +// Works properly even if &x == &res +// Assumes prime is normalized, 2**256 - 2**224 <= prime <= 2**256 +// The function doesn't have neither constant control flow nor constant memory +// access flow with regard to e +void bn_power_mod(const bignum256 *x, const bignum256 *e, + const bignum256 *prime, bignum256 *res) { + // Uses iterative right-to-left exponentiation by squaring, see + // https://en.wikipedia.org/wiki/Modular_exponentiation#Right-to-left_binary_method + + bignum256 acc = {0}; + bn_copy(x, &acc); + + bn_one(res); + for (int i = 0; i < BN_LIMBS; i++) { + uint32_t limb = e->val[i]; + + for (int j = 0; j < BN_BITS_PER_LIMB; j++) { + // Break if the following bits of the last limb are zero + if (i == BN_LIMBS - 1 && limb == 0) break; + + if (limb & 1) + // acc * res < 2**519 + // Proof: + // acc * res <= max(2**259 - 1, 2 * prime) * (2 * prime) + // == max(2**259 - 1, 2**257) * 2**257 < 2**259 * 2**257 + // == 2**516 < 2**519 + bn_multiply(&acc, res, prime); + + limb >>= 1; + // acc * acc < 2**519 + // Proof: + // acc * acc <= max(2**259 - 1, 2 * prime)**2 + // <= (2**259)**2 == 2**518 < 2**519 + bn_multiply(&acc, &acc, prime); + } + // acc == x**(e[:i + 1]) % prime + } + + memzero(&acc, sizeof(acc)); +} + +// x = sqrt(x) % prime +// Explicitly x = x**((prime+1)/4) % prime +// The other root is -sqrt(x) +// Assumes x is normalized, x < 2**259 and quadratic residuum mod prime +// Assumes prime is a prime number, prime % 4 == 3, it is normalized and +// 2**256 - 2**224 <= prime <= 2**256 +// Guarantees x is normalized and fully reduced modulo prime +// The function doesn't have neither constant control flow nor constant memory +// access flow with regard to prime +void bn_sqrt(bignum256 *x, const bignum256 *prime) { + // Uses the Lagrange formula for the primes of the special form, see + // http://en.wikipedia.org/wiki/Quadratic_residue#Prime_or_prime_power_modulus + // If prime % 4 == 3, then sqrt(x) % prime == x**((prime+1)//4) % prime + + assert(prime->val[BN_LIMBS - 1] % 4 == 3); + + // e = (prime + 1) // 4 + bignum256 e = {0}; + bn_copy(prime, &e); + bn_addi(&e, 1); + bn_rshift(&e); + bn_rshift(&e); + + bn_power_mod(x, &e, prime, x); + bn_mod(x, prime); + + memzero(&e, sizeof(e)); +} + +// a = 1/a % 2**n +// Assumes a is odd, 1 <= n <= 32 +// The function doesn't have neither constant control flow nor constant memory +// access flow with regard to n +uint32_t inverse_mod_power_two(uint32_t a, uint32_t n) { + // Uses "Explicit Quadratic Modular inverse modulo 2" from section 3.3 of "On + // Newton-Raphson iteration for multiplicative inverses modulo prime powers" + // by Jean-Guillaume Dumas, see + // https://arxiv.org/pdf/1209.6626.pdf + + // 1/a % 2**n + // = (2-a) * product([1 + (a-1)**(2**i) for i in range(1, floor(log2(n)))]) + + uint32_t acc = 2 - a; + uint32_t f = a - 1; + + // mask = (1 << n) - 1 + uint32_t mask = n == 32 ? 0xFFFFFFFF : (1u << n) - 1; + + for (uint32_t i = 1; i < n; i <<= 1) { + f = (f * f) & mask; + acc = (acc * (1 + f)) & mask; + } + + return acc; +} + +// x = (x / 2**BITS_PER_LIMB) % prime +// Assumes both x and prime are normalized +// Assumes prime is an odd number and normalized +// Guarantees x is normalized +// If x is partly reduced (fully reduced) modulo prime, +// guarantess x will be partly reduced (fully reduced) modulo prime +void bn_divide_base(bignum256 *x, const bignum256 *prime) { + // Uses an explicit formula for the modular inverse of power of two + // (x / 2**n) % prime == (x + ((-x / prime) % 2**n) * prime) // 2**n + // Proof: + // (x + ((-x / prime) % 2**n) * prime) % 2**n + // == (x - x / prime * prime) % 2**n + // == 0 + // (x + ((-1 / prime) % 2**n) * prime) % prime + // == x + // if x < prime: + // (x + ((-x / prime) % 2**n) * prime) // 2**n + // <= ((prime - 1) + (2**n - 1) * prime) / 2**n + // == (2**n * prime - 1) / 2**n == prime - 1 / 2**n < prime + // if x < 2 * prime: + // (x + ((-x / prime) % 2**n) * prime) // 2**n + // <= ((2 * prime - 1) + (2**n - 1) * prime) / 2**n + // == (2**n * prime + prime - 1) / 2**n + // == prime + (prime - 1) / 2**n < 2 * prime + + // m = (-x / prime) % 2**BITS_PER_LIMB + uint32_t m = (x->val[0] * (BN_BASE - inverse_mod_power_two( + prime->val[0], BN_BITS_PER_LIMB))) & + BN_LIMB_MASK; + // m < 2**BITS_PER_LIMB + + uint64_t acc = x->val[0] + (uint64_t)m * prime->val[0]; + acc >>= BN_BITS_PER_LIMB; + + for (int i = 1; i < BN_LIMBS; i++) { + acc = acc + x->val[i] + (uint64_t)m * prime->val[i]; + // acc does not overflow 64 bits + // acc == acc + x + m * prime + // <= 2**(64 - BITS_PER_LIMB) + 2**(BITS_PER_LIMB) + // 2**(BITS_PER_LIMB) * 2**(BITS_PER_LIMB) + // <= 2**(2 * BITS_PER_LIMB) + 2**(64 - BITS_PER_LIMB) + + // 2**(BITS_PER_LIMB) + // <= 2**58 + 2**35 + 2**29 < 2**64 + + x->val[i - 1] = acc & BN_LIMB_MASK; + acc >>= BN_BITS_PER_LIMB; + // acc < 2**35 == 2**(64 - BITS_PER_LIMB) + + // acc == x[:i + 1] + m * prime[:i + 1] >> BITS_PER_LIMB * (i + 1) + } + + x->val[BN_LIMBS - 1] = acc; + + assert(acc >> BN_BITS_PER_LIMB == 0); + + // clang-format off + // acc >> BITS_PER_LIMB == 0 + // Proof: + // acc >> BITS_PER_LIMB + // == (x[:LIMB] + m * prime[:LIMB] >> BITS_PER_LIMB * LIMBS) >> BITS_PER_LIMB * (LIMBS + 1) + // == x + m * prime >> BITS_PER_LIMB * (LIMBS + 1) + // <= (2**(BITS_PER_LIMB * LIMBS) - 1) + (2**BITS_PER_LIMB - 1) * (2**(BITS_PER_LIMB * LIMBS) - 1) >> BITS_PER_LIMB * (LIMBS + 1) + // == 2**(BITS_PER_LIMB * LIMBS) - 1 + 2**(BITS_PER_LIMB * (LIMBS + 1)) - 2**(BITS_PER_LIMB * LIMBS) - 2**BITS_PER_LIMB + 1 >> BITS_PER_LIMB * (LIMBS + 1) + // == 2**(BITS_PER_LIMB * (LIMBS + 1)) - 2**BITS_PER_LIMB >> BITS_PER_LIMB * (LIMBS + 1) + // == 0 + // clang-format on +} + +// x = 1/x % prime if x != 0 else 0 +// Assumes x is normalized +// Assumes prime is a prime number +// Guarantees x is normalized and fully reduced modulo prime +// Assumes prime is normalized, 2**256 - 2**224 <= prime <= 2**256 +// The function doesn't have neither constant control flow nor constant memory +// access flow with regard to prime +void bn_inverse_slow(bignum256 *x, const bignum256 *prime) { + // Uses formula 1/x % prime == x**(prime - 2) % prime + // See https://en.wikipedia.org/wiki/Fermat%27s_little_theorem + + bn_fast_mod(x, prime); + + // e = prime - 2 + bignum256 e = {0}; + bn_read_uint32(2, &e); + bn_subtract(prime, &e, &e); + + bn_power_mod(x, &e, prime, x); + bn_mod(x, prime); + + memzero(&e, sizeof(e)); +} + +#if false +// x = 1/x % prime if x != 0 else 0 +// Assumes x is is_normalized +// Assumes GCD(x, prime) = 1 +// Guarantees x is normalized and fully reduced modulo prime +// Assumes prime is odd, normalized, 2**256 - 2**224 <= prime <= 2**256 +// The function doesn't have neither constant control flow nor constant memory +// access flow with regard to prime and x +void bn_inverse_fast(bignum256 *x, const bignum256 *prime) { + // "The Almost Montgomery Inverse" from the section 3 of "Constant Time + // Modular Inversion" by Joppe W. Bos + // See http://www.joppebos.com/files/CTInversion.pdf + + /* + u = prime + v = x & prime + s = 1 + r = 0 + + k = 0 + while v != 1: + k += 1 + if is_even(u): + u = u // 2 + s = 2 * s + elif is_even(v): + v = v // 2 + r = 2 * r + elif v < u: + u = (u - v) // 2 + r = r + s + s = 2 * s + else: + v = (v - u) // 2 + s = r + s + r = 2 * r + + s = (s / 2**k) % prime + return s + */ + + if (bn_is_zero(x)) return; + + bn_fast_mod(x, prime); + bn_mod(x, prime); + + bignum256 u = {0}, v = {0}, r = {0}, s = {0}; + bn_copy(prime, &u); + bn_copy(x, &v); + bn_one(&s); + bn_zero(&r); + + int k = 0; + while (!bn_is_one(&v)) { + if ((u.val[0] & 1) == 0) { + bn_rshift(&u); + bn_lshift(&s); + } else if ((v.val[0] & 1) == 0) { + bn_rshift(&v); + bn_lshift(&r); + } else if (bn_is_less(&v, &u)) { + bn_subtract(&u, &v, &u); + bn_rshift(&u); + bn_add(&r, &s); + bn_lshift(&s); + } else { + bn_subtract(&v, &u, &v); + bn_rshift(&v); + bn_add(&s, &r); + bn_lshift(&r); + } + k += 1; + assert(!bn_is_zero(&v)); // assert GCD(x, prime) == 1 + } + + // s = s / 2**(k // BITS_PER_LIMB * BITS_PER_LIMB) + for (int i = 0; i < k / BITS_PER_LIMB; i++) { + bn_divide_base(&s, prime); + } + + // s = s / 2**(k % BITS_PER_LIMB) + for (int i = 0; i < k % BN_BITS_PER_LIMB; i++) { + bn_mult_half(&s, prime); + } + + bn_copy(&s, x); + + memzero(&u, sizeof(u)); + memzero(&v, sizeof(v)); + memzero(&r, sizeof(r)); + memzero(&s, sizeof(s)); +} +#endif + +// x = 1/x % prime if x != 0 else 0 +// Assumes x is is_normalized +// Assumes GCD(x, prime) = 1 +// Guarantees x is normalized and fully reduced modulo prime +// Assumes prime is odd, normalized, 2**256 - 2**224 <= prime <= 2**256 +// The function has constant control flow but not constant memory access flow +// with regard to prime and x +void bn_inverse_fast(bignum256 *x, const bignum256 *prime) { + // Custom constant time version of "The Almost Montgomery Inverse" from the + // section 3 of "Constant Time Modular Inversion" by Joppe W. Bos + // See http://www.joppebos.com/files/CTInversion.pdf + + /* + u = prime + v = x % prime + s = 1 + r = 0 + + k = 0 + while v != 1: + k += 1 + if is_even(u): # b1 + u = u // 2 + s = 2 * s + elif is_even(v): # b2 + v = v // 2 + r = 2 * r + elif v < u: # b3 + u = (u - v) // 2 + r = r + s + s = 2 * s + else: # b4 + v = (v - u) // 2 + s = r + s + r = 2 * r + + s = (s / 2**k) % prime + return s + */ + + bn_fast_mod(x, prime); + bn_mod(x, prime); + + bignum256 u = {0}, v = {0}, r = {0}, s = {0}; + bn_copy(prime, &u); + bn_copy(x, &v); + bn_one(&s); + bn_zero(&r); + + bignum256 zero = {0}; + bn_zero(&zero); + + int k = 0; + + int finished = 0, u_even = 0, v_even = 0, v_less_u = 0, b1 = 0, b2 = 0, + b3 = 0, b4 = 0; + finished = 0; + + for (int i = 0; i < 2 * BN_LIMBS * BN_BITS_PER_LIMB; i++) { + finished = finished | -bn_is_one(&v); + u_even = -bn_is_even(&u); + v_even = -bn_is_even(&v); + v_less_u = -bn_is_less(&v, &u); + + b1 = ~finished & u_even; + b2 = ~finished & ~b1 & v_even; + b3 = ~finished & ~b1 & ~b2 & v_less_u; + b4 = ~finished & ~b1 & ~b2 & ~b3; + +// The ternary operator for pointers with constant control flow +// BN_INVERSE_FAST_TERNARY(c, t, f) = t if c else f +// Very nasty hack, sorry for that +#define BN_INVERSE_FAST_TERNARY(c, t, f) \ + ((void *)(((c) & (uintptr_t)(t)) | (~(c) & (uintptr_t)(f)))) + + bn_subtract(BN_INVERSE_FAST_TERNARY(b3, &u, &v), + BN_INVERSE_FAST_TERNARY( + b3 | b4, BN_INVERSE_FAST_TERNARY(b3, &v, &u), &zero), + BN_INVERSE_FAST_TERNARY(b3, &u, &v)); + + bn_add(BN_INVERSE_FAST_TERNARY(b3, &r, &s), + BN_INVERSE_FAST_TERNARY(b3 | b4, BN_INVERSE_FAST_TERNARY(b3, &s, &r), + &zero)); + bn_rshift(BN_INVERSE_FAST_TERNARY(b1 | b3, &u, &v)); + bn_lshift(BN_INVERSE_FAST_TERNARY(b1 | b3, &s, &r)); + + k = k - ~finished; + } + + // s = s / 2**(k // BITS_PER_LIMB * BITS_PER_LIMB) + for (int i = 0; i < 2 * BN_LIMBS; i++) { + // s = s / 2**BITS_PER_LIMB % prime if i < k // BITS_PER_LIMB else s + bn_copy(&s, &r); + bn_divide_base(&r, prime); + bn_cmov(&s, i < k / BN_BITS_PER_LIMB, &r, &s); + } + + // s = s / 2**(k % BITS_PER_LIMB) + for (int i = 0; i < BN_BITS_PER_LIMB; i++) { + // s = s / 2 % prime if i < k % BITS_PER_LIMB else s + bn_copy(&s, &r); + bn_mult_half(&r, prime); + bn_cmov(&s, i < k % BN_BITS_PER_LIMB, &r, &s); + } + + bn_cmov(x, bn_is_zero(x), x, &s); + + memzero(&u, sizeof(u)); + memzero(&v, sizeof(v)); + memzero(&r, sizeof(s)); + memzero(&s, sizeof(s)); +} + +#if false +// x = 1/x % prime if x != 0 else 0 +// Assumes x is is_normalized +// Assumes GCD(x, prime) = 1 +// Guarantees x is normalized and fully reduced modulo prime +// Assumes prime is odd, normalized, 2**256 - 2**224 <= prime <= 2**256 +void bn_inverse_fast(bignum256 *x, const bignum256 *prime) { + // Custom constant time version of "The Almost Montgomery Inverse" from the + // section 3 of "Constant Time Modular Inversion" by Joppe W. Bos + // See http://www.joppebos.com/files/CTInversion.pdf + + /* + u = prime + v = x % prime + s = 1 + r = 0 + + k = 0 + while v != 1: + k += 1 + if is_even(u): # b1 + u = u // 2 + s = 2 * s + elif is_even(v): # b2 + v = v // 2 + r = 2 * r + elif v < u: # b3 + u = (u - v) // 2 + r = r + s + s = 2 * s + else: # b4 + v = (v - u) // 2 + s = r + s + r = 2 * r + + s = (s / 2**k) % prime + return s + */ + + bn_fast_mod(x, prime); + bn_mod(x, prime); + + bignum256 u = {0}, v = {0}, r = {0}, s = {0}; + bn_copy(prime, &u); + bn_copy(x, &v); + bn_one(&s); + bn_zero(&r); + + bignum256 zero = {0}; + bn_zero(&zero); + + int k = 0; + + uint32_t finished = 0, u_even = 0, v_even = 0, v_less_u = 0, b1 = 0, b2 = 0, + b3 = 0, b4 = 0; + finished = 0; + + bignum256 u_half = {0}, v_half = {0}, u_minus_v_half = {0}, v_minus_u_half = {0}, r_plus_s = {0}, r_twice = {0}, s_twice = {0}; + for (int i = 0; i < 2 * BN_LIMBS * BN_BITS_PER_LIMB; i++) { + finished = finished | bn_is_one(&v); + u_even = bn_is_even(&u); + v_even = bn_is_even(&v); + v_less_u = bn_is_less(&v, &u); + + b1 = (finished ^ 1) & u_even; + b2 = (finished ^ 1) & (b1 ^ 1) & v_even; + b3 = (finished ^ 1) & (b1 ^ 1) & (b2 ^ 1) & v_less_u; + b4 = (finished ^ 1) & (b1 ^ 1) & (b2 ^ 1) & (b3 ^ 1); + + // u_half = u // 2 + bn_copy(&u, &u_half); + bn_rshift(&u_half); + + // v_half = v // 2 + bn_copy(&v, &v_half); + bn_rshift(&v_half); + + // u_minus_v_half = (u - v) // 2 + bn_subtract(&u, &v, &u_minus_v_half); + bn_rshift(&u_minus_v_half); + + // v_minus_u_half = (v - u) // 2 + bn_subtract(&v, &u, &v_minus_u_half); + bn_rshift(&v_minus_u_half); + + // r_plus_s = r + s + bn_copy(&r, &r_plus_s); + bn_add(&r_plus_s, &s); + + // r_twice = 2 * r + bn_copy(&r, &r_twice); + bn_lshift(&r_twice); + + // s_twice = 2 * s + bn_copy(&s, &s_twice); + bn_lshift(&s_twice); + + bn_cmov(&u, b1, &u_half, &u); + bn_cmov(&u, b3, &u_minus_v_half, &u); + + bn_cmov(&v, b2, &v_half, &v); + bn_cmov(&v, b4, &v_minus_u_half, &v); + + bn_cmov(&r, b2 | b4, &r_twice, &r); + bn_cmov(&r, b3, &r_plus_s, &r); + + bn_cmov(&s, b1 | b3, &s_twice, &s); + bn_cmov(&s, b4, &r_plus_s, &s); + + k = k + (finished ^ 1); + } + + // s = s / 2**(k // BITS_PER_LIMB * BITS_PER_LIMB) + for (int i = 0; i < 2 * BN_LIMBS; i++) { + // s = s / 2**BITS_PER_LIMB % prime if i < k // BITS_PER_LIMB else s + bn_copy(&s, &r); + bn_divide_base(&r, prime); + bn_cmov(&s, i < k / BITS_PER_LIMB, &r, &s); + } + + // s = s / 2**(k % BITS_PER_LIMB) + for (int i = 0; i < BN_BITS_PER_LIMB; i++) { + // s = s / 2 % prime if i < k % BITS_PER_LIMB else s + bn_copy(&s, &r); + bn_mult_half(&r, prime); + bn_cmov(&s, i < k % BN_BITS_PER_LIMB, &r, &s); + } + + bn_cmov(x, bn_is_zero(x), x, &s); + + memzero(&u, sizeof(u)); + memzero(&v, sizeof(v)); + memzero(&r, sizeof(r)); + memzero(&s, sizeof(s)); + memzero(&u_half, sizeof(u_half)); + memzero(&v_half, sizeof(v_half)); + memzero(&u_minus_v_half, sizeof(u_minus_v_half)); + memzero(&v_minus_u_half, sizeof(v_minus_u_half)); + memzero(&r_twice, sizeof(r_twice)); + memzero(&s_twice, sizeof(s_twice)); + memzero(&r_plus_s, sizeof(r_plus_s)); +} +#endif + +// Normalizes x +// Assumes x < 2**261 == 2**(LIMBS * BITS_PER_LIMB) +// Guarantees x is normalized +void bn_normalize(bignum256 *x) { + uint32_t acc = 0; + + for (int i = 0; i < BN_LIMBS; i++) { + acc += x->val[i]; + // acc doesn't overflow 32 bits + // Proof: + // acc + x[i] + // <= (2**(32 - BITS_PER_LIMB) - 1) + (2**BITS_PER_LIMB - 1) + // == 7 + 2**29 - 1 < 2**32 + + x->val[i] = acc & BN_LIMB_MASK; + acc >>= (BN_BITS_PER_LIMB); + // acc <= 7 == 2**(32 - BITS_PER_LIMB) - 1 + } +} + +// x = x + y +// Assumes x, y are normalized, x + y < 2**(LIMBS*BITS_PER_LIMB) == 2**261 +// Guarantees x is normalized +// Works properly even if &x == &y +void bn_add(bignum256 *x, const bignum256 *y) { + uint32_t acc = 0; + for (int i = 0; i < BN_LIMBS; i++) { + acc += x->val[i] + y->val[i]; + // acc doesn't overflow 32 bits + // Proof: + // acc + x[i] + y[i] + // <= (2**(32 - BITS_PER_LIMB) - 1) + 2 * (2**BITS_PER_LIMB - 1) + // == (2**(32 - BITS_PER_LIMB) - 1) + 2**(BITS_PER_LIMB + 1) - 2 + // == 7 + 2**30 - 2 < 2**32 + + x->val[i] = acc & BN_LIMB_MASK; + acc >>= BN_BITS_PER_LIMB; + // acc <= 7 == 2**(32 - BITS_PER_LIMB) - 1 + + // acc == x[:i + 1] + y[:i + 1] >> BITS_PER_LIMB * (i + 1) + } + + // assert(acc == 0); // assert x + y < 2**261 + // acc == 0 + // Proof: + // acc == x[:LIMBS] + y[:LIMBS] >> LIMBS * BITS_PER_LIMB + // == x + y >> LIMBS * BITS_PER_LIMB + // <= 2**(LIMBS * BITS_PER_LIMB) - 1 >> LIMBS * BITS_PER_LIMB == 0 +} + +// x = x + y % prime +// Assumes x, y are normalized +// Guarantees x is normalized and partly reduced modulo prime +// Assumes prime is normalized and 2^256 - 2^224 <= prime <= 2^256 +void bn_addmod(bignum256 *x, const bignum256 *y, const bignum256 *prime) { + for (int i = 0; i < BN_LIMBS; i++) { + x->val[i] += y->val[i]; + // x[i] doesn't overflow 32 bits + // Proof: + // x[i] + y[i] + // <= 2 * (2**BITS_PER_LIMB - 1) + // == 2**30 - 2 < 2**32 + } + + bn_fast_mod(x, prime); +} + +// x = x + y +// Assumes x is normalized +// Assumes y <= 2**32 - 2**29 == 2**32 - 2**BITS_PER_LIMB and +// x + y < 2**261 == 2**(LIMBS * BITS_PER_LIMB) +// Guarantees x is normalized +void bn_addi(bignum256 *x, uint32_t y) { + // assert(y <= 3758096384); // assert y <= 2**32 - 2**29 + uint32_t acc = y; + + for (int i = 0; i < BN_LIMBS; i++) { + acc += x->val[i]; + // acc doesn't overflow 32 bits + // Proof: + // if i == 0: + // acc + x[i] == y + x[0] + // <= (2**32 - 2**BITS_PER_LIMB) + (2**BITS_PER_LIMB - 1) + // == 2**32 - 1 < 2**32 + // else: + // acc + x[i] + // <= (2**(32 - BITS_PER_LIMB) - 1) + (2**BITS_PER_LIMB - 1) + // == 7 + 2**29 - 1 < 2**32 + + x->val[i] = acc & BN_LIMB_MASK; + acc >>= (BN_BITS_PER_LIMB); + // acc <= 7 == 2**(32 - BITS_PER_LIMB) - 1 + + // acc == x[:i + 1] + y >> BITS_PER_LIMB * (i + 1) + } + + // assert(acc == 0); // assert x + y < 2**261 + // acc == 0 + // Proof: + // acc == x[:LIMBS] + y << LIMBS * BITS_PER_LIMB + // == x + y << LIMBS * BITS_PER_LIMB + // <= 2**(LIMBS + BITS_PER_LIMB) - 1 << LIMBS * BITS_PER_LIMB + // == 0 +} + +// x = x - y % prime +// Explicitly x = x + prime - y +// Assumes x, y are normalized +// Assumes y < prime[0], x + prime - y < 2**261 == 2**(LIMBS * BITS_PER_LIMB) +// Guarantees x is normalized +// If x is fully reduced modulo prime, +// guarantess x will be partly reduced modulo prime +// Assumes prime is nonzero and normalized +void bn_subi(bignum256 *x, uint32_t y, const bignum256 *prime) { + assert(y < prime->val[0]); + + // x = x + prime - y + + uint32_t acc = -y; + for (int i = 0; i < BN_LIMBS; i++) { + acc += x->val[i] + prime->val[i]; + // acc neither overflows 32 bits nor underflows 0 + // Proof: + // acc + x[i] + prime[i] + // <= (2**(32 - BITS_PER_LIMB) - 1) + 2 * (2**BITS_PER_LIMB - 1) + // <= 7 + 2**30 - 2 < 2**32 + // acc + x[i] + prime[i] + // >= -y + prime[0] >= 0 + + x->val[i] = acc & BN_LIMB_MASK; + acc >>= BN_BITS_PER_LIMB; + // acc <= 7 == 2**(32 - BITS_PER_LIMB) - 1 + + // acc == x[:i + 1] + prime[:i + 1] - y >> BITS_PER_LIMB * (i + 1) + } + + // assert(acc == 0); // assert x + prime - y < 2**261 + // acc == 0 + // Proof: + // acc == x[:LIMBS] + prime[:LIMBS] - y >> BITS_PER_LIMB * LIMBS + // == x + prime - y >> BITS_PER_LIMB * LIMBS + // <= 2**(LIMBS * BITS_PER_LIMB) - 1 >> BITS_PER_LIMB * LIMBS == 0 +} + +// res = x - y % prime +// Explicitly res = x + (2 * prime - y) +// Assumes x, y are normalized, y is partly reduced +// Assumes x + 2 * prime - y < 2**261 == 2**(BITS_PER_LIMB * LIMBS) +// Guarantees res is normalized +// Assumes prime is nonzero and normalized +void bn_subtractmod(const bignum256 *x, const bignum256 *y, bignum256 *res, + const bignum256 *prime) { + // res = x + (2 * prime - y) + + uint32_t acc = 1; + + for (int i = 0; i < BN_LIMBS; i++) { + acc += (BN_BASE - 1) + x->val[i] + 2 * prime->val[i] - y->val[i]; + // acc neither overflows 32 bits nor underflows 0 + // Proof: + // acc + (BASE - 1) + x[i] + 2 * prime[i] - y[i] + // >= (BASE - 1) - y[i] + // == (2**BITS_PER_LIMB - 1) - (2**BITS_PER_LIMB - 1) == 0 + // acc + (BASE - 1) + x[i] + 2 * prime[i] - y[i] + // <= acc + (BASE - 1) + x[i] + 2 * prime[i] + // <= (2**(32 - BITS_PER_LIMB) - 1) + (2**BITS_PER_LIMB - 1) + + // (2**BITS_PER_LIMB - 1) + 2 * (2**BITS_PER_LIMB - 1) + // <= (2**(32 - BITS_PER_LIMB) - 1) + 4 * (2**BITS_PER_LIMB - 1) + // == 7 + 4 * 2**29 - 4 == 2**31 + 3 < 2**32 + + res->val[i] = acc & (BN_BASE - 1); + acc >>= BN_BITS_PER_LIMB; + // acc <= 7 == 2**(32 - BITS_PER_LIMB) - 1 + + // acc == 2**(BITS_PER_LIMB * (i + 1)) + x[:i+1] - y[:i+1] + 2*prime[:i+1] + // >> BITS_PER_LIMB * (i+1) + } + + // assert(acc == 1); // assert x + 2 * prime - y < 2**261 + + // clang-format off + // acc == 1 + // Proof: + // acc == 2**(BITS_PER_LIMB * LIMBS) + x[:LIMBS] - y[:LIMBS] + 2 * prime[:LIMBS] >> BITS_PER_LIMB * LIMBS + // == 2**(BITS_PER_LIMB * LIMBS) + x - y + 2 * prime >> BITS_PER_LIMB * LIMBS + // == 2**(BITS_PER_LIMB * LIMBS) + x + (2 * prime - y) >> BITS_PER_LIMB * LIMBS + // <= 2**(BITS_PER_LIMB * LIMBS) + 2**(BITS_PER_LIMB * LIMBS) - 1 >> BITS_PER_LIMB * LIMBS + // <= 2 * 2**(BITS_PER_LIMB * LIMBS) - 1 >> BITS_PER_LIMB * LIMBS + // == 1 + + // acc == 2**(BITS_PER_LIMB * LIMBS) + x[:LIMBS] - y[:LIMBS] + 2 * prime[:LIMBS] >> BITS_PER_LIMB * LIMBS + // == 2**(BITS_PER_LIMB * LIMBS) + x - y + 2 * prime >> BITS_PER_LIMB * LIMBS + // == 2**(BITS_PER_LIMB * LIMBS) + x + (2 * prime - y) >> BITS_PER_LIMB * LIMBS + // >= 2**(BITS_PER_LIMB * LIMBS) + 0 + 1 >> BITS_PER_LIMB * LIMBS + // == 1 + // clang-format on +} + +// res = x - y +// Assumes x, y are normalized and x >= y +// Guarantees res is normalized +// Works properly even if &x == &y or &x == &res or &y == &res or +// &x == &y == &res +void bn_subtract(const bignum256 *x, const bignum256 *y, bignum256 *res) { + uint32_t acc = 1; + for (int i = 0; i < BN_LIMBS; i++) { + acc += (BN_BASE - 1) + x->val[i] - y->val[i]; + // acc neither overflows 32 bits nor underflows 0 + // Proof: + // acc + (BASE - 1) + x[i] - y[i] + // >= (BASE - 1) - y == (2**BITS_PER_LIMB - 1) - (2**BITS_PER_LIMB - 1) + // == 0 + // acc + (BASE - 1) + x[i] - y[i] + // <= acc + (BASE - 1) + x[i] + // <= (2**(32 - BITS_PER_LIMB) - 1) + (2**BITS_PER_LIMB - 1) + + // (2**BITS_PER_LIMB - 1) + // == 7 + 2 * 2**29 < 2 **32 + + res->val[i] = acc & BN_LIMB_MASK; + acc >>= BN_BITS_PER_LIMB; + // acc <= 7 == 2**(32 - BITS_PER_LIMB) - 1 + + // acc == 2**(BITS_PER_LIMB * (i + 1)) + x[:i + 1] - y[:i + 1] + // >> BITS_PER_LIMB * (i + 1) + } + + // assert(acc == 1); // assert x >= y + + // clang-format off + // acc == 1 + // Proof: + // acc == 2**(BITS_PER_LIMB * LIMBS) + x[:LIMBS] - y[:LIMBS] >> BITS_PER_LIMB * LIMBS + // == 2**(BITS_PER_LIMB * LIMBS) + x - y >> BITS_PER_LIMB * LIMBS + // == 2**(BITS_PER_LIMB * LIMBS) + x >> BITS_PER_LIMB * LIMBS + // <= 2**(BITS_PER_LIMB * LIMBS) + 2**(BITS_PER_LIMB * LIMBS) - 1 >> BITS_PER_LIMB * LIMBS + // <= 2 * 2**(BITS_PER_LIMB * LIMBS) - 1 >> BITS_PER_LIMB * LIMBS + // == 1 + + // acc == 2**(BITS_PER_LIMB * LIMBS) + x[:LIMBS] - y[:LIMBS] >> BITS_PER_LIMB * LIMBS + // == 2**(BITS_PER_LIMB * LIMBS) + x - y >> BITS_PER_LIMB * LIMBS + // >= 2**(BITS_PER_LIMB * LIMBS) >> BITS_PER_LIMB * LIMBS + // == 1 +} + +// q = x // d, r = x % d +// Assumes x is normalized, 1 <= d <= 61304 +// Guarantees q is normalized +void bn_long_division(bignum256 *x, uint32_t d, bignum256 *q, uint32_t *r) { + assert(1 <= d && d < 61304); + + uint32_t acc = 0; + + *r = x->val[BN_LIMBS - 1] % d; + q->val[BN_LIMBS - 1] = x->val[BN_LIMBS - 1] / d; + + for (int i = BN_LIMBS - 2; i >= 0; i--) { + acc = *r * (BN_BASE % d) + x->val[i]; + // acc doesn't overflow 32 bits + // Proof: + // r * (BASE % d) + x[i] + // <= (d - 1) * (d - 1) + (2**BITS_PER_LIMB - 1) + // == d**2 - 2*d + 2**BITS_PER_LIMB + // == 61304**2 - 2 * 61304 + 2**29 + // == 3758057808 + 2**29 < 2**32 + + q->val[i] = *r * (BN_BASE / d) + (acc / d); + // q[i] doesn't overflow 32 bits + // Proof: + // r * (BASE // d) + (acc // d) + // <= (d - 1) * (2**BITS_PER_LIMB / d) + + // ((d**2 - 2*d + 2**BITS_PER_LIMB) / d) + // <= (d - 1) * (2**BITS_PER_LIMB / d) + (d - 2 + 2**BITS_PER_LIMB / d) + // == (d - 1 + 1) * (2**BITS_PER_LIMB / d) + d - 2 + // == 2**BITS_PER_LIMB + d - 2 <= 2**29 + 61304 < 2**32 + + // q[i] == (r * BASE + x[i]) // d + // Proof: + // q[i] == r * (BASE // d) + (acc // d) + // == r * (BASE // d) + (r * (BASE % d) + x[i]) // d + // == (r * d * (BASE // d) + r * (BASE % d) + x[i]) // d + // == (r * (d * (BASE // d) + (BASE % d)) + x[i]) // d + // == (r * BASE + x[i]) // d + + // q[i] < 2**BITS_PER_LIMB + // Proof: + // q[i] == (r * BASE + x[i]) // d + // <= ((d - 1) * 2**BITS_PER_LIMB + (2**BITS_PER_LIMB - 1)) / d + // == (d * 2**BITS_PER_LIMB - 1) / d == 2**BITS_PER_LIMB - 1 / d + // < 2**BITS_PER_LIMB + + *r = acc % d; + // r == (r * BASE + x[i]) % d + // Proof: + // r == acc % d == (r * (BASE % d) + x[i]) % d + // == (r * BASE + x[i]) % d + + // x[:i] == q[:i] * d + r + } +} + +// x = x // 58, r = x % 58 +// Assumes x is normalized +// Guarantees x is normalized +void bn_divmod58(bignum256 *x, uint32_t *r) { bn_long_division(x, 58, x, r); } + +// x = x // 1000, r = x % 1000 +// Assumes x is normalized +// Guarantees x is normalized +void bn_divmod1000(bignum256 *x, uint32_t *r) { + bn_long_division(x, 1000, x, r); +} + +// x = x // 10, r = x % 10 +// Assumes x is normalized +// Guarantees x is normalized +void bn_divmod10(bignum256 *x, uint32_t *r) { bn_long_division(x, 10, x, r); } + +// Formats amount +// Assumes amount is normalized +// Assumes prefix and suffix are null-terminated strings +// Assumes output is an array of length output_length +// The function doesn't have neither constant control flow nor constant memory +// access flow with regard to any its argument +size_t bn_format(const bignum256 *amount, const char *prefix, const char *suffix, unsigned int decimals, int exponent, bool trailing, char *output, size_t output_length) { + +/* + Python prototype of the function: + + def format(amount, prefix, suffix, decimals, exponent, trailing): + if exponent >= 0: + amount *= 10 ** exponent + else: + amount //= 10 ** (-exponent) + + d = pow(10, decimals) + + if decimals: + output = "%d.%0*d" % (amount // d, decimals, amount % d) + if not trailing: + output = output.rstrip("0").rstrip(".") + else: + output = "%d" % (amount // d) + + return prefix + output + suffix +*/ + +// Auxiliary macro for bn_format +// If enough space adds one character to output starting from the end +#define BN_FORMAT_ADD_OUTPUT_CHAR(c) \ + { \ + --position; \ + if (output <= position && position < output + output_length) { \ + *position = (c); \ + } else { \ + memset(output, '\0', output_length); \ + return 0; \ + } \ + } + + bignum256 temp = {0}; + bn_copy(amount, &temp); + uint32_t digit = 0; + + char *position = output + output_length; + + // Add string ending character + BN_FORMAT_ADD_OUTPUT_CHAR('\0'); + + // Add suffix + size_t suffix_length = suffix ? strlen(suffix) : 0; + for (int i = suffix_length - 1; i >= 0; --i) + BN_FORMAT_ADD_OUTPUT_CHAR(suffix[i]) + + // amount //= 10**exponent + for (; exponent < 0; ++exponent) { + // if temp == 0, there is no need to divide it by 10 anymore + if (bn_is_zero(&temp)) { + exponent = 0; + break; + } + bn_divmod10(&temp, &digit); + } + + // exponent >= 0 && decimals >= 0 + + bool fractional_part = false; // is fractional-part of amount present + + { // Add fractional-part digits of amount + // Add trailing zeroes + unsigned int trailing_zeros = decimals < (unsigned int) exponent ? decimals : (unsigned int) exponent; + // When casting a negative int to unsigned int, UINT_MAX is added to the int before + // Since exponent >= 0, the value remains unchanged + decimals -= trailing_zeros; + exponent -= trailing_zeros; + + if (trailing && trailing_zeros) { + fractional_part = true; + for (; trailing_zeros > 0; --trailing_zeros) + BN_FORMAT_ADD_OUTPUT_CHAR('0') + } + + // exponent == 0 || decimals == 0 + + // Add significant digits and leading zeroes + for (; decimals > 0; --decimals) { + bn_divmod10(&temp, &digit); + + if (fractional_part || digit || trailing) { + fractional_part = true; + BN_FORMAT_ADD_OUTPUT_CHAR('0' + digit) + } + else if (bn_is_zero(&temp)) { + // We break since the remaining digits are zeroes and fractional_part == trailing == false + decimals = 0; + break; + } + } + // decimals == 0 + } + + if (fractional_part) { + BN_FORMAT_ADD_OUTPUT_CHAR('.') + } + + { // Add integer-part digits of amount + // Add trailing zeroes + if (!bn_is_zero(&temp)) { + for (; exponent > 0; --exponent) { + BN_FORMAT_ADD_OUTPUT_CHAR('0') + } + } + // decimals == 0 && exponent == 0 + + // Add significant digits + do { + bn_divmod10(&temp, &digit); + BN_FORMAT_ADD_OUTPUT_CHAR('0' + digit) + } while (!bn_is_zero(&temp)); + } + + // Add prefix + size_t prefix_length = prefix ? strlen(prefix) : 0; + for (int i = prefix_length - 1; i >= 0; --i) + BN_FORMAT_ADD_OUTPUT_CHAR(prefix[i]) + + // Move formatted amount to the start of output + int length = output - position + output_length; + memmove(output, position, length); + return length - 1; +} + +#if USE_BN_PRINT +// Prints x in hexadecimal +// Assumes x is normalized and x < 2**256 +void bn_print(const bignum256 *x) { + printf("%06x", x->val[8]); + printf("%08x", ((x->val[7] << 3) | (x->val[6] >> 26))); + printf("%07x", ((x->val[6] << 2) | (x->val[5] >> 27)) & 0x0FFFFFFF); + printf("%07x", ((x->val[5] << 1) | (x->val[4] >> 28)) & 0x0FFFFFFF); + printf("%07x", x->val[4] & 0x0FFFFFFF); + printf("%08x", ((x->val[3] << 3) | (x->val[2] >> 26))); + printf("%07x", ((x->val[2] << 2) | (x->val[1] >> 27)) & 0x0FFFFFFF); + printf("%07x", ((x->val[1] << 1) | (x->val[0] >> 28)) & 0x0FFFFFFF); + printf("%07x", x->val[0] & 0x0FFFFFFF); +} + +// Prints comma separated list of limbs of x +void bn_print_raw(const bignum256 *x) { + for (int i = 0; i < BN_LIMBS - 1; i++) { + printf("0x%08x, ", x->val[i]); + } + printf("0x%08x", x->val[BN_LIMBS - 1]); +} +#endif + +#if USE_INVERSE_FAST +void bn_inverse(bignum256 *x, const bignum256 *prime) { + bn_inverse_fast(x, prime); +} +#else +void bn_inverse(bignum256 *x, const bignum256 *prime) { + bn_inverse_slow(x, prime); +} +#endif diff --git a/tools/windows-replace/trezor-crypto/crypto/blake2b.c b/tools/windows-replace/trezor-crypto/crypto/blake2b.c new file mode 100644 index 00000000000..c630ed0f8cf --- /dev/null +++ b/tools/windows-replace/trezor-crypto/crypto/blake2b.c @@ -0,0 +1,345 @@ +/* + BLAKE2 reference source code package - reference C implementations + + Copyright 2012, Samuel Neves . You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ + +#include + +#include +#include +#include + +#ifdef _MSC_VER +#pragma pack(push, 1) +#endif +typedef struct blake2b_param__ +{ + uint8_t digest_length; /* 1 */ + uint8_t key_length; /* 2 */ + uint8_t fanout; /* 3 */ + uint8_t depth; /* 4 */ + uint32_t leaf_length; /* 8 */ + uint32_t node_offset; /* 12 */ + uint32_t xof_length; /* 16 */ + uint8_t node_depth; /* 17 */ + uint8_t inner_length; /* 18 */ + uint8_t reserved[14]; /* 32 */ + uint8_t salt[BLAKE2B_SALTBYTES]; /* 48 */ + uint8_t personal[BLAKE2B_PERSONALBYTES]; /* 64 */ +} //win __attribute__((packed)) blake2b_param; + +#ifndef _MSC_VER +__attribute__((packed)) +#endif +blake2b_param; +#ifdef _MSC_VER +#pragma pack(pop) +#endif + +const uint64_t blake2b_IV[8] = +{ + 0x6a09e667f3bcc908ULL, 0xbb67ae8584caa73bULL, + 0x3c6ef372fe94f82bULL, 0xa54ff53a5f1d36f1ULL, + 0x510e527fade682d1ULL, 0x9b05688c2b3e6c1fULL, + 0x1f83d9abfb41bd6bULL, 0x5be0cd19137e2179ULL +}; + +const uint8_t blake2b_sigma[12][16] = +{ + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } , + { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } , + { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 } , + { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 } , + { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 } , + { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 } , + { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 } , + { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 } , + { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 } , + { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13 , 0 } , + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } , + { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } +}; + + +static void blake2b_set_lastnode( blake2b_state *S ) +{ + S->f[1] = (uint64_t)-1; +} + +/* Some helper functions, not necessarily useful */ +static int blake2b_is_lastblock( const blake2b_state *S ) +{ + return S->f[0] != 0; +} + +static void blake2b_set_lastblock( blake2b_state *S ) +{ + if( S->last_node ) blake2b_set_lastnode( S ); + + S->f[0] = (uint64_t)-1; +} + +static void blake2b_increment_counter( blake2b_state *S, const uint64_t inc ) +{ + S->t[0] += inc; + S->t[1] += ( S->t[0] < inc ); +} + +static void blake2b_init0( blake2b_state *S ) +{ + size_t i = 0; + memzero( S, sizeof( blake2b_state ) ); + + for( i = 0; i < 8; ++i ) S->h[i] = blake2b_IV[i]; +} + +/* init xors IV with input parameter block */ +int blake2b_init_param( blake2b_state *S, const blake2b_param *P ) +{ + const uint8_t *p = ( const uint8_t * )( P ); + size_t i = 0; + + blake2b_init0( S ); + + /* IV XOR ParamBlock */ + for( i = 0; i < 8; ++i ) + S->h[i] ^= load64( p + sizeof( S->h[i] ) * i ); + + S->outlen = P->digest_length; + return 0; +} + + +/* Sequential blake2b initialization */ +int blake2b_Init( blake2b_state *S, size_t outlen ) +{ + blake2b_param P[1] = {0}; + + if ( ( !outlen ) || ( outlen > BLAKE2B_OUTBYTES ) ) return -1; + + P->digest_length = (uint8_t)outlen; + P->key_length = 0; + P->fanout = 1; + P->depth = 1; + store32( &P->leaf_length, 0 ); + store32( &P->node_offset, 0 ); + store32( &P->xof_length, 0 ); + P->node_depth = 0; + P->inner_length = 0; + memzero( P->reserved, sizeof( P->reserved ) ); + memzero( P->salt, sizeof( P->salt ) ); + memzero( P->personal, sizeof( P->personal ) ); + return blake2b_init_param( S, P ); +} + +int blake2b_InitPersonal( blake2b_state *S, size_t outlen, const void *personal, size_t personal_len) +{ + blake2b_param P[1] = {0}; + + if ( ( !outlen ) || ( outlen > BLAKE2B_OUTBYTES ) ) return -1; + if ( ( !personal ) || ( personal_len != BLAKE2B_PERSONALBYTES ) ) return -1; + + P->digest_length = (uint8_t)outlen; + P->key_length = 0; + P->fanout = 1; + P->depth = 1; + store32( &P->leaf_length, 0 ); + store32( &P->node_offset, 0 ); + store32( &P->xof_length, 0 ); + P->node_depth = 0; + P->inner_length = 0; + memzero( P->reserved, sizeof( P->reserved ) ); + memzero( P->salt, sizeof( P->salt ) ); + memcpy( P->personal, personal, BLAKE2B_PERSONALBYTES ); + return blake2b_init_param( S, P ); +} + +int blake2b_InitKey( blake2b_state *S, size_t outlen, const void *key, size_t keylen ) +{ + blake2b_param P[1] = {0}; + + if ( ( !outlen ) || ( outlen > BLAKE2B_OUTBYTES ) ) return -1; + + if ( !key || !keylen || keylen > BLAKE2B_KEYBYTES ) return -1; + + P->digest_length = (uint8_t)outlen; + P->key_length = (uint8_t)keylen; + P->fanout = 1; + P->depth = 1; + store32( &P->leaf_length, 0 ); + store32( &P->node_offset, 0 ); + store32( &P->xof_length, 0 ); + P->node_depth = 0; + P->inner_length = 0; + memzero( P->reserved, sizeof( P->reserved ) ); + memzero( P->salt, sizeof( P->salt ) ); + memzero( P->personal, sizeof( P->personal ) ); + + if( blake2b_init_param( S, P ) < 0 ) return -1; + + { + uint8_t block[BLAKE2B_BLOCKBYTES] = {0}; + memzero( block, BLAKE2B_BLOCKBYTES ); + memcpy( block, key, keylen ); + blake2b_Update( S, block, BLAKE2B_BLOCKBYTES ); + memzero( block, BLAKE2B_BLOCKBYTES ); /* Burn the key from stack */ + } + return 0; +} + +#define G(r,i,a,b,c,d) \ + do { \ + a = a + b + m[blake2b_sigma[r][2*i+0]]; \ + d = rotr64(d ^ a, 32); \ + c = c + d; \ + b = rotr64(b ^ c, 24); \ + a = a + b + m[blake2b_sigma[r][2*i+1]]; \ + d = rotr64(d ^ a, 16); \ + c = c + d; \ + b = rotr64(b ^ c, 63); \ + } while(0) + +#define ROUND(r) \ + do { \ + G(r,0,v[ 0],v[ 4],v[ 8],v[12]); \ + G(r,1,v[ 1],v[ 5],v[ 9],v[13]); \ + G(r,2,v[ 2],v[ 6],v[10],v[14]); \ + G(r,3,v[ 3],v[ 7],v[11],v[15]); \ + G(r,4,v[ 0],v[ 5],v[10],v[15]); \ + G(r,5,v[ 1],v[ 6],v[11],v[12]); \ + G(r,6,v[ 2],v[ 7],v[ 8],v[13]); \ + G(r,7,v[ 3],v[ 4],v[ 9],v[14]); \ + } while(0) + +static void blake2b_compress( blake2b_state *S, const uint8_t block[BLAKE2B_BLOCKBYTES] ) +{ + uint64_t m[16] = {0}; + uint64_t v[16] = {0}; + size_t i = 0; + + for( i = 0; i < 16; ++i ) { + m[i] = load64( block + i * sizeof( m[i] ) ); + } + + for( i = 0; i < 8; ++i ) { + v[i] = S->h[i]; + } + + v[ 8] = blake2b_IV[0]; + v[ 9] = blake2b_IV[1]; + v[10] = blake2b_IV[2]; + v[11] = blake2b_IV[3]; + v[12] = blake2b_IV[4] ^ S->t[0]; + v[13] = blake2b_IV[5] ^ S->t[1]; + v[14] = blake2b_IV[6] ^ S->f[0]; + v[15] = blake2b_IV[7] ^ S->f[1]; + + ROUND( 0 ); + ROUND( 1 ); + ROUND( 2 ); + ROUND( 3 ); + ROUND( 4 ); + ROUND( 5 ); + ROUND( 6 ); + ROUND( 7 ); + ROUND( 8 ); + ROUND( 9 ); + ROUND( 10 ); + ROUND( 11 ); + + for( i = 0; i < 8; ++i ) { + S->h[i] = S->h[i] ^ v[i] ^ v[i + 8]; + } +} + +#undef G +#undef ROUND + +int blake2b_Update( blake2b_state *S, const void *pin, size_t inlen ) +{ + const unsigned char * in = (const unsigned char *)pin; + if( inlen > 0 ) + { + size_t left = S->buflen; + size_t fill = BLAKE2B_BLOCKBYTES - left; + if( inlen > fill ) + { + S->buflen = 0; + memcpy( S->buf + left, in, fill ); /* Fill buffer */ + blake2b_increment_counter( S, BLAKE2B_BLOCKBYTES ); + blake2b_compress( S, S->buf ); /* Compress */ + in += fill; inlen -= fill; + while(inlen > BLAKE2B_BLOCKBYTES) { + blake2b_increment_counter(S, BLAKE2B_BLOCKBYTES); + blake2b_compress( S, in ); + in += BLAKE2B_BLOCKBYTES; + inlen -= BLAKE2B_BLOCKBYTES; + } + } + memcpy( S->buf + S->buflen, in, inlen ); + S->buflen += inlen; + } + return 0; +} + +int blake2b_Final( blake2b_state *S, void *out, size_t outlen ) +{ + uint8_t buffer[BLAKE2B_OUTBYTES] = {0}; + size_t i = 0; + + if( out == NULL || outlen < S->outlen ) + return -1; + + if( blake2b_is_lastblock( S ) ) + return -1; + + blake2b_increment_counter( S, S->buflen ); + blake2b_set_lastblock( S ); + memzero( S->buf + S->buflen, BLAKE2B_BLOCKBYTES - S->buflen ); /* Padding */ + blake2b_compress( S, S->buf ); + + for( i = 0; i < 8; ++i ) /* Output full hash to temp buffer */ + store64( buffer + sizeof( S->h[i] ) * i, S->h[i] ); + + memcpy( out, buffer, S->outlen ); + memzero(buffer, sizeof(buffer)); + return 0; +} + +int blake2b(const uint8_t *msg, uint32_t msg_len, void *out, size_t outlen) +{ + BLAKE2B_CTX ctx; + if (0 != blake2b_Init(&ctx, outlen)) return -1; + if (0 != blake2b_Update(&ctx, msg, msg_len)) return -1; + if (0 != blake2b_Final(&ctx, out, outlen)) return -1; + return 0; +} + +// [wallet-core] +int blake2b_Personal(const uint8_t *msg, uint32_t msg_len, const void *personal, size_t personal_len, void *out, size_t outlen) +{ + BLAKE2B_CTX ctx; + if (0 != blake2b_InitPersonal(&ctx, outlen, personal, personal_len)) return -1; + if (0 != blake2b_Update(&ctx, msg, msg_len)) return -1; + if (0 != blake2b_Final(&ctx, out, outlen)) return -1; + return 0; +} + +int blake2b_Key(const uint8_t *msg, uint32_t msg_len, const void *key, size_t keylen, void *out, size_t outlen) +{ + BLAKE2B_CTX ctx; + if (0 != blake2b_InitKey(&ctx, outlen, key, keylen)) return -1; + if (0 != blake2b_Update(&ctx, msg, msg_len)) return -1; + if (0 != blake2b_Final(&ctx, out, outlen)) return -1; + return 0; +} diff --git a/tools/windows-replace/trezor-crypto/crypto/blake2s.c b/tools/windows-replace/trezor-crypto/crypto/blake2s.c new file mode 100644 index 00000000000..6de68990302 --- /dev/null +++ b/tools/windows-replace/trezor-crypto/crypto/blake2s.c @@ -0,0 +1,329 @@ +/* + BLAKE2 reference source code package - reference C implementations + + Copyright 2012, Samuel Neves . You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ + +#include + +#include +#include +#include + +#ifdef _MSC_VER +#pragma pack(push, 1) +#endif + +typedef struct blake2s_param__ +{ + uint8_t digest_length; /* 1 */ + uint8_t key_length; /* 2 */ + uint8_t fanout; /* 3 */ + uint8_t depth; /* 4 */ + uint32_t leaf_length; /* 8 */ + uint32_t node_offset; /* 12 */ + uint16_t xof_length; /* 14 */ + uint8_t node_depth; /* 15 */ + uint8_t inner_length; /* 16 */ + /* uint8_t reserved[0]; */ + uint8_t salt[BLAKE2S_SALTBYTES]; /* 24 */ + uint8_t personal[BLAKE2S_PERSONALBYTES]; /* 32 */ +} // win __attribute__((packed)) blake2s_param; +#ifndef _MSC_VER +__attribute__((packed)) +#endif +blake2s_param; +#ifdef _MSC_VER +#pragma pack(pop) +#endif + +const uint32_t blake2s_IV[8] = +{ + 0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL, + 0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL +}; + +const uint8_t blake2s_sigma[10][16] = +{ + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } , + { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } , + { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 } , + { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 } , + { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 } , + { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 } , + { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 } , + { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 } , + { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 } , + { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13 , 0 } , +}; + +static void blake2s_set_lastnode( blake2s_state *S ) +{ + S->f[1] = (uint32_t)-1; +} + +/* Some helper functions, not necessarily useful */ +static int blake2s_is_lastblock( const blake2s_state *S ) +{ + return S->f[0] != 0; +} + +static void blake2s_set_lastblock( blake2s_state *S ) +{ + if( S->last_node ) blake2s_set_lastnode( S ); + + S->f[0] = (uint32_t)-1; +} + +static void blake2s_increment_counter( blake2s_state *S, const uint32_t inc ) +{ + S->t[0] += inc; + S->t[1] += ( S->t[0] < inc ); +} + +static void blake2s_init0( blake2s_state *S ) +{ + size_t i = 0; + memzero( S, sizeof( blake2s_state ) ); + + for( i = 0; i < 8; ++i ) S->h[i] = blake2s_IV[i]; +} + +/* init2 xors IV with input parameter block */ +int blake2s_init_param( blake2s_state *S, const blake2s_param *P ) +{ + const unsigned char *p = ( const unsigned char * )( P ); + size_t i = 0; + + blake2s_init0( S ); + + /* IV XOR ParamBlock */ + for( i = 0; i < 8; ++i ) + S->h[i] ^= load32( &p[i * 4] ); + + S->outlen = P->digest_length; + return 0; +} + + +/* Sequential blake2s initialization */ +int blake2s_Init( blake2s_state *S, size_t outlen ) +{ + blake2s_param P[1] = {0}; + + if ( ( !outlen ) || ( outlen > BLAKE2S_OUTBYTES ) ) return -1; + + P->digest_length = (uint8_t)outlen; + P->key_length = 0; + P->fanout = 1; + P->depth = 1; + store32( &P->leaf_length, 0 ); + store32( &P->node_offset, 0 ); + store16( &P->xof_length, 0 ); + P->node_depth = 0; + P->inner_length = 0; + /* memzero(P->reserved, sizeof(P->reserved) ); */ + memzero( P->salt, sizeof( P->salt ) ); + memzero( P->personal, sizeof( P->personal ) ); + return blake2s_init_param( S, P ); +} + +int blake2s_InitPersonal( blake2s_state *S, size_t outlen, const void *personal, size_t personal_len) +{ + blake2s_param P[1] = {0}; + + if ( ( !outlen ) || ( outlen > BLAKE2S_OUTBYTES ) ) return -1; + if ( ( !personal ) || ( personal_len != BLAKE2S_PERSONALBYTES ) ) return -1; + + P->digest_length = (uint8_t)outlen; + P->key_length = 0; + P->fanout = 1; + P->depth = 1; + store32( &P->leaf_length, 0 ); + store32( &P->node_offset, 0 ); + store16( &P->xof_length, 0 ); + P->node_depth = 0; + P->inner_length = 0; + /* memzero(P->reserved, sizeof(P->reserved) ); */ + memzero( P->salt, sizeof( P->salt ) ); + memcpy( P->personal, personal, BLAKE2S_PERSONALBYTES ); + return blake2s_init_param( S, P ); +} + + +int blake2s_InitKey( blake2s_state *S, size_t outlen, const void *key, size_t keylen ) +{ + blake2s_param P[1] = {0}; + + if ( ( !outlen ) || ( outlen > BLAKE2S_OUTBYTES ) ) return -1; + + if ( !key || !keylen || keylen > BLAKE2S_KEYBYTES ) return -1; + + P->digest_length = (uint8_t)outlen; + P->key_length = (uint8_t)keylen; + P->fanout = 1; + P->depth = 1; + store32( &P->leaf_length, 0 ); + store32( &P->node_offset, 0 ); + store16( &P->xof_length, 0 ); + P->node_depth = 0; + P->inner_length = 0; + /* memzero(P->reserved, sizeof(P->reserved) ); */ + memzero( P->salt, sizeof( P->salt ) ); + memzero( P->personal, sizeof( P->personal ) ); + + if( blake2s_init_param( S, P ) < 0 ) return -1; + + { + uint8_t block[BLAKE2S_BLOCKBYTES] = {0}; + memzero( block, BLAKE2S_BLOCKBYTES ); + memcpy( block, key, keylen ); + blake2s_Update( S, block, BLAKE2S_BLOCKBYTES ); + memzero( block, BLAKE2S_BLOCKBYTES ); /* Burn the key from stack */ + } + return 0; +} + +#define G(r,i,a,b,c,d) \ + do { \ + a = a + b + m[blake2s_sigma[r][2*i+0]]; \ + d = rotr32(d ^ a, 16); \ + c = c + d; \ + b = rotr32(b ^ c, 12); \ + a = a + b + m[blake2s_sigma[r][2*i+1]]; \ + d = rotr32(d ^ a, 8); \ + c = c + d; \ + b = rotr32(b ^ c, 7); \ + } while(0) + +#define ROUND(r) \ + do { \ + G(r,0,v[ 0],v[ 4],v[ 8],v[12]); \ + G(r,1,v[ 1],v[ 5],v[ 9],v[13]); \ + G(r,2,v[ 2],v[ 6],v[10],v[14]); \ + G(r,3,v[ 3],v[ 7],v[11],v[15]); \ + G(r,4,v[ 0],v[ 5],v[10],v[15]); \ + G(r,5,v[ 1],v[ 6],v[11],v[12]); \ + G(r,6,v[ 2],v[ 7],v[ 8],v[13]); \ + G(r,7,v[ 3],v[ 4],v[ 9],v[14]); \ + } while(0) + +static void blake2s_compress( blake2s_state *S, const uint8_t in[BLAKE2S_BLOCKBYTES] ) +{ + uint32_t m[16] = {0}; + uint32_t v[16] = {0}; + size_t i = 0; + + for( i = 0; i < 16; ++i ) { + m[i] = load32( in + i * sizeof( m[i] ) ); + } + + for( i = 0; i < 8; ++i ) { + v[i] = S->h[i]; + } + + v[ 8] = blake2s_IV[0]; + v[ 9] = blake2s_IV[1]; + v[10] = blake2s_IV[2]; + v[11] = blake2s_IV[3]; + v[12] = S->t[0] ^ blake2s_IV[4]; + v[13] = S->t[1] ^ blake2s_IV[5]; + v[14] = S->f[0] ^ blake2s_IV[6]; + v[15] = S->f[1] ^ blake2s_IV[7]; + + ROUND( 0 ); + ROUND( 1 ); + ROUND( 2 ); + ROUND( 3 ); + ROUND( 4 ); + ROUND( 5 ); + ROUND( 6 ); + ROUND( 7 ); + ROUND( 8 ); + ROUND( 9 ); + + for( i = 0; i < 8; ++i ) { + S->h[i] = S->h[i] ^ v[i] ^ v[i + 8]; + } +} + +#undef G +#undef ROUND + +int blake2s_Update( blake2s_state *S, const void *pin, size_t inlen ) +{ + const unsigned char * in = (const unsigned char *)pin; + if( inlen > 0 ) + { + size_t left = S->buflen; + size_t fill = BLAKE2S_BLOCKBYTES - left; + if( inlen > fill ) + { + S->buflen = 0; + memcpy( S->buf + left, in, fill ); /* Fill buffer */ + blake2s_increment_counter( S, BLAKE2S_BLOCKBYTES ); + blake2s_compress( S, S->buf ); /* Compress */ + in += fill; inlen -= fill; + while(inlen > BLAKE2S_BLOCKBYTES) { + blake2s_increment_counter(S, BLAKE2S_BLOCKBYTES); + blake2s_compress( S, in ); + in += BLAKE2S_BLOCKBYTES; + inlen -= BLAKE2S_BLOCKBYTES; + } + } + memcpy( S->buf + S->buflen, in, inlen ); + S->buflen += inlen; + } + return 0; +} + +int blake2s_Final( blake2s_state *S, void *out, size_t outlen ) +{ + uint8_t buffer[BLAKE2S_OUTBYTES] = {0}; + size_t i = 0; + + if( out == NULL || outlen < S->outlen ) + return -1; + + if( blake2s_is_lastblock( S ) ) + return -1; + + blake2s_increment_counter( S, ( uint32_t )S->buflen ); + blake2s_set_lastblock( S ); + memzero( S->buf + S->buflen, BLAKE2S_BLOCKBYTES - S->buflen ); /* Padding */ + blake2s_compress( S, S->buf ); + + for( i = 0; i < 8; ++i ) /* Output full hash to temp buffer */ + store32( buffer + sizeof( S->h[i] ) * i, S->h[i] ); + + memcpy( out, buffer, outlen ); + memzero(buffer, sizeof(buffer)); + return 0; +} + +int blake2s(const uint8_t *msg, uint32_t msg_len, void *out, size_t outlen) +{ + BLAKE2S_CTX ctx; + if (0 != blake2s_Init(&ctx, outlen)) return -1; + if (0 != blake2s_Update(&ctx, msg, msg_len)) return -1; + if (0 != blake2s_Final(&ctx, out, outlen)) return -1; + return 0; +} + +int blake2s_Key(const uint8_t *msg, uint32_t msg_len, const void *key, size_t keylen, void *out, size_t outlen) +{ + BLAKE2S_CTX ctx; + if (0 != blake2s_InitKey(&ctx, outlen, key, keylen)) return -1; + if (0 != blake2s_Update(&ctx, msg, msg_len)) return -1; + if (0 != blake2s_Final(&ctx, out, outlen)) return -1; + return 0; +} diff --git a/tools/windows-replace/trezor-crypto/crypto/cardano.c b/tools/windows-replace/trezor-crypto/crypto/cardano.c new file mode 100644 index 00000000000..650b66ad513 --- /dev/null +++ b/tools/windows-replace/trezor-crypto/crypto/cardano.c @@ -0,0 +1,328 @@ +/** + * Copyright (c) 2013-2021 SatoshiLabs + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if USE_CARDANO + +#define CARDANO_MAX_NODE_DEPTH 1048576 + +const curve_info ed25519_cardano_info = { + .bip32_name = ED25519_CARDANO_NAME, + .params = NULL, + .hasher_base58 = HASHER_SHA2D, + .hasher_sign = HASHER_SHA2D, + .hasher_pubkey = HASHER_SHA2_RIPEMD, + .hasher_script = HASHER_SHA2, +}; + +static void scalar_multiply8(const uint8_t *src, int bytes, uint8_t *dst) { + uint8_t prev_acc = 0; + for (int i = 0; i < bytes; i++) { + dst[i] = (src[i] << 3) + (prev_acc & 0x7); + prev_acc = src[i] >> 5; + } + dst[bytes] = src[bytes - 1] >> 5; +} + +static void scalar_add_256bits(const uint8_t *src1, const uint8_t *src2, + uint8_t *dst) { + uint16_t r = 0; + for (int i = 0; i < 32; i++) { + r = r + (uint16_t)src1[i] + (uint16_t)src2[i]; + dst[i] = r & 0xff; + r >>= 8; + } +} + +static void cardano_ed25519_tweak_bits(uint8_t private_key[32]) { + private_key[0] &= 0xf8; + private_key[31] &= 0x1f; + private_key[31] |= 0x40; +} + +int hdnode_private_ckd_cardano(HDNode *inout, uint32_t index) { + if (inout->curve != &ed25519_cardano_info) { + return 0; + } + + if (inout->depth >= CARDANO_MAX_NODE_DEPTH) { + return 0; + } + + // checks for hardened/non-hardened derivation, keysize 32 means we are + // dealing with public key and thus non-h, keysize 64 is for private key + int keysize = 32; + if (index & 0x80000000) { + keysize = 64; + } + + CONFIDENTIAL uint8_t data[1 + 64 + 4]; + CONFIDENTIAL uint8_t z[32 + 32]; + CONFIDENTIAL uint8_t priv_key[64]; + CONFIDENTIAL uint8_t res_key[64]; + + write_le(data + keysize + 1, index); + + memcpy(priv_key, inout->private_key, 32); + memcpy(priv_key + 32, inout->private_key_extension, 32); + + if (keysize == 64) { // private derivation + data[0] = 0; + memcpy(data + 1, inout->private_key, 32); + memcpy(data + 1 + 32, inout->private_key_extension, 32); + } else { // public derivation + if (hdnode_fill_public_key(inout) != 0) { + return 0; + } + data[0] = 2; + memcpy(data + 1, inout->public_key + 1, 32); + } + + CONFIDENTIAL HMAC_SHA512_CTX ctx; + hmac_sha512_Init(&ctx, inout->chain_code, 32); + hmac_sha512_Update(&ctx, data, 1 + keysize + 4); + hmac_sha512_Final(&ctx, z); + + CONFIDENTIAL uint8_t zl8[32]; + memzero(zl8, 32); + + /* get 8 * Zl */ + scalar_multiply8(z, 28, zl8); + /* Kl = 8*Zl + parent(K)l */ + scalar_add_256bits(zl8, priv_key, res_key); + + /* Kr = Zr + parent(K)r */ + scalar_add_256bits(z + 32, priv_key + 32, res_key + 32); + + memcpy(inout->private_key, res_key, 32); + memcpy(inout->private_key_extension, res_key + 32, 32); + + if (keysize == 64) { + data[0] = 1; + } else { + data[0] = 3; + } + hmac_sha512_Init(&ctx, inout->chain_code, 32); + hmac_sha512_Update(&ctx, data, 1 + keysize + 4); + hmac_sha512_Final(&ctx, z); + + memcpy(inout->chain_code, z + 32, 32); + inout->depth++; + inout->child_num = index; + memzero(inout->public_key, sizeof(inout->public_key)); + + // making sure to wipe our memory + memzero(z, sizeof(z)); + memzero(data, sizeof(data)); + memzero(priv_key, sizeof(priv_key)); + memzero(res_key, sizeof(res_key)); + return 1; +} + +int hdnode_from_secret_cardano(const uint8_t secret[CARDANO_SECRET_LENGTH], + HDNode *out) { + memzero(out, sizeof(HDNode)); + out->depth = 0; + out->child_num = 0; + out->curve = &ed25519_cardano_info; + memcpy(out->private_key, secret, 32); + memcpy(out->private_key_extension, secret + 32, 32); + memcpy(out->chain_code, secret + 64, 32); + + cardano_ed25519_tweak_bits(out->private_key); + + out->public_key[0] = 0; + if (hdnode_fill_public_key(out) != 0) { + return 0; + } + + return 1; +} + +// Derives the root Cardano secret from a master secret, aka seed, as defined in +// SLIP-0023. +int secret_from_seed_cardano_slip23(const uint8_t *seed, int seed_len, + uint8_t secret_out[CARDANO_SECRET_LENGTH]) { + CONFIDENTIAL uint8_t I[SHA512_DIGEST_LENGTH]; + CONFIDENTIAL HMAC_SHA512_CTX ctx; + + hmac_sha512_Init(&ctx, (const uint8_t *)ED25519_CARDANO_NAME, + strlen(ED25519_CARDANO_NAME)); + hmac_sha512_Update(&ctx, seed, seed_len); + hmac_sha512_Final(&ctx, I); + + sha512_Raw(I, 32, secret_out); + + memcpy(secret_out + SHA512_DIGEST_LENGTH, I + 32, 32); + cardano_ed25519_tweak_bits(secret_out); + + memzero(I, sizeof(I)); + memzero(&ctx, sizeof(ctx)); + return 1; +} + +// Derives the root Cardano secret from a BIP-32 master secret via the Ledger +// derivation: +// https://github.com/cardano-foundation/CIPs/blob/09d7d8ee1bd64f7e6b20b5a6cae088039dce00cb/CIP-0003/Ledger.md +int secret_from_seed_cardano_ledger(const uint8_t *seed, int seed_len, + uint8_t secret_out[CARDANO_SECRET_LENGTH]) { + CONFIDENTIAL uint8_t chain_code[SHA256_DIGEST_LENGTH]; + CONFIDENTIAL uint8_t root_key[SHA512_DIGEST_LENGTH]; + CONFIDENTIAL HMAC_SHA256_CTX ctx; + CONFIDENTIAL HMAC_SHA512_CTX sctx; + + const uint8_t *intermediate_result = seed; + int intermediate_result_len = seed_len; + do { + // STEP 1: derive a master secret like in BIP-32/SLIP-10 + hmac_sha512_Init(&sctx, (const uint8_t *)ED25519_SEED_NAME, + strlen(ED25519_SEED_NAME)); + hmac_sha512_Update(&sctx, intermediate_result, intermediate_result_len); + hmac_sha512_Final(&sctx, root_key); + + // STEP 2: check that the resulting key does not have a particular bit set, + // otherwise iterate like in SLIP-10 + intermediate_result = root_key; + intermediate_result_len = sizeof(root_key); + } while (root_key[31] & 0x20); + + // STEP 3: calculate the chain code as a HMAC-SHA256 of "\x01" + seed, + // key is "ed25519 seed" + hmac_sha256_Init(&ctx, (const unsigned char *)ED25519_SEED_NAME, + strlen(ED25519_SEED_NAME)); + hmac_sha256_Update(&ctx, (const unsigned char *)"\x01", 1); + hmac_sha256_Update(&ctx, seed, seed_len); + hmac_sha256_Final(&ctx, chain_code); + + //win +#ifdef _MSC_VER +static_assert( + SHA512_DIGEST_LENGTH + SHA256_DIGEST_LENGTH == CARDANO_SECRET_LENGTH, + "Invalid configuration of Cardano secret size"); + memcpy(secret_out, root_key, SHA512_DIGEST_LENGTH); + memcpy(secret_out + SHA512_DIGEST_LENGTH, chain_code, SHA256_DIGEST_LENGTH); +#else +// STEP 4: extract information into output + _Static_assert( + SHA512_DIGEST_LENGTH + SHA256_DIGEST_LENGTH == CARDANO_SECRET_LENGTH, + "Invalid configuration of Cardano secret size"); + memcpy(secret_out, root_key, SHA512_DIGEST_LENGTH); + memcpy(secret_out + SHA512_DIGEST_LENGTH, chain_code, SHA256_DIGEST_LENGTH); +#endif +#define CARDANO_ICARUS_ROUNDS_PER_STEP \ + (CARDANO_ICARUS_PBKDF2_ROUNDS / CARDANO_ICARUS_STEPS) + + + + + // STEP 5: tweak bits of the private key + cardano_ed25519_tweak_bits(secret_out); + + memzero(&ctx, sizeof(ctx)); + memzero(&sctx, sizeof(sctx)); + memzero(root_key, sizeof(root_key)); + memzero(chain_code, sizeof(chain_code)); + return 1; +} + +#define CARDANO_ICARUS_STEPS 32 +//win +#ifdef _MSC_VER +static_assert( + CARDANO_ICARUS_PBKDF2_ROUNDS % CARDANO_ICARUS_STEPS == 0, + "CARDANO_ICARUS_STEPS does not divide CARDANO_ICARUS_PBKDF2_ROUNDS"); +#else +_Static_assert( + CARDANO_ICARUS_PBKDF2_ROUNDS % CARDANO_ICARUS_STEPS == 0, + "CARDANO_ICARUS_STEPS does not divide CARDANO_ICARUS_PBKDF2_ROUNDS"); +#endif +#define CARDANO_ICARUS_ROUNDS_PER_STEP \ + (CARDANO_ICARUS_PBKDF2_ROUNDS / CARDANO_ICARUS_STEPS) + +// Derives the root Cardano HDNode from a passphrase and the entropy encoded in +// a BIP-0039 mnemonic using the Icarus derivation scheme, aka V2 derivation +// scheme: +// https://github.com/cardano-foundation/CIPs/blob/09d7d8ee1bd64f7e6b20b5a6cae088039dce00cb/CIP-0003/Icarus.md +int secret_from_entropy_cardano_icarus( + const uint8_t *pass, int pass_len, const uint8_t *entropy, int entropy_len, + uint8_t secret_out[CARDANO_SECRET_LENGTH], + void (*progress_callback)(uint32_t, uint32_t)) { + CONFIDENTIAL PBKDF2_HMAC_SHA512_CTX pctx; + CONFIDENTIAL uint8_t digest[SHA512_DIGEST_LENGTH]; + uint32_t progress = 0; + + // PASS 1: first 64 bytes + pbkdf2_hmac_sha512_Init(&pctx, pass, pass_len, entropy, entropy_len, 1); + if (progress_callback) { + progress_callback(progress, CARDANO_ICARUS_PBKDF2_ROUNDS * 2); + } + for (int i = 0; i < CARDANO_ICARUS_STEPS; i++) { + pbkdf2_hmac_sha512_Update(&pctx, CARDANO_ICARUS_ROUNDS_PER_STEP); + if (progress_callback) { + progress += CARDANO_ICARUS_ROUNDS_PER_STEP; + progress_callback(progress, CARDANO_ICARUS_PBKDF2_ROUNDS * 2); + } + } + pbkdf2_hmac_sha512_Final(&pctx, digest); + + memcpy(secret_out, digest, SHA512_DIGEST_LENGTH); + + // PASS 2: remaining 32 bytes + pbkdf2_hmac_sha512_Init(&pctx, pass, pass_len, entropy, entropy_len, 2); + if (progress_callback) { + progress_callback(progress, CARDANO_ICARUS_PBKDF2_ROUNDS * 2); + } + for (int i = 0; i < CARDANO_ICARUS_STEPS; i++) { + pbkdf2_hmac_sha512_Update(&pctx, CARDANO_ICARUS_ROUNDS_PER_STEP); + if (progress_callback) { + progress += CARDANO_ICARUS_ROUNDS_PER_STEP; + progress_callback(progress, CARDANO_ICARUS_PBKDF2_ROUNDS * 2); + } + } + pbkdf2_hmac_sha512_Final(&pctx, digest); + + memcpy(secret_out + SHA512_DIGEST_LENGTH, digest, + CARDANO_SECRET_LENGTH - SHA512_DIGEST_LENGTH); + + cardano_ed25519_tweak_bits(secret_out); + + memzero(&pctx, sizeof(pctx)); + memzero(digest, sizeof(digest)); + return 1; +} + +#endif // USE_CARDANO \ No newline at end of file diff --git a/tools/windows-replace/trezor-crypto/crypto/monero/base58.c b/tools/windows-replace/trezor-crypto/crypto/monero/base58.c new file mode 100644 index 00000000000..c5a6f1ccd2b --- /dev/null +++ b/tools/windows-replace/trezor-crypto/crypto/monero/base58.c @@ -0,0 +1,255 @@ +// Copyright (c) 2014-2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include +#include +#include +#include +#include +#include "int-util.h" +#include + +const size_t alphabet_size = 58; // sizeof(b58digits_ordered) - 1; +const size_t encoded_block_sizes[] = {0, 2, 3, 5, 6, 7, 9, 10, 11}; +const size_t full_block_size = sizeof(encoded_block_sizes) / sizeof(encoded_block_sizes[0]) - 1; +const size_t full_encoded_block_size = 11; // encoded_block_sizes[full_block_size]; +const size_t addr_checksum_size = 4; +const int decoded_block_sizes[] = {0, -1, 1, 2, -1, 3, 4, 5, -1, 6, 7, 8}; +#define reverse_alphabet(letter) ((int8_t) b58digits_map[(int)letter]) + + +uint64_t uint_8be_to_64(const uint8_t* data, size_t size) +{ + assert(1 <= size && size <= sizeof(uint64_t)); + + uint64_t res = 0; + switch (9 - size) + { + case 1: res |= *data++; /* FALLTHRU */ + case 2: res <<= 8; res |= *data++; /* FALLTHRU */ + case 3: res <<= 8; res |= *data++; /* FALLTHRU */ + case 4: res <<= 8; res |= *data++; /* FALLTHRU */ + case 5: res <<= 8; res |= *data++; /* FALLTHRU */ + case 6: res <<= 8; res |= *data++; /* FALLTHRU */ + case 7: res <<= 8; res |= *data++; /* FALLTHRU */ + case 8: res <<= 8; res |= *data; break; + default: assert(false); + } + + return res; +} + +void uint_64_to_8be(uint64_t num, size_t size, uint8_t* data) +{ + assert(1 <= size && size <= sizeof(uint64_t)); + + uint64_t num_be = SWAP64(num); + memcpy(data, (uint8_t*)(&num_be) + sizeof(uint64_t) - size, size); +} + +void encode_block(const char* block, size_t size, char* res) +{ + assert(1 <= size && size <= full_block_size); + + uint64_t num = uint_8be_to_64((uint8_t*)(block), size); + int i = ((int)(encoded_block_sizes[size])) - 1; + while (0 <= i) + { + uint64_t remainder = num % alphabet_size; + num /= alphabet_size; + res[i] = b58digits_ordered[remainder]; + --i; + } +} + +bool decode_block(const char* block, size_t size, char* res) +{ + assert(1 <= size && size <= full_encoded_block_size); + + int res_size = decoded_block_sizes[size]; + if (res_size <= 0) + return false; // Invalid block size + + uint64_t res_num = 0; + uint64_t order = 1; + for (size_t i = size - 1; i < size; --i) + { + if (block[i] & 0x80) + return false; // Invalid symbol + int digit = reverse_alphabet(block[i]); + if (digit < 0) + return false; // Invalid symbol + + uint64_t product_hi = 0; + uint64_t tmp = res_num + mul128(order, (uint64_t) digit, &product_hi); + if (tmp < res_num || 0 != product_hi) + return false; // Overflow + + res_num = tmp; + order *= alphabet_size; // Never overflows, 58^10 < 2^64 + } + + if ((size_t)res_size < full_block_size && (UINT64_C(1) << (8 * res_size)) <= res_num) + return false; // Overflow + + uint_64_to_8be(res_num, res_size, (uint8_t*)(res)); + + return true; +} + + +bool xmr_base58_encode(char *b58, size_t *b58sz, const void *data, size_t binsz) +{ + if (binsz==0) + return true; + + const char * data_bin = data; + size_t full_block_count = binsz / full_block_size; + size_t last_block_size = binsz % full_block_size; + size_t res_size = full_block_count * full_encoded_block_size + encoded_block_sizes[last_block_size]; + + if (b58sz){ + if (res_size >= *b58sz){ + return false; + } + *b58sz = res_size; + } + + for (size_t i = 0; i < full_block_count; ++i) + { + encode_block(data_bin + i * full_block_size, full_block_size, b58 + i * full_encoded_block_size); + } + + if (0 < last_block_size) + { + encode_block(data_bin + full_block_count * full_block_size, last_block_size, b58 + full_block_count * full_encoded_block_size); + } + + return true; +} + +bool xmr_base58_decode(const char *b58, size_t b58sz, void *data, size_t *binsz) +{ + if (b58sz == 0) { + *binsz = 0; + return true; + } + + size_t full_block_count = b58sz / full_encoded_block_size; + size_t last_block_size = b58sz % full_encoded_block_size; + int last_block_decoded_size = decoded_block_sizes[last_block_size]; + if (last_block_decoded_size < 0) { + *binsz = 0; + return false; // Invalid enc length + } + + size_t data_size = full_block_count * full_block_size + last_block_decoded_size; + if (*binsz < data_size){ + *binsz = 0; + return false; + } + + char * data_bin = data; + for (size_t i = 0; i < full_block_count; ++i) + { + if (!decode_block(b58 + i * full_encoded_block_size, full_encoded_block_size, data_bin + i * full_block_size)) + return false; + } + + if (0 < last_block_size) + { + if (!decode_block(b58 + full_block_count * full_encoded_block_size, last_block_size, + data_bin + full_block_count * full_block_size)) + return false; + } + + return true; +} + +int xmr_base58_addr_encode_check(uint64_t tag, const uint8_t *data, size_t binsz, char *b58, size_t b58sz) +{ + if (binsz > 128 || tag > 127) { // tag varint + return false; + } + + size_t b58size = b58sz; +#ifdef _MSC_VER + uint8_t *buf = _alloca((binsz + 1) + HASHER_DIGEST_LENGTH); +#else + uint8_t buf[(binsz + 1) + HASHER_DIGEST_LENGTH]; +#endif + memset(buf, 0, sizeof((binsz + 1) + HASHER_DIGEST_LENGTH)); // win memset(buf, 0, sizeof(buf)); + uint8_t *hash = buf + binsz + 1; + buf[0] = (uint8_t) tag; + memcpy(buf + 1, data, binsz); + hasher_Raw(HASHER_SHA3K, buf, binsz + 1, hash); + + bool r = xmr_base58_encode(b58, &b58size, buf, binsz + 1 + addr_checksum_size); + return (int) (!r ? 0 : b58size); +} + +int xmr_base58_addr_decode_check(const char *addr, size_t sz, uint64_t *tag, void *data, size_t datalen) +{ + size_t buflen = 1 + 64 + addr_checksum_size; + #ifdef _MSC_VER + uint8_t *buf = _alloca(buflen); + #else + uint8_t buf[buflen]; + #endif + memset(buf, 0, buflen); //win memset(buf, 0, sizeof(buf)); + + uint8_t hash[HASHER_DIGEST_LENGTH] = {0}; + + if (!xmr_base58_decode(addr, sz, buf, &buflen)){ + return 0; + } + + size_t res_size = buflen - addr_checksum_size - 1; + if (datalen < res_size){ + return 0; + } + + if (buflen <= addr_checksum_size+1) { + return 0; + } + + hasher_Raw(HASHER_SHA3K, buf, buflen - addr_checksum_size, hash); + if (memcmp(hash, buf + buflen - addr_checksum_size, addr_checksum_size) != 0){ + return 0; + } + + *tag = buf[0]; + if (*tag > 127){ + return false; // varint + } + + memcpy(data, buf+1, res_size); + return (int) res_size; +} diff --git a/tools/windows-replace/trezor-crypto/crypto/rand.c b/tools/windows-replace/trezor-crypto/crypto/rand.c new file mode 100644 index 00000000000..caff5a4242c --- /dev/null +++ b/tools/windows-replace/trezor-crypto/crypto/rand.c @@ -0,0 +1,151 @@ +/** + * Copyright (c) 2013-2014 Tomas Dzetkulic + * Copyright (c) 2013-2014 Pavol Rusnak + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +#include + +#include +#include + +#ifdef WIN32 + +#include +#include + +static volatile LONG random_refc = 0; +static volatile BOOL random_lock = 0; +static volatile PVOID random_provider = NULL; + +// [wallet-core] +void *random_init() { + BCRYPT_ALG_HANDLE prov; + while (InterlockedCompareExchange(&random_lock, 1, 0)) + { + SwitchToThread(); + } + LONG inc = InterlockedIncrement(&random_refc); + if (inc == 1) + { + // Create new provider + if (!BCRYPT_SUCCESS(BCryptOpenAlgorithmProvider(&prov, BCRYPT_RNG_ALGORITHM, NULL, 0))) { + prov = NULL; + } + (void)InterlockedExchangePointer(&random_provider, prov); + } + else + { + // Get existing provider + prov = InterlockedCompareExchangePointer(&random_provider, 0, 0); + } + if (!prov) + { + InterlockedDecrement(&random_refc); + } + (void)InterlockedExchange(&random_lock, 0); + return prov; +} + +// [wallet-core] +void random_release() { + while (InterlockedCompareExchange(&random_lock, 1, 0)) + { + SwitchToThread(); + } + LONG dec = InterlockedDecrement(&random_refc); + if (dec == 0) + { + BCryptCloseAlgorithmProvider(random_provider, 0); + random_provider = NULL; + } + (void)InterlockedExchange(&random_lock, 0); +} + +// [wallet-core] +uint32_t random32() { + BCRYPT_ALG_HANDLE prov; + uint32_t res; + if (!(prov = random_init())) { + return 0; + } + if (!BCRYPT_SUCCESS(BCryptGenRandom(prov, (PUCHAR)(&res), sizeof(res), 0))) { + res = 0; + } + random_release(); + return res; +} + +void random_buffer(uint8_t *buf, size_t len) { + BCRYPT_ALG_HANDLE prov; + if (!(prov = random_init())) { + return; + } + for (size_t i = 0; i < len;) + { + size_t l = min(0x80000000UL, len); + (void)BCryptGenRandom(prov, (PUCHAR)(buf + i), (ULONG)l, 0); + i += l; + } + random_release(); +} + +#else +#include +#include + + // [wallet-core] +void *random_init() { + static int dummy; + return &dummy; // return a valid pointer +} + +// [wallet-core] +void random_release() { + // no-op +} + +// [wallet-core] +uint32_t __attribute__((weak)) random32(void) { + int randomData = open("/dev/urandom", O_RDONLY); + if (randomData < 0) { + return 0; + } + + uint32_t result; + if (read(randomData, &result, sizeof(result)) < 0) { + return 0; + } + + close(randomData); + + return result; +} + +void __attribute__((weak)) random_buffer(uint8_t *buf, size_t len) { + int randomData = open("/dev/urandom", O_RDONLY); + if (randomData < 0) { + return; + } + if (read(randomData, buf, len) < 0) { + return; + } + close(randomData); +} +#endif \ No newline at end of file diff --git a/tools/windows-replace/trezor-crypto/crypto/sha3.c b/tools/windows-replace/trezor-crypto/crypto/sha3.c new file mode 100644 index 00000000000..0033186fe59 --- /dev/null +++ b/tools/windows-replace/trezor-crypto/crypto/sha3.c @@ -0,0 +1,398 @@ +/* sha3.c - an implementation of Secure Hash Algorithm 3 (Keccak). + * based on the + * The Keccak SHA-3 submission. Submission to NIST (Round 3), 2011 + * by Guido Bertoni, Joan Daemen, Michaël Peeters and Gilles Van Assche + * + * Copyright: 2013 Aleksey Kravchenko + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. Use this program at your own risk! + */ + +#include +#include + +#include +#include + +#define I64(x) x##LL +#define ROTL64(qword, n) ((qword) << (n) ^ ((qword) >> (64 - (n)))) +#define le2me_64(x) (x) +//#define IS_ALIGNED_64(p) (0 == (7 & ((long)(p)))) // [wallet-core] pointer/numerical type, for MacOS SDK 12.3 +#define IS_ALIGNED_64(p) (0 == (7 & ((const char*)(p) - (const char*)0))) //win +#define me64_to_le_str(to, from, length) memcpy((to), (from), (length)) + +/* constants */ +#define NumberOfRounds 24 + +/* SHA3 (Keccak) constants for 24 rounds */ +uint64_t keccak_round_constants[NumberOfRounds] = { + I64(0x0000000000000001), I64(0x0000000000008082), I64(0x800000000000808A), I64(0x8000000080008000), + I64(0x000000000000808B), I64(0x0000000080000001), I64(0x8000000080008081), I64(0x8000000000008009), + I64(0x000000000000008A), I64(0x0000000000000088), I64(0x0000000080008009), I64(0x000000008000000A), + I64(0x000000008000808B), I64(0x800000000000008B), I64(0x8000000000008089), I64(0x8000000000008003), + I64(0x8000000000008002), I64(0x8000000000000080), I64(0x000000000000800A), I64(0x800000008000000A), + I64(0x8000000080008081), I64(0x8000000000008080), I64(0x0000000080000001), I64(0x8000000080008008) +}; + +/* Initializing a sha3 context for given number of output bits */ +static void keccak_Init(SHA3_CTX *ctx, unsigned bits) +{ + /* NB: The Keccak capacity parameter = bits * 2 */ + unsigned rate = 1600 - bits * 2; + + memzero(ctx, sizeof(SHA3_CTX)); + ctx->block_size = rate / 8; + assert(rate <= 1600 && (rate % 64) == 0); +} + +/** + * Initialize context before calculating hash. + * + * @param ctx context to initialize + */ +void sha3_224_Init(SHA3_CTX *ctx) +{ + keccak_Init(ctx, 224); +} + +/** + * Initialize context before calculating hash. + * + * @param ctx context to initialize + */ +void sha3_256_Init(SHA3_CTX *ctx) +{ + keccak_Init(ctx, 256); +} + +/** + * Initialize context before calculating hash. + * + * @param ctx context to initialize + */ +void sha3_384_Init(SHA3_CTX *ctx) +{ + keccak_Init(ctx, 384); +} + +/** + * Initialize context before calculating hash. + * + * @param ctx context to initialize + */ +void sha3_512_Init(SHA3_CTX *ctx) +{ + keccak_Init(ctx, 512); +} + +/* Keccak theta() transformation */ +static void keccak_theta(uint64_t *A) +{ + unsigned int x = 0; + uint64_t C[5] = {0}, D[5] = {0}; + + for (x = 0; x < 5; x++) { + C[x] = A[x] ^ A[x + 5] ^ A[x + 10] ^ A[x + 15] ^ A[x + 20]; + } + D[0] = ROTL64(C[1], 1) ^ C[4]; + D[1] = ROTL64(C[2], 1) ^ C[0]; + D[2] = ROTL64(C[3], 1) ^ C[1]; + D[3] = ROTL64(C[4], 1) ^ C[2]; + D[4] = ROTL64(C[0], 1) ^ C[3]; + + for (x = 0; x < 5; x++) { + A[x] ^= D[x]; + A[x + 5] ^= D[x]; + A[x + 10] ^= D[x]; + A[x + 15] ^= D[x]; + A[x + 20] ^= D[x]; + } +} + +/* Keccak pi() transformation */ +static void keccak_pi(uint64_t *A) +{ + uint64_t A1 = 0; + A1 = A[1]; + A[ 1] = A[ 6]; + A[ 6] = A[ 9]; + A[ 9] = A[22]; + A[22] = A[14]; + A[14] = A[20]; + A[20] = A[ 2]; + A[ 2] = A[12]; + A[12] = A[13]; + A[13] = A[19]; + A[19] = A[23]; + A[23] = A[15]; + A[15] = A[ 4]; + A[ 4] = A[24]; + A[24] = A[21]; + A[21] = A[ 8]; + A[ 8] = A[16]; + A[16] = A[ 5]; + A[ 5] = A[ 3]; + A[ 3] = A[18]; + A[18] = A[17]; + A[17] = A[11]; + A[11] = A[ 7]; + A[ 7] = A[10]; + A[10] = A1; + /* note: A[ 0] is left as is */ +} + +/* Keccak chi() transformation */ +static void keccak_chi(uint64_t *A) +{ + int i = 0; + for (i = 0; i < 25; i += 5) { + uint64_t A0 = A[0 + i], A1 = A[1 + i]; + A[0 + i] ^= ~A1 & A[2 + i]; + A[1 + i] ^= ~A[2 + i] & A[3 + i]; + A[2 + i] ^= ~A[3 + i] & A[4 + i]; + A[3 + i] ^= ~A[4 + i] & A0; + A[4 + i] ^= ~A0 & A1; + } +} + +static void sha3_permutation(uint64_t *state) +{ + int round = 0; + for (round = 0; round < NumberOfRounds; round++) + { + keccak_theta(state); + + /* apply Keccak rho() transformation */ + state[ 1] = ROTL64(state[ 1], 1); + state[ 2] = ROTL64(state[ 2], 62); + state[ 3] = ROTL64(state[ 3], 28); + state[ 4] = ROTL64(state[ 4], 27); + state[ 5] = ROTL64(state[ 5], 36); + state[ 6] = ROTL64(state[ 6], 44); + state[ 7] = ROTL64(state[ 7], 6); + state[ 8] = ROTL64(state[ 8], 55); + state[ 9] = ROTL64(state[ 9], 20); + state[10] = ROTL64(state[10], 3); + state[11] = ROTL64(state[11], 10); + state[12] = ROTL64(state[12], 43); + state[13] = ROTL64(state[13], 25); + state[14] = ROTL64(state[14], 39); + state[15] = ROTL64(state[15], 41); + state[16] = ROTL64(state[16], 45); + state[17] = ROTL64(state[17], 15); + state[18] = ROTL64(state[18], 21); + state[19] = ROTL64(state[19], 8); + state[20] = ROTL64(state[20], 18); + state[21] = ROTL64(state[21], 2); + state[22] = ROTL64(state[22], 61); + state[23] = ROTL64(state[23], 56); + state[24] = ROTL64(state[24], 14); + + keccak_pi(state); + keccak_chi(state); + + /* apply iota(state, round) */ + *state ^= keccak_round_constants[round]; + } +} + +/** + * The core transformation. Process the specified block of data. + * + * @param hash the algorithm state + * @param block the message block to process + * @param block_size the size of the processed block in bytes + */ +static void sha3_process_block(uint64_t hash[25], const uint64_t *block, size_t block_size) +{ + /* expanded loop */ + hash[ 0] ^= le2me_64(block[ 0]); + hash[ 1] ^= le2me_64(block[ 1]); + hash[ 2] ^= le2me_64(block[ 2]); + hash[ 3] ^= le2me_64(block[ 3]); + hash[ 4] ^= le2me_64(block[ 4]); + hash[ 5] ^= le2me_64(block[ 5]); + hash[ 6] ^= le2me_64(block[ 6]); + hash[ 7] ^= le2me_64(block[ 7]); + hash[ 8] ^= le2me_64(block[ 8]); + /* if not sha3-512 */ + if (block_size > 72) { + hash[ 9] ^= le2me_64(block[ 9]); + hash[10] ^= le2me_64(block[10]); + hash[11] ^= le2me_64(block[11]); + hash[12] ^= le2me_64(block[12]); + /* if not sha3-384 */ + if (block_size > 104) { + hash[13] ^= le2me_64(block[13]); + hash[14] ^= le2me_64(block[14]); + hash[15] ^= le2me_64(block[15]); + hash[16] ^= le2me_64(block[16]); + /* if not sha3-256 */ + if (block_size > 136) { + hash[17] ^= le2me_64(block[17]); +#ifdef FULL_SHA3_FAMILY_SUPPORT + /* if not sha3-224 */ + if (block_size > 144) { + hash[18] ^= le2me_64(block[18]); + hash[19] ^= le2me_64(block[19]); + hash[20] ^= le2me_64(block[20]); + hash[21] ^= le2me_64(block[21]); + hash[22] ^= le2me_64(block[22]); + hash[23] ^= le2me_64(block[23]); + hash[24] ^= le2me_64(block[24]); + } +#endif + } + } + } + /* make a permutation of the hash */ + sha3_permutation(hash); +} + +#define SHA3_FINALIZED 0x80000000 + +/** + * Calculate message hash. + * Can be called repeatedly with chunks of the message to be hashed. + * + * @param ctx the algorithm context containing current hashing state + * @param msg message chunk + * @param size length of the message chunk + */ +void sha3_Update(SHA3_CTX *ctx, const unsigned char *msg, size_t size) +{ + size_t idx = (size_t)ctx->rest; + size_t block_size = (size_t)ctx->block_size; + + if (ctx->rest & SHA3_FINALIZED) return; /* too late for additional input */ + ctx->rest = (unsigned)((ctx->rest + size) % block_size); + + /* fill partial block */ + if (idx) { + size_t left = block_size - idx; + memcpy((char*)ctx->message + idx, msg, (size < left ? size : left)); + if (size < left) return; + + /* process partial block */ + sha3_process_block(ctx->hash, ctx->message, block_size); + msg += left; + size -= left; + } + while (size >= block_size) { + uint64_t *aligned_message_block = NULL; + if (IS_ALIGNED_64(msg)) { + /* the most common case is processing of an already aligned message + without copying it */ + aligned_message_block = (uint64_t*)(void*)msg; + } else { + memcpy(ctx->message, msg, block_size); + aligned_message_block = ctx->message; + } + + sha3_process_block(ctx->hash, aligned_message_block, block_size); + msg += block_size; + size -= block_size; + } + if (size) { + memcpy(ctx->message, msg, size); /* save leftovers */ + } +} + +/** + * Store calculated hash into the given array. + * + * @param ctx the algorithm context containing current hashing state + * @param result calculated hash in binary form + */ +void sha3_Final(SHA3_CTX *ctx, unsigned char* result) +{ + size_t digest_length = 100 - ctx->block_size / 2; + const size_t block_size = ctx->block_size; + + if (!(ctx->rest & SHA3_FINALIZED)) + { + /* clear the rest of the data queue */ + memzero((char*)ctx->message + ctx->rest, block_size - ctx->rest); + ((char*)ctx->message)[ctx->rest] |= 0x06; + ((char*)ctx->message)[block_size - 1] |= 0x80; + + /* process final block */ + sha3_process_block(ctx->hash, ctx->message, block_size); + ctx->rest = SHA3_FINALIZED; /* mark context as finalized */ + } + + assert(block_size > digest_length); + if (result) me64_to_le_str(result, ctx->hash, digest_length); + memzero(ctx, sizeof(SHA3_CTX)); +} + +#if USE_KECCAK +/** +* Store calculated hash into the given array. +* +* @param ctx the algorithm context containing current hashing state +* @param result calculated hash in binary form +*/ +void keccak_Final(SHA3_CTX *ctx, unsigned char* result) +{ + size_t digest_length = 100 - ctx->block_size / 2; + const size_t block_size = ctx->block_size; + + if (!(ctx->rest & SHA3_FINALIZED)) + { + /* clear the rest of the data queue */ + memzero((char*)ctx->message + ctx->rest, block_size - ctx->rest); + ((char*)ctx->message)[ctx->rest] |= 0x01; + ((char*)ctx->message)[block_size - 1] |= 0x80; + + /* process final block */ + sha3_process_block(ctx->hash, ctx->message, block_size); + ctx->rest = SHA3_FINALIZED; /* mark context as finalized */ + } + + assert(block_size > digest_length); + if (result) me64_to_le_str(result, ctx->hash, digest_length); + memzero(ctx, sizeof(SHA3_CTX)); +} + +void keccak_256(const unsigned char* data, size_t len, unsigned char* digest) +{ + SHA3_CTX ctx = {0}; + keccak_256_Init(&ctx); + keccak_Update(&ctx, data, len); + keccak_Final(&ctx, digest); +} + +void keccak_512(const unsigned char* data, size_t len, unsigned char* digest) +{ + SHA3_CTX ctx = {0}; + keccak_512_Init(&ctx); + keccak_Update(&ctx, data, len); + keccak_Final(&ctx, digest); +} +#endif /* USE_KECCAK */ + +void sha3_256(const unsigned char* data, size_t len, unsigned char* digest) +{ + SHA3_CTX ctx = {0}; + sha3_256_Init(&ctx); + sha3_Update(&ctx, data, len); + sha3_Final(&ctx, digest); +} + +void sha3_512(const unsigned char* data, size_t len, unsigned char* digest) +{ + SHA3_CTX ctx = {0}; + sha3_512_Init(&ctx); + sha3_Update(&ctx, data, len); + sha3_Final(&ctx, digest); +} diff --git a/tools/windows-replace/trezor-crypto/crypto/shamir.c b/tools/windows-replace/trezor-crypto/crypto/shamir.c new file mode 100644 index 00000000000..5aea89bec6b --- /dev/null +++ b/tools/windows-replace/trezor-crypto/crypto/shamir.c @@ -0,0 +1,347 @@ +/* + * Implementation of the hazardous parts of the SSS library + * + * Copyright (c) 2017 Daan Sprenkels + * Copyright (c) 2019 SatoshiLabs + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * This code contains the actual Shamir secret sharing functionality. The + * implementation of this code is based on the idea that the user likes to + * generate/combine 32 shares (in GF(2^8)) at the same time, because a 256 bit + * key will be exactly 32 bytes. Therefore we bitslice all the input and + * unbitslice the output right before returning. + * + * This bitslice approach optimizes natively on all architectures that are 32 + * bit or more. Care is taken to use not too many registers, to ensure that no + * values have to be leaked to the stack. + * + * All functions in this module are implemented constant time and constant + * lookup operations, as all proper crypto code must be. + */ + +#include +#include +#include + +static void bitslice(uint32_t r[8], const uint8_t *x, size_t len) { + size_t bit_idx = 0, arr_idx = 0; + uint32_t cur = 0; + + memset(r, 0, sizeof(uint32_t[8])); + for (arr_idx = 0; arr_idx < len; arr_idx++) { + cur = (uint32_t)x[arr_idx]; + for (bit_idx = 0; bit_idx < 8; bit_idx++) { + r[bit_idx] |= ((cur >> bit_idx) & 1) << arr_idx; + } + } +} + +static void unbitslice(uint8_t *r, const uint32_t x[8], size_t len) { + size_t bit_idx = 0, arr_idx = 0; + uint32_t cur = 0; + + memset(r, 0, sizeof(uint8_t) * len); + for (bit_idx = 0; bit_idx < 8; bit_idx++) { + cur = (uint32_t)x[bit_idx]; + for (arr_idx = 0; arr_idx < len; arr_idx++) { + r[arr_idx] |= ((cur >> arr_idx) & 1) << bit_idx; + } + } +} + +static void bitslice_setall(uint32_t r[8], const uint8_t x) { + size_t idx = 0; + for (idx = 0; idx < 8; idx++) { + r[idx] = -((x >> idx) & 1); + } +} + +/* + * Add (XOR) `r` with `x` and store the result in `r`. + */ +static void gf256_add(uint32_t r[8], const uint32_t x[8]) { + size_t idx = 0; + for (idx = 0; idx < 8; idx++) r[idx] ^= x[idx]; +} + +/* + * Safely multiply two bitsliced polynomials in GF(2^8) reduced by + * x^8 + x^4 + x^3 + x + 1. `r` and `a` may overlap, but overlapping of `r` + * and `b` will produce an incorrect result! If you need to square a polynomial + * use `gf256_square` instead. + */ +static void gf256_mul(uint32_t r[8], const uint32_t a[8], const uint32_t b[8]) { + /* This function implements Russian Peasant multiplication on two + * bitsliced polynomials. + * + * I personally think that these kinds of long lists of operations + * are often a bit ugly. A double for loop would be nicer and would + * take up a lot less lines of code. + * However, some compilers seem to fail in optimizing these kinds of + * loops. So we will just have to do this by hand. + */ + uint32_t a2[8] = {0}; + memcpy(a2, a, sizeof(uint32_t[8])); + + r[0] = a2[0] & b[0]; /* add (assignment, because r is 0) */ + r[1] = a2[1] & b[0]; + r[2] = a2[2] & b[0]; + r[3] = a2[3] & b[0]; + r[4] = a2[4] & b[0]; + r[5] = a2[5] & b[0]; + r[6] = a2[6] & b[0]; + r[7] = a2[7] & b[0]; + a2[0] ^= a2[7]; /* reduce */ + a2[2] ^= a2[7]; + a2[3] ^= a2[7]; + + r[0] ^= a2[7] & b[1]; /* add */ + r[1] ^= a2[0] & b[1]; + r[2] ^= a2[1] & b[1]; + r[3] ^= a2[2] & b[1]; + r[4] ^= a2[3] & b[1]; + r[5] ^= a2[4] & b[1]; + r[6] ^= a2[5] & b[1]; + r[7] ^= a2[6] & b[1]; + a2[7] ^= a2[6]; /* reduce */ + a2[1] ^= a2[6]; + a2[2] ^= a2[6]; + + r[0] ^= a2[6] & b[2]; /* add */ + r[1] ^= a2[7] & b[2]; + r[2] ^= a2[0] & b[2]; + r[3] ^= a2[1] & b[2]; + r[4] ^= a2[2] & b[2]; + r[5] ^= a2[3] & b[2]; + r[6] ^= a2[4] & b[2]; + r[7] ^= a2[5] & b[2]; + a2[6] ^= a2[5]; /* reduce */ + a2[0] ^= a2[5]; + a2[1] ^= a2[5]; + + r[0] ^= a2[5] & b[3]; /* add */ + r[1] ^= a2[6] & b[3]; + r[2] ^= a2[7] & b[3]; + r[3] ^= a2[0] & b[3]; + r[4] ^= a2[1] & b[3]; + r[5] ^= a2[2] & b[3]; + r[6] ^= a2[3] & b[3]; + r[7] ^= a2[4] & b[3]; + a2[5] ^= a2[4]; /* reduce */ + a2[7] ^= a2[4]; + a2[0] ^= a2[4]; + + r[0] ^= a2[4] & b[4]; /* add */ + r[1] ^= a2[5] & b[4]; + r[2] ^= a2[6] & b[4]; + r[3] ^= a2[7] & b[4]; + r[4] ^= a2[0] & b[4]; + r[5] ^= a2[1] & b[4]; + r[6] ^= a2[2] & b[4]; + r[7] ^= a2[3] & b[4]; + a2[4] ^= a2[3]; /* reduce */ + a2[6] ^= a2[3]; + a2[7] ^= a2[3]; + + r[0] ^= a2[3] & b[5]; /* add */ + r[1] ^= a2[4] & b[5]; + r[2] ^= a2[5] & b[5]; + r[3] ^= a2[6] & b[5]; + r[4] ^= a2[7] & b[5]; + r[5] ^= a2[0] & b[5]; + r[6] ^= a2[1] & b[5]; + r[7] ^= a2[2] & b[5]; + a2[3] ^= a2[2]; /* reduce */ + a2[5] ^= a2[2]; + a2[6] ^= a2[2]; + + r[0] ^= a2[2] & b[6]; /* add */ + r[1] ^= a2[3] & b[6]; + r[2] ^= a2[4] & b[6]; + r[3] ^= a2[5] & b[6]; + r[4] ^= a2[6] & b[6]; + r[5] ^= a2[7] & b[6]; + r[6] ^= a2[0] & b[6]; + r[7] ^= a2[1] & b[6]; + a2[2] ^= a2[1]; /* reduce */ + a2[4] ^= a2[1]; + a2[5] ^= a2[1]; + + r[0] ^= a2[1] & b[7]; /* add */ + r[1] ^= a2[2] & b[7]; + r[2] ^= a2[3] & b[7]; + r[3] ^= a2[4] & b[7]; + r[4] ^= a2[5] & b[7]; + r[5] ^= a2[6] & b[7]; + r[6] ^= a2[7] & b[7]; + r[7] ^= a2[0] & b[7]; + + memzero(a2, sizeof(a2)); +} + +/* + * Square `x` in GF(2^8) and write the result to `r`. `r` and `x` may overlap. + */ +static void gf256_square(uint32_t r[8], const uint32_t x[8]) { + uint32_t r8 = 0, r10 = 0, r12 = 0, r14 = 0; + /* Use the Freshman's Dream rule to square the polynomial + * Assignments are done from 7 downto 0, because this allows the user + * to execute this function in-place (e.g. `gf256_square(r, r);`). + */ + r14 = x[7]; + r12 = x[6]; + r10 = x[5]; + r8 = x[4]; + r[6] = x[3]; + r[4] = x[2]; + r[2] = x[1]; + r[0] = x[0]; + + /* Reduce with x^8 + x^4 + x^3 + x + 1 until order is less than 8 */ + r[7] = r14; /* r[7] was 0 */ + r[6] ^= r14; + r10 ^= r14; + /* Skip, because r13 is always 0 */ + r[4] ^= r12; + r[5] = r12; /* r[5] was 0 */ + r[7] ^= r12; + r8 ^= r12; + /* Skip, because r11 is always 0 */ + r[2] ^= r10; + r[3] = r10; /* r[3] was 0 */ + r[5] ^= r10; + r[6] ^= r10; + r[1] = r14; /* r[1] was 0 */ + r[2] ^= r14; /* Substitute r9 by r14 because they will always be equal*/ + r[4] ^= r14; + r[5] ^= r14; + r[0] ^= r8; + r[1] ^= r8; + r[3] ^= r8; + r[4] ^= r8; +} + +/* + * Invert `x` in GF(2^8) and write the result to `r` + */ +static void gf256_inv(uint32_t r[8], uint32_t x[8]) { + uint32_t y[8] = {0}, z[8] = {0}; + + gf256_square(y, x); // y = x^2 + gf256_square(y, y); // y = x^4 + gf256_square(r, y); // r = x^8 + gf256_mul(z, r, x); // z = x^9 + gf256_square(r, r); // r = x^16 + gf256_mul(r, r, z); // r = x^25 + gf256_square(r, r); // r = x^50 + gf256_square(z, r); // z = x^100 + gf256_square(z, z); // z = x^200 + gf256_mul(r, r, z); // r = x^250 + gf256_mul(r, r, y); // r = x^254 + + memzero(y, sizeof(y)); + memzero(z, sizeof(z)); +} + +bool shamir_interpolate(uint8_t *result, uint8_t result_index, + const uint8_t *share_indices, + const uint8_t **share_values, uint8_t share_count, + size_t len) { + size_t i = 0, j = 0; + uint32_t x[8] = {0}; + #ifdef _MSC_VER + uint32_t (*xs)[8] = _alloca(sizeof(uint32_t) * share_count * 8); + memset(xs, 0, sizeof(uint32_t) * share_count * 8); + uint32_t (*ys)[8] = _alloca(sizeof(uint32_t) * share_count * 8); + memset(ys, 0, sizeof(uint32_t) * share_count * 8); +#else + uint32_t xs[share_count][8]; + memset(xs, 0, sizeof(xs)); + uint32_t ys[share_count][8]; + memset(ys, 0, sizeof(ys)); + #endif + uint32_t num[8] = {~0}; /* num is the numerator (=1) */ + uint32_t denom[8] = {0}; + uint32_t tmp[8] = {0}; + uint32_t secret[8] = {0}; + bool ret = true; + + if (len > SHAMIR_MAX_LEN) return false; + + /* Collect the x and y values */ + for (i = 0; i < share_count; i++) { + bitslice_setall(xs[i], share_indices[i]); + bitslice(ys[i], share_values[i], len); + } + bitslice_setall(x, result_index); + + for (i = 0; i < share_count; i++) { + memcpy(tmp, x, sizeof(uint32_t[8])); + gf256_add(tmp, xs[i]); + gf256_mul(num, num, tmp); + } + + /* Use Lagrange basis polynomials to calculate the secret coefficient */ + for (i = 0; i < share_count; i++) { + /* The code below assumes that none of the share_indices are equal to + * result_index. We need to treat that as a special case. */ + if (share_indices[i] != result_index) { + memcpy(denom, x, sizeof(denom)); + gf256_add(denom, xs[i]); + } else { + bitslice_setall(denom, 1); + gf256_add(secret, ys[i]); + } + for (j = 0; j < share_count; j++) { + if (i == j) continue; + memcpy(tmp, xs[i], sizeof(uint32_t[8])); + gf256_add(tmp, xs[j]); + gf256_mul(denom, denom, tmp); + } + if ((denom[0] | denom[1] | denom[2] | denom[3] | denom[4] | denom[5] | + denom[6] | denom[7]) == 0) { + /* The share_indices are not unique. */ + ret = false; + break; + } + gf256_inv(tmp, denom); /* inverted denominator */ + gf256_mul(tmp, tmp, num); /* basis polynomial */ + gf256_mul(tmp, tmp, ys[i]); /* scaled coefficient */ + gf256_add(secret, tmp); + } + + if (ret == true) { + unbitslice(result, secret, len); + } + + memzero(x, sizeof(x)); +#ifdef _MSC_VER + memzero(xs, sizeof(uint32_t) * share_count * 8); + memzero(ys, sizeof(uint32_t) * share_count * 8); +#else + memzero(xs, sizeof(xs)); + memzero(ys, sizeof(ys)); +#endif + memzero(num, sizeof(num)); + memzero(denom, sizeof(denom)); + memzero(tmp, sizeof(tmp)); + memzero(secret, sizeof(secret)); + return ret; +} diff --git a/tools/windows-replace/trezor-crypto/crypto/tests/CMakeLists.txt b/tools/windows-replace/trezor-crypto/crypto/tests/CMakeLists.txt new file mode 100644 index 00000000000..4960fbdd0ee --- /dev/null +++ b/tools/windows-replace/trezor-crypto/crypto/tests/CMakeLists.txt @@ -0,0 +1,25 @@ +# 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. + +enable_testing() + +if(WIN32) + find_library(CHECK_LIB_RELEASE check PATH ${CMAKE_SOURCE_DIR}/build/local/lib) + find_library(CHECK_LIB_DEBUG checkd PATH ${CMAKE_SOURCE_DIR}/build/local/lib) + set (CHECK_LIBRARIES optimized ${CHECK_LIB_RELEASE} debug ${CHECK_LIB_DEBUG}) +else() + find_library(check PATH ${CMAKE_SOURCE_DIR}/build/local/lib/pkgconfig NO_DEFAULT_PATH) + set (CHECK_LIBRARIES check) +endif() + +# Test executable +add_executable(TrezorCryptoTests test_check.c) +target_link_libraries(TrezorCryptoTests TrezorCrypto ${CHECK_LIBRARIES}) + +target_link_directories(TrezorCryptoTests PRIVATE ${PREFIX}/lib) +target_include_directories(TrezorCryptoTests PRIVATE ${CMAKE_SOURCE_DIR}/src ${PREFIX}/include) + +add_test(NAME test_check COMMAND TrezorCryptoTests) diff --git a/tools/windows-replace/trezor-crypto/crypto/tests/test_check.c b/tools/windows-replace/trezor-crypto/crypto/tests/test_check.c new file mode 100644 index 00000000000..097689f21ed --- /dev/null +++ b/tools/windows-replace/trezor-crypto/crypto/tests/test_check.c @@ -0,0 +1,9472 @@ +/** + * Copyright (c) 2013-2014 Tomas Dzetkulic + * Copyright (c) 2013-2014 Pavol Rusnak + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef VALGRIND +#include +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if USE_MONERO // [wallet-core] +#include "../monero/monero.h" +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef VALGRIND +/* + * This is a clever trick to make Valgrind's Memcheck verify code + * is constant-time with respect to secret data. + */ + +/* Call after secret data is written, before first use */ +#define MARK_SECRET_DATA(addr, len) VALGRIND_MAKE_MEM_UNDEFINED(addr, len) +/* Call before secret data is freed or to mark non-secret data (public keys or + * signatures) */ +#define UNMARK_SECRET_DATA(addr, len) VALGRIND_MAKE_MEM_DEFINED(addr, len) +#else +#define MARK_SECRET_DATA(addr, len) +#define UNMARK_SECRET_DATA(addr, len) +#endif + +#define FROMHEX_MAXLEN 512 + +#define VERSION_PUBLIC 0x0488b21e +#define VERSION_PRIVATE 0x0488ade4 + +#define DECRED_VERSION_PUBLIC 0x02fda926 +#define DECRED_VERSION_PRIVATE 0x02fda4e8 + +const uint8_t *fromhex(const char *str) { + static uint8_t buf[FROMHEX_MAXLEN]; + size_t len = strlen(str) / 2; + if (len > FROMHEX_MAXLEN) len = FROMHEX_MAXLEN; + for (size_t i = 0; i < len; i++) { + uint8_t c = 0; + if (str[i * 2] >= '0' && str[i * 2] <= '9') c += (str[i * 2] - '0') << 4; + if ((str[i * 2] & ~0x20) >= 'A' && (str[i * 2] & ~0x20) <= 'F') + c += (10 + (str[i * 2] & ~0x20) - 'A') << 4; + if (str[i * 2 + 1] >= '0' && str[i * 2 + 1] <= '9') + c += (str[i * 2 + 1] - '0'); + if ((str[i * 2 + 1] & ~0x20) >= 'A' && (str[i * 2 + 1] & ~0x20) <= 'F') + c += (10 + (str[i * 2 + 1] & ~0x20) - 'A'); + buf[i] = c; + } + return buf; +} + +void nem_private_key(const char *reversed_hex, ed25519_secret_key private_key) { + const uint8_t *reversed_key = fromhex(reversed_hex); + for (size_t j = 0; j < sizeof(ed25519_secret_key); j++) { + private_key[j] = reversed_key[sizeof(ed25519_secret_key) - j - 1]; + } +} + +START_TEST(test_bignum_read_be) { + bignum256 a; + uint8_t input[32]; + + memcpy( + input, + fromhex( + "c55ece858b0ddd5263f96810fe14437cd3b5e1fbd7c6a2ec1e031f05e86d8bd5"), + 32); + + bn_read_be(input, &a); + + bignum256 b = {{0x086d8bd5, 0x1018f82f, 0x11a8bb07, 0x0bc3f7af, 0x0437cd3b, + 0x14087f0a, 0x15498fe5, 0x10b161bb, 0xc55ece}}; + + for (int i = 0; i < 9; i++) { + ck_assert_uint_eq(a.val[i], b.val[i]); + } +} +END_TEST + +START_TEST(test_bignum_write_be) { + bignum256 a = {{0x086d8bd5, 0x1018f82f, 0x11a8bb07, 0x0bc3f7af, 0x0437cd3b, + 0x14087f0a, 0x15498fe5, 0x10b161bb, 0xc55ece}}; + uint8_t tmp[32]; + + bn_write_be(&a, tmp); + + ck_assert_mem_eq( + tmp, + fromhex( + "c55ece858b0ddd5263f96810fe14437cd3b5e1fbd7c6a2ec1e031f05e86d8bd5"), + 32); +} +END_TEST + +START_TEST(test_bignum_is_equal) { + bignum256 a = {{0x086d8bd5, 0x1018f82f, 0x11a8bb07, 0x0bc3f7af, 0x0437cd3b, + 0x14087f0a, 0x15498fe5, 0x10b161bb, 0xc55ece}}; + bignum256 b = {{0x086d8bd5, 0x1018f82f, 0x11a8bb07, 0x0bc3f7af, 0x0437cd3b, + 0x14087f0a, 0x15498fe5, 0x10b161bb, 0xc55ece}}; + bignum256 c = {{ + 0, + }}; + + ck_assert_int_eq(bn_is_equal(&a, &b), 1); + ck_assert_int_eq(bn_is_equal(&c, &c), 1); + ck_assert_int_eq(bn_is_equal(&a, &c), 0); +} +END_TEST + +START_TEST(test_bignum_zero) { + bignum256 a; + bignum256 b; + + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000000000000"), + &a); + bn_zero(&b); + + ck_assert_int_eq(bn_is_equal(&a, &b), 1); +} +END_TEST + +START_TEST(test_bignum_is_zero) { + bignum256 a; + + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000000000000"), + &a); + ck_assert_int_eq(bn_is_zero(&a), 1); + + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000000000001"), + &a); + ck_assert_int_eq(bn_is_zero(&a), 0); + + bn_read_be( + fromhex( + "1000000000000000000000000000000000000000000000000000000000000000"), + &a); + ck_assert_int_eq(bn_is_zero(&a), 0); + + bn_read_be( + fromhex( + "f000000000000000000000000000000000000000000000000000000000000000"), + &a); + ck_assert_int_eq(bn_is_zero(&a), 0); +} +END_TEST + +START_TEST(test_bignum_one) { + bignum256 a; + bignum256 b; + + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000000000001"), + &a); + bn_one(&b); + + ck_assert_int_eq(bn_is_equal(&a, &b), 1); +} +END_TEST + +START_TEST(test_bignum_read_le) { + bignum256 a; + bignum256 b; + + bn_read_be( + fromhex( + "c55ece858b0ddd5263f96810fe14437cd3b5e1fbd7c6a2ec1e031f05e86d8bd5"), + &a); + bn_read_le( + fromhex( + "d58b6de8051f031eeca2c6d7fbe1b5d37c4314fe1068f96352dd0d8b85ce5ec5"), + &b); + + ck_assert_int_eq(bn_is_equal(&a, &b), 1); +} +END_TEST + +START_TEST(test_bignum_write_le) { + bignum256 a; + bignum256 b; + uint8_t tmp[32]; + + bn_read_be( + fromhex( + "c55ece858b0ddd5263f96810fe14437cd3b5e1fbd7c6a2ec1e031f05e86d8bd5"), + &a); + bn_write_le(&a, tmp); + + bn_read_le(tmp, &b); + ck_assert_int_eq(bn_is_equal(&a, &b), 1); + + bn_read_be( + fromhex( + "d58b6de8051f031eeca2c6d7fbe1b5d37c4314fe1068f96352dd0d8b85ce5ec5"), + &a); + bn_read_be(tmp, &b); + ck_assert_int_eq(bn_is_equal(&a, &b), 1); +} +END_TEST + +START_TEST(test_bignum_read_uint32) { + bignum256 a; + bignum256 b; + + // lowest 30 bits set + bn_read_be( + fromhex( + "000000000000000000000000000000000000000000000000000000003fffffff"), + &a); + bn_read_uint32(0x3fffffff, &b); + + ck_assert_int_eq(bn_is_equal(&a, &b), 1); + + // bit 31 set + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000040000000"), + &a); + bn_read_uint32(0x40000000, &b); + ck_assert_int_eq(bn_is_equal(&a, &b), 1); +} +END_TEST + +START_TEST(test_bignum_read_uint64) { + bignum256 a; + bignum256 b; + + // lowest 30 bits set + bn_read_be( + fromhex( + "000000000000000000000000000000000000000000000000000000003fffffff"), + &a); + bn_read_uint64(0x3fffffff, &b); + ck_assert_int_eq(bn_is_equal(&a, &b), 1); + + // bit 31 set + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000040000000"), + &a); + bn_read_uint64(0x40000000, &b); + ck_assert_int_eq(bn_is_equal(&a, &b), 1); + + // bit 33 set + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000100000000"), + &a); + bn_read_uint64(0x100000000LL, &b); + ck_assert_int_eq(bn_is_equal(&a, &b), 1); + + // bit 61 set + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000002000000000000000"), + &a); + bn_read_uint64(0x2000000000000000LL, &b); + ck_assert_int_eq(bn_is_equal(&a, &b), 1); + + // all 64 bits set + bn_read_be( + fromhex( + "000000000000000000000000000000000000000000000000ffffffffffffffff"), + &a); + bn_read_uint64(0xffffffffffffffffLL, &b); + ck_assert_int_eq(bn_is_equal(&a, &b), 1); +} +END_TEST + +START_TEST(test_bignum_write_uint32) { + bignum256 a; + + // lowest 29 bits set + bn_read_be( + fromhex( + "000000000000000000000000000000000000000000000000000000001fffffff"), + &a); + ck_assert_uint_eq(bn_write_uint32(&a), 0x1fffffff); + + // lowest 30 bits set + bn_read_be( + fromhex( + "000000000000000000000000000000000000000000000000000000003fffffff"), + &a); + ck_assert_uint_eq(bn_write_uint32(&a), 0x3fffffff); + + // bit 31 set + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000040000000"), + &a); + ck_assert_uint_eq(bn_write_uint32(&a), 0x40000000); +} +END_TEST + +START_TEST(test_bignum_write_uint64) { + bignum256 a; + + // lowest 30 bits set + bn_read_be( + fromhex( + "000000000000000000000000000000000000000000000000000000003fffffff"), + &a); + ck_assert_uint_eq(bn_write_uint64(&a), 0x3fffffff); + + // bit 31 set + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000040000000"), + &a); + ck_assert_uint_eq(bn_write_uint64(&a), 0x40000000); + + // bit 33 set + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000100000000"), + &a); + ck_assert_uint_eq(bn_write_uint64(&a), 0x100000000LL); + + // bit 61 set + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000002000000000000000"), + &a); + ck_assert_uint_eq(bn_write_uint64(&a), 0x2000000000000000LL); + + // all 64 bits set + bn_read_be( + fromhex( + "000000000000000000000000000000000000000000000000ffffffffffffffff"), + &a); + ck_assert_uint_eq(bn_write_uint64(&a), 0xffffffffffffffffLL); +} +END_TEST + +START_TEST(test_bignum_copy) { + bignum256 a; + bignum256 b; + + bn_read_be( + fromhex( + "c55ece858b0ddd5263f96810fe14437cd3b5e1fbd7c6a2ec1e031f05e86d8bd5"), + &a); + bn_copy(&a, &b); + + ck_assert_int_eq(bn_is_equal(&a, &b), 1); +} +END_TEST + +START_TEST(test_bignum_is_even) { + bignum256 a; + + bn_read_be( + fromhex( + "c55ece858b0ddd5263f96810fe14437cd3b5e1fbd7c6a2ec1e031f05e86d8bd5"), + &a); + ck_assert_int_eq(bn_is_even(&a), 0); + + bn_read_be( + fromhex( + "c55ece858b0ddd5263f96810fe14437cd3b5e1fbd7c6a2ec1e031f05e86d8bd2"), + &a); + ck_assert_int_eq(bn_is_even(&a), 1); + + bn_read_be( + fromhex( + "c55ece858b0ddd5263f96810fe14437cd3b5e1fbd7c6a2ec1e031f05e86d8bd0"), + &a); + ck_assert_int_eq(bn_is_even(&a), 1); +} +END_TEST + +START_TEST(test_bignum_is_odd) { + bignum256 a; + + bn_read_be( + fromhex( + "c55ece858b0ddd5263f96810fe14437cd3b5e1fbd7c6a2ec1e031f05e86d8bd5"), + &a); + ck_assert_int_eq(bn_is_odd(&a), 1); + + bn_read_be( + fromhex( + "c55ece858b0ddd5263f96810fe14437cd3b5e1fbd7c6a2ec1e031f05e86d8bd2"), + &a); + ck_assert_int_eq(bn_is_odd(&a), 0); + + bn_read_be( + fromhex( + "c55ece858b0ddd5263f96810fe14437cd3b5e1fbd7c6a2ec1e031f05e86d8bd0"), + &a); + ck_assert_int_eq(bn_is_odd(&a), 0); +} +END_TEST + +START_TEST(test_bignum_is_less) { + bignum256 a; + bignum256 b; + + bn_read_uint32(0x1234, &a); + bn_read_uint32(0x8765, &b); + + ck_assert_int_eq(bn_is_less(&a, &b), 1); + ck_assert_int_eq(bn_is_less(&b, &a), 0); + + bn_zero(&a); + bn_read_be( + fromhex( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + &b); + + ck_assert_int_eq(bn_is_less(&a, &b), 1); + ck_assert_int_eq(bn_is_less(&b, &a), 0); +} +END_TEST + +START_TEST(test_bignum_bitcount) { + bignum256 a, b; + + bn_zero(&a); + ck_assert_int_eq(bn_bitcount(&a), 0); + + bn_one(&a); + ck_assert_int_eq(bn_bitcount(&a), 1); + + // test for 10000 and 11111 when i=5 + for (int i = 2; i <= 256; i++) { + bn_one(&a); + bn_one(&b); + for (int j = 2; j <= i; j++) { + bn_lshift(&a); + bn_lshift(&b); + bn_addi(&b, 1); + } + ck_assert_int_eq(bn_bitcount(&a), i); + ck_assert_int_eq(bn_bitcount(&b), i); + } + + bn_read_uint32(0x3fffffff, &a); + ck_assert_int_eq(bn_bitcount(&a), 30); + + bn_read_uint32(0xffffffff, &a); + ck_assert_int_eq(bn_bitcount(&a), 32); + + bn_read_be( + fromhex( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + &a); + ck_assert_int_eq(bn_bitcount(&a), 256); +} +END_TEST + +START_TEST(test_bignum_digitcount) { + bignum256 a; + + bn_zero(&a); + ck_assert_int_eq(bn_digitcount(&a), 1); + + // test for (10^i) and (10^i) - 1 + uint64_t m = 1; + for (int i = 0; i <= 19; i++, m *= 10) { + bn_read_uint64(m, &a); + ck_assert_int_eq(bn_digitcount(&a), i + 1); + + uint64_t n = m - 1; + bn_read_uint64(n, &a); + ck_assert_int_eq(bn_digitcount(&a), n == 0 ? 1 : i); + } + + bn_read_uint32(0x3fffffff, &a); + ck_assert_int_eq(bn_digitcount(&a), 10); + + bn_read_uint32(0xffffffff, &a); + ck_assert_int_eq(bn_digitcount(&a), 10); + + bn_read_be( + fromhex( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + &a); + ck_assert_int_eq(bn_digitcount(&a), 78); +} +END_TEST + +START_TEST(test_bignum_format_uint64) { + char buf[128], str[128]; + size_t r; + // test for (10^i) and (10^i) - 1 + uint64_t m = 1; + for (int i = 0; i <= 19; i++, m *= 10) { + sprintf(str, "%" PRIu64, m); + r = bn_format_uint64(m, NULL, NULL, 0, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, strlen(str)); + ck_assert_str_eq(buf, str); + + uint64_t n = m - 1; + sprintf(str, "%" PRIu64, n); + r = bn_format_uint64(n, NULL, NULL, 0, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, strlen(str)); + ck_assert_str_eq(buf, str); + } +} +END_TEST + +START_TEST(test_bignum_format) { + bignum256 a; + char buf[128]; + size_t r; + + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000000000000"), + &a); + r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 1); + ck_assert_str_eq(buf, "0"); + + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000000000000"), + &a); + r = bn_format(&a, NULL, NULL, 20, 0, true, buf, sizeof(buf)); + ck_assert_uint_eq(r, 22); + ck_assert_str_eq(buf, "0.00000000000000000000"); + + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000000000000"), + &a); + r = bn_format(&a, NULL, NULL, 0, 5, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 1); + ck_assert_str_eq(buf, "0"); + + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000000000000"), + &a); + r = bn_format(&a, NULL, NULL, 0, -5, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 1); + ck_assert_str_eq(buf, "0"); + + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000000000000"), + &a); + r = bn_format(&a, "", "", 0, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 1); + ck_assert_str_eq(buf, "0"); + + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000000000000"), + &a); + r = bn_format(&a, NULL, "SFFX", 0, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 1 + 4); + ck_assert_str_eq(buf, "0SFFX"); + + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000000000000"), + &a); + r = bn_format(&a, "PRFX", NULL, 0, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 4 + 1); + ck_assert_str_eq(buf, "PRFX0"); + + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000000000000"), + &a); + r = bn_format(&a, "PRFX", "SFFX", 0, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 4 + 1 + 4); + ck_assert_str_eq(buf, "PRFX0SFFX"); + + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000000000000"), + &a); + r = bn_format(&a, NULL, NULL, 18, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 1); + ck_assert_str_eq(buf, "0"); + + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000000000001"), + &a); + r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 1); + ck_assert_str_eq(buf, "1"); + + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000000000001"), + &a); + r = bn_format(&a, NULL, NULL, 6, 6, true, buf, sizeof(buf)); + ck_assert_uint_eq(r, 8); + ck_assert_str_eq(buf, "1.000000"); + + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000000000002"), + &a); + r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 1); + ck_assert_str_eq(buf, "2"); + + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000000000005"), + &a); + r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 1); + ck_assert_str_eq(buf, "5"); + + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000000000009"), + &a); + r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 1); + ck_assert_str_eq(buf, "9"); + + bn_read_be( + fromhex( + "000000000000000000000000000000000000000000000000000000000000000a"), + &a); + r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 2); + ck_assert_str_eq(buf, "10"); + + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000000000014"), + &a); + r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 2); + ck_assert_str_eq(buf, "20"); + + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000000000032"), + &a); + r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 2); + ck_assert_str_eq(buf, "50"); + + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000000000063"), + &a); + r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 2); + ck_assert_str_eq(buf, "99"); + + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000000000064"), + &a); + r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 3); + ck_assert_str_eq(buf, "100"); + + bn_read_be( + fromhex( + "00000000000000000000000000000000000000000000000000000000000000c8"), + &a); + r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 3); + ck_assert_str_eq(buf, "200"); + + bn_read_be( + fromhex( + "00000000000000000000000000000000000000000000000000000000000001f4"), + &a); + r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 3); + ck_assert_str_eq(buf, "500"); + + bn_read_be( + fromhex( + "00000000000000000000000000000000000000000000000000000000000003e7"), + &a); + r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 3); + ck_assert_str_eq(buf, "999"); + + bn_read_be( + fromhex( + "00000000000000000000000000000000000000000000000000000000000003e8"), + &a); + r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 4); + ck_assert_str_eq(buf, "1000"); + + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000000000000989680"), + &a); + r = bn_format(&a, NULL, NULL, 7, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 1); + ck_assert_str_eq(buf, "1"); + + bn_read_be( + fromhex( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + &a); + r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 78); + ck_assert_str_eq(buf, + "11579208923731619542357098500868790785326998466564056403945" + "7584007913129639935"); + + bn_read_be( + fromhex( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + &a); + r = bn_format(&a, NULL, NULL, 1, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 79); + ck_assert_str_eq(buf, + "11579208923731619542357098500868790785326998466564056403945" + "758400791312963993.5"); + + bn_read_be( + fromhex( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + &a); + r = bn_format(&a, NULL, NULL, 2, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 79); + ck_assert_str_eq(buf, + "11579208923731619542357098500868790785326998466564056403945" + "75840079131296399.35"); + + bn_read_be( + fromhex( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + &a); + r = bn_format(&a, NULL, NULL, 8, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 79); + ck_assert_str_eq(buf, + "11579208923731619542357098500868790785326998466564056403945" + "75840079131.29639935"); + + bn_read_be( + fromhex( + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffe3bbb00"), + &a); + r = bn_format(&a, NULL, NULL, 8, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 70); + ck_assert_str_eq(buf, + "11579208923731619542357098500868790785326998466564056403945" + "75840079131"); + + bn_read_be( + fromhex( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + &a); + r = bn_format(&a, NULL, NULL, 18, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 79); + ck_assert_str_eq(buf, + "11579208923731619542357098500868790785326998466564056403945" + "7.584007913129639935"); + + bn_read_be( + fromhex( + "fffffffffffffffffffffffffffffffffffffffffffffffff7e52fe5afe40000"), + &a); + r = bn_format(&a, NULL, NULL, 18, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 60); + ck_assert_str_eq( + buf, "115792089237316195423570985008687907853269984665640564039457"); + + bn_read_be( + fromhex( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + &a); + r = bn_format(&a, NULL, NULL, 78, 0, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 80); + ck_assert_str_eq(buf, + "0." + "11579208923731619542357098500868790785326998466564056403945" + "7584007913129639935"); + + bn_read_be( + fromhex( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + &a); + r = bn_format(&a, NULL, NULL, 0, 10, false, buf, sizeof(buf)); + ck_assert_uint_eq(r, 88); + ck_assert_str_eq(buf, + "11579208923731619542357098500868790785326998466564056403945" + "75840079131296399350000000000"); + + bn_read_be( + fromhex( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + &a); + r = bn_format(&a, "quite a long prefix", "even longer suffix", 60, 0, false, + buf, sizeof(buf)); + ck_assert_uint_eq(r, 116); + ck_assert_str_eq(buf, + "quite a long " + "prefix115792089237316195." + "42357098500868790785326998466564056403945758400791312963993" + "5even longer suffix"); + + bn_read_be( + fromhex( + "0000000000000000000000000000000000000000000000000123456789abcdef"), + &a); + memset(buf, 'a', sizeof(buf)); + r = bn_format(&a, "prefix", "suffix", 10, 0, false, buf, 31); + ck_assert_str_eq(buf, "prefix8198552.9216486895suffix"); + ck_assert_uint_eq(r, 30); + + memset(buf, 'a', sizeof(buf)); + r = bn_format(&a, "prefix", "suffix", 10, 0, false, buf, 30); + ck_assert_uint_eq(r, 0); + ck_assert_str_eq(buf, ""); +} +END_TEST + +START_TEST(test_bignum_sqrt) { + uint32_t quadratic_residua[] = { + 1, 2, 4, 8, 9, 11, 15, 16, 17, 18, 19, 21, 22, 25, 29, + 30, 31, 32, 34, 35, 36, 38, 39, 42, 43, 44, 47, 49, 50, 58, + 59, 60, 61, 62, 64, 65, 67, 68, 69, 70, 71, 72, 76, 78, 81, + 83, 84, 86, 88, 91, 94, 98, 99, 100, 103, 107, 111, 115, 116, 118, + 120, 121, 122, 123, 124, 127, 128, 130, 131, 134, 135, 136, 137, 138, 139, + 140, 142, 144, 149, 152, 153, 156, 159, 161, 162, 165, 166, 167, 168, 169, + 171, 172, 176, 181, 182, 185, 187, 188, 189, 191, 193, 196, 197, 198, 200, + 205, 206, 209, 214, 219, 222, 223, 225, 229, 230, 231, 232, 233, 236, 237, + 239, 240, 242, 244, 246, 248, 254, 255, 256, 259, 260, 261, 262, 265, 267, + 268, 269, 270, 272, 274, 275, 276, 277, 278, 279, 280, 281, 284, 285, 287, + 288, 289, 291, 293, 298, 299, 303, 304, 306, 311, 312, 315, 318, 319, 322, + 323, 324, 327, 330, 331, 332, 334, 336, 337, 338, 339, 341, 342, 344, 349, + 351, 352, 353, 357, 359, 361, 362, 364, 365, 370, 371, 373, 374, 375, 376, + 378, 379, 382, 383, 385, 386, 387, 389, 392, 394, 395, 396, 399, 400, 409, + 410, 412, 418, 421, 423, 425, 428, 429, 431, 435, 438, 439, 441, 443, 444, + 445, 446, 450, 453, 458, 460, 461, 462, 463, 464, 465, 466, 467, 471, 472, + 473, 474, 475, 478, 479, 480, 481, 484, 485, 487, 488, 489, 492, 493, 496, + 503, 505, 508, 510, 511, 512, 517, 518, 519, 520, 521, 522, 523, 524, 525, + 527, 529, 530, 531, 533, 534, 536, 537, 538, 539, 540, 541, 544, 545, 547, + 548, 549, 550, 551, 552, 553, 554, 556, 557, 558, 560, 562, 563, 565, 568, + 570, 571, 574, 576, 578, 582, 585, 586, 587, 589, 595, 596, 597, 598, 599, + 603, 606, 607, 608, 609, 612, 613, 619, 621, 622, 623, 624, 625, 630, 633, + 636, 638, 639, 644, 645, 646, 648, 649, 651, 653, 654, 660, 662, 663, 664, + 665, 668, 671, 672, 673, 674, 676, 678, 679, 681, 682, 684, 688, 689, 698, + 702, 704, 705, 706, 707, 714, 715, 718, 722, 723, 724, 725, 728, 729, 730, + 731, 733, 735, 737, 740, 741, 742, 746, 747, 748, 750, 751, 752, 753, 755, + 756, 758, 759, 761, 763, 764, 766, 769, 770, 771, 772, 774, 775, 778, 781, + 784, 785, 788, 789, 790, 791, 792, 797, 798, 799, 800, 813, 815, 817, 818, + 819, 820, 823, 824, 833, 836, 841, 842, 846, 849, 850, 851, 856, 857, 858, + 862, 865, 870, 875, 876, 878, 882, 885, 886, 887, 888, 890, 891, 892, 893, + 895, 899, 900, 903, 906, 907, 911, 913, 915, 916, 919, 920, 921, 922, 924, + 926, 927, 928, 930, 931, 932, 934, 937, 939, 942, 943, 944, 946, 948, 949, + 950, 951, 953, 956, 958, 960, 961, 962, 963, 968, 970, 971, 974, 975, 976, + 977, 978, 984, 986, 987, 992, 995, 999}; + + bignum256 a, b; + + bn_zero(&a); + b = a; + bn_sqrt(&b, &secp256k1.prime); + ck_assert_int_eq(bn_is_equal(&a, &b), 1); + + bn_one(&a); + b = a; + bn_sqrt(&b, &secp256k1.prime); + ck_assert_int_eq(bn_is_equal(&a, &b), 1); + + // test some quadratic residua + for (size_t i = 0; i < sizeof(quadratic_residua) / sizeof(*quadratic_residua); + i++) { + bn_read_uint32(quadratic_residua[i], &a); + b = a; + bn_sqrt(&b, &secp256k1.prime); + bn_multiply(&b, &b, &secp256k1.prime); + bn_mod(&b, &secp256k1.prime); + ck_assert_int_eq(bn_is_equal(&a, &b), 1); + } +} +END_TEST + +// https://tools.ietf.org/html/rfc4648#section-10 +START_TEST(test_base32_rfc4648) { + static const struct { + const char *decoded; + const char *encoded; + const char *encoded_lowercase; + } tests[] = { + {"", "", ""}, + {"f", "MY", "my"}, + {"fo", "MZXQ", "mzxq"}, + {"foo", "MZXW6", "mzxw6"}, + {"foob", "MZXW6YQ", "mzxw6yq"}, + {"fooba", "MZXW6YTB", "mzxw6ytb"}, + {"foobar", "MZXW6YTBOI", "mzxw6ytboi"}, + }; + + char buffer[64]; + + for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { + const char *in = tests[i].decoded; + const char *out = tests[i].encoded; + const char *out_lowercase = tests[i].encoded_lowercase; + + size_t inlen = strlen(in); + size_t outlen = strlen(out); + + ck_assert_uint_eq(outlen, base32_encoded_length(inlen)); + ck_assert_uint_eq(inlen, base32_decoded_length(outlen)); + + ck_assert(base32_encode((uint8_t *)in, inlen, buffer, sizeof(buffer), + BASE32_ALPHABET_RFC4648) != NULL); + ck_assert_str_eq(buffer, out); + + char *ret = (char *)base32_decode(out, outlen, (uint8_t *)buffer, + sizeof(buffer), BASE32_ALPHABET_RFC4648); + ck_assert(ret != NULL); + *ret = '\0'; + ck_assert_str_eq(buffer, in); + + ret = (char *)base32_decode(out_lowercase, outlen, (uint8_t *)buffer, + sizeof(buffer), BASE32_ALPHABET_RFC4648); + ck_assert(ret != NULL); + *ret = '\0'; + ck_assert_str_eq(buffer, in); + } +} +END_TEST + +// from +// https://github.com/bitcoin/bitcoin/blob/master/src/test/data/base58_keys_valid.json +START_TEST(test_base58) { + static const char *base58_vector[] = { + "0065a16059864a2fdbc7c99a4723a8395bc6f188eb", + "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i", + "0574f209f6ea907e2ea48f74fae05782ae8a665257", + "3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou", + "6f53c0307d6851aa0ce7825ba883c6bd9ad242b486", + "mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs", + "c46349a418fc4578d10a372b54b45c280cc8c4382f", + "2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br", + "80eddbdc1168f1daeadbd3e44c1e3f8f5a284c2029f78ad26af98583a499de5b19", + "5Kd3NBUAdUnhyzenEwVLy9pBKxSwXvE9FMPyR4UKZvpe6E3AgLr", + "8055c9bccb9ed68446d1b75273bbce89d7fe013a8acd1625514420fb2aca1a21c401", + "Kz6UJmQACJmLtaQj5A3JAge4kVTNQ8gbvXuwbmCj7bsaabudb3RD", + "ef36cb93b9ab1bdabf7fb9f2c04f1b9cc879933530ae7842398eef5a63a56800c2", + "9213qJab2HNEpMpYNBa7wHGFKKbkDn24jpANDs2huN3yi4J11ko", + "efb9f4892c9e8282028fea1d2667c4dc5213564d41fc5783896a0d843fc15089f301", + "cTpB4YiyKiBcPxnefsDpbnDxFDffjqJob8wGCEDXxgQ7zQoMXJdH", + "006d23156cbbdcc82a5a47eee4c2c7c583c18b6bf4", + "1Ax4gZtb7gAit2TivwejZHYtNNLT18PUXJ", + "05fcc5460dd6e2487c7d75b1963625da0e8f4c5975", + "3QjYXhTkvuj8qPaXHTTWb5wjXhdsLAAWVy", + "6ff1d470f9b02370fdec2e6b708b08ac431bf7a5f7", + "n3ZddxzLvAY9o7184TB4c6FJasAybsw4HZ", + "c4c579342c2c4c9220205e2cdc285617040c924a0a", + "2NBFNJTktNa7GZusGbDbGKRZTxdK9VVez3n", + "80a326b95ebae30164217d7a7f57d72ab2b54e3be64928a19da0210b9568d4015e", + "5K494XZwps2bGyeL71pWid4noiSNA2cfCibrvRWqcHSptoFn7rc", + "807d998b45c219a1e38e99e7cbd312ef67f77a455a9b50c730c27f02c6f730dfb401", + "L1RrrnXkcKut5DEMwtDthjwRcTTwED36thyL1DebVrKuwvohjMNi", + "efd6bca256b5abc5602ec2e1c121a08b0da2556587430bcf7e1898af2224885203", + "93DVKyFYwSN6wEo3E2fCrFPUp17FtrtNi2Lf7n4G3garFb16CRj", + "efa81ca4e8f90181ec4b61b6a7eb998af17b2cb04de8a03b504b9e34c4c61db7d901", + "cTDVKtMGVYWTHCb1AFjmVbEbWjvKpKqKgMaR3QJxToMSQAhmCeTN", + "007987ccaa53d02c8873487ef919677cd3db7a6912", + "1C5bSj1iEGUgSTbziymG7Cn18ENQuT36vv", + "0563bcc565f9e68ee0189dd5cc67f1b0e5f02f45cb", + "3AnNxabYGoTxYiTEZwFEnerUoeFXK2Zoks", + "6fef66444b5b17f14e8fae6e7e19b045a78c54fd79", + "n3LnJXCqbPjghuVs8ph9CYsAe4Sh4j97wk", + "c4c3e55fceceaa4391ed2a9677f4a4d34eacd021a0", + "2NB72XtkjpnATMggui83aEtPawyyKvnbX2o", + "80e75d936d56377f432f404aabb406601f892fd49da90eb6ac558a733c93b47252", + "5KaBW9vNtWNhc3ZEDyNCiXLPdVPHCikRxSBWwV9NrpLLa4LsXi9", + "808248bd0375f2f75d7e274ae544fb920f51784480866b102384190b1addfbaa5c01", + "L1axzbSyynNYA8mCAhzxkipKkfHtAXYF4YQnhSKcLV8YXA874fgT", + "ef44c4f6a096eac5238291a94cc24c01e3b19b8d8cef72874a079e00a242237a52", + "927CnUkUbasYtDwYwVn2j8GdTuACNnKkjZ1rpZd2yBB1CLcnXpo", + "efd1de707020a9059d6d3abaf85e17967c6555151143db13dbb06db78df0f15c6901", + "cUcfCMRjiQf85YMzzQEk9d1s5A4K7xL5SmBCLrezqXFuTVefyhY7", + "00adc1cc2081a27206fae25792f28bbc55b831549d", + "1Gqk4Tv79P91Cc1STQtU3s1W6277M2CVWu", + "05188f91a931947eddd7432d6e614387e32b244709", + "33vt8ViH5jsr115AGkW6cEmEz9MpvJSwDk", + "6f1694f5bc1a7295b600f40018a618a6ea48eeb498", + "mhaMcBxNh5cqXm4aTQ6EcVbKtfL6LGyK2H", + "c43b9b3fd7a50d4f08d1a5b0f62f644fa7115ae2f3", + "2MxgPqX1iThW3oZVk9KoFcE5M4JpiETssVN", + "80091035445ef105fa1bb125eccfb1882f3fe69592265956ade751fd095033d8d0", + "5HtH6GdcwCJA4ggWEL1B3jzBBUB8HPiBi9SBc5h9i4Wk4PSeApR", + "80ab2b4bcdfc91d34dee0ae2a8c6b6668dadaeb3a88b9859743156f462325187af01", + "L2xSYmMeVo3Zek3ZTsv9xUrXVAmrWxJ8Ua4cw8pkfbQhcEFhkXT8", + "efb4204389cef18bbe2b353623cbf93e8678fbc92a475b664ae98ed594e6cf0856", + "92xFEve1Z9N8Z641KQQS7ByCSb8kGjsDzw6fAmjHN1LZGKQXyMq", + "efe7b230133f1b5489843260236b06edca25f66adb1be455fbd38d4010d48faeef01", + "cVM65tdYu1YK37tNoAyGoJTR13VBYFva1vg9FLuPAsJijGvG6NEA", + "00c4c1b72491ede1eedaca00618407ee0b772cad0d", + "1JwMWBVLtiqtscbaRHai4pqHokhFCbtoB4", + "05f6fe69bcb548a829cce4c57bf6fff8af3a5981f9", + "3QCzvfL4ZRvmJFiWWBVwxfdaNBT8EtxB5y", + "6f261f83568a098a8638844bd7aeca039d5f2352c0", + "mizXiucXRCsEriQCHUkCqef9ph9qtPbZZ6", + "c4e930e1834a4d234702773951d627cce82fbb5d2e", + "2NEWDzHWwY5ZZp8CQWbB7ouNMLqCia6YRda", + "80d1fab7ab7385ad26872237f1eb9789aa25cc986bacc695e07ac571d6cdac8bc0", + "5KQmDryMNDcisTzRp3zEq9e4awRmJrEVU1j5vFRTKpRNYPqYrMg", + "80b0bbede33ef254e8376aceb1510253fc3550efd0fcf84dcd0c9998b288f166b301", + "L39Fy7AC2Hhj95gh3Yb2AU5YHh1mQSAHgpNixvm27poizcJyLtUi", + "ef037f4192c630f399d9271e26c575269b1d15be553ea1a7217f0cb8513cef41cb", + "91cTVUcgydqyZLgaANpf1fvL55FH53QMm4BsnCADVNYuWuqdVys", + "ef6251e205e8ad508bab5596bee086ef16cd4b239e0cc0c5d7c4e6035441e7d5de01", + "cQspfSzsgLeiJGB2u8vrAiWpCU4MxUT6JseWo2SjXy4Qbzn2fwDw", + "005eadaf9bb7121f0f192561a5a62f5e5f54210292", + "19dcawoKcZdQz365WpXWMhX6QCUpR9SY4r", + "053f210e7277c899c3a155cc1c90f4106cbddeec6e", + "37Sp6Rv3y4kVd1nQ1JV5pfqXccHNyZm1x3", + "6fc8a3c2a09a298592c3e180f02487cd91ba3400b5", + "myoqcgYiehufrsnnkqdqbp69dddVDMopJu", + "c499b31df7c9068d1481b596578ddbb4d3bd90baeb", + "2N7FuwuUuoTBrDFdrAZ9KxBmtqMLxce9i1C", + "80c7666842503db6dc6ea061f092cfb9c388448629a6fe868d068c42a488b478ae", + "5KL6zEaMtPRXZKo1bbMq7JDjjo1bJuQcsgL33je3oY8uSJCR5b4", + "8007f0803fc5399e773555ab1e8939907e9badacc17ca129e67a2f5f2ff84351dd01", + "KwV9KAfwbwt51veZWNscRTeZs9CKpojyu1MsPnaKTF5kz69H1UN2", + "efea577acfb5d1d14d3b7b195c321566f12f87d2b77ea3a53f68df7ebf8604a801", + "93N87D6uxSBzwXvpokpzg8FFmfQPmvX4xHoWQe3pLdYpbiwT5YV", + "ef0b3b34f0958d8a268193a9814da92c3e8b58b4a4378a542863e34ac289cd830c01", + "cMxXusSihaX58wpJ3tNuuUcZEQGt6DKJ1wEpxys88FFaQCYjku9h", + "001ed467017f043e91ed4c44b4e8dd674db211c4e6", + "13p1ijLwsnrcuyqcTvJXkq2ASdXqcnEBLE", + "055ece0cadddc415b1980f001785947120acdb36fc", + "3ALJH9Y951VCGcVZYAdpA3KchoP9McEj1G", + 0, + 0, + }; + const char **raw = base58_vector; + const char **str = base58_vector + 1; + uint8_t rawn[34]; + char strn[53]; + int r; + while (*raw && *str) { + int len = strlen(*raw) / 2; + + memcpy(rawn, fromhex(*raw), len); + r = base58_encode_check(rawn, len, HASHER_SHA2D, strn, sizeof(strn)); + ck_assert_int_eq((size_t)r, strlen(*str) + 1); + ck_assert_str_eq(strn, *str); + + r = base58_decode_check(strn, HASHER_SHA2D, rawn, len); + ck_assert_int_eq(r, len); + ck_assert_mem_eq(rawn, fromhex(*raw), len); + + raw += 2; + str += 2; + } +} +END_TEST + +#if USE_GRAPHENE + +// Graphene Base85CheckEncoding +START_TEST(test_base58gph) { + static const char *base58_vector[] = { + "02e649f63f8e8121345fd7f47d0d185a3ccaa843115cd2e9392dcd9b82263bc680", + "6dumtt9swxCqwdPZBGXh9YmHoEjFFnNfwHaTqRbQTghGAY2gRz", + "021c7359cd885c0e319924d97e3980206ad64387aff54908241125b3a88b55ca16", + "5725vivYpuFWbeyTifZ5KevnHyqXCi5hwHbNU9cYz1FHbFXCxX", + "02f561e0b57a552df3fa1df2d87a906b7a9fc33a83d5d15fa68a644ecb0806b49a", + "6kZKHSuxqAwdCYsMvwTcipoTsNE2jmEUNBQufGYywpniBKXWZK", + "03e7595c3e6b58f907bee951dc29796f3757307e700ecf3d09307a0cc4a564eba3", + "8b82mpnH8YX1E9RHnU2a2YgLTZ8ooevEGP9N15c1yFqhoBvJur", + 0, + 0, + }; + const char **raw = base58_vector; + const char **str = base58_vector + 1; + uint8_t rawn[34]; + char strn[53]; + int r; + while (*raw && *str) { + int len = strlen(*raw) / 2; + + memcpy(rawn, fromhex(*raw), len); + r = base58gph_encode_check(rawn, len, strn, sizeof(strn)); + ck_assert_int_eq((size_t)r, strlen(*str) + 1); + ck_assert_str_eq(strn, *str); + + r = base58gph_decode_check(strn, rawn, len); + ck_assert_int_eq(r, len); + ck_assert_mem_eq(rawn, fromhex(*raw), len); + + raw += 2; + str += 2; + } +} +END_TEST + +#endif + +START_TEST(test_bignum_divmod) { + uint32_t r; + int i; + + bignum256 a; + uint32_t ar[] = {15, 14, 55, 29, 44, 24, 53, 49, 18, 55, 2, 28, 5, 4, 12, + 43, 18, 37, 28, 14, 30, 46, 12, 11, 17, 10, 10, 13, 24, 45, + 4, 33, 44, 42, 2, 46, 34, 43, 45, 28, 21, 18, 13, 17}; + + bn_read_be( + fromhex( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + &a); + + i = 0; + while (!bn_is_zero(&a) && i < 44) { + bn_divmod58(&a, &r); + ck_assert_uint_eq(r, ar[i]); + i++; + } + ck_assert_int_eq(i, 44); + + bignum256 b; + uint32_t br[] = {935, 639, 129, 913, 7, 584, 457, 39, 564, + 640, 665, 984, 269, 853, 907, 687, 8, 985, + 570, 423, 195, 316, 237, 89, 792, 115}; + + bn_read_be( + fromhex( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + &b); + i = 0; + while (!bn_is_zero(&b) && i < 26) { + bn_divmod1000(&b, &r); + ck_assert_uint_eq(r, br[i]); + i++; + } + ck_assert_int_eq(i, 26); +} +END_TEST + +// test vector 1 from +// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#test-vector-1 +START_TEST(test_bip32_vector_1) { + HDNode node, node2, node3; + uint32_t fingerprint; + char str[XPUB_MAXLEN]; + int r; + + // init m + hdnode_from_seed(fromhex("000102030405060708090a0b0c0d0e0f"), 16, + SECP256K1_NAME, &node); + + // [Chain m] + fingerprint = 0; + ck_assert_uint_eq(fingerprint, 0x00000000); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2"), + 33); + hdnode_serialize_private(&node, fingerprint, VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqji" + "ChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi"); + r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); + ck_assert_str_eq(str, + "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2" + "gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"); + r = hdnode_deserialize_public(str, VERSION_PUBLIC, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); + + // [Chain m/0'] + fingerprint = hdnode_fingerprint(&node); + hdnode_private_ckd_prime(&node, 0); + ck_assert_uint_eq(fingerprint, 0x3442193e); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "035a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56"), + 33); + hdnode_serialize_private(&node, fingerprint, VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4" + "cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7"); + r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); + ck_assert_str_eq(str, + "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP" + "6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw"); + r = hdnode_deserialize_public(str, VERSION_PUBLIC, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); + + // [Chain m/0'/1] + fingerprint = hdnode_fingerprint(&node); + hdnode_private_ckd(&node, 1); + ck_assert_uint_eq(fingerprint, 0x5c1bd648); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "2a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "3c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "03501e454bf00751f24b1b489aa925215d66af2234e3891c3b21a52bedb3cd711c"), + 33); + hdnode_serialize_private(&node, fingerprint, VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSx" + "qu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs"); + r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); + ck_assert_str_eq(str, + "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFH" + "KkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ"); + r = hdnode_deserialize_public(str, VERSION_PUBLIC, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); + + // [Chain m/0'/1/2'] + fingerprint = hdnode_fingerprint(&node); + hdnode_private_ckd_prime(&node, 2); + ck_assert_uint_eq(fingerprint, 0xbef5a2f9); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "cbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2"), + 33); + hdnode_serialize_private(&node, fingerprint, VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptW" + "mT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM"); + r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); + ck_assert_str_eq(str, + "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgq" + "FJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5"); + r = hdnode_deserialize_public(str, VERSION_PUBLIC, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); + + // [Chain m/0'/1/2'/2] + fingerprint = hdnode_fingerprint(&node); + hdnode_private_ckd(&node, 2); + ck_assert_uint_eq(fingerprint, 0xee7ab90c); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "0f479245fb19a38a1954c5c7c0ebab2f9bdfd96a17563ef28a6a4b1a2a764ef4"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "02e8445082a72f29b75ca48748a914df60622a609cacfce8ed0e35804560741d29"), + 33); + hdnode_serialize_private(&node, fingerprint, VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Ty" + "h8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334"); + r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); + ck_assert_str_eq(str, + "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJ" + "AyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV"); + r = hdnode_deserialize_public(str, VERSION_PUBLIC, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); + + // [Chain m/0'/1/2'/2/1000000000] + fingerprint = hdnode_fingerprint(&node); + hdnode_private_ckd(&node, 1000000000); + ck_assert_uint_eq(fingerprint, 0xd880d7d8); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "471b76e389e528d6de6d816857e012c5455051cad6660850e58372a6c3e6e7c8"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "022a471424da5e657499d1ff51cb43c47481a03b1e77f951fe64cec9f5a48f7011"), + 33); + hdnode_serialize_private(&node, fingerprint, VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8" + "kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76"); + r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); + ck_assert_str_eq(str, + "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNT" + "EcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy"); + r = hdnode_deserialize_public(str, VERSION_PUBLIC, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); +} +END_TEST + +// test vector 2 from +// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#test-vector-2 +START_TEST(test_bip32_vector_2) { + HDNode node, node2, node3; + uint32_t fingerprint; + char str[XPUB_MAXLEN]; + int r; + + // init m + hdnode_from_seed( + fromhex( + "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c" + "999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542"), + 64, SECP256K1_NAME, &node); + + // [Chain m] + fingerprint = 0; + ck_assert_uint_eq(fingerprint, 0x00000000); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "60499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "4b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "03cbcaa9c98c877a26977d00825c956a238e8dddfbd322cce4f74b0b5bd6ace4a7"), + 33); + hdnode_serialize_private(&node, fingerprint, VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGds" + "o3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U"); + r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); + ck_assert_str_eq(str, + "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSC" + "Gu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB"); + r = hdnode_deserialize_public(str, VERSION_PUBLIC, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); + + // [Chain m/0] + fingerprint = hdnode_fingerprint(&node); + r = hdnode_private_ckd(&node, 0); + ck_assert_int_eq(r, 1); + ck_assert_uint_eq(fingerprint, 0xbd16bee5); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "abe74a98f6c7eabee0428f53798f0ab8aa1bd37873999041703c742f15ac7e1e"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "02fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea"), + 33); + hdnode_serialize_private(&node, fingerprint, VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT" + "3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt"); + r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); + ck_assert_str_eq(str, + "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGm" + "XUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH"); + r = hdnode_deserialize_public(str, VERSION_PUBLIC, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); + + // [Chain m/0/2147483647'] + fingerprint = hdnode_fingerprint(&node); + r = hdnode_private_ckd_prime(&node, 2147483647); + ck_assert_int_eq(r, 1); + ck_assert_uint_eq(fingerprint, 0x5a61ff8e); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "be17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d9"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "877c779ad9687164e9c2f4f0f4ff0340814392330693ce95a58fe18fd52e6e93"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "03c01e7425647bdefa82b12d9bad5e3e6865bee0502694b94ca58b666abc0a5c3b"), + 33); + hdnode_serialize_private(&node, fingerprint, VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYE" + "eEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9"); + r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); + ck_assert_str_eq(str, + "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ" + "85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a"); + r = hdnode_deserialize_public(str, VERSION_PUBLIC, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); + + // [Chain m/0/2147483647'/1] + fingerprint = hdnode_fingerprint(&node); + r = hdnode_private_ckd(&node, 1); + ck_assert_int_eq(r, 1); + ck_assert_uint_eq(fingerprint, 0xd8ab4937); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "704addf544a06e5ee4bea37098463c23613da32020d604506da8c0518e1da4b7"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "03a7d1d856deb74c508e05031f9895dab54626251b3806e16b4bd12e781a7df5b9"), + 33); + hdnode_serialize_private(&node, fingerprint, VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd" + "25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef"); + r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); + ck_assert_str_eq(str, + "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5Ew" + "VvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon"); + r = hdnode_deserialize_public(str, VERSION_PUBLIC, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); + + // [Chain m/0/2147483647'/1/2147483646'] + fingerprint = hdnode_fingerprint(&node); + r = hdnode_private_ckd_prime(&node, 2147483646); + ck_assert_int_eq(r, 1); + ck_assert_uint_eq(fingerprint, 0x78412e3a); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e29"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "f1c7c871a54a804afe328b4c83a1c33b8e5ff48f5087273f04efa83b247d6a2d"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "02d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0"), + 33); + hdnode_serialize_private(&node, fingerprint, VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz" + "7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc"); + r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); + ck_assert_str_eq(str, + "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJ" + "bZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL"); + r = hdnode_deserialize_public(str, VERSION_PUBLIC, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); + + // [Chain m/0/2147483647'/1/2147483646'/2] + fingerprint = hdnode_fingerprint(&node); + r = hdnode_private_ckd(&node, 2); + ck_assert_int_eq(r, 1); + ck_assert_uint_eq(fingerprint, 0x31a507b8); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "bb7d39bdb83ecf58f2fd82b6d918341cbef428661ef01ab97c28a4842125ac23"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c"), + 33); + hdnode_serialize_private(&node, fingerprint, VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw" + "7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j"); + r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); + ck_assert_str_eq(str, + "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLF" + "bdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt"); + r = hdnode_deserialize_public(str, VERSION_PUBLIC, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); + + // init m + hdnode_from_seed( + fromhex( + "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c" + "999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542"), + 64, SECP256K1_NAME, &node); + + // test public derivation + // [Chain m/0] + fingerprint = hdnode_fingerprint(&node); + r = hdnode_public_ckd(&node, 0); + ck_assert_int_eq(r, 1); + ck_assert_uint_eq(fingerprint, 0xbd16bee5); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "0000000000000000000000000000000000000000000000000000000000000000"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "02fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea"), + 33); +} +END_TEST + +// test vector 3 from +// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#test-vector-3 +START_TEST(test_bip32_vector_3) { + HDNode node, node2, node3; + uint32_t fingerprint; + char str[XPUB_MAXLEN]; + int r; + + // init m + hdnode_from_seed( + fromhex( + "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45" + "d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be"), + 64, SECP256K1_NAME, &node); + + // [Chain m] + fingerprint = 0; + ck_assert_uint_eq(fingerprint, 0x00000000); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + hdnode_serialize_private(&node, fingerprint, VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7" + "KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6"); + r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); + ck_assert_str_eq(str, + "xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhR" + "oP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13"); + r = hdnode_deserialize_public(str, VERSION_PUBLIC, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); + + // [Chain m/0'] + fingerprint = hdnode_fingerprint(&node); + r = hdnode_private_ckd_prime(&node, 0); + ck_assert_int_eq(r, 1); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + hdnode_serialize_private(&node, fingerprint, VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu" + "2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L"); + r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); + ck_assert_str_eq(str, + "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrAD" + "WgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y"); + r = hdnode_deserialize_public(str, VERSION_PUBLIC, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); +} +END_TEST + +// test vector 4 from +// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#test-vector-4 +START_TEST(test_bip32_vector_4) { + HDNode node, node2, node3; + uint32_t fingerprint; + char str[XPUB_MAXLEN]; + int r; + + // init m + hdnode_from_seed( + fromhex( + "3ddd5602285899a946114506157c7997e5444528f3003f6134712147db19b678"), + 32, SECP256K1_NAME, &node); + + // [Chain m] + fingerprint = 0; + ck_assert_int_eq(fingerprint, 0x00000000); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + hdnode_serialize_private(&node, fingerprint, VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "xprv9s21ZrQH143K48vGoLGRPxgo2JNkJ3J3fqkirQC2zVdk5Dgd5w14S7f" + "RDyHH4dWNHUgkvsvNDCkvAwcSHNAQwhwgNMgZhLtQC63zxwhQmRv"); + r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); + ck_assert_str_eq(str, + "xpub661MyMwAqRbcGczjuMoRm6dXaLDEhW1u34gKenbeYqAix21mdUKJyuy" + "u5F1rzYGVxyL6tmgBUAEPrEz92mBXjByMRiJdba9wpnN37RLLAXa"); + r = hdnode_deserialize_public(str, VERSION_PUBLIC, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); + + // [Chain m/0'] + fingerprint = hdnode_fingerprint(&node); + r = hdnode_private_ckd_prime(&node, 0); + ck_assert_int_eq(r, 1); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + hdnode_serialize_private(&node, fingerprint, VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "xprv9vB7xEWwNp9kh1wQRfCCQMnZUEG21LpbR9NPCNN1dwhiZkjjeGRnaAL" + "mPXCX7SgjFTiCTT6bXes17boXtjq3xLpcDjzEuGLQBM5ohqkao9G"); + r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); + ck_assert_str_eq(str, + "xpub69AUMk3qDBi3uW1sXgjCmVjJ2G6WQoYSnNHyzkmdCHEhSZ4tBok37xf" + "FEqHd2AddP56Tqp4o56AePAgCjYdvpW2PU2jbUPFKsav5ut6Ch1m"); + r = hdnode_deserialize_public(str, VERSION_PUBLIC, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); + + // [Chain m/0'/1'] + fingerprint = hdnode_fingerprint(&node); + r = hdnode_private_ckd_prime(&node, 1); + ck_assert_int_eq(r, 1); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + hdnode_serialize_private(&node, fingerprint, VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "xprv9xJocDuwtYCMNAo3Zw76WENQeAS6WGXQ55RCy7tDJ8oALr4FWkuVoHJ" + "eHVAcAqiZLE7Je3vZJHxspZdFHfnBEjHqU5hG1Jaj32dVoS6XLT1"); + r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); + ck_assert_str_eq(str, + "xpub6BJA1jSqiukeaesWfxe6sNK9CCGaujFFSJLomWHprUL9DePQ4JDkM5d" + "88n49sMGJxrhpjazuXYWdMf17C9T5XnxkopaeS7jGk1GyyVziaMt"); + r = hdnode_deserialize_public(str, VERSION_PUBLIC, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); +} +END_TEST + +START_TEST(test_bip32_compare) { + HDNode node1, node2, node3; + int i, r; + hdnode_from_seed( + fromhex( + "301133282ad079cbeb59bc446ad39d333928f74c46997d3609cd3e2801ca69d62788" + "f9f174429946ff4e9be89f67c22fae28cb296a9b37734f75e73d1477af19"), + 64, SECP256K1_NAME, &node1); + hdnode_from_seed( + fromhex( + "301133282ad079cbeb59bc446ad39d333928f74c46997d3609cd3e2801ca69d62788" + "f9f174429946ff4e9be89f67c22fae28cb296a9b37734f75e73d1477af19"), + 64, SECP256K1_NAME, &node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + for (i = 0; i < 100; i++) { + memcpy(&node3, &node1, sizeof(HDNode)); + ck_assert_int_eq(hdnode_fill_public_key(&node3), 0); + r = hdnode_private_ckd(&node1, i); + ck_assert_int_eq(r, 1); + r = hdnode_public_ckd(&node2, i); + ck_assert_int_eq(r, 1); + r = hdnode_public_ckd(&node3, i); + ck_assert_int_eq(r, 1); + ck_assert_uint_eq(node1.depth, node2.depth); + ck_assert_uint_eq(node1.depth, node3.depth); + ck_assert_uint_eq(node1.child_num, node2.child_num); + ck_assert_uint_eq(node1.child_num, node3.child_num); + ck_assert_mem_eq(node1.chain_code, node2.chain_code, 32); + ck_assert_mem_eq(node1.chain_code, node3.chain_code, 32); + ck_assert_mem_eq( + node2.private_key, + fromhex( + "0000000000000000000000000000000000000000000000000000000000000000"), + 32); + ck_assert_mem_eq( + node3.private_key, + fromhex( + "0000000000000000000000000000000000000000000000000000000000000000"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node1), 0); + ck_assert_mem_eq(node1.public_key, node2.public_key, 33); + ck_assert_mem_eq(node1.public_key, node3.public_key, 33); + } +} +END_TEST + +START_TEST(test_bip32_optimized) { + HDNode root; + hdnode_from_seed((uint8_t *)"NothingToSeeHere", 16, SECP256K1_NAME, &root); + ck_assert_int_eq(hdnode_fill_public_key(&root), 0); + + curve_point pub; + ecdsa_read_pubkey(&secp256k1, root.public_key, &pub); + + HDNode node; + char addr1[MAX_ADDR_SIZE], addr2[MAX_ADDR_SIZE]; + + for (int i = 0; i < 40; i++) { + // unoptimized + memcpy(&node, &root, sizeof(HDNode)); + hdnode_public_ckd(&node, i); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ecdsa_get_address(node.public_key, 0, HASHER_SHA2_RIPEMD, HASHER_SHA2D, + addr1, sizeof(addr1)); + // optimized + hdnode_public_ckd_address_optimized(&pub, root.chain_code, i, 0, + HASHER_SHA2_RIPEMD, HASHER_SHA2D, addr2, + sizeof(addr2), 0); + // check + ck_assert_str_eq(addr1, addr2); + } +} +END_TEST + +#if USE_BIP32_CACHE + +START_TEST(test_bip32_cache_1) { + HDNode node1, node2; + int i, r; + + // test 1 .. 8 + hdnode_from_seed( + fromhex( + "301133282ad079cbeb59bc446ad39d333928f74c46997d3609cd3e2801ca69d62788" + "f9f174429946ff4e9be89f67c22fae28cb296a9b37734f75e73d1477af19"), + 64, SECP256K1_NAME, &node1); + hdnode_from_seed( + fromhex( + "301133282ad079cbeb59bc446ad39d333928f74c46997d3609cd3e2801ca69d62788" + "f9f174429946ff4e9be89f67c22fae28cb296a9b37734f75e73d1477af19"), + 64, SECP256K1_NAME, &node2); + + uint32_t ii[] = {0x80000001, 0x80000002, 0x80000003, 0x80000004, + 0x80000005, 0x80000006, 0x80000007, 0x80000008}; + + for (i = 0; i < 8; i++) { + r = hdnode_private_ckd(&node1, ii[i]); + ck_assert_int_eq(r, 1); + } + r = hdnode_private_ckd_cached(&node2, ii, 8, NULL); + ck_assert_int_eq(r, 1); + ck_assert_mem_eq(&node1, &node2, sizeof(HDNode)); + + hdnode_from_seed( + fromhex( + "301133282ad079cbeb59bc446ad39d333928f74c46997d3609cd3e2801ca69d62788" + "f9f174429946ff4e9be89f67c22fae28cb296a9b37734f75e73d1477af19"), + 64, SECP256K1_NAME, &node1); + hdnode_from_seed( + fromhex( + "301133282ad079cbeb59bc446ad39d333928f74c46997d3609cd3e2801ca69d62788" + "f9f174429946ff4e9be89f67c22fae28cb296a9b37734f75e73d1477af19"), + 64, SECP256K1_NAME, &node2); + + // test 1 .. 7, 20 + ii[7] = 20; + for (i = 0; i < 8; i++) { + r = hdnode_private_ckd(&node1, ii[i]); + ck_assert_int_eq(r, 1); + } + r = hdnode_private_ckd_cached(&node2, ii, 8, NULL); + ck_assert_int_eq(r, 1); + ck_assert_mem_eq(&node1, &node2, sizeof(HDNode)); + + // test different root node + hdnode_from_seed( + fromhex( + "000000002ad079cbeb59bc446ad39d333928f74c46997d3609cd3e2801ca69d62788" + "f9f174429946ff4e9be89f67c22fae28cb296a9b37734f75e73d1477af19"), + 64, SECP256K1_NAME, &node1); + hdnode_from_seed( + fromhex( + "000000002ad079cbeb59bc446ad39d333928f74c46997d3609cd3e2801ca69d62788" + "f9f174429946ff4e9be89f67c22fae28cb296a9b37734f75e73d1477af19"), + 64, SECP256K1_NAME, &node2); + + for (i = 0; i < 8; i++) { + r = hdnode_private_ckd(&node1, ii[i]); + ck_assert_int_eq(r, 1); + } + r = hdnode_private_ckd_cached(&node2, ii, 8, NULL); + ck_assert_int_eq(r, 1); + ck_assert_mem_eq(&node1, &node2, sizeof(HDNode)); +} +END_TEST + +START_TEST(test_bip32_cache_2) { + HDNode nodea[9], nodeb[9]; + int i, j, r; + + for (j = 0; j < 9; j++) { + hdnode_from_seed( + fromhex( + "301133282ad079cbeb59bc446ad39d333928f74c46997d3609cd3e2801ca69d627" + "88f9f174429946ff4e9be89f67c22fae28cb296a9b37734f75e73d1477af19"), + 64, SECP256K1_NAME, &(nodea[j])); + hdnode_from_seed( + fromhex( + "301133282ad079cbeb59bc446ad39d333928f74c46997d3609cd3e2801ca69d627" + "88f9f174429946ff4e9be89f67c22fae28cb296a9b37734f75e73d1477af19"), + 64, SECP256K1_NAME, &(nodeb[j])); + } + + uint32_t ii[] = {0x80000001, 0x80000002, 0x80000003, 0x80000004, + 0x80000005, 0x80000006, 0x80000007, 0x80000008}; + for (j = 0; j < 9; j++) { + // non cached + for (i = 1; i <= j; i++) { + r = hdnode_private_ckd(&(nodea[j]), ii[i - 1]); + ck_assert_int_eq(r, 1); + } + // cached + r = hdnode_private_ckd_cached(&(nodeb[j]), ii, j, NULL); + ck_assert_int_eq(r, 1); + } + + ck_assert_mem_eq(&(nodea[0]), &(nodeb[0]), sizeof(HDNode)); + ck_assert_mem_eq(&(nodea[1]), &(nodeb[1]), sizeof(HDNode)); + ck_assert_mem_eq(&(nodea[2]), &(nodeb[2]), sizeof(HDNode)); + ck_assert_mem_eq(&(nodea[3]), &(nodeb[3]), sizeof(HDNode)); + ck_assert_mem_eq(&(nodea[4]), &(nodeb[4]), sizeof(HDNode)); + ck_assert_mem_eq(&(nodea[5]), &(nodeb[5]), sizeof(HDNode)); + ck_assert_mem_eq(&(nodea[6]), &(nodeb[6]), sizeof(HDNode)); + ck_assert_mem_eq(&(nodea[7]), &(nodeb[7]), sizeof(HDNode)); + ck_assert_mem_eq(&(nodea[8]), &(nodeb[8]), sizeof(HDNode)); +} +END_TEST +#endif + +START_TEST(test_bip32_nist_seed) { + HDNode node; + + // init m + hdnode_from_seed( + fromhex( + "a7305bc8df8d0951f0cb224c0e95d7707cbdf2c6ce7e8d481fec69c7ff5e9446"), + 32, NIST256P1_NAME, &node); + + // [Chain m] + ck_assert_mem_eq( + node.private_key, + fromhex( + "3b8c18469a4634517d6d0b65448f8e6c62091b45540a1743c5846be55d47d88f"), + 32); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "7762f9729fed06121fd13f326884c82f59aa95c57ac492ce8c9654e60efd130c"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "0383619fadcde31063d8c5cb00dbfe1713f3e6fa169d8541a798752a1c1ca0cb20"), + 33); + + // init m + hdnode_from_seed( + fromhex( + "aa305bc8df8d0951f0cb29ad4568d7707cbdf2c6ce7e8d481fec69c7ff5e9446"), + 32, NIST256P1_NAME, &node); + + // [Chain m] + ck_assert_mem_eq( + node.chain_code, + fromhex( + "a81d21f36f987fa0be3b065301bfb6aa9deefbf3dfef6744c37b9a4abc3c68f1"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "0e49dc46ce1d8c29d9b80a05e40f5d0cd68cbf02ae98572186f5343be18084bf"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "03aaa4c89acd9a98935330773d3dae55122f3591bac4a40942681768de8df6ba63"), + 33); +} +END_TEST + +START_TEST(test_bip32_nist_vector_1) { + HDNode node; + uint32_t fingerprint; + + // init m + hdnode_from_seed(fromhex("000102030405060708090a0b0c0d0e0f"), 16, + NIST256P1_NAME, &node); + + // [Chain m] + fingerprint = 0; + ck_assert_uint_eq(fingerprint, 0x00000000); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "beeb672fe4621673f722f38529c07392fecaa61015c80c34f29ce8b41b3cb6ea"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "612091aaa12e22dd2abef664f8a01a82cae99ad7441b7ef8110424915c268bc2"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "0266874dc6ade47b3ecd096745ca09bcd29638dd52c2c12117b11ed3e458cfa9e8"), + 33); + + // [Chain m/0'] + fingerprint = hdnode_fingerprint(&node); + hdnode_private_ckd_prime(&node, 0); + ck_assert_uint_eq(fingerprint, 0xbe6105b5); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "3460cea53e6a6bb5fb391eeef3237ffd8724bf0a40e94943c98b83825342ee11"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "6939694369114c67917a182c59ddb8cafc3004e63ca5d3b84403ba8613debc0c"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "0384610f5ecffe8fda089363a41f56a5c7ffc1d81b59a612d0d649b2d22355590c"), + 33); + + // [Chain m/0'/1] + fingerprint = hdnode_fingerprint(&node); + hdnode_private_ckd(&node, 1); + ck_assert_uint_eq(fingerprint, 0x9b02312f); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "4187afff1aafa8445010097fb99d23aee9f599450c7bd140b6826ac22ba21d0c"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "284e9d38d07d21e4e281b645089a94f4cf5a5a81369acf151a1c3a57f18b2129"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "03526c63f8d0b4bbbf9c80df553fe66742df4676b241dabefdef67733e070f6844"), + 33); + + // [Chain m/0'/1/2'] + fingerprint = hdnode_fingerprint(&node); + hdnode_private_ckd_prime(&node, 2); + ck_assert_uint_eq(fingerprint, 0xb98005c1); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "98c7514f562e64e74170cc3cf304ee1ce54d6b6da4f880f313e8204c2a185318"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "694596e8a54f252c960eb771a3c41e7e32496d03b954aeb90f61635b8e092aa7"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "0359cf160040778a4b14c5f4d7b76e327ccc8c4a6086dd9451b7482b5a4972dda0"), + 33); + + // [Chain m/0'/1/2'/2] + fingerprint = hdnode_fingerprint(&node); + hdnode_private_ckd(&node, 2); + ck_assert_uint_eq(fingerprint, 0x0e9f3274); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "ba96f776a5c3907d7fd48bde5620ee374d4acfd540378476019eab70790c63a0"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "5996c37fd3dd2679039b23ed6f70b506c6b56b3cb5e424681fb0fa64caf82aaa"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "029f871f4cb9e1c97f9f4de9ccd0d4a2f2a171110c61178f84430062230833ff20"), + 33); + + // [Chain m/0'/1/2'/2/1000000000] + fingerprint = hdnode_fingerprint(&node); + hdnode_private_ckd(&node, 1000000000); + ck_assert_uint_eq(fingerprint, 0x8b2b5c4b); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "b9b7b82d326bb9cb5b5b121066feea4eb93d5241103c9e7a18aad40f1dde8059"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "21c4f269ef0a5fd1badf47eeacebeeaa3de22eb8e5b0adcd0f27dd99d34d0119"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "02216cd26d31147f72427a453c443ed2cde8a1e53c9cc44e5ddf739725413fe3f4"), + 33); +} +END_TEST + +START_TEST(test_bip32_nist_vector_2) { + HDNode node; + uint32_t fingerprint; + int r; + + // init m + hdnode_from_seed( + fromhex( + "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c" + "999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542"), + 64, NIST256P1_NAME, &node); + + // [Chain m] + fingerprint = 0; + ck_assert_uint_eq(fingerprint, 0x00000000); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "96cd4465a9644e31528eda3592aa35eb39a9527769ce1855beafc1b81055e75d"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "eaa31c2e46ca2962227cf21d73a7ef0ce8b31c756897521eb6c7b39796633357"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "02c9e16154474b3ed5b38218bb0463e008f89ee03e62d22fdcc8014beab25b48fa"), + 33); + + // [Chain m/0] + fingerprint = hdnode_fingerprint(&node); + r = hdnode_private_ckd(&node, 0); + ck_assert_int_eq(r, 1); + ck_assert_uint_eq(fingerprint, 0x607f628f); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "84e9c258bb8557a40e0d041115b376dd55eda99c0042ce29e81ebe4efed9b86a"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "d7d065f63a62624888500cdb4f88b6d59c2927fee9e6d0cdff9cad555884df6e"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "039b6df4bece7b6c81e2adfeea4bcf5c8c8a6e40ea7ffa3cf6e8494c61a1fc82cc"), + 33); + + // [Chain m/0/2147483647'] + fingerprint = hdnode_fingerprint(&node); + r = hdnode_private_ckd_prime(&node, 2147483647); + ck_assert_int_eq(r, 1); + ck_assert_uint_eq(fingerprint, 0x946d2a54); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "f235b2bc5c04606ca9c30027a84f353acf4e4683edbd11f635d0dcc1cd106ea6"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "96d2ec9316746a75e7793684ed01e3d51194d81a42a3276858a5b7376d4b94b9"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "02f89c5deb1cae4fedc9905f98ae6cbf6cbab120d8cb85d5bd9a91a72f4c068c76"), + 33); + + // [Chain m/0/2147483647'/1] + fingerprint = hdnode_fingerprint(&node); + r = hdnode_private_ckd(&node, 1); + ck_assert_int_eq(r, 1); + ck_assert_uint_eq(fingerprint, 0x218182d8); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "7c0b833106235e452eba79d2bdd58d4086e663bc8cc55e9773d2b5eeda313f3b"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "974f9096ea6873a915910e82b29d7c338542ccde39d2064d1cc228f371542bbc"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "03abe0ad54c97c1d654c1852dfdc32d6d3e487e75fa16f0fd6304b9ceae4220c64"), + 33); + + // [Chain m/0/2147483647'/1/2147483646'] + fingerprint = hdnode_fingerprint(&node); + r = hdnode_private_ckd_prime(&node, 2147483646); + ck_assert_int_eq(r, 1); + ck_assert_uint_eq(fingerprint, 0x931223e4); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "5794e616eadaf33413aa309318a26ee0fd5163b70466de7a4512fd4b1a5c9e6a"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "da29649bbfaff095cd43819eda9a7be74236539a29094cd8336b07ed8d4eff63"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "03cb8cb067d248691808cd6b5a5a06b48e34ebac4d965cba33e6dc46fe13d9b933"), + 33); + + // [Chain m/0/2147483647'/1/2147483646'/2] + fingerprint = hdnode_fingerprint(&node); + r = hdnode_private_ckd(&node, 2); + ck_assert_int_eq(r, 1); + ck_assert_uint_eq(fingerprint, 0x956c4629); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "3bfb29ee8ac4484f09db09c2079b520ea5616df7820f071a20320366fbe226a7"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "bb0a77ba01cc31d77205d51d08bd313b979a71ef4de9b062f8958297e746bd67"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "020ee02e18967237cf62672983b253ee62fa4dd431f8243bfeccdf39dbe181387f"), + 33); + + // init m + hdnode_from_seed( + fromhex( + "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c" + "999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542"), + 64, NIST256P1_NAME, &node); + + // test public derivation + // [Chain m/0] + fingerprint = hdnode_fingerprint(&node); + r = hdnode_public_ckd(&node, 0); + ck_assert_int_eq(r, 1); + ck_assert_uint_eq(fingerprint, 0x607f628f); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "84e9c258bb8557a40e0d041115b376dd55eda99c0042ce29e81ebe4efed9b86a"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "0000000000000000000000000000000000000000000000000000000000000000"), + 32); + ck_assert_mem_eq( + node.public_key, + fromhex( + "039b6df4bece7b6c81e2adfeea4bcf5c8c8a6e40ea7ffa3cf6e8494c61a1fc82cc"), + 33); +} +END_TEST + +START_TEST(test_bip32_nist_compare) { + HDNode node1, node2, node3; + int i, r; + hdnode_from_seed( + fromhex( + "301133282ad079cbeb59bc446ad39d333928f74c46997d3609cd3e2801ca69d62788" + "f9f174429946ff4e9be89f67c22fae28cb296a9b37734f75e73d1477af19"), + 64, NIST256P1_NAME, &node1); + hdnode_from_seed( + fromhex( + "301133282ad079cbeb59bc446ad39d333928f74c46997d3609cd3e2801ca69d62788" + "f9f174429946ff4e9be89f67c22fae28cb296a9b37734f75e73d1477af19"), + 64, NIST256P1_NAME, &node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + for (i = 0; i < 100; i++) { + memcpy(&node3, &node1, sizeof(HDNode)); + ck_assert_int_eq(hdnode_fill_public_key(&node3), 0); + r = hdnode_private_ckd(&node1, i); + ck_assert_int_eq(r, 1); + r = hdnode_public_ckd(&node2, i); + ck_assert_int_eq(r, 1); + r = hdnode_public_ckd(&node3, i); + ck_assert_int_eq(r, 1); + ck_assert_uint_eq(node1.depth, node2.depth); + ck_assert_uint_eq(node1.depth, node3.depth); + ck_assert_uint_eq(node1.child_num, node2.child_num); + ck_assert_uint_eq(node1.child_num, node3.child_num); + ck_assert_mem_eq(node1.chain_code, node2.chain_code, 32); + ck_assert_mem_eq(node1.chain_code, node3.chain_code, 32); + ck_assert_mem_eq( + node2.private_key, + fromhex( + "0000000000000000000000000000000000000000000000000000000000000000"), + 32); + ck_assert_mem_eq( + node3.private_key, + fromhex( + "0000000000000000000000000000000000000000000000000000000000000000"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node1), 0); + ck_assert_mem_eq(node1.public_key, node2.public_key, 33); + ck_assert_mem_eq(node1.public_key, node3.public_key, 33); + } +} +END_TEST + +START_TEST(test_bip32_nist_repeat) { + HDNode node, node2; + uint32_t fingerprint; + int r; + + // init m + hdnode_from_seed(fromhex("000102030405060708090a0b0c0d0e0f"), 16, + NIST256P1_NAME, &node); + + // [Chain m/28578'] + fingerprint = hdnode_fingerprint(&node); + r = hdnode_private_ckd_prime(&node, 28578); + ck_assert_int_eq(r, 1); + ck_assert_uint_eq(fingerprint, 0xbe6105b5); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "e94c8ebe30c2250a14713212f6449b20f3329105ea15b652ca5bdfc68f6c65c2"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "06f0db126f023755d0b8d86d4591718a5210dd8d024e3e14b6159d63f53aa669"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "02519b5554a4872e8c9c1c847115363051ec43e93400e030ba3c36b52a3e70a5b7"), + 33); + + memcpy(&node2, &node, sizeof(HDNode)); + fingerprint = hdnode_fingerprint(&node); + r = hdnode_private_ckd(&node2, 33941); + ck_assert_int_eq(r, 1); + ck_assert_uint_eq(fingerprint, 0x3e2b7bc6); + ck_assert_mem_eq( + node2.chain_code, + fromhex( + "9e87fe95031f14736774cd82f25fd885065cb7c358c1edf813c72af535e83071"), + 32); + ck_assert_mem_eq( + node2.private_key, + fromhex( + "092154eed4af83e078ff9b84322015aefe5769e31270f62c3f66c33888335f3a"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq( + node2.public_key, + fromhex( + "0235bfee614c0d5b2cae260000bb1d0d84b270099ad790022c1ae0b2e782efe120"), + 33); + + memcpy(&node2, &node, sizeof(HDNode)); + memzero(&node2.private_key, 32); + r = hdnode_public_ckd(&node2, 33941); + ck_assert_int_eq(r, 1); + ck_assert_uint_eq(fingerprint, 0x3e2b7bc6); + ck_assert_mem_eq( + node2.chain_code, + fromhex( + "9e87fe95031f14736774cd82f25fd885065cb7c358c1edf813c72af535e83071"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq( + node2.public_key, + fromhex( + "0235bfee614c0d5b2cae260000bb1d0d84b270099ad790022c1ae0b2e782efe120"), + 33); +} +END_TEST + +// test vector 1 from https://en.bitcoin.it/wiki/BIP_0032_TestVectors +START_TEST(test_bip32_ed25519_vector_1) { + HDNode node; + + // init m + hdnode_from_seed(fromhex("000102030405060708090a0b0c0d0e0f"), 16, + ED25519_NAME, &node); + + // [Chain m] + ck_assert_mem_eq( + node.chain_code, + fromhex( + "90046a93de5380a72b5e45010748567d5ea02bbf6522f979e05c0d8d8ca9fffb"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "2b4be7f19ee27bbf30c667b642d5f4aa69fd169872f8fc3059c08ebae2eb19e7"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "01a4b2856bfec510abab89753fac1ac0e1112364e7d250545963f135f2a33188ed"), + 33); + + // [Chain m/0'] + hdnode_private_ckd_prime(&node, 0); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "8b59aa11380b624e81507a27fedda59fea6d0b779a778918a2fd3590e16e9c69"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "68e0fe46dfb67e368c75379acec591dad19df3cde26e63b93a8e704f1dade7a3"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "018c8a13df77a28f3445213a0f432fde644acaa215fc72dcdf300d5efaa85d350c"), + 33); + + // [Chain m/0'/1'] + hdnode_private_ckd_prime(&node, 1); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "a320425f77d1b5c2505a6b1b27382b37368ee640e3557c315416801243552f14"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "b1d0bad404bf35da785a64ca1ac54b2617211d2777696fbffaf208f746ae84f2"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "011932a5270f335bed617d5b935c80aedb1a35bd9fc1e31acafd5372c30f5c1187"), + 33); + + // [Chain m/0'/1'/2'] + hdnode_private_ckd_prime(&node, 2); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "2e69929e00b5ab250f49c3fb1c12f252de4fed2c1db88387094a0f8c4c9ccd6c"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "92a5b23c0b8a99e37d07df3fb9966917f5d06e02ddbd909c7e184371463e9fc9"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "01ae98736566d30ed0e9d2f4486a64bc95740d89c7db33f52121f8ea8f76ff0fc1"), + 33); + + // [Chain m/0'/1'/2'/2'] + hdnode_private_ckd_prime(&node, 2); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "8f6d87f93d750e0efccda017d662a1b31a266e4a6f5993b15f5c1f07f74dd5cc"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "30d1dc7e5fc04c31219ab25a27ae00b50f6fd66622f6e9c913253d6511d1e662"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "018abae2d66361c879b900d204ad2cc4984fa2aa344dd7ddc46007329ac76c429c"), + 33); + + // [Chain m/0'/1'/2'/2'/1000000000'] + hdnode_private_ckd_prime(&node, 1000000000); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "68789923a0cac2cd5a29172a475fe9e0fb14cd6adb5ad98a3fa70333e7afa230"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "8f94d394a8e8fd6b1bc2f3f49f5c47e385281d5c17e65324b0f62483e37e8793"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "013c24da049451555d51a7014a37337aa4e12d41e485abccfa46b47dfb2af54b7a"), + 33); +} +END_TEST + +// test vector 2 from https://en.bitcoin.it/wiki/BIP_0032_TestVectors +START_TEST(test_bip32_ed25519_vector_2) { + HDNode node; + int r; + + // init m + hdnode_from_seed( + fromhex( + "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c" + "999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542"), + 64, ED25519_NAME, &node); + + // [Chain m] + ck_assert_mem_eq( + node.chain_code, + fromhex( + "ef70a74db9c3a5af931b5fe73ed8e1a53464133654fd55e7a66f8570b8e33c3b"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "171cb88b1b3c1db25add599712e36245d75bc65a1a5c9e18d76f9f2b1eab4012"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "018fe9693f8fa62a4305a140b9764c5ee01e455963744fe18204b4fb948249308a"), + 33); + + // [Chain m/0'] + r = hdnode_private_ckd_prime(&node, 0); + ck_assert_int_eq(r, 1); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "0b78a3226f915c082bf118f83618a618ab6dec793752624cbeb622acb562862d"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "1559eb2bbec5790b0c65d8693e4d0875b1747f4970ae8b650486ed7470845635"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "0186fab68dcb57aa196c77c5f264f215a112c22a912c10d123b0d03c3c28ef1037"), + 33); + + // [Chain m/0'/2147483647'] + r = hdnode_private_ckd_prime(&node, 2147483647); + ck_assert_int_eq(r, 1); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "138f0b2551bcafeca6ff2aa88ba8ed0ed8de070841f0c4ef0165df8181eaad7f"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "ea4f5bfe8694d8bb74b7b59404632fd5968b774ed545e810de9c32a4fb4192f4"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "015ba3b9ac6e90e83effcd25ac4e58a1365a9e35a3d3ae5eb07b9e4d90bcf7506d"), + 33); + + // [Chain m/0'/2147483647'/1'] + r = hdnode_private_ckd_prime(&node, 1); + ck_assert_int_eq(r, 1); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "73bd9fff1cfbde33a1b846c27085f711c0fe2d66fd32e139d3ebc28e5a4a6b90"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "3757c7577170179c7868353ada796c839135b3d30554bbb74a4b1e4a5a58505c"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "012e66aa57069c86cc18249aecf5cb5a9cebbfd6fadeab056254763874a9352b45"), + 33); + + // [Chain m/0'/2147483647'/1'/2147483646'] + r = hdnode_private_ckd_prime(&node, 2147483646); + ck_assert_int_eq(r, 1); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "0902fe8a29f9140480a00ef244bd183e8a13288e4412d8389d140aac1794825a"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "5837736c89570de861ebc173b1086da4f505d4adb387c6a1b1342d5e4ac9ec72"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "01e33c0f7d81d843c572275f287498e8d408654fdf0d1e065b84e2e6f157aab09b"), + 33); + + // [Chain m/0'/2147483647'/1'/2147483646'/2'] + r = hdnode_private_ckd_prime(&node, 2); + ck_assert_int_eq(r, 1); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "5d70af781f3a37b829f0d060924d5e960bdc02e85423494afc0b1a41bbe196d4"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "551d333177df541ad876a60ea71f00447931c0a9da16f227c11ea080d7391b8d"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "0147150c75db263559a70d5778bf36abbab30fb061ad69f69ece61a72b0cfa4fc0"), + 33); +} +END_TEST + +// test vector 1 from +// https://github.com/decred/dcrd/blob/master/hdkeychain/extendedkey_test.go +START_TEST(test_bip32_decred_vector_1) { + HDNode node, node2, node3; + uint32_t fingerprint; + char str[XPUB_MAXLEN]; + int r; + + // init m + hdnode_from_seed(fromhex("000102030405060708090a0b0c0d0e0f"), 16, + SECP256K1_NAME, &node); + + // secp256k1_decred_info.bip32_name != "Bitcoin seed" so we cannot use it in + // hdnode_from_seed + node.curve = &secp256k1_decred_info; + + // [Chain m] + fingerprint = 0; + ck_assert_uint_eq(fingerprint, 0x00000000); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2"), + 33); + hdnode_serialize_private(&node, fingerprint, DECRED_VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "dprv3hCznBesA6jBtmoyVFPfyMSZ1qYZ3WdjdebquvkEfmRfxC9VFEFi2YD" + "aJqHnx7uGe75eGSa3Mn3oHK11hBW7KZUrPxwbCPBmuCi1nwm182s"); + r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, + SECP256K1_DECRED_NAME, &node2, NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, + sizeof(str)); + ck_assert_str_eq(str, + "dpubZ9169KDAEUnyoBhjjmT2VaEodr6pUTDoqCEAeqgbfr2JfkB88BbK77j" + "bTYbcYXb2FVz7DKBdW4P618yd51MwF8DjKVopSbS7Lkgi6bowX5w"); + r = hdnode_deserialize_public(str, DECRED_VERSION_PUBLIC, + SECP256K1_DECRED_NAME, &node2, NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); + + // [Chain m/0'] + fingerprint = hdnode_fingerprint(&node); + hdnode_private_ckd_prime(&node, 0); + ck_assert_uint_eq(fingerprint, 0xbc495588); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "035a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56"), + 33); + hdnode_serialize_private(&node, fingerprint, DECRED_VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "dprv3kUQDBztdyjKuwnaL3hfKYpT7W6X2huYH5d61YSWFBebSYwEBHAXJkC" + "pQ7rvMAxPzKqxVCGLvBqWvGxXjAyMJsV1XwKkfnQCM9KctC8k8bk"); + r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, + SECP256K1_DECRED_NAME, &node2, NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, + sizeof(str)); + ck_assert_str_eq(str, + "dpubZCGVaKZBiMo7pMgLaZm1qmchjWenTeVcUdFQkTNsFGFEA6xs4EW8PKi" + "qYqP7HBAitt9Hw16VQkQ1tjsZQSHNWFc6bEK6bLqrbco24FzBTY4"); + r = hdnode_deserialize_public(str, DECRED_VERSION_PUBLIC, + SECP256K1_DECRED_NAME, &node2, NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); + + // [Chain m/0'/1] + fingerprint = hdnode_fingerprint(&node); + hdnode_private_ckd(&node, 1); + ck_assert_uint_eq(fingerprint, 0xc67bc2ef); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "2a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "3c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "03501e454bf00751f24b1b489aa925215d66af2234e3891c3b21a52bedb3cd711c"), + 33); + hdnode_serialize_private(&node, fingerprint, DECRED_VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "dprv3nRtCZ5VAoHW4RUwQgRafSNRPUDFrmsgyY71A5eoZceVfuyL9SbZe2r" + "cbwDW2UwpkEniE4urffgbypegscNchPajWzy9QS4cRxF8QYXsZtq"); + r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, + SECP256K1_DECRED_NAME, &node2, NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, + sizeof(str)); + ck_assert_str_eq(str, + "dpubZEDyZgdnFBMHxqNhfCUwBfAg1UmXHiTmB5jKtzbAZhF8PTzy2PwAicN" + "dkg1CmW6TARxQeUbgC7nAQenJts4YoG3KMiqcjsjgeMvwLc43w6C"); + r = hdnode_deserialize_public(str, DECRED_VERSION_PUBLIC, + SECP256K1_DECRED_NAME, &node2, NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); + + // [Chain m/0'/1/2'] + fingerprint = hdnode_fingerprint(&node); + hdnode_private_ckd_prime(&node, 2); + ck_assert_uint_eq(fingerprint, 0xe7072187); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "cbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2"), + 33); + hdnode_serialize_private(&node, fingerprint, DECRED_VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "dprv3pYtkZK168vgrU38gXkUSjHQ2LGpEUzQ9fXrR8fGUR59YviSnm6U82X" + "jQYhpJEUPnVcC9bguJBQU5xVM4VFcDHu9BgScGPA6mQMH4bn5Cth"); + r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, + SECP256K1_DECRED_NAME, &node2, NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, + sizeof(str)); + ck_assert_str_eq(str, + "dpubZGLz7gsJAWzUksvtw3opxx5eeLq5fRaUMDABA3bdUVfnGUk5fiS5Cc3" + "kZGTjWtYr3jrEavQQnAF6jv2WCpZtFX4uFgifXqev6ED1TM9rTCB"); + r = hdnode_deserialize_public(str, DECRED_VERSION_PUBLIC, + SECP256K1_DECRED_NAME, &node2, NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); + + // [Chain m/0'/1/2'/2] + fingerprint = hdnode_fingerprint(&node); + hdnode_private_ckd(&node, 2); + ck_assert_uint_eq(fingerprint, 0xbcbbc1c4); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "0f479245fb19a38a1954c5c7c0ebab2f9bdfd96a17563ef28a6a4b1a2a764ef4"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "02e8445082a72f29b75ca48748a914df60622a609cacfce8ed0e35804560741d29"), + 33); + hdnode_serialize_private(&node, fingerprint, DECRED_VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "dprv3r7zqYFjT3NiNzdnwGxGpYh6S1TJCp1zA6mSEGaqLBJFnCB94cRMp7Y" + "YLR49aTZHZ7ya1CXwQJ6rodKeU9NgQTxkPSK7pzgZRgjYkQ7rgJh"); + r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, + SECP256K1_DECRED_NAME, &node2, NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, + sizeof(str)); + ck_assert_str_eq(str, + "dpubZHv6Cfp2XRSWHQXZBo1dLmVM421Zdkc4MePkyBXCLFttVkCmwZkxth4" + "ZV9PzkFP3DtD5xcVq2CPSYpJMWMaoxu1ixz4GNZFVcE2xnHP6chJ"); + r = hdnode_deserialize_public(str, DECRED_VERSION_PUBLIC, + SECP256K1_DECRED_NAME, &node2, NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); + + // [Chain m/0'/1/2'/2/1000000000] + fingerprint = hdnode_fingerprint(&node); + hdnode_private_ckd(&node, 1000000000); + ck_assert_uint_eq(fingerprint, 0xe58b52e4); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "471b76e389e528d6de6d816857e012c5455051cad6660850e58372a6c3e6e7c8"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "022a471424da5e657499d1ff51cb43c47481a03b1e77f951fe64cec9f5a48f7011"), + 33); + hdnode_serialize_private(&node, fingerprint, DECRED_VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "dprv3tJXnTDSb3uE6Euo6WvvhFKfBMNfxuJt5smqyPoHEoomoBMQyhYoQSK" + "JAHWtWxmuqdUVb8q9J2NaTkF6rYm6XDrSotkJ55bM21fffa7VV97"); + r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, + SECP256K1_DECRED_NAME, &node2, NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, + sizeof(str)); + ck_assert_str_eq(str, + "dpubZL6d9amjfRy1zeoZM2zHDU7uoMvwPqtxHRQAiJjeEtQQWjP3retQV1q" + "KJyzUd6ZJNgbJGXjtc5pdoBcTTYTLoxQzvV9JJCzCjB2eCWpRf8T"); + r = hdnode_deserialize_public(str, DECRED_VERSION_PUBLIC, + SECP256K1_DECRED_NAME, &node2, NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); +} +END_TEST + +// test vector 2 from +// https://github.com/decred/dcrd/blob/master/hdkeychain/extendedkey_test.go +START_TEST(test_bip32_decred_vector_2) { + HDNode node, node2, node3; + uint32_t fingerprint; + char str[XPUB_MAXLEN]; + int r; + + // init m + hdnode_from_seed( + fromhex( + "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c" + "999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542"), + 64, SECP256K1_NAME, &node); + + // secp256k1_decred_info.bip32_name != "Bitcoin seed" so we cannot use it in + // hdnode_from_seed + node.curve = &secp256k1_decred_info; + + // [Chain m] + fingerprint = 0; + ck_assert_uint_eq(fingerprint, 0x00000000); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "60499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "4b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "03cbcaa9c98c877a26977d00825c956a238e8dddfbd322cce4f74b0b5bd6ace4a7"), + 33); + hdnode_serialize_private(&node, fingerprint, DECRED_VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "dprv3hCznBesA6jBtPKJbQTxRZAKG2gyj8tZKEPaCsV4e9YYFBAgRP2eTSP" + "Aeu4r8dTMt9q51j2Vdt5zNqj7jbtovvocrP1qLj6WUTLF9xYQt4y"); + r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, + SECP256K1_DECRED_NAME, &node2, NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, + sizeof(str)); + ck_assert_str_eq(str, + "dpubZ9169KDAEUnynoD4qvXJwmxZt3FFA5UdWn1twnRReE9AxjCKJLNFY1u" + "BoegbFmwzA4Du7yqnu8tLivhrCCH6P3DgBS1HH5vmf8MpNXvvYT9"); + r = hdnode_deserialize_public(str, DECRED_VERSION_PUBLIC, + SECP256K1_DECRED_NAME, &node2, NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); + + // [Chain m/0] + fingerprint = hdnode_fingerprint(&node); + r = hdnode_private_ckd(&node, 0); + ck_assert_int_eq(r, 1); + ck_assert_uint_eq(fingerprint, 0x2524c9d3); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "abe74a98f6c7eabee0428f53798f0ab8aa1bd37873999041703c742f15ac7e1e"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "02fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea"), + 33); + hdnode_serialize_private(&node, fingerprint, DECRED_VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "dprv3jMy45BuuDETfxi59P8NTSjHPrNVq4wPRfLgRd57923L2hosj5NUEqi" + "LYQ4i7fJtUpiXZLr2wUeToJY2Tm5sCpAJdajEHDmieVJiPQNXwu9"); + r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, + SECP256K1_DECRED_NAME, &node2, NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, + sizeof(str)); + ck_assert_str_eq(str, + "dpubZBA4RCkCybJFaNbqPuBiyfXY1rvmG1XTdCy1AY1U96dxkFqWc2i5KRE" + "Mh7NYPpy7ZPMhdpFMAesex3JdFDfX4J5FEW3HjSacqEYPfwb9Cj7"); + r = hdnode_deserialize_public(str, DECRED_VERSION_PUBLIC, + SECP256K1_DECRED_NAME, &node2, NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); + + // [Chain m/0/2147483647'] + fingerprint = hdnode_fingerprint(&node); + r = hdnode_private_ckd_prime(&node, 2147483647); + ck_assert_int_eq(r, 1); + ck_assert_uint_eq(fingerprint, 0x6035c6ad); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "be17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d9"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "877c779ad9687164e9c2f4f0f4ff0340814392330693ce95a58fe18fd52e6e93"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "03c01e7425647bdefa82b12d9bad5e3e6865bee0502694b94ca58b666abc0a5c3b"), + 33); + hdnode_serialize_private(&node, fingerprint, DECRED_VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "dprv3mgHPRgK838mLK6T1p6WeBoJoJtXA1pGTHjqFuyHekcM7UTuER8fGwe" + "RRsoLqSuHa98uskVPnJnfWZEBUC1AVmXnSCPDvUFKydXNnnPHTuQ"); + r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, + SECP256K1_DECRED_NAME, &node2, NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, + sizeof(str)); + ck_assert_str_eq(str, + "dpubZDUNkZEcCRCZEizDGL9sAQbZRKSnaxQLeqN9zpueeqCyq2VY7NUGMXA" + "SacsK96S8XzNjq3YgFgwLtj8MJBToW6To9U5zxuazEyh89bjR1xA"); + r = hdnode_deserialize_public(str, DECRED_VERSION_PUBLIC, + SECP256K1_DECRED_NAME, &node2, NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); + + // [Chain m/0/2147483647'/1] + fingerprint = hdnode_fingerprint(&node); + r = hdnode_private_ckd(&node, 1); + ck_assert_int_eq(r, 1); + ck_assert_uint_eq(fingerprint, 0x36fc7080); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "704addf544a06e5ee4bea37098463c23613da32020d604506da8c0518e1da4b7"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "03a7d1d856deb74c508e05031f9895dab54626251b3806e16b4bd12e781a7df5b9"), + 33); + hdnode_serialize_private(&node, fingerprint, DECRED_VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "dprv3oFqwZZ9bJcUmhAeJyyshvrTWtrAsHfcRYQbEzNiiH5nGvM6wVTDn6w" + "oQEz92b2EHTYZBtLi82jKEnxSouA3cVaW8YWBsw5c3f4mwAhA3d2"); + r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, + SECP256K1_DECRED_NAME, &node2, NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, + sizeof(str)); + ck_assert_str_eq(str, + "dpubZF3wJh7SfggGg74QZW3EE9ei8uQSJEFgd62uyuK5iMgQzUNjpSnprgT" + "pYz3d6Q3fXXtEEXQqpzWcP4LUVuXFsgA8JKt1Hot5kyUk4pPRhDz"); + r = hdnode_deserialize_public(str, DECRED_VERSION_PUBLIC, + SECP256K1_DECRED_NAME, &node2, NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); + + // [Chain m/0/2147483647'/1/2147483646'] + fingerprint = hdnode_fingerprint(&node); + r = hdnode_private_ckd_prime(&node, 2147483646); + ck_assert_int_eq(r, 1); + ck_assert_uint_eq(fingerprint, 0x45309b4c); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e29"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "f1c7c871a54a804afe328b4c83a1c33b8e5ff48f5087273f04efa83b247d6a2d"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "02d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0"), + 33); + hdnode_serialize_private(&node, fingerprint, DECRED_VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "dprv3qF3177i87wMirg6sraDvqty8yZg6THpXFPSXuM5AShBiiUQbq8FhSZ" + "DGkYmBNR3RKfBrxzkKDBpsRFJfTnQfLsvpPPqRnakat6hHQA43X9"); + r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, + SECP256K1_DECRED_NAME, &node2, NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, + sizeof(str)); + ck_assert_str_eq(str, + "dpubZH38NEg1CW19dGZs8NdaT4hDkz7wXPstio1mGpHSAXHpSGW3UnTrn25" + "ERT1Mp8ae5GMoQHMbgQiPrChMXQMdx3UqS8YqFkT1pqait8fY92u"); + r = hdnode_deserialize_public(str, DECRED_VERSION_PUBLIC, + SECP256K1_DECRED_NAME, &node2, NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); + + // [Chain m/0/2147483647'/1/2147483646'/2] + fingerprint = hdnode_fingerprint(&node); + r = hdnode_private_ckd(&node, 2); + ck_assert_int_eq(r, 1); + ck_assert_uint_eq(fingerprint, 0x3491a5e6); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "bb7d39bdb83ecf58f2fd82b6d918341cbef428661ef01ab97c28a4842125ac23"), + 32); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + ck_assert_mem_eq( + node.public_key, + fromhex( + "024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c"), + 33); + hdnode_serialize_private(&node, fingerprint, DECRED_VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "dprv3s15tfqzxhw8Kmo7RBEqMeyvC7uGekLniSmvbs3bckpxQ6ks1KKqfmH" + "144Jgh3PLxkyZRcS367kp7DrtUmnG16NpnsoNhxSXRgKbJJ7MUQR"); + r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, + SECP256K1_DECRED_NAME, &node2, NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, + sizeof(str)); + ck_assert_str_eq(str, + "dpubZJoBFoQJ35zvEBgsfhJBssnAp8TY5gvruzQFLmyxcqRb7enVtGfSkLo" + "2CkAZJMpa6T2fx6fUtvTgXtUvSVgAZ56bEwGxQsToeZfFV8VadE1"); + r = hdnode_deserialize_public(str, DECRED_VERSION_PUBLIC, + SECP256K1_DECRED_NAME, &node2, NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); + + // init m + hdnode_deserialize_public( + "dpubZF4LSCdF9YKZfNzTVYhz4RBxsjYXqms8AQnMBHXZ8GUKoRSigG7kQnKiJt5pzk93Q8Fx" + "cdVBEkQZruSXduGtWnkwXzGnjbSovQ97dCxqaXc", + DECRED_VERSION_PUBLIC, SECP256K1_DECRED_NAME, &node, NULL); + + // test public derivation + // [Chain m/0] + fingerprint = hdnode_fingerprint(&node); + r = hdnode_public_ckd(&node, 0); + ck_assert_int_eq(r, 1); + ck_assert_uint_eq(fingerprint, 0x6a19cfb3); + ck_assert_mem_eq( + node.chain_code, + fromhex( + "dcfe00831741a3a4803955147cdfc7053d69b167b1d03b5f9e63934217a005fd"), + 32); + ck_assert_mem_eq( + node.public_key, + fromhex( + "029555ea7bde276cd2c42c4502f40b5d16469fb310ae3aeee2a9000455f41b0866"), + 33); + hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, + sizeof(str)); + ck_assert_str_eq(str, + "dpubZHJs2Z3PtHbbpaXQCi5wBKPhU8tC5ztBKUYBCYNGKk8eZ1EmBs3MhnL" + "JbxHFMAahGnDnZT7qZxC7AXKP8PB6BDNUZgkG77moNMRmXyQ6s6s"); + r = hdnode_deserialize_public(str, DECRED_VERSION_PUBLIC, + SECP256K1_DECRED_NAME, &node2, NULL); + ck_assert_int_eq(r, 0); + ck_assert_mem_eq(&node2, &node, sizeof(HDNode)); +} +END_TEST + +static void test_ecdsa_get_public_key33_helper(int (*ecdsa_get_public_key33_fn)( + const ecdsa_curve *, const uint8_t *, uint8_t *)) { + uint8_t privkey[32] = {0}; + uint8_t pubkey[65] = {0}; + const ecdsa_curve *curve = &secp256k1; + int res = 0; + + memcpy( + privkey, + fromhex( + "c46f5b217f04ff28886a89d3c762ed84e5fa318d1c9a635d541131e69f1f49f5"), + 32); + res = ecdsa_get_public_key33_fn(curve, privkey, pubkey); + ck_assert_int_eq(res, 0); + ck_assert_mem_eq( + pubkey, + fromhex( + "0232b062e9153f573c220b1be0299d6447e81577274bf11a7c08dff71384c6b6ec"), + 33); + + memcpy( + privkey, + fromhex( + "3b90a4de80fb00d77795762c389d1279d4b4ab5992ae3cde6bc12ca63116f74c"), + 32); + res = ecdsa_get_public_key33_fn(curve, privkey, pubkey); + ck_assert_int_eq(res, 0); + ck_assert_mem_eq( + pubkey, + fromhex( + "0332b062e9153f573c220b1be0299d6447e81577274bf11a7c08dff71384c6b6ec"), + 33); +} + +START_TEST(test_ecdsa_get_public_key33) { + test_ecdsa_get_public_key33_helper(ecdsa_get_public_key33); +} +END_TEST + +static void test_ecdsa_get_public_key65_helper(int (*ecdsa_get_public_key65_fn)( + const ecdsa_curve *, const uint8_t *, uint8_t *)) { + uint8_t privkey[32] = {0}; + uint8_t pubkey[65] = {0}; + const ecdsa_curve *curve = &secp256k1; + int res = 0; + + memcpy( + privkey, + fromhex( + "c46f5b217f04ff28886a89d3c762ed84e5fa318d1c9a635d541131e69f1f49f5"), + 32); + res = ecdsa_get_public_key65_fn(curve, privkey, pubkey); + ck_assert_int_eq(res, 0); + ck_assert_mem_eq( + pubkey, + fromhex( + "0432b062e9153f573c220b1be0299d6447e81577274bf11a7c08dff71384c6b6ec" + "179ca56b637a57e0fcd28cefa10c9433dc30532682647f4daa053d43d5cc960a"), + 65); +} + +START_TEST(test_ecdsa_get_public_key65) { + test_ecdsa_get_public_key65_helper(ecdsa_get_public_key65); +} +END_TEST + +static void test_ecdsa_recover_pub_from_sig_helper(int ( + *ecdsa_recover_pub_from_sig_fn)(const ecdsa_curve *, uint8_t *, + const uint8_t *, const uint8_t *, int)) { + int res; + uint8_t digest[32]; + uint8_t pubkey[65]; + const ecdsa_curve *curve = &secp256k1; + + // sha2(sha2("\x18Bitcoin Signed Message:\n\x0cHello World!")) + memcpy( + digest, + fromhex( + "de4e9524586d6fce45667f9ff12f661e79870c4105fa0fb58af976619bb11432"), + 32); + // r = 2: Four points should exist + res = ecdsa_recover_pub_from_sig_fn( + curve, pubkey, + fromhex( + "00000000000000000000000000000000000000000000000000000000000000020123" + "456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"), + digest, 0); + ck_assert_int_eq(res, 0); + ck_assert_mem_eq( + pubkey, + fromhex( + "043fc5bf5fec35b6ffe6fd246226d312742a8c296bfa57dd22da509a2e348529b7dd" + "b9faf8afe1ecda3c05e7b2bda47ee1f5a87e952742b22afca560b29d972fcf"), + 65); + res = ecdsa_recover_pub_from_sig_fn( + curve, pubkey, + fromhex( + "00000000000000000000000000000000000000000000000000000000000000020123" + "456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"), + digest, 1); + ck_assert_int_eq(res, 0); + ck_assert_mem_eq( + pubkey, + fromhex( + "0456d8089137b1fd0d890f8c7d4a04d0fd4520a30b19518ee87bd168ea12ed809032" + "9274c4c6c0d9df04515776f2741eeffc30235d596065d718c3973e19711ad0"), + 65); + res = ecdsa_recover_pub_from_sig_fn( + curve, pubkey, + fromhex( + "00000000000000000000000000000000000000000000000000000000000000020123" + "456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"), + digest, 2); + ck_assert_int_eq(res, 0); + ck_assert_mem_eq( + pubkey, + fromhex( + "04cee0e740f41aab39156844afef0182dea2a8026885b10454a2d539df6f6df9023a" + "bfcb0f01c50bef3c0fa8e59a998d07441e18b1c60583ef75cc8b912fb21a15"), + 65); + res = ecdsa_recover_pub_from_sig_fn( + curve, pubkey, + fromhex( + "00000000000000000000000000000000000000000000000000000000000000020123" + "456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"), + digest, 3); + ck_assert_int_eq(res, 0); + ck_assert_mem_eq( + pubkey, + fromhex( + "0490d2bd2e9a564d6e1d8324fc6ad00aa4ae597684ecf4abea58bdfe7287ea4fa729" + "68c2e5b0b40999ede3d7898d94e82c3f8dc4536a567a4bd45998c826a4c4b2"), + 65); + // The point at infinity is not considered to be a valid public key. + res = ecdsa_recover_pub_from_sig_fn( + curve, pubkey, + fromhex( + "220cf4c7b6d568f2256a8c30cc1784a625a28c3627dac404aa9a9ecd08314ec81a88" + "828f20d69d102bab5de5f6ee7ef040cb0ff7b8e1ba3f29d79efb5250f47d"), + digest, 0); + ck_assert_int_eq(res, 1); + + memcpy( + digest, + fromhex( + "0000000000000000000000000000000000000000000000000000000000000000"), + 32); + // r = 7: No point P with P.x = 7, but P.x = (order + 7) exists + res = ecdsa_recover_pub_from_sig_fn( + curve, pubkey, + fromhex( + "00000000000000000000000000000000000000000000000000000000000000070123" + "456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"), + digest, 2); + ck_assert_int_eq(res, 0); + ck_assert_mem_eq( + pubkey, + fromhex( + "044d81bb47a31ffc6cf1f780ecb1e201ec47214b651650867c07f13ad06e12a1b040" + "de78f8dbda700f4d3cd7ee21b3651a74c7661809699d2be7ea0992b0d39797"), + 65); + res = ecdsa_recover_pub_from_sig_fn( + curve, pubkey, + fromhex( + "00000000000000000000000000000000000000000000000000000000000000070123" + "456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"), + digest, 3); + ck_assert_int_eq(res, 0); + ck_assert_mem_eq( + pubkey, + fromhex( + "044d81bb47a31ffc6cf1f780ecb1e201ec47214b651650867c07f13ad06e12a1b0bf" + "21870724258ff0b2c32811de4c9ae58b3899e7f69662d41815f66c4f2c6498"), + 65); + res = ecdsa_recover_pub_from_sig_fn( + curve, pubkey, + fromhex( + "00000000000000000000000000000000000000000000000000000000000000070123" + "456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"), + digest, 0); + ck_assert_int_eq(res, 1); + + memcpy( + digest, + fromhex( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + 32); + // r = 1: Two points P with P.x = 1, but P.x = (order + 7) doesn't exist + res = ecdsa_recover_pub_from_sig_fn( + curve, pubkey, + fromhex( + "00000000000000000000000000000000000000000000000000000000000000010123" + "456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"), + digest, 0); + ck_assert_int_eq(res, 0); + ck_assert_mem_eq( + pubkey, + fromhex( + "045d330b2f89dbfca149828277bae852dd4aebfe136982cb531a88e9e7a89463fe71" + "519f34ea8feb9490c707f14bc38c9ece51762bfd034ea014719b7c85d2871b"), + 65); + res = ecdsa_recover_pub_from_sig_fn( + curve, pubkey, + fromhex( + "00000000000000000000000000000000000000000000000000000000000000010123" + "456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"), + digest, 1); + ck_assert_int_eq(res, 0); + ck_assert_mem_eq( + pubkey, + fromhex( + "049e609c3950e70d6f3e3f3c81a473b1d5ca72739d51debdd80230ae80cab05134a9" + "4285375c834a417e8115c546c41da83a263087b79ef1cae25c7b3c738daa2b"), + 65); + + // r = 0 is always invalid + res = ecdsa_recover_pub_from_sig_fn( + curve, pubkey, + fromhex( + "00000000000000000000000000000000000000000000000000000000000000010123" + "456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"), + digest, 2); + ck_assert_int_eq(res, 1); + res = ecdsa_recover_pub_from_sig_fn( + curve, pubkey, + fromhex( + "00000000000000000000000000000000000000000000000000000000000000000123" + "456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"), + digest, 0); + ck_assert_int_eq(res, 1); + // r >= order is always invalid + res = ecdsa_recover_pub_from_sig_fn( + curve, pubkey, + fromhex( + "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd03641410123" + "456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"), + digest, 0); + ck_assert_int_eq(res, 1); + // check that overflow of r is handled + res = ecdsa_recover_pub_from_sig_fn( + curve, pubkey, + fromhex( + "000000000000000000000000000000014551231950B75FC4402DA1722FC9BAEE0123" + "456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"), + digest, 2); + ck_assert_int_eq(res, 1); + // s = 0 is always invalid + res = ecdsa_recover_pub_from_sig_fn( + curve, pubkey, + fromhex( + "00000000000000000000000000000000000000000000000000000000000000020000" + "000000000000000000000000000000000000000000000000000000000000"), + digest, 0); + ck_assert_int_eq(res, 1); + // s >= order is always invalid + res = ecdsa_recover_pub_from_sig_fn( + curve, pubkey, + fromhex( + "0000000000000000000000000000000000000000000000000000000000000002ffff" + "fffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"), + digest, 0); + ck_assert_int_eq(res, 1); +} + +START_TEST(test_ecdsa_recover_pub_from_sig) { + test_ecdsa_recover_pub_from_sig_helper(ecdsa_recover_pub_from_sig); +} +END_TEST + +static void test_ecdsa_verify_digest_helper(int (*ecdsa_verify_digest_fn)( + const ecdsa_curve *, const uint8_t *, const uint8_t *, const uint8_t *)) { + int res; + uint8_t digest[32]; + uint8_t pubkey[65]; + uint8_t sig[64]; + const ecdsa_curve *curve = &secp256k1; + + // Signature verification for a digest which is equal to the group order. + // https://github.com/trezor/trezor-firmware/pull/1374 + memcpy( + pubkey, + fromhex( + "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179848" + "3ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8"), + sizeof(pubkey)); + memcpy( + digest, + fromhex( + "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"), + sizeof(digest)); + memcpy(sig, + fromhex( + "a0b37f8fba683cc68f6574cd43b39f0343a50008bf6ccea9d13231d9e7e2e1e41" + "1edc8d307254296264aebfc3dc76cd8b668373a072fd64665b50000e9fcce52"), + sizeof(sig)); + res = ecdsa_verify_digest_fn(curve, pubkey, sig, digest); + ck_assert_int_eq(res, 0); +} + +START_TEST(test_ecdsa_verify_digest) { + test_ecdsa_verify_digest_helper(ecdsa_verify_digest); +} +END_TEST + +#define test_deterministic(KEY, MSG, K) \ + do { \ + sha256_Raw((uint8_t *)MSG, strlen(MSG), buf); \ + init_rfc6979(fromhex(KEY), buf, NULL, &rng); \ + generate_k_rfc6979(&k, &rng); \ + bn_write_be(&k, buf); \ + ck_assert_mem_eq(buf, fromhex(K), 32); \ + } while (0) + +START_TEST(test_rfc6979) { + bignum256 k; + uint8_t buf[32]; + rfc6979_state rng; + + test_deterministic( + "c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721", + "sample", + "a6e3c57dd01abe90086538398355dd4c3b17aa873382b0f24d6129493d8aad60"); + test_deterministic( + "cca9fbcc1b41e5a95d369eaa6ddcff73b61a4efaa279cfc6567e8daa39cbaf50", + "sample", + "2df40ca70e639d89528a6b670d9d48d9165fdc0febc0974056bdce192b8e16a3"); + test_deterministic( + "0000000000000000000000000000000000000000000000000000000000000001", + "Satoshi Nakamoto", + "8f8a276c19f4149656b280621e358cce24f5f52542772691ee69063b74f15d15"); + test_deterministic( + "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", + "Satoshi Nakamoto", + "33a19b60e25fb6f4435af53a3d42d493644827367e6453928554f43e49aa6f90"); + test_deterministic( + "f8b8af8ce3c7cca5e300d33939540c10d45ce001b8f252bfbc57ba0342904181", + "Alan Turing", + "525a82b70e67874398067543fd84c83d30c175fdc45fdeee082fe13b1d7cfdf1"); + test_deterministic( + "0000000000000000000000000000000000000000000000000000000000000001", + "All those moments will be lost in time, like tears in rain. Time to " + "die...", + "38aa22d72376b4dbc472e06c3ba403ee0a394da63fc58d88686c611aba98d6b3"); + test_deterministic( + "e91671c46231f833a6406ccbea0e3e392c76c167bac1cb013f6f1013980455c2", + "There is a computer disease that anybody who works with computers knows " + "about. It's a very serious disease and it interferes completely with " + "the work. The trouble with computers is that you 'play' with them!", + "1f4b84c23a86a221d233f2521be018d9318639d5b8bbd6374a8a59232d16ad3d"); +} +END_TEST + +static void test_ecdsa_sign_digest_deterministic_helper( + int (*ecdsa_sign_digest_fn)(const ecdsa_curve *, const uint8_t *, + const uint8_t *, uint8_t *, uint8_t *, + int (*)(uint8_t by, uint8_t sig[64]))) { + static struct { + const char *priv_key; + const char *digest; + const char *sig; + } tests[] = { + {"312155017c70a204106e034520e0cdf17b3e54516e2ece38e38e38e38e38e38e", + "ffffffffffffffffffffffffffffffff20202020202020202020202020202020", + "e3d70248ea2fc771fc8d5e62d76b9cfd5402c96990333549eaadce1ae9f737eb" + "5cfbdc7d1e0ec18cc9b57bbb18f0a57dc929ec3c4dfac9073c581705015f6a8a"}, + {"312155017c70a204106e034520e0cdf17b3e54516e2ece38e38e38e38e38e38e", + "2020202020202020202020202020202020202020202020202020202020202020", + "40666188895430715552a7e4c6b53851f37a93030fb94e043850921242db78e8" + "75aa2ac9fd7e5a19402973e60e64382cdc29a09ebf6cb37e92f23be5b9251aee"}, + }; + + const ecdsa_curve *curve = &secp256k1; + uint8_t priv_key[32] = {0}; + uint8_t digest[32] = {0}; + uint8_t expected_sig[64] = {0}; + uint8_t computed_sig[64] = {0}; + int res = 0; + + for (size_t i = 0; i < sizeof(tests) / sizeof(*tests); i++) { + memcpy(priv_key, fromhex(tests[i].priv_key), 32); + memcpy(digest, fromhex(tests[i].digest), 32); + memcpy(expected_sig, fromhex(tests[i].sig), 64); + + res = + ecdsa_sign_digest_fn(curve, priv_key, digest, computed_sig, NULL, NULL); + ck_assert_int_eq(res, 0); + ck_assert_mem_eq(expected_sig, computed_sig, 64); + } +} + +START_TEST(test_ecdsa_sign_digest_deterministic) { + test_ecdsa_sign_digest_deterministic_helper(ecdsa_sign_digest); +} +END_TEST + +// test vectors from +// http://www.inconteam.com/software-development/41-encryption/55-aes-test-vectors +START_TEST(test_aes) { + aes_encrypt_ctx ctxe; + aes_decrypt_ctx ctxd; + uint8_t ibuf[16], obuf[16], iv[16], cbuf[16]; + const char **ivp, **plainp, **cipherp; + + // ECB + static const char *ecb_vector[] = { + // plain cipher + "6bc1bee22e409f96e93d7e117393172a", + "f3eed1bdb5d2a03c064b5a7e3db181f8", + "ae2d8a571e03ac9c9eb76fac45af8e51", + "591ccb10d410ed26dc5ba74a31362870", + "30c81c46a35ce411e5fbc1191a0a52ef", + "b6ed21b99ca6f4f9f153e7b1beafed1d", + "f69f2445df4f9b17ad2b417be66c3710", + "23304b7a39f9f3ff067d8d8f9e24ecc7", + 0, + 0, + }; + plainp = ecb_vector; + cipherp = ecb_vector + 1; + while (*plainp && *cipherp) { + // encrypt + aes_encrypt_key256( + fromhex( + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"), + &ctxe); + memcpy(ibuf, fromhex(*plainp), 16); + aes_ecb_encrypt(ibuf, obuf, 16, &ctxe); + ck_assert_mem_eq(obuf, fromhex(*cipherp), 16); + // decrypt + aes_decrypt_key256( + fromhex( + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"), + &ctxd); + memcpy(ibuf, fromhex(*cipherp), 16); + aes_ecb_decrypt(ibuf, obuf, 16, &ctxd); + ck_assert_mem_eq(obuf, fromhex(*plainp), 16); + plainp += 2; + cipherp += 2; + } + + // CBC + static const char *cbc_vector[] = { + // iv plain cipher + "000102030405060708090A0B0C0D0E0F", + "6bc1bee22e409f96e93d7e117393172a", + "f58c4c04d6e5f1ba779eabfb5f7bfbd6", + "F58C4C04D6E5F1BA779EABFB5F7BFBD6", + "ae2d8a571e03ac9c9eb76fac45af8e51", + "9cfc4e967edb808d679f777bc6702c7d", + "9CFC4E967EDB808D679F777BC6702C7D", + "30c81c46a35ce411e5fbc1191a0a52ef", + "39f23369a9d9bacfa530e26304231461", + "39F23369A9D9BACFA530E26304231461", + "f69f2445df4f9b17ad2b417be66c3710", + "b2eb05e2c39be9fcda6c19078c6a9d1b", + 0, + 0, + 0, + }; + ivp = cbc_vector; + plainp = cbc_vector + 1; + cipherp = cbc_vector + 2; + while (*plainp && *cipherp) { + // encrypt + aes_encrypt_key256( + fromhex( + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"), + &ctxe); + memcpy(iv, fromhex(*ivp), 16); + memcpy(ibuf, fromhex(*plainp), 16); + aes_cbc_encrypt(ibuf, obuf, 16, iv, &ctxe); + ck_assert_mem_eq(obuf, fromhex(*cipherp), 16); + // decrypt + aes_decrypt_key256( + fromhex( + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"), + &ctxd); + memcpy(iv, fromhex(*ivp), 16); + memcpy(ibuf, fromhex(*cipherp), 16); + aes_cbc_decrypt(ibuf, obuf, 16, iv, &ctxd); + ck_assert_mem_eq(obuf, fromhex(*plainp), 16); + ivp += 3; + plainp += 3; + cipherp += 3; + } + + // CFB + static const char *cfb_vector[] = { + "000102030405060708090A0B0C0D0E0F", + "6bc1bee22e409f96e93d7e117393172a", + "DC7E84BFDA79164B7ECD8486985D3860", + "DC7E84BFDA79164B7ECD8486985D3860", + "ae2d8a571e03ac9c9eb76fac45af8e51", + "39ffed143b28b1c832113c6331e5407b", + "39FFED143B28B1C832113C6331E5407B", + "30c81c46a35ce411e5fbc1191a0a52ef", + "df10132415e54b92a13ed0a8267ae2f9", + "DF10132415E54B92A13ED0A8267AE2F9", + "f69f2445df4f9b17ad2b417be66c3710", + "75a385741ab9cef82031623d55b1e471", + 0, + 0, + 0, + }; + ivp = cfb_vector; + plainp = cfb_vector + 1; + cipherp = cfb_vector + 2; + while (*plainp && *cipherp) { + // encrypt + aes_encrypt_key256( + fromhex( + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"), + &ctxe); + memcpy(iv, fromhex(*ivp), 16); + memcpy(ibuf, fromhex(*plainp), 16); + aes_cfb_encrypt(ibuf, obuf, 16, iv, &ctxe); + ck_assert_mem_eq(obuf, fromhex(*cipherp), 16); + // decrypt (uses encryption) + aes_encrypt_key256( + fromhex( + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"), + &ctxe); + memcpy(iv, fromhex(*ivp), 16); + memcpy(ibuf, fromhex(*cipherp), 16); + aes_cfb_decrypt(ibuf, obuf, 16, iv, &ctxe); + ck_assert_mem_eq(obuf, fromhex(*plainp), 16); + ivp += 3; + plainp += 3; + cipherp += 3; + } + + // OFB + static const char *ofb_vector[] = { + "000102030405060708090A0B0C0D0E0F", + "6bc1bee22e409f96e93d7e117393172a", + "dc7e84bfda79164b7ecd8486985d3860", + "B7BF3A5DF43989DD97F0FA97EBCE2F4A", + "ae2d8a571e03ac9c9eb76fac45af8e51", + "4febdc6740d20b3ac88f6ad82a4fb08d", + "E1C656305ED1A7A6563805746FE03EDC", + "30c81c46a35ce411e5fbc1191a0a52ef", + "71ab47a086e86eedf39d1c5bba97c408", + "41635BE625B48AFC1666DD42A09D96E7", + "f69f2445df4f9b17ad2b417be66c3710", + "0126141d67f37be8538f5a8be740e484", + 0, + 0, + 0, + }; + ivp = ofb_vector; + plainp = ofb_vector + 1; + cipherp = ofb_vector + 2; + while (*plainp && *cipherp) { + // encrypt + aes_encrypt_key256( + fromhex( + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"), + &ctxe); + memcpy(iv, fromhex(*ivp), 16); + memcpy(ibuf, fromhex(*plainp), 16); + aes_ofb_encrypt(ibuf, obuf, 16, iv, &ctxe); + ck_assert_mem_eq(obuf, fromhex(*cipherp), 16); + // decrypt (uses encryption) + aes_encrypt_key256( + fromhex( + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"), + &ctxe); + memcpy(iv, fromhex(*ivp), 16); + memcpy(ibuf, fromhex(*cipherp), 16); + aes_ofb_decrypt(ibuf, obuf, 16, iv, &ctxe); + ck_assert_mem_eq(obuf, fromhex(*plainp), 16); + ivp += 3; + plainp += 3; + cipherp += 3; + } + + // CTR + static const char *ctr_vector[] = { + // plain cipher + "6bc1bee22e409f96e93d7e117393172a", + "601ec313775789a5b7a7f504bbf3d228", + "ae2d8a571e03ac9c9eb76fac45af8e51", + "f443e3ca4d62b59aca84e990cacaf5c5", + "30c81c46a35ce411e5fbc1191a0a52ef", + "2b0930daa23de94ce87017ba2d84988d", + "f69f2445df4f9b17ad2b417be66c3710", + "dfc9c58db67aada613c2dd08457941a6", + 0, + 0, + }; + // encrypt + plainp = ctr_vector; + cipherp = ctr_vector + 1; + memcpy(cbuf, fromhex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"), 16); + aes_encrypt_key256( + fromhex( + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"), + &ctxe); + while (*plainp && *cipherp) { + memcpy(ibuf, fromhex(*plainp), 16); + aes_ctr_encrypt(ibuf, obuf, 16, cbuf, aes_ctr_cbuf_inc, &ctxe); + ck_assert_mem_eq(obuf, fromhex(*cipherp), 16); + plainp += 2; + cipherp += 2; + } + // decrypt (uses encryption) + plainp = ctr_vector; + cipherp = ctr_vector + 1; + memcpy(cbuf, fromhex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"), 16); + aes_encrypt_key256( + fromhex( + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"), + &ctxe); + while (*plainp && *cipherp) { + memcpy(ibuf, fromhex(*cipherp), 16); + aes_ctr_decrypt(ibuf, obuf, 16, cbuf, aes_ctr_cbuf_inc, &ctxe); + ck_assert_mem_eq(obuf, fromhex(*plainp), 16); + plainp += 2; + cipherp += 2; + } +} +END_TEST + +#define TEST1 "abc" +#define TEST2_1 "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" +#define TEST2_2a "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn" +#define TEST2_2b "hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu" +#define TEST2_2 TEST2_2a TEST2_2b +#define TEST3 "a" /* times 1000000 */ +#define TEST4a "01234567012345670123456701234567" +#define TEST4b "01234567012345670123456701234567" +/* an exact multiple of 512 bits */ +#define TEST4 TEST4a TEST4b /* times 10 */ + +#define TEST7_1 "\x49\xb2\xae\xc2\x59\x4b\xbe\x3a\x3b\x11\x75\x42\xd9\x4a\xc8" +#define TEST8_1 \ + "\x9a\x7d\xfd\xf1\xec\xea\xd0\x6e\xd6\x46\xaa\x55\xfe\x75\x71\x46" +#define TEST9_1 \ + "\x65\xf9\x32\x99\x5b\xa4\xce\x2c\xb1\xb4\xa2\xe7\x1a\xe7\x02\x20" \ + "\xaa\xce\xc8\x96\x2d\xd4\x49\x9c\xbd\x7c\x88\x7a\x94\xea\xaa\x10" \ + "\x1e\xa5\xaa\xbc\x52\x9b\x4e\x7e\x43\x66\x5a\x5a\xf2\xcd\x03\xfe" \ + "\x67\x8e\xa6\xa5\x00\x5b\xba\x3b\x08\x22\x04\xc2\x8b\x91\x09\xf4" \ + "\x69\xda\xc9\x2a\xaa\xb3\xaa\x7c\x11\xa1\xb3\x2a" +#define TEST10_1 \ + "\xf7\x8f\x92\x14\x1b\xcd\x17\x0a\xe8\x9b\x4f\xba\x15\xa1\xd5\x9f" \ + "\x3f\xd8\x4d\x22\x3c\x92\x51\xbd\xac\xbb\xae\x61\xd0\x5e\xd1\x15" \ + "\xa0\x6a\x7c\xe1\x17\xb7\xbe\xea\xd2\x44\x21\xde\xd9\xc3\x25\x92" \ + "\xbd\x57\xed\xea\xe3\x9c\x39\xfa\x1f\xe8\x94\x6a\x84\xd0\xcf\x1f" \ + "\x7b\xee\xad\x17\x13\xe2\xe0\x95\x98\x97\x34\x7f\x67\xc8\x0b\x04" \ + "\x00\xc2\x09\x81\x5d\x6b\x10\xa6\x83\x83\x6f\xd5\x56\x2a\x56\xca" \ + "\xb1\xa2\x8e\x81\xb6\x57\x66\x54\x63\x1c\xf1\x65\x66\xb8\x6e\x3b" \ + "\x33\xa1\x08\xb0\x53\x07\xc0\x0a\xff\x14\xa7\x68\xed\x73\x50\x60" \ + "\x6a\x0f\x85\xe6\xa9\x1d\x39\x6f\x5b\x5c\xbe\x57\x7f\x9b\x38\x80" \ + "\x7c\x7d\x52\x3d\x6d\x79\x2f\x6e\xbc\x24\xa4\xec\xf2\xb3\xa4\x27" \ + "\xcd\xbb\xfb" +#define length(x) (sizeof(x) - 1) + +// test vectors from rfc-4634 +START_TEST(test_sha1) { + struct { + const char *test; + int length; + int repeatcount; + int extrabits; + int numberExtrabits; + const char *result; + } tests[] = { + /* 1 */ {TEST1, length(TEST1), 1, 0, 0, + "A9993E364706816ABA3E25717850C26C9CD0D89D"}, + /* 2 */ + {TEST2_1, length(TEST2_1), 1, 0, 0, + "84983E441C3BD26EBAAE4AA1F95129E5E54670F1"}, + /* 3 */ + {TEST3, length(TEST3), 1000000, 0, 0, + "34AA973CD4C4DAA4F61EEB2BDBAD27316534016F"}, + /* 4 */ + {TEST4, length(TEST4), 10, 0, 0, + "DEA356A2CDDD90C7A7ECEDC5EBB563934F460452"}, + /* 5 */ {"", 0, 0, 0x98, 5, "29826B003B906E660EFF4027CE98AF3531AC75BA"}, + /* 6 */ {"\x5e", 1, 1, 0, 0, "5E6F80A34A9798CAFC6A5DB96CC57BA4C4DB59C2"}, + /* 7 */ + {TEST7_1, length(TEST7_1), 1, 0x80, 3, + "6239781E03729919C01955B3FFA8ACB60B988340"}, + /* 8 */ + {TEST8_1, length(TEST8_1), 1, 0, 0, + "82ABFF6605DBE1C17DEF12A394FA22A82B544A35"}, + /* 9 */ + {TEST9_1, length(TEST9_1), 1, 0xE0, 3, + "8C5B2A5DDAE5A97FC7F9D85661C672ADBF7933D4"}, + /* 10 */ + {TEST10_1, length(TEST10_1), 1, 0, 0, + "CB0082C8F197D260991BA6A460E76E202BAD27B3"}}; + + for (int i = 0; i < 10; i++) { + SHA1_CTX ctx; + uint8_t digest[SHA1_DIGEST_LENGTH]; + sha1_Init(&ctx); + /* extra bits are not supported */ + if (tests[i].numberExtrabits) continue; + for (int j = 0; j < tests[i].repeatcount; j++) { + sha1_Update(&ctx, (const uint8_t *)tests[i].test, tests[i].length); + } + sha1_Final(&ctx, digest); + ck_assert_mem_eq(digest, fromhex(tests[i].result), SHA1_DIGEST_LENGTH); + } +} +END_TEST + +#define TEST7_256 "\xbe\x27\x46\xc6\xdb\x52\x76\x5f\xdb\x2f\x88\x70\x0f\x9a\x73" +#define TEST8_256 \ + "\xe3\xd7\x25\x70\xdc\xdd\x78\x7c\xe3\x88\x7a\xb2\xcd\x68\x46\x52" +#define TEST9_256 \ + "\x3e\x74\x03\x71\xc8\x10\xc2\xb9\x9f\xc0\x4e\x80\x49\x07\xef\x7c" \ + "\xf2\x6b\xe2\x8b\x57\xcb\x58\xa3\xe2\xf3\xc0\x07\x16\x6e\x49\xc1" \ + "\x2e\x9b\xa3\x4c\x01\x04\x06\x91\x29\xea\x76\x15\x64\x25\x45\x70" \ + "\x3a\x2b\xd9\x01\xe1\x6e\xb0\xe0\x5d\xeb\xa0\x14\xeb\xff\x64\x06" \ + "\xa0\x7d\x54\x36\x4e\xff\x74\x2d\xa7\x79\xb0\xb3" +#define TEST10_256 \ + "\x83\x26\x75\x4e\x22\x77\x37\x2f\x4f\xc1\x2b\x20\x52\x7a\xfe\xf0" \ + "\x4d\x8a\x05\x69\x71\xb1\x1a\xd5\x71\x23\xa7\xc1\x37\x76\x00\x00" \ + "\xd7\xbe\xf6\xf3\xc1\xf7\xa9\x08\x3a\xa3\x9d\x81\x0d\xb3\x10\x77" \ + "\x7d\xab\x8b\x1e\x7f\x02\xb8\x4a\x26\xc7\x73\x32\x5f\x8b\x23\x74" \ + "\xde\x7a\x4b\x5a\x58\xcb\x5c\x5c\xf3\x5b\xce\xe6\xfb\x94\x6e\x5b" \ + "\xd6\x94\xfa\x59\x3a\x8b\xeb\x3f\x9d\x65\x92\xec\xed\xaa\x66\xca" \ + "\x82\xa2\x9d\x0c\x51\xbc\xf9\x33\x62\x30\xe5\xd7\x84\xe4\xc0\xa4" \ + "\x3f\x8d\x79\xa3\x0a\x16\x5c\xba\xbe\x45\x2b\x77\x4b\x9c\x71\x09" \ + "\xa9\x7d\x13\x8f\x12\x92\x28\x96\x6f\x6c\x0a\xdc\x10\x6a\xad\x5a" \ + "\x9f\xdd\x30\x82\x57\x69\xb2\xc6\x71\xaf\x67\x59\xdf\x28\xeb\x39" \ + "\x3d\x54\xd6" + +// test vectors from rfc-4634 +START_TEST(test_sha256) { + struct { + const char *test; + int length; + int repeatcount; + int extrabits; + int numberExtrabits; + const char *result; + } tests[] = { + /* 1 */ {TEST1, length(TEST1), 1, 0, 0, + "BA7816BF8F01CFEA4141" + "40DE5DAE2223B00361A396177A9CB410FF61F20015AD"}, + /* 2 */ + {TEST2_1, length(TEST2_1), 1, 0, 0, + "248D6A61D20638B8" + "E5C026930C3E6039A33CE45964FF2167F6ECEDD419DB06C1"}, + /* 3 */ + {TEST3, length(TEST3), 1000000, 0, 0, + "CDC76E5C9914FB92" + "81A1C7E284D73E67F1809A48A497200E046D39CCC7112CD0"}, + /* 4 */ + {TEST4, length(TEST4), 10, 0, 0, + "594847328451BDFA" + "85056225462CC1D867D877FB388DF0CE35F25AB5562BFBB5"}, + /* 5 */ + {"", 0, 0, 0x68, 5, + "D6D3E02A31A84A8CAA9718ED6C2057BE" + "09DB45E7823EB5079CE7A573A3760F95"}, + /* 6 */ + {"\x19", 1, 1, 0, 0, + "68AA2E2EE5DFF96E3355E6C7EE373E3D" + "6A4E17F75F9518D843709C0C9BC3E3D4"}, + /* 7 */ + {TEST7_256, length(TEST7_256), 1, 0x60, 3, + "77EC1DC8" + "9C821FF2A1279089FA091B35B8CD960BCAF7DE01C6A7680756BEB972"}, + /* 8 */ + {TEST8_256, length(TEST8_256), 1, 0, 0, + "175EE69B02BA" + "9B58E2B0A5FD13819CEA573F3940A94F825128CF4209BEABB4E8"}, + /* 9 */ + {TEST9_256, length(TEST9_256), 1, 0xA0, 3, + "3E9AD646" + "8BBBAD2AC3C2CDC292E018BA5FD70B960CF1679777FCE708FDB066E9"}, + /* 10 */ + {TEST10_256, length(TEST10_256), 1, 0, 0, + "97DBCA7D" + "F46D62C8A422C941DD7E835B8AD3361763F7E9B2D95F4F0DA6E1CCBC"}, + }; + + for (int i = 0; i < 10; i++) { + SHA256_CTX ctx; + uint8_t digest[SHA256_DIGEST_LENGTH]; + sha256_Init(&ctx); + /* extra bits are not supported */ + if (tests[i].numberExtrabits) continue; + for (int j = 0; j < tests[i].repeatcount; j++) { + sha256_Update(&ctx, (const uint8_t *)tests[i].test, tests[i].length); + } + sha256_Final(&ctx, digest); + ck_assert_mem_eq(digest, fromhex(tests[i].result), SHA256_DIGEST_LENGTH); + } +} +END_TEST + +#define TEST7_512 "\x08\xec\xb5\x2e\xba\xe1\xf7\x42\x2d\xb6\x2b\xcd\x54\x26\x70" +#define TEST8_512 \ + "\x8d\x4e\x3c\x0e\x38\x89\x19\x14\x91\x81\x6e\x9d\x98\xbf\xf0\xa0" +#define TEST9_512 \ + "\x3a\xdd\xec\x85\x59\x32\x16\xd1\x61\x9a\xa0\x2d\x97\x56\x97\x0b" \ + "\xfc\x70\xac\xe2\x74\x4f\x7c\x6b\x27\x88\x15\x10\x28\xf7\xb6\xa2" \ + "\x55\x0f\xd7\x4a\x7e\x6e\x69\xc2\xc9\xb4\x5f\xc4\x54\x96\x6d\xc3" \ + "\x1d\x2e\x10\xda\x1f\x95\xce\x02\xbe\xb4\xbf\x87\x65\x57\x4c\xbd" \ + "\x6e\x83\x37\xef\x42\x0a\xdc\x98\xc1\x5c\xb6\xd5\xe4\xa0\x24\x1b" \ + "\xa0\x04\x6d\x25\x0e\x51\x02\x31\xca\xc2\x04\x6c\x99\x16\x06\xab" \ + "\x4e\xe4\x14\x5b\xee\x2f\xf4\xbb\x12\x3a\xab\x49\x8d\x9d\x44\x79" \ + "\x4f\x99\xcc\xad\x89\xa9\xa1\x62\x12\x59\xed\xa7\x0a\x5b\x6d\xd4" \ + "\xbd\xd8\x77\x78\xc9\x04\x3b\x93\x84\xf5\x49\x06" +#define TEST10_512 \ + "\xa5\x5f\x20\xc4\x11\xaa\xd1\x32\x80\x7a\x50\x2d\x65\x82\x4e\x31" \ + "\xa2\x30\x54\x32\xaa\x3d\x06\xd3\xe2\x82\xa8\xd8\x4e\x0d\xe1\xde" \ + "\x69\x74\xbf\x49\x54\x69\xfc\x7f\x33\x8f\x80\x54\xd5\x8c\x26\xc4" \ + "\x93\x60\xc3\xe8\x7a\xf5\x65\x23\xac\xf6\xd8\x9d\x03\xe5\x6f\xf2" \ + "\xf8\x68\x00\x2b\xc3\xe4\x31\xed\xc4\x4d\xf2\xf0\x22\x3d\x4b\xb3" \ + "\xb2\x43\x58\x6e\x1a\x7d\x92\x49\x36\x69\x4f\xcb\xba\xf8\x8d\x95" \ + "\x19\xe4\xeb\x50\xa6\x44\xf8\xe4\xf9\x5e\xb0\xea\x95\xbc\x44\x65" \ + "\xc8\x82\x1a\xac\xd2\xfe\x15\xab\x49\x81\x16\x4b\xbb\x6d\xc3\x2f" \ + "\x96\x90\x87\xa1\x45\xb0\xd9\xcc\x9c\x67\xc2\x2b\x76\x32\x99\x41" \ + "\x9c\xc4\x12\x8b\xe9\xa0\x77\xb3\xac\xe6\x34\x06\x4e\x6d\x99\x28" \ + "\x35\x13\xdc\x06\xe7\x51\x5d\x0d\x73\x13\x2e\x9a\x0d\xc6\xd3\xb1" \ + "\xf8\xb2\x46\xf1\xa9\x8a\x3f\xc7\x29\x41\xb1\xe3\xbb\x20\x98\xe8" \ + "\xbf\x16\xf2\x68\xd6\x4f\x0b\x0f\x47\x07\xfe\x1e\xa1\xa1\x79\x1b" \ + "\xa2\xf3\xc0\xc7\x58\xe5\xf5\x51\x86\x3a\x96\xc9\x49\xad\x47\xd7" \ + "\xfb\x40\xd2" + +// test vectors from rfc-4634 +START_TEST(test_sha512) { + struct { + const char *test; + int length; + int repeatcount; + int extrabits; + int numberExtrabits; + const char *result; + } tests[] = {/* 1 */ {TEST1, length(TEST1), 1, 0, 0, + "DDAF35A193617ABACC417349AE20413112E6FA4E89A97EA2" + "0A9EEEE64B55D39A2192992A274FC1A836BA3C23A3FEEBBD" + "454D4423643CE80E2A9AC94FA54CA49F"}, + /* 2 */ + {TEST2_2, length(TEST2_2), 1, 0, 0, + "8E959B75DAE313DA8CF4F72814FC143F8F7779C6EB9F7FA1" + "7299AEADB6889018501D289E4900F7E4331B99DEC4B5433A" + "C7D329EEB6DD26545E96E55B874BE909"}, + /* 3 */ + {TEST3, length(TEST3), 1000000, 0, 0, + "E718483D0CE769644E2E42C7BC15B4638E1F98B13B204428" + "5632A803AFA973EBDE0FF244877EA60A4CB0432CE577C31B" + "EB009C5C2C49AA2E4EADB217AD8CC09B"}, + /* 4 */ + {TEST4, length(TEST4), 10, 0, 0, + "89D05BA632C699C31231DED4FFC127D5A894DAD412C0E024" + "DB872D1ABD2BA8141A0F85072A9BE1E2AA04CF33C765CB51" + "0813A39CD5A84C4ACAA64D3F3FB7BAE9"}, + /* 5 */ + {"", 0, 0, 0xB0, 5, + "D4EE29A9E90985446B913CF1D1376C836F4BE2C1CF3CADA0" + "720A6BF4857D886A7ECB3C4E4C0FA8C7F95214E41DC1B0D2" + "1B22A84CC03BF8CE4845F34DD5BDBAD4"}, + /* 6 */ + {"\xD0", 1, 1, 0, 0, + "9992202938E882E73E20F6B69E68A0A7149090423D93C81B" + "AB3F21678D4ACEEEE50E4E8CAFADA4C85A54EA8306826C4A" + "D6E74CECE9631BFA8A549B4AB3FBBA15"}, + /* 7 */ + {TEST7_512, length(TEST7_512), 1, 0x80, 3, + "ED8DC78E8B01B69750053DBB7A0A9EDA0FB9E9D292B1ED71" + "5E80A7FE290A4E16664FD913E85854400C5AF05E6DAD316B" + "7359B43E64F8BEC3C1F237119986BBB6"}, + /* 8 */ + {TEST8_512, length(TEST8_512), 1, 0, 0, + "CB0B67A4B8712CD73C9AABC0B199E9269B20844AFB75ACBD" + "D1C153C9828924C3DDEDAAFE669C5FDD0BC66F630F677398" + "8213EB1B16F517AD0DE4B2F0C95C90F8"}, + /* 9 */ + {TEST9_512, length(TEST9_512), 1, 0x80, 3, + "32BA76FC30EAA0208AEB50FFB5AF1864FDBF17902A4DC0A6" + "82C61FCEA6D92B783267B21080301837F59DE79C6B337DB2" + "526F8A0A510E5E53CAFED4355FE7C2F1"}, + /* 10 */ + {TEST10_512, length(TEST10_512), 1, 0, 0, + "C665BEFB36DA189D78822D10528CBF3B12B3EEF726039909" + "C1A16A270D48719377966B957A878E720584779A62825C18" + "DA26415E49A7176A894E7510FD1451F5"}}; + + for (int i = 0; i < 10; i++) { + SHA512_CTX ctx; + uint8_t digest[SHA512_DIGEST_LENGTH]; + sha512_Init(&ctx); + /* extra bits are not supported */ + if (tests[i].numberExtrabits) continue; + for (int j = 0; j < tests[i].repeatcount; j++) { + sha512_Update(&ctx, (const uint8_t *)tests[i].test, tests[i].length); + } + sha512_Final(&ctx, digest); + ck_assert_mem_eq(digest, fromhex(tests[i].result), SHA512_DIGEST_LENGTH); + } +} +END_TEST + +// test vectors from http://www.di-mgt.com.au/sha_testvectors.html +START_TEST(test_sha3_256) { + static const struct { + const char *data; + const char *hash; + } tests[] = { + { + "", + "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a", + }, + { + "abc", + "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532", + }, + { + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "41c0dba2a9d6240849100376a8235e2c82e1b9998a999e21db32dd97496d3376", + }, + { + "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijkl" + "mnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", + "916f6061fe879741ca6469b43971dfdb28b1a32dc36cb3254e812be27aad1d18", + }, + }; + + uint8_t digest[SHA3_256_DIGEST_LENGTH]; + for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { + size_t len = strlen(tests[i].data); + sha3_256((uint8_t *)tests[i].data, len, digest); + ck_assert_mem_eq(digest, fromhex(tests[i].hash), SHA3_256_DIGEST_LENGTH); + + // Test progressive hashing. + size_t part_len = len; + SHA3_CTX ctx; + sha3_256_Init(&ctx); + sha3_Update(&ctx, (uint8_t *)tests[i].data, part_len); + sha3_Update(&ctx, NULL, 0); + sha3_Update(&ctx, (uint8_t *)tests[i].data + part_len, len - part_len); + sha3_Final(&ctx, digest); + ck_assert_mem_eq(digest, fromhex(tests[i].hash), SHA3_256_DIGEST_LENGTH); + } +} +END_TEST + +// test vectors from http://www.di-mgt.com.au/sha_testvectors.html +START_TEST(test_sha3_512) { + static const struct { + const char *data; + const char *hash; + } tests[] = { + { + "", + "a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2" + "123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26", + }, + { + "abc", + "b751850b1a57168a5693cd924b6b096e08f621827444f70d884f5d0240d2712e10e1" + "16e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0", + }, + { + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "04a371e84ecfb5b8b77cb48610fca8182dd457ce6f326a0fd3d7ec2f1e91636dee69" + "1fbe0c985302ba1b0d8dc78c086346b533b49c030d99a27daf1139d6e75e", + }, + { + "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijkl" + "mnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", + "afebb2ef542e6579c50cad06d2e578f9f8dd6881d7dc824d26360feebf18a4fa73e3" + "261122948efcfd492e74e82e2189ed0fb440d187f382270cb455f21dd185", + }, + }; + + uint8_t digest[SHA3_512_DIGEST_LENGTH]; + for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { + size_t len = strlen(tests[i].data); + sha3_512((uint8_t *)tests[i].data, len, digest); + ck_assert_mem_eq(digest, fromhex(tests[i].hash), SHA3_512_DIGEST_LENGTH); + + // Test progressive hashing. + size_t part_len = len; + SHA3_CTX ctx; + sha3_512_Init(&ctx); + sha3_Update(&ctx, (const uint8_t *)tests[i].data, part_len); + sha3_Update(&ctx, NULL, 0); + sha3_Update(&ctx, (const uint8_t *)tests[i].data + part_len, + len - part_len); + sha3_Final(&ctx, digest); + ck_assert_mem_eq(digest, fromhex(tests[i].hash), SHA3_512_DIGEST_LENGTH); + } +} +END_TEST + +// test vectors from +// https://raw.githubusercontent.com/NemProject/nem-test-vectors/master/0.test-sha3-256.dat +START_TEST(test_keccak_256) { + static const struct { + const char *hash; + size_t length; + const char *data; + } tests[] = { + { + "4e9e79ab7434f6c7401fb3305d55052ee829b9e46d5d05d43b59fefb32e9a619", + 293, + "a6151d4904e18ec288243028ceda30556e6c42096af7150d6a7232ca5dba52bd2192" + "e23daa5fa2bea3d4bd95efa2389cd193fcd3376e70a5c097b32c1c62c80af9d71021" + "1545f7cdddf63747420281d64529477c61e721273cfd78f8890abb4070e97baa52ac" + "8ff61c26d195fc54c077def7a3f6f79b36e046c1a83ce9674ba1983ec2fb58947de6" + "16dd797d6499b0385d5e8a213db9ad5078a8e0c940ff0cb6bf92357ea5609f778c3d" + "1fb1e7e36c35db873361e2be5c125ea7148eff4a035b0cce880a41190b2e22924ad9" + "d1b82433d9c023924f2311315f07b88bfd42850047bf3be785c4ce11c09d7e02065d" + "30f6324365f93c5e7e423a07d754eb314b5fe9db4614275be4be26af017abdc9c338" + "d01368226fe9af1fb1f815e7317bdbb30a0f36dc69", + }, + { + "c1268babc42d00c3463dc388222100f7e525a74a64665c39f112f788ddb5da42", + 376, + "9db801077952c2324e0044a4994edfb09b3edfcf669bfdd029f4bf42d5b0eab3056b" + "0bf82708ca7bfadba43c9de806b10a19d0f00c2351ef1086b6b108f306e035c6b61b" + "2e70fd7087ba848601c8a3f626a66666423717ef305a1068bfa3a1f7ffc1e5a78cb6" + "182ffc8a577ca2a821630bf900d0fbba848bdf94b77c5946771b6c3f8c02269bc772" + "ca56098f724536d96be68c284ee1d81697989d40029b8ea63ac1fd85f8b3cae8b194" + "f6834ff65a5858f9498ddbb467995eb2d49cdfc6c05d92038c6e9aaeee85f8222b37" + "84165f12a2c3df4c7a142e26dddfd831d07e22dfecc0eded48a69c8a9e1b97f1a4e0" + "efcd4edd310de0edf82af38a6e4d5ab2a19da586e61210d4f75e7a07e2201f9c8154" + "ca52a414a70d2eb2ac1c5b9a2900b4d871f62fa56f70d03b3dd3704bd644808c45a1" + "3231918ea884645b8ec054e8bab2935a66811fe590ddc119ae901dfeb54fc2a87c1e" + "0a236778baab2fa8843709c6676d3c1888ba19d75ec52d73a7d035c143179b938237" + "26b7", + }, + { + "e83b50e8c83cb676a7dd64c055f53e5110d5a4c62245ceb8f683fd87b2b3ec77", + 166, + "c070a957550b7b34113ee6543a1918d96d241f27123425db7f7b9004e047ffbe0561" + "2e7fa8c54b23c83ea427e625e97b7a28b09a70bf6d91e478eeed01d7907931c29ea8" + "6e70f2cdcfb243ccf7f24a1619abf4b5b9e6f75cbf63fc02baf4a820a9790a6b053e" + "50fd94e0ed57037cfc2bab4d95472b97d3c25f434f1cc0b1ede5ba7f15907a42a223" + "933e5e2dfcb518c3531975268c326d60fa911fbb7997eee3ba87656c4fe7", + }, + { + "8ebd2c9d4ff00e285a9b6b140bfc3cef672016f0098100e1f6f250220af7ce1a", + 224, + "b502fbdce4045e49e147eff5463d4b3f37f43461518868368e2c78008c84c2db79d1" + "2b58107034f67e7d0abfee67add0342dd23dce623f26b9156def87b1d7ac15a6e073" + "01f832610fe869ada13a2b0e3d60aa6bb81bc04487e2e800f5106b0402ee0331df74" + "5e021b5ea5e32faf1c7fc1322041d221a54191c0af19948b5f34411937182e30d5cd" + "39b5a6c959d77d92d21bb1de51f1b3411cb6eec00600429916227fb62d2c88e69576" + "f4ac8e5efcde8efa512cc80ce7fb0dfaa6c74d26e898cefe9d4f7dce232a69f2a6a9" + "477aa08366efcdfca117c89cb79eba15a23755e0", + }, + { + "db3961fdddd0c314289efed5d57363459a6700a7bd015e7a03d3e1d03f046401", + 262, + "22e203a98ba2c43d8bc3658f0a48a35766df356d6a5e98b0c7222d16d85a00b31720" + "7d4aef3fc7cabb67b9d8f5838de0b733e1fd59c31f0667e53286972d7090421ad90d" + "54db2ea40047d0d1700c86f53dbf48da532396307e68edad877dcae481848801b0a5" + "db44dbdba6fc7c63b5cd15281d57ca9e6be96f530b209b59d6127ad2bd8750f3f807" + "98f62521f0d5b42633c2f5a9aaefbed38779b7aded2338d66850b0bb0e33c48e040c" + "99f2dcee7a7ebb3d7416e1c5bf038c19d09682dab67c96dbbfad472e45980aa27d1b" + "301b15f7de4d4f549bad2501931c9d4f1a3b1692dcb4b1b834ddd4a636126702307d" + "daeec61841693b21887d56e76cc2069dafb557fd6682160f", + }, + { + "25dd3acacd6bf688c0eace8d33eb7cc550271969142deb769a05b4012f7bb722", + 122, + "99e7f6e0ed46ec866c43a1ab494998d47e9309a79fde2a629eb63bb2160a5ffd0f22" + "06de9c32dd20e9b23e57ab7422cf82971cc2873ec0e173fe93281c7b33e1c76ac792" + "23a6f435f230bdd30260c00d00986c72a399d3ba70f6e783d834bbf8a6127844def5" + "59b8b6db742b2cfd715f7ff29e7b42bf7d567beb", + }, + { + "00d747c9045c093484290afc161437f11c2ddf5f8a9fc2acae9c7ef5fcf511e5", + 440, + "50c392f97f8788377f0ab2e2aab196cb017ad157c6f9d022673d39072cc198b06622" + "a5cbd269d1516089fa59e28c3373a92bd54b2ebf1a79811c7e40fdd7bce200e80983" + "fda6e77fc44c44c1b5f87e01cef2f41e1141103f73364e9c2f25a4597e6517ef31b3" + "16300b770c69595e0fa6d011df1566a8676a88c7698562273bbfa217cc69d4b5c89a" + "8907b902f7dc14481fefc7da4a810c15a60f5641aae854d2f8cc50cbc393015560f0" + "1c94e0d0c075dbcb150ad6eba29dc747919edcaf0231dba3eb3f2b1a87e136a1f0fd" + "4b3d8ee61bad2729e9526a32884f7bcfa41e361add1b4c51dc81463528372b4ec321" + "244de0c541ba00df22b8773cdf4cf898510c867829fa6b4ff11f9627338b9686d905" + "cb7bcdf085080ab842146e0035c808be58cce97827d8926a98bd1ff7c529be3bc14f" + "68c91b2ca4d2f6fc748f56bcf14853b7f8b9aa6d388f0fd82f53fdc4bacf9d9ba10a" + "165f404cf427e199f51bf6773b7c82531e17933f6d8b8d9181e22f8921a2dbb20fc7" + "c8023a87e716e245017c399d0942934f5e085219b3f8d26a196bf8b239438b8e561c" + "28a61ff08872ecb052c5fcb19e2fdbc09565924a50ebee1461c4b414219d4257", + }, + { + "dadcde7c3603ef419d319ba3d50cf00ad57f3e81566fd11b9b6f461cbb9dcb0f", + 338, + "18e1df97abccc91e07dc7b7ffab5ee8919d5610721453176aa2089fb96d9a477e147" + "6f507fa1129f04304e960e8017ff41246cacc0153055fc4b1dc6168a74067ebb077c" + "b5aa80a9df6e8b5b821e906531159668c4c164b9e511d8724aedbe17c1f41da88094" + "17d3c30b79ea5a2e3c961f6bac5436d9af6be24a36eebcb17863fed82c0eb8962339" + "eb612d58659dddd2ea06a120b3a2d8a17050be2de367db25a5bef4290c209bdb4c16" + "c4df5a1fe1ead635169a1c35f0a56bc07bcf6ef0e4c2d8573ed7a3b58030fa268c1a" + "5974b097288f01f34d5a1087946410688016882c6c7621aad680d9a25c7a3e5dbcbb" + "07ffdb7243b91031c08a121b40785e96b7ee46770c760f84aca8f36b7c7da64d25c8" + "f73b4d88ff3acb7eeefc0b75144dffea66d2d1f6b42b905b61929ab3f38538393ba5" + "ca9d3c62c61f46fa63789cac14e4e1d8722bf03cceef6e3de91f783b0072616c", + }, + { + "d184e84a2507fc0f187b640dd5b849a366c0383d9cbdbc6fa30904f054111255", + 141, + "13b8df9c1bcfddd0aa39b3055f52e2bc36562b6677535994b173f07041d141699db4" + "2589d6091ef0e71b645b41ab57577f58c98da966562d24823158f8e1d43b54edea4e" + "61dd66fe8c59ad8405f5a0d9a3eb509a77ae3d8ae4adf926fd3d8d31c3dcccfc1408" + "14541010937024cc554e1daaee1b333a66316e7fbebb07ac8dfb134a918b9090b141" + "68012c4824", + }, + { + "20c19635364a00b151d0168fe5ae03bac6dd7d06030475b40d2e8c577a192f53", + 84, + "e1e96da4b7d8dcc2b316006503a990ea26a5b200cb7a7edfc14f5ce827f06d8d232e" + "c95b1acdc1422ffc16da11d258f0c7b378f026d64c74b2fb41df8bfd3cd30066caec" + "dc6f76c8163de9309d9fd0cf33d54a29", + }, + { + "86cc2c428d469e43fb4ee8d38dffbf5128d20d1659dbc45edf4a855399ca730e", + 319, + "30391840ad14e66c53e1a5aaa03989ff059940b60c44c3b21295a93d023f2e6c7cdc" + "f60208b7d87a7605fb5cee94630d94cad90bc6955328357fa37fea47c09f9cee759c" + "31537187321c7d572e3554eeb90f441a9494575454dfbf8cfd86128da15de9418821" + "ca158856eb84ff6a29a2c8380711e9e6d4955388374fcd3c1ca45b49e0679fc7157f" + "96bc6e4f86ce20a89c12d4449b1ca7056e0b7296fc646f68f6ddbfa6a48e384d63ab" + "68bc75fd69a2add59b8e41c4a0f753935df9a703d7df82a430798b0a67710a780614" + "85a9d15de16f154582082459b4462485ce8a82d35ac6b9498ae40df3a23d5f00e0e8" + "6661cb02c52f677fd374c32969ec63028b5dd2c1d4bce67a6d9f79ba5e7eeb5a2763" + "dc9fe2a05aa2ebaad36aaec2541e343a677fb4e6b6a180eff33c93744a4624f6a79f" + "054c6c9e9c5b6928dbe7ba5fca", + }, + { + "e80eee72a76e6957f7cb7f68c41b92f0ad9aac6e58aa8fc272c1e7364af11c70", + 108, + "3c210ed15889ae938781d2cebd49d4a8007f163ffba1f7669bccdccf6ad5a1418299" + "d5f4348f5cd03b0ba9e6999ab154e46836c3546feb395d17bcc60f23d7ba0e8efe6a" + "a616c00b6bf552fe1cb5e28e3e7bc39dfc20c63ae3901035e91ddd110e43fe59ed74" + "4beeedb6bc1e", + }, + { + "f971bbae97dd8a034835269fb246867de358a889de6de13672e771d6fb4c89b7", + 468, + "64e9a3a99c021df8bea59368cfe1cd3b0a4aca33ffcd5cf6028d9307c0b904b8037d" + "056a3c12803f196f74c4d360a3132452d365922b1157e5b0d76f91fb94bebfdcb4d5" + "0fa23ed5be3d3c5712219e8666debc8abcd5e6c69a542761a6cbbd1b3c0f05248752" + "04b64d2788465f90cb19b6f6da9f8bec6d6e684196e713549ec83e47cbaeff77838a" + "c4936b312562e2de17c970449d49d214ec2597c6d4f642e6c94a613a0c53285abccd" + "7794a3d72241808594fb4e6e4d5d2c310ef1cdcbfd34805ca2408f554797a6cfd49d" + "0f25ed8927f206acb127e6436e1234902489ec2e7f3058e26c0eba80341bc7ad0da8" + "b8bd80bd1b43c9099269e3f8b68445c69b79d8cf5693d4a0d47a44f9e9114dbb3399" + "2d2ea9d3b5b86e4ea57a44a638848de4ac365bb6bb7855305ade62b07ebf0954d70b" + "7c2fb5e6fcc154c7a36fb1756df5f20a84d35696627ebf22d44f40f805c0878ad110" + "bc17dcd66821084ca87902e05bc0afa61161086956b85a6ea900d35c7784d4c361a4" + "3fe294e267d5762408be58962cdb4f45a9c0efd7d2335916df3acb98ccfbcf5ee395" + "30540e5f3d3c5f3326a9a536d7bfa37aae2b143e2499b81bf0670e3a418c26c7dc82" + "b293d9bd182dd6435670514237df88d8286e19ce93e0a0db2790", + }, + { + "b97fd51f4e4eaa40c7a2853010fc46be5be2f43b9520ea0c533b68f728c978a2", + 214, + "ced3a43193caceb269d2517f4ecb892bb7d57d7201869e28e669b0b17d1c44d286e0" + "2734e2210ea9009565832975cc6303b9b6008fe1165b99ae5f1b29962ef042ebad8b" + "676d7433ed2fe0d0d6f4f32b2cb4c519da61552328c2caea799bb2fd907308173a1c" + "d2b798fb0df7d2eaf2ff0be733af74f42889e211843fc80b09952ae7eb246725b91d" + "31c1f7a5503fdf3bc9c269c76519cf2dc3225e862436b587bb74adbad88c773056cf" + "ea3bddb1f6533c01125eeae0986e5c817359912c9d0472bf8320b824ee097f82a8e0" + "5b9f53a5be7d153225de", + }, + { + "f0fecf766e4f7522568b3be71843cce3e5fcb10ea96b1a236c8c0a71c9ad55c9", + 159, + "8aca4de41275f5c4102f66266d70cff1a2d56f58df8d12061c64cb6cd8f616a5bf19" + "c2bb3c91585c695326f561a2d0eb4eef2e202d82dcc9089e4bee82b62a199a11963c" + "d08987d3abd5914def2cdd3c1e4748d46b654f338e3959121e869c18d5327e88090d" + "0ba0ac6762a2b14514cc505af7499f1a22f421dbe978494f9ffe1e88f1c59228f21d" + "a5bc9fcc911d022300a443bca17258bdd6cfbbf52fde61", + }, + { + "5c4f16043c0084bf98499fc7dc4d674ce9c730b7135210acdbf5e41d3dcf317b", + 87, + "01bbc193d0ee2396a7d8267ad63f18149667b31d8f7f48c8bb0c634755febc9ef1a7" + "9e93c475f6cd137ee37d4dc243ea2fdcdc0d098844af2208337b7bbf6930e39e74e2" + "3952ac1a19b4d38b83810a10c3b069e4fafb06", + }, + { + "14b61fc981f7d9449b7b6a2d57eb48cc8f7896f4dced2005291b2a2f38cb4a63", + 358, + "cbc1709a531438d5ead32cea20a9e4ddc0101ec555ab42b2e378145013cc05a97b9e" + "2c43c89bfa63ae5e9e9ce1fc022035c6b68f0a906ee1f53396d9dbe41cb2bc4bfeb1" + "44b005b0f40e0fec872d9c4aca9929ba3cbacd84c58ab43c26f10d345a24692bbd55" + "a76506876768e8e32a461bf160cee953da88920d36ad4aff6eea7126aa6f44a7a6fc" + "e770ce43f0f90a20590bdaad3ffcda30ca8e3700f832c62caa5df030c16bcf74aff4" + "92466f781eb69863a80663535fc154abd7cfdd02eef1019221cf608b9780f807e507" + "fbbf559b1dfe4e971b4d08fe45263a3c697ba90f9f71bec97e12438b4b12f6a84ab6" + "6872b888097089d76c9c2502d9ed2eece6bef8eee1d439782e218f5cc75d38f98860" + "12cdcb4bbe6caf812e97c5a336bcceae38b1109e3243a291ce23d097aaee7d9a711d" + "e6886749a7a6d15d7e7cbc4a51b1b4da9fcf139e4a6fd7dc0bc017db624b17fc9b8f" + "847592ed42467c25ad9fe96acbf20c0ffd18", + }, + { + "47ec7f3a362becbb110867995a0f066a66152603c4d433f11bf51870c67e2864", + 354, + "0636983353c9ea3f75256ed00b70e8b7cfc6f4e4c0ba3aa9a8da59b6e6ad9dfb5bc2" + "c49f48cc0b4237f87dedf34b888e54ecebf1d435bcd4aab72eb4ce39e5262fb68c6f" + "86423dac123bf59e903989eda7df4a982822d0831521403cedcfe9a5bbea648bb2e7" + "ef8cd81442ea5abe468b3ee8b06376ef8099447255c2fdc1b73af37fe0e0b852ffbc" + "9339868db756680db99e6e9837dbd28c39a69f229044ad7ec772524a6e01f679d25f" + "dc2e736a2418e5dfd7c2ab1348d0f821b777c975244c6cfc2fca5c36ccae7cf1d07b" + "190a9d17a088a1276bd096250b92f53b29b6ef88ef69d744b56fb2ec5078cc0b68a9" + "106943ef242b466097b9e29df11eb5cb0c06c29d7917410ba1097215d6aa4dafd90a" + "dff0c3e7221b9e8832613bd9aca8bcc6b2aa7b43acedcbc11aee1b5ba56f77a210be" + "7cf3485ee813e1126c3eeccd8419bbf22c412cad32cc0fc7a73aca4e379651caac3d" + "13d6cf5ca05508fd2d96f3ad94e7", + }, + { + "73778e7f1943646a89d3c78909e0afbe584071ba5230546a39cd73e44e36d78a", + 91, + "6217504a26b3395855eab6ddeb79f2e3490d74b80eff343721150ee0c1c02b071867" + "43589f93c22a03dc5ed29fb5bf592de0a089763e83e5b95f9dd524d66c8da3e04c18" + "14e65e68b2810c1b517648aabc266ad62896c51864a7f4", + }, + { + "35ef6868e750cf0c1d5285992c231d93ec644670fb79cf85324067a9f77fde78", + 185, + "0118b7fb15f927a977e0b330b4fa351aeeec299d6ba090eb16e5114fc4a6749e5915" + "434a123c112697390c96ea2c26dc613eb5c75f5ecfb6c419317426367e34da0ddc6d" + "7b7612cefa70a22fea0025f5186593b22449dab71f90a49f7de7352e54e0c0bd8837" + "e661ca2127c3313a7268cafdd5ccfbf3bdd7c974b0e7551a2d96766579ef8d2e1f37" + "6af74cd1ab62162fc2dc61a8b7ed4163c1caccf20ed73e284da2ed257ec974eee96b" + "502acb2c60a04886465e44debb0317", + }, + }; + + uint8_t hash[SHA3_256_DIGEST_LENGTH]; + + for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { + keccak_256(fromhex(tests[i].data), tests[i].length, hash); + ck_assert_mem_eq(hash, fromhex(tests[i].hash), SHA3_256_DIGEST_LENGTH); + + // Test progressive hashing. + size_t part_len = tests[i].length / 2; + SHA3_CTX ctx = {0}; + keccak_256_Init(&ctx); + keccak_Update(&ctx, fromhex(tests[i].data), part_len); + keccak_Update(&ctx, fromhex(tests[i].data), 0); + keccak_Update(&ctx, fromhex(tests[i].data) + part_len, + tests[i].length - part_len); + keccak_Final(&ctx, hash); + ck_assert_mem_eq(hash, fromhex(tests[i].hash), SHA3_256_DIGEST_LENGTH); + } +} +END_TEST + +// test vectors from +// https://raw.githubusercontent.com/monero-project/monero/master/tests/hash/tests-extra-blake.txt +START_TEST(test_blake256) { + static const struct { + const char *hash; + const char *data; + } tests[] = { + { + "716f6e863f744b9ac22c97ec7b76ea5f5908bc5b2f67c61510bfc4751384ea7a", + "", + }, + { + "e104256a2bc501f459d03fac96b9014f593e22d30f4de525fa680c3aa189eb4f", + "cc", + }, + { + "8f341148be7e354fdf38b693d8c6b4e0bd57301a734f6fd35cd85b8491c3ddcd", + "41fb", + }, + { + "bc334d1069099f10c601883ac6f3e7e9787c6aa53171f76a21923cc5ad3ab937", + "1f877c", + }, + { + "b672a16f53982bab1e77685b71c0a5f6703ffd46a1c834be69f614bd128d658e", + "c1ecfdfc", + }, + { + "d9134b2899057a7d8d320cc99e3e116982bc99d3c69d260a7f1ed3da8be68d99", + "21f134ac57", + }, + { + "637923bd29a35aa3ecbbd2a50549fc32c14cf0fdcaf41c3194dd7414fd224815", + "c6f50bb74e29", + }, + { + "70c092fd5c8c21e9ef4bbc82a5c7819e262a530a748caf285ff0cba891954f1e", + "119713cc83eeef", + }, + { + "fdf092993edbb7a0dc7ca67f04051bbd14481639da0808947aff8bfab5abed4b", + "4a4f202484512526", + }, + { + "6f6fc234bf35beae1a366c44c520c59ad5aa70351b5f5085e21e1fe2bfcee709", + "1f66ab4185ed9b6375", + }, + { + "4fdaf89e2a0e78c000061b59455e0ea93a4445b440e7562c8f0cfa165c93de2e", + "eed7422227613b6f53c9", + }, + { + "d6b780eee9c811f664393dc2c58b5a68c92b3c9fe9ceb70371d33ece63b5787e", + "eaeed5cdffd89dece455f1", + }, + { + "d0015071d3e7ed048c764850d76406eceae52b8e2e6e5a2c3aa92ae880485b34", + "5be43c90f22902e4fe8ed2d3", + }, + { + "9b0207902f9932f7a85c24722e93e31f6ed2c75c406509aa0f2f6d1cab046ce4", + "a746273228122f381c3b46e4f1", + }, + { + "258020d5b04a814f2b72c1c661e1f5a5c395d9799e5eee8b8519cf7300e90cb1", + "3c5871cd619c69a63b540eb5a625", + }, + { + "4adae3b55baa907fefc253365fdd99d8398befd0551ed6bf9a2a2784d3c304d1", + "fa22874bcc068879e8ef11a69f0722", + }, + { + "6dd10d772f8d5b4a96c3c5d30878cd9a1073fa835bfe6d2b924fa64a1fab1711", + "52a608ab21ccdd8a4457a57ede782176", + }, + { + "0b8741ddf2259d3af2901eb1ae354f22836442c965556f5c1eb89501191cb46a", + "82e192e4043ddcd12ecf52969d0f807eed", + }, + { + "f48a754ca8193a82643150ab94038b5dd170b4ebd1e0751b78cfb0a98fa5076a", + "75683dcb556140c522543bb6e9098b21a21e", + }, + { + "5698409ab856b74d9fa5e9b259dfa46001f89041752da424e56e491577b88c86", + "06e4efe45035e61faaf4287b4d8d1f12ca97e5", + }, + }; + + uint8_t hash[BLAKE256_DIGEST_LENGTH]; + + for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { + size_t len = strlen(tests[i].data) / 2; + blake256(fromhex(tests[i].data), len, hash); + ck_assert_mem_eq(hash, fromhex(tests[i].hash), BLAKE256_DIGEST_LENGTH); + + // Test progressive hashing. + size_t part_len = len / 2; + BLAKE256_CTX ctx; + blake256_Init(&ctx); + blake256_Update(&ctx, fromhex(tests[i].data), part_len); + blake256_Update(&ctx, NULL, 0); + blake256_Update(&ctx, fromhex(tests[i].data) + part_len, len - part_len); + blake256_Final(&ctx, hash); + ck_assert_mem_eq(hash, fromhex(tests[i].hash), BLAKE256_DIGEST_LENGTH); + } +} +END_TEST + +// test vectors from +// https://raw.githubusercontent.com/BLAKE2/BLAKE2/master/testvectors/blake2b-kat.txt +START_TEST(test_blake2b) { + static const struct { + const char *msg; + const char *hash; + } tests[] = { + { + "", + "10ebb67700b1868efb4417987acf4690ae9d972fb7a590c2f02871799aaa4786b5e9" + "96e8f0f4eb981fc214b005f42d2ff4233499391653df7aefcbc13fc51568", + }, + { + "000102", + "33d0825dddf7ada99b0e7e307104ad07ca9cfd9692214f1561356315e784f3e5a17e" + "364ae9dbb14cb2036df932b77f4b292761365fb328de7afdc6d8998f5fc1", + }, + { + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021" + "22232425262728292a2b2c2d2e2f3031323334353637", + "f8f3726ac5a26cc80132493a6fedcb0e60760c09cfc84cad178175986819665e7684" + "2d7b9fedf76dddebf5d3f56faaad4477587af21606d396ae570d8e719af2", + }, + { + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021" + "22232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40414243" + "4445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465" + "666768696a6b6c6d6e6f", + "227e3aed8d2cb10b918fcb04f9de3e6d0a57e08476d93759cd7b2ed54a1cbf0239c5" + "28fb04bbf288253e601d3bc38b21794afef90b17094a182cac557745e75f", + }, + }; + + uint8_t key[BLAKE2B_KEY_LENGTH]; + memcpy(key, + fromhex( + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2" + "02122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"), + BLAKE2B_KEY_LENGTH); + + uint8_t digest[BLAKE2B_DIGEST_LENGTH]; + for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { + size_t msg_len = strlen(tests[i].msg) / 2; + blake2b_Key(fromhex(tests[i].msg), msg_len, key, sizeof(key), digest, + sizeof(digest)); + ck_assert_mem_eq(digest, fromhex(tests[i].hash), sizeof(digest)); + + // Test progressive hashing. + size_t part_len = msg_len / 2; + BLAKE2B_CTX ctx; + ck_assert_int_eq(blake2b_InitKey(&ctx, sizeof(digest), key, sizeof(key)), + 0); + ck_assert_int_eq(blake2b_Update(&ctx, fromhex(tests[i].msg), part_len), 0); + ck_assert_int_eq(blake2b_Update(&ctx, NULL, 0), 0); + ck_assert_int_eq(blake2b_Update(&ctx, fromhex(tests[i].msg) + part_len, + msg_len - part_len), + 0); + ck_assert_int_eq(blake2b_Final(&ctx, digest, sizeof(digest)), 0); + ck_assert_mem_eq(digest, fromhex(tests[i].hash), BLAKE2B_DIGEST_LENGTH); + } +} +END_TEST + +// Blake2b-256 personalized, a la ZCash +// Test vectors from https://zips.z.cash/zip-0243 +START_TEST(test_blake2bp) { + static const struct { + const char *msg; + const char *personal; + const char *hash; + } tests[] = { + { + "", + "ZcashPrevoutHash", + "d53a633bbecf82fe9e9484d8a0e727c73bb9e68c96e72dec30144f6a84afa136", + }, + { + "", + "ZcashSequencHash", + "a5f25f01959361ee6eb56a7401210ee268226f6ce764a4f10b7f29e54db37272", + + }, + { + "e7719811893e0000095200ac6551ac636565b2835a0805750200025151", + "ZcashOutputsHash", + "ab6f7f6c5ad6b56357b5f37e16981723db6c32411753e28c175e15589172194a", + }, + { + "0bbe32a598c22adfb48cef72ba5d4287c0cefbacfd8ce195b4963c34a94bba7a1" + "75dae4b090f47a068e227433f9e49d3aa09e356d8d66d0c0121e91a3c4aa3f27fa1b" + "63396e2b41d", + "ZcashPrevoutHash", + "cacf0f5210cce5fa65a59f314292b3111d299e7d9d582753cf61e1e408552ae4", + }}; + + uint8_t digest[32]; + for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { + size_t msg_len = strlen(tests[i].msg) / 2; + + // Test progressive hashing. + size_t part_len = msg_len / 2; + BLAKE2B_CTX ctx; + ck_assert_int_eq( + blake2b_InitPersonal(&ctx, sizeof(digest), tests[i].personal, + strlen(tests[i].personal)), + 0); + ck_assert_int_eq(blake2b_Update(&ctx, fromhex(tests[i].msg), part_len), 0); + ck_assert_int_eq(blake2b_Update(&ctx, NULL, 0), 0); + ck_assert_int_eq(blake2b_Update(&ctx, fromhex(tests[i].msg) + part_len, + msg_len - part_len), + 0); + ck_assert_int_eq(blake2b_Final(&ctx, digest, sizeof(digest)), 0); + ck_assert_mem_eq(digest, fromhex(tests[i].hash), sizeof(digest)); + } +} +END_TEST + +// test vectors from +// https://raw.githubusercontent.com/BLAKE2/BLAKE2/master/testvectors/blake2s-kat.txt +START_TEST(test_blake2s) { + static const struct { + const char *msg; + const char *hash; + } tests[] = { + { + "", + "48a8997da407876b3d79c0d92325ad3b89cbb754d86ab71aee047ad345fd2c49", + }, + { + "000102", + "1d220dbe2ee134661fdf6d9e74b41704710556f2f6e5a091b227697445dbea6b", + }, + { + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021" + "22232425262728292a2b2c2d2e2f3031323334353637", + "2966b3cfae1e44ea996dc5d686cf25fa053fb6f67201b9e46eade85d0ad6b806", + }, + { + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021" + "22232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40414243" + "4445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465" + "666768696a6b6c6d6e6f", + "90a83585717b75f0e9b725e055eeeeb9e7a028ea7e6cbc07b20917ec0363e38c", + }, + }; + + uint8_t key[BLAKE2S_KEY_LENGTH]; + memcpy( + key, + fromhex( + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"), + BLAKE2S_KEY_LENGTH); + + uint8_t digest[BLAKE2S_DIGEST_LENGTH]; + for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { + size_t msg_len = strlen(tests[i].msg) / 2; + blake2s_Key(fromhex(tests[i].msg), msg_len, key, sizeof(key), digest, + sizeof(digest)); + ck_assert_mem_eq(digest, fromhex(tests[i].hash), sizeof(digest)); + + // Test progressive hashing. + size_t part_len = msg_len / 2; + BLAKE2S_CTX ctx; + ck_assert_int_eq(blake2s_InitKey(&ctx, sizeof(digest), key, sizeof(key)), + 0); + ck_assert_int_eq(blake2s_Update(&ctx, fromhex(tests[i].msg), part_len), 0); + ck_assert_int_eq(blake2s_Update(&ctx, NULL, 0), 0); + ck_assert_int_eq(blake2s_Update(&ctx, fromhex(tests[i].msg) + part_len, + msg_len - part_len), + 0); + ck_assert_int_eq(blake2s_Final(&ctx, digest, sizeof(digest)), 0); + ck_assert_mem_eq(digest, fromhex(tests[i].hash), BLAKE2S_DIGEST_LENGTH); + } +} +END_TEST + +#include + +START_TEST(test_chacha_drbg) { + char entropy[] = + "06032cd5eed33f39265f49ecb142c511da9aff2af71203bffaf34a9ca5bd9c0d"; + char nonce[] = "0e66f71edc43e42a45ad3c6fc6cdc4df"; + char reseed[] = + "01920a4e669ed3a85ae8a33b35a74ad7fb2a6bb4cf395ce00334a9c9a5a5d552"; + char expected[] = + "e172c5d18f3e8c77e9f66f9e1c24560772117161a9a0a237ab490b0769ad5d910f5dfb36" + "22edc06c18be0495c52588b200893d90fd80ff2149ead0c45d062c90f5890149c0f9591c" + "41bf4110865129a0fe524f210cca1340bd16f71f57906946cbaaf1fa863897d70d203b5a" + "f9996f756eec08861ee5875f9d915adcddc38719"; + uint8_t result[128]; + uint8_t null_bytes[128] = {0}; + + uint8_t nonce_bytes[16]; + memcpy(nonce_bytes, fromhex(nonce), sizeof(nonce_bytes)); + CHACHA_DRBG_CTX ctx; + chacha_drbg_init(&ctx, fromhex(entropy), strlen(entropy) / 2, nonce_bytes, + strlen(nonce) / 2); + chacha_drbg_reseed(&ctx, fromhex(reseed), strlen(reseed) / 2, NULL, 0); + chacha_drbg_generate(&ctx, result, sizeof(result)); + chacha_drbg_generate(&ctx, result, sizeof(result)); + ck_assert_mem_eq(result, fromhex(expected), sizeof(result)); + + for (size_t i = 0; i <= sizeof(result); ++i) { + chacha_drbg_init(&ctx, fromhex(entropy), strlen(entropy) / 2, nonce_bytes, + strlen(nonce) / 2); + chacha_drbg_reseed(&ctx, fromhex(reseed), strlen(reseed) / 2, NULL, 0); + chacha_drbg_generate(&ctx, result, sizeof(result) - 13); + memset(result, 0, sizeof(result)); + chacha_drbg_generate(&ctx, result, i); + ck_assert_mem_eq(result, fromhex(expected), i); + ck_assert_mem_eq(result + i, null_bytes, sizeof(result) - i); + } +} +END_TEST + +START_TEST(test_pbkdf2_hmac_sha256) { + uint8_t k[64]; + + // test vectors from + // https://stackoverflow.com/questions/5130513/pbkdf2-hmac-sha2-test-vectors + pbkdf2_hmac_sha256((const uint8_t *)"password", 8, (const uint8_t *)"salt", 4, + 1, k, 32); + ck_assert_mem_eq( + k, + fromhex( + "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b"), + 32); + + pbkdf2_hmac_sha256((const uint8_t *)"password", 8, (const uint8_t *)"salt", 4, + 2, k, 32); + ck_assert_mem_eq( + k, + fromhex( + "ae4d0c95af6b46d32d0adff928f06dd02a303f8ef3c251dfd6e2d85a95474c43"), + 32); + + pbkdf2_hmac_sha256((const uint8_t *)"password", 8, (const uint8_t *)"salt", 4, + 4096, k, 32); + ck_assert_mem_eq( + k, + fromhex( + "c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a"), + 32); + + pbkdf2_hmac_sha256((const uint8_t *)"passwordPASSWORDpassword", 3 * 8, + (const uint8_t *)"saltSALTsaltSALTsaltSALTsaltSALTsalt", + 9 * 4, 4096, k, 40); + ck_assert_mem_eq(k, + fromhex("348c89dbcbd32b2f32d814b8116e84cf2b17347ebc1800181c4" + "e2a1fb8dd53e1c635518c7dac47e9"), + 40); + + pbkdf2_hmac_sha256((const uint8_t *)"pass\x00word", 9, + (const uint8_t *)"sa\x00lt", 5, 4096, k, 16); + ck_assert_mem_eq(k, fromhex("89b69d0516f829893c696226650a8687"), 16); + + // test vector from https://tools.ietf.org/html/rfc7914.html#section-11 + pbkdf2_hmac_sha256((const uint8_t *)"passwd", 6, (const uint8_t *)"salt", 4, + 1, k, 64); + ck_assert_mem_eq( + k, + fromhex( + "55ac046e56e3089fec1691c22544b605f94185216dde0465e68b9d57c20dacbc49ca" + "9cccf179b645991664b39d77ef317c71b845b1e30bd509112041d3a19783"), + 64); +} +END_TEST + +// test vectors from +// http://stackoverflow.com/questions/15593184/pbkdf2-hmac-sha-512-test-vectors +START_TEST(test_pbkdf2_hmac_sha512) { + uint8_t k[64]; + + pbkdf2_hmac_sha512((uint8_t *)"password", 8, (const uint8_t *)"salt", 4, 1, k, + 64); + ck_assert_mem_eq( + k, + fromhex( + "867f70cf1ade02cff3752599a3a53dc4af34c7a669815ae5d513554e1c8cf252c02d" + "470a285a0501bad999bfe943c08f050235d7d68b1da55e63f73b60a57fce"), + 64); + + pbkdf2_hmac_sha512((uint8_t *)"password", 8, (const uint8_t *)"salt", 4, 2, k, + 64); + ck_assert_mem_eq( + k, + fromhex( + "e1d9c16aa681708a45f5c7c4e215ceb66e011a2e9f0040713f18aefdb866d53cf76c" + "ab2868a39b9f7840edce4fef5a82be67335c77a6068e04112754f27ccf4e"), + 64); + + pbkdf2_hmac_sha512((uint8_t *)"password", 8, (const uint8_t *)"salt", 4, 4096, + k, 64); + ck_assert_mem_eq( + k, + fromhex( + "d197b1b33db0143e018b12f3d1d1479e6cdebdcc97c5c0f87f6902e072f457b5143f" + "30602641b3d55cd335988cb36b84376060ecd532e039b742a239434af2d5"), + 64); + + pbkdf2_hmac_sha512((uint8_t *)"passwordPASSWORDpassword", 3 * 8, + (const uint8_t *)"saltSALTsaltSALTsaltSALTsaltSALTsalt", + 9 * 4, 4096, k, 64); + ck_assert_mem_eq( + k, + fromhex( + "8c0511f4c6e597c6ac6315d8f0362e225f3c501495ba23b868c005174dc4ee71115b" + "59f9e60cd9532fa33e0f75aefe30225c583a186cd82bd4daea9724a3d3b8"), + 64); +} +END_TEST + +START_TEST(test_hmac_drbg) { + char entropy[] = + "06032cd5eed33f39265f49ecb142c511da9aff2af71203bffaf34a9ca5bd9c0d"; + char nonce[] = "0e66f71edc43e42a45ad3c6fc6cdc4df"; + char reseed[] = + "01920a4e669ed3a85ae8a33b35a74ad7fb2a6bb4cf395ce00334a9c9a5a5d552"; + char expected[] = + "76fc79fe9b50beccc991a11b5635783a83536add03c157fb30645e611c2898bb2b1bc215" + "000209208cd506cb28da2a51bdb03826aaf2bd2335d576d519160842e7158ad0949d1a9e" + "c3e66ea1b1a064b005de914eac2e9d4f2d72a8616a80225422918250ff66a41bd2f864a6" + "a38cc5b6499dc43f7f2bd09e1e0f8f5885935124"; + uint8_t result[128]; + uint8_t null_bytes[128] = {0}; + + uint8_t nonce_bytes[16]; + memcpy(nonce_bytes, fromhex(nonce), sizeof(nonce_bytes)); + HMAC_DRBG_CTX ctx; + hmac_drbg_init(&ctx, fromhex(entropy), strlen(entropy) / 2, nonce_bytes, + strlen(nonce) / 2); + hmac_drbg_reseed(&ctx, fromhex(reseed), strlen(reseed) / 2, NULL, 0); + hmac_drbg_generate(&ctx, result, sizeof(result)); + hmac_drbg_generate(&ctx, result, sizeof(result)); + ck_assert_mem_eq(result, fromhex(expected), sizeof(result)); + + for (size_t i = 0; i <= sizeof(result); ++i) { + hmac_drbg_init(&ctx, fromhex(entropy), strlen(entropy) / 2, nonce_bytes, + strlen(nonce) / 2); + hmac_drbg_reseed(&ctx, fromhex(reseed), strlen(reseed) / 2, NULL, 0); + hmac_drbg_generate(&ctx, result, sizeof(result) - 13); + memset(result, 0, sizeof(result)); + hmac_drbg_generate(&ctx, result, i); + ck_assert_mem_eq(result, fromhex(expected), i); + ck_assert_mem_eq(result + i, null_bytes, sizeof(result) - i); + } +} +END_TEST + +START_TEST(test_mnemonic) { + static const char *vectors[] = { + "00000000000000000000000000000000", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon " + "abandon abandon about", + "c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a698" + "7599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04", + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", + "legal winner thank year wave sausage worth useful legal winner thank " + "yellow", + "2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6fa457fe12" + "96106559a3c80937a1c1069be3a3a5bd381ee6260e8d9739fce1f607", + "80808080808080808080808080808080", + "letter advice cage absurd amount doctor acoustic avoid letter advice " + "cage above", + "d71de856f81a8acc65e6fc851a38d4d7ec216fd0796d0a6827a3ad6ed5511a30fa280f12" + "eb2e47ed2ac03b5c462a0358d18d69fe4f985ec81778c1b370b652a8", + "ffffffffffffffffffffffffffffffff", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong", + "ac27495480225222079d7be181583751e86f571027b0497b5b5d11218e0a8a1333257291" + "7f0f8e5a589620c6f15b11c61dee327651a14c34e18231052e48c069", + "000000000000000000000000000000000000000000000000", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon " + "abandon abandon abandon abandon abandon abandon abandon abandon agent", + "035895f2f481b1b0f01fcf8c289c794660b289981a78f8106447707fdd9666ca06da5a9a" + "565181599b79f53b844d8a71dd9f439c52a3d7b3e8a79c906ac845fa", + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", + "legal winner thank year wave sausage worth useful legal winner thank " + "year wave sausage worth useful legal will", + "f2b94508732bcbacbcc020faefecfc89feafa6649a5491b8c952cede496c214a0c7b3c39" + "2d168748f2d4a612bada0753b52a1c7ac53c1e93abd5c6320b9e95dd", + "808080808080808080808080808080808080808080808080", + "letter advice cage absurd amount doctor acoustic avoid letter advice " + "cage absurd amount doctor acoustic avoid letter always", + "107d7c02a5aa6f38c58083ff74f04c607c2d2c0ecc55501dadd72d025b751bc27fe913ff" + "b796f841c49b1d33b610cf0e91d3aa239027f5e99fe4ce9e5088cd65", + "ffffffffffffffffffffffffffffffffffffffffffffffff", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo " + "when", + "0cd6e5d827bb62eb8fc1e262254223817fd068a74b5b449cc2f667c3f1f985a76379b433" + "48d952e2265b4cd129090758b3e3c2c49103b5051aac2eaeb890a528", + "0000000000000000000000000000000000000000000000000000000000000000", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon " + "abandon abandon abandon abandon abandon abandon abandon abandon abandon " + "abandon abandon abandon abandon abandon art", + "bda85446c68413707090a52022edd26a1c9462295029f2e60cd7c4f2bbd3097170af7a4d" + "73245cafa9c3cca8d561a7c3de6f5d4a10be8ed2a5e608d68f92fcc8", + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", + "legal winner thank year wave sausage worth useful legal winner thank " + "year wave sausage worth useful legal winner thank year wave sausage " + "worth title", + "bc09fca1804f7e69da93c2f2028eb238c227f2e9dda30cd63699232578480a4021b146ad" + "717fbb7e451ce9eb835f43620bf5c514db0f8add49f5d121449d3e87", + "8080808080808080808080808080808080808080808080808080808080808080", + "letter advice cage absurd amount doctor acoustic avoid letter advice " + "cage absurd amount doctor acoustic avoid letter advice cage absurd " + "amount doctor acoustic bless", + "c0c519bd0e91a2ed54357d9d1ebef6f5af218a153624cf4f2da911a0ed8f7a09e2ef61af" + "0aca007096df430022f7a2b6fb91661a9589097069720d015e4e982f", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo " + "zoo zoo zoo zoo zoo vote", + "dd48c104698c30cfe2b6142103248622fb7bb0ff692eebb00089b32d22484e1613912f0a" + "5b694407be899ffd31ed3992c456cdf60f5d4564b8ba3f05a69890ad", + "77c2b00716cec7213839159e404db50d", + "jelly better achieve collect unaware mountain thought cargo oxygen act " + "hood bridge", + "b5b6d0127db1a9d2226af0c3346031d77af31e918dba64287a1b44b8ebf63cdd52676f67" + "2a290aae502472cf2d602c051f3e6f18055e84e4c43897fc4e51a6ff", + "b63a9c59a6e641f288ebc103017f1da9f8290b3da6bdef7b", + "renew stay biology evidence goat welcome casual join adapt armor " + "shuffle fault little machine walk stumble urge swap", + "9248d83e06f4cd98debf5b6f010542760df925ce46cf38a1bdb4e4de7d21f5c39366941c" + "69e1bdbf2966e0f6e6dbece898a0e2f0a4c2b3e640953dfe8b7bbdc5", + "3e141609b97933b66a060dcddc71fad1d91677db872031e85f4c015c5e7e8982", + "dignity pass list indicate nasty swamp pool script soccer toe leaf " + "photo multiply desk host tomato cradle drill spread actor shine dismiss " + "champion exotic", + "ff7f3184df8696d8bef94b6c03114dbee0ef89ff938712301d27ed8336ca89ef9635da20" + "af07d4175f2bf5f3de130f39c9d9e8dd0472489c19b1a020a940da67", + "0460ef47585604c5660618db2e6a7e7f", + "afford alter spike radar gate glance object seek swamp infant panel " + "yellow", + "65f93a9f36b6c85cbe634ffc1f99f2b82cbb10b31edc7f087b4f6cb9e976e9faf76ff41f" + "8f27c99afdf38f7a303ba1136ee48a4c1e7fcd3dba7aa876113a36e4", + "72f60ebac5dd8add8d2a25a797102c3ce21bc029c200076f", + "indicate race push merry suffer human cruise dwarf pole review arch " + "keep canvas theme poem divorce alter left", + "3bbf9daa0dfad8229786ace5ddb4e00fa98a044ae4c4975ffd5e094dba9e0bb289349dbe" + "2091761f30f382d4e35c4a670ee8ab50758d2c55881be69e327117ba", + "2c85efc7f24ee4573d2b81a6ec66cee209b2dcbd09d8eddc51e0215b0b68e416", + "clutch control vehicle tonight unusual clog visa ice plunge glimpse " + "recipe series open hour vintage deposit universe tip job dress radar " + "refuse motion taste", + "fe908f96f46668b2d5b37d82f558c77ed0d69dd0e7e043a5b0511c48c2f1064694a956f8" + "6360c93dd04052a8899497ce9e985ebe0c8c52b955e6ae86d4ff4449", + "eaebabb2383351fd31d703840b32e9e2", + "turtle front uncle idea crush write shrug there lottery flower risk " + "shell", + "bdfb76a0759f301b0b899a1e3985227e53b3f51e67e3f2a65363caedf3e32fde42a66c40" + "4f18d7b05818c95ef3ca1e5146646856c461c073169467511680876c", + "7ac45cfe7722ee6c7ba84fbc2d5bd61b45cb2fe5eb65aa78", + "kiss carry display unusual confirm curtain upgrade antique rotate hello " + "void custom frequent obey nut hole price segment", + "ed56ff6c833c07982eb7119a8f48fd363c4a9b1601cd2de736b01045c5eb8ab4f57b0794" + "03485d1c4924f0790dc10a971763337cb9f9c62226f64fff26397c79", + "4fa1a8bc3e6d80ee1316050e862c1812031493212b7ec3f3bb1b08f168cabeef", + "exile ask congress lamp submit jacket era scheme attend cousin alcohol " + "catch course end lucky hurt sentence oven short ball bird grab wing top", + "095ee6f817b4c2cb30a5a797360a81a40ab0f9a4e25ecd672a3f58a0b5ba0687c096a6b1" + "4d2c0deb3bdefce4f61d01ae07417d502429352e27695163f7447a8c", + "18ab19a9f54a9274f03e5209a2ac8a91", + "board flee heavy tunnel powder denial science ski answer betray cargo " + "cat", + "6eff1bb21562918509c73cb990260db07c0ce34ff0e3cc4a8cb3276129fbcb300bddfe00" + "5831350efd633909f476c45c88253276d9fd0df6ef48609e8bb7dca8", + "18a2e1d81b8ecfb2a333adcb0c17a5b9eb76cc5d05db91a4", + "board blade invite damage undo sun mimic interest slam gaze truly " + "inherit resist great inject rocket museum chief", + "f84521c777a13b61564234bf8f8b62b3afce27fc4062b51bb5e62bdfecb23864ee6ecf07" + "c1d5a97c0834307c5c852d8ceb88e7c97923c0a3b496bedd4e5f88a9", + "15da872c95a13dd738fbf50e427583ad61f18fd99f628c417a61cf8343c90419", + "beyond stage sleep clip because twist token leaf atom beauty genius " + "food business side grid unable middle armed observe pair crouch tonight " + "away coconut", + "b15509eaa2d09d3efd3e006ef42151b30367dc6e3aa5e44caba3fe4d3e352e65101fbdb8" + "6a96776b91946ff06f8eac594dc6ee1d3e82a42dfe1b40fef6bcc3fd", + 0, + 0, + 0, + }; + + const char **a, **b, **c, *m; + uint8_t seed[64]; + + a = vectors; + b = vectors + 1; + c = vectors + 2; + #define TC_BUF_SIZE 308 + char buf[TC_BUF_SIZE]; + + while (*a && *b && *c) { + m = mnemonic_from_data(fromhex(*a), strlen(*a) / 2, buf, TC_BUF_SIZE); + ck_assert_str_eq(m, *b); + mnemonic_to_seed(m, "TREZOR", seed, 0); + ck_assert_mem_eq(seed, fromhex(*c), strlen(*c) / 2); +#if USE_BIP39_CACHE + // try second time to check whether caching results work + mnemonic_to_seed(m, "TREZOR", seed, 0); + ck_assert_mem_eq(seed, fromhex(*c), strlen(*c) / 2); +#endif + a += 3; + b += 3; + c += 3; + memzero(buf, TC_BUF_SIZE ); +#undef TC_BUF_SIZE + } +} +END_TEST + +START_TEST(test_mnemonic_check) { + static const char *vectors_ok[] = { + "abandon abandon abandon abandon abandon abandon abandon abandon abandon " + "abandon abandon about", + "legal winner thank year wave sausage worth useful legal winner thank " + "yellow", + "letter advice cage absurd amount doctor acoustic avoid letter advice " + "cage above", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon " + "abandon abandon abandon abandon abandon abandon abandon abandon agent", + "legal winner thank year wave sausage worth useful legal winner thank " + "year wave sausage worth useful legal will", + "letter advice cage absurd amount doctor acoustic avoid letter advice " + "cage absurd amount doctor acoustic avoid letter always", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo " + "when", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon " + "abandon abandon abandon abandon abandon abandon abandon abandon abandon " + "abandon abandon abandon abandon abandon art", + "legal winner thank year wave sausage worth useful legal winner thank " + "year wave sausage worth useful legal winner thank year wave sausage " + "worth title", + "letter advice cage absurd amount doctor acoustic avoid letter advice " + "cage absurd amount doctor acoustic avoid letter advice cage absurd " + "amount doctor acoustic bless", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo " + "zoo zoo zoo zoo zoo vote", + "jelly better achieve collect unaware mountain thought cargo oxygen act " + "hood bridge", + "renew stay biology evidence goat welcome casual join adapt armor " + "shuffle fault little machine walk stumble urge swap", + "dignity pass list indicate nasty swamp pool script soccer toe leaf " + "photo multiply desk host tomato cradle drill spread actor shine dismiss " + "champion exotic", + "afford alter spike radar gate glance object seek swamp infant panel " + "yellow", + "indicate race push merry suffer human cruise dwarf pole review arch " + "keep canvas theme poem divorce alter left", + "clutch control vehicle tonight unusual clog visa ice plunge glimpse " + "recipe series open hour vintage deposit universe tip job dress radar " + "refuse motion taste", + "turtle front uncle idea crush write shrug there lottery flower risk " + "shell", + "kiss carry display unusual confirm curtain upgrade antique rotate hello " + "void custom frequent obey nut hole price segment", + "exile ask congress lamp submit jacket era scheme attend cousin alcohol " + "catch course end lucky hurt sentence oven short ball bird grab wing top", + "board flee heavy tunnel powder denial science ski answer betray cargo " + "cat", + "board blade invite damage undo sun mimic interest slam gaze truly " + "inherit resist great inject rocket museum chief", + "beyond stage sleep clip because twist token leaf atom beauty genius " + "food business side grid unable middle armed observe pair crouch tonight " + "away coconut", + 0, + }; + static const char *vectors_fail[] = { + "above abandon abandon abandon abandon abandon abandon abandon abandon " + "abandon abandon about", + "above winner thank year wave sausage worth useful legal winner thank " + "yellow", + "above advice cage absurd amount doctor acoustic avoid letter advice " + "cage above", + "above zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong", + "above abandon abandon abandon abandon abandon abandon abandon abandon " + "abandon abandon abandon abandon abandon abandon abandon abandon agent", + "above winner thank year wave sausage worth useful legal winner thank " + "year wave sausage worth useful legal will", + "above advice cage absurd amount doctor acoustic avoid letter advice " + "cage absurd amount doctor acoustic avoid letter always", + "above zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo " + "when", + "above abandon abandon abandon abandon abandon abandon abandon abandon " + "abandon abandon abandon abandon abandon abandon abandon abandon abandon " + "abandon abandon abandon abandon abandon art", + "above winner thank year wave sausage worth useful legal winner thank " + "year wave sausage worth useful legal winner thank year wave sausage " + "worth title", + "above advice cage absurd amount doctor acoustic avoid letter advice " + "cage absurd amount doctor acoustic avoid letter advice cage absurd " + "amount doctor acoustic bless", + "above zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo " + "zoo zoo zoo zoo zoo zoo vote", + "above better achieve collect unaware mountain thought cargo oxygen act " + "hood bridge", + "above stay biology evidence goat welcome casual join adapt armor " + "shuffle fault little machine walk stumble urge swap", + "above pass list indicate nasty swamp pool script soccer toe leaf photo " + "multiply desk host tomato cradle drill spread actor shine dismiss " + "champion exotic", + "above alter spike radar gate glance object seek swamp infant panel " + "yellow", + "above race push merry suffer human cruise dwarf pole review arch keep " + "canvas theme poem divorce alter left", + "above control vehicle tonight unusual clog visa ice plunge glimpse " + "recipe series open hour vintage deposit universe tip job dress radar " + "refuse motion taste", + "above front uncle idea crush write shrug there lottery flower risk " + "shell", + "above carry display unusual confirm curtain upgrade antique rotate " + "hello void custom frequent obey nut hole price segment", + "above ask congress lamp submit jacket era scheme attend cousin alcohol " + "catch course end lucky hurt sentence oven short ball bird grab wing top", + "above flee heavy tunnel powder denial science ski answer betray cargo " + "cat", + "above blade invite damage undo sun mimic interest slam gaze truly " + "inherit resist great inject rocket museum chief", + "above stage sleep clip because twist token leaf atom beauty genius food " + "business side grid unable middle armed observe pair crouch tonight away " + "coconut", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon " + "abandon about", + "winner thank year wave sausage worth useful legal winner thank yellow", + "advice cage absurd amount doctor acoustic avoid letter advice cage " + "above", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon " + "abandon abandon abandon abandon abandon abandon abandon agent", + "winner thank year wave sausage worth useful legal winner thank year " + "wave sausage worth useful legal will", + "advice cage absurd amount doctor acoustic avoid letter advice cage " + "absurd amount doctor acoustic avoid letter always", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon " + "abandon abandon abandon abandon abandon abandon abandon abandon abandon " + "abandon abandon abandon abandon art", + "winner thank year wave sausage worth useful legal winner thank year " + "wave sausage worth useful legal winner thank year wave sausage worth " + "title", + "advice cage absurd amount doctor acoustic avoid letter advice cage " + "absurd amount doctor acoustic avoid letter advice cage absurd amount " + "doctor acoustic bless", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo " + "zoo zoo zoo zoo vote", + "better achieve collect unaware mountain thought cargo oxygen act hood " + "bridge", + "stay biology evidence goat welcome casual join adapt armor shuffle " + "fault little machine walk stumble urge swap", + "pass list indicate nasty swamp pool script soccer toe leaf photo " + "multiply desk host tomato cradle drill spread actor shine dismiss " + "champion exotic", + "alter spike radar gate glance object seek swamp infant panel yellow", + "race push merry suffer human cruise dwarf pole review arch keep canvas " + "theme poem divorce alter left", + "control vehicle tonight unusual clog visa ice plunge glimpse recipe " + "series open hour vintage deposit universe tip job dress radar refuse " + "motion taste", + "front uncle idea crush write shrug there lottery flower risk shell", + "carry display unusual confirm curtain upgrade antique rotate hello void " + "custom frequent obey nut hole price segment", + "ask congress lamp submit jacket era scheme attend cousin alcohol catch " + "course end lucky hurt sentence oven short ball bird grab wing top", + "flee heavy tunnel powder denial science ski answer betray cargo cat", + "blade invite damage undo sun mimic interest slam gaze truly inherit " + "resist great inject rocket museum chief", + "stage sleep clip because twist token leaf atom beauty genius food " + "business side grid unable middle armed observe pair crouch tonight away " + "coconut", + 0, + }; + + const char **m; + int r; + m = vectors_ok; + while (*m) { + r = mnemonic_check(*m); + ck_assert_int_eq(r, 1); + m++; + } + m = vectors_fail; + while (*m) { + r = mnemonic_check(*m); + ck_assert_int_eq(r, 0); + m++; + } +} +END_TEST + +START_TEST(test_mnemonic_to_bits) { + static const char *vectors[] = { + "00000000000000000000000000000000", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon " + "abandon abandon about", + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", + "legal winner thank year wave sausage worth useful legal winner thank " + "yellow", + "80808080808080808080808080808080", + "letter advice cage absurd amount doctor acoustic avoid letter advice " + "cage above", + "ffffffffffffffffffffffffffffffff", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong", + "000000000000000000000000000000000000000000000000", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon " + "abandon abandon abandon abandon abandon abandon abandon abandon agent", + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", + "legal winner thank year wave sausage worth useful legal winner thank " + "year wave sausage worth useful legal will", + "808080808080808080808080808080808080808080808080", + "letter advice cage absurd amount doctor acoustic avoid letter advice " + "cage absurd amount doctor acoustic avoid letter always", + "ffffffffffffffffffffffffffffffffffffffffffffffff", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo " + "when", + "0000000000000000000000000000000000000000000000000000000000000000", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon " + "abandon abandon abandon abandon abandon abandon abandon abandon abandon " + "abandon abandon abandon abandon abandon art", + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", + "legal winner thank year wave sausage worth useful legal winner thank " + "year wave sausage worth useful legal winner thank year wave sausage " + "worth title", + "8080808080808080808080808080808080808080808080808080808080808080", + "letter advice cage absurd amount doctor acoustic avoid letter advice " + "cage absurd amount doctor acoustic avoid letter advice cage absurd " + "amount doctor acoustic bless", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo " + "zoo zoo zoo zoo zoo vote", + "77c2b00716cec7213839159e404db50d", + "jelly better achieve collect unaware mountain thought cargo oxygen act " + "hood bridge", + "b63a9c59a6e641f288ebc103017f1da9f8290b3da6bdef7b", + "renew stay biology evidence goat welcome casual join adapt armor " + "shuffle fault little machine walk stumble urge swap", + "3e141609b97933b66a060dcddc71fad1d91677db872031e85f4c015c5e7e8982", + "dignity pass list indicate nasty swamp pool script soccer toe leaf " + "photo multiply desk host tomato cradle drill spread actor shine dismiss " + "champion exotic", + "0460ef47585604c5660618db2e6a7e7f", + "afford alter spike radar gate glance object seek swamp infant panel " + "yellow", + "72f60ebac5dd8add8d2a25a797102c3ce21bc029c200076f", + "indicate race push merry suffer human cruise dwarf pole review arch " + "keep canvas theme poem divorce alter left", + "2c85efc7f24ee4573d2b81a6ec66cee209b2dcbd09d8eddc51e0215b0b68e416", + "clutch control vehicle tonight unusual clog visa ice plunge glimpse " + "recipe series open hour vintage deposit universe tip job dress radar " + "refuse motion taste", + "eaebabb2383351fd31d703840b32e9e2", + "turtle front uncle idea crush write shrug there lottery flower risk " + "shell", + "7ac45cfe7722ee6c7ba84fbc2d5bd61b45cb2fe5eb65aa78", + "kiss carry display unusual confirm curtain upgrade antique rotate hello " + "void custom frequent obey nut hole price segment", + "4fa1a8bc3e6d80ee1316050e862c1812031493212b7ec3f3bb1b08f168cabeef", + "exile ask congress lamp submit jacket era scheme attend cousin alcohol " + "catch course end lucky hurt sentence oven short ball bird grab wing top", + "18ab19a9f54a9274f03e5209a2ac8a91", + "board flee heavy tunnel powder denial science ski answer betray cargo " + "cat", + "18a2e1d81b8ecfb2a333adcb0c17a5b9eb76cc5d05db91a4", + "board blade invite damage undo sun mimic interest slam gaze truly " + "inherit resist great inject rocket museum chief", + "15da872c95a13dd738fbf50e427583ad61f18fd99f628c417a61cf8343c90419", + "beyond stage sleep clip because twist token leaf atom beauty genius " + "food business side grid unable middle armed observe pair crouch tonight " + "away coconut", + 0, + 0, + }; + + const char **a, **b; + uint8_t mnemonic_bits[64]; + + a = vectors; + b = vectors + 1; + while (*a && *b) { + int mnemonic_bits_len = mnemonic_to_bits(*b, mnemonic_bits); + ck_assert_int_eq(mnemonic_bits_len % 33, 0); + mnemonic_bits_len = mnemonic_bits_len * 4 / 33; + ck_assert_uint_eq((size_t)mnemonic_bits_len, strlen(*a) / 2); + ck_assert_mem_eq(mnemonic_bits, fromhex(*a), mnemonic_bits_len); + a += 2; + b += 2; + } +} +END_TEST + +START_TEST(test_mnemonic_find_word) { + ck_assert_int_eq(-1, mnemonic_find_word("aaaa")); + ck_assert_int_eq(-1, mnemonic_find_word("zzzz")); + for (int i = 0; i < BIP39_WORD_COUNT; i++) { + const char *word = mnemonic_get_word(i); + int index = mnemonic_find_word(word); + ck_assert_int_eq(i, index); + } +} +END_TEST + +START_TEST(test_slip39_get_word) { + static const struct { + const int index; + const char *expected_word; + } vectors[] = {{573, "member"}, + {0, "academic"}, + {1023, "zero"}, + {245, "drove"}, + {781, "satoshi"}}; + for (size_t i = 0; i < (sizeof(vectors) / sizeof(*vectors)); i++) { + const char *a = get_word(vectors[i].index); + ck_assert_str_eq(a, vectors[i].expected_word); + } +} +END_TEST + +START_TEST(test_slip39_word_index) { + uint16_t index; + static const struct { + const char *word; + bool expected_result; + uint16_t expected_index; + } vectors[] = {{"academic", true, 0}, + {"zero", true, 1023}, + {"drove", true, 245}, + {"satoshi", true, 781}, + {"member", true, 573}, + // 9999 value is never checked since the word is not in list + {"fakeword", false, 9999}}; + for (size_t i = 0; i < (sizeof(vectors) / sizeof(*vectors)); i++) { + bool result = word_index(&index, vectors[i].word, strlen(vectors[i].word)); + ck_assert_int_eq(result, vectors[i].expected_result); + if (result) { + ck_assert_uint_eq(index, vectors[i].expected_index); + } + } +} +END_TEST + +START_TEST(test_slip39_word_completion_mask) { + static const struct { + const uint16_t prefix; + const uint16_t expected_mask; + } vectors[] = { + {12, 0xFD}, // 011111101 + {21, 0xF8}, // 011111000 + {75, 0xAD}, // 010101101 + {4, 0x1F7}, // 111110111 + {738, 0x6D}, // 001101101 + {9, 0x6D}, // 001101101 + {0, 0x1FF}, // 111111111 + {10, 0x00}, // 000000000 + {255, 0x00}, // 000000000 + {203, 0x00}, // 000000000 + {9999, 0x00}, // 000000000 + {20000, 0x00}, // 000000000 + }; + for (size_t i = 0; i < (sizeof(vectors) / sizeof(*vectors)); i++) { + uint16_t mask = slip39_word_completion_mask(vectors[i].prefix); + ck_assert_uint_eq(mask, vectors[i].expected_mask); + } +} +END_TEST + +START_TEST(test_slip39_sequence_to_word) { + static const struct { + const uint16_t prefix; + const char *expected_word; + } vectors[] = { + {7945, "swimming"}, {646, "pipeline"}, {5, "laden"}, {34, "fiber"}, + {62, "ocean"}, {0, "academic"}, {10, NULL}, {255, NULL}, + {203, NULL}, {9999, NULL}, {20000, NULL}, + }; + for (size_t i = 0; i < (sizeof(vectors) / sizeof(*vectors)); i++) { + const char *word = button_sequence_to_word(vectors[i].prefix); + if (vectors[i].expected_word != NULL) { + ck_assert_str_eq(word, vectors[i].expected_word); + } else { + ck_assert_ptr_eq(word, NULL); + } + } +} +END_TEST + +START_TEST(test_slip39_word_completion) { + const char t9[] = {1, 1, 2, 2, 3, 3, 4, 4, 4, 4, 5, 5, 5, + 6, 6, 6, 6, 7, 7, 8, 8, 8, 9, 9, 9, 9}; + for (size_t i = 0; i < WORDS_COUNT; ++i) { + const char *word = slip39_wordlist[i]; + uint16_t prefix = t9[word[0] - 'a']; + for (size_t j = 1; j < 4; ++j) { + uint16_t mask = slip39_word_completion_mask(prefix); + uint8_t next = t9[word[j] - 'a']; + ck_assert_uint_ne(mask & (1 << (next - 1)), 0); + prefix = prefix * 10 + next; + } + ck_assert_str_eq(button_sequence_to_word(prefix), word); + } +} +END_TEST + +START_TEST(test_shamir) { +#define SHAMIR_MAX_COUNT 16 + static const struct { + const uint8_t result[SHAMIR_MAX_LEN]; + uint8_t result_index; + const uint8_t share_indices[SHAMIR_MAX_COUNT]; + const uint8_t share_values[SHAMIR_MAX_COUNT][SHAMIR_MAX_LEN]; + uint8_t share_count; + size_t len; + bool ret; + } vectors[] = {{{7, 151, 168, 57, 186, 104, 218, 21, 209, 96, 106, + 152, 252, 35, 210, 208, 43, 47, 13, 21, 142, 122, + 24, 42, 149, 192, 95, 24, 240, 24, 148, 110}, + 0, + {2}, + { + {7, 151, 168, 57, 186, 104, 218, 21, 209, 96, 106, + 152, 252, 35, 210, 208, 43, 47, 13, 21, 142, 122, + 24, 42, 149, 192, 95, 24, 240, 24, 148, 110}, + }, + 1, + 32, + true}, + + {{53}, + 255, + {14, 10, 1, 13, 8, 7, 3, 11, 9, 4, 6, 0, 5, 12, 15, 2}, + { + {114}, + {41}, + {116}, + {67}, + {198}, + {109}, + {232}, + {39}, + {90}, + {241}, + {156}, + {75}, + {46}, + {181}, + {144}, + {175}, + }, + 16, + 1, + true}, + + {{91, 188, 226, 91, 254, 197, 225}, + 1, + {5, 1, 10}, + { + {129, 18, 104, 86, 236, 73, 176}, + {91, 188, 226, 91, 254, 197, 225}, + {69, 53, 151, 204, 224, 37, 19}, + }, + 3, + 7, + true}, + + {{0}, + 1, + {5, 1, 1}, + { + {129, 18, 104, 86, 236, 73, 176}, + {91, 188, 226, 91, 254, 197, 225}, + {69, 53, 151, 204, 224, 37, 19}, + }, + 3, + 7, + false}, + + {{0}, + 255, + {3, 12, 3}, + { + {100, 176, 99, 142, 115, 192, 138}, + {54, 139, 99, 172, 29, 137, 58}, + {216, 119, 222, 40, 87, 25, 147}, + }, + 3, + 7, + false}, + + {{163, 120, 30, 243, 179, 172, 196, 137, 119, 17}, + 3, + {1, 0, 12}, + {{80, 180, 198, 131, 111, 251, 45, 181, 2, 242}, + {121, 9, 79, 98, 132, 164, 9, 165, 19, 230}, + {86, 52, 173, 138, 189, 223, 122, 102, 248, 157}}, + 3, + 10, + true}}; + + for (size_t i = 0; i < (sizeof(vectors) / sizeof(*vectors)); ++i) { + uint8_t result[SHAMIR_MAX_LEN]; + const uint8_t *share_values[SHAMIR_MAX_COUNT]; + for (size_t j = 0; j < vectors[i].share_count; ++j) { + share_values[j] = vectors[i].share_values[j]; + } + ck_assert_int_eq(shamir_interpolate(result, vectors[i].result_index, + vectors[i].share_indices, share_values, + vectors[i].share_count, vectors[i].len), + vectors[i].ret); + if (vectors[i].ret == true) { + ck_assert_mem_eq(result, vectors[i].result, vectors[i].len); + } + } +} +END_TEST + +START_TEST(test_address) { + char address[36]; + uint8_t pub_key[65]; + + memcpy( + pub_key, + fromhex( + "0226659c1cf7321c178c07437150639ff0c5b7679c7ea195253ed9abda2e081a37"), + 33); + ecdsa_get_address(pub_key, 0, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "139MaMHp3Vjo8o4x8N1ZLWEtovLGvBsg6s"); + ecdsa_get_address(pub_key, 111, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "mhfJsQNnrXB3uuYZqvywARTDfuvyjg4RBh"); + ecdsa_get_address(pub_key, 52, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "MxiimznnxsqMfLKTQBL8Z2PoY9jKpjgkCu"); + ecdsa_get_address(pub_key, 48, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "LMNJqZbe89yrPbm7JVzrcXJf28hZ1rKPaH"); + ecdsa_get_address(pub_key, 36, HASHER_SHA2_RIPEMD, HASHER_GROESTLD_TRUNC, + address, sizeof(address)); + ck_assert_str_eq(address, "FXK52G2BbzRLaQ651U12o23DU5cEQdhvU6"); + ecdsa_get_address_segwit_p2sh(pub_key, 5, HASHER_SHA2_RIPEMD, HASHER_SHA2D, + address, sizeof(address)); + ck_assert_str_eq(address, "34PyTHn74syS796eTgsyoLfwoBC3cwLn6p"); + + memcpy( + pub_key, + fromhex( + "025b1654a0e78d28810094f6c5a96b8efb8a65668b578f170ac2b1f83bc63ba856"), + 33); + ecdsa_get_address(pub_key, 0, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "19Ywfm3witp6C1yBMy4NRYHY2347WCRBfQ"); + ecdsa_get_address(pub_key, 111, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "mp4txp8vXvFLy8So5Y2kFTVrt2epN6YzdP"); + ecdsa_get_address(pub_key, 52, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "N58JsQYveGueiZDgdnNwe4SSkGTAToutAY"); + ecdsa_get_address(pub_key, 48, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "LTmtvyMmoZ49SpfLY73fhZMJEFRPdyohKh"); + ecdsa_get_address(pub_key, 36, HASHER_SHA2_RIPEMD, HASHER_GROESTLD_TRUNC, + address, sizeof(address)); + ck_assert_str_eq(address, "Fdif7fnKHPVddczJF53qt45rgCL51yWN6x"); + ecdsa_get_address_segwit_p2sh(pub_key, 5, HASHER_SHA2_RIPEMD, HASHER_SHA2D, + address, sizeof(address)); + ck_assert_str_eq(address, "35trq6eeuHf6VL9L8pQv46x3vegHnHoTuB"); + + memcpy( + pub_key, + fromhex( + "03433f246a12e6486a51ff08802228c61cf895175a9b49ed4766ea9a9294a3c7fe"), + 33); + ecdsa_get_address(pub_key, 0, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "1FWE2bn3MWhc4QidcF6AvEWpK77sSi2cAP"); + ecdsa_get_address(pub_key, 111, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "mv2BKes2AY8rqXCFKp4Yk9j9B6iaMfWRLN"); + ecdsa_get_address(pub_key, 52, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "NB5bEFH2GtoAawy8t4Qk8kfj3LWvQs3MhB"); + ecdsa_get_address(pub_key, 48, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "LZjBHp5sSAwfKDQnnP5UCFaaXKV9YheGxQ"); + ecdsa_get_address(pub_key, 36, HASHER_SHA2_RIPEMD, HASHER_GROESTLD_TRUNC, + address, sizeof(address)); + ck_assert_str_eq(address, "FjfwUWWQv1P9W1jkVM5eNkK8yGPq5XyZZy"); + ecdsa_get_address_segwit_p2sh(pub_key, 5, HASHER_SHA2_RIPEMD, HASHER_SHA2D, + address, sizeof(address)); + ck_assert_str_eq(address, "3456DYaKUWuY6RWWw8Hp5CftHLcQN29h9Y"); + + memcpy( + pub_key, + fromhex( + "03aeb03abeee0f0f8b4f7a5d65ce31f9570cef9f72c2dd8a19b4085a30ab033d48"), + 33); + ecdsa_get_address(pub_key, 0, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "1yrZb8dhdevoqpUEGi2tUccUEeiMKeLcs"); + ecdsa_get_address(pub_key, 111, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "mgVoreDcWf6BaxJ5wqgQiPpwLEFRLSr8U8"); + ecdsa_get_address(pub_key, 52, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "MwZDmEdcd1kVLP4yW62c6zmXCU3mNbveDo"); + ecdsa_get_address(pub_key, 48, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "LLCopoSTnHtz4eWdQQhLAVgNgT1zTi4QBK"); + ecdsa_get_address(pub_key, 36, HASHER_SHA2_RIPEMD, HASHER_GROESTLD_TRUNC, + address, sizeof(address)); + ck_assert_str_eq(address, "FW9a1Vs1G8LUFSqb7NhWLzQw8PvfwAxmxA"); + ecdsa_get_address_segwit_p2sh(pub_key, 5, HASHER_SHA2_RIPEMD, HASHER_SHA2D, + address, sizeof(address)); + ck_assert_str_eq(address, "3DBU4tJ9tkMR9fnmCtjW48kjvseoNLQZXd"); + + memcpy( + pub_key, + fromhex( + "0496e8f2093f018aff6c2e2da5201ee528e2c8accbf9cac51563d33a7bb74a016054" + "201c025e2a5d96b1629b95194e806c63eb96facaedc733b1a4b70ab3b33e3a"), + 65); + ecdsa_get_address(pub_key, 0, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "194SZbL75xCCGBbKtMsyWLE5r9s2V6mhVM"); + ecdsa_get_address(pub_key, 111, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "moaPreR5tydT3J4wbvrMLFSQi9TjPCiZc6"); + ecdsa_get_address(pub_key, 52, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "N4domEq61LHkniqqABCYirNzaPG5NRU8GH"); + ecdsa_get_address(pub_key, 48, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "LTHPpodwAcSFWzHV4VsGnMHr4NEJajMnKX"); + ecdsa_get_address(pub_key, 36, HASHER_SHA2_RIPEMD, HASHER_GROESTLD_TRUNC, + address, sizeof(address)); + ck_assert_str_eq(address, "FdEA1W4UeSsjhncSmTsSxr2QWK8z2xGkjc"); + + memcpy( + pub_key, + fromhex( + "0498010f8a687439ff497d3074beb4519754e72c4b6220fb669224749591dde416f3" + "961f8ece18f8689bb32235e436874d2174048b86118a00afbd5a4f33a24f0f"), + 65); + ecdsa_get_address(pub_key, 0, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "1A2WfBD4BJFwYHFPc5KgktqtbdJLBuVKc4"); + ecdsa_get_address(pub_key, 111, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "mpYTxEJ2zKhCKPj1KeJ4ap4DTcu39T3uzD"); + ecdsa_get_address(pub_key, 52, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "N5bsrpi36gMW4pVtsteFyQzoKrhPE7nkxK"); + ecdsa_get_address(pub_key, 48, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "LUFTvPWtFxVzo5wYnDJz2uueoqfcMYiuxH"); + ecdsa_get_address(pub_key, 36, HASHER_SHA2_RIPEMD, HASHER_GROESTLD_TRUNC, + address, sizeof(address)); + ck_assert_str_eq(address, "FeCE75wRjnwUytGWVBKADQeDFnaHpJ8t3B"); + + memcpy( + pub_key, + fromhex( + "04f80490839af36d13701ec3f9eebdac901b51c362119d74553a3c537faff31b17e2" + "a59ebddbdac9e87b816307a7ed5b826b8f40b92719086238e1bebf19b77a4d"), + 65); + ecdsa_get_address(pub_key, 0, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "19J81hrPnQxg9UGx45ibTieCkb2ttm8CLL"); + ecdsa_get_address(pub_key, 111, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "mop5JkwNbSPvvakZmegyHdrXcadbjLazww"); + ecdsa_get_address(pub_key, 52, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "N4sVDMMNho4Eg1XTKu3AgEo7UpRwq3aNbn"); + ecdsa_get_address(pub_key, 48, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "LTX5GvADs5CjQGy7EDhtjjhxxoQB2Uhicd"); + ecdsa_get_address(pub_key, 36, HASHER_SHA2_RIPEMD, HASHER_GROESTLD_TRUNC, + address, sizeof(address)); + ck_assert_str_eq(address, "FdTqTcamLueDb5J4wBi4vESXQkJrS54H6k"); +} +END_TEST + +START_TEST(test_pubkey_validity) { + uint8_t pub_key[65]; + curve_point pub; + int res; + const ecdsa_curve *curve = &secp256k1; + + memcpy( + pub_key, + fromhex( + "0226659c1cf7321c178c07437150639ff0c5b7679c7ea195253ed9abda2e081a37"), + 33); + res = ecdsa_read_pubkey(curve, pub_key, &pub); + ck_assert_int_eq(res, 1); + + memcpy( + pub_key, + fromhex( + "025b1654a0e78d28810094f6c5a96b8efb8a65668b578f170ac2b1f83bc63ba856"), + 33); + res = ecdsa_read_pubkey(curve, pub_key, &pub); + ck_assert_int_eq(res, 1); + + memcpy( + pub_key, + fromhex( + "03433f246a12e6486a51ff08802228c61cf895175a9b49ed4766ea9a9294a3c7fe"), + 33); + res = ecdsa_read_pubkey(curve, pub_key, &pub); + ck_assert_int_eq(res, 1); + + memcpy( + pub_key, + fromhex( + "03aeb03abeee0f0f8b4f7a5d65ce31f9570cef9f72c2dd8a19b4085a30ab033d48"), + 33); + res = ecdsa_read_pubkey(curve, pub_key, &pub); + ck_assert_int_eq(res, 1); + + memcpy( + pub_key, + fromhex( + "0496e8f2093f018aff6c2e2da5201ee528e2c8accbf9cac51563d33a7bb74a016054" + "201c025e2a5d96b1629b95194e806c63eb96facaedc733b1a4b70ab3b33e3a"), + 65); + res = ecdsa_read_pubkey(curve, pub_key, &pub); + ck_assert_int_eq(res, 1); + + memcpy( + pub_key, + fromhex( + "0498010f8a687439ff497d3074beb4519754e72c4b6220fb669224749591dde416f3" + "961f8ece18f8689bb32235e436874d2174048b86118a00afbd5a4f33a24f0f"), + 65); + res = ecdsa_read_pubkey(curve, pub_key, &pub); + ck_assert_int_eq(res, 1); + + memcpy( + pub_key, + fromhex( + "04f80490839af36d13701ec3f9eebdac901b51c362119d74553a3c537faff31b17e2" + "a59ebddbdac9e87b816307a7ed5b826b8f40b92719086238e1bebf19b77a4d"), + 65); + res = ecdsa_read_pubkey(curve, pub_key, &pub); + ck_assert_int_eq(res, 1); + + memcpy( + pub_key, + fromhex( + "04f80490839af36d13701ec3f9eebdac901b51c362119d74553a3c537faff31b17e2" + "a59ebddbdac9e87b816307a7ed5b826b8f40b92719086238e1bebf00000000"), + 65); + res = ecdsa_read_pubkey(curve, pub_key, &pub); + ck_assert_int_eq(res, 0); + + memcpy( + pub_key, + fromhex( + "04f80490839af36d13701ec3f9eebdac901b51c362119d74553a3c537faff31b17e2" + "a59ebddbdac9e87b816307a7ed5b8211111111111111111111111111111111"), + 65); + res = ecdsa_read_pubkey(curve, pub_key, &pub); + ck_assert_int_eq(res, 0); + + memcpy(pub_key, fromhex("00"), 1); + res = ecdsa_read_pubkey(curve, pub_key, &pub); + ck_assert_int_eq(res, 0); +} +END_TEST + +START_TEST(test_pubkey_uncompress) { + uint8_t pub_key[65]; + uint8_t uncompressed[65]; + int res; + const ecdsa_curve *curve = &secp256k1; + + memcpy( + pub_key, + fromhex( + "0226659c1cf7321c178c07437150639ff0c5b7679c7ea195253ed9abda2e081a37"), + 33); + res = ecdsa_uncompress_pubkey(curve, pub_key, uncompressed); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq( + uncompressed, + fromhex( + "0426659c1cf7321c178c07437150639ff0c5b7679c7ea195253ed9abda2e081a37b3" + "cfbad6b39a8ce8cb3a675f53b7b57e120fe067b8035d771fd99e3eba7cf4de"), + 65); + + memcpy( + pub_key, + fromhex( + "03433f246a12e6486a51ff08802228c61cf895175a9b49ed4766ea9a9294a3c7fe"), + 33); + res = ecdsa_uncompress_pubkey(curve, pub_key, uncompressed); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq( + uncompressed, + fromhex( + "04433f246a12e6486a51ff08802228c61cf895175a9b49ed4766ea9a9294a3c7feeb" + "4c25bcb840f720a16e8857a011e6b91e0ab2d03dbb5f9762844bb21a7b8ca7"), + 65); + + memcpy( + pub_key, + fromhex( + "0496e8f2093f018aff6c2e2da5201ee528e2c8accbf9cac51563d33a7bb74a016054" + "201c025e2a5d96b1629b95194e806c63eb96facaedc733b1a4b70ab3b33e3a"), + 65); + res = ecdsa_uncompress_pubkey(curve, pub_key, uncompressed); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq( + uncompressed, + fromhex( + "0496e8f2093f018aff6c2e2da5201ee528e2c8accbf9cac51563d33a7bb74a016054" + "201c025e2a5d96b1629b95194e806c63eb96facaedc733b1a4b70ab3b33e3a"), + 65); + + memcpy(pub_key, fromhex("00"), 1); + res = ecdsa_uncompress_pubkey(curve, pub_key, uncompressed); + ck_assert_int_eq(res, 0); +} +END_TEST + +START_TEST(test_wif) { + uint8_t priv_key[32]; + char wif[53]; + + memcpy( + priv_key, + fromhex( + "1111111111111111111111111111111111111111111111111111111111111111"), + 32); + ecdsa_get_wif(priv_key, 0x80, HASHER_SHA2D, wif, sizeof(wif)); + ck_assert_str_eq(wif, "KwntMbt59tTsj8xqpqYqRRWufyjGunvhSyeMo3NTYpFYzZbXJ5Hp"); + ecdsa_get_wif(priv_key, 0xEF, HASHER_SHA2D, wif, sizeof(wif)); + ck_assert_str_eq(wif, "cN9spWsvaxA8taS7DFMxnk1yJD2gaF2PX1npuTpy3vuZFJdwavaw"); + + memcpy( + priv_key, + fromhex( + "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"), + 32); + ecdsa_get_wif(priv_key, 0x80, HASHER_SHA2D, wif, sizeof(wif)); + ck_assert_str_eq(wif, "L4ezQvyC6QoBhxB4GVs9fAPhUKtbaXYUn8YTqoeXwbevQq4U92vN"); + ecdsa_get_wif(priv_key, 0xEF, HASHER_SHA2D, wif, sizeof(wif)); + ck_assert_str_eq(wif, "cV1ysqy3XUVSsPeKeugH2Utm6ZC1EyeArAgvxE73SiJvfa6AJng7"); + + memcpy( + priv_key, + fromhex( + "47f7616ea6f9b923076625b4488115de1ef1187f760e65f89eb6f4f7ff04b012"), + 32); + ecdsa_get_wif(priv_key, 0x80, HASHER_SHA2D, wif, sizeof(wif)); + ck_assert_str_eq(wif, "KydbzBtk6uc7M6dXwEgTEH2sphZxSPbmDSz6kUUHi4eUpSQuhEbq"); + ecdsa_get_wif(priv_key, 0xEF, HASHER_SHA2D, wif, sizeof(wif)); + ck_assert_str_eq(wif, "cPzbT6tbXyJNWY6oKeVabbXwSvsN6qhTHV8ZrtvoDBJV5BRY1G5Q"); +} +END_TEST + +START_TEST(test_address_decode) { + int res; + uint8_t decode[MAX_ADDR_RAW_SIZE]; + + res = ecdsa_address_decode("1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T", 0, + HASHER_SHA2D, decode); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq(decode, + fromhex("00c4c5d791fcb4654a1ef5e03fe0ad3d9c598f9827"), 21); + + res = ecdsa_address_decode("myTPjxggahXyAzuMcYp5JTkbybANyLsYBW", 111, + HASHER_SHA2D, decode); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq(decode, + fromhex("6fc4c5d791fcb4654a1ef5e03fe0ad3d9c598f9827"), 21); + + res = ecdsa_address_decode("NEWoeZ6gh4CGvRgFAoAGh4hBqpxizGT6gZ", 52, + HASHER_SHA2D, decode); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq(decode, + fromhex("34c4c5d791fcb4654a1ef5e03fe0ad3d9c598f9827"), 21); + + res = ecdsa_address_decode("LdAPi7uXrLLmeh7u57pzkZc3KovxEDYRJq", 48, + HASHER_SHA2D, decode); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq(decode, + fromhex("30c4c5d791fcb4654a1ef5e03fe0ad3d9c598f9827"), 21); + + res = ecdsa_address_decode("1C7zdTfnkzmr13HfA2vNm5SJYRK6nEKyq8", 0, + HASHER_SHA2D, decode); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq(decode, + fromhex("0079fbfc3f34e7745860d76137da68f362380c606c"), 21); + + res = ecdsa_address_decode("mrdwvWkma2D6n9mGsbtkazedQQuoksnqJV", 111, + HASHER_SHA2D, decode); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq(decode, + fromhex("6f79fbfc3f34e7745860d76137da68f362380c606c"), 21); + + res = ecdsa_address_decode("N7hMq7AmgNsQXaYARrEwybbDGei9mcPNqr", 52, + HASHER_SHA2D, decode); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq(decode, + fromhex("3479fbfc3f34e7745860d76137da68f362380c606c"), 21); + + res = ecdsa_address_decode("LWLwtfycqf1uFqypLAug36W4kdgNwrZdNs", 48, + HASHER_SHA2D, decode); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq(decode, + fromhex("3079fbfc3f34e7745860d76137da68f362380c606c"), 21); + + // invalid char + res = ecdsa_address_decode("1JwSSubhmg6i000jtyqhUYYH7bZg3Lfy1T", 0, + HASHER_SHA2D, decode); + ck_assert_int_eq(res, 0); + + // invalid address + res = ecdsa_address_decode("1111Subhmg6iPtRjtyqhUYYH7bZg3Lfy1T", 0, + HASHER_SHA2D, decode); + ck_assert_int_eq(res, 0); + + // invalid version + res = ecdsa_address_decode("LWLwtfycqf1uFqypLAug36W4kdgNwrZdNs", 0, + HASHER_SHA2D, decode); + ck_assert_int_eq(res, 0); +} +END_TEST + +START_TEST(test_ecdsa_der) { + static const struct { + const char *r; + const char *s; + const char *der; + } vectors[] = { + { + "9a0b7be0d4ed3146ee262b42202841834698bb3ee39c24e7437df208b8b70771", + "2b79ab1e7736219387dffe8d615bbdba87e11477104b867ef47afed1a5ede781", + "30450221009a0b7be0d4ed3146ee262b42202841834698bb3ee39c24e7437df208b8" + "b7077102202b79ab1e7736219387dffe8d615bbdba87e11477104b867ef47afed1a5" + "ede781", + }, + { + "6666666666666666666666666666666666666666666666666666666666666666", + "7777777777777777777777777777777777777777777777777777777777777777", + "30440220666666666666666666666666666666666666666666666666666666666666" + "66660220777777777777777777777777777777777777777777777777777777777777" + "7777", + }, + { + "6666666666666666666666666666666666666666666666666666666666666666", + "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "30450220666666666666666666666666666666666666666666666666666666666666" + "6666022100eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeee", + }, + { + "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "7777777777777777777777777777777777777777777777777777777777777777", + "3045022100eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeee02207777777777777777777777777777777777777777777777777777777777" + "777777", + }, + { + "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "3046022100eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeee022100ffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "ffffffff", + }, + { + "0000000000000000000000000000000000000000000000000000000000000066", + "0000000000000000000000000000000000000000000000000000000000000077", + "3006020166020177", + }, + { + "0000000000000000000000000000000000000000000000000000000000000066", + "00000000000000000000000000000000000000000000000000000000000000ee", + "3007020166020200ee", + }, + { + "00000000000000000000000000000000000000000000000000000000000000ee", + "0000000000000000000000000000000000000000000000000000000000000077", + "3007020200ee020177", + }, + { + "00000000000000000000000000000000000000000000000000000000000000ee", + "00000000000000000000000000000000000000000000000000000000000000ff", + "3008020200ee020200ff", + }, + { + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "3006020100020100", + }, + }; + + uint8_t sig[64]; + uint8_t der[72]; + uint8_t out[72]; + for (size_t i = 0; i < (sizeof(vectors) / sizeof(*vectors)); ++i) { + size_t der_len = strlen(vectors[i].der) / 2; + memcpy(der, fromhex(vectors[i].der), der_len); + memcpy(sig, fromhex(vectors[i].r), 32); + memcpy(sig + 32, fromhex(vectors[i].s), 32); + ck_assert_int_eq(ecdsa_sig_to_der(sig, out), der_len); + ck_assert_mem_eq(out, der, der_len); + ck_assert_int_eq(ecdsa_sig_from_der(der, der_len, out), 0); + ck_assert_mem_eq(out, sig, 64); + } +} +END_TEST + +static void test_codepoints_curve(const ecdsa_curve *curve) { + int i, j; + bignum256 a; + curve_point p, p1; + for (i = 0; i < 64; i++) { + for (j = 0; j < 8; j++) { + bn_zero(&a); + a.val[(4 * i) / BN_BITS_PER_LIMB] = (uint32_t)(2 * j + 1) + << (4 * i % BN_BITS_PER_LIMB); + bn_normalize(&a); + // note that this is not a trivial test. We add 64 curve + // points in the table to get that particular curve point. + scalar_multiply(curve, &a, &p); + ck_assert_mem_eq(&p, &curve->cp[i][j], sizeof(curve_point)); + bn_zero(&p.y); // test that point_multiply curve, is not a noop + point_multiply(curve, &a, &curve->G, &p); + ck_assert_mem_eq(&p, &curve->cp[i][j], sizeof(curve_point)); + // mul 2 test. this should catch bugs + bn_lshift(&a); + bn_mod(&a, &curve->order); + p1 = curve->cp[i][j]; + point_double(curve, &p1); + // note that this is not a trivial test. We add 64 curve + // points in the table to get that particular curve point. + scalar_multiply(curve, &a, &p); + ck_assert_mem_eq(&p, &p1, sizeof(curve_point)); + bn_zero(&p.y); // test that point_multiply curve, is not a noop + point_multiply(curve, &a, &curve->G, &p); + ck_assert_mem_eq(&p, &p1, sizeof(curve_point)); + } + } +} + +START_TEST(test_codepoints_secp256k1) { test_codepoints_curve(&secp256k1); } +END_TEST +START_TEST(test_codepoints_nist256p1) { test_codepoints_curve(&nist256p1); } +END_TEST + +static void test_mult_border_cases_curve(const ecdsa_curve *curve) { + bignum256 a; + curve_point p; + curve_point expected; + bn_zero(&a); // a == 0 + scalar_multiply(curve, &a, &p); + ck_assert(point_is_infinity(&p)); + point_multiply(curve, &a, &p, &p); + ck_assert(point_is_infinity(&p)); + point_multiply(curve, &a, &curve->G, &p); + ck_assert(point_is_infinity(&p)); + + bn_addi(&a, 1); // a == 1 + scalar_multiply(curve, &a, &p); + ck_assert_mem_eq(&p, &curve->G, sizeof(curve_point)); + point_multiply(curve, &a, &curve->G, &p); + ck_assert_mem_eq(&p, &curve->G, sizeof(curve_point)); + + bn_subtract(&curve->order, &a, &a); // a == -1 + expected = curve->G; + bn_subtract(&curve->prime, &expected.y, &expected.y); + scalar_multiply(curve, &a, &p); + ck_assert_mem_eq(&p, &expected, sizeof(curve_point)); + point_multiply(curve, &a, &curve->G, &p); + ck_assert_mem_eq(&p, &expected, sizeof(curve_point)); + + bn_subtract(&curve->order, &a, &a); + bn_addi(&a, 1); // a == 2 + expected = curve->G; + point_add(curve, &expected, &expected); + scalar_multiply(curve, &a, &p); + ck_assert_mem_eq(&p, &expected, sizeof(curve_point)); + point_multiply(curve, &a, &curve->G, &p); + ck_assert_mem_eq(&p, &expected, sizeof(curve_point)); + + bn_subtract(&curve->order, &a, &a); // a == -2 + expected = curve->G; + point_add(curve, &expected, &expected); + bn_subtract(&curve->prime, &expected.y, &expected.y); + scalar_multiply(curve, &a, &p); + ck_assert_mem_eq(&p, &expected, sizeof(curve_point)); + point_multiply(curve, &a, &curve->G, &p); + ck_assert_mem_eq(&p, &expected, sizeof(curve_point)); +} + +START_TEST(test_mult_border_cases_secp256k1) { + test_mult_border_cases_curve(&secp256k1); +} +END_TEST +START_TEST(test_mult_border_cases_nist256p1) { + test_mult_border_cases_curve(&nist256p1); +} +END_TEST + +static void test_scalar_mult_curve(const ecdsa_curve *curve) { + int i; + // get two "random" numbers + bignum256 a = curve->G.x; + bignum256 b = curve->G.y; + curve_point p1, p2, p3; + for (i = 0; i < 1000; i++) { + /* test distributivity: (a + b)G = aG + bG */ + bn_mod(&a, &curve->order); + bn_mod(&b, &curve->order); + scalar_multiply(curve, &a, &p1); + scalar_multiply(curve, &b, &p2); + bn_addmod(&a, &b, &curve->order); + bn_mod(&a, &curve->order); + scalar_multiply(curve, &a, &p3); + point_add(curve, &p1, &p2); + ck_assert_mem_eq(&p2, &p3, sizeof(curve_point)); + // new "random" numbers + a = p3.x; + b = p3.y; + } +} + +START_TEST(test_scalar_mult_secp256k1) { test_scalar_mult_curve(&secp256k1); } +END_TEST +START_TEST(test_scalar_mult_nist256p1) { test_scalar_mult_curve(&nist256p1); } +END_TEST + +static void test_point_mult_curve(const ecdsa_curve *curve) { + int i; + // get two "random" numbers and a "random" point + bignum256 a = curve->G.x; + bignum256 b = curve->G.y; + curve_point p = curve->G; + curve_point p1, p2, p3; + for (i = 0; i < 200; i++) { + /* test distributivity: (a + b)P = aP + bP */ + bn_mod(&a, &curve->order); + bn_mod(&b, &curve->order); + ck_assert_int_eq(point_multiply(curve, &a, &p, &p1), 0); + ck_assert_int_eq(point_multiply(curve, &b, &p, &p2), 0); + bn_addmod(&a, &b, &curve->order); + bn_mod(&a, &curve->order); + ck_assert_int_eq(point_multiply(curve, &a, &p, &p3), 0); + point_add(curve, &p1, &p2); + ck_assert_mem_eq(&p2, &p3, sizeof(curve_point)); + // new "random" numbers and a "random" point + a = p1.x; + b = p1.y; + p = p3; + } +} + +START_TEST(test_point_mult_secp256k1) { test_point_mult_curve(&secp256k1); } +END_TEST +START_TEST(test_point_mult_nist256p1) { test_point_mult_curve(&nist256p1); } +END_TEST + +static void test_scalar_point_mult_curve(const ecdsa_curve *curve) { + int i; + // get two "random" numbers + bignum256 a = curve->G.x; + bignum256 b = curve->G.y; + curve_point p1, p2; + for (i = 0; i < 200; i++) { + /* test commutativity and associativity: + * a(bG) = (ab)G = b(aG) + */ + bn_mod(&a, &curve->order); + bn_mod(&b, &curve->order); + ck_assert_int_eq(scalar_multiply(curve, &a, &p1), 0); + ck_assert_int_eq(point_multiply(curve, &b, &p1, &p1), 0); + + ck_assert_int_eq(scalar_multiply(curve, &b, &p2), 0); + ck_assert_int_eq(point_multiply(curve, &a, &p2, &p2), 0); + + ck_assert_mem_eq(&p1, &p2, sizeof(curve_point)); + + bn_multiply(&a, &b, &curve->order); + bn_mod(&b, &curve->order); + ck_assert_int_eq(scalar_multiply(curve, &b, &p2), 0); + + ck_assert_mem_eq(&p1, &p2, sizeof(curve_point)); + + // new "random" numbers + a = p1.x; + b = p1.y; + } +} + +START_TEST(test_scalar_point_mult_secp256k1) { + test_scalar_point_mult_curve(&secp256k1); +} +END_TEST +START_TEST(test_scalar_point_mult_nist256p1) { + test_scalar_point_mult_curve(&nist256p1); +} +END_TEST + +START_TEST(test_ed25519) { + // test vectors from + // https://github.com/torproject/tor/blob/master/src/test/ed25519_vectors.inc + static const char *vectors[] = { + "26c76712d89d906e6672dafa614c42e5cb1caac8c6568e4d2493087db51f0d3" + "6", // secret + "c2247870536a192d142d056abefca68d6193158e7c1a59c1654c954eccaff89" + "4", // public + "d23188eac3773a316d46006fa59c095060be8b1a23582a0dd99002a82a0662bd" + "246d8449e172e04c5f46ac0d1404cebe4aabd8a75a1457aa06cae41f3334f10" + "4", // selfsig + "fba7a5366b5cb98c2667a18783f5cf8f4f8d1a2ce939ad22a6e685edde85128" + "d", + "1519a3b15816a1aafab0b213892026ebf5c0dc232c58b21088d88cb90e9b940" + "d", + "3a785ac1201c97ee5f6f0d99323960d5f264c7825e61aa7cc81262f15bef75eb" + "4fa5723add9b9d45b12311b6d403eb3ac79ff8e4e631fc3cd51e4ad2185b200" + "b", + "67e3aa7a14fac8445d15e45e38a523481a69ae35513c9e4143eb1c2196729a0" + "e", + "081faa81992e360ea22c06af1aba096e7a73f1c665bc8b3e4e531c46455fd1d" + "d", + "cf431fd0416bfbd20c9d95ef9b723e2acddffb33900edc72195dea95965d52d8" + "88d30b7b8a677c0bd8ae1417b1e1a0ec6700deadd5d8b54b6689275e04a0450" + "9", + "d51385942033a76dc17f089a59e6a5a7fe80d9c526ae8ddd8c3a506b99d3d0a" + "6", + "73cfa1189a723aad7966137cbffa35140bb40d7e16eae4c40b79b5f0360dd65" + "a", + "2375380cd72d1a6c642aeddff862be8a5804b916acb72c02d9ed052c1561881a" + "a658a5af856fcd6d43113e42f698cd6687c99efeef7f2ce045824440d26c5d0" + "0", + "5c8eac469bb3f1b85bc7cd893f52dc42a9ab66f1b02b5ce6a68e9b175d3bb43" + "3", + "66c1a77104d86461b6f98f73acf3cd229c80624495d2d74d6fda1e940080a96" + "b", + "2385a472f599ca965bbe4d610e391cdeabeba9c336694b0d6249e551458280be" + "122c2441dd9746a81bbfb9cd619364bab0df37ff4ceb7aefd24469c39d3bc50" + "8", + "eda433d483059b6d1ff8b7cfbd0fe406bfb23722c8f3c8252629284573b61b8" + "6", + "d21c294db0e64cb2d8976625786ede1d9754186ae8197a64d72f68c792eecc1" + "9", + "e500cd0b8cfff35442f88008d894f3a2fa26ef7d3a0ca5714ae0d3e2d40caae5" + "8ba7cdf69dd126994dad6be536fcda846d89dd8138d1683cc144c8853dce760" + "7", + "4377c40431c30883c5fbd9bc92ae48d1ed8a47b81d13806beac5351739b5533" + "d", + "c4d58b4cf85a348ff3d410dd936fa460c4f18da962c01b1963792b9dcc8a6ea" + "6", + "d187b9e334b0050154de10bf69b3e4208a584e1a65015ec28b14bcc252cf84b8" + "baa9c94867daa60f2a82d09ba9652d41e8dde292b624afc8d2c26441b95e3c0" + "e", + "c6bbcce615839756aed2cc78b1de13884dd3618f48367a17597a16c1cd7a290" + "b", + "95126f14d86494020665face03f2d42ee2b312a85bc729903eb17522954a1c4" + "a", + "815213640a643d198bd056e02bba74e1c8d2d931643e84497adf3347eb485079" + "c9afe0afce9284cdc084946b561abbb214f1304ca11228ff82702185cf28f60" + "d", + 0, + 0, + 0, + }; + const char **ssk, **spk, **ssig; + ssk = vectors; + spk = vectors + 1; + ssig = vectors + 2; + ed25519_public_key pk; + ed25519_secret_key sk; + ed25519_signature sig; + while (*ssk && *spk && *ssig) { + memcpy(sk, fromhex(*ssk), 32); + MARK_SECRET_DATA(sk, sizeof(sk)); + + ed25519_publickey(sk, pk); + UNMARK_SECRET_DATA(pk, sizeof(pk)); + ck_assert_mem_eq(pk, fromhex(*spk), 32); + + ed25519_sign(pk, 32, sk, sig); + UNMARK_SECRET_DATA(sig, sizeof(sig)); + ck_assert_mem_eq(sig, fromhex(*ssig), 64); + + ssk += 3; + spk += 3; + ssig += 3; + + UNMARK_SECRET_DATA(sk, sizeof(sk)); + } +} +END_TEST + +// test vectors from +// https://raw.githubusercontent.com/NemProject/nem-test-vectors/master/2.test-sign.dat +START_TEST(test_ed25519_keccak) { + static const struct { + const char *private_key; + const char *public_key; + const char *signature; + size_t length; + const char *data; + } tests[] = { + { + "abf4cf55a2b3f742d7543d9cc17f50447b969e6e06f5ea9195d428ab12b7318d", + "8a558c728c21c126181e5e654b404a45b4f0137ce88177435a69978cc6bec1f4", + "d9cec0cc0e3465fab229f8e1d6db68ab9cc99a18cb0435f70deb6100948576cd5c0a" + "a1feb550bdd8693ef81eb10a556a622db1f9301986827b96716a7134230c", + 41, + "8ce03cd60514233b86789729102ea09e867fc6d964dea8c2018ef7d0a2e0e24bf7e3" + "48e917116690b9", + }, + { + "6aa6dad25d3acb3385d5643293133936cdddd7f7e11818771db1ff2f9d3f9215", + "bbc8cbb43dda3ecf70a555981a351a064493f09658fffe884c6fab2a69c845c6", + "98bca58b075d1748f1c3a7ae18f9341bc18e90d1beb8499e8a654c65d8a0b4fbd2e0" + "84661088d1e5069187a2811996ae31f59463668ef0f8cb0ac46a726e7902", + 49, + "e4a92208a6fc52282b620699191ee6fb9cf04daf48b48fd542c5e43daa9897763a19" + "9aaa4b6f10546109f47ac3564fade0", + }, + { + "8e32bc030a4c53de782ec75ba7d5e25e64a2a072a56e5170b77a4924ef3c32a9", + "72d0e65f1ede79c4af0ba7ec14204e10f0f7ea09f2bc43259cd60ea8c3a087e2", + "ef257d6e73706bb04878875c58aa385385bf439f7040ea8297f7798a0ea30c1c5eff" + "5ddc05443f801849c68e98111ae65d088e726d1d9b7eeca2eb93b677860c", + 40, + "13ed795344c4448a3b256f23665336645a853c5c44dbff6db1b9224b5303b6447fbf" + "8240a2249c55", + }, + { + "c83ce30fcb5b81a51ba58ff827ccbc0142d61c13e2ed39e78e876605da16d8d7", + "3ec8923f9ea5ea14f8aaa7e7c2784653ed8c7de44e352ef9fc1dee81fc3fa1a3", + "0c684e71b35fed4d92b222fc60561db34e0d8afe44bdd958aaf4ee965911bef59912" + "36f3e1bced59fc44030693bcac37f34d29e5ae946669dc326e706e81b804", + 49, + "a2704638434e9f7340f22d08019c4c8e3dbee0df8dd4454a1d70844de11694f4c8ca" + "67fdcb08fed0cec9abb2112b5e5f89", + }, + { + "2da2a0aae0f37235957b51d15843edde348a559692d8fa87b94848459899fc27", + "d73d0b14a9754eec825fcb25ef1cfa9ae3b1370074eda53fc64c22334a26c254", + "6f17f7b21ef9d6907a7ab104559f77d5a2532b557d95edffd6d88c073d87ac00fc83" + "8fc0d05282a0280368092a4bd67e95c20f3e14580be28d8b351968c65e03", + 40, + "d2488e854dbcdfdb2c9d16c8c0b2fdbc0abb6bac991bfe2b14d359a6bc99d66c00fd" + "60d731ae06d0", + }, + { + "0c066261fb1b18ebf2a9bcdeda81eb47d5a3745438b3d0b9d19b75885ad0a154", + "2e5773f0e725024bc0359ce93a44e15d6507e7b160b6c592200385fee4a269cf", + "13b5d2dd1b04f62cc2ec1544fed256423684f2dbca4538ceddda1d15c59dc7196c87" + "840ea303ea30f4f6914a6ec9167841980c1d717f47fd641225068de88507", + 41, + "f15cb706e29fcfbcb324e38cbac62bb355deddb845c142e970f0c029ea4d05e59fd6" + "adf85573cf1775", + }, + { + "ef3d8e22a592f04c3a31aa736e10901757a821d053f1a49a525b4ec91eacdee3", + "72a2b4910a502b30e13a96aba643c59c79328c1ba1462be6f254e817ef157fee", + "95f2437a0210d2d2f125a3c377ed666c0d596cd104185e70204924a182a11a6eb3bd" + "ba4395bbfc3f4e827d38805752657ee52d1ce0f17e70f59bfd4999282509", + 50, + "6c3e4387345740b8d62cf0c9dec48f98c292539431b2b54020d8072d9cb55f0197f7" + "d99ff066afcf9e41ea8b7aea78eb082d", + }, + { + "f7fb79743e9ba957d2a4f1bd95ceb1299552abecaf758bf840d2dc2c09f3e3cb", + "8b7d7531280f76a8abac8293d87508e3953894087112ae01b6ad32485d4e9b67", + "c868ecf31cee783fe8799ac7e6a662431c822967351d8b79687f4ddf608f79a080c4" + "ff9eed4fdee8c99fe1be905f734cae2a172f1cfdb00771625c0695a5260e", + 42, + "55d8e60c307ee533b1af9ff677a2de40a6eace722bcc9eb5d79907b420e533bc06db" + "674dafbd9f43d672", + }, + { + "8cc9a2469a77fad18b44b871b2b6932cd354641d2d1e84403f746c4fff829791", + "aed5da202d4983dac560faf6704dc76ac111616318570e244043e82ed1bbcd2b", + "aee9616db4135150818eaffa3e4503c2d7e9e834847a4c7d0a8856e952761d361a65" + "7104d36950c9b75770ded00d56a96e06f383fa2406bc935dcf51f272300e", + 42, + "d9b8be2f71b83261304e333d6e35563dc3c36c2eb5a23e1461b6e95aa7c6f381f9c3" + "bd39deaa1b6df2f9", + }, + { + "a247abbef0c1affbf021d1aff128888550532fc0edd77bc39f6ef5312317ec47", + "98ededbad1e5ad7a0d5a0cf4fcd7a794eb5c6900a65e7e921884a636f19b131d", + "f8cc02933851432f0c5df0b70f2067f740ccb72de7d6fa1e9a9b0d6de1402b9c6c52" + "5fd848e45aaaac1423b52880ec3474a2f64b38db6fc8e008d95a310e6e0c", + 47, + "4a5f07eb713932532fc3132c96efdc45862fe7a954c1d2ae4640afdf4728fb58c65e" + "8a4ebfe0d53d5797d5146442b9", + }, + { + "163d69079ddad1f16695c47d81c3b72f869b2fdd50e6e47113db6c85051a6ede", + "93fe602642ee5773f4aaf6a3bc21e98e354035225353f419e78e43c3ec36c88a", + "da747fa2cb47aae1effc1e4cfde0e39fa79937948592a712a7665bf948b8311e7f3f" + "80f966301679520d5c2afa3eadd60e061f0d264887500d8d03a17e10fd02", + 41, + "65fe5c1a0214a59644892e5ac4216f09fbb4e191b89bfb63d6540177d25ef9e37148" + "50b8453bd6b2b6", + }, + { + "7b061bf90eb760971b9ec66a96fd6609635ca4b531f33e3c126b9ae6fdb3d491", + "cb392ebb6912df4111efeeb1278160daf9da396e9291b83979a5ac479f7276d2", + "f6eebe86f7ea672e0707ee518e1798d6fbd118c11b2aa30be07d10e3882e3721f203" + "0f9f044b77c3a7a9a2f1feba7e7ce75d1f7f3807a96a764fded35d341d02", + 45, + "a17f5ce39b9ba7b7cf1147e515d6aa84b22fd0e2d8323a91367198fc6c3aff04ebb2" + "1fc2bdbe7bc0364e8040a9", + }, + { + "c9f8ccbf761cec00ab236c52651e76b5f46d90f8936d44d40561ed5c277104de", + "a3192641e343b669ffd43677c2e5cd4efaed174e876141f1d773bd6cfe30d875", + "d44f884ec9eae2e99e74194b5acc769b7aa369aaad359e92ba6ff0fe629af2a9a715" + "6c19b720e7de8c7f03c039563f160948073cab6f99b26a56a8bb1023ba08", + 47, + "3d7e33b0ecead8269966e9dcd192b73eb8a12573fc8a5fdfbe5753541026ef2e49f5" + "280cba9bc2515a049b3a1c1b49", + }, + { + "ebfa409ac6f987df476858dd35310879bf564eeb62984a52115d2e6c24590124", + "7bb1601fe7215f3f4da9c8ab5e804dc58f57ba41b03223f57ec80d9c9a2dd0e1", + "f3e7c1abfcc9f35556cb1e4c5a2b34445177ac188312d9148f1d1d8467ea8411fa3c" + "da031d023034e45bbe407ef7d1b937bfb098266138857d35cb4efe407306", + 52, + "0c37564f718eda683aa6f3e9ab2487620b1a8b5c8f20adb3b2d7550af0d635371e53" + "1f27cebe76a2abcc96de0875bdae987a45ac", + }, + { + "f993f61902b7da332f2bb001baa7accaf764d824eb0cd073315f7ec43158b8fb", + "55fc8e0da1b454cab6ddefb235311db2b01504bf9ac3f71c7e3f3d0d1f09f80b", + "178bd147673c0ca330e45da63cbd1f1811906bd5284bb44e4bb00f7d7163d1f39697" + "5610b6f71c1ae4686466fad4c5e7bb9685099e21ca4f1a45bb3fcf56ae0c", + 42, + "b7dd613bc9c364d9eeb9a52636d72bc881dfc81a836b6537bbb928bff5b738313589" + "47ea9edea1570550", + }, + { + "05188c09c31b4bb63f0d49b47ccc1654c2aba907b8c6c0a82ee403e950169167", + "e096d808dfabe8e44eb74950199dadcd586f9de6b141a0ce85ab94b3d97866eb", + "669491c8eb7cedbbc0252f3eafb048b39a2a37f60ac87837777c72c879ac8b726c39" + "e10060750c2f539102999b71889746111bc5f71ec8c158cc81cf566aef03", + 44, + "bb8e22469d1c7f1d5418563e8781f69eccb56678bd36d8919f358c2778562ff6b50d" + "e916c12d44f1a778a7f3", + }, + { + "eabe57e1a916ebbffa4ba7abc7f23e83d4deb1338816cc1784d7495d92e98d0b", + "3aad275642f48a46ed1032f3de9f4053e0fd35cf217e065d2e4579c3683932f7", + "b2e9dac2c83942ca374f29c8eff5a30c377c3db3c1c645e593e524d17484e7705b11" + "f79573e2d63495fc3ce3bf216a209f0cb7bea477ae0f8bd297f193af8805", + 44, + "3f2c2d6682ee597f2a92d7e560ac53d5623550311a4939d68adfb904045ed8d215a9" + "fdb757a2368ea4d89f5f", + }, + { + "fef7b893b4b517fab68ca12d36b603bc00826bf3c9b31a05149642ae10bb3f55", + "b3fb891868708dfa5da5b9b5234058767ab42c117f12c3228c02a1976d1c0f83", + "6243e289314b7c7587802909a9be6173a916b36f9de1e164954dfe5d1ebd57c869a7" + "9552d770e13b51855502be6b15e7be42a3675298a81284df58e609b06503", + 47, + "38c69f884045cdbeebe4478fdbd1ccc6cf00a08d8a3120c74e7167d3a2e26a67a043" + "b8e5bd198f7b0ce0358cef7cf9", + }, + { + "16228bec9b724300a37e88e535fc1c58548d34d7148b57c226f2b3af974c1822", + "3c92423a8360c9a5d9a093730d72831bec4601dcadfe84de19fc8c8f91fc3d4b", + "6aebfa9a4294ec888d54bcb517fcb6821e4c16d2708a2afe701f431a28149ff4f139" + "f9d16a52a63f1f91baf4c8dea37710c73f25c263a8035a39cc118ad0280f", + 44, + "a3d7b122cd4431b396b20d8cc46cc73ed4a5253a44a76fc83db62cdc845a2bf7081d" + "069a857955a161cccf84", + }, + { + "2dc3f5f0a0bc32c6632534e1e8f27e59cbe0bf7617d31aff98098e974c828be7", + "b998a416edc28ded988dcacb1caf2bd96c87354b0d1eeccb6980e54a3104f21f", + "76a2ddfc4bea48c47e0c82bcbfee28a37c61ec626af39a468e643e0ef9f6533056a5" + "a0b44e64d614ba3c641a40e5b003a99463445ae2c3c8e1e9882092d74b07", + 42, + "bdae276d738b9758ea3d322b54fd12fe82b767e8d817d8ef3d41f78705748e28d15e" + "9c506962a1b85901", + }, + }; + + ed25519_secret_key private_key; + ed25519_public_key public_key; + ed25519_signature signature; + + for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { + nem_private_key(tests[i].private_key, private_key); + MARK_SECRET_DATA(private_key, sizeof(private_key)); + + ed25519_publickey_keccak(private_key, public_key); + UNMARK_SECRET_DATA(public_key, sizeof(public_key)); + ck_assert_mem_eq(public_key, fromhex(tests[i].public_key), 32); + + ed25519_sign_keccak(fromhex(tests[i].data), tests[i].length, private_key, + signature); + UNMARK_SECRET_DATA(signature, sizeof(signature)); + ck_assert_mem_eq(signature, fromhex(tests[i].signature), 64); + + UNMARK_SECRET_DATA(private_key, sizeof(private_key)); + } +} +END_TEST + + +START_TEST(test_ed25519_cosi) { +//win +#ifdef _MSC_VER + #define MAXN 10 +#else + const int MAXN = 10; +#endif + + ed25519_secret_key keys[MAXN]; + ed25519_public_key pubkeys[MAXN]; + ed25519_secret_key nonces[MAXN]; + ed25519_public_key Rs[MAXN]; + ed25519_cosi_signature sigs[MAXN]; + uint8_t msg[32]; + rfc6979_state rng; + int res; + + init_rfc6979( + fromhex( + "26c76712d89d906e6672dafa614c42e5cb1caac8c6568e4d2493087db51f0d36"), + fromhex( + "26659c1cf7321c178c07437150639ff0c5b7679c7ea195253ed9abda2e081a37"), + NULL, &rng); + + for (int N = 1; N < 11; N++) { + ed25519_public_key pk; + ed25519_public_key R; + ed25519_signature sig; + /* phase 0: create priv/pubkeys and combine pubkeys */ + for (int j = 0; j < N; j++) { + generate_rfc6979(keys[j], &rng); + ed25519_publickey(keys[j], pubkeys[j]); + } + res = ed25519_cosi_combine_publickeys(pk, pubkeys, N); + ck_assert_int_eq(res, 0); + + generate_rfc6979(msg, &rng); + + /* phase 1: create nonces, commitments (R values) and combine commitments */ + for (int j = 0; j < N; j++) { + generate_rfc6979(nonces[j], &rng); + ed25519_publickey(nonces[j], Rs[j]); + } + res = ed25519_cosi_combine_publickeys(R, Rs, N); + ck_assert_int_eq(res, 0); + + MARK_SECRET_DATA(keys, sizeof(keys)); + /* phase 2: sign and combine signatures */ + for (int j = 0; j < N; j++) { + ed25519_cosi_sign(msg, sizeof(msg), keys[j], nonces[j], R, pk, sigs[j]); + } + UNMARK_SECRET_DATA(sigs, sizeof(sigs)); + + ed25519_cosi_combine_signatures(sig, R, sigs, N); + + /* check signature */ + res = ed25519_sign_open(msg, sizeof(msg), pk, sig); + ck_assert_int_eq(res, 0); + + UNMARK_SECRET_DATA(keys, sizeof(keys)); + } +} +END_TEST + +START_TEST(test_ed25519_modl_add) { + char tests[][3][65] = { + { + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + }, + + {"eef80ad5a9aad8b35b84f6a4eb3a7e2b222f403d455d8cdf40ad27e4cd5ae90a", + "0000000000000000000000000000000000000000000000000000000000000000", + "eef80ad5a9aad8b35b84f6a4eb3a7e2b222f403d455d8cdf40ad27e4cd5ae90a"}, + + {"0000000000000000000000000000000000000000000000000000000000000000", + "eef80ad5a9aad8b35b84f6a4eb3a7e2b222f403d455d8cdf40ad27e4cd5ae90a", + "eef80ad5a9aad8b35b84f6a4eb3a7e2b222f403d455d8cdf40ad27e4cd5ae90a"}, + + {"0100000000000000000000000000000000000000000000000000000000000000", + "0200000000000000000000000000000000000000000000000000000000000000", + "0300000000000000000000000000000000000000000000000000000000000000"}, + + {"e3d3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010", + "0a00000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000"}, + + {"f7bb3bf42b3e58e2edd06f173fc7bfbc7aaf657217946b75648447101136aa08", + "3c16b013109cc27ff39805be2abe04ba4cd6a8526a1d3023047693e950936c06", + "33d2eb073cda1a62e16975d56985c476c7850ec581b19b9868fadaf961c9160f"}, + }; + + unsigned char buff[32]; + bignum256modm a = {0}, b = {0}, c = {0}; + + for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { + expand256_modm(a, fromhex(tests[i][0]), 32); + expand256_modm(b, fromhex(tests[i][1]), 32); + add256_modm(c, a, b); + contract256_modm(buff, c); + ck_assert_mem_eq(buff, fromhex(tests[i][2]), 32); + } +} +END_TEST + +START_TEST(test_ed25519_modl_neg) { + char tests[][2][65] = { + {"05d0f55c1a631258d69cf7a2def9de1400000000000000000000000000000010", + "e803000000000000000000000000000000000000000000000000000000000000"}, + + {"4d4df45c1a631258d69cf7a2def9de1400000000000000000000000000000010", + "a086010000000000000000000000000000000000000000000000000000000000"}, + + {"25958944a1b7d4073975ca48996a1d740d0ed98ceec366760c5358da681e9608", + "c83e6c1879ab3d509d272d5a458fc1a0f2f12673113c9989f3aca72597e16907"}, + + {"0100000000000000000000000000000000000000000000000000000000000000", + "ecd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010"}, + + {"ecd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010", + "0100000000000000000000000000000000000000000000000000000000000000"}, + + {"0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000"}, + }; + + unsigned char buff[32]; + bignum256modm a = {0}, b = {0}; + + for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { + expand256_modm(a, fromhex(tests[i][0]), 32); + neg256_modm(b, a); + contract256_modm((unsigned char *)buff, b); + ck_assert_mem_eq(buff, fromhex(tests[i][1]), 32); + } +} +END_TEST + +START_TEST(test_ed25519_modl_sub) { + char tests[][3][65] = { + { + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + }, + + {"eef80ad5a9aad8b35b84f6a4eb3a7e2b222f403d455d8cdf40ad27e4cd5ae90a", + "53732f60e51ee3a48d21d2d526548c0dadbb79a185678fd7710613d0e76aad0c", + "8859d1d1deee0767a4ff1b72a3e0d0327573c69bbff5fc07cfa61414e6ef3b0e"}, + + {"9d91e26dbe7a14fdca9f5b20d13e828dc8c1ffe03fe90136a6bba507436ce500", + "9ca406705ccce65eb8cbf63706d3df09fcc67216c0dc3990270731aacbb2e607", + "eec0d15a7c1140f6e8705c8ba9658198ccfa8cca7f0cc8a57eb4745d77b9fe08"}, + + {"eef80ad5a9aad8b35b84f6a4eb3a7e2b222f403d455d8cdf40ad27e4cd5ae90a", + "0000000000000000000000000000000000000000000000000000000000000000", + "eef80ad5a9aad8b35b84f6a4eb3a7e2b222f403d455d8cdf40ad27e4cd5ae90a"}, + + {"0000000000000000000000000000000000000000000000000000000000000000", + "39897fbebf137a34572b014b0638ac0186d17874e3cc142ebdfe24327f5b8509", + "b44a769e5a4f98237f71f657d8c132137a2e878b1c33ebd14201dbcd80a47a06"}, + + {"0200000000000000000000000000000000000000000000000000000000000000", + "e3d3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010", + "0c00000000000000000000000000000000000000000000000000000000000000"}, + + {"e3d3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010", + "0800000000000000000000000000000000000000000000000000000000000000", + "dbd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010"}, + + {"ecd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010", + "0000000000000000000000000000000000000000000000000000000000000000", + "ecd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010"}, + + {"0000000000000000000000000000000000000000000000000000000000000000", + "ecd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010", + "0100000000000000000000000000000000000000000000000000000000000000"}, + + {"0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000010", + "edd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000000"}, + + {"0000000000000000000000000000000000000000000000000000000000000000", + "ffffff3f00000000000000000000000000000000000000000000000000000010", + "eed3f51c1a631258d69cf7a2def9de1400000000000000000000000000000000"}, + + {"0000000000000000000000000000000000000000000000000000000000000000", + "edd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000010"}, + + {"0000000000000000000000000000000000000000000000000000000000000000", + "e75f947f11d49d25a137fac8757538a980dec23811235cf63c48ee6bc6e4ed03", + "067461dd088f74323565fdd96884a66b7f213dc7eedca309c3b71194391b120c"}, + + {"0000000000000000000000000000000000000000000000000000000000000000", + "ecd3f55c1a631258d69cf7a2def9de140000000000000000000000000000ff0f", + "0100000000000000000000000000000000000000000000000000000000000100"}, + + {"0000000000000000000000000000000000000000000000000000000000000000", + "edd3f55c1a631258d69cf7a2def9de140000000000000000000004000000ff0f", + "0000000000000000000000000000000000000000000000000000fcffffff0000"}, + + {"0000000000000000000000000000000000000000000000000000000000000000", + "edd3f55c1a631258d69cf7a2def9de150000c0ffffffffffffffffffffffff0f", + "000000000000000000000000000000ffffff3f00000000000000000000000000"}, + + {"0000000000000000000000000000000000000000000000000000000000000000", + "edd3f55c1a631258d69cf7a2def9de1200000000000000000000000000000110", + "edd3f55c1a631258d69cf7a2def9de160000000000000000000000000000ff0f"}, + + {"0000000000000000000000000000000000000000000000000000000000000000", + "edd3f55c1a631258d69cf7a2def9de1300000000000000000000000000000010", + "0000000000000000000000000000000100000000000000000000000000000000"}, + }; + + unsigned char buff[32]; + bignum256modm a = {0}, b = {0}, c = {0}; + + for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { + expand256_modm(a, fromhex(tests[i][0]), 32); + expand256_modm(b, fromhex(tests[i][1]), 32); + sub256_modm(c, a, b); + contract256_modm(buff, c); + ck_assert_mem_eq(buff, fromhex(tests[i][2]), 32); + } +} +END_TEST + +#if USE_MONERO + +START_TEST(test_ge25519_double_scalarmult_vartime2) { + char tests[][5][65] = { + {"c537208ed4985e66e9f7a35c9a69448a732ba93960bbbd2823604f7ae9e3ed08", + "365233e5af17c8888d5ce508787464f4642e91a6212b1b104e6c3769535601b1", + "a84f871580176708b4ac21843cb197ad96e8456034442b50859c83c5807b9901", + "f022360d1bce903fa3ac58ae42f997328b31f477b8d576a9f6d26fc1d08f14ea", + "bf25da82c6b210948b823ae48422a2dcd205d3c94842e68ac27e5cbeaa704ebc"}, + {"4abfabc0dda33588a98127ef3bfe724fed286395fe15932e898b5621661ea102", + "e5fd79d03f5df8edfc8def663dcb96bba6cadf857f2ae6f6f51f52f8d14079b7", + "4754c286b23e3c1b50054fe3937ebdc4ec01b28da5d05fb6111798b42fc5bf06", + "b7e7f9464b98de5bfcf6b02c1b7053cc359df407ad59d943523c6d2ee773b2f6", + "6d7d5f729bfa4882dbff8e477cd2b4c354ba347f10e7b178a24f3f16a4e0fec6"}, + {"19f2af4d04cb8181f1fe0d01fe9bb9ecc476c67ceb4a9830dae1bc7fe5fe3b04", + "d3c462f4f30991220387a1fbbd1ba1dc45ce058c70a8fb1475071e7b4f0fc463", + "577790e025c1fd2014db44a8d613c4e2ab1f248a4a6d14b5d39cbbafd7b20f06", + "1376c6837f131f6cd1a45b1056297d2314aa0ac5f7d581d2d878261eb3259b4d", + "ce790760ada87dd819b59e4f6765d836d346567ec34f02bbcfcae0585c1d758f"}, + {"cf209db9e7ee85f1e648924ec97edd86b56a833b25707519d4fbe64fd50e150a", + "804f0806087dc665a26230ed5fd44c062980ee182a6bd7dbdb33df018c983778", + "30d3c448cb08935309753b3051366f52328ca1d9a0b63c72b989edee0da32b0e", + "98e3c973a7e85b5eab8111521c66ca584bed5597f060ab0c6b5cdeece502ac48", + "2646276e1305396a1b2473690066011a39789570a09e10ce1a013c8f32cd5bea"}, + {"b0a0ffeea67b656c4c585ba58ff528a6f45d2f915db98e4a14a8ff17f27fc105", + "4fabe16274f6af526ee053028485db6acd13804e02dcdddccc4183a319ab9e1c", + "1e140bb08a936ac6b7437644ca0769f3c165c7aa5501d49f064a0346179b4008", + "68fc1be64fb68761542a655b8dbebf50980f1fbc1845528df8d8a06bf89a1495", + "7dab86994b47014efe38493fc2b62ffcead806da6e0d73c992db8cb5618a19dc"}, + {"0fee422c2294b06ca83bc3704384dffc580e7ff5921881e51a755e5f9b80af03", + "4359a663ead3f7ffc3a0ead5c3c2bde348017e7bfa620f21759c32e469a16dfe", + "532066e3eec29334fffc37b17178dfbac3bee15f7845f01449ddbaf5e57a7b0c", + "32e46c2fb99402837631c8175db31cdd334c145f922be9070d62e6d9c493c3ea", + "8c7b7d2d61cdb648960434d894787426a76d16dd46949c7aa4b85dcf1054b4d5"}, + {"3a712d5b7ceb5257dcf6e6bb06548de6ef3deba5d456cd91fc305a12b46b5d01", + "5e7da62e3ec42cf3e554639dd4d2006754ee6839b720cadba94a26b73b1665ee", + "2a518ecab17a2d9dde219c775bcf4f2306b190bef2dea34fb65b8e4dccc13405", + "3b5d66a4dfb068923b3bc21cc8b40b59e12f845e0b85a86d394db0fa310bf185", + "2ec17f1cc0be093e9cdb741a991c0f417230dea275cd7babdad35e949e250521"}, + {"5f815f2d65cef584c5e5d48b2d3d3e4cae310d70b328f88af6e9f63c52b4c90d", + "8a539a8c6b2339922b31cf4bc064f1fedeb3912fd89585d79dfcff2a60aee295", + "385f7132b72db04146b9e472736b32adfca29556b4775a743c18e2bfab939007", + "884aaf96d625968ddb2582922a87abca131272884c47f6b86890ebccf0a79d5b", + "a7afdaf24fe8472d8b89e95c3ce4a40bdf700af7cedee44ed3aa5ccca09839bd"}, + {"a043340d072df16a8ab5135f8c1d601bff14c5aba01b9212b886ad71fe164506", + "52f6de5fa0fae32d4020a54d395319509d6b92092a0bf849fb34e73f8e71fc99", + "37d7472d360164da29e6dcb8f9796976022571c5df4ddf7e30e9a579ba13d509", + "8c369e3fd5b1112e4437b1f09e987acca4966f2f8c5227eb15ace240a2c64cc7", + "fc795fe7baff5c3ac98366e6882f25874ea2b0a649d16f139e5c54ea47042a1e"}, + {"97a3268db03fa184c8cba020bf216fc789292fa9615a28962385b86870ffd70f", + "a76c215587022bb9252ece4c5afeb0e65b820834cd41ac76e6c062d3eea75dc6", + "8310271017154cbddf7005e24eb9a9a86777b3f42fa5e35095eafaac4eb24802", + "b822665c2406083c851ecaa91ea67aa740c057e7679b5755cee60a6c63f17fd6", + "f83e2444527056eba595d49bde40b2e8da76d2c145f203331d26e94560993fbc"}, + {"edaad13efad39f26298e86ba8d06a46e59122232c9529bd22f2f656595421e00", + "f38e56a79f5159eb3b581dea537ec12c9c6fac381b2cf6073e27fc621197cb62", + "1eea79485954b5958d9d5478f86133af1088806d923535d483b115ab23099a0f", + "b32c5e57d57db7a349f4ab845f12a5045c52b4a7a5bce7fd54a1a255b0118185", + "3bfb42b4ffd2c6cfc8cce9e4187dc6fbcaecd9d44a4ca1d2b68b97410bb25b81"}, + {"b15eaebe0fc83cb11d755a6f067b710204d4a59101078d8286454b652879080a", + "4667a2e61d9df1690f5c33c4168e480f7e26d2f0998168ebdc0a39712946f741", + "125379da1a88bfdf5b928f8795d3ea5415ef8c3d9106eb16934c3842873fd707", + "8727a692a25e38b1afa98e3dd5bf88815dec6d9810c1fd8a31b56b3de8630f1e", + "540883dde400b909e9955a276c20e13d99252ebe542750b8bfbbe5c3b87c51e3"}, + {"e42bdd4af3121bea644a90a76b2007615621ee5b842b9a74c4334ac309478706", + "6dc4ab715d3bb975ebfd0f08e2b6f3f39922d0121ae518a8f8d2952ea2fe0b5d", + "0285059b0095c97f4a50d43c7726c64c2830bf2b55dfa934ebba7ad71064dc07", + "f738c0a3cee31fd8f438f282aa6c823fccfa49cf7b5c86fbf9d56bf0394b6d8d", + "a1bd106841e55010decd95a170a1d0dd11780fd00759819e024b15ea3a83b4be"}, + {"5077c30fd08795dbdc7a230c050ca07e316fa3b040fd0dac45907036ab25dd0e", + "96f0897f000e49e2439a9166cab40ebc125a31b82851f0541516c19683e7bfaf", + "2b67d79a2efdc6451508e7f3c97c4a61b135bb839c02338bb444ef8208dd970b", + "7ef4cd7cdc29c2b88ccff49898b5d0b7be5993f93c5772476feec9dc57d7b6e3", + "62449b901b25760c964704b28efc184fbd5947e83851ebaf3bbfeb6f742f679f"}, + {"a4b3ce6928fe8f77d13e65ae255eee8310ab0d75bca47028b4570f0511a66006", + "4e9da8d77ee337e3bcce3730ccfff2121728641c7bb4fdeb2155890f998af09a", + "ff01a5075569d0f6afee45da065c72f5841f46ce772917ef75eb4d230473580f", + "36ca32da8a10f4083f5a60ee21868d9d448548d49c56f19cbe6005005e34f816", + "99df362a3b762cc1cbb70bc5ddff3c8614ed306037013102e387ef32e7f2494f"}, + {"074aa76351dceb752aa09887d9aca932d5821f58eedb4988fd64d8548e3f2c09", + "588b4552f3b98b2f77aee2ef8cc72f88acd424c4373b3e3626393ed2ea24cbda", + "f2d9175633f2e3c661b01172b4b4176850cd5b3098ffb0f927e0a5e19c1c8a02", + "a6c34868736b2517fd46f57a4e30805ffd475e44a8b1413078f43d9cb3d6edd6", + "46e1e7d7b1e939dd5c07c8363af01f4f9dae7c3d10f237ff9776ddc4a1903771"}, + {"ae1c8abd5a542208ee0aa93ffbf0b8e5a957edc4854fe2b48153c5c85bbf3d08", + "5e084b9541a70bd5bef400be6525c5a806a5b7fb12de38b07dcd35a22c57edbe", + "d95f179a215fb322d81720bf3aecde78d6d676d6f941455d0e0920f1e3619707", + "c3e5d43221824de51d8f95705de69c80a2440c0483ca88549d639aee15390429", + "df9fea42d3b5ac243244abb4ca4948a69493becddc5d5906f9a4e4c5645b0eab"}, + {"2f1c5adedb7341dc7638bafacc6024bd48255197ea2347fc05714b9341dd4403", + "47f55263001542f796c928988f641f59d0cd43294fc8d8616b184bfe9dddf368", + "aa5e884e782ab116151c609680c37b1a49b52f23bce5e2ebf28dd8532510d20b", + "ef2d6d97ad1a18edfce6450c1e70295b2c7ed2bc749ea8b438a523eae078d1f3", + "2396a355c6ae8e2ac24da8f55a674c96fc4cc69b38678b2bd8eb91b96f462bca"}, + {"0242e14105ced74e91cf4d4dcd22a9c09279018901d2fb8319eb54c2a1c4900a", + "fcb62a6c520d31fa46efeb4a1000330653b3402f575c2ddc0c688f527e7b97be", + "73a7e2e0602e5345f040dedc4db67f6d8e37c5fca3bbb124fa43963d76dbbb08", + "152bf4a3305c656f77e292b1256cc470da4d3f6efc3667199db4316d7f431174", + "c21ba2080013dfb225e06378d9ac27df623df552526cfddbf9e71bb1d4705dd9"}, + {"07fab4fc7b02fbcf868ffb0326cf60425fef2af1fbad83a8926cc62c2b5dff05", + "29ff12c5e052eb5829e8334e0e082c5edde1f293d2b4ed499a79bcca20e48010", + "97afb3dd9167877b432a23503aad1ab39188b9be07cc124ceb3fbdbd8d8b890a", + "ed121240a2f4591eeedbfd880305ccd17e522673900b03279fb66e73583514ae", + "b27f209e88ce5701766565e231e8123adb1df9c9f1dc461920acbc2b38d9f6d7"}, + }; + + unsigned char buff[32]; + bignum256modm a = {0}, b = {0}; + ge25519 A, B, R; + + for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { + expand256_modm(a, fromhex(tests[i][0]), 32); + expand256_modm(b, fromhex(tests[i][2]), 32); + ge25519_unpack_negative_vartime(&A, fromhex(tests[i][1])); + curve25519_neg(A.x, A.x); + curve25519_neg(A.t, A.t); + ge25519_unpack_negative_vartime(&B, fromhex(tests[i][3])); + curve25519_neg(B.x, B.x); + curve25519_neg(B.t, B.t); + ge25519_double_scalarmult_vartime2(&R, &A, a, &B, b); + ge25519_pack(buff, &R); + ck_assert_mem_eq(buff, fromhex(tests[i][4]), 32); + } +} +END_TEST + +#endif + +static void test_bip32_ecdh_init_node(HDNode *node, const char *seed_str, + const char *curve_name) { + hdnode_from_seed((const uint8_t *)seed_str, strlen(seed_str), curve_name, + node); + ck_assert_int_eq(hdnode_fill_public_key(node), 0); + if (node->public_key[0] == 1) { + node->public_key[0] = 0x40; // Curve25519 public keys start with 0x40 byte + } +} + +static void test_bip32_ecdh(const char *curve_name, int expected_key_size, + const uint8_t *expected_key) { + #ifdef _MSC_VER + uint8_t * session_key1 = (uint8_t *)malloc(sizeof(uint8_t) * expected_key_size); + uint8_t * session_key2 = (uint8_t *)malloc(sizeof(uint8_t) * expected_key_size); + + int res, key_size; + HDNode alice, bob; + + test_bip32_ecdh_init_node(&alice, "Alice", curve_name); + test_bip32_ecdh_init_node(&bob, "Bob", curve_name); + // Generate shared key from Alice's secret key and Bob's public key + res = hdnode_get_shared_key(&alice, bob.public_key, session_key1, &key_size); + free (session_key1); + free (session_key2); + + ck_assert_int_eq(res, 0); + ck_assert_int_eq(key_size, expected_key_size); + ck_assert_mem_eq(session_key1, expected_key, key_size); + + // Generate shared key from Bob's secret key and Alice's public key + res = hdnode_get_shared_key(&bob, alice.public_key, session_key2, &key_size); + ck_assert_int_eq(res, 0); + ck_assert_int_eq(key_size, expected_key_size); + ck_assert_mem_eq(session_key2, expected_key, key_size); + #else + int res, key_size; + HDNode alice, bob; + uint8_t session_key1[expected_key_size], session_key2[expected_key_size]; + + test_bip32_ecdh_init_node(&alice, "Alice", curve_name); + test_bip32_ecdh_init_node(&bob, "Bob", curve_name); + + // Generate shared key from Alice's secret key and Bob's public key + res = hdnode_get_shared_key(&alice, bob.public_key, session_key1, &key_size); + ck_assert_int_eq(res, 0); + ck_assert_int_eq(key_size, expected_key_size); + ck_assert_mem_eq(session_key1, expected_key, key_size); + + // Generate shared key from Bob's secret key and Alice's public key + res = hdnode_get_shared_key(&bob, alice.public_key, session_key2, &key_size); + ck_assert_int_eq(res, 0); + ck_assert_int_eq(key_size, expected_key_size); + ck_assert_mem_eq(session_key2, expected_key, key_size); + #endif + +} + +START_TEST(test_bip32_ecdh_nist256p1) { + test_bip32_ecdh( + NIST256P1_NAME, 65, + fromhex( + "044aa56f917323f071148cd29aa423f6bee96e7fe87f914d0b91a0f95388c6631646" + "ea92e882773d7b0b1bec356b842c8559a1377673d3965fb931c8fe51e64873")); +} +END_TEST + +START_TEST(test_bip32_ecdh_curve25519) { + test_bip32_ecdh(CURVE25519_NAME, 33, + fromhex("04f34e35516325bb0d4a58507096c444a05ba13524ccf66910f1" + "1ce96c62224169")); +} +END_TEST + +START_TEST(test_bip32_ecdh_errors) { + HDNode node; + const uint8_t peer_public_key[65] = {0}; // invalid public key + uint8_t session_key[65]; + int res, key_size = 0; + + test_bip32_ecdh_init_node(&node, "Seed", ED25519_NAME); + res = hdnode_get_shared_key(&node, peer_public_key, session_key, &key_size); + ck_assert_int_eq(res, 1); + ck_assert_int_eq(key_size, 0); + + test_bip32_ecdh_init_node(&node, "Seed", CURVE25519_NAME); + res = hdnode_get_shared_key(&node, peer_public_key, session_key, &key_size); + ck_assert_int_eq(res, 1); + ck_assert_int_eq(key_size, 0); + + test_bip32_ecdh_init_node(&node, "Seed", NIST256P1_NAME); + res = hdnode_get_shared_key(&node, peer_public_key, session_key, &key_size); + ck_assert_int_eq(res, 1); + ck_assert_int_eq(key_size, 0); +} +END_TEST + +START_TEST(test_output_script) { + static const char *vectors[] = { + "76A914010966776006953D5567439E5E39F86A0D273BEE88AC", + "16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM", + "A914010966776006953D5567439E5E39F86A0D273BEE87", + "31nVrspaydBz8aMpxH9WkS2DuhgqS1fCuG", + "0014010966776006953D5567439E5E39F86A0D273BEE", + "p2xtZoXeX5X8BP8JfFhQK2nD3emtjch7UeFm", + "00200102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20", + "7XhPD7te3C6CVKnJWUhrTJbFTwudhHqfrjpS59AS6sMzL4RYFiCNg", + 0, + 0, + }; + const char **scr, **adr; + scr = vectors; + adr = vectors + 1; + char address[60]; + while (*scr && *adr) { + int r = + script_output_to_address(fromhex(*scr), strlen(*scr) / 2, address, 60); + ck_assert_uint_eq((size_t)r, strlen(*adr) + 1); + ck_assert_str_eq(address, *adr); + scr += 2; + adr += 2; + } +} +END_TEST + +#if USE_ETHEREUM + +START_TEST(test_ethereum_pubkeyhash) { + uint8_t pubkeyhash[20]; + int res; + HDNode node; + + // init m + hdnode_from_seed(fromhex("000102030405060708090a0b0c0d0e0f"), 16, + SECP256K1_NAME, &node); + + // [Chain m] + res = hdnode_get_ethereum_pubkeyhash(&node, pubkeyhash); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq(pubkeyhash, + fromhex("056db290f8ba3250ca64a45d16284d04bc6f5fbf"), 20); + + // [Chain m/0'] + hdnode_private_ckd_prime(&node, 0); + res = hdnode_get_ethereum_pubkeyhash(&node, pubkeyhash); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq(pubkeyhash, + fromhex("bf6e48966d0dcf553b53e7b56cb2e0e72dca9e19"), 20); + + // [Chain m/0'/1] + hdnode_private_ckd(&node, 1); + res = hdnode_get_ethereum_pubkeyhash(&node, pubkeyhash); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq(pubkeyhash, + fromhex("29379f45f515c494483298225d1b347f73d1babf"), 20); + + // [Chain m/0'/1/2'] + hdnode_private_ckd_prime(&node, 2); + res = hdnode_get_ethereum_pubkeyhash(&node, pubkeyhash); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq(pubkeyhash, + fromhex("d8e85fbbb4b3b3c71c4e63a5580d0c12fb4d2f71"), 20); + + // [Chain m/0'/1/2'/2] + hdnode_private_ckd(&node, 2); + res = hdnode_get_ethereum_pubkeyhash(&node, pubkeyhash); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq(pubkeyhash, + fromhex("1d3462d2319ac0bfc1a52e177a9d372492752130"), 20); + + // [Chain m/0'/1/2'/2/1000000000] + hdnode_private_ckd(&node, 1000000000); + res = hdnode_get_ethereum_pubkeyhash(&node, pubkeyhash); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq(pubkeyhash, + fromhex("73659c60270d326c06ac204f1a9c63f889a3d14b"), 20); + + // init m + hdnode_from_seed( + fromhex( + "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c" + "999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542"), + 64, SECP256K1_NAME, &node); + + // [Chain m] + res = hdnode_get_ethereum_pubkeyhash(&node, pubkeyhash); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq(pubkeyhash, + fromhex("6dd2a6f3b05fd15d901fbeec61b87a34bdcfb843"), 20); + + // [Chain m/0] + hdnode_private_ckd(&node, 0); + res = hdnode_get_ethereum_pubkeyhash(&node, pubkeyhash); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq(pubkeyhash, + fromhex("abbcd4471a0b6e76a2f6fdc44008fe53831e208e"), 20); + + // [Chain m/0/2147483647'] + hdnode_private_ckd_prime(&node, 2147483647); + res = hdnode_get_ethereum_pubkeyhash(&node, pubkeyhash); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq(pubkeyhash, + fromhex("40ef2cef1b2588ae862e7a511162ec7ff33c30fd"), 20); + + // [Chain m/0/2147483647'/1] + hdnode_private_ckd(&node, 1); + res = hdnode_get_ethereum_pubkeyhash(&node, pubkeyhash); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq(pubkeyhash, + fromhex("3f2e8905488f795ebc84a39560d133971ccf9b50"), 20); + + // [Chain m/0/2147483647'/1/2147483646'] + hdnode_private_ckd_prime(&node, 2147483646); + res = hdnode_get_ethereum_pubkeyhash(&node, pubkeyhash); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq(pubkeyhash, + fromhex("a5016fdf975f767e4e6f355c7a82efa69bf42ea7"), 20); + + // [Chain m/0/2147483647'/1/2147483646'/2] + hdnode_private_ckd(&node, 2); + res = hdnode_get_ethereum_pubkeyhash(&node, pubkeyhash); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq(pubkeyhash, + fromhex("8ff2a9f7e7917804e8c8ec150d931d9c5a6fbc50"), 20); +} +END_TEST + +START_TEST(test_ethereum_address) { + static const char *vectors[] = {"0x52908400098527886E0F7030069857D2E4169EE7", + "0x8617E340B3D01FA5F11F306F4090FD50E238070D", + "0xde709f2102306220921060314715629080e2fb77", + "0x27b1fdb04752bbc536007a920d24acb045561c26", + "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", + "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", + "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", + "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", + "0x5A4EAB120fB44eb6684E5e32785702FF45ea344D", + "0x5be4BDC48CeF65dbCbCaD5218B1A7D37F58A0741", + "0xa7dD84573f5ffF821baf2205745f768F8edCDD58", + "0x027a49d11d118c0060746F1990273FcB8c2fC196", + "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + 0}; + uint8_t addr[20]; + char address[43]; + const char **vec = vectors; + while (*vec) { + memcpy(addr, fromhex(*vec + 2), 20); + ethereum_address_checksum(addr, address, false, 0); + ck_assert_str_eq(address, *vec); + vec++; + } +} +END_TEST + +// test vectors from +// https://github.com/rsksmart/RSKIPs/blob/master/IPs/RSKIP60.md +START_TEST(test_rsk_address) { + uint8_t addr[20]; + char address[43]; + + static const char *rskip60_chain30[] = { + "0x5aaEB6053f3e94c9b9a09f33669435E7ef1bEAeD", + "0xFb6916095cA1Df60bb79ce92cE3EA74c37c5d359", + "0xDBF03B407c01E7CD3cBea99509D93F8Dddc8C6FB", + "0xD1220A0Cf47c7B9BE7a2e6ba89F429762E7B9adB", 0}; + const char **vec = rskip60_chain30; + while (*vec) { + memcpy(addr, fromhex(*vec + 2), 20); + ethereum_address_checksum(addr, address, true, 30); + ck_assert_str_eq(address, *vec); + vec++; + } + + static const char *rskip60_chain31[] = { + "0x5aAeb6053F3e94c9b9A09F33669435E7EF1BEaEd", + "0xFb6916095CA1dF60bb79CE92ce3Ea74C37c5D359", + "0xdbF03B407C01E7cd3cbEa99509D93f8dDDc8C6fB", + "0xd1220a0CF47c7B9Be7A2E6Ba89f429762E7b9adB", 0}; + vec = rskip60_chain31; + while (*vec) { + memcpy(addr, fromhex(*vec + 2), 20); + ethereum_address_checksum(addr, address, true, 31); + ck_assert_str_eq(address, *vec); + vec++; + } +} +END_TEST + +#endif + +#if USE_NEM +// test vectors from +// https://raw.githubusercontent.com/NemProject/nem-test-vectors/master/1.test-keys.dat +START_TEST(test_nem_address) { + static const struct { + const char *private_key; + const char *public_key; + const char *address; + } tests[] = { + { + "575dbb3062267eff57c970a336ebbc8fbcfe12c5bd3ed7bc11eb0481d7704ced", + "c5f54ba980fcbb657dbaaa42700539b207873e134d2375efeab5f1ab52f87844", + "NDD2CT6LQLIYQ56KIXI3ENTM6EK3D44P5JFXJ4R4", + }, + { + "5b0e3fa5d3b49a79022d7c1e121ba1cbbf4db5821f47ab8c708ef88defc29bfe", + "96eb2a145211b1b7ab5f0d4b14f8abc8d695c7aee31a3cfc2d4881313c68eea3", + "NABHFGE5ORQD3LE4O6B7JUFN47ECOFBFASC3SCAC", + }, + { + "738ba9bb9110aea8f15caa353aca5653b4bdfca1db9f34d0efed2ce1325aeeda", + "2d8425e4ca2d8926346c7a7ca39826acd881a8639e81bd68820409c6e30d142a", + "NAVOZX4HDVOAR4W6K4WJHWPD3MOFU27DFHC7KZOZ", + }, + { + "e8bf9bc0f35c12d8c8bf94dd3a8b5b4034f1063948e3cc5304e55e31aa4b95a6", + "4feed486777ed38e44c489c7c4e93a830e4c4a907fa19a174e630ef0f6ed0409", + "NBZ6JK5YOCU6UPSSZ5D3G27UHAPHTY5HDQMGE6TT", + }, + { + "c325ea529674396db5675939e7988883d59a5fc17a28ca977e3ba85370232a83", + "83ee32e4e145024d29bca54f71fa335a98b3e68283f1a3099c4d4ae113b53e54", + "NCQW2P5DNZ5BBXQVGS367DQ4AHC3RXOEVGRCLY6V", + }, + { + "a811cb7a80a7227ae61f6da536534ee3c2744e3c7e4b85f3e0df3c6a9c5613df", + "6d34c04f3a0e42f0c3c6f50e475ae018cfa2f56df58c481ad4300424a6270cbb", + "NA5IG3XFXZHIPJ5QLKX2FBJPEZYPMBPPK2ZRC3EH", + }, + { + "9c66de1ec77f4dfaaebdf9c8bc599ca7e8e6f0bc71390ffee2c9dd3f3619242a", + "a8fefd72a3b833dc7c7ed7d57ed86906dac22f88f1f4331873eb2da3152a3e77", + "NAABHVFJDBM74XMJJ52R7QN2MTTG2ZUXPQS62QZ7", + }, + { + "c56bc16ecf727878c15e24f4ae68569600ac7b251218a44ef50ce54175776edc", + "c92f761e6d83d20068fd46fe4bd5b97f4c6ba05d23180679b718d1f3e4fb066e", + "NCLK3OLMHR3F2E3KSBUIZ4K5PNWUDN37MLSJBJZP", + }, + { + "9dd73599283882fa1561ddfc9be5830b5dd453c90465d3fe5eeb646a3606374e", + "eaf16a4833e59370a04ccd5c63395058de34877b48c17174c71db5ed37b537ed", + "ND3AHW4VTI5R5QE5V44KIGPRU5FBJ5AFUCJXOY5H", + }, + { + "d9639dc6f49dad02a42fd8c217f1b1b4f8ce31ccd770388b645e639c72ff24fa", + "0f74a2f537cd9c986df018994dde75bdeee05e35eb9fe27adf506ca8475064f7", + "NCTZ4YAP43ONK3UYTASQVNDMBO24ZHJE65F3QPYE", + }, + { + "efc1992cd50b70ca55ac12c07aa5d026a8b78ffe28a7dbffc9228b26e02c38c1", + "2ebff201255f6cf948c78f528658b99a7c13ac791942fa22d59af610558111f5", + "NDQ2TMCMXBSFPZQPE2YKH6XLC24HD6LUMN6Z4GIC", + }, + { + "143a815e92e43f3ed1a921ee48cd143931b88b7c3d8e1e981f743c2a5be3c5ba", + "419ed11d48730e4ae2c93f0ea4df853b8d578713a36dab227517cf965861af4e", + "NA32IDDW2C53BDSBJNFL3Z6UU3J5CJZJMCZDXCF4", + }, + { + "bc1a082f5ac6fdd3a83ade211e5986ac0551bad6c7da96727ec744e5df963e2a", + "a160e6f9112233a7ce94202ed7a4443e1dac444b5095f9fecbb965fba3f92cac", + "NADUCEQLC3FTGB25GTA5HOUTB53CBVQNVOIP7NTJ", + }, + { + "4e47b4c6f4c7886e49ec109c61f4af5cfbb1637283218941d55a7f9fe1053f72", + "fbb91b16df828e21a9802980a44fc757c588bc1382a4cea429d6fa2ae0333f56", + "NBAF3BFLLPWH33MYE6VUPP5T6DQBZBKIDEQKZQOE", + }, + { + "efc4389da48ce49f85365cfa578c746530e9eac42db1b64ec346119b1becd347", + "2232f24dda0f2ded3ecd831210d4e8521a096b50cadd5a34f3f7083374e1ec12", + "NBOGTK2I2ATOGGD7ZFJHROG5MWL7XCKAUKSWIVSA", + }, + { + "bdba57c78ca7da16a3360efd13f06276284db8c40351de7fcd38ba0c35ac754d", + "c334c6c0dad5aaa2a0d0fb4c6032cb6a0edd96bf61125b5ea9062d5a00ee0eee", + "NCLERTEFYXKLK7RA4MVACEFMXMK3P7QMWTM7FBW2", + }, + { + "20694c1ec3c4a311bcdb29ed2edc428f6d4f9a4c429ad6a5bf3222084e35695f", + "518c4de412efa93de06a55947d11f697639443916ec8fcf04ebc3e6d17d0bd93", + "NB5V4BPIJHXVONO7UGMJDPFARMFA73BOBNOOYCOV", + }, + { + "e0d4f3760ac107b33c22c2cac24ab2f520b282684f5f66a4212ff95d926323ce", + "b3d16f4ead9de67c290144da535a0ed2504b03c05e5f1ceb8c7863762f786857", + "NC4PBAO5TPCAVQKBVOC4F6DMZP3CFSQBU46PSKBD", + }, + { + "efa9afc617412093c9c7a7c211a5332dd556f941e1a88c494ec860608610eea2", + "7e7716e4cebceb731d6f1fd28676f34888e9a0000fcfa1471db1c616c2ddf559", + "NCFW2LPXIWLBWAQN2QVIWEOD7IVDO3HQBD2OU56K", + }, + { + "d98499b3db61944684ce06a91735af4e14105338473fcf6ebe2b0bcada3dfd21", + "114171230ad6f8522a000cdc73fbc5c733b30bb71f2b146ccbdf34499f79a810", + "NCUKWDY3J3THKQHAKOK5ALF6ANJQABZHCH7VN6DP", + }, + }; + + HDNode node; + ed25519_secret_key private_key; + uint8_t chain_code[32]; + char address[41]; + + for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { + nem_private_key(tests[i].private_key, private_key); + + ck_assert(hdnode_from_xprv(0, 0, chain_code, private_key, + ED25519_KECCAK_NAME, &node)); + + ck_assert(hdnode_get_nem_address(&node, NEM_NETWORK_MAINNET, address)); + ck_assert_str_eq(address, tests[i].address); + + ck_assert_mem_eq(&node.public_key[1], fromhex(tests[i].public_key), 32); + } +} +END_TEST + +// test vectors from +// https://raw.githubusercontent.com/NemProject/nem-test-vectors/master/3.test-derive.dat +START_TEST(test_nem_derive) { + static const struct { + const char *salt; + const char *private_key; + const char *public_key; + const char *mul; + const char *shared_key; + } tests[] = { + { + "ad63ac08f9afc85eb0bf4f8881ca6eaa0215924c87aa2f137d56109bb76c6f98", + "e8857f8e488d4e6d4b71bcd44bb4cff49208c32651e1f6500c3b58cafeb8def6", + "9d8e5f200b05a2638fb084a375408cabd6d5989590d96e3eea5f2cb34668178e", + "a8352060ba5718745ee4d78b9df564e0fbe13f50d50ab15a8dd524159d81d18b", + "990a5f611c65fbcde735378bdec38e1039c5466503511e8c645bbe42795c752b", + }, + { + "96104f0a28f9cca40901c066cd435134662a3b053eb6c8df80ee0d05dc941963", + "d7f67b5f52cbcd1a1367e0376a8eb1012b634acfcf35e8322bae8b22bb9e8dea", + "9735c92d150dcee0ade5a8d1822f46a4db22c9cda25f33773ae856fe374a3e8a", + "ea14d521d83328dba70982d42094300585818cc2da609fdb1f73bb32235576ff", + "b498aa21d4ba4879ea9fd4225e93bacc760dcd9c119f8f38ab0716457d1a6f85", + }, + { + "d8f94a0bbb1de80aea17aab42e2ffb982e73fc49b649a318479e951e392d8728", + "d026ddb445fb3bbf3020e4b55ed7b5f9b7fd1278c34978ca1a6ed6b358dadbae", + "d19e6beca3b26b9d1abc127835ebeb7a6c19c33dec8ec472e1c4d458202f4ec8", + "0d561f54728ad837ae108ec66c2ece2bb3b26041d3ee9b77fdc6d36d9ebfb2e3", + "d012afe3d1d932304e613c91545bf571cf2c7281d6cafa8e81393a551f675540", + }, + { + "3f8c969678a8abdbfb76866a142c284a6f01636c1c1607947436e0d2c30d5245", + "c522b38c391d1c3fa539cc58802bc66ac34bb3c73accd7f41b47f539bedcd016", + "ea5b6a0053237f7712b1d2347c447d3e83e0f2191762d07e1f53f8eb7f2dfeaa", + "23cccd3b63a9456e4425098b6df36f28c8999461a85e4b2b0c8d8f53c62c9ea9", + "7e27efa50eed1c2ac51a12089cbab6a192624709c7330c016d5bc9af146584c1", + }, + { + "e66415c58b981c7f1f2b8f45a42149e9144616ff6de49ff83d97000ac6f6f992", + "2f1b82be8e65d610a4e588f000a89a733a9de98ffc1ac9d55e138b3b0a855da0", + "65aeda1b47f66c978a4a41d4dcdfbd8eab5cdeb135695c2b0c28f54417b1486d", + "43e5b0a5cc8146c03ac63e6f8cf3d8825a9ca1ed53ea4a88304af4ddf5461b33", + "bb4ab31c334e55d378937978c90bb33779b23cd5ef4c68342a394f4ec8fa1ada", + }, + { + "58487c9818c9d28ddf97cb09c13331941e05d0b62bf4c35ee368de80b552e4d1", + "f3869b68183b2e4341307653e8f659bd7cd20e37ea5c00f5a9e203a8fa92359a", + "c7e4153a18b4162f5c1f60e1ba483264aa5bb3f4889dca45b434fcd30b9cf56f", + "5ae9408ab3156b8828c3e639730bd5e5db93d7afe2cee3fcda98079316c5bb3a", + "0224d85ae8f17bfe127ec24b8960b7639a0dbde9c8c39a0575b939448687bb14", + }, + { + "ad66f3b654844e53d6fb3568134fae75672ba63868c113659d3ca58c2c39d24f", + "d38f2ef8dfdc7224fef596130c3f4ff68ac83d3f932a56ee908061466ac28572", + "d0c79d0340dc66f0a895ce5ad60a933cf6be659569096fb9d4b81e5d32292372", + "1ea22db4708ed585ab541a4c70c3069f8e2c0c1faa188ddade3efaa53c8329f6", + "886a7187246934aedf2794748deadfe038c9fd7e78d4b7be76c5a651440ac843", + }, + { + "eed48604eab279c6ad8128aa83483a3da0928271a4cae1a5745671284e1fb89d", + "e2342a8450fc0adfa0ea2fbd0b1d28f100f0a3a905a3da29de34d1353afa7df7", + "d2dbe07e0f2dbc3dbb01c70092e3c4247d12827ddcd8d76534fd740a65c30de2", + "4c4b30eb6a2bfa17312f5729b4212cb51c2eee8fbfaea82a0e957ca68f4f6a30", + "dcae613ac5641ff9d4c3ca58632245f93b0b8657fe4d48bac7b062cc53dd21ad", + }, + { + "f35b315287b268c0d0b386fb5b31810f65e1c4497cffae24527f69a3abac3884", + "049016230dbef7a14a439e5ab2f6d12e78cb8df127db4e0c312673b3c361e350", + "1b3b1925a8a535cd7d78725d25298f45bba8ca3dee2cdaabf14241c9b51b37c4", + "04c9685dae1f8eb72a6438f24a87dc83a56d79c9024edf7e01aa1ae34656f29e", + "b48469d0428c223b93cd1fe34bb2cafd3fb78a8fa15f98f89f1ac9c0ce7c9001", + }, + { + "d6cf082c5d9a96e756a94a2e27703138580a7c7c1af505c96c3abf7ab6802e1d", + "67cd02b0b8b0adcf6fdd4d4d64a1f4193ced68bb8605d0ec941a62011326d140", + "a842d5127c93a435e4672dcadd9fccbfd0e9208c79c5404848b298597eccdbdb", + "d5c6bd6d81b99659d0bafe91025b6ecf73b16c6b07931cf44718b13f00fde3f7", + "8aa160795b587f4be53aa35d26e9b618b4cd6ec765b523bc908e53c258ca8fd4", + }, + { + "dda32c91c95527a645b00dd05d13f0b98ed612a726ce5f5221431430b7660944", + "eba026f92a8ffb5e95060a22e15d597fe838a99a0b2bbcb423c933b6bc718c50", + "7dbaf9c313a1ff9128c54d6cd740c7d0cc46bca588e7910d438dd619ca4fd69a", + "5bb20a145de83ba27a0c261e1f54bfd7dcea61888fc2eebbe6166876f7b000b8", + "3a96f152ad8bf355cccb307e4a40108aa17f8e925522a2b5bb0b3f1e1a262914", + }, + { + "63c500acbd4ff747f7dadde7d3286482894ac4d7fe68f396712bca29879aa65c", + "9663cd3c2030a5fe4a3ea3cc9a1d182b3a63ade68616aaeb4caf40b495f6f227", + "b1e7d9070ac820d986d839b79f7aa594dcf708473529dad87af8682cc6197374", + "1f7a97584d8db9f395b9ac4447de4b33c5c1f5020187cd4996286a49b07eb8a7", + "4d2a974ec12dcf525b5654d31053460850c3092648b7e15598b7131d2930e9fb", + }, + { + "91f340961029398cc8bcd906388044a6801d24328efdf919d8ed0c609862a073", + "45a588500d00142e2226231c01fd11ac6f842ab6a85872418f5b6a1999f8bd98", + "397233c96069b6f4a57d6e93f759fa327222eaef71fc981afa673b248206df3f", + "062123ef9782e3893f7b2e1d557b4ecce92b9f9aa8577e76380f228b75152f84", + "235848cb04230a16d8978aa7c19fe7fbff3ebe29389ea6eb24fb8bc3cb50afc6", + }, + { + "46120b4da6ba4eb54fb65213cfef99b812c86f7c42a1de1107f8e8c12c0c3b6b", + "cc19a97a99ad71ce348bcf831c0218e6a1f0a8d52186cabe6298b56f24e640f9", + "c54631bb62f0f9d86b3301fcb2a671621e655e578c95504af5f78da83f7fec4f", + "ab73cc20c75260ff3a7cefea8039291d4d15114a07a9250375628cca32225bb6", + "70040a360b6a2dfa881272ef4fa6616e2e8fcc45194fa2a21a1eef1160271cd5", + }, + { + "f0a69ded09f1d731ab9561d0c3a40b7ef30bcb2bf60f92beccd8734e2441403d", + "ea732822a381c46a7ac9999bf5ef85e16b7460b26aaf6c1a1c6ffa8c8c82c923", + "110decbff79c382b1e60af4259564a3c473087792350b24fca98ae9a74ba9dd9", + "81bdee95aecdcf821a9d682f79056f1abdcf1245d2f3b55244447881a283e0d4", + "1bc29d4470ccf97d4e35e8d3cd4b12e3ebf2cb0a82425d35984aeedf7ad0f6f9", + }, + { + "e79cf4536fb1547e57626c0f1a87f71a396fdfb985b00731c0c2876a00645eda", + "04213fc02b59c372e3e7f53faa71a2f73b31064102cb6fc8b68432ba7cdf7eb4", + "ca1c750aaed53bc30dac07d0696ed86bcd7cdbbcbd3d15bb90d90cb5c6117bac", + "c68cd0872a42a3a64e8a229ef7fcad3d722047d5af966f7dda4d4e32d0d57203", + "bfdd3d07563d966d95afe4b8abea4b567265fceea8c4ecddb0946256c33e07b2", + }, + { + "81a40db4cddaf076e0206bd2b0fa7470a72cc456bad34aa3a0469a4859f286be", + "52156799fd86cc63345cdbffd65ef4f5f8df0ffd9906a40af5f41d269bbcff5d", + "54d61aa0b0b17a87f1376fe89cd8cd6b314827c1f1b9e5e7b20e7a7eee2a8335", + "4553fb2cab8555068c32f86ceb692bbf1c2beeaf21627ef1b1be57344b52eea8", + "55096b6710ade3bbe38702458ee13faa10c24413261bc076f17675dcbf2c4ee6", + }, + { + "d28e4a9e6832a3a4dad014a2bf1f666f01093cbba8b9ad4d1dcad3ea10cb42b9", + "8ca134404c8fa199b0c72cb53cfa0adcf196dfa560fb521017cce5cbace3ba59", + "3a6c39a1e5f9f550f1debedd9a0bc84210cce5f9834797db8f14122bf5817e45", + "eb632ca818b4f659630226a339a3ce536b31c8e1e686fea8da3760e8abc20b8e", + "9fbb3fbaf1cd54ee0cd90685f59b082545983f1f662ef701332379306a6ad546", + }, + { + "f9c4bfad9e2a3d98c44c12734881a6f217d6c9523cc210772fad1297993454b4", + "b85960fcadda8d0a0960559b6b7818a0d8d4574b4e928b17c9b498fa9ffab4ef", + "6a1d0ef23ce0b40a7077ecb7b7264945054e3bdb58ee25e1b0ee8b3e19dbfcdc", + "bb145dddcb75074a6a03249fca1aa7d6fa9549e3ed965f138ca5e7071b7878f2", + "87d3faea4a98e41009eb8625611ea0fc12094c295af540c126c14a0f55afa76e", + }, + { + "656df4789a369d220aceb7b318517787d27004ecccedea019d623bcb2d79f5ff", + "acf83e30afb2a5066728ec5d93564c08abe5e68e3a2a2ff953bdcf4d44f9da06", + "bdda65efe56d7890286aada1452f62f85ba157d0b4621ba641de15d8d1c9e331", + "958beef5dc6babc6de383c32ad7dd3a6d6eb8bb3236ed5558eec0f9eb31e5458", + "6f6d4ee36d9d76e462c9635adfbb6073134a276cfc7cb86762004ec47197afa0", + }, + }; + + HDNode node; + ed25519_secret_key private_key; + uint8_t chain_code[32]; + ed25519_public_key public_key, mul; + uint8_t shared_key[SHA3_256_DIGEST_LENGTH]; + + for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { + nem_private_key(tests[i].private_key, private_key); + + ck_assert(hdnode_from_xprv(0, 0, chain_code, private_key, + ED25519_KECCAK_NAME, &node)); + memcpy(public_key, fromhex(tests[i].public_key), 32); + + ck_assert(hdnode_get_nem_shared_key( + &node, public_key, fromhex(tests[i].salt), mul, shared_key)); + ck_assert_mem_eq(mul, fromhex(tests[i].mul), sizeof(mul)); + ck_assert_mem_eq(shared_key, fromhex(tests[i].shared_key), + sizeof(shared_key)); + } +} +END_TEST + +// test vectors from +// https://raw.githubusercontent.com/NemProject/nem-test-vectors/master/4.test-cipher.dat +START_TEST(test_nem_cipher) { + static const struct { + const char *private_key; + const char *public_key; + const char *salt; + const char *iv; + const char *input; + const char *output; + } tests[] = { + { + "3140f94c79f249787d1ec75a97a885980eb8f0a7d9b7aa03e7200296e422b2b6", + "57a70eb553a7b3fd621f0dba6abf51312ea2e2a2a1e19d0305516730f4bcbd21", + "83616c67f076d356fd1288a6e0fd7a60488ba312a3adf0088b1b33c7655c3e6a", + "a73ff5c32f8fd055b09775817a6a3f95", + "86ddb9e713a8ebf67a51830eff03b837e147c20d75e67b2a54aa29e98c", + "70815da779b1b954d7a7f00c16940e9917a0412a06a444b539bf147603eef87f", + }, + { + "3140f94c79f249787d1ec75a97a885980eb8f0a7d9b7aa03e7200296e422b2b6", + "57a70eb553a7b3fd621f0dba6abf51312ea2e2a2a1e19d0305516730f4bcbd21", + "703ce0b1d276b10eef35672df03234385a903460db18ba9d4e05b3ad31abb284", + "91246c2d5493867c4fa3e78f85963677", + "86ddb9e713a8ebf67a51830eff03b837e147c20d75e67b2a54aa29e98c", + "564b2f40d42c0efc1bd6f057115a5abd1564cae36d7ccacf5d825d38401aa894", + }, + { + "3140f94c79f249787d1ec75a97a885980eb8f0a7d9b7aa03e7200296e422b2b6", + "57a70eb553a7b3fd621f0dba6abf51312ea2e2a2a1e19d0305516730f4bcbd21", + "b22e8e8e7373ac31ca7f0f6eb8b93130aba5266772a658593f3a11792e7e8d92", + "9f8e33d82374dad6aac0e3dbe7aea704", + "86ddb9e713a8ebf67a51830eff03b837e147c20d75e67b2a54aa29e98c", + "7cab88d00a3fc656002eccbbd966e1d5d14a3090d92cf502cdbf843515625dcf", + }, + { + "3140f94c79f249787d1ec75a97a885980eb8f0a7d9b7aa03e7200296e422b2b6", + "57a70eb553a7b3fd621f0dba6abf51312ea2e2a2a1e19d0305516730f4bcbd21", + "af646c54cd153dffe453b60efbceeb85c1e95a414ea0036c4da94afb3366f5d9", + "6acdf8e01acc8074ddc807281b6af888", + "86ddb9e713a8ebf67a51830eff03b837e147c20d75e67b2a54aa29e98c", + "aa70543a485b63a4dd141bb7fd78019092ac6fad731e914280a287c7467bae1a", + }, + { + "3140f94c79f249787d1ec75a97a885980eb8f0a7d9b7aa03e7200296e422b2b6", + "57a70eb553a7b3fd621f0dba6abf51312ea2e2a2a1e19d0305516730f4bcbd21", + "d9c0d386636c8a024935c024589f9cd39e820a16485b14951e690a967830e269", + "f2e9f18aeb374965f54d2f4e31189a8f", + "86ddb9e713a8ebf67a51830eff03b837e147c20d75e67b2a54aa29e98c", + "33d97c216ea6498dfddabf94c2e2403d73efc495e9b284d9d90aaff840217d25", + }, + { + "d5c0762ecea2cd6b5c56751b58debcb32713aab348f4a59c493e38beb3244f3a", + "66a35941d615b5644d19c2a602c363ada8b1a8a0dac3682623852dcab4afac04", + "06c227baac1ae3b0b1dc583f4850f13f9ba5d53be4a98fa5c3ea16217847530d", + "3735123e78c44895df6ea33fa57e9a72", + "86ddb9e713a8ebf67a51830eff03b837e147c20d75e67b2a54aa29e98c", + "d5b5d66ba8cee0eb7ecf95b143fa77a46d6de13749e12eff40f5a7e649167ccb", + }, + { + "d5c0762ecea2cd6b5c56751b58debcb32713aab348f4a59c493e38beb3244f3a", + "66a35941d615b5644d19c2a602c363ada8b1a8a0dac3682623852dcab4afac04", + "92f55ba5bc6fc2f23e3eedc299357c71518e36ba2447a4da7a9dfe9dfeb107b5", + "1cbc4982e53e370052af97ab088fa942", + "86ddb9e713a8ebf67a51830eff03b837e147c20d75e67b2a54aa29e98c", + "d48ef1ef526d805656cfc932aff259eadb17aa3391dde1877a722cba31d935b2", + }, + { + "d5c0762ecea2cd6b5c56751b58debcb32713aab348f4a59c493e38beb3244f3a", + "66a35941d615b5644d19c2a602c363ada8b1a8a0dac3682623852dcab4afac04", + "10f15a39ba49866292a43b7781bc71ca8bbd4889f1616461caf056bcb91b0158", + "c40d531d92bfee969dce91417346c892", + "49de3cd5890e0cd0559f143807ff688ff62789b7236a332b7d7255ec0b4e73e6b3a" + "4", + "e6d75afdb542785669b42198577c5b358d95397d71ec6f5835dca46d332cc08dbf73" + "ea790b7bcb169a65719c0d55054c", + }, + { + "d5c0762ecea2cd6b5c56751b58debcb32713aab348f4a59c493e38beb3244f3a", + "66a35941d615b5644d19c2a602c363ada8b1a8a0dac3682623852dcab4afac04", + "9c01ed42b219b3bbe1a43ae9d7af5c1dd09363baacfdba8f4d03d1046915e26e", + "059a35d5f83249e632790015ed6518b9", + "49de3cd5890e0cd0559f143807ff688ff62789b7236a332b7d7255ec0b4e73e6b3a" + "4", + "5ef11aadff2eccee8b712dab968fa842eb770818ec0e6663ed242ea8b6bbc1c66d62" + "85ee5b5f03d55dfee382fb4fa25d", + }, + { + "d5c0762ecea2cd6b5c56751b58debcb32713aab348f4a59c493e38beb3244f3a", + "66a35941d615b5644d19c2a602c363ada8b1a8a0dac3682623852dcab4afac04", + "bc1067e2a7415ea45ff1ca9894338c591ff15f2e57ae2789ae31b9d5bea0f11e", + "8c73f0d6613898daeefa3cf8b0686d37", + "49de3cd5890e0cd0559f143807ff688ff62789b7236a332b7d7255ec0b4e73e6b3a" + "4", + "6d220213b1878cd40a458f2a1e6e3b48040455fdf504dcd857f4f2ca1ad642e3a44f" + "c401d04e339d302f66a9fad3d919", + }, + { + "9ef87ba8aa2e664bdfdb978b98bc30fb61773d9298e7b8c72911683eeff41921", + "441e76d7e53be0a967181076a842f69c20fd8c0e3f0ce3aa421b490b059fe094", + "cf4a21cb790552165827b678ca9695fcaf77566d382325112ff79483455de667", + "bfbf5482e06f55b88bdd9e053b7eee6e", + "49de3cd5890e0cd0559f143807ff688ff62789b7236a332b7d7255ec0b4e73e6b3a" + "4", + "1198a78c29c215d5c450f7b8513ead253160bc9fde80d9cc8e6bee2efe9713cf5a09" + "d6293c41033271c9e8c22036a28b", + }, + { + "9ef87ba8aa2e664bdfdb978b98bc30fb61773d9298e7b8c72911683eeff41921", + "441e76d7e53be0a967181076a842f69c20fd8c0e3f0ce3aa421b490b059fe094", + "eba5eae8aef79114082c3e70baef95bb02edf13b3897e8be7a70272962ef8838", + "af9a56da3da18e2fbd2948a16332532b", + "49de3cd5890e0cd0559f143807ff688ff62789b7236a332b7d7255ec0b4e73e6b3a" + "4", + "1062ab5fbbdee9042ad35bdadfd3047c0a2127fe0f001da1be1b0582185edfc9687b" + "e8d68f85795833bb04af9cedd3bb", + }, + { + "9ef87ba8aa2e664bdfdb978b98bc30fb61773d9298e7b8c72911683eeff41921", + "441e76d7e53be0a967181076a842f69c20fd8c0e3f0ce3aa421b490b059fe094", + "518f8dfd0c138f1ffb4ea8029db15441d70abd893c3d767dc668f23ba7770e27", + "42d28307974a1b2a2d921d270cfce03b", + "49de3cd5890e0cd0559f143807ff688ff62789b7236a332b7d7255ec0b4e73e6b3a" + "4", + "005e49fb7c5da540a84b034c853fc9f78a6b901ea495aed0c2abd4f08f1a96f9ffef" + "c6a57f1ac09e0aea95ca0f03ffd8", + }, + { + "9ef87ba8aa2e664bdfdb978b98bc30fb61773d9298e7b8c72911683eeff41921", + "441e76d7e53be0a967181076a842f69c20fd8c0e3f0ce3aa421b490b059fe094", + "582fdf58b53715c26e10ba809e8f2ab70502e5a3d4e9a81100b7227732ab0bbc", + "91f2aad3189bb2edc93bc891e73911ba", + "49de3cd5890e0cd0559f143807ff688ff62789b7236a332b7d7255ec0b4e73e6b3a" + "4", + "821a69cb16c57f0cb866e590b38069e35faec3ae18f158bb067db83a11237d29ab1e" + "6b868b3147236a0958f15c2e2167", + }, + { + "9ef87ba8aa2e664bdfdb978b98bc30fb61773d9298e7b8c72911683eeff41921", + "441e76d7e53be0a967181076a842f69c20fd8c0e3f0ce3aa421b490b059fe094", + "a415b4c006118fb72fc37b2746ef288e23ac45c8ff7ade5f368a31557b6ac93a", + "2b7c5f75606c0b8106c6489ea5657a9e", + "24512b714aefd5cbc4bcc4ef44ce6c67ffc447c65460a6c6e4a92e85", + "2781d5ee8ef1cb1596f8902b33dfae5045f84a987ca58173af5830dbce386062", + }, + { + "ed93c5a101ab53382ceee4f7e6b5aa112621d3bb9d18891509b1834ede235bcc", + "5a5e14c633d7d269302849d739d80344ff14db51d7bcda86045723f05c4e4541", + "47e73ec362ea82d3a7c5d55532ad51d2cdf5316b981b2b2bd542b0efa027e8ea", + "b2193f59030c8d05a7d3577b7f64dd33", + "24512b714aefd5cbc4bcc4ef44ce6c67ffc447c65460a6c6e4a92e85", + "3f43912db8dd6672b9996e5272e18c4b88fec9d7e8372db9c5f4709a4af1d86f", + }, + { + "ed93c5a101ab53382ceee4f7e6b5aa112621d3bb9d18891509b1834ede235bcc", + "5a5e14c633d7d269302849d739d80344ff14db51d7bcda86045723f05c4e4541", + "aaa006c57b6d1e402650577fe9787d8d285f4bacd7c01f998be49c766f8860c7", + "130304ddb9adc8870cf56bcae9487b7f", + "24512b714aefd5cbc4bcc4ef44ce6c67ffc447c65460a6c6e4a92e85", + "878cc7d8c0ef8dac0182a78eedc8080a402f59d8062a6b4ca8f4a74f3c3b3de7", + }, + { + "ed93c5a101ab53382ceee4f7e6b5aa112621d3bb9d18891509b1834ede235bcc", + "5a5e14c633d7d269302849d739d80344ff14db51d7bcda86045723f05c4e4541", + "28dc7ccd6c2a939eef64b8be7b9ae248295e7fcd8471c22fa2f98733fea97611", + "cb13890d3a11bc0a7433738263006710", + "24512b714aefd5cbc4bcc4ef44ce6c67ffc447c65460a6c6e4a92e85", + "e74ded846bebfa912fa1720e4c1415e6e5df7e7a1a7fedb5665d68f1763209a4", + }, + { + "ed93c5a101ab53382ceee4f7e6b5aa112621d3bb9d18891509b1834ede235bcc", + "5a5e14c633d7d269302849d739d80344ff14db51d7bcda86045723f05c4e4541", + "79974fa2cad95154d0873902c153ccc3e7d54b17f2eeb3f29b6344cad9365a9a", + "22123357979d20f44cc8eb0263d84e0e", + "24512b714aefd5cbc4bcc4ef44ce6c67ffc447c65460a6c6e4a92e85", + "eb14dec7b8b64d81a2ee4db07b0adf144d4f79a519bbf332b823583fa2d45405", + }, + { + "ed93c5a101ab53382ceee4f7e6b5aa112621d3bb9d18891509b1834ede235bcc", + "5a5e14c633d7d269302849d739d80344ff14db51d7bcda86045723f05c4e4541", + "3409a6f8c4dcd9bd04144eb67e55a98696b674735b01bf1196191f29871ef966", + "a823a0965969380ea1f8659ea5fd8fdd", + "24512b714aefd5cbc4bcc4ef44ce6c67ffc447c65460a6c6e4a92e85", + "00a7eb708eae745847173f8217efb05be13059710aee632e3f471ac3c6202b51", + }, + { + "a73a0b2686f7d699c018b6b08a352856e556070caa329c26241aec889eefde10", + "9b493403bee45ae6277430ef8d0c4163ffd81ace2db6c7821205da09a664a86c", + "c25701b9b7328c4ac3d23223d10623bd527c0a98e38ae9c62fbc403c80ab20ae", + "4b4ee0e4443779f3af429a749212f476", + "b6926d0ec82cec86c0d27ec9a33a0e0f", + "f39f7d66e0fde39ecdf58be2c0ef361a17cfd6843e310adbe0ec3118cd72800d", + }, + { + "a73a0b2686f7d699c018b6b08a352856e556070caa329c26241aec889eefde10", + "9b493403bee45ae6277430ef8d0c4163ffd81ace2db6c7821205da09a664a86c", + "31d18fdffc480310828778496ff817039df5d6f30bf6d9edd0b4396863d05f93", + "418bcbdf52860a450bfacc96920d02cf", + "b6926d0ec82cec86c0d27ec9a33a0e0f", + "0e6ce9889fe7b3cd82794b0ae27c1f5985d2f2a1f398371a138f8db1df1f54de", + }, + { + "e2e4dee102fad0f47f60202269605589cd9cf70f816b34016796c74b766f3041", + "c5ce283033a3255ae14d42dff1e4c18a224ac79d084b285123421b105ee654c9", + "56b4c645f81dbfb6ba0c6d3f1626e1e5cd648eeb36562715f7cd7e9ea86a0d7f", + "dc9bdce76d68d2e4d72267cf4e72b022", + "b6926d0ec82cec86c0d27ec9a33a0e0f", + "dc6f046c3008002041517a7c4f3ababe609cf02616fcccda39c075d1be4175f5", + }, + { + "e2e4dee102fad0f47f60202269605589cd9cf70f816b34016796c74b766f3041", + "c5ce283033a3255ae14d42dff1e4c18a224ac79d084b285123421b105ee654c9", + "df180b91986c8c7000792f96d1faa61e30138330430a402322be1855089b0e7f", + "ccf9b77341c866465b474e2f4a3b1cf8", + "b6926d0ec82cec86c0d27ec9a33a0e0f", + "94e4ae89041437f39826704f02cb5d775226f34344635e592846417497a5020b", + }, + { + "e2e4dee102fad0f47f60202269605589cd9cf70f816b34016796c74b766f3041", + "c5ce283033a3255ae14d42dff1e4c18a224ac79d084b285123421b105ee654c9", + "a0eee7e84c76e63fdae6e938b43330775eaf17d260e40b98c9e6616b668102a7", + "662c681cfec6f6d052ff0e2c1255f2c2", + "b6926d0ec82cec86c0d27ec9a33a0e0f", + "70bba3c48be9c75a144b1888ca3d21a6b21f52eec133981a024390a6a0ba36f9", + }, + { + "e2e4dee102fad0f47f60202269605589cd9cf70f816b34016796c74b766f3041", + "c5ce283033a3255ae14d42dff1e4c18a224ac79d084b285123421b105ee654c9", + "c6acd2d90eb782c3053b366680ffa0e148de81fea198c87bb643869fd97e5cb0", + "908dc33ba80520f2f0f04e7890e3a3c0", + "b6926d0ec82cec86c0d27ec9a33a0e0f", + "f6efe1d76d270aac264aa35d03049d9ce63be1996d543aef00559219c8666f71", + }, + }; + + HDNode node; + ed25519_secret_key private_key; + uint8_t chain_code[32]; + ed25519_public_key public_key; + uint8_t salt[sizeof(public_key)]; + + uint8_t iv[AES_BLOCK_SIZE]; + uint8_t buffer[FROMHEX_MAXLEN]; + + uint8_t input[FROMHEX_MAXLEN]; + uint8_t output[FROMHEX_MAXLEN]; + + for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { + nem_private_key(tests[i].private_key, private_key); + + ck_assert(hdnode_from_xprv(0, 0, chain_code, private_key, + ED25519_KECCAK_NAME, &node)); + memcpy(public_key, fromhex(tests[i].public_key), 32); + memcpy(salt, fromhex(tests[i].salt), sizeof(salt)); + + size_t input_size = strlen(tests[i].input) / 2; + size_t output_size = strlen(tests[i].output) / 2; + + memcpy(input, fromhex(tests[i].input), input_size); + memcpy(output, fromhex(tests[i].output), output_size); + + memcpy(iv, fromhex(tests[i].iv), sizeof(iv)); + ck_assert(hdnode_nem_encrypt(&node, public_key, iv, salt, input, input_size, + buffer)); + ck_assert_uint_eq(output_size, NEM_ENCRYPTED_SIZE(input_size)); + ck_assert_mem_eq(buffer, output, output_size); + + memcpy(iv, fromhex(tests[i].iv), sizeof(iv)); + ck_assert(hdnode_nem_decrypt(&node, public_key, iv, salt, output, + output_size, buffer)); + ck_assert_uint_eq(input_size, NEM_DECRYPTED_SIZE(buffer, output_size)); + ck_assert_mem_eq(buffer, input, input_size); + } +} +END_TEST + +START_TEST(test_nem_transaction_transfer) { + nem_transaction_ctx ctx; + + uint8_t buffer[1024], hash[SHA3_256_DIGEST_LENGTH]; + + // http://bob.nem.ninja:8765/#/transfer/0acbf8df91e6a65dc56c56c43d65f31ff2a6a48d06fc66e78c7f3436faf3e74f + + nem_transaction_start( + &ctx, + fromhex( + "e59ef184a612d4c3c4d89b5950eb57262c69862b2f96e59c5043bf41765c482f"), + buffer, sizeof(buffer)); + + ck_assert(nem_transaction_create_transfer( + &ctx, NEM_NETWORK_TESTNET, 0, NULL, 0, 0, + "TBGIMRE4SBFRUJXMH7DVF2IBY36L2EDWZ37GVSC4", 50000000000000, NULL, 0, + false, 0)); + + keccak_256(ctx.buffer, ctx.offset, hash); + ck_assert_mem_eq( + hash, + fromhex( + "0acbf8df91e6a65dc56c56c43d65f31ff2a6a48d06fc66e78c7f3436faf3e74f"), + sizeof(hash)); + + // http://bob.nem.ninja:8765/#/transfer/3409d9ece28d6296d6d5e220a7e3cb8641a3fb235ffcbd20c95da64f003ace6c + + nem_transaction_start( + &ctx, + fromhex( + "994793ba1c789fa9bdea918afc9b06e2d0309beb1081ac5b6952991e4defd324"), + buffer, sizeof(buffer)); + + ck_assert(nem_transaction_create_transfer( + &ctx, NEM_NETWORK_TESTNET, 14072100, NULL, 194000000, 14075700, + "TBLOODPLWOWMZ2TARX4RFPOSOWLULHXMROBN2WXI", 3000000, + (uint8_t *)"sending you 3 pairs of paddles\n", 31, false, 2)); + + ck_assert( + nem_transaction_write_mosaic(&ctx, "gimre.games.pong", "paddles", 2)); + + ck_assert(nem_transaction_write_mosaic(&ctx, "nem", "xem", 44000000)); + + keccak_256(ctx.buffer, ctx.offset, hash); + ck_assert_mem_eq( + hash, + fromhex( + "3409d9ece28d6296d6d5e220a7e3cb8641a3fb235ffcbd20c95da64f003ace6c"), + sizeof(hash)); + + // http://chain.nem.ninja/#/transfer/e90e98614c7598fbfa4db5411db1b331d157c2f86b558fb7c943d013ed9f71cb + + nem_transaction_start( + &ctx, + fromhex( + "8d07f90fb4bbe7715fa327c926770166a11be2e494a970605f2e12557f66c9b9"), + buffer, sizeof(buffer)); + + ck_assert(nem_transaction_create_transfer( + &ctx, NEM_NETWORK_MAINNET, 0, NULL, 0, 0, + "NBT3WHA2YXG2IR4PWKFFMO772JWOITTD2V4PECSB", 5175000000000, + (uint8_t *)"Good luck!", 10, false, 0)); + + keccak_256(ctx.buffer, ctx.offset, hash); + ck_assert_mem_eq( + hash, + fromhex( + "e90e98614c7598fbfa4db5411db1b331d157c2f86b558fb7c943d013ed9f71cb"), + sizeof(hash)); + + // http://chain.nem.ninja/#/transfer/40e89160e6f83d37f7c82defc0afe2c1605ae8c919134570a51dd27ea1bb516c + + nem_transaction_start( + &ctx, + fromhex( + "f85ab43dad059b9d2331ddacc384ad925d3467f03207182e01296bacfb242d01"), + buffer, sizeof(buffer)); + + ck_assert(nem_transaction_create_transfer( + &ctx, NEM_NETWORK_MAINNET, 77229, NULL, 30000000, 80829, + "NALICEPFLZQRZGPRIJTMJOCPWDNECXTNNG7QLSG3", 30000000, + fromhex("4d9dcf9186967d30be93d6d5404ded22812dbbae7c3f0de5" + "01bcd7228cba45bded13000eec7b4c6215fc4d3588168c92" + "18167cec98e6977359153a4132e050f594548e61e0dc61c1" + "53f0f53c5e65c595239c9eb7c4e7d48e0f4bb8b1dd2f5ddc"), + 96, true, 0)); + + keccak_256(ctx.buffer, ctx.offset, hash); + ck_assert_mem_eq( + hash, + fromhex( + "40e89160e6f83d37f7c82defc0afe2c1605ae8c919134570a51dd27ea1bb516c"), + sizeof(hash)); + + // http://chain.nem.ninja/#/transfer/882dca18dcbe075e15e0ec5a1d7e6ccd69cc0f1309ffd3fde227bfbc107b3f6e + + nem_transaction_start( + &ctx, + fromhex( + "f85ab43dad059b9d2331ddacc384ad925d3467f03207182e01296bacfb242d01"), + buffer, sizeof(buffer)); + + ck_assert(nem_transaction_create_transfer( + &ctx, NEM_NETWORK_MAINNET, 26730750, NULL, 179500000, 26734350, + "NBE223WPKEBHQPCYUC4U4CDUQCRRFMPZLOQLB5OP", 1000000, + (uint8_t *)"enjoy! :)", 9, false, 1)); + + ck_assert(nem_transaction_write_mosaic(&ctx, "imre.g", "tokens", 1)); + + keccak_256(ctx.buffer, ctx.offset, hash); + ck_assert_mem_eq( + hash, + fromhex( + "882dca18dcbe075e15e0ec5a1d7e6ccd69cc0f1309ffd3fde227bfbc107b3f6e"), + sizeof(hash)); +} +END_TEST + +START_TEST(test_nem_transaction_multisig) { + nem_transaction_ctx ctx, other_trans; + + uint8_t buffer[1024], inner[1024]; + const uint8_t *signature; + + // http://bob.nem.ninja:8765/#/multisig/7d3a7087023ee29005262016706818579a2b5499eb9ca76bad98c1e6f4c46642 + + nem_transaction_start( + &other_trans, + fromhex( + "abac2ee3d4aaa7a3bfb65261a00cc04c761521527dd3f2cf741e2815cbba83ac"), + inner, sizeof(inner)); + + ck_assert(nem_transaction_create_aggregate_modification( + &other_trans, NEM_NETWORK_TESTNET, 3939039, NULL, 16000000, 3960639, 1, + false)); + + ck_assert(nem_transaction_write_cosignatory_modification( + &other_trans, 2, + fromhex( + "e6cff9b3725a91f31089c3acca0fac3e341c00b1c8c6e9578f66c4514509c3b3"))); + + nem_transaction_start( + &ctx, + fromhex( + "59d89076964742ef2a2089d26a5aa1d2c7a7bb052a46c1de159891e91ad3d76e"), + buffer, sizeof(buffer)); + + ck_assert(nem_transaction_create_multisig(&ctx, NEM_NETWORK_TESTNET, 3939039, + NULL, 6000000, 3960639, + &other_trans)); + + signature = fromhex( + "933930a8828b560168bddb3137df9252048678d829aa5135fa27bb306ff6562efb927554" + "62988b852b0314bde058487d00e47816b6fb7df6bcfd7e1f150d1d00"); + ck_assert_int_eq(ed25519_sign_open_keccak(ctx.buffer, ctx.offset, + ctx.public_key, signature), + 0); + + nem_transaction_start( + &ctx, + fromhex( + "71cba4f2a28fd19f902ba40e9937994154d9eeaad0631d25d525ec37922567d4"), + buffer, sizeof(buffer)); + + ck_assert(nem_transaction_create_multisig_signature(&ctx, NEM_NETWORK_TESTNET, + 3939891, NULL, 6000000, + 3961491, &other_trans)); + + signature = fromhex( + "a849f13bfeeba808a8a4a79d579febe584d831a3a6ad03da3b9d008530b3d7a79fcf7156" + "121cd7ee847029d94af7ea7a683ca8e643dc5e5f489557c2054b830b"); + ck_assert_int_eq(ed25519_sign_open_keccak(ctx.buffer, ctx.offset, + ctx.public_key, signature), + 0); + + // http://chain.nem.ninja/#/multisig/1016cf3bdd61bd57b9b2b07b6ff2dee390279d8d899265bdc23d42360abe2e6c + + nem_transaction_start( + &other_trans, + fromhex( + "a1df5306355766bd2f9a64efdc089eb294be265987b3359093ae474c051d7d5a"), + inner, sizeof(inner)); + + ck_assert(nem_transaction_create_provision_namespace( + &other_trans, NEM_NETWORK_MAINNET, 59414272, NULL, 20000000, 59500672, + "dim", NULL, "NAMESPACEWH4MKFMBCVFERDPOOP4FK7MTBXDPZZA", 5000000000)); + + nem_transaction_start( + &ctx, + fromhex( + "cfe58463f0eaebceb5d00717f8aead49171a5d7c08f6b1299bd534f11715acc9"), + buffer, sizeof(buffer)); + + ck_assert(nem_transaction_create_multisig(&ctx, NEM_NETWORK_MAINNET, 59414272, + NULL, 6000000, 59500672, + &other_trans)); + + signature = fromhex( + "52a876a37511068fe214bd710b2284823921ec7318c01e083419a062eae5369c9c11c3ab" + "fdb590f65c717fab82873431d52be62e10338cb5656d1833bbdac70c"); + ck_assert_int_eq(ed25519_sign_open_keccak(ctx.buffer, ctx.offset, + ctx.public_key, signature), + 0); + + nem_transaction_start( + &ctx, + fromhex( + "1b49b80203007117d034e45234ffcdf402c044aeef6dbb06351f346ca892bce2"), + buffer, sizeof(buffer)); + + ck_assert(nem_transaction_create_multisig_signature(&ctx, NEM_NETWORK_MAINNET, + 59414342, NULL, 6000000, + 59500742, &other_trans)); + + signature = fromhex( + "b9a59239e5d06992c28840034ff7a7f13da9c4e6f4a6f72c1b1806c3b602f83a7d727a34" + "5371f5d15abf958208a32359c6dd77bde92273ada8ea6fda3dc76b00"); + ck_assert_int_eq(ed25519_sign_open_keccak(ctx.buffer, ctx.offset, + ctx.public_key, signature), + 0); + + nem_transaction_start( + &ctx, + fromhex( + "7ba4b39209f1b9846b098fe43f74381e43cb2882ccde780f558a63355840aa87"), + buffer, sizeof(buffer)); + + ck_assert(nem_transaction_create_multisig_signature(&ctx, NEM_NETWORK_MAINNET, + 59414381, NULL, 6000000, + 59500781, &other_trans)); + + signature = fromhex( + "e874ae9f069f0538008631d2df9f2e8a59944ff182e8672f743d2700fb99224aafb7a0ab" + "09c4e9ea39ee7c8ca04a8a3d6103ae1122d87772e871761d4f00ca01"); + ck_assert_int_eq(ed25519_sign_open_keccak(ctx.buffer, ctx.offset, + ctx.public_key, signature), + 0); +} +END_TEST + +START_TEST(test_nem_transaction_provision_namespace) { + nem_transaction_ctx ctx; + + uint8_t buffer[1024], hash[SHA3_256_DIGEST_LENGTH]; + + // http://bob.nem.ninja:8765/#/namespace/f7cab28da57204d01a907c697836577a4ae755e6c9bac60dcc318494a22debb3 + + nem_transaction_start( + &ctx, + fromhex( + "84afa1bbc993b7f5536344914dde86141e61f8cbecaf8c9cefc07391f3287cf5"), + buffer, sizeof(buffer)); + + ck_assert(nem_transaction_create_provision_namespace( + &ctx, NEM_NETWORK_TESTNET, 56999445, NULL, 20000000, 57003045, "gimre", + NULL, "TAMESPACEWH4MKFMBCVFERDPOOP4FK7MTDJEYP35", 5000000000)); + + keccak_256(ctx.buffer, ctx.offset, hash); + ck_assert_mem_eq( + hash, + fromhex( + "f7cab28da57204d01a907c697836577a4ae755e6c9bac60dcc318494a22debb3"), + sizeof(hash)); + + // http://bob.nem.ninja:8765/#/namespace/7ddd5fe607e1bfb5606e0ac576024c318c8300d237273117d4db32a60c49524d + + nem_transaction_start( + &ctx, + fromhex( + "244fa194e2509ac0d2fbc18779c2618d8c2ebb61c16a3bcbebcf448c661ba8dc"), + buffer, sizeof(buffer)); + + ck_assert(nem_transaction_create_provision_namespace( + &ctx, NEM_NETWORK_TESTNET, 21496797, NULL, 108000000, 21500397, "misc", + "alice", "TAMESPACEWH4MKFMBCVFERDPOOP4FK7MTDJEYP35", 5000000000)); + + keccak_256(ctx.buffer, ctx.offset, hash); + ck_assert_mem_eq( + hash, + fromhex( + "7ddd5fe607e1bfb5606e0ac576024c318c8300d237273117d4db32a60c49524d"), + sizeof(hash)); + + // http://chain.nem.ninja/#/namespace/57071aad93ca125dc231dc02c07ad8610cd243d35068f9b36a7d231383907569 + + nem_transaction_start( + &ctx, + fromhex( + "9f3c14f304309c8b72b2821339c4428793b1518bea72d58dd01f19d523518614"), + buffer, sizeof(buffer)); + + ck_assert(nem_transaction_create_provision_namespace( + &ctx, NEM_NETWORK_MAINNET, 26699717, NULL, 108000000, 26703317, "sex", + NULL, "NAMESPACEWH4MKFMBCVFERDPOOP4FK7MTBXDPZZA", 50000000000)); + + keccak_256(ctx.buffer, ctx.offset, hash); + ck_assert_mem_eq( + hash, + fromhex( + "57071aad93ca125dc231dc02c07ad8610cd243d35068f9b36a7d231383907569"), + sizeof(hash)); +} +END_TEST + +START_TEST(test_nem_transaction_mosaic_creation) { + nem_transaction_ctx ctx; + + uint8_t buffer[1024], hash[SHA3_256_DIGEST_LENGTH]; + + // http://bob.nem.ninja:8765/#/mosaic/68364353c29105e6d361ad1a42abbccbf419cfc7adb8b74c8f35d8f8bdaca3fa/0 + + nem_transaction_start( + &ctx, + fromhex( + "994793ba1c789fa9bdea918afc9b06e2d0309beb1081ac5b6952991e4defd324"), + buffer, sizeof(buffer)); + + ck_assert(nem_transaction_create_mosaic_creation( + &ctx, NEM_NETWORK_TESTNET, 14070896, NULL, 108000000, 14074496, + "gimre.games.pong", "paddles", "Paddles for the bong game.\n", 0, 10000, + true, true, 0, 0, NULL, NULL, NULL, + "TBMOSAICOD4F54EE5CDMR23CCBGOAM2XSJBR5OLC", 50000000000)); + + keccak_256(ctx.buffer, ctx.offset, hash); + ck_assert_mem_eq( + hash, + fromhex( + "68364353c29105e6d361ad1a42abbccbf419cfc7adb8b74c8f35d8f8bdaca3fa"), + sizeof(hash)); + + // http://bob.nem.ninja:8765/#/mosaic/b2f4a98113ff1f3a8f1e9d7197aa982545297fe0aa3fa6094af8031569953a55/0 + + nem_transaction_start( + &ctx, + fromhex( + "244fa194e2509ac0d2fbc18779c2618d8c2ebb61c16a3bcbebcf448c661ba8dc"), + buffer, sizeof(buffer)); + + ck_assert(nem_transaction_create_mosaic_creation( + &ctx, NEM_NETWORK_TESTNET, 21497248, NULL, 108000000, 21500848, + "alice.misc", "bar", "Special offer: get one bar extra by bying one foo!", + 0, 1000, false, true, 1, 1, "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J", + "nem", "xem", "TBMOSAICOD4F54EE5CDMR23CCBGOAM2XSJBR5OLC", 50000000000)); + + keccak_256(ctx.buffer, ctx.offset, hash); + ck_assert_mem_eq( + hash, + fromhex( + "b2f4a98113ff1f3a8f1e9d7197aa982545297fe0aa3fa6094af8031569953a55"), + sizeof(hash)); + + // http://chain.nem.ninja/#/mosaic/269c6fda657aba3053a0e5b138c075808cc20e244e1182d9b730798b60a1f77b/0 + + nem_transaction_start( + &ctx, + fromhex( + "58956ac77951622dc5f1c938affbf017c458e30e6b21ddb5783d38b302531f23"), + buffer, sizeof(buffer)); + + ck_assert(nem_transaction_create_mosaic_creation( + &ctx, NEM_NETWORK_MAINNET, 26729938, NULL, 108000000, 26733538, "jabo38", + "red_token", + "This token is to celebrate the release of Namespaces and Mosaics on the " + "NEM system. " + "This token was the fist ever mosaic created other than nem.xem. " + "There are only 10,000 Red Tokens that will ever be created. " + "It has no levy and can be traded freely among third parties.", + 2, 10000, false, true, 0, 0, NULL, NULL, NULL, + "NBMOSAICOD4F54EE5CDMR23CCBGOAM2XSIUX6TRS", 50000000000)); + + keccak_256(ctx.buffer, ctx.offset, hash); + ck_assert_mem_eq( + hash, + fromhex( + "269c6fda657aba3053a0e5b138c075808cc20e244e1182d9b730798b60a1f77b"), + sizeof(hash)); + + // http://chain.nem.ninja/#/mosaic/e8dc14821dbea4831d9051f86158ef348001447968fc22c01644fdaf2bda75c6/0 + + nem_transaction_start( + &ctx, + fromhex( + "a1df5306355766bd2f9a64efdc089eb294be265987b3359093ae474c051d7d5a"), + buffer, sizeof(buffer)); + + ck_assert(nem_transaction_create_mosaic_creation( + &ctx, NEM_NETWORK_MAINNET, 69251020, NULL, 20000000, 69337420, "dim", + "coin", "DIM COIN", 6, 9000000000, false, true, 2, 10, + "NCGGLVO2G3CUACVI5GNX2KRBJSQCN4RDL2ZWJ4DP", "dim", "coin", + "NBMOSAICOD4F54EE5CDMR23CCBGOAM2XSIUX6TRS", 500000000)); + + keccak_256(ctx.buffer, ctx.offset, hash); + ck_assert_mem_eq( + hash, + fromhex( + "e8dc14821dbea4831d9051f86158ef348001447968fc22c01644fdaf2bda75c6"), + sizeof(hash)); +} +END_TEST + +START_TEST(test_nem_transaction_mosaic_supply_change) { + nem_transaction_ctx ctx; + + uint8_t buffer[1024], hash[SHA3_256_DIGEST_LENGTH]; + + // http://bigalice2.nem.ninja:7890/transaction/get?hash=33a50fdd4a54913643a580b2af08b9a5b51b7cee922bde380e84c573a7969c50 + + nem_transaction_start( + &ctx, + fromhex( + "994793ba1c789fa9bdea918afc9b06e2d0309beb1081ac5b6952991e4defd324"), + buffer, sizeof(buffer)); + + ck_assert(nem_transaction_create_mosaic_supply_change( + &ctx, NEM_NETWORK_TESTNET, 14071648, NULL, 108000000, 14075248, + "gimre.games.pong", "paddles", 1, 1234)); + + keccak_256(ctx.buffer, ctx.offset, hash); + ck_assert_mem_eq( + hash, + fromhex( + "33a50fdd4a54913643a580b2af08b9a5b51b7cee922bde380e84c573a7969c50"), + sizeof(hash)); + + // http://bigalice2.nem.ninja:7890/transaction/get?hash=1ce8e8894d077a66ff22294b000825d090a60742ec407efd80eb8b19657704f2 + + nem_transaction_start( + &ctx, + fromhex( + "84afa1bbc993b7f5536344914dde86141e61f8cbecaf8c9cefc07391f3287cf5"), + buffer, sizeof(buffer)); + + ck_assert(nem_transaction_create_mosaic_supply_change( + &ctx, NEM_NETWORK_TESTNET, 14126909, NULL, 108000000, 14130509, + "jabo38_ltd.fuzzy_kittens_cafe", "coupons", 2, 1)); + + keccak_256(ctx.buffer, ctx.offset, hash); + ck_assert_mem_eq( + hash, + fromhex( + "1ce8e8894d077a66ff22294b000825d090a60742ec407efd80eb8b19657704f2"), + sizeof(hash)); + + // http://bigalice3.nem.ninja:7890/transaction/get?hash=694e493e9576d2bcf60d85747e302ac2e1cc27783187947180d4275a713ff1ff + + nem_transaction_start( + &ctx, + fromhex( + "b7ccc27b21ba6cf5c699a8dc86ba6ba98950442597ff9fa30e0abe0f5f4dd05d"), + buffer, sizeof(buffer)); + + ck_assert(nem_transaction_create_mosaic_supply_change( + &ctx, NEM_NETWORK_MAINNET, 53377685, NULL, 20000000, 53464085, "abvapp", + "abv", 1, 9000000)); + + keccak_256(ctx.buffer, ctx.offset, hash); + ck_assert_mem_eq( + hash, + fromhex( + "694e493e9576d2bcf60d85747e302ac2e1cc27783187947180d4275a713ff1ff"), + sizeof(hash)); + + // http://bigalice3.nem.ninja:7890/transaction/get?hash=09836334e123970e068d5b411e4d1df54a3ead10acf1ad5935a2cdd9f9680185 + + nem_transaction_start( + &ctx, + fromhex( + "75f001a8641e2ce5c4386883dda561399ed346177411b492a677b73899502f13"), + buffer, sizeof(buffer)); + + ck_assert(nem_transaction_create_mosaic_supply_change( + &ctx, NEM_NETWORK_MAINNET, 55176304, NULL, 20000000, 55262704, "sushi", + "wasabi", 2, 20)); + + keccak_256(ctx.buffer, ctx.offset, hash); + ck_assert_mem_eq( + hash, + fromhex( + "09836334e123970e068d5b411e4d1df54a3ead10acf1ad5935a2cdd9f9680185"), + sizeof(hash)); +} +END_TEST + +START_TEST(test_nem_transaction_aggregate_modification) { + nem_transaction_ctx ctx; + + uint8_t buffer[1024], hash[SHA3_256_DIGEST_LENGTH]; + + // http://bob.nem.ninja:8765/#/aggregate/6a55471b17159e5b6cd579c421e95a4e39d92e3f78b0a55ee337e785a601d3a2 + + nem_transaction_start( + &ctx, + fromhex( + "462ee976890916e54fa825d26bdd0235f5eb5b6a143c199ab0ae5ee9328e08ce"), + buffer, sizeof(buffer)); + + ck_assert(nem_transaction_create_aggregate_modification( + &ctx, NEM_NETWORK_TESTNET, 0, NULL, 22000000, 0, 2, false)); + + ck_assert(nem_transaction_write_cosignatory_modification( + &ctx, 1, + fromhex( + "994793ba1c789fa9bdea918afc9b06e2d0309beb1081ac5b6952991e4defd324"))); + ck_assert(nem_transaction_write_cosignatory_modification( + &ctx, 1, + fromhex( + "c54d6e33ed1446eedd7f7a80a588dd01857f723687a09200c1917d5524752f8b"))); + + keccak_256(ctx.buffer, ctx.offset, hash); + ck_assert_mem_eq( + hash, + fromhex( + "6a55471b17159e5b6cd579c421e95a4e39d92e3f78b0a55ee337e785a601d3a2"), + sizeof(hash)); + + // http://bob.nem.ninja:8765/#/aggregate/1fbdae5ba753e68af270930413ae90f671eb8ab58988116684bac0abd5726584 + + nem_transaction_start( + &ctx, + fromhex( + "6bf7849c1eec6a2002995cc457dc00c4e29bad5c88de63f51e42dfdcd7b2131d"), + buffer, sizeof(buffer)); + + ck_assert(nem_transaction_create_aggregate_modification( + &ctx, NEM_NETWORK_TESTNET, 6542254, NULL, 40000000, 6545854, 4, true)); + + ck_assert(nem_transaction_write_cosignatory_modification( + &ctx, 1, + fromhex( + "5f53d076c8c3ec3110b98364bc423092c3ec2be2b1b3c40fd8ab68d54fa39295"))); + ck_assert(nem_transaction_write_cosignatory_modification( + &ctx, 1, + fromhex( + "9eb199c2b4d406f64cb7aa5b2b0815264b56ba8fe44d558a6cb423a31a33c4c2"))); + ck_assert(nem_transaction_write_cosignatory_modification( + &ctx, 1, + fromhex( + "94b2323dab23a3faba24fa6ddda0ece4fbb06acfedd74e76ad9fae38d006882b"))); + ck_assert(nem_transaction_write_cosignatory_modification( + &ctx, 1, + fromhex( + "d88c6ee2a2cd3929d0d76b6b14ecb549d21296ab196a2b3a4cb2536bcce32e87"))); + + ck_assert(nem_transaction_write_minimum_cosignatories(&ctx, 2)); + + keccak_256(ctx.buffer, ctx.offset, hash); + ck_assert_mem_eq( + hash, + fromhex( + "1fbdae5ba753e68af270930413ae90f671eb8ab58988116684bac0abd5726584"), + sizeof(hash)); + + // http://chain.nem.ninja/#/aggregate/cc64ca69bfa95db2ff7ac1e21fe6d27ece189c603200ebc9778d8bb80ca25c3c + + nem_transaction_start( + &ctx, + fromhex( + "f41b99320549741c5cce42d9e4bb836d98c50ed5415d0c3c2912d1bb50e6a0e5"), + buffer, sizeof(buffer)); + + ck_assert(nem_transaction_create_aggregate_modification( + &ctx, NEM_NETWORK_MAINNET, 0, NULL, 40000000, 0, 5, false)); + + ck_assert(nem_transaction_write_cosignatory_modification( + &ctx, 1, + fromhex( + "1fbdbdde28daf828245e4533765726f0b7790e0b7146e2ce205df3e86366980b"))); + ck_assert(nem_transaction_write_cosignatory_modification( + &ctx, 1, + fromhex( + "f94e8702eb1943b23570b1b83be1b81536df35538978820e98bfce8f999e2d37"))); + ck_assert(nem_transaction_write_cosignatory_modification( + &ctx, 1, + fromhex( + "826cedee421ff66e708858c17815fcd831a4bb68e3d8956299334e9e24380ba8"))); + ck_assert(nem_transaction_write_cosignatory_modification( + &ctx, 1, + fromhex( + "719862cd7d0f4e875a6a0274c9a1738f38f40ad9944179006a54c34724c1274d"))); + ck_assert(nem_transaction_write_cosignatory_modification( + &ctx, 1, + fromhex( + "43aa69177018fc3e2bdbeb259c81cddf24be50eef9c5386db51d82386c41475a"))); + + keccak_256(ctx.buffer, ctx.offset, hash); + ck_assert_mem_eq( + hash, + fromhex( + "cc64ca69bfa95db2ff7ac1e21fe6d27ece189c603200ebc9778d8bb80ca25c3c"), + sizeof(hash)); +} +END_TEST +#endif + +START_TEST(test_multibyte_address) { + uint8_t priv_key[32]; + char wif[57]; + uint8_t pub_key[33]; + char address[40]; + uint8_t decode[24]; + int res; + + memcpy( + priv_key, + fromhex( + "47f7616ea6f9b923076625b4488115de1ef1187f760e65f89eb6f4f7ff04b012"), + 32); + ecdsa_get_wif(priv_key, 0, HASHER_SHA2D, wif, sizeof(wif)); + ck_assert_str_eq(wif, "13QtoXmbhELWcrwD9YA9KzvXy5rTaptiNuFR8L8ArpBNn4xmQj4N"); + ecdsa_get_wif(priv_key, 0x12, HASHER_SHA2D, wif, sizeof(wif)); + ck_assert_str_eq(wif, "3hrF6SFnqzpzABB36uGDf8dJSuUCcMmoJrTmCWMshRkBr2Vx86qJ"); + ecdsa_get_wif(priv_key, 0x1234, HASHER_SHA2D, wif, sizeof(wif)); + ck_assert_str_eq(wif, + "CtPTF9awbVbfDWGepGdVhB3nBhr4HktUGya8nf8dLxgC8tbqBreB9"); + ecdsa_get_wif(priv_key, 0x123456, HASHER_SHA2D, wif, sizeof(wif)); + ck_assert_str_eq(wif, + "uTrDevVQt5QZgoL3iJ1cPWHaCz7ZMBncM7QXZfCegtxiMHqBvWoYJa"); + ecdsa_get_wif(priv_key, 0x12345678, HASHER_SHA2D, wif, sizeof(wif)); + ck_assert_str_eq(wif, + "4zZWMzv1SVbs95pmLXWrXJVp9ntPEam1mfwb6CXBLn9MpWNxLg9huYgv"); + ecdsa_get_wif(priv_key, 0xffffffff, HASHER_SHA2D, wif, sizeof(wif)); + ck_assert_str_eq(wif, + "y9KVfV1RJXcTxpVjeuh6WYWh8tMwnAUeyUwDEiRviYdrJ61njTmnfUjE"); + + memcpy( + pub_key, + fromhex( + "0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71"), + 33); + ecdsa_get_address(pub_key, 0, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "1C7zdTfnkzmr13HfA2vNm5SJYRK6nEKyq8"); + ecdsa_get_address(pub_key, 0x12, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "8SCrMR2yYF7ciqoDbav7VLLTsVx5dTVPPq"); + ecdsa_get_address(pub_key, 0x1234, HASHER_SHA2_RIPEMD, HASHER_SHA2D, address, + sizeof(address)); + ck_assert_str_eq(address, "ZLH8q1UgMPg8o2s1MD55YVMpPV7vqms9kiV"); + ecdsa_get_address(pub_key, 0x123456, HASHER_SHA2_RIPEMD, HASHER_SHA2D, + address, sizeof(address)); + ck_assert_str_eq(address, "3ThqvsQVFnbiF66NwHtfe2j6AKn75DpLKpQSq"); + ecdsa_get_address(pub_key, 0x12345678, HASHER_SHA2_RIPEMD, HASHER_SHA2D, + address, sizeof(address)); + ck_assert_str_eq(address, "BrsGxAHga3VbopvSnb3gmLvMBhJNCGuDxBZL44"); + ecdsa_get_address(pub_key, 0xffffffff, HASHER_SHA2_RIPEMD, HASHER_SHA2D, + address, sizeof(address)); + ck_assert_str_eq(address, "3diW7paWGJyZRLGqMJZ55DMfPExob8QxQHkrfYT"); + + res = ecdsa_address_decode("1C7zdTfnkzmr13HfA2vNm5SJYRK6nEKyq8", 0, + HASHER_SHA2D, decode); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq(decode, + fromhex("0079fbfc3f34e7745860d76137da68f362380c606c"), 21); + res = ecdsa_address_decode("8SCrMR2yYF7ciqoDbav7VLLTsVx5dTVPPq", 0x12, + HASHER_SHA2D, decode); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq(decode, + fromhex("1279fbfc3f34e7745860d76137da68f362380c606c"), 21); + res = ecdsa_address_decode("ZLH8q1UgMPg8o2s1MD55YVMpPV7vqms9kiV", 0x1234, + HASHER_SHA2D, decode); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq(decode, + fromhex("123479fbfc3f34e7745860d76137da68f362380c606c"), 21); + res = ecdsa_address_decode("3ThqvsQVFnbiF66NwHtfe2j6AKn75DpLKpQSq", 0x123456, + HASHER_SHA2D, decode); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq( + decode, fromhex("12345679fbfc3f34e7745860d76137da68f362380c606c"), 21); + res = ecdsa_address_decode("BrsGxAHga3VbopvSnb3gmLvMBhJNCGuDxBZL44", + 0x12345678, HASHER_SHA2D, decode); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq( + decode, fromhex("1234567879fbfc3f34e7745860d76137da68f362380c606c"), 21); + res = ecdsa_address_decode("3diW7paWGJyZRLGqMJZ55DMfPExob8QxQHkrfYT", + 0xffffffff, HASHER_SHA2D, decode); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq( + decode, fromhex("ffffffff79fbfc3f34e7745860d76137da68f362380c606c"), 21); + + // wrong length + res = ecdsa_address_decode("BrsGxAHga3VbopvSnb3gmLvMBhJNCGuDxBZL44", 0x123456, + HASHER_SHA2D, decode); + ck_assert_int_eq(res, 0); + + // wrong address prefix + res = ecdsa_address_decode("BrsGxAHga3VbopvSnb3gmLvMBhJNCGuDxBZL44", + 0x22345678, HASHER_SHA2D, decode); + ck_assert_int_eq(res, 0); + + // wrong checksum + res = ecdsa_address_decode("BrsGxAHga3VbopvSnb3gmLvMBhJNCGuDxBZL45", + 0x12345678, HASHER_SHA2D, decode); + ck_assert_int_eq(res, 0); +} +END_TEST + +// https://tools.ietf.org/html/rfc6229#section-2 +START_TEST(test_rc4_rfc6229) { + static const size_t offsets[] = { + 0x0, 0xf0, 0x1f0, 0x2f0, 0x3f0, 0x5f0, 0x7f0, 0xbf0, 0xff0, + }; + + static const struct { + char key[65]; + char vectors[sizeof(offsets) / sizeof(*offsets)][65]; + } tests[] = { + {"0102030405", + { + "b2396305f03dc027ccc3524a0a1118a8" + "6982944f18fc82d589c403a47a0d0919", + "28cb1132c96ce286421dcaadb8b69eae" + "1cfcf62b03eddb641d77dfcf7f8d8c93", + "42b7d0cdd918a8a33dd51781c81f4041" + "6459844432a7da923cfb3eb4980661f6", + "ec10327bde2beefd18f9277680457e22" + "eb62638d4f0ba1fe9fca20e05bf8ff2b", + "45129048e6a0ed0b56b490338f078da5" + "30abbcc7c20b01609f23ee2d5f6bb7df", + "3294f744d8f9790507e70f62e5bbceea" + "d8729db41882259bee4f825325f5a130", + "1eb14a0c13b3bf47fa2a0ba93ad45b8b" + "cc582f8ba9f265e2b1be9112e975d2d7", + "f2e30f9bd102ecbf75aaade9bc35c43c" + "ec0e11c479dc329dc8da7968fe965681", + "068326a2118416d21f9d04b2cd1ca050" + "ff25b58995996707e51fbdf08b34d875", + }}, + {"01020304050607", + { + "293f02d47f37c9b633f2af5285feb46b" + "e620f1390d19bd84e2e0fd752031afc1", + "914f02531c9218810df60f67e338154c" + "d0fdb583073ce85ab83917740ec011d5", + "75f81411e871cffa70b90c74c592e454" + "0bb87202938dad609e87a5a1b079e5e4", + "c2911246b612e7e7b903dfeda1dad866" + "32828f91502b6291368de8081de36fc2", + "f3b9a7e3b297bf9ad804512f9063eff1" + "8ecb67a9ba1f55a5a067e2b026a3676f", + "d2aa902bd42d0d7cfd340cd45810529f" + "78b272c96e42eab4c60bd914e39d06e3", + "f4332fd31a079396ee3cee3f2a4ff049" + "05459781d41fda7f30c1be7e1246c623", + "adfd3868b8e51485d5e610017e3dd609" + "ad26581c0c5be45f4cea01db2f3805d5", + "f3172ceffc3b3d997c85ccd5af1a950c" + "e74b0b9731227fd37c0ec08a47ddd8b8", + }}, + {"0102030405060708", + { + "97ab8a1bf0afb96132f2f67258da15a8" + "8263efdb45c4a18684ef87e6b19e5b09", + "9636ebc9841926f4f7d1f362bddf6e18" + "d0a990ff2c05fef5b90373c9ff4b870a", + "73239f1db7f41d80b643c0c52518ec63" + "163b319923a6bdb4527c626126703c0f", + "49d6c8af0f97144a87df21d91472f966" + "44173a103b6616c5d5ad1cee40c863d0", + "273c9c4b27f322e4e716ef53a47de7a4" + "c6d0e7b226259fa9023490b26167ad1d", + "1fe8986713f07c3d9ae1c163ff8cf9d3" + "8369e1a965610be887fbd0c79162aafb", + "0a0127abb44484b9fbef5abcae1b579f" + "c2cdadc6402e8ee866e1f37bdb47e42c", + "26b51ea37df8e1d6f76fc3b66a7429b3" + "bc7683205d4f443dc1f29dda3315c87b", + "d5fa5a3469d29aaaf83d23589db8c85b" + "3fb46e2c8f0f068edce8cdcd7dfc5862", + }}, + {"0102030405060708090a", + { + "ede3b04643e586cc907dc21851709902" + "03516ba78f413beb223aa5d4d2df6711", + "3cfd6cb58ee0fdde640176ad0000044d" + "48532b21fb6079c9114c0ffd9c04a1ad", + "3e8cea98017109979084b1ef92f99d86" + "e20fb49bdb337ee48b8d8dc0f4afeffe", + "5c2521eacd7966f15e056544bea0d315" + "e067a7031931a246a6c3875d2f678acb", + "a64f70af88ae56b6f87581c0e23e6b08" + "f449031de312814ec6f319291f4a0516", + "bdae85924b3cb1d0a2e33a30c6d79599" + "8a0feddbac865a09bcd127fb562ed60a", + "b55a0a5b51a12a8be34899c3e047511a" + "d9a09cea3ce75fe39698070317a71339", + "552225ed1177f44584ac8cfa6c4eb5fc" + "7e82cbabfc95381b080998442129c2f8", + "1f135ed14ce60a91369d2322bef25e3c" + "08b6be45124a43e2eb77953f84dc8553", + }}, + {"0102030405060708090a0b0c0d0e0f10", + { + "9ac7cc9a609d1ef7b2932899cde41b97" + "5248c4959014126a6e8a84f11d1a9e1c", + "065902e4b620f6cc36c8589f66432f2b" + "d39d566bc6bce3010768151549f3873f", + "b6d1e6c4a5e4771cad79538df295fb11" + "c68c1d5c559a974123df1dbc52a43b89", + "c5ecf88de897fd57fed301701b82a259" + "eccbe13de1fcc91c11a0b26c0bc8fa4d", + "e7a72574f8782ae26aabcf9ebcd66065" + "bdf0324e6083dcc6d3cedd3ca8c53c16", + "b40110c4190b5622a96116b0017ed297" + "ffa0b514647ec04f6306b892ae661181", + "d03d1bc03cd33d70dff9fa5d71963ebd" + "8a44126411eaa78bd51e8d87a8879bf5", + "fabeb76028ade2d0e48722e46c4615a3" + "c05d88abd50357f935a63c59ee537623", + "ff38265c1642c1abe8d3c2fe5e572bf8" + "a36a4c301ae8ac13610ccbc12256cacc", + }}, + {"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", + { + "eaa6bd25880bf93d3f5d1e4ca2611d91" + "cfa45c9f7e714b54bdfa80027cb14380", + "114ae344ded71b35f2e60febad727fd8" + "02e1e7056b0f623900496422943e97b6", + "91cb93c787964e10d9527d999c6f936b" + "49b18b42f8e8367cbeb5ef104ba1c7cd", + "87084b3ba700bade955610672745b374" + "e7a7b9e9ec540d5ff43bdb12792d1b35", + "c799b596738f6b018c76c74b1759bd90" + "7fec5bfd9f9b89ce6548309092d7e958", + "40f250b26d1f096a4afd4c340a588815" + "3e34135c79db010200767651cf263073", + "f656abccf88dd827027b2ce917d464ec" + "18b62503bfbc077fbabb98f20d98ab34", + "8aed95ee5b0dcbfbef4eb21d3a3f52f9" + "625a1ab00ee39a5327346bddb01a9c18", + "a13a7c79c7e119b5ab0296ab28c300b9" + "f3e4c0a2e02d1d01f7f0a74618af2b48", + }}, + {"833222772a", + { + "80ad97bdc973df8a2e879e92a497efda" + "20f060c2f2e5126501d3d4fea10d5fc0", + "faa148e99046181fec6b2085f3b20ed9" + "f0daf5bab3d596839857846f73fbfe5a", + "1c7e2fc4639232fe297584b296996bc8" + "3db9b249406cc8edffac55ccd322ba12", + "e4f9f7e0066154bbd125b745569bc897" + "75d5ef262b44c41a9cf63ae14568e1b9", + "6da453dbf81e82334a3d8866cb50a1e3" + "7828d074119cab5c22b294d7a9bfa0bb", + "adb89cea9a15fbe617295bd04b8ca05c" + "6251d87fd4aaae9a7e4ad5c217d3f300", + "e7119bd6dd9b22afe8f89585432881e2" + "785b60fd7ec4e9fcb6545f350d660fab", + "afecc037fdb7b0838eb3d70bcd268382" + "dbc1a7b49d57358cc9fa6d61d73b7cf0", + "6349d126a37afcba89794f9804914fdc" + "bf42c3018c2f7c66bfde524975768115", + }}, + {"1910833222772a", + { + "bc9222dbd3274d8fc66d14ccbda6690b" + "7ae627410c9a2be693df5bb7485a63e3", + "3f0931aa03defb300f060103826f2a64" + "beaa9ec8d59bb68129f3027c96361181", + "74e04db46d28648d7dee8a0064b06cfe" + "9b5e81c62fe023c55be42f87bbf932b8", + "ce178fc1826efecbc182f57999a46140" + "8bdf55cd55061c06dba6be11de4a578a", + "626f5f4dce652501f3087d39c92cc349" + "42daac6a8f9ab9a7fd137c6037825682", + "cc03fdb79192a207312f53f5d4dc33d9" + "f70f14122a1c98a3155d28b8a0a8a41d", + "2a3a307ab2708a9c00fe0b42f9c2d6a1" + "862617627d2261eab0b1246597ca0ae9", + "55f877ce4f2e1ddbbf8e13e2cde0fdc8" + "1b1556cb935f173337705fbb5d501fc1", + "ecd0e96602be7f8d5092816cccf2c2e9" + "027881fab4993a1c262024a94fff3f61", + }}, + {"641910833222772a", + { + "bbf609de9413172d07660cb680716926" + "46101a6dab43115d6c522b4fe93604a9", + "cbe1fff21c96f3eef61e8fe0542cbdf0" + "347938bffa4009c512cfb4034b0dd1a7", + "7867a786d00a7147904d76ddf1e520e3" + "8d3e9e1caefcccb3fbf8d18f64120b32", + "942337f8fd76f0fae8c52d7954810672" + "b8548c10f51667f6e60e182fa19b30f7", + "0211c7c6190c9efd1237c34c8f2e06c4" + "bda64f65276d2aacb8f90212203a808e", + "bd3820f732ffb53ec193e79d33e27c73" + "d0168616861907d482e36cdac8cf5749", + "97b0f0f224b2d2317114808fb03af7a0" + "e59616e469787939a063ceea9af956d1", + "c47e0dc1660919c11101208f9e69aa1f" + "5ae4f12896b8379a2aad89b5b553d6b0", + "6b6b098d0c293bc2993d80bf0518b6d9" + "8170cc3ccd92a698621b939dd38fe7b9", + }}, + {"8b37641910833222772a", + { + "ab65c26eddb287600db2fda10d1e605c" + "bb759010c29658f2c72d93a2d16d2930", + "b901e8036ed1c383cd3c4c4dd0a6ab05" + "3d25ce4922924c55f064943353d78a6c", + "12c1aa44bbf87e75e611f69b2c38f49b" + "28f2b3434b65c09877470044c6ea170d", + "bd9ef822de5288196134cf8af7839304" + "67559c23f052158470a296f725735a32", + "8bab26fbc2c12b0f13e2ab185eabf241" + "31185a6d696f0cfa9b42808b38e132a2", + "564d3dae183c5234c8af1e51061c44b5" + "3c0778a7b5f72d3c23a3135c7d67b9f4", + "f34369890fcf16fb517dcaae4463b2dd" + "02f31c81e8200731b899b028e791bfa7", + "72da646283228c14300853701795616f" + "4e0a8c6f7934a788e2265e81d6d0c8f4", + "438dd5eafea0111b6f36b4b938da2a68" + "5f6bfc73815874d97100f086979357d8", + }}, + {"ebb46227c6cc8b37641910833222772a", + { + "720c94b63edf44e131d950ca211a5a30" + "c366fdeacf9ca80436be7c358424d20b", + "b3394a40aabf75cba42282ef25a0059f" + "4847d81da4942dbc249defc48c922b9f", + "08128c469f275342adda202b2b58da95" + "970dacef40ad98723bac5d6955b81761", + "3cb89993b07b0ced93de13d2a11013ac" + "ef2d676f1545c2c13dc680a02f4adbfe", + "b60595514f24bc9fe522a6cad7393644" + "b515a8c5011754f59003058bdb81514e", + "3c70047e8cbc038e3b9820db601da495" + "1175da6ee756de46a53e2b075660b770", + "00a542bba02111cc2c65b38ebdba587e" + "5865fdbb5b48064104e830b380f2aede", + "34b21ad2ad44e999db2d7f0863f0d9b6" + "84a9218fc36e8a5f2ccfbeae53a27d25", + "a2221a11b833ccb498a59540f0545f4a" + "5bbeb4787d59e5373fdbea6c6f75c29b", + }}, + {"c109163908ebe51debb46227c6cc8b37641910833222772a", + { + "54b64e6b5a20b5e2ec84593dc7989da7" + "c135eee237a85465ff97dc03924f45ce", + "cfcc922fb4a14ab45d6175aabbf2d201" + "837b87e2a446ad0ef798acd02b94124f", + "17a6dbd664926a0636b3f4c37a4f4694" + "4a5f9f26aeeed4d4a25f632d305233d9", + "80a3d01ef00c8e9a4209c17f4eeb358c" + "d15e7d5ffaaabc0207bf200a117793a2", + "349682bf588eaa52d0aa1560346aeafa" + "f5854cdb76c889e3ad63354e5f7275e3", + "532c7ceccb39df3236318405a4b1279c" + "baefe6d9ceb651842260e0d1e05e3b90", + "e82d8c6db54e3c633f581c952ba04207" + "4b16e50abd381bd70900a9cd9a62cb23", + "3682ee33bd148bd9f58656cd8f30d9fb" + "1e5a0b8475045d9b20b2628624edfd9e", + "63edd684fb826282fe528f9c0e9237bc" + "e4dd2e98d6960fae0b43545456743391", + }}, + {"1ada31d5cf688221c109163908ebe51debb46227c6cc8b37641910833222772a", + { + "dd5bcb0018e922d494759d7c395d02d3" + "c8446f8f77abf737685353eb89a1c9eb", + "af3e30f9c095045938151575c3fb9098" + "f8cb6274db99b80b1d2012a98ed48f0e", + "25c3005a1cb85de076259839ab7198ab" + "9dcbc183e8cb994b727b75be3180769c", + "a1d3078dfa9169503ed9d4491dee4eb2" + "8514a5495858096f596e4bcd66b10665", + "5f40d59ec1b03b33738efa60b2255d31" + "3477c7f764a41baceff90bf14f92b7cc", + "ac4e95368d99b9eb78b8da8f81ffa795" + "8c3c13f8c2388bb73f38576e65b7c446", + "13c4b9c1dfb66579eddd8a280b9f7316" + "ddd27820550126698efaadc64b64f66e", + "f08f2e66d28ed143f3a237cf9de73559" + "9ea36c525531b880ba124334f57b0b70", + "d5a39e3dfcc50280bac4a6b5aa0dca7d" + "370b1c1fe655916d97fd0d47ca1d72b8", + }}}; + + RC4_CTX ctx; + uint8_t key[64]; + uint8_t buffer[0x1010]; + + for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { + size_t length = strlen(tests[i].key) / 2; + memcpy(key, fromhex(tests[i].key), length); + memzero(buffer, sizeof(buffer)); + + rc4_init(&ctx, key, length); + rc4_encrypt(&ctx, buffer, sizeof(buffer)); + + for (size_t j = 0; j < (sizeof(offsets) / sizeof(*offsets)); j++) { + size_t size = strlen(tests[i].vectors[j]) / 2; + ck_assert_mem_eq(&buffer[offsets[j]], fromhex(tests[i].vectors[j]), size); + } + } +} +END_TEST + +static void test_compress_coord(const char *k_raw) { + const ecdsa_curve *curve = &secp256k1; + curve_point expected_coords; + + bignum256 k = {0}; + + bn_read_be(fromhex(k_raw), &k); + + point_multiply(curve, &k, &curve->G, &expected_coords); + + uint8_t compress[33] = {0}; + compress_coords(&expected_coords, compress); + + bignum256 x = {0}, y = {0}; + bn_read_be(compress + 1, &x); + uncompress_coords(curve, compress[0], &x, &y); + + ck_assert(bn_is_equal(&expected_coords.x, &x)); + ck_assert(bn_is_equal(&expected_coords.y, &y)); +} + +START_TEST(test_compress_coords) { + static const char *k_raw[] = { + "dc05960ac673fd59554c98655e26722d007bb7ada0c8ff00883fdee70783d0be", + "41e41e0a218c980411108a0a58cf88f528c828b4d6f0d2c86234bc2504bdc3cd", + "1d963ddcb79f6028a32cadd2421ff7fff969bff5774f73063dab41519b3da175", + "2414141f96da0874dbc374b58861589935b7f940806ddf8d2e6b911f62e240f3", + "01cc1fb182e29f60fe43e22d250de34f2d3f956bbef2aa9b182d09e5d9176873", + "89b3d621d813682692fd61b2baea6b2ea696a44abc76925d29c4887fc4db9367", + "20c80c633e05a3a7dfac05fa0e0a7c7a6b708b02323e687735cff81ea5944f59", + "5a803c263aa93a4f74648066c03e63fb00641193bae93dfa254dabd634e8b49c", + "05efbcc87007797dca68315b9271ac8fb75bddbece53f4dcbfb83fc21cb91fc0", + "0bed78ef43474630bd646eef2d7ec19a1acb8e9eecf6a0a3ac7241ac40a7706f", + }; + + for (int i = 0; i < (int)(sizeof(k_raw) / sizeof(*k_raw)); i++) + test_compress_coord(k_raw[i]); +} +END_TEST + +static int my_strncasecmp(const char *s1, const char *s2, size_t n) { + size_t i = 0; + while (i < n) { + char c1 = s1[i]; + char c2 = s2[i]; + if (c1 >= 'A' && c1 <= 'Z') c1 = (c1 - 'A') + 'a'; + if (c2 >= 'A' && c2 <= 'Z') c2 = (c2 - 'A') + 'a'; + if (c1 < c2) return -1; + if (c1 > c2) return 1; + if (c1 == 0) return 0; + ++i; + } + return 0; +} + +#include "test_check_cashaddr.h" +#include "test_check_zilliqa.h" // [wallet-core] +#if USE_SEGWIT +#include "test_check_segwit.h" +#endif + +#if USE_CARDANO +#include "test_check_cardano.h" +#endif + +#if USE_MONERO +#include "test_check_monero.h" +#endif + +// define test suite and cases +Suite *test_suite(void) { + Suite *s = suite_create("trezor-crypto"); + TCase *tc; + + tc = tcase_create("bignum"); + tcase_add_test(tc, test_bignum_read_be); + tcase_add_test(tc, test_bignum_write_be); + tcase_add_test(tc, test_bignum_is_equal); + tcase_add_test(tc, test_bignum_zero); + tcase_add_test(tc, test_bignum_is_zero); + tcase_add_test(tc, test_bignum_one); + tcase_add_test(tc, test_bignum_read_le); + tcase_add_test(tc, test_bignum_write_le); + tcase_add_test(tc, test_bignum_read_uint32); + tcase_add_test(tc, test_bignum_read_uint64); + tcase_add_test(tc, test_bignum_write_uint32); + tcase_add_test(tc, test_bignum_write_uint64); + tcase_add_test(tc, test_bignum_copy); + tcase_add_test(tc, test_bignum_is_even); + tcase_add_test(tc, test_bignum_is_odd); + tcase_add_test(tc, test_bignum_bitcount); + tcase_add_test(tc, test_bignum_digitcount); + tcase_add_test(tc, test_bignum_is_less); + tcase_add_test(tc, test_bignum_format); + tcase_add_test(tc, test_bignum_format_uint64); + tcase_add_test(tc, test_bignum_sqrt); + suite_add_tcase(s, tc); + + tc = tcase_create("base32"); + tcase_add_test(tc, test_base32_rfc4648); + suite_add_tcase(s, tc); + + tc = tcase_create("base58"); + tcase_add_test(tc, test_base58); + suite_add_tcase(s, tc); + +#if USE_GRAPHENE + tc = tcase_create("base58gph"); + tcase_add_test(tc, test_base58gph); + suite_add_tcase(s, tc); +#endif + + tc = tcase_create("bignum_divmod"); + tcase_add_test(tc, test_bignum_divmod); + suite_add_tcase(s, tc); + + tc = tcase_create("bip32"); + tcase_add_test(tc, test_bip32_vector_1); + tcase_add_test(tc, test_bip32_vector_2); + tcase_add_test(tc, test_bip32_vector_3); + tcase_add_test(tc, test_bip32_vector_4); + tcase_add_test(tc, test_bip32_compare); + tcase_add_test(tc, test_bip32_optimized); +#if USE_BIP32_CACHE + tcase_add_test(tc, test_bip32_cache_1); + tcase_add_test(tc, test_bip32_cache_2); +#endif + suite_add_tcase(s, tc); + + tc = tcase_create("bip32-nist"); + tcase_add_test(tc, test_bip32_nist_seed); + tcase_add_test(tc, test_bip32_nist_vector_1); + tcase_add_test(tc, test_bip32_nist_vector_2); + tcase_add_test(tc, test_bip32_nist_compare); + tcase_add_test(tc, test_bip32_nist_repeat); + suite_add_tcase(s, tc); + + tc = tcase_create("bip32-ed25519"); + tcase_add_test(tc, test_bip32_ed25519_vector_1); + tcase_add_test(tc, test_bip32_ed25519_vector_2); + suite_add_tcase(s, tc); + + tc = tcase_create("bip32-ecdh"); + tcase_add_test(tc, test_bip32_ecdh_nist256p1); + tcase_add_test(tc, test_bip32_ecdh_curve25519); + tcase_add_test(tc, test_bip32_ecdh_errors); + suite_add_tcase(s, tc); + + tc = tcase_create("bip32-decred"); + tcase_add_test(tc, test_bip32_decred_vector_1); + tcase_add_test(tc, test_bip32_decred_vector_2); + suite_add_tcase(s, tc); + + tc = tcase_create("ecdsa"); + tcase_add_test(tc, test_ecdsa_get_public_key33); + tcase_add_test(tc, test_ecdsa_get_public_key65); + tcase_add_test(tc, test_ecdsa_recover_pub_from_sig); + tcase_add_test(tc, test_ecdsa_verify_digest); +#if USE_RFC6979 + tcase_add_test(tc, test_ecdsa_sign_digest_deterministic); +#endif + suite_add_tcase(s, tc); + + tc = tcase_create("rfc6979"); + tcase_add_test(tc, test_rfc6979); + suite_add_tcase(s, tc); + + tc = tcase_create("address"); + tcase_add_test(tc, test_address); + suite_add_tcase(s, tc); + + tc = tcase_create("address_decode"); + tcase_add_test(tc, test_address_decode); + suite_add_tcase(s, tc); + +#if USE_ETHEREUM + tc = tcase_create("ethereum_address"); + tcase_add_test(tc, test_ethereum_address); + suite_add_tcase(s, tc); + + tc = tcase_create("rsk_address"); + tcase_add_test(tc, test_rsk_address); + suite_add_tcase(s, tc); +#endif + + tc = tcase_create("wif"); + tcase_add_test(tc, test_wif); + suite_add_tcase(s, tc); + + tc = tcase_create("ecdsa_der"); + tcase_add_test(tc, test_ecdsa_der); + suite_add_tcase(s, tc); + + tc = tcase_create("aes"); + tcase_add_test(tc, test_aes); + suite_add_tcase(s, tc); + + tc = tcase_create("sha2"); + tcase_add_test(tc, test_sha1); + tcase_add_test(tc, test_sha256); + tcase_add_test(tc, test_sha512); + suite_add_tcase(s, tc); + + tc = tcase_create("sha3"); + tcase_add_test(tc, test_sha3_256); + tcase_add_test(tc, test_sha3_512); + tcase_add_test(tc, test_keccak_256); + suite_add_tcase(s, tc); + + tc = tcase_create("blake"); + tcase_add_test(tc, test_blake256); + suite_add_tcase(s, tc); + + tc = tcase_create("blake2"); + tcase_add_test(tc, test_blake2b); + tcase_add_test(tc, test_blake2bp); + tcase_add_test(tc, test_blake2s); + suite_add_tcase(s, tc); + + tc = tcase_create("chacha_drbg"); + tcase_add_test(tc, test_chacha_drbg); + suite_add_tcase(s, tc); + + tc = tcase_create("pbkdf2"); + tcase_add_test(tc, test_pbkdf2_hmac_sha256); + tcase_add_test(tc, test_pbkdf2_hmac_sha512); + suite_add_tcase(s, tc); + + tc = tcase_create("hmac_drbg"); + tcase_add_test(tc, test_hmac_drbg); + suite_add_tcase(s, tc); + + tc = tcase_create("bip39"); + tcase_add_test(tc, test_mnemonic); + tcase_add_test(tc, test_mnemonic_check); + tcase_add_test(tc, test_mnemonic_to_bits); + tcase_add_test(tc, test_mnemonic_find_word); + suite_add_tcase(s, tc); + + tc = tcase_create("slip39"); + tcase_add_test(tc, test_slip39_get_word); + tcase_add_test(tc, test_slip39_word_index); + tcase_add_test(tc, test_slip39_word_completion_mask); + tcase_add_test(tc, test_slip39_sequence_to_word); + tcase_add_test(tc, test_slip39_word_completion); + suite_add_tcase(s, tc); + + tc = tcase_create("shamir"); + tcase_add_test(tc, test_shamir); + suite_add_tcase(s, tc); + + tc = tcase_create("pubkey_validity"); + tcase_add_test(tc, test_pubkey_validity); + suite_add_tcase(s, tc); + + tc = tcase_create("pubkey_uncompress"); + tcase_add_test(tc, test_pubkey_uncompress); + suite_add_tcase(s, tc); + + tc = tcase_create("codepoints"); + tcase_add_test(tc, test_codepoints_secp256k1); + tcase_add_test(tc, test_codepoints_nist256p1); + suite_add_tcase(s, tc); + + tc = tcase_create("mult_border_cases"); + tcase_add_test(tc, test_mult_border_cases_secp256k1); + tcase_add_test(tc, test_mult_border_cases_nist256p1); + suite_add_tcase(s, tc); + + tc = tcase_create("scalar_mult"); + tcase_add_test(tc, test_scalar_mult_secp256k1); + tcase_add_test(tc, test_scalar_mult_nist256p1); + suite_add_tcase(s, tc); + + tc = tcase_create("point_mult"); + tcase_add_test(tc, test_point_mult_secp256k1); + tcase_add_test(tc, test_point_mult_nist256p1); + suite_add_tcase(s, tc); + + tc = tcase_create("scalar_point_mult"); + tcase_add_test(tc, test_scalar_point_mult_secp256k1); + tcase_add_test(tc, test_scalar_point_mult_nist256p1); + suite_add_tcase(s, tc); + + tc = tcase_create("ed25519"); + tcase_add_test(tc, test_ed25519); + suite_add_tcase(s, tc); + + tc = tcase_create("ed25519_keccak"); + tcase_add_test(tc, test_ed25519_keccak); + suite_add_tcase(s, tc); + + tc = tcase_create("ed25519_cosi"); + tcase_add_test(tc, test_ed25519_cosi); + suite_add_tcase(s, tc); + + tc = tcase_create("ed25519_modm"); + tcase_add_test(tc, test_ed25519_modl_add); + tcase_add_test(tc, test_ed25519_modl_neg); + tcase_add_test(tc, test_ed25519_modl_sub); + suite_add_tcase(s, tc); + +#if USE_MONERO + tc = tcase_create("ed25519_ge"); + tcase_add_test(tc, test_ge25519_double_scalarmult_vartime2); + suite_add_tcase(s, tc); +#endif + + tc = tcase_create("script"); + tcase_add_test(tc, test_output_script); + suite_add_tcase(s, tc); + +#if USE_ETHEREUM + tc = tcase_create("ethereum_pubkeyhash"); + tcase_add_test(tc, test_ethereum_pubkeyhash); + suite_add_tcase(s, tc); +#endif + +#if USE_NEM + tc = tcase_create("nem_address"); + tcase_add_test(tc, test_nem_address); + suite_add_tcase(s, tc); + + tc = tcase_create("nem_encryption"); + tcase_add_test(tc, test_nem_derive); + tcase_add_test(tc, test_nem_cipher); + suite_add_tcase(s, tc); + + tc = tcase_create("nem_transaction"); + tcase_add_test(tc, test_nem_transaction_transfer); + tcase_add_test(tc, test_nem_transaction_multisig); + tcase_add_test(tc, test_nem_transaction_provision_namespace); + tcase_add_test(tc, test_nem_transaction_mosaic_creation); + tcase_add_test(tc, test_nem_transaction_mosaic_supply_change); + tcase_add_test(tc, test_nem_transaction_aggregate_modification); + suite_add_tcase(s, tc); +#endif + + tc = tcase_create("multibyte_address"); + tcase_add_test(tc, test_multibyte_address); + suite_add_tcase(s, tc); + + tc = tcase_create("rc4"); + tcase_add_test(tc, test_rc4_rfc6229); + suite_add_tcase(s, tc); + +#if USE_SEGWIT + tc = tcase_create("segwit"); + tcase_add_test(tc, test_segwit); + suite_add_tcase(s, tc); +#endif + + tc = tcase_create("cashaddr"); + tcase_add_test(tc, test_cashaddr); + suite_add_tcase(s, tc); + + tc = tcase_create("compress_coords"); + tcase_add_test(tc, test_compress_coords); + suite_add_tcase(s, tc); + +#if USE_CARDANO + tc = tcase_create("bip32-cardano"); + + tcase_add_test(tc, test_bip32_cardano_hdnode_vector_1); + tcase_add_test(tc, test_bip32_cardano_hdnode_vector_2); + tcase_add_test(tc, test_bip32_cardano_hdnode_vector_3); + tcase_add_test(tc, test_bip32_cardano_hdnode_vector_4); + tcase_add_test(tc, test_bip32_cardano_hdnode_vector_5); + tcase_add_test(tc, test_bip32_cardano_hdnode_vector_6); + tcase_add_test(tc, test_bip32_cardano_hdnode_vector_7); + tcase_add_test(tc, test_bip32_cardano_hdnode_vector_8); + tcase_add_test(tc, test_bip32_cardano_hdnode_vector_9); + + tcase_add_test(tc, test_cardano_ledger_vector_1); + tcase_add_test(tc, test_cardano_ledger_vector_2); + tcase_add_test(tc, test_cardano_ledger_vector_3); + + tcase_add_test(tc, test_ed25519_cardano_sign_vectors); + suite_add_tcase(s, tc); +#endif + +#if USE_MONERO + tc = tcase_create("xmr_base58"); + tcase_add_test(tc, test_xmr_base58); + suite_add_tcase(s, tc); + + tc = tcase_create("xmr_crypto"); + tcase_add_test(tc, test_xmr_getset256_modm); + tcase_add_test(tc, test_xmr_cmp256_modm); + tcase_add_test(tc, test_xmr_copy_check_modm); + tcase_add_test(tc, test_xmr_mulsub256_modm); + tcase_add_test(tc, test_xmr_muladd256_modm); + tcase_add_test(tc, test_xmr_curve25519_set); + tcase_add_test(tc, test_xmr_curve25519_consts); + tcase_add_test(tc, test_xmr_curve25519_tests); + tcase_add_test(tc, test_xmr_curve25519_expand_reduce); + tcase_add_test(tc, test_xmr_ge25519_base); + tcase_add_test(tc, test_xmr_ge25519_check); + tcase_add_test(tc, test_xmr_ge25519_scalarmult_base_wrapper); + tcase_add_test(tc, test_xmr_ge25519_scalarmult); + tcase_add_test(tc, test_xmr_ge25519_ops); + suite_add_tcase(s, tc); + + tc = tcase_create("xmr_xmr"); + tcase_add_test(tc, test_xmr_check_point); + tcase_add_test(tc, test_xmr_h); + tcase_add_test(tc, test_xmr_fast_hash); + tcase_add_test(tc, test_xmr_hasher); + tcase_add_test(tc, test_xmr_hash_to_scalar); + tcase_add_test(tc, test_xmr_hash_to_ec); + tcase_add_test(tc, test_xmr_derivation_to_scalar); + tcase_add_test(tc, test_xmr_generate_key_derivation); + tcase_add_test(tc, test_xmr_derive_private_key); + tcase_add_test(tc, test_xmr_derive_public_key); + tcase_add_test(tc, test_xmr_add_keys2); + tcase_add_test(tc, test_xmr_add_keys3); + tcase_add_test(tc, test_xmr_get_subaddress_secret_key); + tcase_add_test(tc, test_xmr_gen_c); + tcase_add_test(tc, test_xmr_varint); + suite_add_tcase(s, tc); +#endif + + return s; +} + +// run suite +int main(void) { + int number_failed; + Suite *s = test_suite(); + SRunner *sr = srunner_create(s); + srunner_run_all(sr, CK_VERBOSE); + number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + if (number_failed == 0) { + printf("PASSED ALL TESTS\n"); + } + return number_failed; +} diff --git a/tools/windows-replace/trezor-crypto/include/TrezorCrypto/aes.h b/tools/windows-replace/trezor-crypto/include/TrezorCrypto/aes.h new file mode 100644 index 00000000000..62e9518a6a6 --- /dev/null +++ b/tools/windows-replace/trezor-crypto/include/TrezorCrypto/aes.h @@ -0,0 +1,228 @@ +/* +--------------------------------------------------------------------------- +Copyright (c) 1998-2013, Brian Gladman, Worcester, UK. All rights reserved. + +The redistribution and use of this software (with or without changes) +is allowed without the payment of fees or royalties provided that: + + source code distributions include the above copyright notice, this + list of conditions and the following disclaimer; + + binary distributions include the above copyright notice, this list + of conditions and the following disclaimer in their documentation. + +This software is provided 'as is' with no explicit or implied warranties +in respect of its operation, including, but not limited to, correctness +and fitness for purpose. +--------------------------------------------------------------------------- +Issue Date: 02/08/2018 + + This file contains the definitions required to use AES in C. See aesopt.h + for optimisation details. +*/ + +#ifndef _AES_H +#define _AES_H + +#include + +#include +#include + +#define VOID_RETURN void +#define INT_RETURN int +#define ALIGN_OFFSET(x,n) (((intptr_t)(x)) & ((n) - 1)) +#define ALIGN_FLOOR(x,n) ((uint8_t*)(x) - ( ((intptr_t)(x)) & ((n) - 1))) +#define ALIGN_CEIL(x,n) ((uint8_t*)(x) + (-((intptr_t)(x)) & ((n) - 1))) + +#if defined(__cplusplus) +extern "C" +{ +#endif + +// #define AES_128 /* if a fast 128 bit key scheduler is needed */ +// #define AES_192 /* if a fast 192 bit key scheduler is needed */ +#define AES_256 /* if a fast 256 bit key scheduler is needed */ +// #define AES_VAR /* if variable key size scheduler is needed */ +#if 1 +# define AES_MODES /* if support is needed for modes in the C code */ +#endif /* (these will use AES_NI if it is present) */ +#if 0 /* add this to make direct calls to the AES_NI */ +# /* implemented CBC and CTR modes available */ +# define ADD_AESNI_MODE_CALLS +#endif + +/* The following must also be set in assembler files if being used */ + +#define AES_ENCRYPT /* if support for encryption is needed */ +#define AES_DECRYPT /* if support for decryption is needed */ + +#define AES_BLOCK_SIZE_P2 4 /* AES block size as a power of 2 */ +#define AES_BLOCK_SIZE (1 << AES_BLOCK_SIZE_P2) /* AES block size */ +#define N_COLS 4 /* the number of columns in the state */ + +/* The key schedule length is 11, 13 or 15 16-byte blocks for 128, */ +/* 192 or 256-bit keys respectively. That is 176, 208 or 240 bytes */ +/* or 44, 52 or 60 32-bit words. */ + +#if defined( AES_VAR ) || defined( AES_256 ) +#define KS_LENGTH 60 +#elif defined( AES_192 ) +#define KS_LENGTH 52 +#else +#define KS_LENGTH 44 +#endif + +#define AES_RETURN INT_RETURN + +/* the character array 'inf' in the following structures is used */ +/* to hold AES context information. This AES code uses cx->inf.b[0] */ +/* to hold the number of rounds multiplied by 16. The other three */ +/* elements can be used by code that implements additional modes */ + +typedef union +{ uint32_t l; + uint8_t b[4]; +} aes_inf; + +#ifdef _MSC_VER //win Ignore the warning 4324 +# pragma warning( disable : 4324 ) +#endif + +#if defined(_MSC_VER) && defined(_WIN64) +#define ALIGNED_(x) __declspec(align(x)) +#elif defined(__GNUC__) && defined(__x86_64__) +#define ALIGNED_(x) __attribute__ ((aligned(x))) +#else +#define ALIGNED_(x) +#endif + +typedef struct ALIGNED_(16) +{ uint32_t ks[KS_LENGTH]; + aes_inf inf; +} aes_encrypt_ctx; + +typedef struct ALIGNED_(16) +{ uint32_t ks[KS_LENGTH]; + aes_inf inf; +} aes_decrypt_ctx; + +#ifdef _MSC_VER +# pragma warning( default : 4324 ) +#endif + +/* This routine must be called before first use if non-static */ +/* tables are being used */ + +AES_RETURN aes_init(void); + +/* Key lengths in the range 16 <= key_len <= 32 are given in bytes, */ +/* those in the range 128 <= key_len <= 256 are given in bits */ + +#if defined( AES_ENCRYPT ) + +#if defined( AES_128 ) || defined( AES_VAR) +AES_RETURN aes_encrypt_key128(const unsigned char *key, aes_encrypt_ctx cx[1]); +#endif + +#if defined( AES_192 ) || defined( AES_VAR) +AES_RETURN aes_encrypt_key192(const unsigned char *key, aes_encrypt_ctx cx[1]); +#endif + +#if defined( AES_256 ) || defined( AES_VAR) +AES_RETURN aes_encrypt_key256(const unsigned char *key, aes_encrypt_ctx cx[1]); +#endif + +#if defined( AES_VAR ) +AES_RETURN aes_encrypt_key(const unsigned char *key, int key_len, aes_encrypt_ctx cx[1]); +#endif + +AES_RETURN aes_encrypt(const unsigned char *in, unsigned char *out, const aes_encrypt_ctx cx[1]); + +#endif + +#if defined( AES_DECRYPT ) + +#if defined( AES_128 ) || defined( AES_VAR) +AES_RETURN aes_decrypt_key128(const unsigned char *key, aes_decrypt_ctx cx[1]); +#endif + +#if defined( AES_192 ) || defined( AES_VAR) +AES_RETURN aes_decrypt_key192(const unsigned char *key, aes_decrypt_ctx cx[1]); +#endif + +#if defined( AES_256 ) || defined( AES_VAR) +AES_RETURN aes_decrypt_key256(const unsigned char *key, aes_decrypt_ctx cx[1]); +#endif + +#if defined( AES_VAR ) +AES_RETURN aes_decrypt_key(const unsigned char *key, int key_len, aes_decrypt_ctx cx[1]); +#endif + +AES_RETURN aes_decrypt(const unsigned char *in, unsigned char *out, const aes_decrypt_ctx cx[1]); + +#endif + +#if defined( AES_MODES ) + +/* Multiple calls to the following subroutines for multiple block */ +/* ECB, CBC, CFB, OFB and CTR mode encryption can be used to handle */ +/* long messages incrementally provided that the context AND the iv */ +/* are preserved between all such calls. For the ECB and CBC modes */ +/* each individual call within a series of incremental calls must */ +/* process only full blocks (i.e. len must be a multiple of 16) but */ +/* the CFB, OFB and CTR mode calls can handle multiple incremental */ +/* calls of any length. Each mode is reset when a new AES key is */ +/* set but ECB needs no reset and CBC can be reset without setting */ +/* a new key by setting a new IV value. To reset CFB, OFB and CTR */ +/* without setting the key, aes_mode_reset() must be called and the */ +/* IV must be set. NOTE: All these calls update the IV on exit so */ +/* this has to be reset if a new operation with the same IV as the */ +/* previous one is required (or decryption follows encryption with */ +/* the same IV array). */ + +AES_RETURN aes_test_alignment_detection(unsigned int n); + +AES_RETURN aes_ecb_encrypt(const unsigned char *ibuf, unsigned char *obuf, + int len, const aes_encrypt_ctx cx[1]); + +AES_RETURN aes_ecb_decrypt(const unsigned char *ibuf, unsigned char *obuf, + int len, const aes_decrypt_ctx cx[1]); + +AES_RETURN aes_cbc_encrypt(const unsigned char *ibuf, unsigned char *obuf, + int len, unsigned char *iv, const aes_encrypt_ctx cx[1]); + +AES_RETURN aes_cbc_decrypt(const unsigned char *ibuf, unsigned char *obuf, + int len, unsigned char *iv, const aes_decrypt_ctx cx[1]); + +AES_RETURN aes_mode_reset(aes_encrypt_ctx cx[1]); + +AES_RETURN aes_cfb_encrypt(const unsigned char *ibuf, unsigned char *obuf, + int len, unsigned char *iv, aes_encrypt_ctx cx[1]); + +AES_RETURN aes_cfb_decrypt(const unsigned char *ibuf, unsigned char *obuf, + int len, unsigned char *iv, aes_encrypt_ctx cx[1]); + +#define aes_ofb_encrypt aes_ofb_crypt +#define aes_ofb_decrypt aes_ofb_crypt + +AES_RETURN aes_ofb_crypt(const unsigned char *ibuf, unsigned char *obuf, + int len, unsigned char *iv, aes_encrypt_ctx cx[1]); + +typedef void cbuf_inc(unsigned char *cbuf); + +#define aes_ctr_encrypt aes_ctr_crypt +#define aes_ctr_decrypt aes_ctr_crypt + +AES_RETURN aes_ctr_crypt(const unsigned char *ibuf, unsigned char *obuf, + int len, unsigned char *cbuf, cbuf_inc ctr_inc, aes_encrypt_ctx cx[1]); + +void aes_ctr_cbuf_inc(unsigned char *cbuf); + +#endif + +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/tools/windows-replace/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-donna-portable.h b/tools/windows-replace/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-donna-portable.h new file mode 100644 index 00000000000..33dad64dcf1 --- /dev/null +++ b/tools/windows-replace/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-donna-portable.h @@ -0,0 +1,38 @@ +#define mul32x32_64(a,b) (((uint64_t)(a))*(b)) + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define DONNA_INLINE +#undef ALIGN + +#ifdef _MSC_VER +#define ALIGN(x) __declspec(align(x)) +#else +#define ALIGN(x) __attribute__((aligned(x))) +#endif + + +static inline void U32TO8_LE(unsigned char *p, const uint32_t v) { + p[0] = (unsigned char)(v ); + p[1] = (unsigned char)(v >> 8); + p[2] = (unsigned char)(v >> 16); + p[3] = (unsigned char)(v >> 24); +} + +static inline uint32_t U8TO32_LE(const unsigned char *p) { + return + (((uint32_t)(p[0]) ) | + ((uint32_t)(p[1]) << 8) | + ((uint32_t)(p[2]) << 16) | + ((uint32_t)(p[3]) << 24)); +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif diff --git a/tools/windows-replace/trezor-crypto/include/TrezorCrypto/endian.h b/tools/windows-replace/trezor-crypto/include/TrezorCrypto/endian.h new file mode 100644 index 00000000000..e456cf945cd --- /dev/null +++ b/tools/windows-replace/trezor-crypto/include/TrezorCrypto/endian.h @@ -0,0 +1,132 @@ +/*- + * Copyright 2007-2009 Colin Percival + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file was originally written by Colin Percival as part of the Tarsnap + * online backup system. + */ +#ifndef _ENDIAN_H_ +#define _ENDIAN_H_ + + +#include +#ifdef _MSC_VER + #define INLINE __inline +#else + #define INLINE inline +#endif + +static INLINE uint32_t +be32dec(const void *pp) +{ + const uint8_t *p = (uint8_t const *)pp; + + return ((uint32_t)(p[3]) + ((uint32_t)(p[2]) << 8) + + ((uint32_t)(p[1]) << 16) + ((uint32_t)(p[0]) << 24)); +} + +static INLINE void +be32enc(void *pp, uint32_t x) +{ + uint8_t * p = (uint8_t *)pp; + + p[3] = x & 0xff; + p[2] = (x >> 8) & 0xff; + p[1] = (x >> 16) & 0xff; + p[0] = (x >> 24) & 0xff; +} + +static INLINE uint64_t +be64dec(const void *pp) +{ + const uint8_t *p = (uint8_t const *)pp; + + return ((uint64_t)(p[7]) + ((uint64_t)(p[6]) << 8) + + ((uint64_t)(p[5]) << 16) + ((uint64_t)(p[4]) << 24) + + ((uint64_t)(p[3]) << 32) + ((uint64_t)(p[2]) << 40) + + ((uint64_t)(p[1]) << 48) + ((uint64_t)(p[0]) << 56)); +} + +static INLINE void +be64enc(void *pp, uint64_t x) +{ + uint8_t * p = (uint8_t *)pp; + + p[7] = x & 0xff; + p[6] = (x >> 8) & 0xff; + p[5] = (x >> 16) & 0xff; + p[4] = (x >> 24) & 0xff; + p[3] = (x >> 32) & 0xff; + p[2] = (x >> 40) & 0xff; + p[1] = (x >> 48) & 0xff; + p[0] = (x >> 56) & 0xff; +} + +static INLINE uint32_t +le32dec(const void *pp) +{ + const uint8_t *p = (uint8_t const *)pp; + + return ((uint32_t)(p[0]) + ((uint32_t)(p[1]) << 8) + + ((uint32_t)(p[2]) << 16) + ((uint32_t)(p[3]) << 24)); +} + +static INLINE void +le32enc(void *pp, uint32_t x) +{ + uint8_t * p = (uint8_t *)pp; + + p[0] = x & 0xff; + p[1] = (x >> 8) & 0xff; + p[2] = (x >> 16) & 0xff; + p[3] = (x >> 24) & 0xff; +} + +static INLINE uint64_t +le64dec(const void *pp) +{ + const uint8_t *p = (uint8_t const *)pp; + + return ((uint64_t)(p[0]) + ((uint64_t)(p[1]) << 8) + + ((uint64_t)(p[2]) << 16) + ((uint64_t)(p[3]) << 24) + + ((uint64_t)(p[4]) << 32) + ((uint64_t)(p[5]) << 40) + + ((uint64_t)(p[6]) << 48) + ((uint64_t)(p[7]) << 56)); +} + +static INLINE void +le64enc(void *pp, uint64_t x) +{ + uint8_t * p = (uint8_t *)pp; + + p[0] = x & 0xff; + p[1] = (x >> 8) & 0xff; + p[2] = (x >> 16) & 0xff; + p[3] = (x >> 24) & 0xff; + p[4] = (x >> 32) & 0xff; + p[5] = (x >> 40) & 0xff; + p[6] = (x >> 48) & 0xff; + p[7] = (x >> 56) & 0xff; +} + +#endif /* !_ENDIAN_H_ */ diff --git a/tools/windows-replace/trezor-crypto/include/TrezorCrypto/groestl_internal.h b/tools/windows-replace/trezor-crypto/include/TrezorCrypto/groestl_internal.h new file mode 100644 index 00000000000..84587358e4a --- /dev/null +++ b/tools/windows-replace/trezor-crypto/include/TrezorCrypto/groestl_internal.h @@ -0,0 +1,510 @@ +/* Groestl hash from https://github.com/Groestlcoin/vanitygen + * Trezor adaptation by Yura Pakhuchiy . */ +/** + * Basic type definitions. + * + * This header file defines the generic integer types that will be used + * for the implementation of hash functions; it also contains helper + * functions which encode and decode multi-byte integer values, using + * either little-endian or big-endian conventions. + * + * This file contains a compile-time test on the size of a byte + * (the unsigned char C type). If bytes are not octets, + * i.e. if they do not have a size of exactly 8 bits, then compilation + * is aborted. Architectures where bytes are not octets are relatively + * rare, even in the embedded devices market. We forbid non-octet bytes + * because there is no clear convention on how octet streams are encoded + * on such systems. + * + * ==========================(LICENSE BEGIN)============================ + * + * Copyright (c) 2007-2010 Projet RNRT SAPHIR + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * ===========================(LICENSE END)============================= + * + * @file sph_types.h + * @author Thomas Pornin + */ + +#ifndef GROESTL_INTERNAL_H__ +#define GROESTL_INTERNAL_H__ + +#include + +/* + * All our I/O functions are defined over octet streams. We do not know + * how to handle input data if bytes are not octets. + */ +#if CHAR_BIT != 8 +#error This code requires 8-bit bytes +#endif + +//win #if defined __STDC__ && __STDC_VERSION__ >= 199901L +#if (defined __STDC__ && __STDC_VERSION__ >= 199901L) || defined(_MSC_VER) // win +#include + +typedef uint32_t sph_u32; +typedef int32_t sph_s32; +typedef uint64_t sph_u64; +typedef int64_t sph_s64; + +#define SPH_C32(x) ((sph_u32)(x)) +#define SPH_C64(x) ((sph_u64)(x)) + +#else +#error We need at least C99 compiler +#endif + +#define SPH_T32(x) ((x) & SPH_C32(0xFFFFFFFF)) +#define SPH_ROTL32(x, n) SPH_T32(((x) << (n)) | ((x) >> (32 - (n)))) +#define SPH_ROTR32(x, n) SPH_ROTL32(x, (32 - (n))) + +#define SPH_T64(x) ((x) & SPH_C64(0xFFFFFFFFFFFFFFFF)) +#define SPH_ROTL64(x, n) SPH_T64(((x) << (n)) | ((x) >> (64 - (n)))) +#define SPH_ROTR64(x, n) SPH_ROTL64(x, (64 - (n))) + +/* + * 32-bit x86, aka "i386 compatible". + */ +#if defined __i386__ || defined _M_IX86 + +#define SPH_DETECT_LITTLE_ENDIAN 1 +#define SPH_DETECT_BIG_ENDIAN 0 + +/* + * 64-bit x86, hereafter known as "amd64". + */ +#elif defined __x86_64 || defined _M_X64 + +#define SPH_DETECT_LITTLE_ENDIAN 1 +#define SPH_DETECT_BIG_ENDIAN 0 + +/* + * ARM, little-endian. + */ +#elif defined __arm__ && __ARMEL__ + +#define SPH_DETECT_LITTLE_ENDIAN 1 +#define SPH_DETECT_BIG_ENDIAN 0 + +/* + * ARM64, little-endian. + */ +#elif defined __aarch64__ + +#define SPH_DETECT_LITTLE_ENDIAN 1 +#define SPH_DETECT_BIG_ENDIAN 0 + +#endif + +#define SPH_LITTLE_ENDIAN 1 // [wallet-core] + +#if defined SPH_DETECT_LITTLE_ENDIAN && !defined SPH_LITTLE_ENDIAN +#define SPH_LITTLE_ENDIAN SPH_DETECT_LITTLE_ENDIAN +#endif +#if defined SPH_DETECT_BIG_ENDIAN && !defined SPH_BIG_ENDIAN +#define SPH_BIG_ENDIAN SPH_DETECT_BIG_ENDIAN +#endif + +static inline sph_u32 +sph_bswap32(sph_u32 x) +{ + x = SPH_T32((x << 16) | (x >> 16)); + x = ((x & SPH_C32(0xFF00FF00)) >> 8) + | ((x & SPH_C32(0x00FF00FF)) << 8); + return x; +} + +/** + * Byte-swap a 64-bit value. + * + * @param x the input value + * @return the byte-swapped value + */ +static inline sph_u64 +sph_bswap64(sph_u64 x) +{ + x = SPH_T64((x << 32) | (x >> 32)); + x = ((x & SPH_C64(0xFFFF0000FFFF0000)) >> 16) + | ((x & SPH_C64(0x0000FFFF0000FFFF)) << 16); + x = ((x & SPH_C64(0xFF00FF00FF00FF00)) >> 8) + | ((x & SPH_C64(0x00FF00FF00FF00FF)) << 8); + return x; +} + +static inline void +sph_enc16be(void *dst, unsigned val) +{ + ((unsigned char *)dst)[0] = (val >> 8); + ((unsigned char *)dst)[1] = val; +} + +static inline unsigned +sph_dec16be(const void *src) +{ + return ((unsigned)(((const unsigned char *)src)[0]) << 8) + | (unsigned)(((const unsigned char *)src)[1]); +} + +static inline void +sph_enc16le(void *dst, unsigned val) +{ + ((unsigned char *)dst)[0] = val; + ((unsigned char *)dst)[1] = val >> 8; +} + +static inline unsigned +sph_dec16le(const void *src) +{ + return (unsigned)(((const unsigned char *)src)[0]) + | ((unsigned)(((const unsigned char *)src)[1]) << 8); +} + +/** + * Encode a 32-bit value into the provided buffer (big endian convention). + * + * @param dst the destination buffer + * @param val the 32-bit value to encode + */ +static inline void +sph_enc32be(void *dst, sph_u32 val) +{ + ((unsigned char *)dst)[0] = (val >> 24); + ((unsigned char *)dst)[1] = (val >> 16); + ((unsigned char *)dst)[2] = (val >> 8); + ((unsigned char *)dst)[3] = val; +} + +/** + * Encode a 32-bit value into the provided buffer (big endian convention). + * The destination buffer must be properly aligned. + * + * @param dst the destination buffer (32-bit aligned) + * @param val the value to encode + */ +static inline void +sph_enc32be_aligned(void *dst, sph_u32 val) +{ +#if SPH_LITTLE_ENDIAN + *(sph_u32 *)dst = sph_bswap32(val); +#elif SPH_BIG_ENDIAN + *(sph_u32 *)dst = val; +#else + ((unsigned char *)dst)[0] = (val >> 24); + ((unsigned char *)dst)[1] = (val >> 16); + ((unsigned char *)dst)[2] = (val >> 8); + ((unsigned char *)dst)[3] = val; +#endif +} + +/** + * Decode a 32-bit value from the provided buffer (big endian convention). + * + * @param src the source buffer + * @return the decoded value + */ +static inline sph_u32 +sph_dec32be(const void *src) +{ + return ((sph_u32)(((const unsigned char *)src)[0]) << 24) + | ((sph_u32)(((const unsigned char *)src)[1]) << 16) + | ((sph_u32)(((const unsigned char *)src)[2]) << 8) + | (sph_u32)(((const unsigned char *)src)[3]); +} + +/** + * Decode a 32-bit value from the provided buffer (big endian convention). + * The source buffer must be properly aligned. + * + * @param src the source buffer (32-bit aligned) + * @return the decoded value + */ +static inline sph_u32 +sph_dec32be_aligned(const void *src) +{ +#if SPH_LITTLE_ENDIAN + return sph_bswap32(*(const sph_u32 *)src); +#elif SPH_BIG_ENDIAN + return *(const sph_u32 *)src; +#else + return ((sph_u32)(((const unsigned char *)src)[0]) << 24) + | ((sph_u32)(((const unsigned char *)src)[1]) << 16) + | ((sph_u32)(((const unsigned char *)src)[2]) << 8) + | (sph_u32)(((const unsigned char *)src)[3]); +#endif +} + +/** + * Encode a 32-bit value into the provided buffer (little endian convention). + * + * @param dst the destination buffer + * @param val the 32-bit value to encode + */ +static inline void +sph_enc32le(void *dst, sph_u32 val) +{ + ((unsigned char *)dst)[0] = val; + ((unsigned char *)dst)[1] = (val >> 8); + ((unsigned char *)dst)[2] = (val >> 16); + ((unsigned char *)dst)[3] = (val >> 24); +} + +/** + * Encode a 32-bit value into the provided buffer (little endian convention). + * The destination buffer must be properly aligned. + * + * @param dst the destination buffer (32-bit aligned) + * @param val the value to encode + */ +static inline void +sph_enc32le_aligned(void *dst, sph_u32 val) +{ +#if SPH_LITTLE_ENDIAN + *(sph_u32 *)dst = val; +#elif SPH_BIG_ENDIAN + *(sph_u32 *)dst = sph_bswap32(val); +#else + ((unsigned char *)dst)[0] = val; + ((unsigned char *)dst)[1] = (val >> 8); + ((unsigned char *)dst)[2] = (val >> 16); + ((unsigned char *)dst)[3] = (val >> 24); +#endif +} + +/** + * Decode a 32-bit value from the provided buffer (little endian convention). + * + * @param src the source buffer + * @return the decoded value + */ +static inline sph_u32 +sph_dec32le(const void *src) +{ + return (sph_u32)(((const unsigned char *)src)[0]) + | ((sph_u32)(((const unsigned char *)src)[1]) << 8) + | ((sph_u32)(((const unsigned char *)src)[2]) << 16) + | ((sph_u32)(((const unsigned char *)src)[3]) << 24); +} + +/** + * Decode a 32-bit value from the provided buffer (little endian convention). + * The source buffer must be properly aligned. + * + * @param src the source buffer (32-bit aligned) + * @return the decoded value + */ +static inline sph_u32 +sph_dec32le_aligned(const void *src) +{ +#if SPH_LITTLE_ENDIAN + return *(const sph_u32 *)src; +#elif SPH_BIG_ENDIAN + return sph_bswap32(*(const sph_u32 *)src); +#else + return (sph_u32)(((const unsigned char *)src)[0]) + | ((sph_u32)(((const unsigned char *)src)[1]) << 8) + | ((sph_u32)(((const unsigned char *)src)[2]) << 16) + | ((sph_u32)(((const unsigned char *)src)[3]) << 24); +#endif +} + +/** + * Encode a 64-bit value into the provided buffer (big endian convention). + * + * @param dst the destination buffer + * @param val the 64-bit value to encode + */ +static inline void +sph_enc64be(void *dst, sph_u64 val) +{ + ((unsigned char *)dst)[0] = (val >> 56); + ((unsigned char *)dst)[1] = (val >> 48); + ((unsigned char *)dst)[2] = (val >> 40); + ((unsigned char *)dst)[3] = (val >> 32); + ((unsigned char *)dst)[4] = (val >> 24); + ((unsigned char *)dst)[5] = (val >> 16); + ((unsigned char *)dst)[6] = (val >> 8); + ((unsigned char *)dst)[7] = val; +} + +/** + * Encode a 64-bit value into the provided buffer (big endian convention). + * The destination buffer must be properly aligned. + * + * @param dst the destination buffer (64-bit aligned) + * @param val the value to encode + */ +static inline void +sph_enc64be_aligned(void *dst, sph_u64 val) +{ +#if SPH_LITTLE_ENDIAN + *(sph_u64 *)dst = sph_bswap64(val); +#elif SPH_BIG_ENDIAN + *(sph_u64 *)dst = val; +#else + ((unsigned char *)dst)[0] = (val >> 56); + ((unsigned char *)dst)[1] = (val >> 48); + ((unsigned char *)dst)[2] = (val >> 40); + ((unsigned char *)dst)[3] = (val >> 32); + ((unsigned char *)dst)[4] = (val >> 24); + ((unsigned char *)dst)[5] = (val >> 16); + ((unsigned char *)dst)[6] = (val >> 8); + ((unsigned char *)dst)[7] = val; +#endif +} + +/** + * Decode a 64-bit value from the provided buffer (big endian convention). + * + * @param src the source buffer + * @return the decoded value + */ +static inline sph_u64 +sph_dec64be(const void *src) +{ + return ((sph_u64)(((const unsigned char *)src)[0]) << 56) + | ((sph_u64)(((const unsigned char *)src)[1]) << 48) + | ((sph_u64)(((const unsigned char *)src)[2]) << 40) + | ((sph_u64)(((const unsigned char *)src)[3]) << 32) + | ((sph_u64)(((const unsigned char *)src)[4]) << 24) + | ((sph_u64)(((const unsigned char *)src)[5]) << 16) + | ((sph_u64)(((const unsigned char *)src)[6]) << 8) + | (sph_u64)(((const unsigned char *)src)[7]); +} + +/** + * Decode a 64-bit value from the provided buffer (big endian convention). + * The source buffer must be properly aligned. + * + * @param src the source buffer (64-bit aligned) + * @return the decoded value + */ +static inline sph_u64 +sph_dec64be_aligned(const void *src) +{ +#if SPH_LITTLE_ENDIAN + return sph_bswap64(*(const sph_u64 *)src); +#elif SPH_BIG_ENDIAN + return *(const sph_u64 *)src; +#else + return ((sph_u64)(((const unsigned char *)src)[0]) << 56) + | ((sph_u64)(((const unsigned char *)src)[1]) << 48) + | ((sph_u64)(((const unsigned char *)src)[2]) << 40) + | ((sph_u64)(((const unsigned char *)src)[3]) << 32) + | ((sph_u64)(((const unsigned char *)src)[4]) << 24) + | ((sph_u64)(((const unsigned char *)src)[5]) << 16) + | ((sph_u64)(((const unsigned char *)src)[6]) << 8) + | (sph_u64)(((const unsigned char *)src)[7]); +#endif +} + +/** + * Encode a 64-bit value into the provided buffer (little endian convention). + * + * @param dst the destination buffer + * @param val the 64-bit value to encode + */ +static inline void +sph_enc64le(void *dst, sph_u64 val) +{ + ((unsigned char *)dst)[0] = val; + ((unsigned char *)dst)[1] = (val >> 8); + ((unsigned char *)dst)[2] = (val >> 16); + ((unsigned char *)dst)[3] = (val >> 24); + ((unsigned char *)dst)[4] = (val >> 32); + ((unsigned char *)dst)[5] = (val >> 40); + ((unsigned char *)dst)[6] = (val >> 48); + ((unsigned char *)dst)[7] = (val >> 56); +} + +/** + * Encode a 64-bit value into the provided buffer (little endian convention). + * The destination buffer must be properly aligned. + * + * @param dst the destination buffer (64-bit aligned) + * @param val the value to encode + */ +static inline void +sph_enc64le_aligned(void *dst, sph_u64 val) +{ +#if SPH_LITTLE_ENDIAN + *(sph_u64 *)dst = val; +#elif SPH_BIG_ENDIAN + *(sph_u64 *)dst = sph_bswap64(val); +#else + ((unsigned char *)dst)[0] = val; + ((unsigned char *)dst)[1] = (val >> 8); + ((unsigned char *)dst)[2] = (val >> 16); + ((unsigned char *)dst)[3] = (val >> 24); + ((unsigned char *)dst)[4] = (val >> 32); + ((unsigned char *)dst)[5] = (val >> 40); + ((unsigned char *)dst)[6] = (val >> 48); + ((unsigned char *)dst)[7] = (val >> 56); +#endif +} + +/** + * Decode a 64-bit value from the provided buffer (little endian convention). + * + * @param src the source buffer + * @return the decoded value + */ +static inline sph_u64 +sph_dec64le(const void *src) +{ + return (sph_u64)(((const unsigned char *)src)[0]) + | ((sph_u64)(((const unsigned char *)src)[1]) << 8) + | ((sph_u64)(((const unsigned char *)src)[2]) << 16) + | ((sph_u64)(((const unsigned char *)src)[3]) << 24) + | ((sph_u64)(((const unsigned char *)src)[4]) << 32) + | ((sph_u64)(((const unsigned char *)src)[5]) << 40) + | ((sph_u64)(((const unsigned char *)src)[6]) << 48) + | ((sph_u64)(((const unsigned char *)src)[7]) << 56); +} + +/** + * Decode a 64-bit value from the provided buffer (little endian convention). + * The source buffer must be properly aligned. + * + * @param src the source buffer (64-bit aligned) + * @return the decoded value + */ +static inline sph_u64 +sph_dec64le_aligned(const void *src) +{ +#if SPH_LITTLE_ENDIAN + return *(const sph_u64 *)src; +#elif SPH_BIG_ENDIAN + return sph_bswap64(*(const sph_u64 *)src); +#else + return (sph_u64)(((const unsigned char *)src)[0]) + | ((sph_u64)(((const unsigned char *)src)[1]) << 8) + | ((sph_u64)(((const unsigned char *)src)[2]) << 16) + | ((sph_u64)(((const unsigned char *)src)[3]) << 24) + | ((sph_u64)(((const unsigned char *)src)[4]) << 32) + | ((sph_u64)(((const unsigned char *)src)[5]) << 40) + | ((sph_u64)(((const unsigned char *)src)[6]) << 48) + | ((sph_u64)(((const unsigned char *)src)[7]) << 56); +#endif +} + +#endif diff --git a/tools/windows-replace/trezor-crypto/include/TrezorCrypto/nist256p1.h b/tools/windows-replace/trezor-crypto/include/TrezorCrypto/nist256p1.h new file mode 100644 index 00000000000..0c0a743d566 --- /dev/null +++ b/tools/windows-replace/trezor-crypto/include/TrezorCrypto/nist256p1.h @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2013-2014 Tomas Dzetkulic + * Copyright (c) 2013-2014 Pavol Rusnak + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __NIST256P1_H__ +#define __NIST256P1_H__ + +#include + +#include "bip32.h" +#include "ecdsa.h" + +//extern const ecdsa_curve nist256p1; +//extern const curve_info nist256p1_info; +//win +#ifdef __cplusplus +extern "C" { +#endif + +extern const ecdsa_curve nist256p1; +extern const curve_info nist256p1_info; + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif diff --git a/tools/windows-replace/trezor-crypto/include/TrezorCrypto/rand.h b/tools/windows-replace/trezor-crypto/include/TrezorCrypto/rand.h new file mode 100644 index 00000000000..7171a9ad860 --- /dev/null +++ b/tools/windows-replace/trezor-crypto/include/TrezorCrypto/rand.h @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2013-2014 Tomas Dzetkulic + * Copyright (c) 2013-2014 Pavol Rusnak + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __RAND_H__ +#define __RAND_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +//win +// [wallet-core] Reference counted init and release +void *random_init(void); +void random_release(void); + + +uint32_t random32(void); +void random_buffer(uint8_t *buf, size_t len); + +//uint32_t random_uniform(uint32_t n); +//void random_permute(char *buf, size_t len); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/tools/windows-replace/walletconsole/CMakeLists.txt b/tools/windows-replace/walletconsole/CMakeLists.txt new file mode 100644 index 00000000000..1c6bba462d5 --- /dev/null +++ b/tools/windows-replace/walletconsole/CMakeLists.txt @@ -0,0 +1,16 @@ +# 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. + +# walletconsole executable +file(GLOB walletconsole_sources *.cpp) +add_executable(walletconsole ${walletconsole_sources}) + + +target_link_libraries(walletconsole walletconsolelib TrezorCrypto TrustWalletCore ${Protobuf_LIBRARIES} Boost::boost) + +target_include_directories(walletconsole PRIVATE ${CMAKE_SOURCE_DIR}/walletconsole/lib ${CMAKE_SOURCE_DIR}/src) + +INSTALL(TARGETS walletconsole DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/tools/windows-replace/walletconsole/lib/CMakeLists.txt b/tools/windows-replace/walletconsole/lib/CMakeLists.txt new file mode 100644 index 00000000000..b14eb51ec08 --- /dev/null +++ b/tools/windows-replace/walletconsole/lib/CMakeLists.txt @@ -0,0 +1,12 @@ +# 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. + +# walletconsolelib library +file(GLOB_RECURSE walletconsolelib_sources *.cpp) +add_library(walletconsolelib ${walletconsolelib_sources}) +#target_link_libraries(tests gtest_main TrezorCrypto TrustWalletCore ${Protobuf_LIBRARIES} Boost::boost) +target_link_libraries(walletconsolelib TrezorCrypto TrustWalletCore ${Protobuf_LIBRARIES} Boost::boost) +target_include_directories(walletconsolelib PRIVATE ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/src) diff --git a/tools/windows-replace/windows-dragon-king.pl b/tools/windows-replace/windows-dragon-king.pl new file mode 100644 index 00000000000..1d9512255ad --- /dev/null +++ b/tools/windows-replace/windows-dragon-king.pl @@ -0,0 +1,96 @@ +if ($#ARGV+1 != 1 ) { + + print "Enter the command: -spouting or -suction,\n"; + print " -spouting : Place Windows dependencies in the tools folder and project home directory.\n"; + print " -suction : Bring Windows dependencies back into the tools folder.\n"; + exit; +} +sub replace_head +{ + @toolsPath = @_; + @TWtoolsFiles; + for($a = 0;$a < $#toolsPath + 1;$a = $a + 1) + { + $toolsPathString = join("",$toolsPath[$a]); + my $stringPos = rindex($toolsPathString,"/"); + my $toolsName = substr($toolsPathString,$stringPos + 1); + + my $TWtoolsFilesString = join( "",$tools_Dir ,$toolsName ); + $TWtoolsFiles[$a] = $TWtoolsFilesString; + } + return @TWtoolsFiles; + +} + + +$op=$ARGV[0]; +#path--> /wallet-core-win +use Cwd; +$TWdir = getcwd; + +$TWdir = join( "", $TWdir ,"/" ); + +#path--> tools/ +$tools_Dir = join( "", $TWdir,"tools/" ); + + +#path--> tools/windows-replace/ +$tools_windowsReplace_Dir = join( "", $TWdir,"tools/windows-replace/" ); + + +#file--> tools/windows-replace/powerShell/* +$powerShellAllDir = join( "", $TWdir,"tools/windows-replace/powerShell/*" ); + +#path--> tools/windows-replace/powerShell/ +$powerShellDir = join( "", $TWdir,"tools/windows-replace/powerShell/" ); + +#file--> tools/windows-replace/powerShell/*.* +@powerShellFiles = glob( $powerShellAllDir ); + +#file--> tools/* +@TWtoolsFile = replace_head(@powerShellFiles); + +#file--> tools/windows-replace/powerShell/windows-bootstrap.ps1 +$windowsBootstrapFile = join( "", $powerShellDir,"windows-bootstrap.ps1" ); + +#file--> ./windows-bootstrap.ps1 +$TWwindowsBootstrapFile = join( "", $TWdir,"windows-bootstrap.ps1" ); + + +if ($op eq "-spouting") +{ + use File::Copy; + for($a = 0;$a < @powerShellFiles;$a = $a + 1) + { + + if($windowsBootstrapFile eq $powerShellFiles[$a] ) + { + #å°†windowsBootstrap文件拷è´åˆ°ä¸»ç›®å½•ä¸‹ + copy $windowsBootstrapFile ,$TWdir or warn 'copy failed.'; + } + else + { + #æ‹·è´åˆ°tools目录下 + copy $powerShellFiles[$a] ,$tools_Dir or warn 'copy failed.'; + } + + } + +} +elsif($op eq "-suction") +{ + use File::Copy; + move($TWwindowsBootstrapFile,$powerShellDir); + for($a = 0;$a < @powerShellFiles;$a = $a + 1) + { + move($TWtoolsFile[$a],$powerShellDir); + } +} +else{ + + print "Enter the command: -spouting or -suction,\n"; + print " -spouting : Place Windows dependencies in the tools folder and project home directory.\n"; + print " -suction : Bring Windows dependencies back into the tools folder.\n"; + exit; +} + diff --git a/tools/windows-samples.ps1 b/tools/windows-samples.ps1 index f67ecda997b..4ed82a3a656 100644 --- a/tools/windows-samples.ps1 +++ b/tools/windows-samples.ps1 @@ -1,15 +1,15 @@ # Build Samples +# Load dependencies version +. $PSScriptRoot\windows-dependencies-version.ps1 + $ErrorActionPreference = "Stop" $root = $pwd $prefix = Join-Path $pwd "build\local" $install = Join-Path $pwd "build\install" -$cmakeGenerator = "Visual Studio 17 2022" -$cmakePlatform = "x64" -$cmakeToolset = "v143" cd samples\cpp if (-not(Test-Path -Path "build" -PathType Container)) { diff --git a/tools/windows-tests.ps1 b/tools/windows-tests.ps1 index 2330dd680c5..d3883de0bb2 100644 --- a/tools/windows-tests.ps1 +++ b/tools/windows-tests.ps1 @@ -1,4 +1,3 @@ - # Run tests $ErrorActionPreference = "Stop" @@ -12,3 +11,4 @@ if ($LASTEXITCODE -ne 0) { if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + diff --git a/trezor-crypto/CMakeLists.txt b/trezor-crypto/CMakeLists.txt index 09eeb4b0f6e..eae01a61570 100644 --- a/trezor-crypto/CMakeLists.txt +++ b/trezor-crypto/CMakeLists.txt @@ -1,5 +1,32 @@ +# 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. + +set(TW_WARNING_FLAGS + -W + -Wall + -Wextra + -Wimplicit-function-declaration + -Wredundant-decls + -Wstrict-prototypes + -Wundef + -Wshadow + -Wpointer-arith + -Wformat + -Wreturn-type + -Wsign-compare + -Wmultichar + -Wformat-nonliteral + -Winit-self + -Wuninitialized + -Wformat-security + -Wno-missing-braces +) + add_library(TrezorCrypto - crypto/bignum.c crypto/ecdsa.c crypto/curves.c crypto/secp256k1.c crypto/rand.c crypto/hmac.c crypto/bip32.c crypto/bip39.c crypto/pbkdf2.c crypto/base58.c crypto/base32.c + crypto/bignum.c crypto/ecdsa.c crypto/curves.c crypto/secp256k1.c crypto/rand.c crypto/hmac.c crypto/bip32.c crypto/bip39.c crypto/slip39.c crypto/pbkdf2.c crypto/base58.c crypto/base32.c crypto/address.c crypto/script.c crypto/ripemd160.c @@ -29,39 +56,29 @@ add_library(TrezorCrypto crypto/groestl.c crypto/hmac_drbg.c crypto/rfc6979.c - crypto/schnorr.c crypto/shamir.c + crypto/zilliqa.c + crypto/cardano.c ) +if (EMSCRIPTEN) + message(STATUS "Skip building trezor-crypto/tests") + set(TW_WARNING_FLAGS ${TW_WARNING_FLAGS} -Wno-bitwise-instead-of-logical) +else () + if(NOT ANDROID AND NOT IOS_PLATFORM AND NOT (WIN32 AND NOT TW_STATIC_LIBRARY)) + add_subdirectory(crypto/tests) + endif() +endif() + if(NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "MSVC")) - target_compile_options(TrezorCrypto - PRIVATE - -W - -Wall - -Wextra - -Wimplicit-function-declaration - -Wredundant-decls - -Wstrict-prototypes - -Wundef - -Wshadow - -Wpointer-arith - -Wformat - -Wreturn-type - -Wsign-compare - -Wmultichar - -Wformat-nonliteral - -Winit-self - -Wuninitialized - -Wformat-security - -Wno-missing-braces - -Werror - ) + target_compile_options(TrezorCrypto PRIVATE ${TW_WARNING_FLAGS} -Werror PUBLIC -Wno-deprecated-volatile) endif() if(WIN32) target_link_libraries(TrezorCrypto bcrypt) endif() + target_include_directories(TrezorCrypto PUBLIC $ @@ -70,13 +87,10 @@ target_include_directories(TrezorCrypto src ) -if(NOT ANDROID AND NOT IOS_PLATFORM AND NOT (WIN32 AND NOT TW_STATIC_LIBRARY)) - add_subdirectory(crypto/tests) -endif() - -install(TARGETS TrezorCrypto - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - ) +install( + TARGETS TrezorCrypto + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/trezor-crypto/crypto/base32.c b/trezor-crypto/crypto/base32.c index d4b50ac6dd3..d1cb294c77f 100644 --- a/trezor-crypto/crypto/base32.c +++ b/trezor-crypto/crypto/base32.c @@ -152,12 +152,12 @@ bool base32_8to5(const uint8_t *in, uint8_t length, uint8_t *out, } if (alphabet) { -#ifdef _MSC_VER - uint8_t *decoded = _alloca(length); -#else - uint8_t decoded[length]; -#endif - memset(decoded, 0, length); + #ifdef _MSC_VER + uint8_t *decoded = _alloca(length); + #else + uint8_t decoded[length]; + #endif + memset(decoded, 0, length); //win memset(decoded, 0, sizeof(decoded)); for (size_t i = 0; i < length; i++) { int ret = base32_decode_character(in[i], alphabet); diff --git a/trezor-crypto/crypto/base58.c b/trezor-crypto/crypto/base58.c index faa3ce9a24c..4c870cd50e5 100644 --- a/trezor-crypto/crypto/base58.c +++ b/trezor-crypto/crypto/base58.c @@ -60,11 +60,13 @@ bool b58tobin(void *bin, size_t *binszp, const char *b58) { unsigned char *binu = bin; size_t outisz = (binsz + sizeof(b58_almostmaxint_t) - 1) / sizeof(b58_almostmaxint_t); -#ifdef _MSC_VER + //win + #ifdef _MSC_VER b58_almostmaxint_t *outi = _alloca(sizeof(b58_almostmaxint_t) * outisz); #else b58_almostmaxint_t outi[outisz]; #endif +//win b58_maxint_t t = 0; b58_almostmaxint_t c = 0; size_t i = 0, j = 0; @@ -75,7 +77,7 @@ bool b58tobin(void *bin, size_t *binszp, const char *b58) { size_t b58sz = strlen(b58); - memzero(outi, sizeof(b58_almostmaxint_t) * outisz); + memzero(outi, sizeof(b58_almostmaxint_t) * outisz);//win // Leading zeros, just count for (i = 0; i < b58sz && b58u[i] == '1'; ++i) ++zerocount; @@ -158,13 +160,14 @@ bool b58enc(char *b58, size_t *b58sz, const void *data, size_t binsz) { while (zcount < binsz && !bin[zcount]) ++zcount; size = (binsz - zcount) * 138 / 100 + 1; -#ifdef _MSC_VER + #ifdef _MSC_VER uint8_t *buf = _alloca(size); #else uint8_t buf[size]; #endif memzero(buf, size); + for (i = zcount, high = size - 1; i < binsz; ++i, high = j) { for (carry = bin[i], j = size - 1; (j > high) || carry; --j) { carry += 256 * buf[j]; @@ -198,18 +201,19 @@ int base58_encode_check(const uint8_t *data, int datalen, if (datalen > 128) { return 0; } + #ifdef _MSC_VER uint8_t *buf = _alloca((size_t)datalen + 32); #else uint8_t buf[datalen + 32]; #endif - memset(buf, 0, (size_t)datalen + 32); + memset(buf, 0, (size_t)datalen + 32);//win memset(buf, 0, (size_t)datalen + 32); uint8_t *hash = buf + datalen; memcpy(buf, data, datalen); hasher_Raw(hasher_type, data, datalen, hash); size_t res = strsize; bool success = b58enc(str, &res, buf, datalen + 4); - memzero(buf, (size_t)datalen + 32); + memzero(buf, (size_t)datalen + 32); //win memzero(buf, (size_t)datalen + 32); return success ? res : 0; } @@ -218,12 +222,12 @@ int base58_decode_check(const char *str, HasherType hasher_type, uint8_t *data, if (datalen > 128) { return 0; } -#ifdef _MSC_VER + #ifdef _MSC_VER uint8_t *d = _alloca((size_t)datalen + 4); #else uint8_t d[datalen + 4]; -#endif - memset(d, 0, (size_t)datalen + 4); + #endif + memset(d, 0, (size_t)datalen + 4); //win memset(d, 0, sizeof(d)); size_t res = datalen + 4; if (b58tobin(d, &res, str) != true) { return 0; diff --git a/trezor-crypto/crypto/bignum.c b/trezor-crypto/crypto/bignum.c index 57abe8c8cfa..be739c611e5 100644 --- a/trezor-crypto/crypto/bignum.c +++ b/trezor-crypto/crypto/bignum.c @@ -161,7 +161,7 @@ void bn_read_uint64(uint64_t in_number, bignum256 *out_number) { out_number->val[2] = in_number >> BN_BITS_PER_LIMB; for (uint32_t i = 3; i < BN_LIMBS; i++) out_number->val[i] = 0; } - +//win #ifdef _MSC_VER #include uint32_t __forceinline bn_clz(uint32_t value) @@ -186,7 +186,7 @@ int bn_bitcount(const bignum256 *x) { if (limb != 0) { // __builtin_clz returns the number of leading zero bits starting at the // most significant bit position - return i * BN_BITS_PER_LIMB + (32 - bn_clz(limb)); + return i * BN_BITS_PER_LIMB + (32 - bn_clz(limb)); //win return i * BN_BITS_PER_LIMB + (32 - __builtin_clz(limb)); } } return 0; @@ -285,7 +285,7 @@ int bn_is_equal(const bignum256 *x, const bignum256 *y) { // &truecase == &falsecase or &res == &truecase == &falsecase void bn_cmov(bignum256 *res, volatile uint32_t cond, const bignum256 *truecase, const bignum256 *falsecase) { - assert((cond == 1) | (cond == 0)); + assert((int)(cond == 1) | (cond == 0)); uint32_t tmask = -cond; // tmask = 0xFFFFFFFF if cond else 0x00000000 uint32_t fmask = ~tmask; // fmask = 0x00000000 if cond else 0xFFFFFFFF @@ -304,7 +304,7 @@ void bn_cmov(bignum256 *res, volatile uint32_t cond, const bignum256 *truecase, // Assumes prime is normalized and // 0 < prime < 2**260 == 2**(BITS_PER_LIMB * LIMBS - 1) void bn_cnegate(volatile uint32_t cond, bignum256 *x, const bignum256 *prime) { - assert((cond == 1) | (cond == 0)); + assert((int)(cond == 1) | (cond == 0)); uint32_t tmask = -cond; // tmask = 0xFFFFFFFF if cond else 0x00000000 uint32_t fmask = ~tmask; // fmask = 0x00000000 if cond else 0xFFFFFFFF diff --git a/trezor-crypto/crypto/bip32.c b/trezor-crypto/crypto/bip32.c index 9c0da8217ae..4d821a8cf69 100644 --- a/trezor-crypto/crypto/bip32.c +++ b/trezor-crypto/crypto/bip32.c @@ -48,23 +48,12 @@ #include "nem.h" #endif #if USE_CARDANO -#include +#include #endif #include -#define CARDANO_MAX_NODE_DEPTH 1048576 - const curve_info ed25519_info = { - .bip32_name = "ed25519 seed", - .params = NULL, - .hasher_base58 = HASHER_SHA2D, - .hasher_sign = HASHER_SHA2D, - .hasher_pubkey = HASHER_SHA2_RIPEMD, - .hasher_script = HASHER_SHA2, -}; - -const curve_info ed25519_cardano_info = { - .bip32_name = "ed25519 cardano seed", + .bip32_name = ED25519_SEED_NAME, .params = NULL, .hasher_base58 = HASHER_SHA2D, .hasher_sign = HASHER_SHA2D, @@ -215,11 +204,17 @@ uint32_t hdnode_fingerprint(HDNode *node) { return fingerprint; } -int hdnode_private_ckd(HDNode *inout, uint32_t i) { +int hdnode_private_ckd_bip32(HDNode *inout, uint32_t i) { CONFIDENTIAL uint8_t data[1 + 32 + 4]; CONFIDENTIAL uint8_t I[32 + 32]; CONFIDENTIAL bignum256 a, b; +#if USE_CARDANO + if (inout->curve == &ed25519_cardano_info) { + return 0; + } +#endif + if (i & 0x80000000) { // private derivation data[0] = 0; memcpy(data + 1, inout->private_key, 32); @@ -227,7 +222,9 @@ int hdnode_private_ckd(HDNode *inout, uint32_t i) { if (!inout->curve->params) { return 0; } - hdnode_fill_public_key(inout); + if (hdnode_fill_public_key(inout) != 0) { + return 0; + } memcpy(data, inout->public_key, 33); } write_be(data + 33, i); @@ -281,156 +278,17 @@ int hdnode_private_ckd(HDNode *inout, uint32_t i) { return 1; } +int hdnode_private_ckd(HDNode *inout, uint32_t i) { #if USE_CARDANO -static void scalar_multiply8(const uint8_t *src, int bytes, uint8_t *dst) { - uint8_t prev_acc = 0; - for (int i = 0; i < bytes; i++) { - dst[i] = (src[i] << 3) + (prev_acc & 0x7); - prev_acc = src[i] >> 5; - } - dst[bytes] = src[bytes - 1] >> 5; -} - -static void scalar_add_256bits(const uint8_t *src1, const uint8_t *src2, - uint8_t *dst) { - uint16_t r = 0; - for (int i = 0; i < 32; i++) { - r = r + (uint16_t)src1[i] + (uint16_t)src2[i]; - dst[i] = r & 0xff; - r >>= 8; - } -} - -int hdnode_private_ckd_cardano(HDNode *inout, uint32_t index) { - if (inout->depth >= CARDANO_MAX_NODE_DEPTH) { - return 0; - } - - // checks for hardened/non-hardened derivation, keysize 32 means we are - // dealing with public key and thus non-h, keysize 64 is for private key - int keysize = 32; - if (index & 0x80000000) { - keysize = 64; - } - - CONFIDENTIAL uint8_t data[1 + 64 + 4]; - CONFIDENTIAL uint8_t z[32 + 32]; - CONFIDENTIAL uint8_t priv_key[64]; - CONFIDENTIAL uint8_t res_key[64]; - - write_le(data + keysize + 1, index); - - memcpy(priv_key, inout->private_key, 32); - memcpy(priv_key + 32, inout->private_key_extension, 32); - - if (keysize == 64) { // private derivation - data[0] = 0; - memcpy(data + 1, inout->private_key, 32); - memcpy(data + 1 + 32, inout->private_key_extension, 32); - } else { // public derivation - hdnode_fill_public_key(inout); - data[0] = 2; - memcpy(data + 1, inout->public_key + 1, 32); - } - - CONFIDENTIAL HMAC_SHA512_CTX ctx; - hmac_sha512_Init(&ctx, inout->chain_code, 32); - hmac_sha512_Update(&ctx, data, 1 + keysize + 4); - hmac_sha512_Final(&ctx, z); - - CONFIDENTIAL uint8_t zl8[32]; - memzero(zl8, 32); - - /* get 8 * Zl */ - scalar_multiply8(z, 28, zl8); - /* Kl = 8*Zl + parent(K)l */ - scalar_add_256bits(zl8, priv_key, res_key); - - /* Kr = Zr + parent(K)r */ - scalar_add_256bits(z + 32, priv_key + 32, res_key + 32); - - memcpy(inout->private_key, res_key, 32); - memcpy(inout->private_key_extension, res_key + 32, 32); - - if (keysize == 64) { - data[0] = 1; - } else { - data[0] = 3; + if (inout->curve == &ed25519_cardano_info) { + return hdnode_private_ckd_cardano(inout, i); + } else +#endif + { + return hdnode_private_ckd_bip32(inout, i); } - hmac_sha512_Init(&ctx, inout->chain_code, 32); - hmac_sha512_Update(&ctx, data, 1 + keysize + 4); - hmac_sha512_Final(&ctx, z); - - memcpy(inout->chain_code, z + 32, 32); - inout->depth++; - inout->child_num = index; - memzero(inout->public_key, sizeof(inout->public_key)); - - // making sure to wipe our memory - memzero(z, sizeof(z)); - memzero(data, sizeof(data)); - memzero(priv_key, sizeof(priv_key)); - memzero(res_key, sizeof(res_key)); - return 1; -} - -static int hdnode_from_secret_cardano(const uint8_t *k, - const uint8_t *chain_code, HDNode *out) { - memzero(out, sizeof(HDNode)); - out->depth = 0; - out->child_num = 0; - out->curve = &ed25519_cardano_info; - memcpy(out->private_key, k, 32); - memcpy(out->private_key_extension, k + 32, 32); - memcpy(out->chain_code, chain_code, 32); - - out->private_key[0] &= 0xf8; - out->private_key[31] &= 0x1f; - out->private_key[31] |= 0x40; - - out->public_key[0] = 0; - hdnode_fill_public_key(out); - - return 1; -} - -// Derives the root Cardano HDNode from a master secret, aka seed, as defined in -// SLIP-0023. -int hdnode_from_seed_cardano(const uint8_t *seed, int seed_len, HDNode *out) { - CONFIDENTIAL uint8_t I[SHA512_DIGEST_LENGTH]; - CONFIDENTIAL uint8_t k[SHA512_DIGEST_LENGTH]; - CONFIDENTIAL HMAC_SHA512_CTX ctx; - - hmac_sha512_Init(&ctx, (const uint8_t *)ED25519_CARDANO_NAME, - strlen(ED25519_CARDANO_NAME)); - hmac_sha512_Update(&ctx, seed, seed_len); - hmac_sha512_Final(&ctx, I); - - sha512_Raw(I, 32, k); - - int ret = hdnode_from_secret_cardano(k, I + 32, out); - - memzero(I, sizeof(I)); - memzero(k, sizeof(k)); - memzero(&ctx, sizeof(ctx)); - return ret; } -// Derives the root Cardano HDNode from a passphrase and the entropy encoded in -// a BIP-0039 mnemonic using the Icarus derivation scheme, aka V2 derivation -// scheme. -int hdnode_from_entropy_cardano_icarus(const uint8_t *pass, int pass_len, - const uint8_t *entropy, int entropy_len, - HDNode *out) { - CONFIDENTIAL uint8_t secret[96]; - pbkdf2_hmac_sha512(pass, pass_len, entropy, entropy_len, 4096, secret, 96); - - int ret = hdnode_from_secret_cardano(secret, secret + 64, out); - memzero(secret, sizeof(secret)); - return ret; -} -#endif - int hdnode_public_ckd_cp(const ecdsa_curve *curve, const curve_point *parent, const uint8_t *parent_chain_code, uint32_t i, curve_point *child, uint8_t *child_chain_code) { @@ -530,6 +388,13 @@ CONFIDENTIAL struct { HDNode node; } private_ckd_cache[BIP32_CACHE_SIZE]; +void bip32_cache_clear(void) { + private_ckd_cache_root_set = false; + private_ckd_cache_index = 0; + memzero(&private_ckd_cache_root, sizeof(private_ckd_cache_root)); + memzero(private_ckd_cache, sizeof(private_ckd_cache)); +} + int hdnode_private_ckd_cached(HDNode *inout, const uint32_t *i, size_t i_count, uint32_t *fingerprint) { if (i_count == 0) { @@ -597,26 +462,34 @@ int hdnode_private_ckd_cached(HDNode *inout, const uint32_t *i, size_t i_count, } #endif -void hdnode_get_address_raw(HDNode *node, uint32_t version, uint8_t *addr_raw) { - hdnode_fill_public_key(node); +int hdnode_get_address_raw(HDNode *node, uint32_t version, uint8_t *addr_raw) { + if (hdnode_fill_public_key(node) != 0) { + return 1; + } ecdsa_get_address_raw(node->public_key, version, node->curve->hasher_pubkey, addr_raw); + return 0; } -void hdnode_get_address(HDNode *node, uint32_t version, char *addr, - int addrsize) { - hdnode_fill_public_key(node); +int hdnode_get_address(HDNode *node, uint32_t version, char *addr, + int addrsize) { + if (hdnode_fill_public_key(node) != 0) { + return 1; + } ecdsa_get_address(node->public_key, version, node->curve->hasher_pubkey, node->curve->hasher_base58, addr, addrsize); + return 0; } -void hdnode_fill_public_key(HDNode *node) { - if (node->public_key[0] != 0) return; +int hdnode_fill_public_key(HDNode *node) { + if (node->public_key[0] != 0) return 0; #if USE_BIP32_25519_CURVES if (node->curve->params) { - ecdsa_get_public_key33(node->curve->params, node->private_key, - node->public_key); + if (ecdsa_get_public_key33(node->curve->params, node->private_key, + node->public_key) != 0) { + return 1; + } } else { node->public_key[0] = 1; if (node->curve == &ed25519_info) { @@ -631,16 +504,18 @@ void hdnode_fill_public_key(HDNode *node) { curve25519_scalarmult_basepoint(node->public_key + 1, node->private_key); #if USE_CARDANO } else if (node->curve == &ed25519_cardano_info) { - ed25519_publickey_ext(node->private_key, node->private_key_extension, - node->public_key + 1); + ed25519_publickey_ext(node->private_key, node->public_key + 1); #endif } } #else - ecdsa_get_public_key33(node->curve->params, node->private_key, - node->public_key); + if (ecdsa_get_public_key33(node->curve->params, node->private_key, + node->public_key) != 0) { + return 1; + } #endif + return 0; } #if USE_ETHEREUM @@ -649,7 +524,10 @@ int hdnode_get_ethereum_pubkeyhash(const HDNode *node, uint8_t *pubkeyhash) { SHA3_CTX ctx = {0}; /* get uncompressed public key */ - ecdsa_get_public_key65(node->curve->params, node->private_key, buf); + if (ecdsa_get_public_key65(node->curve->params, node->private_key, buf) != + 0) { + return 0; + } /* compute sha3 of x and y coordinate without 04 prefix */ sha3_256_Init(&ctx); @@ -669,7 +547,10 @@ int hdnode_get_nem_address(HDNode *node, uint8_t version, char *address) { return 0; } - hdnode_fill_public_key(node); + if (hdnode_fill_public_key(node) != 0) { + return 0; + } + return nem_get_address(&node->public_key[1], version, address); } @@ -778,17 +659,12 @@ int hdnode_sign(HDNode *node, const uint8_t *msg, uint32_t msg_len, return 1; // signatures are not supported } else { if (node->curve == &ed25519_info) { - hdnode_fill_public_key(node); - ed25519_sign(msg, msg_len, node->private_key, node->public_key + 1, sig); + ed25519_sign(msg, msg_len, node->private_key, sig); } else if (node->curve == &ed25519_sha3_info) { - hdnode_fill_public_key(node); - ed25519_sign_sha3(msg, msg_len, node->private_key, node->public_key + 1, - sig); + ed25519_sign_sha3(msg, msg_len, node->private_key, sig); #if USE_KECCAK } else if (node->curve == &ed25519_keccak_info) { - hdnode_fill_public_key(node); - ed25519_sign_keccak(msg, msg_len, node->private_key, node->public_key + 1, - sig); + ed25519_sign_keccak(msg, msg_len, node->private_key, sig); #endif } else { return 1; // unknown or unsupported curve diff --git a/trezor-crypto/crypto/bip39.c b/trezor-crypto/crypto/bip39.c index b64a75207c9..fc61539c938 100644 --- a/trezor-crypto/crypto/bip39.c +++ b/trezor-crypto/crypto/bip39.c @@ -44,6 +44,11 @@ CONFIDENTIAL struct { uint8_t seed[512 / 8]; } bip39_cache[BIP39_CACHE_SIZE]; +void bip39_cache_clear(void) { + memzero(bip39_cache, sizeof(bip39_cache)); + bip39_cache_index = 0; +} + #endif // [wallet-core] Added output buffer @@ -249,7 +254,7 @@ void mnemonic_to_seed(const char *mnemonic, const char *passphrase, // binary search for finding the word in the wordlist int mnemonic_find_word(const char *word) { - int lo = 0, hi = BIP39_WORDS - 1; + int lo = 0, hi = BIP39_WORD_COUNT - 1; while (lo <= hi) { int mid = lo + (hi - lo) / 2; int cmp = strcmp(word, wordlist[mid]); @@ -277,7 +282,7 @@ const char *mnemonic_complete_word(const char *prefix, int len) { } const char *mnemonic_get_word(int index) { - if (index >= 0 && index < BIP39_WORDS) { + if (index >= 0 && index < BIP39_WORD_COUNT) { return wordlist[index]; } else { return NULL; diff --git a/trezor-crypto/crypto/blake256.c b/trezor-crypto/crypto/blake256.c index 0da6918102a..a4e9b489c37 100644 --- a/trezor-crypto/crypto/blake256.c +++ b/trezor-crypto/crypto/blake256.c @@ -169,9 +169,8 @@ void blake256_Update( BLAKE256_CTX *S, const uint8_t *in, size_t inlen ) { memcpy( ( void * ) ( S->buf + left ), \ ( void * ) in, ( size_t ) inlen ); - S->buflen = left + ( int )inlen; } - else S->buflen = 0; + S->buflen = left + inlen; } diff --git a/trezor-crypto/crypto/blake2b.c b/trezor-crypto/crypto/blake2b.c index a279cc5d08b..c630ed0f8cf 100644 --- a/trezor-crypto/crypto/blake2b.c +++ b/trezor-crypto/crypto/blake2b.c @@ -36,7 +36,8 @@ typedef struct blake2b_param__ uint8_t reserved[14]; /* 32 */ uint8_t salt[BLAKE2B_SALTBYTES]; /* 48 */ uint8_t personal[BLAKE2B_PERSONALBYTES]; /* 64 */ -} +} //win __attribute__((packed)) blake2b_param; + #ifndef _MSC_VER __attribute__((packed)) #endif diff --git a/trezor-crypto/crypto/blake2s.c b/trezor-crypto/crypto/blake2s.c index 7a828295540..6de68990302 100644 --- a/trezor-crypto/crypto/blake2s.c +++ b/trezor-crypto/crypto/blake2s.c @@ -22,6 +22,7 @@ #ifdef _MSC_VER #pragma pack(push, 1) #endif + typedef struct blake2s_param__ { uint8_t digest_length; /* 1 */ @@ -36,7 +37,7 @@ typedef struct blake2s_param__ /* uint8_t reserved[0]; */ uint8_t salt[BLAKE2S_SALTBYTES]; /* 24 */ uint8_t personal[BLAKE2S_PERSONALBYTES]; /* 32 */ -} +} // win __attribute__((packed)) blake2s_param; #ifndef _MSC_VER __attribute__((packed)) #endif diff --git a/trezor-crypto/crypto/cardano.c b/trezor-crypto/crypto/cardano.c new file mode 100644 index 00000000000..650b66ad513 --- /dev/null +++ b/trezor-crypto/crypto/cardano.c @@ -0,0 +1,328 @@ +/** + * Copyright (c) 2013-2021 SatoshiLabs + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if USE_CARDANO + +#define CARDANO_MAX_NODE_DEPTH 1048576 + +const curve_info ed25519_cardano_info = { + .bip32_name = ED25519_CARDANO_NAME, + .params = NULL, + .hasher_base58 = HASHER_SHA2D, + .hasher_sign = HASHER_SHA2D, + .hasher_pubkey = HASHER_SHA2_RIPEMD, + .hasher_script = HASHER_SHA2, +}; + +static void scalar_multiply8(const uint8_t *src, int bytes, uint8_t *dst) { + uint8_t prev_acc = 0; + for (int i = 0; i < bytes; i++) { + dst[i] = (src[i] << 3) + (prev_acc & 0x7); + prev_acc = src[i] >> 5; + } + dst[bytes] = src[bytes - 1] >> 5; +} + +static void scalar_add_256bits(const uint8_t *src1, const uint8_t *src2, + uint8_t *dst) { + uint16_t r = 0; + for (int i = 0; i < 32; i++) { + r = r + (uint16_t)src1[i] + (uint16_t)src2[i]; + dst[i] = r & 0xff; + r >>= 8; + } +} + +static void cardano_ed25519_tweak_bits(uint8_t private_key[32]) { + private_key[0] &= 0xf8; + private_key[31] &= 0x1f; + private_key[31] |= 0x40; +} + +int hdnode_private_ckd_cardano(HDNode *inout, uint32_t index) { + if (inout->curve != &ed25519_cardano_info) { + return 0; + } + + if (inout->depth >= CARDANO_MAX_NODE_DEPTH) { + return 0; + } + + // checks for hardened/non-hardened derivation, keysize 32 means we are + // dealing with public key and thus non-h, keysize 64 is for private key + int keysize = 32; + if (index & 0x80000000) { + keysize = 64; + } + + CONFIDENTIAL uint8_t data[1 + 64 + 4]; + CONFIDENTIAL uint8_t z[32 + 32]; + CONFIDENTIAL uint8_t priv_key[64]; + CONFIDENTIAL uint8_t res_key[64]; + + write_le(data + keysize + 1, index); + + memcpy(priv_key, inout->private_key, 32); + memcpy(priv_key + 32, inout->private_key_extension, 32); + + if (keysize == 64) { // private derivation + data[0] = 0; + memcpy(data + 1, inout->private_key, 32); + memcpy(data + 1 + 32, inout->private_key_extension, 32); + } else { // public derivation + if (hdnode_fill_public_key(inout) != 0) { + return 0; + } + data[0] = 2; + memcpy(data + 1, inout->public_key + 1, 32); + } + + CONFIDENTIAL HMAC_SHA512_CTX ctx; + hmac_sha512_Init(&ctx, inout->chain_code, 32); + hmac_sha512_Update(&ctx, data, 1 + keysize + 4); + hmac_sha512_Final(&ctx, z); + + CONFIDENTIAL uint8_t zl8[32]; + memzero(zl8, 32); + + /* get 8 * Zl */ + scalar_multiply8(z, 28, zl8); + /* Kl = 8*Zl + parent(K)l */ + scalar_add_256bits(zl8, priv_key, res_key); + + /* Kr = Zr + parent(K)r */ + scalar_add_256bits(z + 32, priv_key + 32, res_key + 32); + + memcpy(inout->private_key, res_key, 32); + memcpy(inout->private_key_extension, res_key + 32, 32); + + if (keysize == 64) { + data[0] = 1; + } else { + data[0] = 3; + } + hmac_sha512_Init(&ctx, inout->chain_code, 32); + hmac_sha512_Update(&ctx, data, 1 + keysize + 4); + hmac_sha512_Final(&ctx, z); + + memcpy(inout->chain_code, z + 32, 32); + inout->depth++; + inout->child_num = index; + memzero(inout->public_key, sizeof(inout->public_key)); + + // making sure to wipe our memory + memzero(z, sizeof(z)); + memzero(data, sizeof(data)); + memzero(priv_key, sizeof(priv_key)); + memzero(res_key, sizeof(res_key)); + return 1; +} + +int hdnode_from_secret_cardano(const uint8_t secret[CARDANO_SECRET_LENGTH], + HDNode *out) { + memzero(out, sizeof(HDNode)); + out->depth = 0; + out->child_num = 0; + out->curve = &ed25519_cardano_info; + memcpy(out->private_key, secret, 32); + memcpy(out->private_key_extension, secret + 32, 32); + memcpy(out->chain_code, secret + 64, 32); + + cardano_ed25519_tweak_bits(out->private_key); + + out->public_key[0] = 0; + if (hdnode_fill_public_key(out) != 0) { + return 0; + } + + return 1; +} + +// Derives the root Cardano secret from a master secret, aka seed, as defined in +// SLIP-0023. +int secret_from_seed_cardano_slip23(const uint8_t *seed, int seed_len, + uint8_t secret_out[CARDANO_SECRET_LENGTH]) { + CONFIDENTIAL uint8_t I[SHA512_DIGEST_LENGTH]; + CONFIDENTIAL HMAC_SHA512_CTX ctx; + + hmac_sha512_Init(&ctx, (const uint8_t *)ED25519_CARDANO_NAME, + strlen(ED25519_CARDANO_NAME)); + hmac_sha512_Update(&ctx, seed, seed_len); + hmac_sha512_Final(&ctx, I); + + sha512_Raw(I, 32, secret_out); + + memcpy(secret_out + SHA512_DIGEST_LENGTH, I + 32, 32); + cardano_ed25519_tweak_bits(secret_out); + + memzero(I, sizeof(I)); + memzero(&ctx, sizeof(ctx)); + return 1; +} + +// Derives the root Cardano secret from a BIP-32 master secret via the Ledger +// derivation: +// https://github.com/cardano-foundation/CIPs/blob/09d7d8ee1bd64f7e6b20b5a6cae088039dce00cb/CIP-0003/Ledger.md +int secret_from_seed_cardano_ledger(const uint8_t *seed, int seed_len, + uint8_t secret_out[CARDANO_SECRET_LENGTH]) { + CONFIDENTIAL uint8_t chain_code[SHA256_DIGEST_LENGTH]; + CONFIDENTIAL uint8_t root_key[SHA512_DIGEST_LENGTH]; + CONFIDENTIAL HMAC_SHA256_CTX ctx; + CONFIDENTIAL HMAC_SHA512_CTX sctx; + + const uint8_t *intermediate_result = seed; + int intermediate_result_len = seed_len; + do { + // STEP 1: derive a master secret like in BIP-32/SLIP-10 + hmac_sha512_Init(&sctx, (const uint8_t *)ED25519_SEED_NAME, + strlen(ED25519_SEED_NAME)); + hmac_sha512_Update(&sctx, intermediate_result, intermediate_result_len); + hmac_sha512_Final(&sctx, root_key); + + // STEP 2: check that the resulting key does not have a particular bit set, + // otherwise iterate like in SLIP-10 + intermediate_result = root_key; + intermediate_result_len = sizeof(root_key); + } while (root_key[31] & 0x20); + + // STEP 3: calculate the chain code as a HMAC-SHA256 of "\x01" + seed, + // key is "ed25519 seed" + hmac_sha256_Init(&ctx, (const unsigned char *)ED25519_SEED_NAME, + strlen(ED25519_SEED_NAME)); + hmac_sha256_Update(&ctx, (const unsigned char *)"\x01", 1); + hmac_sha256_Update(&ctx, seed, seed_len); + hmac_sha256_Final(&ctx, chain_code); + + //win +#ifdef _MSC_VER +static_assert( + SHA512_DIGEST_LENGTH + SHA256_DIGEST_LENGTH == CARDANO_SECRET_LENGTH, + "Invalid configuration of Cardano secret size"); + memcpy(secret_out, root_key, SHA512_DIGEST_LENGTH); + memcpy(secret_out + SHA512_DIGEST_LENGTH, chain_code, SHA256_DIGEST_LENGTH); +#else +// STEP 4: extract information into output + _Static_assert( + SHA512_DIGEST_LENGTH + SHA256_DIGEST_LENGTH == CARDANO_SECRET_LENGTH, + "Invalid configuration of Cardano secret size"); + memcpy(secret_out, root_key, SHA512_DIGEST_LENGTH); + memcpy(secret_out + SHA512_DIGEST_LENGTH, chain_code, SHA256_DIGEST_LENGTH); +#endif +#define CARDANO_ICARUS_ROUNDS_PER_STEP \ + (CARDANO_ICARUS_PBKDF2_ROUNDS / CARDANO_ICARUS_STEPS) + + + + + // STEP 5: tweak bits of the private key + cardano_ed25519_tweak_bits(secret_out); + + memzero(&ctx, sizeof(ctx)); + memzero(&sctx, sizeof(sctx)); + memzero(root_key, sizeof(root_key)); + memzero(chain_code, sizeof(chain_code)); + return 1; +} + +#define CARDANO_ICARUS_STEPS 32 +//win +#ifdef _MSC_VER +static_assert( + CARDANO_ICARUS_PBKDF2_ROUNDS % CARDANO_ICARUS_STEPS == 0, + "CARDANO_ICARUS_STEPS does not divide CARDANO_ICARUS_PBKDF2_ROUNDS"); +#else +_Static_assert( + CARDANO_ICARUS_PBKDF2_ROUNDS % CARDANO_ICARUS_STEPS == 0, + "CARDANO_ICARUS_STEPS does not divide CARDANO_ICARUS_PBKDF2_ROUNDS"); +#endif +#define CARDANO_ICARUS_ROUNDS_PER_STEP \ + (CARDANO_ICARUS_PBKDF2_ROUNDS / CARDANO_ICARUS_STEPS) + +// Derives the root Cardano HDNode from a passphrase and the entropy encoded in +// a BIP-0039 mnemonic using the Icarus derivation scheme, aka V2 derivation +// scheme: +// https://github.com/cardano-foundation/CIPs/blob/09d7d8ee1bd64f7e6b20b5a6cae088039dce00cb/CIP-0003/Icarus.md +int secret_from_entropy_cardano_icarus( + const uint8_t *pass, int pass_len, const uint8_t *entropy, int entropy_len, + uint8_t secret_out[CARDANO_SECRET_LENGTH], + void (*progress_callback)(uint32_t, uint32_t)) { + CONFIDENTIAL PBKDF2_HMAC_SHA512_CTX pctx; + CONFIDENTIAL uint8_t digest[SHA512_DIGEST_LENGTH]; + uint32_t progress = 0; + + // PASS 1: first 64 bytes + pbkdf2_hmac_sha512_Init(&pctx, pass, pass_len, entropy, entropy_len, 1); + if (progress_callback) { + progress_callback(progress, CARDANO_ICARUS_PBKDF2_ROUNDS * 2); + } + for (int i = 0; i < CARDANO_ICARUS_STEPS; i++) { + pbkdf2_hmac_sha512_Update(&pctx, CARDANO_ICARUS_ROUNDS_PER_STEP); + if (progress_callback) { + progress += CARDANO_ICARUS_ROUNDS_PER_STEP; + progress_callback(progress, CARDANO_ICARUS_PBKDF2_ROUNDS * 2); + } + } + pbkdf2_hmac_sha512_Final(&pctx, digest); + + memcpy(secret_out, digest, SHA512_DIGEST_LENGTH); + + // PASS 2: remaining 32 bytes + pbkdf2_hmac_sha512_Init(&pctx, pass, pass_len, entropy, entropy_len, 2); + if (progress_callback) { + progress_callback(progress, CARDANO_ICARUS_PBKDF2_ROUNDS * 2); + } + for (int i = 0; i < CARDANO_ICARUS_STEPS; i++) { + pbkdf2_hmac_sha512_Update(&pctx, CARDANO_ICARUS_ROUNDS_PER_STEP); + if (progress_callback) { + progress += CARDANO_ICARUS_ROUNDS_PER_STEP; + progress_callback(progress, CARDANO_ICARUS_PBKDF2_ROUNDS * 2); + } + } + pbkdf2_hmac_sha512_Final(&pctx, digest); + + memcpy(secret_out + SHA512_DIGEST_LENGTH, digest, + CARDANO_SECRET_LENGTH - SHA512_DIGEST_LENGTH); + + cardano_ed25519_tweak_bits(secret_out); + + memzero(&pctx, sizeof(pctx)); + memzero(digest, sizeof(digest)); + return 1; +} + +#endif // USE_CARDANO \ No newline at end of file diff --git a/trezor-crypto/crypto/chacha20poly1305/LICENSE b/trezor-crypto/crypto/chacha20poly1305/LICENSE new file mode 100644 index 00000000000..95404966f07 --- /dev/null +++ b/trezor-crypto/crypto/chacha20poly1305/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (C) 2016 Will Glozer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/trezor-crypto/crypto/chacha20poly1305/chacha_merged.c b/trezor-crypto/crypto/chacha20poly1305/chacha_merged.c index 8d4ee90c755..3819b865a59 100644 --- a/trezor-crypto/crypto/chacha20poly1305/chacha_merged.c +++ b/trezor-crypto/crypto/chacha20poly1305/chacha_merged.c @@ -23,6 +23,7 @@ void ECRYPT_init(void) return; } +// [wallet-core][non static] rename to avoid duplicate symbol in blake256.c const char chacha_sigma[16] = "expand 32-byte k"; const char tau[16] = "expand 16-byte k"; @@ -59,6 +60,12 @@ void ECRYPT_ivsetup(ECRYPT_ctx *x,const u8 *iv) x->input[15] = U8TO32_LITTLE(iv + 4); } +void ECRYPT_ctrsetup(ECRYPT_ctx *x,const u8 *ctr) +{ + x->input[12] = U8TO32_LITTLE(ctr + 0); + x->input[13] = U8TO32_LITTLE(ctr + 4); +} + void ECRYPT_encrypt_bytes(ECRYPT_ctx *x,const u8 *m,u8 *c,u32 bytes) { u32 x0 = 0, x1 = 0, x2 = 0, x3 = 0, x4 = 0, x5 = 0, x6 = 0, x7 = 0, x8 = 0, x9 = 0, x10 = 0, x11 = 0, x12 = 0, x13 = 0, x14 = 0, x15 = 0; diff --git a/trezor-crypto/crypto/chacha_drbg.c b/trezor-crypto/crypto/chacha_drbg.c index c1bd5d08e6f..e8027ffe939 100644 --- a/trezor-crypto/crypto/chacha_drbg.c +++ b/trezor-crypto/crypto/chacha_drbg.c @@ -19,44 +19,108 @@ #include +#include #include +#include + +#include +#include +#include + +#define CHACHA_DRBG_KEY_LENGTH 32 +#define CHACHA_DRBG_COUNTER_LENGTH 8 +#define CHACHA_DRBG_IV_LENGTH 8 +#define CHACHA_DRBG_SEED_LENGTH \ + (CHACHA_DRBG_KEY_LENGTH + CHACHA_DRBG_COUNTER_LENGTH + CHACHA_DRBG_IV_LENGTH) #define MAX(a, b) (a) > (b) ? (a) : (b) -void chacha_drbg_init(CHACHA_DRBG_CTX *ctx, - const uint8_t entropy[CHACHA_DRBG_SEED_LENGTH]) { +static void derivation_function(const uint8_t *input1, size_t input1_length, + const uint8_t *input2, size_t input2_length, + uint8_t *output, size_t output_length) { + // Implementation of Hash_df from NIST SP 800-90A + uint32_t block_count = (output_length - 1) / SHA256_DIGEST_LENGTH + 1; + size_t partial_block_length = output_length % SHA256_DIGEST_LENGTH; + assert(block_count <= 255); + + uint32_t output_length_bits = output_length * 8; +#if BYTE_ORDER == LITTLE_ENDIAN + REVERSE32(output_length_bits, output_length_bits); +#endif + + SHA256_CTX ctx = {0}; + + for (uint8_t counter = 1; counter <= block_count; counter++) { + sha256_Init(&ctx); + sha256_Update(&ctx, &counter, sizeof(counter)); + sha256_Update(&ctx, (uint8_t *)&output_length_bits, + sizeof(output_length_bits)); + sha256_Update(&ctx, input1, input1_length); + sha256_Update(&ctx, input2, input2_length); + + if (counter != block_count || partial_block_length == 0) { + sha256_Final(&ctx, output); + output += SHA256_DIGEST_LENGTH; + } else { // last block is partial + uint8_t digest[SHA256_DIGEST_LENGTH] = {0}; + sha256_Final(&ctx, digest); + memcpy(output, digest, partial_block_length); + memzero(digest, sizeof(digest)); + } + } + + memzero(&ctx, sizeof(ctx)); +} + +void chacha_drbg_init(CHACHA_DRBG_CTX *ctx, const uint8_t *entropy, + size_t entropy_length, const uint8_t *nonce, + size_t nonce_length) { uint8_t buffer[MAX(CHACHA_DRBG_KEY_LENGTH, CHACHA_DRBG_IV_LENGTH)] = {0}; ECRYPT_keysetup(&ctx->chacha_ctx, buffer, CHACHA_DRBG_KEY_LENGTH * 8, CHACHA_DRBG_IV_LENGTH * 8); ECRYPT_ivsetup(&ctx->chacha_ctx, buffer); - chacha_drbg_reseed(ctx, entropy); + chacha_drbg_reseed(ctx, entropy, entropy_length, nonce, nonce_length); } static void chacha_drbg_update(CHACHA_DRBG_CTX *ctx, const uint8_t data[CHACHA_DRBG_SEED_LENGTH]) { - uint8_t buffer[CHACHA_DRBG_SEED_LENGTH] = {0}; + uint8_t seed[CHACHA_DRBG_SEED_LENGTH] = {0}; if (data) - ECRYPT_encrypt_bytes(&ctx->chacha_ctx, data, buffer, - CHACHA_DRBG_SEED_LENGTH); + ECRYPT_encrypt_bytes(&ctx->chacha_ctx, data, seed, CHACHA_DRBG_SEED_LENGTH); else - ECRYPT_keystream_bytes(&ctx->chacha_ctx, buffer, CHACHA_DRBG_SEED_LENGTH); + ECRYPT_keystream_bytes(&ctx->chacha_ctx, seed, CHACHA_DRBG_SEED_LENGTH); - ECRYPT_keysetup(&ctx->chacha_ctx, buffer, CHACHA_DRBG_KEY_LENGTH * 8, + ECRYPT_keysetup(&ctx->chacha_ctx, seed, CHACHA_DRBG_KEY_LENGTH * 8, CHACHA_DRBG_IV_LENGTH * 8); - ECRYPT_ivsetup(&ctx->chacha_ctx, buffer + CHACHA_DRBG_KEY_LENGTH); + + ECRYPT_ivsetup(&ctx->chacha_ctx, + seed + CHACHA_DRBG_KEY_LENGTH + CHACHA_DRBG_COUNTER_LENGTH); + + ECRYPT_ctrsetup(&ctx->chacha_ctx, seed + CHACHA_DRBG_KEY_LENGTH); + + memzero(seed, sizeof(seed)); } void chacha_drbg_generate(CHACHA_DRBG_CTX *ctx, uint8_t *output, - uint8_t output_length) { + size_t output_length) { + assert(output_length < 65536); + assert(ctx->reseed_counter + 1 != 0); + ECRYPT_keystream_bytes(&ctx->chacha_ctx, output, output_length); chacha_drbg_update(ctx, NULL); ctx->reseed_counter++; } -void chacha_drbg_reseed(CHACHA_DRBG_CTX *ctx, - const uint8_t entropy[CHACHA_DRBG_SEED_LENGTH]) { - chacha_drbg_update(ctx, entropy); +void chacha_drbg_reseed(CHACHA_DRBG_CTX *ctx, const uint8_t *entropy, + size_t entropy_length, const uint8_t *additional_input, + size_t additional_input_length) { + uint8_t seed[CHACHA_DRBG_SEED_LENGTH] = {0}; + derivation_function(entropy, entropy_length, additional_input, + additional_input_length, seed, sizeof(seed)); + chacha_drbg_update(ctx, seed); + memzero(seed, sizeof(seed)); + ctx->reseed_counter = 1; } diff --git a/trezor-crypto/crypto/curves.c b/trezor-crypto/crypto/curves.c index c95ac8b7512..a6221a9943f 100644 --- a/trezor-crypto/crypto/curves.c +++ b/trezor-crypto/crypto/curves.c @@ -21,6 +21,7 @@ */ #include +#include const char SECP256K1_NAME[] = "secp256k1"; const char SECP256K1_DECRED_NAME[] = "secp256k1-decred"; @@ -28,10 +29,14 @@ const char SECP256K1_GROESTL_NAME[] = "secp256k1-groestl"; const char SECP256K1_SMART_NAME[] = "secp256k1-smart"; const char NIST256P1_NAME[] = "nist256p1"; const char ED25519_NAME[] = "ed25519"; +const char ED25519_SEED_NAME[] = "ed25519 seed"; +#if USE_CARDANO const char ED25519_CARDANO_NAME[] = "ed25519 cardano seed"; -const char ED25519_BLAKE2B_NANO_NAME[] = "ed25519-blake2b-nano"; // [wallet-core] +#endif const char ED25519_SHA3_NAME[] = "ed25519-sha3"; #if USE_KECCAK const char ED25519_KECCAK_NAME[] = "ed25519-keccak"; #endif const char CURVE25519_NAME[] = "curve25519"; + +const char ED25519_BLAKE2B_NANO_NAME[] = "ed25519-blake2b-nano"; // [wallet-core] diff --git a/trezor-crypto/crypto/ecdsa.c b/trezor-crypto/crypto/ecdsa.c index 0f2198fbbc0..445f29aa549 100644 --- a/trezor-crypto/crypto/ecdsa.c +++ b/trezor-crypto/crypto/ecdsa.c @@ -36,7 +36,6 @@ #include #include #include -#include // Set cp2 = cp1 void point_copy(const curve_point *cp1, curve_point *cp2) { *cp2 = *cp1; } @@ -404,13 +403,16 @@ void point_jacobian_double(jacobian_curve_point *p, const ecdsa_curve *curve) { } // res = k * p -void point_multiply(const ecdsa_curve *curve, const bignum256 *k, - const curve_point *p, curve_point *res) { +// returns 0 on success +int point_multiply(const ecdsa_curve *curve, const bignum256 *k, + const curve_point *p, curve_point *res) { // this algorithm is loosely based on // Katsuyuki Okeya and Tsuyoshi Takagi, The Width-w NAF Method Provides // Small Memory and Fast Elliptic Scalar Multiplications Secure against // Side Channel Attacks. - assert(bn_is_less(k, &curve->order)); + if (!bn_is_less(k, &curve->order)) { + return 1; + } int i = 0, j = 0; CONFIDENTIAL bignum256 a; @@ -442,7 +444,7 @@ void point_multiply(const ecdsa_curve *curve, const bignum256 *k, // special case 0*p: just return zero. We don't care about constant time. if (!is_non_zero) { point_set_infinity(res); - return; + return 1; } // Now a = k + 2^256 (mod curve->order) and a is odd. @@ -523,15 +525,20 @@ void point_multiply(const ecdsa_curve *curve, const bignum256 *k, jacobian_to_curve(&jres, res, prime); memzero(&a, sizeof(a)); memzero(&jres, sizeof(jres)); + + return 0; } #if USE_PRECOMPUTED_CP // res = k * G // k must be a normalized number with 0 <= k < curve->order -void scalar_multiply(const ecdsa_curve *curve, const bignum256 *k, - curve_point *res) { - assert(bn_is_less(k, &curve->order)); +// returns 0 on success +int scalar_multiply(const ecdsa_curve *curve, const bignum256 *k, + curve_point *res) { + if (!bn_is_less(k, &curve->order)) { + return 1; + } int i = {0}, j = {0}; CONFIDENTIAL bignum256 a; @@ -559,7 +566,7 @@ void scalar_multiply(const ecdsa_curve *curve, const bignum256 *k, // special case 0*G: just return zero. We don't care about constant time. if (!is_non_zero) { point_set_infinity(res); - return; + return 0; } // Now a = k + 2^256 (mod curve->order) and a is odd. @@ -612,13 +619,15 @@ void scalar_multiply(const ecdsa_curve *curve, const bignum256 *k, jacobian_to_curve(&jres, res, prime); memzero(&a, sizeof(a)); memzero(&jres, sizeof(jres)); + + return 0; } #else -void scalar_multiply(const ecdsa_curve *curve, const bignum256 *k, - curve_point *res) { - point_multiply(curve, k, &curve->G, res); +int scalar_multiply(const ecdsa_curve *curve, const bignum256 *k, + curve_point *res) { + return point_multiply(curve, k, &curve->G, res); } #endif @@ -632,6 +641,11 @@ int ecdh_multiply(const ecdsa_curve *curve, const uint8_t *priv_key, bignum256 k = {0}; bn_read_be(priv_key, &k); + if (bn_is_zero(&k) || !bn_is_less(&k, &curve->order)) { + // Invalid private key. + return 2; + } + point_multiply(curve, &k, &point, &point); memzero(&k, sizeof(k)); @@ -673,10 +687,17 @@ int ecdsa_sign_digest(const ecdsa_curve *curve, const uint8_t *priv_key, #if USE_RFC6979 rfc6979_state rng = {0}; - init_rfc6979(priv_key, digest, &rng); + init_rfc6979(priv_key, digest, curve, &rng); #endif bn_read_be(digest, &z); + if (bn_is_zero(&z)) { + // The probability of the digest being all-zero by chance is infinitesimal, + // so this is most likely an indication of a bug. Furthermore, the signature + // has no value, because in this case it can be easily forged for any public + // key, see ecdsa_verify_digest(). + return 1; + } for (i = 0; i < 10000; i++) { #if USE_RFC6979 @@ -704,11 +725,16 @@ int ecdsa_sign_digest(const ecdsa_curve *curve, const uint8_t *priv_key, continue; } + bn_read_be(priv_key, s); + if (bn_is_zero(s) || !bn_is_less(s, &curve->order)) { + // Invalid private key. + return 2; + } + // randomize operations to counter side-channel attacks generate_k_random(&randk, &curve->order); bn_multiply(&randk, &k, &curve->order); // k*rand bn_inverse(&k, &curve->order); // (k*rand)^-1 - bn_read_be(priv_key, s); // priv bn_multiply(&R.x, s, &curve->order); // R.x*priv bn_add(s, &z); // R.x*priv + z bn_multiply(&k, s, &curve->order); // (k*rand)^-1 (R.x*priv + z) @@ -755,33 +781,55 @@ int ecdsa_sign_digest(const ecdsa_curve *curve, const uint8_t *priv_key, return -1; } -void ecdsa_get_public_key33(const ecdsa_curve *curve, const uint8_t *priv_key, - uint8_t *pub_key) { +// returns 0 on success +int ecdsa_get_public_key33(const ecdsa_curve *curve, const uint8_t *priv_key, + uint8_t *pub_key) { curve_point R = {0}; bignum256 k = {0}; bn_read_be(priv_key, &k); + if (bn_is_zero(&k) || !bn_is_less(&k, &curve->order)) { + // Invalid private key. + memzero(pub_key, 33); + return -1; + } + // compute k*G - scalar_multiply(curve, &k, &R); + if (scalar_multiply(curve, &k, &R) != 0) { + memzero(&k, sizeof(k)); + return 1; + } pub_key[0] = 0x02 | (R.y.val[0] & 0x01); bn_write_be(&R.x, pub_key + 1); memzero(&R, sizeof(R)); memzero(&k, sizeof(k)); + return 0; } -void ecdsa_get_public_key65(const ecdsa_curve *curve, const uint8_t *priv_key, - uint8_t *pub_key) { +// returns 0 on success +int ecdsa_get_public_key65(const ecdsa_curve *curve, const uint8_t *priv_key, + uint8_t *pub_key) { curve_point R = {0}; bignum256 k = {0}; bn_read_be(priv_key, &k); + if (bn_is_zero(&k) || !bn_is_less(&k, &curve->order)) { + // Invalid private key. + memzero(pub_key, 65); + return -1; + } + // compute k*G - scalar_multiply(curve, &k, &R); + if (scalar_multiply(curve, &k, &R) != 0) { + memzero(&k, sizeof(k)); + return 1; + } pub_key[0] = 0x04; bn_write_be(&R.x, pub_key + 1); bn_write_be(&R.y, pub_key + 33); memzero(&R, sizeof(R)); memzero(&k, sizeof(k)); + return 0; } int ecdsa_uncompress_pubkey(const ecdsa_curve *curve, const uint8_t *pub_key, @@ -1017,6 +1065,10 @@ int ecdsa_recover_pub_from_sig(const ecdsa_curve *curve, uint8_t *pub_key, scalar_multiply(curve, &e, &cp2); // cp = (s * r^-1 * k - digest * r^-1) * G = Pub point_add(curve, &cp2, &cp); + // The point at infinity is not considered to be a valid public key. + if (point_is_infinity(&cp)) { + return 1; + } pub_key[0] = 0x04; bn_write_be(&cp.x, pub_key + 1); bn_write_be(&cp.y, pub_key + 33); @@ -1107,7 +1159,7 @@ int ecdsa_sig_to_der(const uint8_t *sig, uint8_t *der) { // process R i = 0; - while (sig[i] == 0 && i < 32) { + while (i < 31 && sig[i] == 0) { i++; } // skip leading zeroes if (sig[i] >= 0x80) { // put zero in output if MSB set @@ -1130,7 +1182,7 @@ int ecdsa_sig_to_der(const uint8_t *sig, uint8_t *der) { // process S i = 32; - while (sig[i] == 0 && i < 64) { + while (i < 63 && sig[i] == 0) { i++; } // skip leading zeroes if (sig[i] >= 0x80) { // put zero in output if MSB set @@ -1197,56 +1249,3 @@ int ecdsa_sig_from_der(const uint8_t *der, size_t der_len, uint8_t sig[64]) { return 0; } - -// [wallet-core] -int zil_schnorr_sign(const ecdsa_curve *curve, const uint8_t *priv_key, const uint8_t *msg, const uint32_t msg_len, uint8_t *sig) -{ - int i; - bignum256 k; - - uint8_t hash[32]; - sha256_Raw(msg, msg_len, hash); - - rfc6979_state rng; - init_rfc6979(priv_key, hash, &rng); - - for (i = 0; i < 10000; i++) { - // generate K deterministically - generate_k_rfc6979(&k, &rng); - // if k is too big or too small, we don't like it - if (bn_is_zero(&k) || !bn_is_less(&k, &curve->order)) { - continue; - } - - schnorr_sign_pair sign; - if (schnorr_sign(curve, priv_key, &k, msg, msg_len, &sign) != 0) { - continue; - } - - // we're done - memcpy(sig, sign.r, 32); - memcpy(sig + 32, sign.s, 32); - - memzero(&k, sizeof(k)); - memzero(&rng, sizeof(rng)); - memzero(&sign, sizeof(sign)); - return 0; - } - - // Too many retries without a valid signature - // -> fail with an error - memzero(&k, sizeof(k)); - memzero(&rng, sizeof(rng)); - return -1; -} - -// [wallet-core] -int zil_schnorr_verify(const ecdsa_curve *curve, const uint8_t *pub_key, const uint8_t *sig, const uint8_t *msg, const uint32_t msg_len) -{ - schnorr_sign_pair sign; - - memcpy(sign.r, sig, 32); - memcpy(sign.s, sig + 32, 32); - - return schnorr_verify(curve, pub_key, msg, msg_len, &sign); -} diff --git a/trezor-crypto/crypto/ed25519-donna/ed25519.c b/trezor-crypto/crypto/ed25519-donna/ed25519.c index 8c3b837fa4d..6e01c0c1e98 100644 --- a/trezor-crypto/crypto/ed25519-donna/ed25519.c +++ b/trezor-crypto/crypto/ed25519-donna/ed25519.c @@ -18,6 +18,7 @@ #include #include +#include /* Generates a (extsk[0..31]) and aExt (extsk[32..63]) @@ -31,10 +32,10 @@ ed25519_extsk(hash_512bits extsk, const ed25519_secret_key sk) { } static void -ed25519_hram(hash_512bits hram, const ed25519_signature RS, const ed25519_public_key pk, const unsigned char *m, size_t mlen) { +ed25519_hram(hash_512bits hram, const ed25519_public_key R, const ed25519_public_key pk, const unsigned char *m, size_t mlen) { ed25519_hash_context ctx; ed25519_hash_init(&ctx); - ed25519_hash_update(&ctx, RS, 32); + ed25519_hash_update(&ctx, R, 32); ed25519_hash_update(&ctx, pk, 32); ed25519_hash_update(&ctx, m, mlen); ed25519_hash_final(&ctx, hram); @@ -42,34 +43,11 @@ ed25519_hram(hash_512bits hram, const ed25519_signature RS, const ed25519_public void ED25519_FN(ed25519_publickey) (const ed25519_secret_key sk, ed25519_public_key pk) { - bignum256modm a = {0}; - ge25519 ALIGN(16) A; hash_512bits extsk = {0}; - - /* A = aB */ ed25519_extsk(extsk, sk); - - expand256_modm(a, extsk, 32); - ge25519_scalarmult_base_niels(&A, ge25519_niels_base_multiples, a); - ge25519_pack(pk, &A); -} - -#if USE_CARDANO -void -ED25519_FN(ed25519_publickey_ext) (const ed25519_secret_key sk, const ed25519_secret_key skext, ed25519_public_key pk) { - bignum256modm a = {0}; - ge25519 ALIGN(16) A; - hash_512bits extsk = {0}; - - /* we don't stretch the key through hashing first since its already 64 bytes */ - - memcpy(extsk, sk, 32); - memcpy(extsk+32, skext, 32); - expand256_modm(a, extsk, 32); - ge25519_scalarmult_base_niels(&A, ge25519_niels_base_multiples, a); - ge25519_pack(pk, &A); + ed25519_publickey_ext(extsk, pk); + memzero(&extsk, sizeof(extsk)); } -#endif void ED25519_FN(ed25519_cosi_sign) (const unsigned char *m, size_t mlen, const ed25519_secret_key sk, const ed25519_secret_key nonce, const ed25519_public_key R, const ed25519_public_key pk, ed25519_cosi_signature sig) { @@ -81,6 +59,7 @@ ED25519_FN(ed25519_cosi_sign) (const unsigned char *m, size_t mlen, const ed2551 /* r = nonce */ expand256_modm(r, extnonce, 32); + memzero(&extnonce, sizeof(extnonce)); /* S = H(R,A,m).. */ ed25519_hram(hram, R, pk, m, mlen); @@ -88,57 +67,25 @@ ED25519_FN(ed25519_cosi_sign) (const unsigned char *m, size_t mlen, const ed2551 /* S = H(R,A,m)a */ expand256_modm(a, extsk, 32); + memzero(&extsk, sizeof(extsk)); mul256_modm(S, S, a); + memzero(&a, sizeof(a)); /* S = (r + H(R,A,m)a) */ add256_modm(S, S, r); + memzero(&r, sizeof(r)); /* S = (r + H(R,A,m)a) mod L */ contract256_modm(sig, S); } void -ED25519_FN(ed25519_sign) (const unsigned char *m, size_t mlen, const ed25519_secret_key sk, const ed25519_public_key pk, ed25519_signature RS) { - ed25519_hash_context ctx; - bignum256modm r = {0}, S = {0}, a = {0}; - ge25519 ALIGN(16) R = {0}; - hash_512bits extsk = {0}, hashr = {0}, hram = {0}; - - ed25519_extsk(extsk, sk); - - - /* r = H(aExt[32..64], m) */ - ed25519_hash_init(&ctx); - ed25519_hash_update(&ctx, extsk + 32, 32); - ed25519_hash_update(&ctx, m, mlen); - ed25519_hash_final(&ctx, hashr); - expand256_modm(r, hashr, 64); - - /* R = rB */ - ge25519_scalarmult_base_niels(&R, ge25519_niels_base_multiples, r); - ge25519_pack(RS, &R); - - /* S = H(R,A,m).. */ - ed25519_hram(hram, RS, pk, m, mlen); - expand256_modm(S, hram, 64); - - /* S = H(R,A,m)a */ - expand256_modm(a, extsk, 32); - mul256_modm(S, S, a); - - /* S = (r + H(R,A,m)a) */ - add256_modm(S, S, r); - - /* S = (r + H(R,A,m)a) mod L */ - contract256_modm(RS + 32, S); -} - -#if USE_CARDANO -void -ED25519_FN(ed25519_sign_ext) (const unsigned char *m, size_t mlen, const ed25519_secret_key sk, const ed25519_secret_key skext, const ed25519_public_key pk, ed25519_signature RS) { +ED25519_FN(ed25519_sign_ext) (const unsigned char *m, size_t mlen, const ed25519_secret_key sk, const ed25519_secret_key skext, ed25519_signature RS) { ed25519_hash_context ctx; bignum256modm r = {0}, S = {0}, a = {0}; ge25519 ALIGN(16) R = {0}; + ge25519 ALIGN(16) A = {0}; + ed25519_public_key pk = {0}; hash_512bits extsk = {0}, hashr = {0}, hram = {0}; /* we don't stretch the key through hashing first since its already 64 bytes */ @@ -153,30 +100,47 @@ ED25519_FN(ed25519_sign_ext) (const unsigned char *m, size_t mlen, const ed25519 ed25519_hash_update(&ctx, m, mlen); ed25519_hash_final(&ctx, hashr); expand256_modm(r, hashr, 64); + memzero(&hashr, sizeof(hashr)); /* R = rB */ ge25519_scalarmult_base_niels(&R, ge25519_niels_base_multiples, r); ge25519_pack(RS, &R); + /* a = aExt[0..31] */ + expand256_modm(a, extsk, 32); + memzero(&extsk, sizeof(extsk)); + + /* A = aB */ + ge25519_scalarmult_base_niels(&A, ge25519_niels_base_multiples, a); + ge25519_pack(pk, &A); + /* S = H(R,A,m).. */ ed25519_hram(hram, RS, pk, m, mlen); expand256_modm(S, hram, 64); /* S = H(R,A,m)a */ - expand256_modm(a, extsk, 32); mul256_modm(S, S, a); + memzero(&a, sizeof(a)); /* S = (r + H(R,A,m)a) */ add256_modm(S, S, r); + memzero(&r, sizeof(r)); /* S = (r + H(R,A,m)a) mod L */ contract256_modm(RS + 32, S); } -#endif + +void +ED25519_FN(ed25519_sign) (const unsigned char *m, size_t mlen, const ed25519_secret_key sk, ed25519_signature RS) { + hash_512bits extsk = {0}; + ed25519_extsk(extsk, sk); + ED25519_FN(ed25519_sign_ext)(m, mlen, extsk, extsk + 32, RS); + memzero(&extsk, sizeof(extsk)); +} int ED25519_FN(ed25519_sign_open) (const unsigned char *m, size_t mlen, const ed25519_public_key pk, const ed25519_signature RS) { - ge25519 ALIGN(16) R, A; + ge25519 ALIGN(16) R = {0}, A = {0}; hash_512bits hash = {0}; bignum256modm hram = {0}, S = {0}; unsigned char checkR[32] = {0}; @@ -204,17 +168,19 @@ ED25519_FN(ed25519_sign_open) (const unsigned char *m, size_t mlen, const ed2551 int ED25519_FN(ed25519_scalarmult) (ed25519_public_key res, const ed25519_secret_key sk, const ed25519_public_key pk) { bignum256modm a = {0}; - ge25519 ALIGN(16) A, P; + ge25519 ALIGN(16) A = {0}, P = {0}; hash_512bits extsk = {0}; ed25519_extsk(extsk, sk); expand256_modm(a, extsk, 32); + memzero(&extsk, sizeof(extsk)); if (!ge25519_unpack_negative_vartime(&P, pk)) { return -1; } ge25519_scalarmult(&A, &P, a); + memzero(&a, sizeof(a)); curve25519_neg(A.x, A.x); ge25519_pack(res, &A); return 0; @@ -225,6 +191,19 @@ ED25519_FN(ed25519_scalarmult) (ed25519_public_key res, const ed25519_secret_key #include +void +ed25519_publickey_ext(const ed25519_secret_key extsk, ed25519_public_key pk) { + bignum256modm a = {0}; + ge25519 ALIGN(16) A = {0}; + + expand256_modm(a, extsk, 32); + + /* A = aB */ + ge25519_scalarmult_base_niels(&A, ge25519_niels_base_multiples, a); + memzero(&a, sizeof(a)); + ge25519_pack(pk, &A); +} + int ed25519_cosi_combine_publickeys(ed25519_public_key res, CONST ed25519_public_key *pks, size_t n) { size_t i = 0; @@ -277,8 +256,8 @@ void curve25519_scalarmult_basepoint(curve25519_key pk, const curve25519_key e) { curve25519_key ec = {0}; bignum256modm s = {0}; - bignum25519 ALIGN(16) yplusz, zminusy; - ge25519 ALIGN(16) p; + bignum25519 ALIGN(16) yplusz = {0}, zminusy = {0}; + ge25519 ALIGN(16) p = {0}; size_t i = 0; /* clamp */ @@ -288,9 +267,11 @@ curve25519_scalarmult_basepoint(curve25519_key pk, const curve25519_key e) { ec[31] |= 64; expand_raw256_modm(s, ec); + memzero(&ec, sizeof(ec)); /* scalar * basepoint */ ge25519_scalarmult_base_niels(&p, ge25519_niels_base_multiples, s); + memzero(&s, sizeof(s)); /* u = (y + z) / (z - y) */ curve25519_add(yplusz, p.y, p.z); @@ -310,6 +291,7 @@ curve25519_scalarmult(curve25519_key mypublic, const curve25519_key secret, cons e[31] &= 0x7f; e[31] |= 0x40; curve25519_scalarmult_donna(mypublic, e, basepoint); + memzero(&e, sizeof(e)); } #endif // ED25519_SUFFIX diff --git a/trezor-crypto/crypto/monero/base58.c b/trezor-crypto/crypto/monero/base58.c index 278f23455b1..c5a6f1ccd2b 100644 --- a/trezor-crypto/crypto/monero/base58.c +++ b/trezor-crypto/crypto/monero/base58.c @@ -205,7 +205,7 @@ int xmr_base58_addr_encode_check(uint64_t tag, const uint8_t *data, size_t binsz #else uint8_t buf[(binsz + 1) + HASHER_DIGEST_LENGTH]; #endif - memset(buf, 0, sizeof((binsz + 1) + HASHER_DIGEST_LENGTH)); + memset(buf, 0, sizeof((binsz + 1) + HASHER_DIGEST_LENGTH)); // win memset(buf, 0, sizeof(buf)); uint8_t *hash = buf + binsz + 1; buf[0] = (uint8_t) tag; memcpy(buf + 1, data, binsz); @@ -218,12 +218,13 @@ int xmr_base58_addr_encode_check(uint64_t tag, const uint8_t *data, size_t binsz int xmr_base58_addr_decode_check(const char *addr, size_t sz, uint64_t *tag, void *data, size_t datalen) { size_t buflen = 1 + 64 + addr_checksum_size; -#ifdef _MSC_VER - uint8_t *buf = _alloca(buflen); -#else - uint8_t buf[buflen]; -#endif - memset(buf, 0, buflen); + #ifdef _MSC_VER + uint8_t *buf = _alloca(buflen); + #else + uint8_t buf[buflen]; + #endif + memset(buf, 0, buflen); //win memset(buf, 0, sizeof(buf)); + uint8_t hash[HASHER_DIGEST_LENGTH] = {0}; if (!xmr_base58_decode(addr, sz, buf, &buflen)){ diff --git a/trezor-crypto/crypto/nem.c b/trezor-crypto/crypto/nem.c index fd844156871..66de5cfa9e6 100644 --- a/trezor-crypto/crypto/nem.c +++ b/trezor-crypto/crypto/nem.c @@ -205,8 +205,7 @@ size_t nem_transaction_end(nem_transaction_ctx *ctx, const ed25519_secret_key private_key, ed25519_signature signature) { if (private_key != NULL && signature != NULL) { - ed25519_sign_keccak(ctx->buffer, ctx->offset, private_key, ctx->public_key, - signature); + ed25519_sign_keccak(ctx->buffer, ctx->offset, private_key, signature); } return ctx->offset; diff --git a/trezor-crypto/crypto/rand.c b/trezor-crypto/crypto/rand.c index d874e264aa8..caff5a4242c 100644 --- a/trezor-crypto/crypto/rand.c +++ b/trezor-crypto/crypto/rand.c @@ -20,7 +20,6 @@ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ - #include #include @@ -108,7 +107,6 @@ void random_buffer(uint8_t *buf, size_t len) { } #else - #include #include @@ -124,7 +122,7 @@ void random_release() { } // [wallet-core] -uint32_t __attribute__((weak)) random32() { +uint32_t __attribute__((weak)) random32(void) { int randomData = open("/dev/urandom", O_RDONLY); if (randomData < 0) { return 0; @@ -150,5 +148,4 @@ void __attribute__((weak)) random_buffer(uint8_t *buf, size_t len) { } close(randomData); } - -#endif +#endif \ No newline at end of file diff --git a/trezor-crypto/crypto/rfc6979.c b/trezor-crypto/crypto/rfc6979.c index 98491594bf5..c781e47b926 100644 --- a/trezor-crypto/crypto/rfc6979.c +++ b/trezor-crypto/crypto/rfc6979.c @@ -21,14 +21,30 @@ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ +#include -#include #include #include +#include void init_rfc6979(const uint8_t *priv_key, const uint8_t *hash, - rfc6979_state *state) { - hmac_drbg_init(state, priv_key, 32, hash, 32); + const ecdsa_curve *curve, rfc6979_state *state) { + if (curve) { + bignum256 hash_bn = {0}; + bn_read_be(hash, &hash_bn); + + // Make sure hash is partly reduced modulo order + assert(bn_bitcount(&curve->order) >= 256); + bn_mod(&hash_bn, &curve->order); + + uint8_t hash_reduced[32] = {0}; + bn_write_be(&hash_bn, hash_reduced); + memzero(&hash_bn, sizeof(hash_bn)); + hmac_drbg_init(state, priv_key, 32, hash_reduced, 32); + memzero(hash_reduced, sizeof(hash_reduced)); + } else { + hmac_drbg_init(state, priv_key, 32, hash, 32); + } } // generate next number from deterministic random number generator diff --git a/trezor-crypto/crypto/sha2.c b/trezor-crypto/crypto/sha2.c index 0f14e970874..bea30dae7cf 100644 --- a/trezor-crypto/crypto/sha2.c +++ b/trezor-crypto/crypto/sha2.c @@ -643,7 +643,7 @@ void sha1_Final(SHA1_CTX* context, sha2_byte digest[]) { usedspace = 0; } -char *sha1_End(SHA1_CTX* context, char buffer[]) { +char *sha1_End(SHA1_CTX* context, char buffer[SHA1_DIGEST_STRING_LENGTH]) { sha2_byte digest[SHA1_DIGEST_LENGTH] = {0}, *d = digest; int i = 0; @@ -950,7 +950,7 @@ void sha256_Final(SHA256_CTX* context, sha2_byte digest[]) { usedspace = 0; } -char *sha256_End(SHA256_CTX* context, char buffer[]) { +char *sha256_End(SHA256_CTX* context, char buffer[SHA256_DIGEST_STRING_LENGTH]) { sha2_byte digest[SHA256_DIGEST_LENGTH] = {0}, *d = digest; int i = 0; @@ -1269,7 +1269,7 @@ void sha512_Final(SHA512_CTX* context, sha2_byte digest[]) { memzero(context, sizeof(SHA512_CTX)); } -char *sha512_End(SHA512_CTX* context, char buffer[]) { +char *sha512_End(SHA512_CTX* context, char buffer[SHA512_DIGEST_STRING_LENGTH]) { sha2_byte digest[SHA512_DIGEST_LENGTH] = {0}, *d = digest; int i = 0; diff --git a/trezor-crypto/crypto/sha3.c b/trezor-crypto/crypto/sha3.c index a2563450dcb..0033186fe59 100644 --- a/trezor-crypto/crypto/sha3.c +++ b/trezor-crypto/crypto/sha3.c @@ -26,8 +26,9 @@ #define I64(x) x##LL #define ROTL64(qword, n) ((qword) << (n) ^ ((qword) >> (64 - (n)))) #define le2me_64(x) (x) -#define IS_ALIGNED_64(p) (0 == (7 & ((const char*)(p) - (const char*)0))) -# define me64_to_le_str(to, from, length) memcpy((to), (from), (length)) +//#define IS_ALIGNED_64(p) (0 == (7 & ((long)(p)))) // [wallet-core] pointer/numerical type, for MacOS SDK 12.3 +#define IS_ALIGNED_64(p) (0 == (7 & ((const char*)(p) - (const char*)0))) //win +#define me64_to_le_str(to, from, length) memcpy((to), (from), (length)) /* constants */ #define NumberOfRounds 24 diff --git a/trezor-crypto/crypto/shamir.c b/trezor-crypto/crypto/shamir.c index 5e4ab24c205..5aea89bec6b 100644 --- a/trezor-crypto/crypto/shamir.c +++ b/trezor-crypto/crypto/shamir.c @@ -266,7 +266,7 @@ bool shamir_interpolate(uint8_t *result, uint8_t result_index, size_t len) { size_t i = 0, j = 0; uint32_t x[8] = {0}; -#ifdef _MSC_VER + #ifdef _MSC_VER uint32_t (*xs)[8] = _alloca(sizeof(uint32_t) * share_count * 8); memset(xs, 0, sizeof(uint32_t) * share_count * 8); uint32_t (*ys)[8] = _alloca(sizeof(uint32_t) * share_count * 8); @@ -276,7 +276,7 @@ bool shamir_interpolate(uint8_t *result, uint8_t result_index, memset(xs, 0, sizeof(xs)); uint32_t ys[share_count][8]; memset(ys, 0, sizeof(ys)); -#endif + #endif uint32_t num[8] = {~0}; /* num is the numerator (=1) */ uint32_t denom[8] = {0}; uint32_t tmp[8] = {0}; diff --git a/trezor-crypto/crypto/slip39.c b/trezor-crypto/crypto/slip39.c new file mode 100644 index 00000000000..ec1adf20169 --- /dev/null +++ b/trezor-crypto/crypto/slip39.c @@ -0,0 +1,151 @@ +/** + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include + +/** + * Returns word at position `index`. + */ +const char* get_word(uint16_t index) { + if (index >= WORDS_COUNT) { + return NULL; + } + + return slip39_wordlist[index]; +} + +/** + * Finds the index of a given word. + * Returns true on success and stores result in `index`. + */ +bool word_index(uint16_t* index, const char* word, uint8_t word_length) { + uint16_t lo = 0; + uint16_t hi = WORDS_COUNT; + uint16_t mid = 0; + + while ((hi - lo) > 1) { + mid = (hi + lo) / 2; + if (strncmp(slip39_wordlist[mid], word, word_length) > 0) { + hi = mid; + } else { + lo = mid; + } + } + if (strncmp(slip39_wordlist[lo], word, word_length) != 0) { + return false; + } + *index = lo; + return true; +} + +/** + * Returns the index of the first sequence in words_button_seq[] which is not + * less than the given sequence. Returns WORDS_COUNT if there is no such + * sequence. + */ +static uint16_t find_sequence(uint16_t sequence) { + if (sequence <= words_button_seq[0].sequence) { + return 0; + } + + uint16_t lo = 0; + uint16_t hi = WORDS_COUNT; + + while (hi - lo > 1) { + uint16_t mid = (hi + lo) / 2; + if (words_button_seq[mid].sequence >= sequence) { + hi = mid; + } else { + lo = mid; + } + } + + return hi; +} + +/** + * Returns a word matching the button sequence prefix or NULL if no match is + * found. + */ +const char* button_sequence_to_word(uint16_t sequence) { + if (sequence == 0) { + return slip39_wordlist[words_button_seq[0].index]; + } + + uint16_t multiplier = 1; + while (sequence < 1000) { + sequence *= 10; + multiplier *= 10; + } + + uint16_t i = find_sequence(sequence); + if (i >= WORDS_COUNT || + words_button_seq[i].sequence - sequence >= multiplier) { + return NULL; + } + + return slip39_wordlist[words_button_seq[i].index]; +} + +/** + * Calculates which buttons on the T9 keyboard can still be pressed after the + * prefix was entered. Returns a 9-bit bitmask, where each bit specifies which + * buttons can be pressed (there are still words in this combination). The least + * significant bit corresponds to the first button. + * + * Example: 110000110 - second, third, eighth and ninth button still can be + * pressed. + */ +uint16_t slip39_word_completion_mask(uint16_t prefix) { + if (prefix >= 1000) { + // Four char prefix -> the mask is zero. + return 0; + } + + // Determine the range of sequences [min, max), which have the given prefix. + uint16_t min = prefix; + uint16_t max = prefix + 1; + uint16_t divider = 1; + while (max <= 1000) { + min *= 10; + max *= 10; + divider *= 10; + } + divider /= 10; + + // Determine the range we will be searching in words_button_seq[]. + min = find_sequence(min); + max = find_sequence(max); + + uint16_t bitmap = 0; + for (uint16_t i = min; i < max; ++i) { + uint8_t digit = (words_button_seq[i].sequence / divider) % 10; + bitmap |= 1 << (digit - 1); + } + + return bitmap; +} diff --git a/trezor-crypto/crypto/tests/CMakeLists.txt b/trezor-crypto/crypto/tests/CMakeLists.txt index 39a32b5b96e..4960fbdd0ee 100644 --- a/trezor-crypto/crypto/tests/CMakeLists.txt +++ b/trezor-crypto/crypto/tests/CMakeLists.txt @@ -1,3 +1,9 @@ +# 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. + enable_testing() if(WIN32) @@ -12,6 +18,8 @@ endif() # Test executable add_executable(TrezorCryptoTests test_check.c) target_link_libraries(TrezorCryptoTests TrezorCrypto ${CHECK_LIBRARIES}) -target_include_directories(TrezorCryptoTests PRIVATE ${CMAKE_SOURCE_DIR}/src) + +target_link_directories(TrezorCryptoTests PRIVATE ${PREFIX}/lib) +target_include_directories(TrezorCryptoTests PRIVATE ${CMAKE_SOURCE_DIR}/src ${PREFIX}/include) add_test(NAME test_check COMMAND TrezorCryptoTests) diff --git a/trezor-crypto/crypto/tests/test_check.c b/trezor-crypto/crypto/tests/test_check.c index 80ac09461ad..097689f21ed 100644 --- a/trezor-crypto/crypto/tests/test_check.c +++ b/trezor-crypto/crypto/tests/test_check.c @@ -21,6 +21,7 @@ * OTHER DEALINGS IN THE SOFTWARE. */ +#include #include #include #include @@ -31,7 +32,7 @@ #include -#if VALGRIND +#ifdef VALGRIND #include #include #endif @@ -48,6 +49,7 @@ #include #include #include +#include #include #include #include @@ -70,11 +72,10 @@ #include #include #include -#include // [wallet-core] -//#include // [wallet-core] -//#include +#include +#include -#if VALGRIND +#ifdef VALGRIND /* * This is a clever trick to make Valgrind's Memcheck verify code * is constant-time with respect to secret data. @@ -139,7 +140,7 @@ START_TEST(test_bignum_read_be) { 0x14087f0a, 0x15498fe5, 0x10b161bb, 0xc55ece}}; for (int i = 0; i < 9; i++) { - ck_assert_int_eq(a.val[i], b.val[i]); + ck_assert_uint_eq(a.val[i], b.val[i]); } } END_TEST @@ -348,21 +349,21 @@ START_TEST(test_bignum_write_uint32) { fromhex( "000000000000000000000000000000000000000000000000000000001fffffff"), &a); - ck_assert_int_eq(bn_write_uint32(&a), 0x1fffffff); + ck_assert_uint_eq(bn_write_uint32(&a), 0x1fffffff); // lowest 30 bits set bn_read_be( fromhex( "000000000000000000000000000000000000000000000000000000003fffffff"), &a); - ck_assert_int_eq(bn_write_uint32(&a), 0x3fffffff); + ck_assert_uint_eq(bn_write_uint32(&a), 0x3fffffff); // bit 31 set bn_read_be( fromhex( "0000000000000000000000000000000000000000000000000000000040000000"), &a); - ck_assert_int_eq(bn_write_uint32(&a), 0x40000000); + ck_assert_uint_eq(bn_write_uint32(&a), 0x40000000); } END_TEST @@ -374,35 +375,35 @@ START_TEST(test_bignum_write_uint64) { fromhex( "000000000000000000000000000000000000000000000000000000003fffffff"), &a); - ck_assert_int_eq(bn_write_uint64(&a), 0x3fffffff); + ck_assert_uint_eq(bn_write_uint64(&a), 0x3fffffff); // bit 31 set bn_read_be( fromhex( "0000000000000000000000000000000000000000000000000000000040000000"), &a); - ck_assert_int_eq(bn_write_uint64(&a), 0x40000000); + ck_assert_uint_eq(bn_write_uint64(&a), 0x40000000); // bit 33 set bn_read_be( fromhex( "0000000000000000000000000000000000000000000000000000000100000000"), &a); - ck_assert_int_eq(bn_write_uint64(&a), 0x100000000LL); + ck_assert_uint_eq(bn_write_uint64(&a), 0x100000000LL); // bit 61 set bn_read_be( fromhex( "0000000000000000000000000000000000000000000000002000000000000000"), &a); - ck_assert_int_eq(bn_write_uint64(&a), 0x2000000000000000LL); + ck_assert_uint_eq(bn_write_uint64(&a), 0x2000000000000000LL); // all 64 bits set bn_read_be( fromhex( "000000000000000000000000000000000000000000000000ffffffffffffffff"), &a); - ck_assert_int_eq(bn_write_uint64(&a), 0xffffffffffffffffLL); + ck_assert_uint_eq(bn_write_uint64(&a), 0xffffffffffffffffLL); } END_TEST @@ -556,19 +557,19 @@ END_TEST START_TEST(test_bignum_format_uint64) { char buf[128], str[128]; - int r; + size_t r; // test for (10^i) and (10^i) - 1 uint64_t m = 1; for (int i = 0; i <= 19; i++, m *= 10) { sprintf(str, "%" PRIu64, m); r = bn_format_uint64(m, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, strlen(str)); + ck_assert_uint_eq(r, strlen(str)); ck_assert_str_eq(buf, str); uint64_t n = m - 1; sprintf(str, "%" PRIu64, n); r = bn_format_uint64(n, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, strlen(str)); + ck_assert_uint_eq(r, strlen(str)); ck_assert_str_eq(buf, str); } } @@ -577,14 +578,14 @@ END_TEST START_TEST(test_bignum_format) { bignum256 a; char buf[128]; - int r; + size_t r; bn_read_be( fromhex( "0000000000000000000000000000000000000000000000000000000000000000"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 1); + ck_assert_uint_eq(r, 1); ck_assert_str_eq(buf, "0"); bn_read_be( @@ -592,7 +593,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000000"), &a); r = bn_format(&a, NULL, NULL, 20, 0, true, buf, sizeof(buf)); - ck_assert_int_eq(r, 22); + ck_assert_uint_eq(r, 22); ck_assert_str_eq(buf, "0.00000000000000000000"); bn_read_be( @@ -600,7 +601,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000000"), &a); r = bn_format(&a, NULL, NULL, 0, 5, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 1); + ck_assert_uint_eq(r, 1); ck_assert_str_eq(buf, "0"); bn_read_be( @@ -608,7 +609,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000000"), &a); r = bn_format(&a, NULL, NULL, 0, -5, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 1); + ck_assert_uint_eq(r, 1); ck_assert_str_eq(buf, "0"); bn_read_be( @@ -616,7 +617,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000000"), &a); r = bn_format(&a, "", "", 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 1); + ck_assert_uint_eq(r, 1); ck_assert_str_eq(buf, "0"); bn_read_be( @@ -624,7 +625,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000000"), &a); r = bn_format(&a, NULL, "SFFX", 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 1 + 4); + ck_assert_uint_eq(r, 1 + 4); ck_assert_str_eq(buf, "0SFFX"); bn_read_be( @@ -632,7 +633,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000000"), &a); r = bn_format(&a, "PRFX", NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 4 + 1); + ck_assert_uint_eq(r, 4 + 1); ck_assert_str_eq(buf, "PRFX0"); bn_read_be( @@ -640,7 +641,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000000"), &a); r = bn_format(&a, "PRFX", "SFFX", 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 4 + 1 + 4); + ck_assert_uint_eq(r, 4 + 1 + 4); ck_assert_str_eq(buf, "PRFX0SFFX"); bn_read_be( @@ -648,7 +649,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000000"), &a); r = bn_format(&a, NULL, NULL, 18, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 1); + ck_assert_uint_eq(r, 1); ck_assert_str_eq(buf, "0"); bn_read_be( @@ -656,7 +657,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000001"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 1); + ck_assert_uint_eq(r, 1); ck_assert_str_eq(buf, "1"); bn_read_be( @@ -664,7 +665,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000001"), &a); r = bn_format(&a, NULL, NULL, 6, 6, true, buf, sizeof(buf)); - ck_assert_int_eq(r, 8); + ck_assert_uint_eq(r, 8); ck_assert_str_eq(buf, "1.000000"); bn_read_be( @@ -672,7 +673,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000002"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 1); + ck_assert_uint_eq(r, 1); ck_assert_str_eq(buf, "2"); bn_read_be( @@ -680,7 +681,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000005"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 1); + ck_assert_uint_eq(r, 1); ck_assert_str_eq(buf, "5"); bn_read_be( @@ -688,7 +689,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000009"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 1); + ck_assert_uint_eq(r, 1); ck_assert_str_eq(buf, "9"); bn_read_be( @@ -696,7 +697,7 @@ START_TEST(test_bignum_format) { "000000000000000000000000000000000000000000000000000000000000000a"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 2); + ck_assert_uint_eq(r, 2); ck_assert_str_eq(buf, "10"); bn_read_be( @@ -704,7 +705,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000014"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 2); + ck_assert_uint_eq(r, 2); ck_assert_str_eq(buf, "20"); bn_read_be( @@ -712,7 +713,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000032"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 2); + ck_assert_uint_eq(r, 2); ck_assert_str_eq(buf, "50"); bn_read_be( @@ -720,7 +721,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000063"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 2); + ck_assert_uint_eq(r, 2); ck_assert_str_eq(buf, "99"); bn_read_be( @@ -728,7 +729,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000064"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 3); + ck_assert_uint_eq(r, 3); ck_assert_str_eq(buf, "100"); bn_read_be( @@ -736,7 +737,7 @@ START_TEST(test_bignum_format) { "00000000000000000000000000000000000000000000000000000000000000c8"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 3); + ck_assert_uint_eq(r, 3); ck_assert_str_eq(buf, "200"); bn_read_be( @@ -744,7 +745,7 @@ START_TEST(test_bignum_format) { "00000000000000000000000000000000000000000000000000000000000001f4"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 3); + ck_assert_uint_eq(r, 3); ck_assert_str_eq(buf, "500"); bn_read_be( @@ -752,7 +753,7 @@ START_TEST(test_bignum_format) { "00000000000000000000000000000000000000000000000000000000000003e7"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 3); + ck_assert_uint_eq(r, 3); ck_assert_str_eq(buf, "999"); bn_read_be( @@ -760,7 +761,7 @@ START_TEST(test_bignum_format) { "00000000000000000000000000000000000000000000000000000000000003e8"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 4); + ck_assert_uint_eq(r, 4); ck_assert_str_eq(buf, "1000"); bn_read_be( @@ -768,7 +769,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000989680"), &a); r = bn_format(&a, NULL, NULL, 7, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 1); + ck_assert_uint_eq(r, 1); ck_assert_str_eq(buf, "1"); bn_read_be( @@ -776,7 +777,7 @@ START_TEST(test_bignum_format) { "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 78); + ck_assert_uint_eq(r, 78); ck_assert_str_eq(buf, "11579208923731619542357098500868790785326998466564056403945" "7584007913129639935"); @@ -786,7 +787,7 @@ START_TEST(test_bignum_format) { "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), &a); r = bn_format(&a, NULL, NULL, 1, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 79); + ck_assert_uint_eq(r, 79); ck_assert_str_eq(buf, "11579208923731619542357098500868790785326998466564056403945" "758400791312963993.5"); @@ -796,7 +797,7 @@ START_TEST(test_bignum_format) { "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), &a); r = bn_format(&a, NULL, NULL, 2, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 79); + ck_assert_uint_eq(r, 79); ck_assert_str_eq(buf, "11579208923731619542357098500868790785326998466564056403945" "75840079131296399.35"); @@ -806,7 +807,7 @@ START_TEST(test_bignum_format) { "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), &a); r = bn_format(&a, NULL, NULL, 8, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 79); + ck_assert_uint_eq(r, 79); ck_assert_str_eq(buf, "11579208923731619542357098500868790785326998466564056403945" "75840079131.29639935"); @@ -816,7 +817,7 @@ START_TEST(test_bignum_format) { "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffe3bbb00"), &a); r = bn_format(&a, NULL, NULL, 8, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 70); + ck_assert_uint_eq(r, 70); ck_assert_str_eq(buf, "11579208923731619542357098500868790785326998466564056403945" "75840079131"); @@ -826,7 +827,7 @@ START_TEST(test_bignum_format) { "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), &a); r = bn_format(&a, NULL, NULL, 18, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 79); + ck_assert_uint_eq(r, 79); ck_assert_str_eq(buf, "11579208923731619542357098500868790785326998466564056403945" "7.584007913129639935"); @@ -836,7 +837,7 @@ START_TEST(test_bignum_format) { "fffffffffffffffffffffffffffffffffffffffffffffffff7e52fe5afe40000"), &a); r = bn_format(&a, NULL, NULL, 18, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 60); + ck_assert_uint_eq(r, 60); ck_assert_str_eq( buf, "115792089237316195423570985008687907853269984665640564039457"); @@ -845,7 +846,7 @@ START_TEST(test_bignum_format) { "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), &a); r = bn_format(&a, NULL, NULL, 78, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 80); + ck_assert_uint_eq(r, 80); ck_assert_str_eq(buf, "0." "11579208923731619542357098500868790785326998466564056403945" @@ -856,7 +857,7 @@ START_TEST(test_bignum_format) { "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), &a); r = bn_format(&a, NULL, NULL, 0, 10, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 88); + ck_assert_uint_eq(r, 88); ck_assert_str_eq(buf, "11579208923731619542357098500868790785326998466564056403945" "75840079131296399350000000000"); @@ -867,7 +868,7 @@ START_TEST(test_bignum_format) { &a); r = bn_format(&a, "quite a long prefix", "even longer suffix", 60, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 116); + ck_assert_uint_eq(r, 116); ck_assert_str_eq(buf, "quite a long " "prefix115792089237316195." @@ -881,11 +882,11 @@ START_TEST(test_bignum_format) { memset(buf, 'a', sizeof(buf)); r = bn_format(&a, "prefix", "suffix", 10, 0, false, buf, 31); ck_assert_str_eq(buf, "prefix8198552.9216486895suffix"); - ck_assert_int_eq(r, 30); + ck_assert_uint_eq(r, 30); memset(buf, 'a', sizeof(buf)); r = bn_format(&a, "prefix", "suffix", 10, 0, false, buf, 30); - ck_assert_int_eq(r, 0); + ck_assert_uint_eq(r, 0); ck_assert_str_eq(buf, ""); } END_TEST @@ -954,7 +955,7 @@ END_TEST // https://tools.ietf.org/html/rfc4648#section-10 START_TEST(test_base32_rfc4648) { - const struct { + static const struct { const char *decoded; const char *encoded; const char *encoded_lowercase; @@ -978,8 +979,8 @@ START_TEST(test_base32_rfc4648) { size_t inlen = strlen(in); size_t outlen = strlen(out); - ck_assert_int_eq(outlen, base32_encoded_length(inlen)); - ck_assert_int_eq(inlen, base32_decoded_length(outlen)); + ck_assert_uint_eq(outlen, base32_encoded_length(inlen)); + ck_assert_uint_eq(inlen, base32_decoded_length(outlen)); ck_assert(base32_encode((uint8_t *)in, inlen, buffer, sizeof(buffer), BASE32_ALPHABET_RFC4648) != NULL); @@ -1003,7 +1004,7 @@ END_TEST // from // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/base58_keys_valid.json START_TEST(test_base58) { - const char *base58_vector[] = { + static const char *base58_vector[] = { "0065a16059864a2fdbc7c99a4723a8395bc6f188eb", "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i", "0574f209f6ea907e2ea48f74fae05782ae8a665257", @@ -1134,7 +1135,7 @@ END_TEST // Graphene Base85CheckEncoding START_TEST(test_base58gph) { - const char *base58_vector[] = { + static const char *base58_vector[] = { "02e649f63f8e8121345fd7f47d0d185a3ccaa843115cd2e9392dcd9b82263bc680", "6dumtt9swxCqwdPZBGXh9YmHoEjFFnNfwHaTqRbQTghGAY2gRz", "021c7359cd885c0e319924d97e3980206ad64387aff54908241125b3a88b55ca16", @@ -1188,7 +1189,7 @@ START_TEST(test_bignum_divmod) { i = 0; while (!bn_is_zero(&a) && i < 44) { bn_divmod58(&a, &r); - ck_assert_int_eq(r, ar[i]); + ck_assert_uint_eq(r, ar[i]); i++; } ck_assert_int_eq(i, 44); @@ -1205,7 +1206,7 @@ START_TEST(test_bignum_divmod) { i = 0; while (!bn_is_zero(&b) && i < 26) { bn_divmod1000(&b, &r); - ck_assert_int_eq(r, br[i]); + ck_assert_uint_eq(r, br[i]); i++; } ck_assert_int_eq(i, 26); @@ -1226,7 +1227,7 @@ START_TEST(test_bip32_vector_1) { // [Chain m] fingerprint = 0; - ck_assert_int_eq(fingerprint, 0x00000000); + ck_assert_uint_eq(fingerprint, 0x00000000); ck_assert_mem_eq( node.chain_code, fromhex( @@ -1237,7 +1238,7 @@ START_TEST(test_bip32_vector_1) { fromhex( "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -1251,8 +1252,7 @@ START_TEST(test_bip32_vector_1) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1268,7 +1268,7 @@ START_TEST(test_bip32_vector_1) { // [Chain m/0'] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd_prime(&node, 0); - ck_assert_int_eq(fingerprint, 0x3442193e); + ck_assert_uint_eq(fingerprint, 0x3442193e); ck_assert_mem_eq( node.chain_code, fromhex( @@ -1279,7 +1279,7 @@ START_TEST(test_bip32_vector_1) { fromhex( "edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -1293,7 +1293,7 @@ START_TEST(test_bip32_vector_1) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1309,7 +1309,7 @@ START_TEST(test_bip32_vector_1) { // [Chain m/0'/1] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd(&node, 1); - ck_assert_int_eq(fingerprint, 0x5c1bd648); + ck_assert_uint_eq(fingerprint, 0x5c1bd648); ck_assert_mem_eq( node.chain_code, fromhex( @@ -1320,7 +1320,7 @@ START_TEST(test_bip32_vector_1) { fromhex( "3c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -1334,7 +1334,7 @@ START_TEST(test_bip32_vector_1) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1350,7 +1350,7 @@ START_TEST(test_bip32_vector_1) { // [Chain m/0'/1/2'] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd_prime(&node, 2); - ck_assert_int_eq(fingerprint, 0xbef5a2f9); + ck_assert_uint_eq(fingerprint, 0xbef5a2f9); ck_assert_mem_eq( node.chain_code, fromhex( @@ -1361,7 +1361,7 @@ START_TEST(test_bip32_vector_1) { fromhex( "cbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -1375,7 +1375,7 @@ START_TEST(test_bip32_vector_1) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1391,7 +1391,7 @@ START_TEST(test_bip32_vector_1) { // [Chain m/0'/1/2'/2] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd(&node, 2); - ck_assert_int_eq(fingerprint, 0xee7ab90c); + ck_assert_uint_eq(fingerprint, 0xee7ab90c); ck_assert_mem_eq( node.chain_code, fromhex( @@ -1402,7 +1402,7 @@ START_TEST(test_bip32_vector_1) { fromhex( "0f479245fb19a38a1954c5c7c0ebab2f9bdfd96a17563ef28a6a4b1a2a764ef4"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -1416,7 +1416,7 @@ START_TEST(test_bip32_vector_1) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1432,7 +1432,7 @@ START_TEST(test_bip32_vector_1) { // [Chain m/0'/1/2'/2/1000000000] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd(&node, 1000000000); - ck_assert_int_eq(fingerprint, 0xd880d7d8); + ck_assert_uint_eq(fingerprint, 0xd880d7d8); ck_assert_mem_eq( node.chain_code, fromhex( @@ -1443,7 +1443,7 @@ START_TEST(test_bip32_vector_1) { fromhex( "471b76e389e528d6de6d816857e012c5455051cad6660850e58372a6c3e6e7c8"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -1457,7 +1457,7 @@ START_TEST(test_bip32_vector_1) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1489,7 +1489,7 @@ START_TEST(test_bip32_vector_2) { // [Chain m] fingerprint = 0; - ck_assert_int_eq(fingerprint, 0x00000000); + ck_assert_uint_eq(fingerprint, 0x00000000); ck_assert_mem_eq( node.chain_code, fromhex( @@ -1500,7 +1500,7 @@ START_TEST(test_bip32_vector_2) { fromhex( "4b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -1514,7 +1514,7 @@ START_TEST(test_bip32_vector_2) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1531,7 +1531,7 @@ START_TEST(test_bip32_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd(&node, 0); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0xbd16bee5); + ck_assert_uint_eq(fingerprint, 0xbd16bee5); ck_assert_mem_eq( node.chain_code, fromhex( @@ -1542,7 +1542,7 @@ START_TEST(test_bip32_vector_2) { fromhex( "abe74a98f6c7eabee0428f53798f0ab8aa1bd37873999041703c742f15ac7e1e"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -1556,7 +1556,7 @@ START_TEST(test_bip32_vector_2) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1573,7 +1573,7 @@ START_TEST(test_bip32_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd_prime(&node, 2147483647); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x5a61ff8e); + ck_assert_uint_eq(fingerprint, 0x5a61ff8e); ck_assert_mem_eq( node.chain_code, fromhex( @@ -1584,7 +1584,7 @@ START_TEST(test_bip32_vector_2) { fromhex( "877c779ad9687164e9c2f4f0f4ff0340814392330693ce95a58fe18fd52e6e93"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -1598,7 +1598,7 @@ START_TEST(test_bip32_vector_2) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1615,7 +1615,7 @@ START_TEST(test_bip32_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd(&node, 1); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0xd8ab4937); + ck_assert_uint_eq(fingerprint, 0xd8ab4937); ck_assert_mem_eq( node.chain_code, fromhex( @@ -1626,7 +1626,7 @@ START_TEST(test_bip32_vector_2) { fromhex( "704addf544a06e5ee4bea37098463c23613da32020d604506da8c0518e1da4b7"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -1640,7 +1640,7 @@ START_TEST(test_bip32_vector_2) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1657,7 +1657,7 @@ START_TEST(test_bip32_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd_prime(&node, 2147483646); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x78412e3a); + ck_assert_uint_eq(fingerprint, 0x78412e3a); ck_assert_mem_eq( node.chain_code, fromhex( @@ -1668,7 +1668,7 @@ START_TEST(test_bip32_vector_2) { fromhex( "f1c7c871a54a804afe328b4c83a1c33b8e5ff48f5087273f04efa83b247d6a2d"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -1682,7 +1682,7 @@ START_TEST(test_bip32_vector_2) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1699,7 +1699,7 @@ START_TEST(test_bip32_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd(&node, 2); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x31a507b8); + ck_assert_uint_eq(fingerprint, 0x31a507b8); ck_assert_mem_eq( node.chain_code, fromhex( @@ -1710,7 +1710,7 @@ START_TEST(test_bip32_vector_2) { fromhex( "bb7d39bdb83ecf58f2fd82b6d918341cbef428661ef01ab97c28a4842125ac23"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -1724,7 +1724,7 @@ START_TEST(test_bip32_vector_2) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1749,7 +1749,7 @@ START_TEST(test_bip32_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_public_ckd(&node, 0); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0xbd16bee5); + ck_assert_uint_eq(fingerprint, 0xbd16bee5); ck_assert_mem_eq( node.chain_code, fromhex( @@ -1760,7 +1760,7 @@ START_TEST(test_bip32_vector_2) { fromhex( "0000000000000000000000000000000000000000000000000000000000000000"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -1786,8 +1786,8 @@ START_TEST(test_bip32_vector_3) { // [Chain m] fingerprint = 0; - ck_assert_int_eq(fingerprint, 0x00000000); - hdnode_fill_public_key(&node); + ck_assert_uint_eq(fingerprint, 0x00000000); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); hdnode_serialize_private(&node, fingerprint, VERSION_PRIVATE, str, sizeof(str)); ck_assert_str_eq(str, @@ -1796,7 +1796,7 @@ START_TEST(test_bip32_vector_3) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1813,7 +1813,7 @@ START_TEST(test_bip32_vector_3) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd_prime(&node, 0); ck_assert_int_eq(r, 1); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); hdnode_serialize_private(&node, fingerprint, VERSION_PRIVATE, str, sizeof(str)); ck_assert_str_eq(str, @@ -1822,7 +1822,7 @@ START_TEST(test_bip32_vector_3) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1854,7 +1854,7 @@ START_TEST(test_bip32_vector_4) { // [Chain m] fingerprint = 0; ck_assert_int_eq(fingerprint, 0x00000000); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); hdnode_serialize_private(&node, fingerprint, VERSION_PRIVATE, str, sizeof(str)); ck_assert_str_eq(str, @@ -1863,7 +1863,7 @@ START_TEST(test_bip32_vector_4) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1880,7 +1880,7 @@ START_TEST(test_bip32_vector_4) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd_prime(&node, 0); ck_assert_int_eq(r, 1); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); hdnode_serialize_private(&node, fingerprint, VERSION_PRIVATE, str, sizeof(str)); ck_assert_str_eq(str, @@ -1889,7 +1889,7 @@ START_TEST(test_bip32_vector_4) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1901,6 +1901,32 @@ START_TEST(test_bip32_vector_4) { memcpy(&node3, &node, sizeof(HDNode)); memzero(&node3.private_key, 32); ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); + + // [Chain m/0'/1'] + fingerprint = hdnode_fingerprint(&node); + r = hdnode_private_ckd_prime(&node, 1); + ck_assert_int_eq(r, 1); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + hdnode_serialize_private(&node, fingerprint, VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "xprv9xJocDuwtYCMNAo3Zw76WENQeAS6WGXQ55RCy7tDJ8oALr4FWkuVoHJ" + "eHVAcAqiZLE7Je3vZJHxspZdFHfnBEjHqU5hG1Jaj32dVoS6XLT1"); + r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); + ck_assert_str_eq(str, + "xpub6BJA1jSqiukeaesWfxe6sNK9CCGaujFFSJLomWHprUL9DePQ4JDkM5d" + "88n49sMGJxrhpjazuXYWdMf17C9T5XnxkopaeS7jGk1GyyVziaMt"); + r = hdnode_deserialize_public(str, VERSION_PUBLIC, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); } END_TEST @@ -1917,20 +1943,20 @@ START_TEST(test_bip32_compare) { "301133282ad079cbeb59bc446ad39d333928f74c46997d3609cd3e2801ca69d62788" "f9f174429946ff4e9be89f67c22fae28cb296a9b37734f75e73d1477af19"), 64, SECP256K1_NAME, &node2); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); for (i = 0; i < 100; i++) { memcpy(&node3, &node1, sizeof(HDNode)); - hdnode_fill_public_key(&node3); + ck_assert_int_eq(hdnode_fill_public_key(&node3), 0); r = hdnode_private_ckd(&node1, i); ck_assert_int_eq(r, 1); r = hdnode_public_ckd(&node2, i); ck_assert_int_eq(r, 1); r = hdnode_public_ckd(&node3, i); ck_assert_int_eq(r, 1); - ck_assert_int_eq(node1.depth, node2.depth); - ck_assert_int_eq(node1.depth, node3.depth); - ck_assert_int_eq(node1.child_num, node2.child_num); - ck_assert_int_eq(node1.child_num, node3.child_num); + ck_assert_uint_eq(node1.depth, node2.depth); + ck_assert_uint_eq(node1.depth, node3.depth); + ck_assert_uint_eq(node1.child_num, node2.child_num); + ck_assert_uint_eq(node1.child_num, node3.child_num); ck_assert_mem_eq(node1.chain_code, node2.chain_code, 32); ck_assert_mem_eq(node1.chain_code, node3.chain_code, 32); ck_assert_mem_eq( @@ -1943,7 +1969,7 @@ START_TEST(test_bip32_compare) { fromhex( "0000000000000000000000000000000000000000000000000000000000000000"), 32); - hdnode_fill_public_key(&node1); + ck_assert_int_eq(hdnode_fill_public_key(&node1), 0); ck_assert_mem_eq(node1.public_key, node2.public_key, 33); ck_assert_mem_eq(node1.public_key, node3.public_key, 33); } @@ -1953,7 +1979,7 @@ END_TEST START_TEST(test_bip32_optimized) { HDNode root; hdnode_from_seed((uint8_t *)"NothingToSeeHere", 16, SECP256K1_NAME, &root); - hdnode_fill_public_key(&root); + ck_assert_int_eq(hdnode_fill_public_key(&root), 0); curve_point pub; ecdsa_read_pubkey(&secp256k1, root.public_key, &pub); @@ -1965,7 +1991,7 @@ START_TEST(test_bip32_optimized) { // unoptimized memcpy(&node, &root, sizeof(HDNode)); hdnode_public_ckd(&node, i); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ecdsa_get_address(node.public_key, 0, HASHER_SHA2_RIPEMD, HASHER_SHA2D, addr1, sizeof(addr1)); // optimized @@ -1978,7 +2004,8 @@ START_TEST(test_bip32_optimized) { } END_TEST -#if USE_BIP32_CACHE // [wallet-core] +#if USE_BIP32_CACHE + START_TEST(test_bip32_cache_1) { HDNode node1, node2; int i, r; @@ -2112,7 +2139,7 @@ START_TEST(test_bip32_nist_seed) { fromhex( "7762f9729fed06121fd13f326884c82f59aa95c57ac492ce8c9654e60efd130c"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2136,7 +2163,7 @@ START_TEST(test_bip32_nist_seed) { fromhex( "0e49dc46ce1d8c29d9b80a05e40f5d0cd68cbf02ae98572186f5343be18084bf"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2155,7 +2182,7 @@ START_TEST(test_bip32_nist_vector_1) { // [Chain m] fingerprint = 0; - ck_assert_int_eq(fingerprint, 0x00000000); + ck_assert_uint_eq(fingerprint, 0x00000000); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2166,7 +2193,7 @@ START_TEST(test_bip32_nist_vector_1) { fromhex( "612091aaa12e22dd2abef664f8a01a82cae99ad7441b7ef8110424915c268bc2"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2176,7 +2203,7 @@ START_TEST(test_bip32_nist_vector_1) { // [Chain m/0'] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd_prime(&node, 0); - ck_assert_int_eq(fingerprint, 0xbe6105b5); + ck_assert_uint_eq(fingerprint, 0xbe6105b5); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2187,7 +2214,7 @@ START_TEST(test_bip32_nist_vector_1) { fromhex( "6939694369114c67917a182c59ddb8cafc3004e63ca5d3b84403ba8613debc0c"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2197,7 +2224,7 @@ START_TEST(test_bip32_nist_vector_1) { // [Chain m/0'/1] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd(&node, 1); - ck_assert_int_eq(fingerprint, 0x9b02312f); + ck_assert_uint_eq(fingerprint, 0x9b02312f); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2208,7 +2235,7 @@ START_TEST(test_bip32_nist_vector_1) { fromhex( "284e9d38d07d21e4e281b645089a94f4cf5a5a81369acf151a1c3a57f18b2129"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2218,7 +2245,7 @@ START_TEST(test_bip32_nist_vector_1) { // [Chain m/0'/1/2'] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd_prime(&node, 2); - ck_assert_int_eq(fingerprint, 0xb98005c1); + ck_assert_uint_eq(fingerprint, 0xb98005c1); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2229,7 +2256,7 @@ START_TEST(test_bip32_nist_vector_1) { fromhex( "694596e8a54f252c960eb771a3c41e7e32496d03b954aeb90f61635b8e092aa7"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2239,7 +2266,7 @@ START_TEST(test_bip32_nist_vector_1) { // [Chain m/0'/1/2'/2] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd(&node, 2); - ck_assert_int_eq(fingerprint, 0x0e9f3274); + ck_assert_uint_eq(fingerprint, 0x0e9f3274); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2250,7 +2277,7 @@ START_TEST(test_bip32_nist_vector_1) { fromhex( "5996c37fd3dd2679039b23ed6f70b506c6b56b3cb5e424681fb0fa64caf82aaa"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2260,7 +2287,7 @@ START_TEST(test_bip32_nist_vector_1) { // [Chain m/0'/1/2'/2/1000000000] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd(&node, 1000000000); - ck_assert_int_eq(fingerprint, 0x8b2b5c4b); + ck_assert_uint_eq(fingerprint, 0x8b2b5c4b); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2271,7 +2298,7 @@ START_TEST(test_bip32_nist_vector_1) { fromhex( "21c4f269ef0a5fd1badf47eeacebeeaa3de22eb8e5b0adcd0f27dd99d34d0119"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2294,7 +2321,7 @@ START_TEST(test_bip32_nist_vector_2) { // [Chain m] fingerprint = 0; - ck_assert_int_eq(fingerprint, 0x00000000); + ck_assert_uint_eq(fingerprint, 0x00000000); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2305,7 +2332,7 @@ START_TEST(test_bip32_nist_vector_2) { fromhex( "eaa31c2e46ca2962227cf21d73a7ef0ce8b31c756897521eb6c7b39796633357"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2316,7 +2343,7 @@ START_TEST(test_bip32_nist_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd(&node, 0); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x607f628f); + ck_assert_uint_eq(fingerprint, 0x607f628f); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2327,7 +2354,7 @@ START_TEST(test_bip32_nist_vector_2) { fromhex( "d7d065f63a62624888500cdb4f88b6d59c2927fee9e6d0cdff9cad555884df6e"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2338,7 +2365,7 @@ START_TEST(test_bip32_nist_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd_prime(&node, 2147483647); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x946d2a54); + ck_assert_uint_eq(fingerprint, 0x946d2a54); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2349,7 +2376,7 @@ START_TEST(test_bip32_nist_vector_2) { fromhex( "96d2ec9316746a75e7793684ed01e3d51194d81a42a3276858a5b7376d4b94b9"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2360,7 +2387,7 @@ START_TEST(test_bip32_nist_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd(&node, 1); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x218182d8); + ck_assert_uint_eq(fingerprint, 0x218182d8); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2371,7 +2398,7 @@ START_TEST(test_bip32_nist_vector_2) { fromhex( "974f9096ea6873a915910e82b29d7c338542ccde39d2064d1cc228f371542bbc"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2382,7 +2409,7 @@ START_TEST(test_bip32_nist_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd_prime(&node, 2147483646); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x931223e4); + ck_assert_uint_eq(fingerprint, 0x931223e4); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2393,7 +2420,7 @@ START_TEST(test_bip32_nist_vector_2) { fromhex( "da29649bbfaff095cd43819eda9a7be74236539a29094cd8336b07ed8d4eff63"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2404,7 +2431,7 @@ START_TEST(test_bip32_nist_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd(&node, 2); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x956c4629); + ck_assert_uint_eq(fingerprint, 0x956c4629); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2415,7 +2442,7 @@ START_TEST(test_bip32_nist_vector_2) { fromhex( "bb0a77ba01cc31d77205d51d08bd313b979a71ef4de9b062f8958297e746bd67"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2434,7 +2461,7 @@ START_TEST(test_bip32_nist_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_public_ckd(&node, 0); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x607f628f); + ck_assert_uint_eq(fingerprint, 0x607f628f); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2466,20 +2493,20 @@ START_TEST(test_bip32_nist_compare) { "301133282ad079cbeb59bc446ad39d333928f74c46997d3609cd3e2801ca69d62788" "f9f174429946ff4e9be89f67c22fae28cb296a9b37734f75e73d1477af19"), 64, NIST256P1_NAME, &node2); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); for (i = 0; i < 100; i++) { memcpy(&node3, &node1, sizeof(HDNode)); - hdnode_fill_public_key(&node3); + ck_assert_int_eq(hdnode_fill_public_key(&node3), 0); r = hdnode_private_ckd(&node1, i); ck_assert_int_eq(r, 1); r = hdnode_public_ckd(&node2, i); ck_assert_int_eq(r, 1); r = hdnode_public_ckd(&node3, i); ck_assert_int_eq(r, 1); - ck_assert_int_eq(node1.depth, node2.depth); - ck_assert_int_eq(node1.depth, node3.depth); - ck_assert_int_eq(node1.child_num, node2.child_num); - ck_assert_int_eq(node1.child_num, node3.child_num); + ck_assert_uint_eq(node1.depth, node2.depth); + ck_assert_uint_eq(node1.depth, node3.depth); + ck_assert_uint_eq(node1.child_num, node2.child_num); + ck_assert_uint_eq(node1.child_num, node3.child_num); ck_assert_mem_eq(node1.chain_code, node2.chain_code, 32); ck_assert_mem_eq(node1.chain_code, node3.chain_code, 32); ck_assert_mem_eq( @@ -2492,7 +2519,7 @@ START_TEST(test_bip32_nist_compare) { fromhex( "0000000000000000000000000000000000000000000000000000000000000000"), 32); - hdnode_fill_public_key(&node1); + ck_assert_int_eq(hdnode_fill_public_key(&node1), 0); ck_assert_mem_eq(node1.public_key, node2.public_key, 33); ck_assert_mem_eq(node1.public_key, node3.public_key, 33); } @@ -2512,7 +2539,7 @@ START_TEST(test_bip32_nist_repeat) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd_prime(&node, 28578); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0xbe6105b5); + ck_assert_uint_eq(fingerprint, 0xbe6105b5); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2523,7 +2550,7 @@ START_TEST(test_bip32_nist_repeat) { fromhex( "06f0db126f023755d0b8d86d4591718a5210dd8d024e3e14b6159d63f53aa669"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2534,7 +2561,7 @@ START_TEST(test_bip32_nist_repeat) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd(&node2, 33941); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x3e2b7bc6); + ck_assert_uint_eq(fingerprint, 0x3e2b7bc6); ck_assert_mem_eq( node2.chain_code, fromhex( @@ -2545,7 +2572,7 @@ START_TEST(test_bip32_nist_repeat) { fromhex( "092154eed4af83e078ff9b84322015aefe5769e31270f62c3f66c33888335f3a"), 32); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq( node2.public_key, fromhex( @@ -2556,13 +2583,13 @@ START_TEST(test_bip32_nist_repeat) { memzero(&node2.private_key, 32); r = hdnode_public_ckd(&node2, 33941); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x3e2b7bc6); + ck_assert_uint_eq(fingerprint, 0x3e2b7bc6); ck_assert_mem_eq( node2.chain_code, fromhex( "9e87fe95031f14736774cd82f25fd885065cb7c358c1edf813c72af535e83071"), 32); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq( node2.public_key, fromhex( @@ -2590,7 +2617,7 @@ START_TEST(test_bip32_ed25519_vector_1) { fromhex( "2b4be7f19ee27bbf30c667b642d5f4aa69fd169872f8fc3059c08ebae2eb19e7"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2609,7 +2636,7 @@ START_TEST(test_bip32_ed25519_vector_1) { fromhex( "68e0fe46dfb67e368c75379acec591dad19df3cde26e63b93a8e704f1dade7a3"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2628,7 +2655,7 @@ START_TEST(test_bip32_ed25519_vector_1) { fromhex( "b1d0bad404bf35da785a64ca1ac54b2617211d2777696fbffaf208f746ae84f2"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2647,7 +2674,7 @@ START_TEST(test_bip32_ed25519_vector_1) { fromhex( "92a5b23c0b8a99e37d07df3fb9966917f5d06e02ddbd909c7e184371463e9fc9"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2666,7 +2693,7 @@ START_TEST(test_bip32_ed25519_vector_1) { fromhex( "30d1dc7e5fc04c31219ab25a27ae00b50f6fd66622f6e9c913253d6511d1e662"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2685,7 +2712,7 @@ START_TEST(test_bip32_ed25519_vector_1) { fromhex( "8f94d394a8e8fd6b1bc2f3f49f5c47e385281d5c17e65324b0f62483e37e8793"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2717,7 +2744,7 @@ START_TEST(test_bip32_ed25519_vector_2) { fromhex( "171cb88b1b3c1db25add599712e36245d75bc65a1a5c9e18d76f9f2b1eab4012"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2737,7 +2764,7 @@ START_TEST(test_bip32_ed25519_vector_2) { fromhex( "1559eb2bbec5790b0c65d8693e4d0875b1747f4970ae8b650486ed7470845635"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2757,7 +2784,7 @@ START_TEST(test_bip32_ed25519_vector_2) { fromhex( "ea4f5bfe8694d8bb74b7b59404632fd5968b774ed545e810de9c32a4fb4192f4"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2777,7 +2804,7 @@ START_TEST(test_bip32_ed25519_vector_2) { fromhex( "3757c7577170179c7868353ada796c839135b3d30554bbb74a4b1e4a5a58505c"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2797,7 +2824,7 @@ START_TEST(test_bip32_ed25519_vector_2) { fromhex( "5837736c89570de861ebc173b1086da4f505d4adb387c6a1b1342d5e4ac9ec72"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2817,7 +2844,7 @@ START_TEST(test_bip32_ed25519_vector_2) { fromhex( "551d333177df541ad876a60ea71f00447931c0a9da16f227c11ea080d7391b8d"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2844,7 +2871,7 @@ START_TEST(test_bip32_decred_vector_1) { // [Chain m] fingerprint = 0; - ck_assert_int_eq(fingerprint, 0x00000000); + ck_assert_uint_eq(fingerprint, 0x00000000); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2855,7 +2882,7 @@ START_TEST(test_bip32_decred_vector_1) { fromhex( "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2870,7 +2897,7 @@ START_TEST(test_bip32_decred_vector_1) { SECP256K1_DECRED_NAME, &node2, NULL); ck_assert_int_eq(r, 0); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, sizeof(str)); @@ -2887,7 +2914,7 @@ START_TEST(test_bip32_decred_vector_1) { // [Chain m/0'] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd_prime(&node, 0); - ck_assert_int_eq(fingerprint, 0xbc495588); + ck_assert_uint_eq(fingerprint, 0xbc495588); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2898,7 +2925,7 @@ START_TEST(test_bip32_decred_vector_1) { fromhex( "edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2912,7 +2939,7 @@ START_TEST(test_bip32_decred_vector_1) { r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, SECP256K1_DECRED_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, sizeof(str)); @@ -2929,7 +2956,7 @@ START_TEST(test_bip32_decred_vector_1) { // [Chain m/0'/1] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd(&node, 1); - ck_assert_int_eq(fingerprint, 0xc67bc2ef); + ck_assert_uint_eq(fingerprint, 0xc67bc2ef); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2940,7 +2967,7 @@ START_TEST(test_bip32_decred_vector_1) { fromhex( "3c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2954,7 +2981,7 @@ START_TEST(test_bip32_decred_vector_1) { r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, SECP256K1_DECRED_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, sizeof(str)); @@ -2971,7 +2998,7 @@ START_TEST(test_bip32_decred_vector_1) { // [Chain m/0'/1/2'] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd_prime(&node, 2); - ck_assert_int_eq(fingerprint, 0xe7072187); + ck_assert_uint_eq(fingerprint, 0xe7072187); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2982,7 +3009,7 @@ START_TEST(test_bip32_decred_vector_1) { fromhex( "cbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2996,7 +3023,7 @@ START_TEST(test_bip32_decred_vector_1) { r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, SECP256K1_DECRED_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, sizeof(str)); @@ -3013,7 +3040,7 @@ START_TEST(test_bip32_decred_vector_1) { // [Chain m/0'/1/2'/2] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd(&node, 2); - ck_assert_int_eq(fingerprint, 0xbcbbc1c4); + ck_assert_uint_eq(fingerprint, 0xbcbbc1c4); ck_assert_mem_eq( node.chain_code, fromhex( @@ -3024,7 +3051,7 @@ START_TEST(test_bip32_decred_vector_1) { fromhex( "0f479245fb19a38a1954c5c7c0ebab2f9bdfd96a17563ef28a6a4b1a2a764ef4"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -3038,7 +3065,7 @@ START_TEST(test_bip32_decred_vector_1) { r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, SECP256K1_DECRED_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, sizeof(str)); @@ -3055,7 +3082,7 @@ START_TEST(test_bip32_decred_vector_1) { // [Chain m/0'/1/2'/2/1000000000] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd(&node, 1000000000); - ck_assert_int_eq(fingerprint, 0xe58b52e4); + ck_assert_uint_eq(fingerprint, 0xe58b52e4); ck_assert_mem_eq( node.chain_code, fromhex( @@ -3066,7 +3093,7 @@ START_TEST(test_bip32_decred_vector_1) { fromhex( "471b76e389e528d6de6d816857e012c5455051cad6660850e58372a6c3e6e7c8"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -3080,7 +3107,7 @@ START_TEST(test_bip32_decred_vector_1) { r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, SECP256K1_DECRED_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, sizeof(str)); @@ -3117,7 +3144,7 @@ START_TEST(test_bip32_decred_vector_2) { // [Chain m] fingerprint = 0; - ck_assert_int_eq(fingerprint, 0x00000000); + ck_assert_uint_eq(fingerprint, 0x00000000); ck_assert_mem_eq( node.chain_code, fromhex( @@ -3128,7 +3155,7 @@ START_TEST(test_bip32_decred_vector_2) { fromhex( "4b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -3142,7 +3169,7 @@ START_TEST(test_bip32_decred_vector_2) { r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, SECP256K1_DECRED_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, sizeof(str)); @@ -3160,7 +3187,7 @@ START_TEST(test_bip32_decred_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd(&node, 0); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x2524c9d3); + ck_assert_uint_eq(fingerprint, 0x2524c9d3); ck_assert_mem_eq( node.chain_code, fromhex( @@ -3171,7 +3198,7 @@ START_TEST(test_bip32_decred_vector_2) { fromhex( "abe74a98f6c7eabee0428f53798f0ab8aa1bd37873999041703c742f15ac7e1e"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -3185,7 +3212,7 @@ START_TEST(test_bip32_decred_vector_2) { r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, SECP256K1_DECRED_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, sizeof(str)); @@ -3203,7 +3230,7 @@ START_TEST(test_bip32_decred_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd_prime(&node, 2147483647); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x6035c6ad); + ck_assert_uint_eq(fingerprint, 0x6035c6ad); ck_assert_mem_eq( node.chain_code, fromhex( @@ -3214,7 +3241,7 @@ START_TEST(test_bip32_decred_vector_2) { fromhex( "877c779ad9687164e9c2f4f0f4ff0340814392330693ce95a58fe18fd52e6e93"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -3228,7 +3255,7 @@ START_TEST(test_bip32_decred_vector_2) { r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, SECP256K1_DECRED_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, sizeof(str)); @@ -3246,7 +3273,7 @@ START_TEST(test_bip32_decred_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd(&node, 1); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x36fc7080); + ck_assert_uint_eq(fingerprint, 0x36fc7080); ck_assert_mem_eq( node.chain_code, fromhex( @@ -3257,7 +3284,7 @@ START_TEST(test_bip32_decred_vector_2) { fromhex( "704addf544a06e5ee4bea37098463c23613da32020d604506da8c0518e1da4b7"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -3271,7 +3298,7 @@ START_TEST(test_bip32_decred_vector_2) { r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, SECP256K1_DECRED_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, sizeof(str)); @@ -3289,7 +3316,7 @@ START_TEST(test_bip32_decred_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd_prime(&node, 2147483646); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x45309b4c); + ck_assert_uint_eq(fingerprint, 0x45309b4c); ck_assert_mem_eq( node.chain_code, fromhex( @@ -3300,7 +3327,7 @@ START_TEST(test_bip32_decred_vector_2) { fromhex( "f1c7c871a54a804afe328b4c83a1c33b8e5ff48f5087273f04efa83b247d6a2d"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -3314,7 +3341,7 @@ START_TEST(test_bip32_decred_vector_2) { r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, SECP256K1_DECRED_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, sizeof(str)); @@ -3332,7 +3359,7 @@ START_TEST(test_bip32_decred_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd(&node, 2); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x3491a5e6); + ck_assert_uint_eq(fingerprint, 0x3491a5e6); ck_assert_mem_eq( node.chain_code, fromhex( @@ -3343,7 +3370,7 @@ START_TEST(test_bip32_decred_vector_2) { fromhex( "bb7d39bdb83ecf58f2fd82b6d918341cbef428661ef01ab97c28a4842125ac23"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -3357,7 +3384,7 @@ START_TEST(test_bip32_decred_vector_2) { r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, SECP256K1_DECRED_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, sizeof(str)); @@ -3382,7 +3409,7 @@ START_TEST(test_bip32_decred_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_public_ckd(&node, 0); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x6a19cfb3); + ck_assert_uint_eq(fingerprint, 0x6a19cfb3); ck_assert_mem_eq( node.chain_code, fromhex( @@ -3405,33 +3432,79 @@ START_TEST(test_bip32_decred_vector_2) { } END_TEST -START_TEST(test_ecdsa_signature) { - int res; - uint8_t digest[32]; - uint8_t pubkey[65]; - uint8_t sig[64]; +static void test_ecdsa_get_public_key33_helper(int (*ecdsa_get_public_key33_fn)( + const ecdsa_curve *, const uint8_t *, uint8_t *)) { + uint8_t privkey[32] = {0}; + uint8_t pubkey[65] = {0}; const ecdsa_curve *curve = &secp256k1; + int res = 0; + + memcpy( + privkey, + fromhex( + "c46f5b217f04ff28886a89d3c762ed84e5fa318d1c9a635d541131e69f1f49f5"), + 32); + res = ecdsa_get_public_key33_fn(curve, privkey, pubkey); + ck_assert_int_eq(res, 0); + ck_assert_mem_eq( + pubkey, + fromhex( + "0232b062e9153f573c220b1be0299d6447e81577274bf11a7c08dff71384c6b6ec"), + 33); - // Signature verification for a digest which is equal to the group order. - // https://github.com/trezor/trezor-firmware/pull/1374 memcpy( + privkey, + fromhex( + "3b90a4de80fb00d77795762c389d1279d4b4ab5992ae3cde6bc12ca63116f74c"), + 32); + res = ecdsa_get_public_key33_fn(curve, privkey, pubkey); + ck_assert_int_eq(res, 0); + ck_assert_mem_eq( pubkey, fromhex( - "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179848" - "3ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8"), - sizeof(pubkey)); + "0332b062e9153f573c220b1be0299d6447e81577274bf11a7c08dff71384c6b6ec"), + 33); +} + +START_TEST(test_ecdsa_get_public_key33) { + test_ecdsa_get_public_key33_helper(ecdsa_get_public_key33); +} +END_TEST + +static void test_ecdsa_get_public_key65_helper(int (*ecdsa_get_public_key65_fn)( + const ecdsa_curve *, const uint8_t *, uint8_t *)) { + uint8_t privkey[32] = {0}; + uint8_t pubkey[65] = {0}; + const ecdsa_curve *curve = &secp256k1; + int res = 0; + memcpy( - digest, + privkey, fromhex( - "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"), - sizeof(digest)); - memcpy(sig, - fromhex( - "a0b37f8fba683cc68f6574cd43b39f0343a50008bf6ccea9d13231d9e7e2e1e41" - "1edc8d307254296264aebfc3dc76cd8b668373a072fd64665b50000e9fcce52"), - sizeof(sig)); - res = ecdsa_verify_digest(curve, pubkey, sig, digest); + "c46f5b217f04ff28886a89d3c762ed84e5fa318d1c9a635d541131e69f1f49f5"), + 32); + res = ecdsa_get_public_key65_fn(curve, privkey, pubkey); ck_assert_int_eq(res, 0); + ck_assert_mem_eq( + pubkey, + fromhex( + "0432b062e9153f573c220b1be0299d6447e81577274bf11a7c08dff71384c6b6ec" + "179ca56b637a57e0fcd28cefa10c9433dc30532682647f4daa053d43d5cc960a"), + 65); +} + +START_TEST(test_ecdsa_get_public_key65) { + test_ecdsa_get_public_key65_helper(ecdsa_get_public_key65); +} +END_TEST + +static void test_ecdsa_recover_pub_from_sig_helper(int ( + *ecdsa_recover_pub_from_sig_fn)(const ecdsa_curve *, uint8_t *, + const uint8_t *, const uint8_t *, int)) { + int res; + uint8_t digest[32]; + uint8_t pubkey[65]; + const ecdsa_curve *curve = &secp256k1; // sha2(sha2("\x18Bitcoin Signed Message:\n\x0cHello World!")) memcpy( @@ -3440,7 +3513,7 @@ START_TEST(test_ecdsa_signature) { "de4e9524586d6fce45667f9ff12f661e79870c4105fa0fb58af976619bb11432"), 32); // r = 2: Four points should exist - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "00000000000000000000000000000000000000000000000000000000000000020123" @@ -3453,7 +3526,7 @@ START_TEST(test_ecdsa_signature) { "043fc5bf5fec35b6ffe6fd246226d312742a8c296bfa57dd22da509a2e348529b7dd" "b9faf8afe1ecda3c05e7b2bda47ee1f5a87e952742b22afca560b29d972fcf"), 65); - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "00000000000000000000000000000000000000000000000000000000000000020123" @@ -3466,7 +3539,7 @@ START_TEST(test_ecdsa_signature) { "0456d8089137b1fd0d890f8c7d4a04d0fd4520a30b19518ee87bd168ea12ed809032" "9274c4c6c0d9df04515776f2741eeffc30235d596065d718c3973e19711ad0"), 65); - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "00000000000000000000000000000000000000000000000000000000000000020123" @@ -3479,7 +3552,7 @@ START_TEST(test_ecdsa_signature) { "04cee0e740f41aab39156844afef0182dea2a8026885b10454a2d539df6f6df9023a" "bfcb0f01c50bef3c0fa8e59a998d07441e18b1c60583ef75cc8b912fb21a15"), 65); - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "00000000000000000000000000000000000000000000000000000000000000020123" @@ -3492,6 +3565,14 @@ START_TEST(test_ecdsa_signature) { "0490d2bd2e9a564d6e1d8324fc6ad00aa4ae597684ecf4abea58bdfe7287ea4fa729" "68c2e5b0b40999ede3d7898d94e82c3f8dc4536a567a4bd45998c826a4c4b2"), 65); + // The point at infinity is not considered to be a valid public key. + res = ecdsa_recover_pub_from_sig_fn( + curve, pubkey, + fromhex( + "220cf4c7b6d568f2256a8c30cc1784a625a28c3627dac404aa9a9ecd08314ec81a88" + "828f20d69d102bab5de5f6ee7ef040cb0ff7b8e1ba3f29d79efb5250f47d"), + digest, 0); + ck_assert_int_eq(res, 1); memcpy( digest, @@ -3499,7 +3580,7 @@ START_TEST(test_ecdsa_signature) { "0000000000000000000000000000000000000000000000000000000000000000"), 32); // r = 7: No point P with P.x = 7, but P.x = (order + 7) exists - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "00000000000000000000000000000000000000000000000000000000000000070123" @@ -3512,7 +3593,7 @@ START_TEST(test_ecdsa_signature) { "044d81bb47a31ffc6cf1f780ecb1e201ec47214b651650867c07f13ad06e12a1b040" "de78f8dbda700f4d3cd7ee21b3651a74c7661809699d2be7ea0992b0d39797"), 65); - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "00000000000000000000000000000000000000000000000000000000000000070123" @@ -3525,7 +3606,7 @@ START_TEST(test_ecdsa_signature) { "044d81bb47a31ffc6cf1f780ecb1e201ec47214b651650867c07f13ad06e12a1b0bf" "21870724258ff0b2c32811de4c9ae58b3899e7f69662d41815f66c4f2c6498"), 65); - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "00000000000000000000000000000000000000000000000000000000000000070123" @@ -3539,7 +3620,7 @@ START_TEST(test_ecdsa_signature) { "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), 32); // r = 1: Two points P with P.x = 1, but P.x = (order + 7) doesn't exist - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "00000000000000000000000000000000000000000000000000000000000000010123" @@ -3552,7 +3633,7 @@ START_TEST(test_ecdsa_signature) { "045d330b2f89dbfca149828277bae852dd4aebfe136982cb531a88e9e7a89463fe71" "519f34ea8feb9490c707f14bc38c9ece51762bfd034ea014719b7c85d2871b"), 65); - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "00000000000000000000000000000000000000000000000000000000000000010123" @@ -3567,14 +3648,14 @@ START_TEST(test_ecdsa_signature) { 65); // r = 0 is always invalid - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "00000000000000000000000000000000000000000000000000000000000000010123" "456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"), digest, 2); ck_assert_int_eq(res, 1); - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "00000000000000000000000000000000000000000000000000000000000000000123" @@ -3582,7 +3663,7 @@ START_TEST(test_ecdsa_signature) { digest, 0); ck_assert_int_eq(res, 1); // r >= order is always invalid - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd03641410123" @@ -3590,7 +3671,7 @@ START_TEST(test_ecdsa_signature) { digest, 0); ck_assert_int_eq(res, 1); // check that overflow of r is handled - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "000000000000000000000000000000014551231950B75FC4402DA1722FC9BAEE0123" @@ -3598,7 +3679,7 @@ START_TEST(test_ecdsa_signature) { digest, 2); ck_assert_int_eq(res, 1); // s = 0 is always invalid - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "00000000000000000000000000000000000000000000000000000000000000020000" @@ -3606,7 +3687,7 @@ START_TEST(test_ecdsa_signature) { digest, 0); ck_assert_int_eq(res, 1); // s >= order is always invalid - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "0000000000000000000000000000000000000000000000000000000000000002ffff" @@ -3614,12 +3695,51 @@ START_TEST(test_ecdsa_signature) { digest, 0); ck_assert_int_eq(res, 1); } + +START_TEST(test_ecdsa_recover_pub_from_sig) { + test_ecdsa_recover_pub_from_sig_helper(ecdsa_recover_pub_from_sig); +} +END_TEST + +static void test_ecdsa_verify_digest_helper(int (*ecdsa_verify_digest_fn)( + const ecdsa_curve *, const uint8_t *, const uint8_t *, const uint8_t *)) { + int res; + uint8_t digest[32]; + uint8_t pubkey[65]; + uint8_t sig[64]; + const ecdsa_curve *curve = &secp256k1; + + // Signature verification for a digest which is equal to the group order. + // https://github.com/trezor/trezor-firmware/pull/1374 + memcpy( + pubkey, + fromhex( + "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179848" + "3ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8"), + sizeof(pubkey)); + memcpy( + digest, + fromhex( + "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"), + sizeof(digest)); + memcpy(sig, + fromhex( + "a0b37f8fba683cc68f6574cd43b39f0343a50008bf6ccea9d13231d9e7e2e1e41" + "1edc8d307254296264aebfc3dc76cd8b668373a072fd64665b50000e9fcce52"), + sizeof(sig)); + res = ecdsa_verify_digest_fn(curve, pubkey, sig, digest); + ck_assert_int_eq(res, 0); +} + +START_TEST(test_ecdsa_verify_digest) { + test_ecdsa_verify_digest_helper(ecdsa_verify_digest); +} END_TEST #define test_deterministic(KEY, MSG, K) \ do { \ sha256_Raw((uint8_t *)MSG, strlen(MSG), buf); \ - init_rfc6979(fromhex(KEY), buf, &rng); \ + init_rfc6979(fromhex(KEY), buf, NULL, &rng); \ generate_k_rfc6979(&k, &rng); \ bn_write_be(&k, buf); \ ck_assert_mem_eq(buf, fromhex(K), 32); \ @@ -3664,6 +3784,49 @@ START_TEST(test_rfc6979) { } END_TEST +static void test_ecdsa_sign_digest_deterministic_helper( + int (*ecdsa_sign_digest_fn)(const ecdsa_curve *, const uint8_t *, + const uint8_t *, uint8_t *, uint8_t *, + int (*)(uint8_t by, uint8_t sig[64]))) { + static struct { + const char *priv_key; + const char *digest; + const char *sig; + } tests[] = { + {"312155017c70a204106e034520e0cdf17b3e54516e2ece38e38e38e38e38e38e", + "ffffffffffffffffffffffffffffffff20202020202020202020202020202020", + "e3d70248ea2fc771fc8d5e62d76b9cfd5402c96990333549eaadce1ae9f737eb" + "5cfbdc7d1e0ec18cc9b57bbb18f0a57dc929ec3c4dfac9073c581705015f6a8a"}, + {"312155017c70a204106e034520e0cdf17b3e54516e2ece38e38e38e38e38e38e", + "2020202020202020202020202020202020202020202020202020202020202020", + "40666188895430715552a7e4c6b53851f37a93030fb94e043850921242db78e8" + "75aa2ac9fd7e5a19402973e60e64382cdc29a09ebf6cb37e92f23be5b9251aee"}, + }; + + const ecdsa_curve *curve = &secp256k1; + uint8_t priv_key[32] = {0}; + uint8_t digest[32] = {0}; + uint8_t expected_sig[64] = {0}; + uint8_t computed_sig[64] = {0}; + int res = 0; + + for (size_t i = 0; i < sizeof(tests) / sizeof(*tests); i++) { + memcpy(priv_key, fromhex(tests[i].priv_key), 32); + memcpy(digest, fromhex(tests[i].digest), 32); + memcpy(expected_sig, fromhex(tests[i].sig), 64); + + res = + ecdsa_sign_digest_fn(curve, priv_key, digest, computed_sig, NULL, NULL); + ck_assert_int_eq(res, 0); + ck_assert_mem_eq(expected_sig, computed_sig, 64); + } +} + +START_TEST(test_ecdsa_sign_digest_deterministic) { + test_ecdsa_sign_digest_deterministic_helper(ecdsa_sign_digest); +} +END_TEST + // test vectors from // http://www.inconteam.com/software-development/41-encryption/55-aes-test-vectors START_TEST(test_aes) { @@ -3673,7 +3836,7 @@ START_TEST(test_aes) { const char **ivp, **plainp, **cipherp; // ECB - const char *ecb_vector[] = { + static const char *ecb_vector[] = { // plain cipher "6bc1bee22e409f96e93d7e117393172a", "f3eed1bdb5d2a03c064b5a7e3db181f8", @@ -3710,7 +3873,7 @@ START_TEST(test_aes) { } // CBC - const char *cbc_vector[] = { + static const char *cbc_vector[] = { // iv plain cipher "000102030405060708090A0B0C0D0E0F", "6bc1bee22e409f96e93d7e117393172a", @@ -3756,7 +3919,7 @@ START_TEST(test_aes) { } // CFB - const char *cfb_vector[] = { + static const char *cfb_vector[] = { "000102030405060708090A0B0C0D0E0F", "6bc1bee22e409f96e93d7e117393172a", "DC7E84BFDA79164B7ECD8486985D3860", @@ -3801,7 +3964,7 @@ START_TEST(test_aes) { } // OFB - const char *ofb_vector[] = { + static const char *ofb_vector[] = { "000102030405060708090A0B0C0D0E0F", "6bc1bee22e409f96e93d7e117393172a", "dc7e84bfda79164b7ecd8486985d3860", @@ -3846,7 +4009,7 @@ START_TEST(test_aes) { } // CTR - const char *ctr_vector[] = { + static const char *ctr_vector[] = { // plain cipher "6bc1bee22e409f96e93d7e117393172a", "601ec313775789a5b7a7f504bbf3d228", @@ -4171,84 +4334,101 @@ END_TEST // test vectors from http://www.di-mgt.com.au/sha_testvectors.html START_TEST(test_sha3_256) { - uint8_t digest[SHA3_256_DIGEST_LENGTH]; - - sha3_256((uint8_t *)"", 0, digest); - ck_assert_mem_eq( - digest, - fromhex( - "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a"), - SHA3_256_DIGEST_LENGTH); - - sha3_256((uint8_t *)"abc", 3, digest); - ck_assert_mem_eq( - digest, - fromhex( - "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532"), - SHA3_256_DIGEST_LENGTH); - - sha3_256( - (uint8_t *)"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", 56, - digest); - ck_assert_mem_eq( - digest, - fromhex( - "41c0dba2a9d6240849100376a8235e2c82e1b9998a999e21db32dd97496d3376"), - SHA3_256_DIGEST_LENGTH); + static const struct { + const char *data; + const char *hash; + } tests[] = { + { + "", + "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a", + }, + { + "abc", + "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532", + }, + { + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "41c0dba2a9d6240849100376a8235e2c82e1b9998a999e21db32dd97496d3376", + }, + { + "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijkl" + "mnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", + "916f6061fe879741ca6469b43971dfdb28b1a32dc36cb3254e812be27aad1d18", + }, + }; - sha3_256((uint8_t *)"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", 112, digest); - ck_assert_mem_eq( - digest, - fromhex( - "916f6061fe879741ca6469b43971dfdb28b1a32dc36cb3254e812be27aad1d18"), - SHA3_256_DIGEST_LENGTH); + uint8_t digest[SHA3_256_DIGEST_LENGTH]; + for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { + size_t len = strlen(tests[i].data); + sha3_256((uint8_t *)tests[i].data, len, digest); + ck_assert_mem_eq(digest, fromhex(tests[i].hash), SHA3_256_DIGEST_LENGTH); + + // Test progressive hashing. + size_t part_len = len; + SHA3_CTX ctx; + sha3_256_Init(&ctx); + sha3_Update(&ctx, (uint8_t *)tests[i].data, part_len); + sha3_Update(&ctx, NULL, 0); + sha3_Update(&ctx, (uint8_t *)tests[i].data + part_len, len - part_len); + sha3_Final(&ctx, digest); + ck_assert_mem_eq(digest, fromhex(tests[i].hash), SHA3_256_DIGEST_LENGTH); + } } END_TEST // test vectors from http://www.di-mgt.com.au/sha_testvectors.html START_TEST(test_sha3_512) { - uint8_t digest[SHA3_512_DIGEST_LENGTH]; - - sha3_512((uint8_t *)"", 0, digest); - ck_assert_mem_eq( - digest, - fromhex( + static const struct { + const char *data; + const char *hash; + } tests[] = { + { + "", "a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2" - "123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26"), - SHA3_512_DIGEST_LENGTH); - - sha3_512((uint8_t *)"abc", 3, digest); - ck_assert_mem_eq( - digest, - fromhex( + "123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26", + }, + { + "abc", "b751850b1a57168a5693cd924b6b096e08f621827444f70d884f5d0240d2712e10e1" - "16e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0"), - SHA3_512_DIGEST_LENGTH); - - sha3_512( - (uint8_t *)"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", 56, - digest); - ck_assert_mem_eq( - digest, - fromhex( + "16e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0", + }, + { + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "04a371e84ecfb5b8b77cb48610fca8182dd457ce6f326a0fd3d7ec2f1e91636dee69" - "1fbe0c985302ba1b0d8dc78c086346b533b49c030d99a27daf1139d6e75e"), - SHA3_512_DIGEST_LENGTH); - - sha3_512((uint8_t *)"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", 112, digest); - ck_assert_mem_eq( - digest, - fromhex( + "1fbe0c985302ba1b0d8dc78c086346b533b49c030d99a27daf1139d6e75e", + }, + { + "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijkl" + "mnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", "afebb2ef542e6579c50cad06d2e578f9f8dd6881d7dc824d26360feebf18a4fa73e3" - "261122948efcfd492e74e82e2189ed0fb440d187f382270cb455f21dd185"), - SHA3_512_DIGEST_LENGTH); + "261122948efcfd492e74e82e2189ed0fb440d187f382270cb455f21dd185", + }, + }; + + uint8_t digest[SHA3_512_DIGEST_LENGTH]; + for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { + size_t len = strlen(tests[i].data); + sha3_512((uint8_t *)tests[i].data, len, digest); + ck_assert_mem_eq(digest, fromhex(tests[i].hash), SHA3_512_DIGEST_LENGTH); + + // Test progressive hashing. + size_t part_len = len; + SHA3_CTX ctx; + sha3_512_Init(&ctx); + sha3_Update(&ctx, (const uint8_t *)tests[i].data, part_len); + sha3_Update(&ctx, NULL, 0); + sha3_Update(&ctx, (const uint8_t *)tests[i].data + part_len, + len - part_len); + sha3_Final(&ctx, digest); + ck_assert_mem_eq(digest, fromhex(tests[i].hash), SHA3_512_DIGEST_LENGTH); + } } END_TEST // test vectors from // https://raw.githubusercontent.com/NemProject/nem-test-vectors/master/0.test-sha3-256.dat START_TEST(test_keccak_256) { - const struct { + static const struct { const char *hash; size_t length; const char *data; @@ -4490,6 +4670,17 @@ START_TEST(test_keccak_256) { for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { keccak_256(fromhex(tests[i].data), tests[i].length, hash); ck_assert_mem_eq(hash, fromhex(tests[i].hash), SHA3_256_DIGEST_LENGTH); + + // Test progressive hashing. + size_t part_len = tests[i].length / 2; + SHA3_CTX ctx = {0}; + keccak_256_Init(&ctx); + keccak_Update(&ctx, fromhex(tests[i].data), part_len); + keccak_Update(&ctx, fromhex(tests[i].data), 0); + keccak_Update(&ctx, fromhex(tests[i].data) + part_len, + tests[i].length - part_len); + keccak_Final(&ctx, hash); + ck_assert_mem_eq(hash, fromhex(tests[i].hash), SHA3_256_DIGEST_LENGTH); } } END_TEST @@ -4497,7 +4688,7 @@ END_TEST // test vectors from // https://raw.githubusercontent.com/monero-project/monero/master/tests/hash/tests-extra-blake.txt START_TEST(test_blake256) { - struct { + static const struct { const char *hash; const char *data; } tests[] = { @@ -4586,7 +4777,18 @@ START_TEST(test_blake256) { uint8_t hash[BLAKE256_DIGEST_LENGTH]; for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { - blake256(fromhex(tests[i].data), i, hash); + size_t len = strlen(tests[i].data) / 2; + blake256(fromhex(tests[i].data), len, hash); + ck_assert_mem_eq(hash, fromhex(tests[i].hash), BLAKE256_DIGEST_LENGTH); + + // Test progressive hashing. + size_t part_len = len / 2; + BLAKE256_CTX ctx; + blake256_Init(&ctx); + blake256_Update(&ctx, fromhex(tests[i].data), part_len); + blake256_Update(&ctx, NULL, 0); + blake256_Update(&ctx, fromhex(tests[i].data) + part_len, len - part_len); + blake256_Final(&ctx, hash); ck_assert_mem_eq(hash, fromhex(tests[i].hash), BLAKE256_DIGEST_LENGTH); } } @@ -4595,6 +4797,36 @@ END_TEST // test vectors from // https://raw.githubusercontent.com/BLAKE2/BLAKE2/master/testvectors/blake2b-kat.txt START_TEST(test_blake2b) { + static const struct { + const char *msg; + const char *hash; + } tests[] = { + { + "", + "10ebb67700b1868efb4417987acf4690ae9d972fb7a590c2f02871799aaa4786b5e9" + "96e8f0f4eb981fc214b005f42d2ff4233499391653df7aefcbc13fc51568", + }, + { + "000102", + "33d0825dddf7ada99b0e7e307104ad07ca9cfd9692214f1561356315e784f3e5a17e" + "364ae9dbb14cb2036df932b77f4b292761365fb328de7afdc6d8998f5fc1", + }, + { + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021" + "22232425262728292a2b2c2d2e2f3031323334353637", + "f8f3726ac5a26cc80132493a6fedcb0e60760c09cfc84cad178175986819665e7684" + "2d7b9fedf76dddebf5d3f56faaad4477587af21606d396ae570d8e719af2", + }, + { + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021" + "22232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40414243" + "4445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465" + "666768696a6b6c6d6e6f", + "227e3aed8d2cb10b918fcb04f9de3e6d0a57e08476d93759cd7b2ed54a1cbf0239c5" + "28fb04bbf288253e601d3bc38b21794afef90b17094a182cac557745e75f", + }, + }; + uint8_t key[BLAKE2B_KEY_LENGTH]; memcpy(key, fromhex( @@ -4603,54 +4835,111 @@ START_TEST(test_blake2b) { BLAKE2B_KEY_LENGTH); uint8_t digest[BLAKE2B_DIGEST_LENGTH]; + for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { + size_t msg_len = strlen(tests[i].msg) / 2; + blake2b_Key(fromhex(tests[i].msg), msg_len, key, sizeof(key), digest, + sizeof(digest)); + ck_assert_mem_eq(digest, fromhex(tests[i].hash), sizeof(digest)); + + // Test progressive hashing. + size_t part_len = msg_len / 2; + BLAKE2B_CTX ctx; + ck_assert_int_eq(blake2b_InitKey(&ctx, sizeof(digest), key, sizeof(key)), + 0); + ck_assert_int_eq(blake2b_Update(&ctx, fromhex(tests[i].msg), part_len), 0); + ck_assert_int_eq(blake2b_Update(&ctx, NULL, 0), 0); + ck_assert_int_eq(blake2b_Update(&ctx, fromhex(tests[i].msg) + part_len, + msg_len - part_len), + 0); + ck_assert_int_eq(blake2b_Final(&ctx, digest, sizeof(digest)), 0); + ck_assert_mem_eq(digest, fromhex(tests[i].hash), BLAKE2B_DIGEST_LENGTH); + } +} +END_TEST - blake2b_Key((uint8_t *)"", 0, key, BLAKE2B_KEY_LENGTH, digest, - BLAKE2B_DIGEST_LENGTH); - ck_assert_mem_eq( - digest, - fromhex( - "10ebb67700b1868efb4417987acf4690ae9d972fb7a590c2f02871799aaa4786b5e9" - "96e8f0f4eb981fc214b005f42d2ff4233499391653df7aefcbc13fc51568"), - BLAKE2B_DIGEST_LENGTH); - - blake2b_Key(fromhex("000102"), 3, key, BLAKE2B_KEY_LENGTH, digest, - BLAKE2B_DIGEST_LENGTH); - ck_assert_mem_eq( - digest, - fromhex( - "33d0825dddf7ada99b0e7e307104ad07ca9cfd9692214f1561356315e784f3e5a17e" - "364ae9dbb14cb2036df932b77f4b292761365fb328de7afdc6d8998f5fc1"), - BLAKE2B_DIGEST_LENGTH); +// Blake2b-256 personalized, a la ZCash +// Test vectors from https://zips.z.cash/zip-0243 +START_TEST(test_blake2bp) { + static const struct { + const char *msg; + const char *personal; + const char *hash; + } tests[] = { + { + "", + "ZcashPrevoutHash", + "d53a633bbecf82fe9e9484d8a0e727c73bb9e68c96e72dec30144f6a84afa136", + }, + { + "", + "ZcashSequencHash", + "a5f25f01959361ee6eb56a7401210ee268226f6ce764a4f10b7f29e54db37272", - blake2b_Key( - fromhex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" - "202122232425262728292a2b2c2d2e2f3031323334353637"), - 56, key, BLAKE2B_KEY_LENGTH, digest, BLAKE2B_DIGEST_LENGTH); - ck_assert_mem_eq( - digest, - fromhex( - "f8f3726ac5a26cc80132493a6fedcb0e60760c09cfc84cad178175986819665e7684" - "2d7b9fedf76dddebf5d3f56faaad4477587af21606d396ae570d8e719af2"), - BLAKE2B_DIGEST_LENGTH); + }, + { + "e7719811893e0000095200ac6551ac636565b2835a0805750200025151", + "ZcashOutputsHash", + "ab6f7f6c5ad6b56357b5f37e16981723db6c32411753e28c175e15589172194a", + }, + { + "0bbe32a598c22adfb48cef72ba5d4287c0cefbacfd8ce195b4963c34a94bba7a1" + "75dae4b090f47a068e227433f9e49d3aa09e356d8d66d0c0121e91a3c4aa3f27fa1b" + "63396e2b41d", + "ZcashPrevoutHash", + "cacf0f5210cce5fa65a59f314292b3111d299e7d9d582753cf61e1e408552ae4", + }}; - blake2b_Key( - fromhex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" - "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f" - "404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f" - "606162636465666768696a6b6c6d6e6f"), - 112, key, BLAKE2B_KEY_LENGTH, digest, BLAKE2B_DIGEST_LENGTH); - ck_assert_mem_eq( - digest, - fromhex( - "227e3aed8d2cb10b918fcb04f9de3e6d0a57e08476d93759cd7b2ed54a1cbf0239c5" - "28fb04bbf288253e601d3bc38b21794afef90b17094a182cac557745e75f"), - BLAKE2B_DIGEST_LENGTH); + uint8_t digest[32]; + for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { + size_t msg_len = strlen(tests[i].msg) / 2; + + // Test progressive hashing. + size_t part_len = msg_len / 2; + BLAKE2B_CTX ctx; + ck_assert_int_eq( + blake2b_InitPersonal(&ctx, sizeof(digest), tests[i].personal, + strlen(tests[i].personal)), + 0); + ck_assert_int_eq(blake2b_Update(&ctx, fromhex(tests[i].msg), part_len), 0); + ck_assert_int_eq(blake2b_Update(&ctx, NULL, 0), 0); + ck_assert_int_eq(blake2b_Update(&ctx, fromhex(tests[i].msg) + part_len, + msg_len - part_len), + 0); + ck_assert_int_eq(blake2b_Final(&ctx, digest, sizeof(digest)), 0); + ck_assert_mem_eq(digest, fromhex(tests[i].hash), sizeof(digest)); + } } END_TEST // test vectors from // https://raw.githubusercontent.com/BLAKE2/BLAKE2/master/testvectors/blake2s-kat.txt START_TEST(test_blake2s) { + static const struct { + const char *msg; + const char *hash; + } tests[] = { + { + "", + "48a8997da407876b3d79c0d92325ad3b89cbb754d86ab71aee047ad345fd2c49", + }, + { + "000102", + "1d220dbe2ee134661fdf6d9e74b41704710556f2f6e5a091b227697445dbea6b", + }, + { + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021" + "22232425262728292a2b2c2d2e2f3031323334353637", + "2966b3cfae1e44ea996dc5d686cf25fa053fb6f67201b9e46eade85d0ad6b806", + }, + { + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021" + "22232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40414243" + "4445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465" + "666768696a6b6c6d6e6f", + "90a83585717b75f0e9b725e055eeeeb9e7a028ea7e6cbc07b20917ec0363e38c", + }, + }; + uint8_t key[BLAKE2S_KEY_LENGTH]; memcpy( key, @@ -4659,62 +4948,64 @@ START_TEST(test_blake2s) { BLAKE2S_KEY_LENGTH); uint8_t digest[BLAKE2S_DIGEST_LENGTH]; - - blake2s_Key((uint8_t *)"", 0, key, BLAKE2S_KEY_LENGTH, digest, - BLAKE2S_DIGEST_LENGTH); - ck_assert_mem_eq( - digest, - fromhex( - "48a8997da407876b3d79c0d92325ad3b89cbb754d86ab71aee047ad345fd2c49"), - BLAKE2S_DIGEST_LENGTH); - - blake2s_Key(fromhex("000102"), 3, key, BLAKE2S_KEY_LENGTH, digest, - BLAKE2S_DIGEST_LENGTH); - ck_assert_mem_eq( - digest, - fromhex( - "1d220dbe2ee134661fdf6d9e74b41704710556f2f6e5a091b227697445dbea6b"), - BLAKE2S_DIGEST_LENGTH); - - blake2s_Key( - fromhex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" - "202122232425262728292a2b2c2d2e2f3031323334353637"), - 56, key, BLAKE2S_KEY_LENGTH, digest, BLAKE2S_DIGEST_LENGTH); - ck_assert_mem_eq( - digest, - fromhex( - "2966b3cfae1e44ea996dc5d686cf25fa053fb6f67201b9e46eade85d0ad6b806"), - BLAKE2S_DIGEST_LENGTH); - - blake2s_Key( - fromhex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" - "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f" - "404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f" - "606162636465666768696a6b6c6d6e6f"), - 112, key, BLAKE2S_KEY_LENGTH, digest, BLAKE2S_DIGEST_LENGTH); - ck_assert_mem_eq( - digest, - fromhex( - "90a83585717b75f0e9b725e055eeeeb9e7a028ea7e6cbc07b20917ec0363e38c"), - BLAKE2S_DIGEST_LENGTH); + for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { + size_t msg_len = strlen(tests[i].msg) / 2; + blake2s_Key(fromhex(tests[i].msg), msg_len, key, sizeof(key), digest, + sizeof(digest)); + ck_assert_mem_eq(digest, fromhex(tests[i].hash), sizeof(digest)); + + // Test progressive hashing. + size_t part_len = msg_len / 2; + BLAKE2S_CTX ctx; + ck_assert_int_eq(blake2s_InitKey(&ctx, sizeof(digest), key, sizeof(key)), + 0); + ck_assert_int_eq(blake2s_Update(&ctx, fromhex(tests[i].msg), part_len), 0); + ck_assert_int_eq(blake2s_Update(&ctx, NULL, 0), 0); + ck_assert_int_eq(blake2s_Update(&ctx, fromhex(tests[i].msg) + part_len, + msg_len - part_len), + 0); + ck_assert_int_eq(blake2s_Final(&ctx, digest, sizeof(digest)), 0); + ck_assert_mem_eq(digest, fromhex(tests[i].hash), BLAKE2S_DIGEST_LENGTH); + } } END_TEST +#include + START_TEST(test_chacha_drbg) { - char entropy[] = "8a09b482de30c12ee1d2eb69dd49753d4252b3d36128ee1e"; - char reseed[] = "9ec4b991f939dbb44355392d05cd793a2e281809d2ed7139"; + char entropy[] = + "06032cd5eed33f39265f49ecb142c511da9aff2af71203bffaf34a9ca5bd9c0d"; + char nonce[] = "0e66f71edc43e42a45ad3c6fc6cdc4df"; + char reseed[] = + "01920a4e669ed3a85ae8a33b35a74ad7fb2a6bb4cf395ce00334a9c9a5a5d552"; char expected[] = - "4caaeb7db073d34b37b5b26f8a3863849f298dab754966e0f75526823216057c2626e044" - "9f7ffda7c3dba8841c06af01029eebfd4d4cae951c19c9f6ff6812783e58438840883401" - "2a05cd24c38cd22d18296aceed6829299190ebb9455eb8fd8d1cac1d"; - uint8_t result[100]; + "e172c5d18f3e8c77e9f66f9e1c24560772117161a9a0a237ab490b0769ad5d910f5dfb36" + "22edc06c18be0495c52588b200893d90fd80ff2149ead0c45d062c90f5890149c0f9591c" + "41bf4110865129a0fe524f210cca1340bd16f71f57906946cbaaf1fa863897d70d203b5a" + "f9996f756eec08861ee5875f9d915adcddc38719"; + uint8_t result[128]; + uint8_t null_bytes[128] = {0}; + uint8_t nonce_bytes[16]; + memcpy(nonce_bytes, fromhex(nonce), sizeof(nonce_bytes)); CHACHA_DRBG_CTX ctx; - chacha_drbg_init(&ctx, fromhex(entropy)); - chacha_drbg_reseed(&ctx, fromhex(reseed)); + chacha_drbg_init(&ctx, fromhex(entropy), strlen(entropy) / 2, nonce_bytes, + strlen(nonce) / 2); + chacha_drbg_reseed(&ctx, fromhex(reseed), strlen(reseed) / 2, NULL, 0); chacha_drbg_generate(&ctx, result, sizeof(result)); chacha_drbg_generate(&ctx, result, sizeof(result)); ck_assert_mem_eq(result, fromhex(expected), sizeof(result)); + + for (size_t i = 0; i <= sizeof(result); ++i) { + chacha_drbg_init(&ctx, fromhex(entropy), strlen(entropy) / 2, nonce_bytes, + strlen(nonce) / 2); + chacha_drbg_reseed(&ctx, fromhex(reseed), strlen(reseed) / 2, NULL, 0); + chacha_drbg_generate(&ctx, result, sizeof(result) - 13); + memset(result, 0, sizeof(result)); + chacha_drbg_generate(&ctx, result, i); + ck_assert_mem_eq(result, fromhex(expected), i); + ck_assert_mem_eq(result + i, null_bytes, sizeof(result) - i); + } } END_TEST @@ -4853,7 +5144,7 @@ START_TEST(test_hmac_drbg) { END_TEST START_TEST(test_mnemonic) { - const char *vectors[] = { + static const char *vectors[] = { "00000000000000000000000000000000", "abandon abandon abandon abandon abandon abandon abandon abandon abandon " "abandon abandon about", @@ -4990,8 +5281,9 @@ START_TEST(test_mnemonic) { a = vectors; b = vectors + 1; c = vectors + 2; -#define TC_BUF_SIZE 300 + #define TC_BUF_SIZE 308 char buf[TC_BUF_SIZE]; + while (*a && *b && *c) { m = mnemonic_from_data(fromhex(*a), strlen(*a) / 2, buf, TC_BUF_SIZE); ck_assert_str_eq(m, *b); @@ -5005,18 +5297,14 @@ START_TEST(test_mnemonic) { a += 3; b += 3; c += 3; + memzero(buf, TC_BUF_SIZE ); #undef TC_BUF_SIZE } - - // [wallet-core] negative test: provided buffer invalid (too small or null) - ck_assert_int_eq((int)(mnemonic_from_data(fromhex(vectors[0]), strlen(vectors[0]) / 2, buf, 200)), 0); - ck_assert_int_eq((int)(mnemonic_from_data(fromhex(vectors[0]), strlen(vectors[0]) / 2, buf, 0)), 0); - ck_assert_int_eq((int)(mnemonic_from_data(fromhex(vectors[0]), strlen(vectors[0]) / 2, NULL, 240)), 0); } END_TEST START_TEST(test_mnemonic_check) { - const char *vectors_ok[] = { + static const char *vectors_ok[] = { "abandon abandon abandon abandon abandon abandon abandon abandon abandon " "abandon abandon about", "legal winner thank year wave sausage worth useful legal winner thank " @@ -5072,7 +5360,7 @@ START_TEST(test_mnemonic_check) { "away coconut", 0, }; - const char *vectors_fail[] = { + static const char *vectors_fail[] = { "above abandon abandon abandon abandon abandon abandon abandon abandon " "abandon abandon about", "above winner thank year wave sausage worth useful legal winner thank " @@ -5195,7 +5483,7 @@ START_TEST(test_mnemonic_check) { END_TEST START_TEST(test_mnemonic_to_bits) { - const char *vectors[] = { + static const char *vectors[] = { "00000000000000000000000000000000", "abandon abandon abandon abandon abandon abandon abandon abandon abandon " "abandon abandon about", @@ -5286,7 +5574,7 @@ START_TEST(test_mnemonic_to_bits) { int mnemonic_bits_len = mnemonic_to_bits(*b, mnemonic_bits); ck_assert_int_eq(mnemonic_bits_len % 33, 0); mnemonic_bits_len = mnemonic_bits_len * 4 / 33; - ck_assert_int_eq(mnemonic_bits_len, strlen(*a) / 2); + ck_assert_uint_eq((size_t)mnemonic_bits_len, strlen(*a) / 2); ck_assert_mem_eq(mnemonic_bits, fromhex(*a), mnemonic_bits_len); a += 2; b += 2; @@ -5297,7 +5585,7 @@ END_TEST START_TEST(test_mnemonic_find_word) { ck_assert_int_eq(-1, mnemonic_find_word("aaaa")); ck_assert_int_eq(-1, mnemonic_find_word("zzzz")); - for (int i = 0; i < BIP39_WORDS; i++) { + for (int i = 0; i < BIP39_WORD_COUNT; i++) { const char *word = mnemonic_get_word(i); int index = mnemonic_find_word(word); ck_assert_int_eq(i, index); @@ -5305,9 +5593,8 @@ START_TEST(test_mnemonic_find_word) { } END_TEST -/* // [wallet-core] START_TEST(test_slip39_get_word) { - const struct { + static const struct { const int index; const char *expected_word; } vectors[] = {{573, "member"}, @@ -5324,7 +5611,7 @@ END_TEST START_TEST(test_slip39_word_index) { uint16_t index; - const struct { + static const struct { const char *word; bool expected_result; uint16_t expected_index; @@ -5336,17 +5623,17 @@ START_TEST(test_slip39_word_index) { // 9999 value is never checked since the word is not in list {"fakeword", false, 9999}}; for (size_t i = 0; i < (sizeof(vectors) / sizeof(*vectors)); i++) { - bool result = word_index(&index, vectors[i].word, sizeof(vectors[i].word)); + bool result = word_index(&index, vectors[i].word, strlen(vectors[i].word)); ck_assert_int_eq(result, vectors[i].expected_result); if (result) { - ck_assert_int_eq(index, vectors[i].expected_index); + ck_assert_uint_eq(index, vectors[i].expected_index); } } } END_TEST START_TEST(test_slip39_word_completion_mask) { - const struct { + static const struct { const uint16_t prefix; const uint16_t expected_mask; } vectors[] = { @@ -5365,13 +5652,13 @@ START_TEST(test_slip39_word_completion_mask) { }; for (size_t i = 0; i < (sizeof(vectors) / sizeof(*vectors)); i++) { uint16_t mask = slip39_word_completion_mask(vectors[i].prefix); - ck_assert_int_eq(mask, vectors[i].expected_mask); + ck_assert_uint_eq(mask, vectors[i].expected_mask); } } END_TEST START_TEST(test_slip39_sequence_to_word) { - const struct { + static const struct { const uint16_t prefix; const char *expected_word; } vectors[] = { @@ -5406,11 +5693,10 @@ START_TEST(test_slip39_word_completion) { } } END_TEST -*/ START_TEST(test_shamir) { #define SHAMIR_MAX_COUNT 16 - const struct { + static const struct { const uint8_t result[SHAMIR_MAX_LEN]; uint8_t result_index; const uint8_t share_indices[SHAMIR_MAX_COUNT]; @@ -5938,7 +6224,7 @@ START_TEST(test_address_decode) { END_TEST START_TEST(test_ecdsa_der) { - const struct { + static const struct { const char *r; const char *s; const char *der; @@ -5998,6 +6284,11 @@ START_TEST(test_ecdsa_der) { "00000000000000000000000000000000000000000000000000000000000000ff", "3008020200ee020200ff", }, + { + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "3006020100020100", + }, }; uint8_t sig[64]; @@ -6147,11 +6438,11 @@ static void test_point_mult_curve(const ecdsa_curve *curve) { /* test distributivity: (a + b)P = aP + bP */ bn_mod(&a, &curve->order); bn_mod(&b, &curve->order); - point_multiply(curve, &a, &p, &p1); - point_multiply(curve, &b, &p, &p2); + ck_assert_int_eq(point_multiply(curve, &a, &p, &p1), 0); + ck_assert_int_eq(point_multiply(curve, &b, &p, &p2), 0); bn_addmod(&a, &b, &curve->order); bn_mod(&a, &curve->order); - point_multiply(curve, &a, &p, &p3); + ck_assert_int_eq(point_multiply(curve, &a, &p, &p3), 0); point_add(curve, &p1, &p2); ck_assert_mem_eq(&p2, &p3, sizeof(curve_point)); // new "random" numbers and a "random" point @@ -6178,17 +6469,17 @@ static void test_scalar_point_mult_curve(const ecdsa_curve *curve) { */ bn_mod(&a, &curve->order); bn_mod(&b, &curve->order); - scalar_multiply(curve, &a, &p1); - point_multiply(curve, &b, &p1, &p1); + ck_assert_int_eq(scalar_multiply(curve, &a, &p1), 0); + ck_assert_int_eq(point_multiply(curve, &b, &p1, &p1), 0); - scalar_multiply(curve, &b, &p2); - point_multiply(curve, &a, &p2, &p2); + ck_assert_int_eq(scalar_multiply(curve, &b, &p2), 0); + ck_assert_int_eq(point_multiply(curve, &a, &p2, &p2), 0); ck_assert_mem_eq(&p1, &p2, sizeof(curve_point)); bn_multiply(&a, &b, &curve->order); bn_mod(&b, &curve->order); - scalar_multiply(curve, &b, &p2); + ck_assert_int_eq(scalar_multiply(curve, &b, &p2), 0); ck_assert_mem_eq(&p1, &p2, sizeof(curve_point)); @@ -6210,7 +6501,7 @@ END_TEST START_TEST(test_ed25519) { // test vectors from // https://github.com/torproject/tor/blob/master/src/test/ed25519_vectors.inc - const char *vectors[] = { + static const char *vectors[] = { "26c76712d89d906e6672dafa614c42e5cb1caac8c6568e4d2493087db51f0d3" "6", // secret "c2247870536a192d142d056abefca68d6193158e7c1a59c1654c954eccaff89" @@ -6286,7 +6577,7 @@ START_TEST(test_ed25519) { UNMARK_SECRET_DATA(pk, sizeof(pk)); ck_assert_mem_eq(pk, fromhex(*spk), 32); - ed25519_sign(pk, 32, sk, pk, sig); + ed25519_sign(pk, 32, sk, sig); UNMARK_SECRET_DATA(sig, sizeof(sig)); ck_assert_mem_eq(sig, fromhex(*ssig), 64); @@ -6302,7 +6593,7 @@ END_TEST // test vectors from // https://raw.githubusercontent.com/NemProject/nem-test-vectors/master/2.test-sign.dat START_TEST(test_ed25519_keccak) { - const struct { + static const struct { const char *private_key; const char *public_key; const char *signature; @@ -6504,7 +6795,7 @@ START_TEST(test_ed25519_keccak) { ck_assert_mem_eq(public_key, fromhex(tests[i].public_key), 32); ed25519_sign_keccak(fromhex(tests[i].data), tests[i].length, private_key, - public_key, signature); + signature); UNMARK_SECRET_DATA(signature, sizeof(signature)); ck_assert_mem_eq(signature, fromhex(tests[i].signature), 64); @@ -6513,8 +6804,15 @@ START_TEST(test_ed25519_keccak) { } END_TEST + START_TEST(test_ed25519_cosi) { -#define MAXN 10 +//win +#ifdef _MSC_VER + #define MAXN 10 +#else + const int MAXN = 10; +#endif + ed25519_secret_key keys[MAXN]; ed25519_public_key pubkeys[MAXN]; ed25519_secret_key nonces[MAXN]; @@ -6529,7 +6827,7 @@ START_TEST(test_ed25519_cosi) { "26c76712d89d906e6672dafa614c42e5cb1caac8c6568e4d2493087db51f0d36"), fromhex( "26659c1cf7321c178c07437150639ff0c5b7679c7ea195253ed9abda2e081a37"), - &rng); + NULL, &rng); for (int N = 1; N < 11; N++) { ed25519_public_key pk; @@ -6568,7 +6866,6 @@ START_TEST(test_ed25519_cosi) { UNMARK_SECRET_DATA(keys, sizeof(keys)); } -#undef MAXN } END_TEST @@ -6737,7 +7034,8 @@ START_TEST(test_ed25519_modl_sub) { } END_TEST -#if USE_MONERO // [wallet-core] +#if USE_MONERO + START_TEST(test_ge25519_double_scalarmult_vartime2) { char tests[][5][65] = { {"c537208ed4985e66e9f7a35c9a69448a732ba93960bbbd2823604f7ae9e3ed08", @@ -6861,13 +7159,14 @@ START_TEST(test_ge25519_double_scalarmult_vartime2) { } } END_TEST + #endif static void test_bip32_ecdh_init_node(HDNode *node, const char *seed_str, const char *curve_name) { hdnode_from_seed((const uint8_t *)seed_str, strlen(seed_str), curve_name, node); - hdnode_fill_public_key(node); + ck_assert_int_eq(hdnode_fill_public_key(node), 0); if (node->public_key[0] == 1) { node->public_key[0] = 0x40; // Curve25519 public keys start with 0x40 byte } @@ -6875,29 +7174,50 @@ static void test_bip32_ecdh_init_node(HDNode *node, const char *seed_str, static void test_bip32_ecdh(const char *curve_name, int expected_key_size, const uint8_t *expected_key) { - int res, key_size; - HDNode alice, bob; -#ifdef _MSC_VER - uint8_t *session_key1 = _alloca(expected_key_size); - uint8_t *session_key2 = _alloca(expected_key_size); -#else - uint8_t session_key1[expected_key_size], session_key2[expected_key_size]; -#endif - - test_bip32_ecdh_init_node(&alice, "Alice", curve_name); - test_bip32_ecdh_init_node(&bob, "Bob", curve_name); - - // Generate shared key from Alice's secret key and Bob's public key - res = hdnode_get_shared_key(&alice, bob.public_key, session_key1, &key_size); - ck_assert_int_eq(res, 0); - ck_assert_int_eq(key_size, expected_key_size); - ck_assert_mem_eq(session_key1, expected_key, key_size); - - // Generate shared key from Bob's secret key and Alice's public key - res = hdnode_get_shared_key(&bob, alice.public_key, session_key2, &key_size); - ck_assert_int_eq(res, 0); - ck_assert_int_eq(key_size, expected_key_size); - ck_assert_mem_eq(session_key2, expected_key, key_size); + #ifdef _MSC_VER + uint8_t * session_key1 = (uint8_t *)malloc(sizeof(uint8_t) * expected_key_size); + uint8_t * session_key2 = (uint8_t *)malloc(sizeof(uint8_t) * expected_key_size); + + int res, key_size; + HDNode alice, bob; + + test_bip32_ecdh_init_node(&alice, "Alice", curve_name); + test_bip32_ecdh_init_node(&bob, "Bob", curve_name); + // Generate shared key from Alice's secret key and Bob's public key + res = hdnode_get_shared_key(&alice, bob.public_key, session_key1, &key_size); + free (session_key1); + free (session_key2); + + ck_assert_int_eq(res, 0); + ck_assert_int_eq(key_size, expected_key_size); + ck_assert_mem_eq(session_key1, expected_key, key_size); + + // Generate shared key from Bob's secret key and Alice's public key + res = hdnode_get_shared_key(&bob, alice.public_key, session_key2, &key_size); + ck_assert_int_eq(res, 0); + ck_assert_int_eq(key_size, expected_key_size); + ck_assert_mem_eq(session_key2, expected_key, key_size); + #else + int res, key_size; + HDNode alice, bob; + uint8_t session_key1[expected_key_size], session_key2[expected_key_size]; + + test_bip32_ecdh_init_node(&alice, "Alice", curve_name); + test_bip32_ecdh_init_node(&bob, "Bob", curve_name); + + // Generate shared key from Alice's secret key and Bob's public key + res = hdnode_get_shared_key(&alice, bob.public_key, session_key1, &key_size); + ck_assert_int_eq(res, 0); + ck_assert_int_eq(key_size, expected_key_size); + ck_assert_mem_eq(session_key1, expected_key, key_size); + + // Generate shared key from Bob's secret key and Alice's public key + res = hdnode_get_shared_key(&bob, alice.public_key, session_key2, &key_size); + ck_assert_int_eq(res, 0); + ck_assert_int_eq(key_size, expected_key_size); + ck_assert_mem_eq(session_key2, expected_key, key_size); + #endif + } START_TEST(test_bip32_ecdh_nist256p1) { @@ -6940,7 +7260,7 @@ START_TEST(test_bip32_ecdh_errors) { END_TEST START_TEST(test_output_script) { - const char *vectors[] = { + static const char *vectors[] = { "76A914010966776006953D5567439E5E39F86A0D273BEE88AC", "16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM", "A914010966776006953D5567439E5E39F86A0D273BEE87", @@ -6959,7 +7279,7 @@ START_TEST(test_output_script) { while (*scr && *adr) { int r = script_output_to_address(fromhex(*scr), strlen(*scr) / 2, address, 60); - ck_assert_int_eq(r, (int)(strlen(*adr) + 1)); + ck_assert_uint_eq((size_t)r, strlen(*adr) + 1); ck_assert_str_eq(address, *adr); scr += 2; adr += 2; @@ -6968,6 +7288,7 @@ START_TEST(test_output_script) { END_TEST #if USE_ETHEREUM + START_TEST(test_ethereum_pubkeyhash) { uint8_t pubkeyhash[20]; int res; @@ -7069,25 +7390,25 @@ START_TEST(test_ethereum_pubkeyhash) { END_TEST START_TEST(test_ethereum_address) { - const char *vectors[] = {"52908400098527886E0F7030069857D2E4169EE7", - "8617E340B3D01FA5F11F306F4090FD50E238070D", - "de709f2102306220921060314715629080e2fb77", - "27b1fdb04752bbc536007a920d24acb045561c26", - "5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", - "fB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", - "dbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", - "D1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", - "5A4EAB120fB44eb6684E5e32785702FF45ea344D", - "5be4BDC48CeF65dbCbCaD5218B1A7D37F58A0741", - "a7dD84573f5ffF821baf2205745f768F8edCDD58", - "027a49d11d118c0060746F1990273FcB8c2fC196", - "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + static const char *vectors[] = {"0x52908400098527886E0F7030069857D2E4169EE7", + "0x8617E340B3D01FA5F11F306F4090FD50E238070D", + "0xde709f2102306220921060314715629080e2fb77", + "0x27b1fdb04752bbc536007a920d24acb045561c26", + "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", + "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", + "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", + "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", + "0x5A4EAB120fB44eb6684E5e32785702FF45ea344D", + "0x5be4BDC48CeF65dbCbCaD5218B1A7D37F58A0741", + "0xa7dD84573f5ffF821baf2205745f768F8edCDD58", + "0x027a49d11d118c0060746F1990273FcB8c2fC196", + "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", 0}; uint8_t addr[20]; - char address[41]; + char address[43]; const char **vec = vectors; while (*vec) { - memcpy(addr, fromhex(*vec), 20); + memcpy(addr, fromhex(*vec + 2), 20); ethereum_address_checksum(addr, address, false, 0); ck_assert_str_eq(address, *vec); vec++; @@ -7099,42 +7420,43 @@ END_TEST // https://github.com/rsksmart/RSKIPs/blob/master/IPs/RSKIP60.md START_TEST(test_rsk_address) { uint8_t addr[20]; - char address[41]; + char address[43]; - const char *rskip60_chain30[] = { - "5aaEB6053f3e94c9b9a09f33669435E7ef1bEAeD", - "Fb6916095cA1Df60bb79ce92cE3EA74c37c5d359", - "DBF03B407c01E7CD3cBea99509D93F8Dddc8C6FB", - "D1220A0Cf47c7B9BE7a2e6ba89F429762E7B9adB", 0}; + static const char *rskip60_chain30[] = { + "0x5aaEB6053f3e94c9b9a09f33669435E7ef1bEAeD", + "0xFb6916095cA1Df60bb79ce92cE3EA74c37c5d359", + "0xDBF03B407c01E7CD3cBea99509D93F8Dddc8C6FB", + "0xD1220A0Cf47c7B9BE7a2e6ba89F429762E7B9adB", 0}; const char **vec = rskip60_chain30; while (*vec) { - memcpy(addr, fromhex(*vec), 20); + memcpy(addr, fromhex(*vec + 2), 20); ethereum_address_checksum(addr, address, true, 30); ck_assert_str_eq(address, *vec); vec++; } - const char *rskip60_chain31[] = { - "5aAeb6053F3e94c9b9A09F33669435E7EF1BEaEd", - "Fb6916095CA1dF60bb79CE92ce3Ea74C37c5D359", - "dbF03B407C01E7cd3cbEa99509D93f8dDDc8C6fB", - "d1220a0CF47c7B9Be7A2E6Ba89f429762E7b9adB", 0}; + static const char *rskip60_chain31[] = { + "0x5aAeb6053F3e94c9b9A09F33669435E7EF1BEaEd", + "0xFb6916095CA1dF60bb79CE92ce3Ea74C37c5D359", + "0xdbF03B407C01E7cd3cbEa99509D93f8dDDc8C6fB", + "0xd1220a0CF47c7B9Be7A2E6Ba89f429762E7b9adB", 0}; vec = rskip60_chain31; while (*vec) { - memcpy(addr, fromhex(*vec), 20); + memcpy(addr, fromhex(*vec + 2), 20); ethereum_address_checksum(addr, address, true, 31); ck_assert_str_eq(address, *vec); vec++; } } END_TEST + #endif #if USE_NEM // test vectors from // https://raw.githubusercontent.com/NemProject/nem-test-vectors/master/1.test-keys.dat START_TEST(test_nem_address) { - const struct { + static const struct { const char *private_key; const char *public_key; const char *address; @@ -7263,7 +7585,7 @@ END_TEST // test vectors from // https://raw.githubusercontent.com/NemProject/nem-test-vectors/master/3.test-derive.dat START_TEST(test_nem_derive) { - const struct { + static const struct { const char *salt; const char *private_key; const char *public_key; @@ -7437,7 +7759,7 @@ END_TEST // test vectors from // https://raw.githubusercontent.com/NemProject/nem-test-vectors/master/4.test-cipher.dat START_TEST(test_nem_cipher) { - const struct { + static const struct { const char *private_key; const char *public_key; const char *salt; @@ -7698,13 +8020,13 @@ START_TEST(test_nem_cipher) { memcpy(iv, fromhex(tests[i].iv), sizeof(iv)); ck_assert(hdnode_nem_encrypt(&node, public_key, iv, salt, input, input_size, buffer)); - ck_assert_int_eq(output_size, NEM_ENCRYPTED_SIZE(input_size)); + ck_assert_uint_eq(output_size, NEM_ENCRYPTED_SIZE(input_size)); ck_assert_mem_eq(buffer, output, output_size); memcpy(iv, fromhex(tests[i].iv), sizeof(iv)); ck_assert(hdnode_nem_decrypt(&node, public_key, iv, salt, output, output_size, buffer)); - ck_assert_int_eq(input_size, NEM_DECRYPTED_SIZE(buffer, output_size)); + ck_assert_uint_eq(input_size, NEM_DECRYPTED_SIZE(buffer, output_size)); ck_assert_mem_eq(buffer, input, input_size); } } @@ -8408,11 +8730,11 @@ END_TEST // https://tools.ietf.org/html/rfc6229#section-2 START_TEST(test_rc4_rfc6229) { - const size_t offsets[] = { + static const size_t offsets[] = { 0x0, 0xf0, 0x1f0, 0x2f0, 0x3f0, 0x5f0, 0x7f0, 0xbf0, 0xff0, }; - const struct { + static const struct { char key[65]; char vectors[sizeof(offsets) / sizeof(*offsets)][65]; } tests[] = { @@ -8732,7 +9054,7 @@ static void test_compress_coord(const char *k_raw) { } START_TEST(test_compress_coords) { - const char *k_raw[] = { + static const char *k_raw[] = { "dc05960ac673fd59554c98655e26722d007bb7ada0c8ff00883fdee70783d0be", "41e41e0a218c980411108a0a58cf88f528c828b4d6f0d2c86234bc2504bdc3cd", "1d963ddcb79f6028a32cadd2421ff7fff969bff5774f73063dab41519b3da175", @@ -8750,219 +9072,6 @@ START_TEST(test_compress_coords) { } END_TEST -// [wallet-core] -START_TEST(test_schnorr_sign_verify) { - static struct { - const char *message; - const char *priv_key; - const char *k_hex; - const char *s_hex; - const char *r_hex; - } test_cases[] = { - { - "123", - "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", - "669301F724C555D7BB1185C04909E9CACA3EC7A292B3A1C92DDCCD5A5A7DDDD3", - "FFD72C290B98C93A4BCEDC0EDCDF040C35579BE962FE83E6821D4F3CB4B795D2", - "74AAE9C3E069E2806E1B0D890970BE387AEBED8040F37991AACAD70B27895E39", - }, - { - "1234", - "51a2758eed776c40b367364909c8a9c98cc969104f69ff316f7a287495c37c9b", - "A0A1A9B3570AAE963535B8D4376C58A61646C18182C9FDDA5FB13703F88D4D1E", - "99A0CB942C81571B77C682F79CD3CB663CE9E1C55BB425BA24B9F11A0DE84FE2", - "C3C10363E38158BBA20556A36DE9358DFD81A31C180ABC9E7617C1CC1CAF03B3", - }, - { - "12345", - "2685adffdbb4b2c515054cffc25cfcbfe2e462df65bbe82fb50f71e1e68dd285", - "38DE7B3315F201433D271E91FBE62966576CA05CBFEC1770B77D7EC9D6A01D6D", - "28982FA6C2B620CBC550F7EF9EAB605F409C584FBE5A765678877B79AB517086", - "9A0788E5B0947DEDEDE386DF57A006CF3FE43919A74D9CA630F8A1A9D97B4650", - }, - { - "fun", - "7457dc574d927e5dae84b05264a5b637b5a68e34a85b3965084ed6fed5b7f12d", - "E005ABD242C7C602AB5EED080C5083C7C5F8DAEC6D046A54F384A8B8CDECF740", - "51070ABCA039DAC294F6BA3BFC8C36CFC66020EDF66D1ACF1A9B545B0BF09F52", - "330A924525EF722FA20E8E25CB6E8BD7DF4394886FA4414E4A0B6812AA25BBC0", - }, - { - "funny", - "52c395a6d304de1a959e73e4604e32c5ad3f2bf01c8f730af426b38d7d5dd908", - "0CF28B5C40A8830F3195BB99A9F0E2808F576105F41D16ABCF596AC5A8CFE88A", - "3D60FB4664C994AD956378B9402BC68F7B4799D74F4783A6199C0D74865EA2B6", - "5ED5EDEE0314DFFBEE39EE4E9C76DE8BC3EB8CB891AEC32B83957514284B205B", - }, - { - "What is great in man is that he is a bridge and not a goal", - "52c395a6d304de1a959e73e4604e32c5ad3f2bf01c8f730af426b38d7d5dd908", - "000000000000000000000000000000000000000000000000000000000000007B", - "546F70AA1FEE3718C95508240CDC073B9FEFED05959C5319DD8E2BF07A1DD028", - "B8667BE5E10B113608BFE5327C44E9F0462BE26F789177E10DCE53019AA33DAA", - }, - { - "123456789147258369qwertyuiopasdfghjklzxcvbnm,", - "2685adffdbb4b2c515054cffc25cfcbfe2e462df65bbe82fb50f71e1e68dd285", - "1D0CB70310C4D793A4561FE592B7C156771E3E26283B28AB588E968243B52DD0", - "54D7A435E5E3F2811AA542F8895C20CCB760F2713DBDDB7291DAB6DA4E4F927E", - "20A3BDABFFF2C1BF8E2AF709F6CDCAFE70DA9A1DBC22305B6332E36844092984", - }, - { - "11111111111111111111111111111111111111111111111111111111111111111" - "11111111111111111111111111111111111111111111111111111111111111111" - "111111111111111111", - "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", - "A669F372B3C2EEA351210082CAEC3B96767A7B222D19FF2EE3D814860F0D703A", - "4890F9AC3A8D102EE3A2A473930C01CAD29DCE3860ACB7A5DADAEF16FE808991", - "979F088E58F1814D5E462CB9F935D2924ABD8D32211D8F02DD7E0991726DF573", - }, - { - "qwertyuiop[]asdfghjkl;'zxcvbnm,./1234567890-=", - "7457dc574d927e5dae84b05264a5b637b5a68e34a85b3965084ed6fed5b7f12d", - "000000000000000000000000000000000000000000000000000000000000007C", - "0AA595A649E517133D3448CA657424DD07BBED289030F0C0AA6738D26AB9A910", - "83812632F1443A70B198D112D075D886BE7BBC6EC6275AE52661E52B7358BB8B", - }, - }; - - const ecdsa_curve *curve = &secp256k1; - bignum256 k; - uint8_t priv_key[32]; - uint8_t pub_key[33]; - uint8_t buf_raw[32]; - schnorr_sign_pair result; - schnorr_sign_pair expected; - int res; - - for (size_t i = 0; i < sizeof(test_cases) / sizeof(*test_cases); i++) { - memcpy(priv_key, fromhex(test_cases[i].priv_key), 32); - memcpy(&buf_raw, fromhex(test_cases[i].k_hex), 32); - bn_read_be(buf_raw, &k); - schnorr_sign(curve, priv_key, &k, (const uint8_t *)test_cases[i].message, - strlen(test_cases[i].message), &result); - - memcpy(&expected.s, fromhex(test_cases[i].s_hex), 32); - memcpy(&expected.r, fromhex(test_cases[i].r_hex), 32); - - ck_assert_mem_eq(&expected.r, &result.r, 32); - ck_assert_mem_eq(&expected.s, &result.s, 32); - - ecdsa_get_public_key33(curve, priv_key, pub_key); - res = schnorr_verify(curve, pub_key, (const uint8_t *)test_cases[i].message, - strlen(test_cases[i].message), &result); - ck_assert_int_eq(res, 0); - } -} -END_TEST - -START_TEST(test_schnorr_fail_verify) { - static struct { - const char *message; - const char *priv_key; - const char *k_hex; - const char *s_hex; - const char *r_hex; - } test_case = { - "123", - "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", - "669301F724C555D7BB1185C04909E9CACA3EC7A292B3A1C92DDCCD5A5A7DDDD3", - "FFD72C290B98C93A4BCEDC0EDCDF040C35579BE962FE83E6821D4F3CB4B795D2", - "74AAE9C3E069E2806E1B0D890970BE387AEBED8040F37991AACAD70B27895E39", - }; - - const ecdsa_curve *curve = &secp256k1; - bignum256 k; - bignum256 bn_temp; - uint8_t priv_key[32]; - uint8_t pub_key[33]; - uint8_t buf_raw[32]; - schnorr_sign_pair result; - schnorr_sign_pair bad_result; - int res; - - memcpy(priv_key, fromhex(test_case.priv_key), 32); - memcpy(&buf_raw, fromhex(test_case.k_hex), 32); - bn_read_be(buf_raw, &k); - - schnorr_sign(curve, priv_key, &k, (const uint8_t *)test_case.message, - strlen(test_case.message), &result); - - ecdsa_get_public_key33(curve, priv_key, pub_key); - - // Test result = 0 (OK) - res = schnorr_verify(curve, pub_key, (const uint8_t *)test_case.message, - strlen(test_case.message), &result); - ck_assert_int_eq(res, 0); - - // Test result = 1 (empty message) - res = schnorr_verify(curve, pub_key, (const uint8_t *)test_case.message, 0, - &result); - ck_assert_int_eq(res, 1); - - // Test result = 2 (r = 0) - bn_zero(&bn_temp); - bn_write_be(&bn_temp, bad_result.r); - memcpy(bad_result.s, result.s, 32); - res = schnorr_verify(curve, pub_key, (const uint8_t *)test_case.message, - strlen(test_case.message), &bad_result); - ck_assert_int_eq(res, 2); - - // Test result = 3 (s = 0) - memcpy(bad_result.r, result.r, 32); - bn_zero(&bn_temp); - bn_write_be(&bn_temp, bad_result.s); - res = schnorr_verify(curve, pub_key, (const uint8_t *)test_case.message, - strlen(test_case.message), &bad_result); - ck_assert_int_eq(res, 3); - - // Test result = 4 (curve->order < r) - bn_copy(&curve->order, &bn_temp); - bn_addi(&bn_temp, 1); - bn_write_be(&bn_temp, bad_result.r); - memcpy(bad_result.s, result.s, 32); - res = schnorr_verify(curve, pub_key, (const uint8_t *)test_case.message, - strlen(test_case.message), &bad_result); - ck_assert_int_eq(res, 4); - - // Test result = 5 (curve->order < s) - memcpy(bad_result.r, result.r, 32); - bn_copy(&curve->order, &bn_temp); - bn_addi(&bn_temp, 1); - bn_write_be(&bn_temp, bad_result.s); - res = schnorr_verify(curve, pub_key, (const uint8_t *)test_case.message, - strlen(test_case.message), &bad_result); - ck_assert_int_eq(res, 5); - - // Test result = 6 (curve->order = r) - bn_copy(&curve->order, &bn_temp); - bn_write_be(&bn_temp, bad_result.r); - memcpy(bad_result.s, result.s, 32); - res = schnorr_verify(curve, pub_key, (const uint8_t *)test_case.message, - strlen(test_case.message), &bad_result); - ck_assert_int_eq(res, 6); - - // Test result = 7 (curve->order = s) - memcpy(bad_result.r, result.r, 32); - bn_copy(&curve->order, &bn_temp); - bn_write_be(&bn_temp, bad_result.s); - res = schnorr_verify(curve, pub_key, (const uint8_t *)test_case.message, - strlen(test_case.message), &bad_result); - ck_assert_int_eq(res, 7); - - // Test result = 8 (failed ecdsa_read_pubkey) - // TBD - - // Test result = 10 (r != r') - memcpy(bad_result.r, result.r, 32); - memcpy(bad_result.s, result.s, 32); - test_case.message = "12"; - res = schnorr_verify(curve, pub_key, (const uint8_t *)test_case.message, - strlen(test_case.message), &bad_result); - ck_assert_int_eq(res, 10); -} -END_TEST - static int my_strncasecmp(const char *s1, const char *s2, size_t n) { size_t i = 0; while (i < n) { @@ -8979,6 +9088,7 @@ static int my_strncasecmp(const char *s1, const char *s2, size_t n) { } #include "test_check_cashaddr.h" +#include "test_check_zilliqa.h" // [wallet-core] #if USE_SEGWIT #include "test_check_segwit.h" #endif @@ -9076,7 +9186,13 @@ Suite *test_suite(void) { suite_add_tcase(s, tc); tc = tcase_create("ecdsa"); - tcase_add_test(tc, test_ecdsa_signature); + tcase_add_test(tc, test_ecdsa_get_public_key33); + tcase_add_test(tc, test_ecdsa_get_public_key65); + tcase_add_test(tc, test_ecdsa_recover_pub_from_sig); + tcase_add_test(tc, test_ecdsa_verify_digest); +#if USE_RFC6979 + tcase_add_test(tc, test_ecdsa_sign_digest_deterministic); +#endif suite_add_tcase(s, tc); tc = tcase_create("rfc6979"); @@ -9131,6 +9247,7 @@ Suite *test_suite(void) { tc = tcase_create("blake2"); tcase_add_test(tc, test_blake2b); + tcase_add_test(tc, test_blake2bp); tcase_add_test(tc, test_blake2s); suite_add_tcase(s, tc); @@ -9154,7 +9271,6 @@ Suite *test_suite(void) { tcase_add_test(tc, test_mnemonic_find_word); suite_add_tcase(s, tc); -/* tc = tcase_create("slip39"); tcase_add_test(tc, test_slip39_get_word); tcase_add_test(tc, test_slip39_word_index); @@ -9162,7 +9278,6 @@ Suite *test_suite(void) { tcase_add_test(tc, test_slip39_sequence_to_word); tcase_add_test(tc, test_slip39_word_completion); suite_add_tcase(s, tc); -*/ tc = tcase_create("shamir"); tcase_add_test(tc, test_shamir); @@ -9290,6 +9405,10 @@ Suite *test_suite(void) { tcase_add_test(tc, test_bip32_cardano_hdnode_vector_8); tcase_add_test(tc, test_bip32_cardano_hdnode_vector_9); + tcase_add_test(tc, test_cardano_ledger_vector_1); + tcase_add_test(tc, test_cardano_ledger_vector_2); + tcase_add_test(tc, test_cardano_ledger_vector_3); + tcase_add_test(tc, test_ed25519_cardano_sign_vectors); suite_add_tcase(s, tc); #endif @@ -9332,16 +9451,9 @@ Suite *test_suite(void) { tcase_add_test(tc, test_xmr_get_subaddress_secret_key); tcase_add_test(tc, test_xmr_gen_c); tcase_add_test(tc, test_xmr_varint); - tcase_add_test(tc, test_xmr_gen_range_sig); suite_add_tcase(s, tc); #endif - // [wallet-core] - tc = tcase_create("schnorr"); - tcase_add_test(tc, test_schnorr_sign_verify); - tcase_add_test(tc, test_schnorr_fail_verify); - suite_add_tcase(s, tc); - return s; } diff --git a/trezor-crypto/crypto/tests/test_check_cardano.h b/trezor-crypto/crypto/tests/test_check_cardano.h index 4fc03cb3ee1..4f31a55309f 100644 --- a/trezor-crypto/crypto/tests/test_check_cardano.h +++ b/trezor-crypto/crypto/tests/test_check_cardano.h @@ -5,7 +5,7 @@ START_TEST(test_ed25519_cardano_sign_vectors) { ed25519_secret_key secret_key_extension; ed25519_signature signature; - const char *vectors[] = { + static const char *vectors[] = { "6065a956b1b34145c4416fdc3ba3276801850e91a77a31a7be782463288aea5" "3", // private key "60ba6e25b1a02157fb69c5d1d7b96c4619736e545447069a6a6f0ba90844bc8" @@ -89,14 +89,13 @@ START_TEST(test_ed25519_cardano_sign_vectors) { memcpy(secret_key_extension, fromhex(*(test_data + 1)), 32); MARK_SECRET_DATA(secret_key_extension, sizeof(secret_key_extension)); - ed25519_publickey_ext(secret_key, secret_key_extension, public_key); + ed25519_publickey_ext(secret_key, public_key); UNMARK_SECRET_DATA(public_key, sizeof(public_key)); ck_assert_mem_eq(public_key, fromhex(*(test_data + 2)), 32); const uint8_t *message = (const uint8_t *)"Hello World"; - ed25519_sign_ext(message, 11, secret_key, secret_key_extension, public_key, - signature); + ed25519_sign_ext(message, 11, secret_key, secret_key_extension, signature); UNMARK_SECRET_DATA(signature, sizeof(signature)); ck_assert_mem_eq(signature, fromhex(*(test_data + 3)), 64); @@ -113,14 +112,24 @@ START_TEST(test_bip32_cardano_hdnode_vector_1) { HDNode node; uint8_t mnemonic_bits[66]; + uint8_t cardano_secret[CARDANO_SECRET_LENGTH]; int mnemonic_bits_len = mnemonic_to_bits( "ring crime symptom enough erupt lady behave ramp apart settle citizen " "junk", mnemonic_bits); ck_assert_int_eq(mnemonic_bits_len, 132); - hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, - mnemonic_bits_len / 8, &node); + secret_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, + mnemonic_bits_len / 8, cardano_secret, + NULL); + hdnode_from_secret_cardano(cardano_secret, &node); + ck_assert_mem_eq( + cardano_secret, + fromhex( + "08a14df748e477a69d21c97c56db151fc19e2521f31dd0ac5360f269e5b6ea46" + "daeb991f2d2128e2525415c56a07f4366baa26c1e48572a5e073934b6de35fbc" + "affbc325d9027c0f2d9f925b1dcf6c12bf5c1dd08904474066a4f2c00db56173"), + 96); ck_assert_mem_eq( node.chain_code, fromhex( @@ -136,7 +145,7 @@ START_TEST(test_bip32_cardano_hdnode_vector_1) { fromhex( "daeb991f2d2128e2525415c56a07f4366baa26c1e48572a5e073934b6de35fbc"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key + 1, fromhex( @@ -149,15 +158,18 @@ START_TEST(test_bip32_cardano_hdnode_vector_2) { HDNode node; uint8_t mnemonic_bits[66]; + uint8_t cardano_secret[CARDANO_SECRET_LENGTH]; int mnemonic_bits_len = mnemonic_to_bits( "ring crime symptom enough erupt lady behave ramp apart settle citizen " "junk", mnemonic_bits); ck_assert_int_eq(mnemonic_bits_len, 132); - hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, - mnemonic_bits_len / 8, &node); + secret_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, + mnemonic_bits_len / 8, cardano_secret, + NULL); + hdnode_from_secret_cardano(cardano_secret, &node); - hdnode_private_ckd_cardano(&node, 0x80000000); + hdnode_private_ckd(&node, 0x80000000); ck_assert_mem_eq( node.chain_code, @@ -174,7 +186,7 @@ START_TEST(test_bip32_cardano_hdnode_vector_2) { fromhex( "64aa9a16331f14c981b769efcf96addcc4c6db44047fe7a7feae0be23d33bf54"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key + 1, fromhex( @@ -187,15 +199,18 @@ START_TEST(test_bip32_cardano_hdnode_vector_3) { HDNode node; uint8_t mnemonic_bits[66]; + uint8_t cardano_secret[CARDANO_SECRET_LENGTH]; int mnemonic_bits_len = mnemonic_to_bits( "ring crime symptom enough erupt lady behave ramp apart settle citizen " "junk", mnemonic_bits); ck_assert_int_eq(mnemonic_bits_len, 132); - hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, - mnemonic_bits_len / 8, &node); + secret_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, + mnemonic_bits_len / 8, cardano_secret, + NULL); + hdnode_from_secret_cardano(cardano_secret, &node); - hdnode_private_ckd_cardano(&node, 0x80000001); + hdnode_private_ckd(&node, 0x80000001); ck_assert_mem_eq( node.chain_code, @@ -212,7 +227,7 @@ START_TEST(test_bip32_cardano_hdnode_vector_3) { fromhex( "b4fc241feffe840b8a54a26ab447f5a5caa31032db3a8091fca14f38b86ed539"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key + 1, fromhex( @@ -225,16 +240,19 @@ START_TEST(test_bip32_cardano_hdnode_vector_4) { HDNode node; uint8_t mnemonic_bits[66]; + uint8_t cardano_secret[CARDANO_SECRET_LENGTH]; int mnemonic_bits_len = mnemonic_to_bits( "ring crime symptom enough erupt lady behave ramp apart settle citizen " "junk", mnemonic_bits); ck_assert_int_eq(mnemonic_bits_len, 132); - hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, - mnemonic_bits_len / 8, &node); + secret_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, + mnemonic_bits_len / 8, cardano_secret, + NULL); + hdnode_from_secret_cardano(cardano_secret, &node); - hdnode_private_ckd_cardano(&node, 0x80000000); - hdnode_private_ckd_cardano(&node, 0x80000001); + hdnode_private_ckd(&node, 0x80000000); + hdnode_private_ckd(&node, 0x80000001); ck_assert_mem_eq( node.chain_code, @@ -251,7 +269,7 @@ START_TEST(test_bip32_cardano_hdnode_vector_4) { fromhex( "a3071959013af95aaecf78a7a2e1b9838bbbc4864d6a8a2295243782078345cd"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key + 1, fromhex( @@ -264,17 +282,20 @@ START_TEST(test_bip32_cardano_hdnode_vector_5) { HDNode node; uint8_t mnemonic_bits[66]; + uint8_t cardano_secret[CARDANO_SECRET_LENGTH]; int mnemonic_bits_len = mnemonic_to_bits( "ring crime symptom enough erupt lady behave ramp apart settle citizen " "junk", mnemonic_bits); ck_assert_int_eq(mnemonic_bits_len, 132); - hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, - mnemonic_bits_len / 8, &node); + secret_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, + mnemonic_bits_len / 8, cardano_secret, + NULL); + hdnode_from_secret_cardano(cardano_secret, &node); - hdnode_private_ckd_cardano(&node, 0x80000000); - hdnode_private_ckd_cardano(&node, 0x80000001); - hdnode_private_ckd_cardano(&node, 0x80000002); + hdnode_private_ckd(&node, 0x80000000); + hdnode_private_ckd(&node, 0x80000001); + hdnode_private_ckd(&node, 0x80000002); ck_assert_mem_eq( node.chain_code, @@ -291,7 +312,7 @@ START_TEST(test_bip32_cardano_hdnode_vector_5) { fromhex( "5bebf1eea68acd04932653d944b064b10baaf5886dd73c185cc285059bf93363"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key + 1, fromhex( @@ -304,18 +325,21 @@ START_TEST(test_bip32_cardano_hdnode_vector_6) { HDNode node; uint8_t mnemonic_bits[66]; + uint8_t cardano_secret[CARDANO_SECRET_LENGTH]; int mnemonic_bits_len = mnemonic_to_bits( "ring crime symptom enough erupt lady behave ramp apart settle citizen " "junk", mnemonic_bits); ck_assert_int_eq(mnemonic_bits_len, 132); - hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, - mnemonic_bits_len / 8, &node); + secret_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, + mnemonic_bits_len / 8, cardano_secret, + NULL); + hdnode_from_secret_cardano(cardano_secret, &node); - hdnode_private_ckd_cardano(&node, 0x80000000); - hdnode_private_ckd_cardano(&node, 0x80000001); - hdnode_private_ckd_cardano(&node, 0x80000002); - hdnode_private_ckd_cardano(&node, 0x80000002); + hdnode_private_ckd(&node, 0x80000000); + hdnode_private_ckd(&node, 0x80000001); + hdnode_private_ckd(&node, 0x80000002); + hdnode_private_ckd(&node, 0x80000002); ck_assert_mem_eq( node.chain_code, @@ -332,7 +356,7 @@ START_TEST(test_bip32_cardano_hdnode_vector_6) { fromhex( "466332cb097934b43008701e7e27044aa56c7859019e4eba18d91a3bea23dff7"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key + 1, fromhex( @@ -345,19 +369,22 @@ START_TEST(test_bip32_cardano_hdnode_vector_7) { HDNode node; uint8_t mnemonic_bits[66]; + uint8_t cardano_secret[CARDANO_SECRET_LENGTH]; int mnemonic_bits_len = mnemonic_to_bits( "ring crime symptom enough erupt lady behave ramp apart settle citizen " "junk", mnemonic_bits); ck_assert_int_eq(mnemonic_bits_len, 132); - hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, - mnemonic_bits_len / 8, &node); + secret_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, + mnemonic_bits_len / 8, cardano_secret, + NULL); + hdnode_from_secret_cardano(cardano_secret, &node); - hdnode_private_ckd_cardano(&node, 0x80000000); - hdnode_private_ckd_cardano(&node, 0x80000001); - hdnode_private_ckd_cardano(&node, 0x80000002); - hdnode_private_ckd_cardano(&node, 0x80000002); - hdnode_private_ckd_cardano(&node, 0xBB9ACA00); + hdnode_private_ckd(&node, 0x80000000); + hdnode_private_ckd(&node, 0x80000001); + hdnode_private_ckd(&node, 0x80000002); + hdnode_private_ckd(&node, 0x80000002); + hdnode_private_ckd(&node, 0xBB9ACA00); ck_assert_mem_eq( node.chain_code, @@ -374,7 +401,7 @@ START_TEST(test_bip32_cardano_hdnode_vector_7) { fromhex( "01eccef768a79859f824a1d3c3e35e131184e2940c3fca9a4c9b307741f65363"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key + 1, fromhex( @@ -387,19 +414,22 @@ START_TEST(test_bip32_cardano_hdnode_vector_8) { HDNode node; uint8_t mnemonic_bits[66]; + uint8_t cardano_secret[CARDANO_SECRET_LENGTH]; int mnemonic_bits_len = mnemonic_to_bits( "found differ bulb shadow wrist blue bind vessel deposit tip pelican " "action surprise weapon check fiction muscle this", mnemonic_bits); ck_assert_int_eq(mnemonic_bits_len, 198); - hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, - mnemonic_bits_len / 8, &node); + secret_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, + mnemonic_bits_len / 8, cardano_secret, + NULL); + hdnode_from_secret_cardano(cardano_secret, &node); - hdnode_private_ckd_cardano(&node, 0x80000000); - hdnode_private_ckd_cardano(&node, 0x80000001); - hdnode_private_ckd_cardano(&node, 0x80000002); - hdnode_private_ckd_cardano(&node, 0x80000002); - hdnode_private_ckd_cardano(&node, 0xBB9ACA00); + hdnode_private_ckd(&node, 0x80000000); + hdnode_private_ckd(&node, 0x80000001); + hdnode_private_ckd(&node, 0x80000002); + hdnode_private_ckd(&node, 0x80000002); + hdnode_private_ckd(&node, 0xBB9ACA00); ck_assert_mem_eq( node.chain_code, @@ -416,7 +446,7 @@ START_TEST(test_bip32_cardano_hdnode_vector_8) { fromhex( "170e0d3b65ba8d71f27a6db60d0ac26dcb16e52e08cc259db72066f206b258d5"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key + 1, fromhex( @@ -429,20 +459,23 @@ START_TEST(test_bip32_cardano_hdnode_vector_9) { HDNode node; uint8_t mnemonic_bits[66]; + uint8_t cardano_secret[CARDANO_SECRET_LENGTH]; int mnemonic_bits_len = mnemonic_to_bits( "balance exotic ranch knife glory slow tape favorite yard gym awake " "ill exist useless parent aim pig stay effort into square gasp credit " "butter", mnemonic_bits); ck_assert_int_eq(mnemonic_bits_len, 264); - hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, - mnemonic_bits_len / 8, &node); + secret_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, + mnemonic_bits_len / 8, cardano_secret, + NULL); + hdnode_from_secret_cardano(cardano_secret, &node); - hdnode_private_ckd_cardano(&node, 0x80000000); - hdnode_private_ckd_cardano(&node, 0x80000001); - hdnode_private_ckd_cardano(&node, 0x80000002); - hdnode_private_ckd_cardano(&node, 0x80000002); - hdnode_private_ckd_cardano(&node, 0xBB9ACA00); + hdnode_private_ckd(&node, 0x80000000); + hdnode_private_ckd(&node, 0x80000001); + hdnode_private_ckd(&node, 0x80000002); + hdnode_private_ckd(&node, 0x80000002); + hdnode_private_ckd(&node, 0xBB9ACA00); ck_assert_mem_eq( node.chain_code, @@ -459,7 +492,7 @@ START_TEST(test_bip32_cardano_hdnode_vector_9) { fromhex( "80d2c677638e5dbd4395cdec279bf2a42077f2797c9e887949d37cdb317fce6a"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key + 1, fromhex( @@ -467,3 +500,72 @@ START_TEST(test_bip32_cardano_hdnode_vector_9) { 32); } END_TEST + +START_TEST(test_cardano_ledger_vector_1) { + uint8_t seed[512 / 8]; + uint8_t cardano_secret[CARDANO_SECRET_LENGTH]; + + const char *mnemonic = + "recall grace sport punch exhibit mad harbor stand obey " + "short width stem awkward used stairs wool ugly " + "trap season stove worth toward congress jaguar"; + + mnemonic_to_seed(mnemonic, "", seed, NULL); + const int res = + secret_from_seed_cardano_ledger(seed, sizeof(seed), cardano_secret); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq( + cardano_secret, + fromhex( + "a08cf85b564ecf3b947d8d4321fb96d70ee7bb760877e371899b14e2ccf88658" + "104b884682b57efd97decbb318a45c05a527b9cc5c2f64f7352935a049ceea60" + "680d52308194ccef2a18e6812b452a5815fbd7f5babc083856919aaf668fe7e4"), + CARDANO_SECRET_LENGTH); +} +END_TEST + +START_TEST(test_cardano_ledger_vector_2) { + uint8_t seed[512 / 8]; + uint8_t cardano_secret[CARDANO_SECRET_LENGTH]; + + const char *mnemonic = + "correct cherry mammal bubble want mandate polar hazard " + "crater better craft exotic choice fun tourist census " + "gap lottery neglect address glow carry old business"; + + mnemonic_to_seed(mnemonic, "", seed, NULL); + const int res = + secret_from_seed_cardano_ledger(seed, sizeof(seed), cardano_secret); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq( + cardano_secret, + fromhex( + "587c6774357ecbf840d4db6404ff7af016dace0400769751ad2abfc77b9a3844" + "cc71702520ef1a4d1b68b91187787a9b8faab0a9bb6b160de541b6ee62469901" + "fc0beda0975fe4763beabd83b7051a5fd5cbce5b88e82c4bbaca265014e524bd"), + CARDANO_SECRET_LENGTH); +} +END_TEST + +START_TEST(test_cardano_ledger_vector_3) { + uint8_t seed[512 / 8]; + uint8_t cardano_secret[CARDANO_SECRET_LENGTH]; + + const char *mnemonic = + "abandon abandon abandon abandon abandon abandon abandon abandon " + "abandon abandon abandon abandon abandon abandon abandon abandon " + "abandon abandon abandon abandon abandon abandon abandon art"; + + mnemonic_to_seed(mnemonic, "foo", seed, NULL); + const int res = + secret_from_seed_cardano_ledger(seed, sizeof(seed), cardano_secret); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq( + cardano_secret, + fromhex( + "f053a1e752de5c26197b60f032a4809f08bb3e5d90484fe42024be31efcba757" + "8d914d3ff992e21652fee6a4d99f6091006938fac2c0c0f9d2de0ba64b754e92" + "a4f3723f23472077aa4cd4dd8a8a175dba07ea1852dad1cf268c61a2679c3890"), + CARDANO_SECRET_LENGTH); +} +END_TEST diff --git a/trezor-crypto/crypto/tests/test_check_zilliqa.h b/trezor-crypto/crypto/tests/test_check_zilliqa.h new file mode 100644 index 00000000000..b8b16824b51 --- /dev/null +++ b/trezor-crypto/crypto/tests/test_check_zilliqa.h @@ -0,0 +1,214 @@ +#include +#include + +START_TEST(test_zil_schnorr_sign_verify) { + static struct { + const char *message; + const char *priv_key; + const char *k_hex; + const char *s_hex; + const char *r_hex; + } test_cases[] = { + { + "123", + "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", + "669301F724C555D7BB1185C04909E9CACA3EC7A292B3A1C92DDCCD5A5A7DDDD3", + "FFD72C290B98C93A4BCEDC0EDCDF040C35579BE962FE83E6821D4F3CB4B795D2", + "74AAE9C3E069E2806E1B0D890970BE387AEBED8040F37991AACAD70B27895E39", + }, + { + "1234", + "51a2758eed776c40b367364909c8a9c98cc969104f69ff316f7a287495c37c9b", + "A0A1A9B3570AAE963535B8D4376C58A61646C18182C9FDDA5FB13703F88D4D1E", + "99A0CB942C81571B77C682F79CD3CB663CE9E1C55BB425BA24B9F11A0DE84FE2", + "C3C10363E38158BBA20556A36DE9358DFD81A31C180ABC9E7617C1CC1CAF03B3", + }, + { + "12345", + "2685adffdbb4b2c515054cffc25cfcbfe2e462df65bbe82fb50f71e1e68dd285", + "38DE7B3315F201433D271E91FBE62966576CA05CBFEC1770B77D7EC9D6A01D6D", + "28982FA6C2B620CBC550F7EF9EAB605F409C584FBE5A765678877B79AB517086", + "9A0788E5B0947DEDEDE386DF57A006CF3FE43919A74D9CA630F8A1A9D97B4650", + }, + { + "fun", + "7457dc574d927e5dae84b05264a5b637b5a68e34a85b3965084ed6fed5b7f12d", + "E005ABD242C7C602AB5EED080C5083C7C5F8DAEC6D046A54F384A8B8CDECF740", + "51070ABCA039DAC294F6BA3BFC8C36CFC66020EDF66D1ACF1A9B545B0BF09F52", + "330A924525EF722FA20E8E25CB6E8BD7DF4394886FA4414E4A0B6812AA25BBC0", + }, + { + "funny", + "52c395a6d304de1a959e73e4604e32c5ad3f2bf01c8f730af426b38d7d5dd908", + "0CF28B5C40A8830F3195BB99A9F0E2808F576105F41D16ABCF596AC5A8CFE88A", + "3D60FB4664C994AD956378B9402BC68F7B4799D74F4783A6199C0D74865EA2B6", + "5ED5EDEE0314DFFBEE39EE4E9C76DE8BC3EB8CB891AEC32B83957514284B205B", + }, + { + "What is great in man is that he is a bridge and not a goal", + "52c395a6d304de1a959e73e4604e32c5ad3f2bf01c8f730af426b38d7d5dd908", + "000000000000000000000000000000000000000000000000000000000000007B", + "546F70AA1FEE3718C95508240CDC073B9FEFED05959C5319DD8E2BF07A1DD028", + "B8667BE5E10B113608BFE5327C44E9F0462BE26F789177E10DCE53019AA33DAA", + }, + { + "123456789147258369qwertyuiopasdfghjklzxcvbnm,", + "2685adffdbb4b2c515054cffc25cfcbfe2e462df65bbe82fb50f71e1e68dd285", + "1D0CB70310C4D793A4561FE592B7C156771E3E26283B28AB588E968243B52DD0", + "54D7A435E5E3F2811AA542F8895C20CCB760F2713DBDDB7291DAB6DA4E4F927E", + "20A3BDABFFF2C1BF8E2AF709F6CDCAFE70DA9A1DBC22305B6332E36844092984", + }, + { + "11111111111111111111111111111111111111111111111111111111111111111" + "11111111111111111111111111111111111111111111111111111111111111111" + "111111111111111111", + "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", + "A669F372B3C2EEA351210082CAEC3B96767A7B222D19FF2EE3D814860F0D703A", + "4890F9AC3A8D102EE3A2A473930C01CAD29DCE3860ACB7A5DADAEF16FE808991", + "979F088E58F1814D5E462CB9F935D2924ABD8D32211D8F02DD7E0991726DF573", + }, + { + "qwertyuiop[]asdfghjkl;'zxcvbnm,./1234567890-=", + "7457dc574d927e5dae84b05264a5b637b5a68e34a85b3965084ed6fed5b7f12d", + "000000000000000000000000000000000000000000000000000000000000007C", + "0AA595A649E517133D3448CA657424DD07BBED289030F0C0AA6738D26AB9A910", + "83812632F1443A70B198D112D075D886BE7BBC6EC6275AE52661E52B7358BB8B", + }, + }; + + const ecdsa_curve *curve = &secp256k1; + bignum256 k; + uint8_t priv_key[32]; + uint8_t pub_key[33]; + uint8_t buf_raw[32]; + schnorr_sign_pair result; + schnorr_sign_pair expected; + int res; + + for (size_t i = 0; i < sizeof(test_cases) / sizeof(*test_cases); i++) { + memcpy(priv_key, fromhex(test_cases[i].priv_key), 32); + memcpy(&buf_raw, fromhex(test_cases[i].k_hex), 32); + bn_read_be(buf_raw, &k); + zil_schnorr_sign_k(curve, priv_key, &k, (const uint8_t *)test_cases[i].message, + strlen(test_cases[i].message), &result); + + memcpy(&expected.s, fromhex(test_cases[i].s_hex), 32); + memcpy(&expected.r, fromhex(test_cases[i].r_hex), 32); + + ck_assert_mem_eq(&expected.r, &result.r, 32); + ck_assert_mem_eq(&expected.s, &result.s, 32); + + ecdsa_get_public_key33(curve, priv_key, pub_key); + res = zil_schnorr_verify_pair(curve, pub_key, (const uint8_t *)test_cases[i].message, + strlen(test_cases[i].message), &result); + ck_assert_int_eq(res, 0); + } +} +END_TEST + +START_TEST(test_zil_schnorr_fail_verify) { + static struct { + const char *message; + const char *priv_key; + const char *k_hex; + const char *s_hex; + const char *r_hex; + } test_case = { + "123", + "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", + "669301F724C555D7BB1185C04909E9CACA3EC7A292B3A1C92DDCCD5A5A7DDDD3", + "FFD72C290B98C93A4BCEDC0EDCDF040C35579BE962FE83E6821D4F3CB4B795D2", + "74AAE9C3E069E2806E1B0D890970BE387AEBED8040F37991AACAD70B27895E39", + }; + + const ecdsa_curve *curve = &secp256k1; + bignum256 k; + bignum256 bn_temp; + uint8_t priv_key[32]; + uint8_t pub_key[33]; + uint8_t buf_raw[32]; + schnorr_sign_pair result; + schnorr_sign_pair bad_result; + int res; + + memcpy(priv_key, fromhex(test_case.priv_key), 32); + memcpy(&buf_raw, fromhex(test_case.k_hex), 32); + bn_read_be(buf_raw, &k); + + zil_schnorr_sign_k(curve, priv_key, &k, (const uint8_t *)test_case.message, + strlen(test_case.message), &result); + + ecdsa_get_public_key33(curve, priv_key, pub_key); + + // Test result = 0 (OK) + res = zil_schnorr_verify_pair(curve, pub_key, (const uint8_t *)test_case.message, + strlen(test_case.message), &result); + ck_assert_int_eq(res, 0); + + // Test result = 1 (empty message) + res = zil_schnorr_verify_pair(curve, pub_key, (const uint8_t *)test_case.message, 0, + &result); + ck_assert_int_eq(res, 1); + + // Test result = 2 (r = 0) + bn_zero(&bn_temp); + bn_write_be(&bn_temp, bad_result.r); + memcpy(bad_result.s, result.s, 32); + res = zil_schnorr_verify_pair(curve, pub_key, (const uint8_t *)test_case.message, + strlen(test_case.message), &bad_result); + ck_assert_int_eq(res, 2); + + // Test result = 3 (s = 0) + memcpy(bad_result.r, result.r, 32); + bn_zero(&bn_temp); + bn_write_be(&bn_temp, bad_result.s); + res = zil_schnorr_verify_pair(curve, pub_key, (const uint8_t *)test_case.message, + strlen(test_case.message), &bad_result); + ck_assert_int_eq(res, 3); + + // Test result = 4 (curve->order < r) + bn_copy(&curve->order, &bn_temp); + bn_addi(&bn_temp, 1); + bn_write_be(&bn_temp, bad_result.r); + memcpy(bad_result.s, result.s, 32); + res = zil_schnorr_verify_pair(curve, pub_key, (const uint8_t *)test_case.message, + strlen(test_case.message), &bad_result); + ck_assert_int_eq(res, 4); + + // Test result = 5 (curve->order < s) + memcpy(bad_result.r, result.r, 32); + bn_copy(&curve->order, &bn_temp); + bn_addi(&bn_temp, 1); + bn_write_be(&bn_temp, bad_result.s); + res = zil_schnorr_verify_pair(curve, pub_key, (const uint8_t *)test_case.message, + strlen(test_case.message), &bad_result); + ck_assert_int_eq(res, 5); + + // Test result = 6 (curve->order = r) + bn_copy(&curve->order, &bn_temp); + bn_write_be(&bn_temp, bad_result.r); + memcpy(bad_result.s, result.s, 32); + res = zil_schnorr_verify_pair(curve, pub_key, (const uint8_t *)test_case.message, + strlen(test_case.message), &bad_result); + ck_assert_int_eq(res, 6); + + // Test result = 7 (curve->order = s) + memcpy(bad_result.r, result.r, 32); + bn_copy(&curve->order, &bn_temp); + bn_write_be(&bn_temp, bad_result.s); + res = zil_schnorr_verify_pair(curve, pub_key, (const uint8_t *)test_case.message, + strlen(test_case.message), &bad_result); + ck_assert_int_eq(res, 7); + + // Test result = 8 (failed ecdsa_read_pubkey) + // TBD + + // Test result = 10 (r != r') + memcpy(bad_result.r, result.r, 32); + memcpy(bad_result.s, result.s, 32); + test_case.message = "12"; + res = zil_schnorr_verify_pair(curve, pub_key, (const uint8_t *)test_case.message, + strlen(test_case.message), &bad_result); + ck_assert_int_eq(res, 10); +} +END_TEST diff --git a/trezor-crypto/crypto/tests/test_openssl.c b/trezor-crypto/crypto/tests/test_openssl.c index 4b38658f811..b9390846cb9 100644 --- a/trezor-crypto/crypto/tests/test_openssl.c +++ b/trezor-crypto/crypto/tests/test_openssl.c @@ -79,8 +79,15 @@ void openssl_check(unsigned int iterations, int nid, const ecdsa_curve *curve) { } // generate public key from private key - ecdsa_get_public_key33(curve, priv_key, pub_key33); - ecdsa_get_public_key65(curve, priv_key, pub_key65); + if (ecdsa_get_public_key33(curve, priv_key, pub_key33) != 0) { + printf("ecdsa_get_public_key33 failed\n"); + return; + } + + if (ecdsa_get_public_key65(curve, priv_key, pub_key65) != 0) { + printf("ecdsa_get_public_key65 failed\n"); + return; + } // use our ECDSA verifier to verify the message signature if (ecdsa_verify(curve, HASHER_SHA2, pub_key65, sig, msg, msg_len) != 0) { diff --git a/trezor-crypto/crypto/tests/test_speed.c b/trezor-crypto/crypto/tests/test_speed.c index 8a675eca576..a82f67f3e65 100644 --- a/trezor-crypto/crypto/tests/test_speed.c +++ b/trezor-crypto/crypto/tests/test_speed.c @@ -11,7 +11,7 @@ #include "nist256p1.h" #include -uint8_t msg[256]; +static uint8_t msg[256]; void prepare_msg(void) { for (size_t i = 0; i < sizeof(msg); i++) { @@ -50,18 +50,16 @@ void bench_sign_nist256p1(int iterations) { } void bench_sign_ed25519(int iterations) { - ed25519_public_key pk; ed25519_secret_key sk; ed25519_signature sig; - memcpy(pk, + memcpy(sk, "\xc5\x5e\xce\x85\x8b\x0d\xdd\x52\x63\xf9\x68\x10\xfe\x14\x43\x7c\xd3" "\xb5\xe1\xfb\xd7\xc6\xa2\xec\x1e\x03\x1f\x05\xe8\x6d\x8b\xd5", 32); - ed25519_publickey(sk, pk); for (int i = 0; i < iterations; i++) { - ed25519_sign(msg, sizeof(msg), sk, pk, sig); + ed25519_sign(msg, sizeof(msg), sk, sig); } } @@ -138,12 +136,12 @@ void bench_verify_ed25519(int iterations) { ed25519_secret_key sk; ed25519_signature sig; - memcpy(pk, + memcpy(sk, "\xc5\x5e\xce\x85\x8b\x0d\xdd\x52\x63\xf9\x68\x10\xfe\x14\x43\x7c\xd3" "\xb5\xe1\xfb\xd7\xc6\xa2\xec\x1e\x03\x1f\x05\xe8\x6d\x8b\xd5", 32); ed25519_publickey(sk, pk); - ed25519_sign(msg, sizeof(msg), sk, pk, sig); + ed25519_sign(msg, sizeof(msg), sk, sig); for (int i = 0; i < iterations; i++) { ed25519_sign_open(msg, sizeof(msg), pk, sig); @@ -169,7 +167,7 @@ void bench_multiply_curve25519(int iterations) { } } -HDNode root; +static HDNode root; void prepare_node(void) { hdnode_from_seed((uint8_t *)"NothingToSeeHere", 16, SECP256K1_NAME, &root); diff --git a/trezor-crypto/crypto/schnorr.c b/trezor-crypto/crypto/zilliqa.c similarity index 73% rename from trezor-crypto/crypto/schnorr.c rename to trezor-crypto/crypto/zilliqa.c index c37e48f75ba..5d30b56bf6c 100644 --- a/trezor-crypto/crypto/schnorr.c +++ b/trezor-crypto/crypto/zilliqa.c @@ -20,7 +20,53 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include +#include "string.h" + +#include +#include +#include +#include + +int zil_schnorr_sign(const ecdsa_curve *curve, const uint8_t *priv_key, const uint8_t *msg, const uint32_t msg_len, uint8_t *sig) +{ + int i; + bignum256 k; + + uint8_t hash[32]; + sha256_Raw(msg, msg_len, hash); + + rfc6979_state rng; + init_rfc6979(priv_key, hash, curve, &rng); + + for (i = 0; i < 10000; i++) { + // generate K deterministically + generate_k_rfc6979(&k, &rng); + // if k is too big or too small, we don't like it + if (bn_is_zero(&k) || !bn_is_less(&k, &curve->order)) { + continue; + } + + schnorr_sign_pair sign; + if (zil_schnorr_sign_k(curve, priv_key, &k, msg, msg_len, &sign) != 0) { + continue; + } + + // we're done + memcpy(sig, sign.r, 32); + memcpy(sig + 32, sign.s, 32); + + memzero(&k, sizeof(k)); + memzero(&rng, sizeof(rng)); + memzero(&sign, sizeof(sign)); + return 0; + } + + // Too many retries without a valid signature + // -> fail with an error + memzero(&k, sizeof(k)); + memzero(&rng, sizeof(rng)); + return -1; +} // r = H(Q, kpub, m) static void calc_r(const curve_point *Q, const uint8_t pub_key[33], @@ -41,7 +87,7 @@ static void calc_r(const curve_point *Q, const uint8_t pub_key[33], } // Returns 0 if signing succeeded -int schnorr_sign(const ecdsa_curve *curve, const uint8_t *priv_key, +int zil_schnorr_sign_k(const ecdsa_curve *curve, const uint8_t *priv_key, const bignum256 *k, const uint8_t *msg, const uint32_t msg_len, schnorr_sign_pair *result) { uint8_t pub_key[33]; @@ -89,8 +135,18 @@ int schnorr_sign(const ecdsa_curve *curve, const uint8_t *priv_key, return 0; } +int zil_schnorr_verify(const ecdsa_curve *curve, const uint8_t *pub_key, const uint8_t *sig, const uint8_t *msg, const uint32_t msg_len) +{ + schnorr_sign_pair sign; + + memcpy(sign.r, sig, 32); + memcpy(sign.s, sig + 32, 32); + + return zil_schnorr_verify_pair(curve, pub_key, msg, msg_len, &sign); +} + // Returns 0 if verification succeeded -int schnorr_verify(const ecdsa_curve *curve, const uint8_t *pub_key, +int zil_schnorr_verify_pair(const ecdsa_curve *curve, const uint8_t *pub_key, const uint8_t *msg, const uint32_t msg_len, const schnorr_sign_pair *sign) { curve_point pub_key_point; diff --git a/trezor-crypto/include/TrezorCrypto/aes.h b/trezor-crypto/include/TrezorCrypto/aes.h index 615f19db0da..62e9518a6a6 100644 --- a/trezor-crypto/include/TrezorCrypto/aes.h +++ b/trezor-crypto/include/TrezorCrypto/aes.h @@ -85,7 +85,7 @@ typedef union uint8_t b[4]; } aes_inf; -#ifdef _MSC_VER +#ifdef _MSC_VER //win Ignore the warning 4324 # pragma warning( disable : 4324 ) #endif diff --git a/trezor-crypto/include/TrezorCrypto/bip32.h b/trezor-crypto/include/TrezorCrypto/bip32.h index 99b35762469..4b698bcc600 100644 --- a/trezor-crypto/include/TrezorCrypto/bip32.h +++ b/trezor-crypto/include/TrezorCrypto/bip32.h @@ -79,14 +79,6 @@ int hdnode_from_seed(const uint8_t *seed, int seed_len, const char *curve, int hdnode_private_ckd(HDNode *inout, uint32_t i); -#if USE_CARDANO -int hdnode_private_ckd_cardano(HDNode *inout, uint32_t i); -int hdnode_from_seed_cardano(const uint8_t *seed, int seed_len, HDNode *out); -int hdnode_from_entropy_cardano_icarus(const uint8_t *pass, int pass_len, - const uint8_t *seed, int seed_len, - HDNode *out); -#endif - int hdnode_public_ckd_cp(const ecdsa_curve *curve, const curve_point *parent, const uint8_t *parent_chain_code, uint32_t i, curve_point *child, uint8_t *child_chain_code); @@ -101,13 +93,14 @@ void hdnode_public_ckd_address_optimized(const curve_point *pub, int addrsize, int addrformat); #if USE_BIP32_CACHE +void bip32_cache_clear(void); int hdnode_private_ckd_cached(HDNode *inout, const uint32_t *i, size_t i_count, uint32_t *fingerprint); #endif uint32_t hdnode_fingerprint(HDNode *node); -void hdnode_fill_public_key(HDNode *node); +int hdnode_fill_public_key(HDNode *node); #if USE_ETHEREUM int hdnode_get_ethereum_pubkeyhash(const HDNode *node, uint8_t *pubkeyhash); @@ -151,9 +144,9 @@ int hdnode_deserialize_private(const char *str, uint32_t version, const char *curve, HDNode *node, uint32_t *fingerprint); -void hdnode_get_address_raw(HDNode *node, uint32_t version, uint8_t *addr_raw); -void hdnode_get_address(HDNode *node, uint32_t version, char *addr, - int addrsize); +int hdnode_get_address_raw(HDNode *node, uint32_t version, uint8_t *addr_raw); +int hdnode_get_address(HDNode *node, uint32_t version, char *addr, + int addrsize); const curve_info *get_curve_by_name(const char *curve_name); diff --git a/trezor-crypto/include/TrezorCrypto/bip39.h b/trezor-crypto/include/TrezorCrypto/bip39.h index cd073a2d62e..46ec5b0229c 100644 --- a/trezor-crypto/include/TrezorCrypto/bip39.h +++ b/trezor-crypto/include/TrezorCrypto/bip39.h @@ -24,25 +24,30 @@ #ifndef __BIP39_H__ #define __BIP39_H__ -#include -#include - #ifdef __cplusplus extern "C" { #endif -#define BIP39_WORDS 2048 +#include +#include + +#include + +#define BIP39_WORD_COUNT 2048 #define BIP39_PBKDF2_ROUNDS 2048 -#define BIP39_MAX_WORDS 24 // [wallet-core] -#define BIP39_MAX_WORD_LENGTH 9 // [wallet-core] +#if USE_BIP39_CACHE +void bip39_cache_clear(void); +#endif + +// [wallet-core] +#define BIP39_MAX_WORDS 24 +#define BIP39_MAX_WORD_LENGTH 9 // [wallet-core] Added output buffer -const char *mnemonic_generate(int strength, char *buf, int buflen); // strength in bits -// [wallet-core] Added output buffer +const char *mnemonic_generate(int strength, char *buf, int buflen); // strength in bits const char *mnemonic_from_data(const uint8_t *data, int datalen, char *buf, int buflen); -// [wallet-core] No longer used -//void mnemonic_clear(void); +void mnemonic_clear(void); int mnemonic_check(const char *mnemonic); @@ -54,13 +59,13 @@ void mnemonic_to_seed(const char *mnemonic, const char *passphrase, void (*progress_callback)(uint32_t current, uint32_t total)); -#ifdef __cplusplus -} /* extern "C" */ -#endif - int mnemonic_find_word(const char *word); const char *mnemonic_complete_word(const char *prefix, int len); const char *mnemonic_get_word(int index); uint32_t mnemonic_word_completion_mask(const char *prefix, int len); +#ifdef __cplusplus +} /* extern "C" */ +#endif + #endif diff --git a/trezor-crypto/include/TrezorCrypto/cardano.h b/trezor-crypto/include/TrezorCrypto/cardano.h new file mode 100644 index 00000000000..3e9986c52bd --- /dev/null +++ b/trezor-crypto/include/TrezorCrypto/cardano.h @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2013-2021 SatoshiLabs + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __CARDANO_H__ +#define __CARDANO_H__ + +#if defined(__cplusplus) +extern "C" +{ +#endif + +#include +#include +#include +#include + +#if USE_CARDANO + +#define CARDANO_SECRET_LENGTH 96 +#define CARDANO_ICARUS_PBKDF2_ROUNDS 4096 + +extern const curve_info ed25519_cardano_info; + +int hdnode_private_ckd_cardano(HDNode *inout, uint32_t i); + +int secret_from_entropy_cardano_icarus( + const uint8_t *pass, int pass_len, const uint8_t *entropy, int entropy_len, + uint8_t secret_out[CARDANO_SECRET_LENGTH], + void (*progress_callback)(uint32_t current, uint32_t total)); +int secret_from_seed_cardano_ledger(const uint8_t *seed, int seed_len, + uint8_t secret_out[CARDANO_SECRET_LENGTH]); +int secret_from_seed_cardano_slip23(const uint8_t *seed, int seed_len, + uint8_t secret_out[CARDANO_SECRET_LENGTH]); + +int hdnode_from_secret_cardano(const uint8_t secret[CARDANO_SECRET_LENGTH], + HDNode *out); + +#endif // USE_CARDANO + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif // __CARDANO_H__ diff --git a/trezor-crypto/include/TrezorCrypto/chacha20poly1305/ecrypt-sync.h b/trezor-crypto/include/TrezorCrypto/chacha20poly1305/ecrypt-sync.h index 39be5c9bc88..efce9dde273 100644 --- a/trezor-crypto/include/TrezorCrypto/chacha20poly1305/ecrypt-sync.h +++ b/trezor-crypto/include/TrezorCrypto/chacha20poly1305/ecrypt-sync.h @@ -90,12 +90,21 @@ void ECRYPT_keysetup( * IV setup. After having called ECRYPT_keysetup(), the user is * allowed to call ECRYPT_ivsetup() different times in order to * encrypt/decrypt different messages with the same key but different - * IV's. + * IV's. ECRYPT_ivsetup() also sets block counter to zero. */ void ECRYPT_ivsetup( ECRYPT_ctx* ctx, const u8* iv); +/* + * Block counter setup. It is used only for special purposes, + * since block counter is usually initialized with ECRYPT_ivsetup. + * ECRYPT_ctrsetup has to be called after ECRYPT_ivsetup. + */ +void ECRYPT_ctrsetup( + ECRYPT_ctx* ctx, + const u8* ctr); + /* * Encryption/decryption of arbitrary length messages. * diff --git a/trezor-crypto/include/TrezorCrypto/chacha_drbg.h b/trezor-crypto/include/TrezorCrypto/chacha_drbg.h index 416ace82414..0676d126cd3 100644 --- a/trezor-crypto/include/TrezorCrypto/chacha_drbg.h +++ b/trezor-crypto/include/TrezorCrypto/chacha_drbg.h @@ -21,23 +21,34 @@ #define __CHACHA_DRBG__ #include +#include -// Very fast deterministic random bit generator inspired by CTR_DRBG in NIST SP -// 800-90A +// A very fast deterministic random bit generator based on CTR_DRBG in NIST SP +// 800-90A. Chacha is used instead of a block cipher in the counter mode, SHA256 +// is used as a derivation function. The highest supported security strength is +// at least 256 bits. Reseeding is left up to caller. -#define CHACHA_DRBG_KEY_LENGTH 16 -#define CHACHA_DRBG_IV_LENGTH 8 -#define CHACHA_DRBG_SEED_LENGTH (CHACHA_DRBG_KEY_LENGTH + CHACHA_DRBG_IV_LENGTH) +// Length of inputs of chacha_drbg_init (entropy and nonce) or +// chacha_drbg_reseed (entropy and additional_input) that fill exactly +// block_count blocks of hash function in derivation_function. There is no need +// the input to have this length, it's just an optimalization. +#define CHACHA_DRBG_OPTIMAL_RESEED_LENGTH(block_count) \ + ((block_count)*SHA256_BLOCK_LENGTH - 1 - 4 - 9) +// 1 = sizeof(counter), 4 = sizeof(output_length) in +// derivation_function, 9 is length of SHA256 padding of message +// aligned to bytes typedef struct _CHACHA_DRBG_CTX { ECRYPT_ctx chacha_ctx; uint32_t reseed_counter; } CHACHA_DRBG_CTX; -void chacha_drbg_init(CHACHA_DRBG_CTX *ctx, - const uint8_t entropy[CHACHA_DRBG_SEED_LENGTH]); -void chacha_drbg_reseed(CHACHA_DRBG_CTX *ctx, - const uint8_t entropy[CHACHA_DRBG_SEED_LENGTH]); +void chacha_drbg_init(CHACHA_DRBG_CTX *ctx, const uint8_t *entropy, + size_t entropy_length, const uint8_t *nonce, + size_t nonce_length); void chacha_drbg_generate(CHACHA_DRBG_CTX *ctx, uint8_t *output, - uint8_t output_length); + size_t output_length); +void chacha_drbg_reseed(CHACHA_DRBG_CTX *ctx, const uint8_t *entropy, + size_t entropy_length, const uint8_t *additional_input, + size_t additional_input_length); #endif // __CHACHA_DRBG__ diff --git a/trezor-crypto/include/TrezorCrypto/curves.h b/trezor-crypto/include/TrezorCrypto/curves.h index d65d1fe953f..d8d423563c6 100644 --- a/trezor-crypto/include/TrezorCrypto/curves.h +++ b/trezor-crypto/include/TrezorCrypto/curves.h @@ -35,16 +35,16 @@ extern const char SECP256K1_GROESTL_NAME[]; extern const char SECP256K1_SMART_NAME[]; extern const char NIST256P1_NAME[]; extern const char ED25519_NAME[]; -// [wallet-core] +extern const char ED25519_SEED_NAME[]; extern const char ED25519_CARDANO_NAME[]; -// [wallet-core] -extern const char ED25519_BLAKE2B_NANO_NAME[]; extern const char ED25519_SHA3_NAME[]; #if USE_KECCAK extern const char ED25519_KECCAK_NAME[]; #endif extern const char CURVE25519_NAME[]; +extern const char ED25519_BLAKE2B_NANO_NAME[]; // [wallet-core] + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/trezor-crypto/include/TrezorCrypto/ecdsa.h b/trezor-crypto/include/TrezorCrypto/ecdsa.h index 4d9046d5c82..48033642325 100644 --- a/trezor-crypto/include/TrezorCrypto/ecdsa.h +++ b/trezor-crypto/include/TrezorCrypto/ecdsa.h @@ -70,14 +70,14 @@ void point_copy(const curve_point *cp1, curve_point *cp2); void point_add(const ecdsa_curve *curve, const curve_point *cp1, curve_point *cp2); void point_double(const ecdsa_curve *curve, curve_point *cp); -void point_multiply(const ecdsa_curve *curve, const bignum256 *k, - const curve_point *p, curve_point *res); +int point_multiply(const ecdsa_curve *curve, const bignum256 *k, + const curve_point *p, curve_point *res); void point_set_infinity(curve_point *p); int point_is_infinity(const curve_point *p); int point_is_equal(const curve_point *p, const curve_point *q); int point_is_negative_of(const curve_point *p, const curve_point *q); -void scalar_multiply(const ecdsa_curve *curve, const bignum256 *k, - curve_point *res); +int scalar_multiply(const ecdsa_curve *curve, const bignum256 *k, + curve_point *res); int ecdh_multiply(const ecdsa_curve *curve, const uint8_t *priv_key, const uint8_t *pub_key, uint8_t *session_key); void compress_coords(const curve_point *cp, uint8_t *compressed); @@ -93,10 +93,10 @@ int ecdsa_sign(const ecdsa_curve *curve, HasherType hasher_sign, int ecdsa_sign_digest(const ecdsa_curve *curve, const uint8_t *priv_key, const uint8_t *digest, uint8_t *sig, uint8_t *pby, int (*is_canonical)(uint8_t by, uint8_t sig[64])); -void ecdsa_get_public_key33(const ecdsa_curve *curve, const uint8_t *priv_key, - uint8_t *pub_key); -void ecdsa_get_public_key65(const ecdsa_curve *curve, const uint8_t *priv_key, - uint8_t *pub_key); +int ecdsa_get_public_key33(const ecdsa_curve *curve, const uint8_t *priv_key, + uint8_t *pub_key); +int ecdsa_get_public_key65(const ecdsa_curve *curve, const uint8_t *priv_key, + uint8_t *pub_key); void ecdsa_get_pubkeyhash(const uint8_t *pub_key, HasherType hasher_pubkey, uint8_t *pubkeyhash); void ecdsa_get_address_raw(const uint8_t *pub_key, uint32_t version, @@ -130,10 +130,6 @@ int ecdsa_recover_pub_from_sig(const ecdsa_curve *curve, uint8_t *pub_key, int ecdsa_sig_to_der(const uint8_t *sig, uint8_t *der); int ecdsa_sig_from_der(const uint8_t *der, size_t der_len, uint8_t sig[64]); -// [wallet-core] -int zil_schnorr_sign(const ecdsa_curve *curve, const uint8_t *priv_key, const uint8_t *msg, const uint32_t msg_len, uint8_t *sig); -int zil_schnorr_verify(const ecdsa_curve *curve, const uint8_t *pub_key, const uint8_t *sig, const uint8_t *msg, const uint32_t msg_len); - #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-blake2b.h b/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-blake2b.h index 9dddc5f7401..f9bd619e93c 100644 --- a/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-blake2b.h +++ b/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-blake2b.h @@ -10,7 +10,7 @@ extern "C" { void ed25519_publickey_blake2b(const ed25519_secret_key sk, ed25519_public_key pk); int ed25519_sign_open_blake2b(const unsigned char *m, size_t mlen, const ed25519_public_key pk, const ed25519_signature RS); -void ed25519_sign_blake2b(const unsigned char *m, size_t mlen, const ed25519_secret_key sk, const ed25519_public_key pk, ed25519_signature RS); +void ed25519_sign_blake2b(const unsigned char *m, size_t mlen, const ed25519_secret_key sk, ed25519_signature RS); int ed25519_scalarmult_blake2b(ed25519_public_key res, const ed25519_secret_key sk, const ed25519_public_key pk); diff --git a/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-donna-portable.h b/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-donna-portable.h index a169fa41779..33dad64dcf1 100644 --- a/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-donna-portable.h +++ b/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-donna-portable.h @@ -10,12 +10,14 @@ extern "C" { #define DONNA_INLINE #undef ALIGN + #ifdef _MSC_VER #define ALIGN(x) __declspec(align(x)) #else #define ALIGN(x) __attribute__((aligned(x))) #endif + static inline void U32TO8_LE(unsigned char *p, const uint32_t v) { p[0] = (unsigned char)(v ); p[1] = (unsigned char)(v >> 8); diff --git a/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-keccak.h b/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-keccak.h index fa63770e92b..58fd8355ae4 100644 --- a/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-keccak.h +++ b/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-keccak.h @@ -10,7 +10,7 @@ extern "C" { void ed25519_publickey_keccak(const ed25519_secret_key sk, ed25519_public_key pk); int ed25519_sign_open_keccak(const unsigned char *m, size_t mlen, const ed25519_public_key pk, const ed25519_signature RS); -void ed25519_sign_keccak(const unsigned char *m, size_t mlen, const ed25519_secret_key sk, const ed25519_public_key pk, ed25519_signature RS); +void ed25519_sign_keccak(const unsigned char *m, size_t mlen, const ed25519_secret_key sk, ed25519_signature RS); int ed25519_scalarmult_keccak(ed25519_public_key res, const ed25519_secret_key sk, const ed25519_public_key pk); diff --git a/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-sha3.h b/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-sha3.h index e8a62d0dca3..3adcf84891f 100644 --- a/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-sha3.h +++ b/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-sha3.h @@ -10,7 +10,7 @@ extern "C" { void ed25519_publickey_sha3(const ed25519_secret_key sk, ed25519_public_key pk); int ed25519_sign_open_sha3(const unsigned char *m, size_t mlen, const ed25519_public_key pk, const ed25519_signature RS); -void ed25519_sign_sha3(const unsigned char *m, size_t mlen, const ed25519_secret_key sk, const ed25519_public_key pk, ed25519_signature RS); +void ed25519_sign_sha3(const unsigned char *m, size_t mlen, const ed25519_secret_key sk, ed25519_signature RS); int ed25519_scalarmult_sha3(ed25519_public_key res, const ed25519_secret_key sk, const ed25519_public_key pk); diff --git a/trezor-crypto/include/TrezorCrypto/ed25519.h b/trezor-crypto/include/TrezorCrypto/ed25519.h index 78a27a279ed..6b4c098b963 100644 --- a/trezor-crypto/include/TrezorCrypto/ed25519.h +++ b/trezor-crypto/include/TrezorCrypto/ed25519.h @@ -16,15 +16,11 @@ typedef unsigned char curve25519_key[32]; typedef unsigned char ed25519_cosi_signature[32]; void ed25519_publickey(const ed25519_secret_key sk, ed25519_public_key pk); -#if USE_CARDANO -void ed25519_publickey_ext(const ed25519_secret_key sk, const ed25519_secret_key skext, ed25519_public_key pk); -#endif +void ed25519_publickey_ext(const ed25519_secret_key extsk, ed25519_public_key pk); int ed25519_sign_open(const unsigned char *m, size_t mlen, const ed25519_public_key pk, const ed25519_signature RS); -void ed25519_sign(const unsigned char *m, size_t mlen, const ed25519_secret_key sk, const ed25519_public_key pk, ed25519_signature RS); -#if USE_CARDANO -void ed25519_sign_ext(const unsigned char *m, size_t mlen, const ed25519_secret_key sk, const ed25519_secret_key skext, const ed25519_public_key pk, ed25519_signature RS); -#endif +void ed25519_sign(const unsigned char *m, size_t mlen, const ed25519_secret_key sk, ed25519_signature RS); +void ed25519_sign_ext(const unsigned char *m, size_t mlen, const ed25519_secret_key sk, const ed25519_secret_key skext, ed25519_signature RS); int ed25519_scalarmult(ed25519_public_key res, const ed25519_secret_key sk, const ed25519_public_key pk); diff --git a/trezor-crypto/include/TrezorCrypto/groestl_internal.h b/trezor-crypto/include/TrezorCrypto/groestl_internal.h index a6cd8df4ba5..84587358e4a 100644 --- a/trezor-crypto/include/TrezorCrypto/groestl_internal.h +++ b/trezor-crypto/include/TrezorCrypto/groestl_internal.h @@ -58,8 +58,8 @@ #error This code requires 8-bit bytes #endif -#if (defined __STDC__ && __STDC_VERSION__ >= 199901L) || defined(_MSC_VER) - +//win #if defined __STDC__ && __STDC_VERSION__ >= 199901L +#if (defined __STDC__ && __STDC_VERSION__ >= 199901L) || defined(_MSC_VER) // win #include typedef uint32_t sph_u32; @@ -116,6 +116,7 @@ typedef int64_t sph_s64; #endif +#define SPH_LITTLE_ENDIAN 1 // [wallet-core] #if defined SPH_DETECT_LITTLE_ENDIAN && !defined SPH_LITTLE_ENDIAN #define SPH_LITTLE_ENDIAN SPH_DETECT_LITTLE_ENDIAN diff --git a/trezor-crypto/include/TrezorCrypto/nist256p1.h b/trezor-crypto/include/TrezorCrypto/nist256p1.h index 9464ca5b0a5..0c0a743d566 100644 --- a/trezor-crypto/include/TrezorCrypto/nist256p1.h +++ b/trezor-crypto/include/TrezorCrypto/nist256p1.h @@ -29,6 +29,9 @@ #include "bip32.h" #include "ecdsa.h" +//extern const ecdsa_curve nist256p1; +//extern const curve_info nist256p1_info; +//win #ifdef __cplusplus extern "C" { #endif @@ -39,5 +42,4 @@ extern const curve_info nist256p1_info; #ifdef __cplusplus } /* extern "C" */ #endif - #endif diff --git a/trezor-crypto/include/TrezorCrypto/rand.h b/trezor-crypto/include/TrezorCrypto/rand.h index 40844b6848c..7171a9ad860 100644 --- a/trezor-crypto/include/TrezorCrypto/rand.h +++ b/trezor-crypto/include/TrezorCrypto/rand.h @@ -31,10 +31,12 @@ extern "C" { #endif +//win // [wallet-core] Reference counted init and release void *random_init(void); void random_release(void); + uint32_t random32(void); void random_buffer(uint8_t *buf, size_t len); diff --git a/trezor-crypto/include/TrezorCrypto/rfc6979.h b/trezor-crypto/include/TrezorCrypto/rfc6979.h index 3e409535093..e4cb9ff049f 100644 --- a/trezor-crypto/include/TrezorCrypto/rfc6979.h +++ b/trezor-crypto/include/TrezorCrypto/rfc6979.h @@ -27,13 +27,14 @@ #include #include "bignum.h" +#include "ecdsa.h" #include "hmac_drbg.h" // rfc6979 pseudo random number generator state typedef HMAC_DRBG_CTX rfc6979_state; void init_rfc6979(const uint8_t *priv_key, const uint8_t *hash, - rfc6979_state *rng); + const ecdsa_curve *curve, rfc6979_state *rng); void generate_rfc6979(uint8_t rnd[32], rfc6979_state *rng); void generate_k_rfc6979(bignum256 *k, rfc6979_state *rng); diff --git a/trezor-crypto/include/TrezorCrypto/slip39.h b/trezor-crypto/include/TrezorCrypto/slip39.h new file mode 100644 index 00000000000..08883edf2b5 --- /dev/null +++ b/trezor-crypto/include/TrezorCrypto/slip39.h @@ -0,0 +1,47 @@ +/** + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __SLIP39_H__ +#define __SLIP39_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +const char* get_word(uint16_t index); + +bool word_index(uint16_t* index, const char* word, uint8_t word_length); + +uint16_t slip39_word_completion_mask(uint16_t prefix); + +const char* button_sequence_to_word(uint16_t prefix); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/trezor-crypto/include/TrezorCrypto/slip39_wordlist.h b/trezor-crypto/include/TrezorCrypto/slip39_wordlist.h new file mode 100644 index 00000000000..3464aae9412 --- /dev/null +++ b/trezor-crypto/include/TrezorCrypto/slip39_wordlist.h @@ -0,0 +1,1246 @@ +/** + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __SLIP39_WORDLIST_H__ +#define __SLIP39_WORDLIST_H__ + +#include + +#define WORDS_COUNT 1024 + +static const char* const slip39_wordlist[WORDS_COUNT] = { + "academic", "acid", "acne", "acquire", "acrobat", "activity", + "actress", "adapt", "adequate", "adjust", "admit", "adorn", + "adult", "advance", "advocate", "afraid", "again", "agency", + "agree", "aide", "aircraft", "airline", "airport", "ajar", + "alarm", "album", "alcohol", "alien", "alive", "alpha", + "already", "alto", "aluminum", "always", "amazing", "ambition", + "amount", "amuse", "analysis", "anatomy", "ancestor", "ancient", + "angel", "angry", "animal", "answer", "antenna", "anxiety", + "apart", "aquatic", "arcade", "arena", "argue", "armed", + "artist", "artwork", "aspect", "auction", "august", "aunt", + "average", "aviation", "avoid", "award", "away", "axis", + "axle", "beam", "beard", "beaver", "become", "bedroom", + "behavior", "being", "believe", "belong", "benefit", "best", + "beyond", "bike", "biology", "birthday", "bishop", "black", + "blanket", "blessing", "blimp", "blind", "blue", "body", + "bolt", "boring", "born", "both", "boundary", "bracelet", + "branch", "brave", "breathe", "briefing", "broken", "brother", + "browser", "bucket", "budget", "building", "bulb", "bulge", + "bumpy", "bundle", "burden", "burning", "busy", "buyer", + "cage", "calcium", "camera", "campus", "canyon", "capacity", + "capital", "capture", "carbon", "cards", "careful", "cargo", + "carpet", "carve", "category", "cause", "ceiling", "center", + "ceramic", "champion", "change", "charity", "check", "chemical", + "chest", "chew", "chubby", "cinema", "civil", "class", + "clay", "cleanup", "client", "climate", "clinic", "clock", + "clogs", "closet", "clothes", "club", "cluster", "coal", + "coastal", "coding", "column", "company", "corner", "costume", + "counter", "course", "cover", "cowboy", "cradle", "craft", + "crazy", "credit", "cricket", "criminal", "crisis", "critical", + "crowd", "crucial", "crunch", "crush", "crystal", "cubic", + "cultural", "curious", "curly", "custody", "cylinder", "daisy", + "damage", "dance", "darkness", "database", "daughter", "deadline", + "deal", "debris", "debut", "decent", "decision", "declare", + "decorate", "decrease", "deliver", "demand", "density", "deny", + "depart", "depend", "depict", "deploy", "describe", "desert", + "desire", "desktop", "destroy", "detailed", "detect", "device", + "devote", "diagnose", "dictate", "diet", "dilemma", "diminish", + "dining", "diploma", "disaster", "discuss", "disease", "dish", + "dismiss", "display", "distance", "dive", "divorce", "document", + "domain", "domestic", "dominant", "dough", "downtown", "dragon", + "dramatic", "dream", "dress", "drift", "drink", "drove", + "drug", "dryer", "duckling", "duke", "duration", "dwarf", + "dynamic", "early", "earth", "easel", "easy", "echo", + "eclipse", "ecology", "edge", "editor", "educate", "either", + "elbow", "elder", "election", "elegant", "element", "elephant", + "elevator", "elite", "else", "email", "emerald", "emission", + "emperor", "emphasis", "employer", "empty", "ending", "endless", + "endorse", "enemy", "energy", "enforce", "engage", "enjoy", + "enlarge", "entrance", "envelope", "envy", "epidemic", "episode", + "equation", "equip", "eraser", "erode", "escape", "estate", + "estimate", "evaluate", "evening", "evidence", "evil", "evoke", + "exact", "example", "exceed", "exchange", "exclude", "excuse", + "execute", "exercise", "exhaust", "exotic", "expand", "expect", + "explain", "express", "extend", "extra", "eyebrow", "facility", + "fact", "failure", "faint", "fake", "false", "family", + "famous", "fancy", "fangs", "fantasy", "fatal", "fatigue", + "favorite", "fawn", "fiber", "fiction", "filter", "finance", + "findings", "finger", "firefly", "firm", "fiscal", "fishing", + "fitness", "flame", "flash", "flavor", "flea", "flexible", + "flip", "float", "floral", "fluff", "focus", "forbid", + "force", "forecast", "forget", "formal", "fortune", "forward", + "founder", "fraction", "fragment", "frequent", "freshman", "friar", + "fridge", "friendly", "frost", "froth", "frozen", "fumes", + "funding", "furl", "fused", "galaxy", "game", "garbage", + "garden", "garlic", "gasoline", "gather", "general", "genius", + "genre", "genuine", "geology", "gesture", "glad", "glance", + "glasses", "glen", "glimpse", "goat", "golden", "graduate", + "grant", "grasp", "gravity", "gray", "greatest", "grief", + "grill", "grin", "grocery", "gross", "group", "grownup", + "grumpy", "guard", "guest", "guilt", "guitar", "gums", + "hairy", "hamster", "hand", "hanger", "harvest", "have", + "havoc", "hawk", "hazard", "headset", "health", "hearing", + "heat", "helpful", "herald", "herd", "hesitate", "hobo", + "holiday", "holy", "home", "hormone", "hospital", "hour", + "huge", "human", "humidity", "hunting", "husband", "hush", + "husky", "hybrid", "idea", "identify", "idle", "image", + "impact", "imply", "improve", "impulse", "include", "income", + "increase", "index", "indicate", "industry", "infant", "inform", + "inherit", "injury", "inmate", "insect", "inside", "install", + "intend", "intimate", "invasion", "involve", "iris", "island", + "isolate", "item", "ivory", "jacket", "jerky", "jewelry", + "join", "judicial", "juice", "jump", "junction", "junior", + "junk", "jury", "justice", "kernel", "keyboard", "kidney", + "kind", "kitchen", "knife", "knit", "laden", "ladle", + "ladybug", "lair", "lamp", "language", "large", "laser", + "laundry", "lawsuit", "leader", "leaf", "learn", "leaves", + "lecture", "legal", "legend", "legs", "lend", "length", + "level", "liberty", "library", "license", "lift", "likely", + "lilac", "lily", "lips", "liquid", "listen", "literary", + "living", "lizard", "loan", "lobe", "location", "losing", + "loud", "loyalty", "luck", "lunar", "lunch", "lungs", + "luxury", "lying", "lyrics", "machine", "magazine", "maiden", + "mailman", "main", "makeup", "making", "mama", "manager", + "mandate", "mansion", "manual", "marathon", "march", "market", + "marvel", "mason", "material", "math", "maximum", "mayor", + "meaning", "medal", "medical", "member", "memory", "mental", + "merchant", "merit", "method", "metric", "midst", "mild", + "military", "mineral", "minister", "miracle", "mixed", "mixture", + "mobile", "modern", "modify", "moisture", "moment", "morning", + "mortgage", "mother", "mountain", "mouse", "move", "much", + "mule", "multiple", "muscle", "museum", "music", "mustang", + "nail", "national", "necklace", "negative", "nervous", "network", + "news", "nuclear", "numb", "numerous", "nylon", "oasis", + "obesity", "object", "observe", "obtain", "ocean", "often", + "olympic", "omit", "oral", "orange", "orbit", "order", + "ordinary", "organize", "ounce", "oven", "overall", "owner", + "paces", "pacific", "package", "paid", "painting", "pajamas", + "pancake", "pants", "papa", "paper", "parcel", "parking", + "party", "patent", "patrol", "payment", "payroll", "peaceful", + "peanut", "peasant", "pecan", "penalty", "pencil", "percent", + "perfect", "permit", "petition", "phantom", "pharmacy", "photo", + "phrase", "physics", "pickup", "picture", "piece", "pile", + "pink", "pipeline", "pistol", "pitch", "plains", "plan", + "plastic", "platform", "playoff", "pleasure", "plot", "plunge", + "practice", "prayer", "preach", "predator", "pregnant", "premium", + "prepare", "presence", "prevent", "priest", "primary", "priority", + "prisoner", "privacy", "prize", "problem", "process", "profile", + "program", "promise", "prospect", "provide", "prune", "public", + "pulse", "pumps", "punish", "puny", "pupal", "purchase", + "purple", "python", "quantity", "quarter", "quick", "quiet", + "race", "racism", "radar", "railroad", "rainbow", "raisin", + "random", "ranked", "rapids", "raspy", "reaction", "realize", + "rebound", "rebuild", "recall", "receiver", "recover", "regret", + "regular", "reject", "relate", "remember", "remind", "remove", + "render", "repair", "repeat", "replace", "require", "rescue", + "research", "resident", "response", "result", "retailer", "retreat", + "reunion", "revenue", "review", "reward", "rhyme", "rhythm", + "rich", "rival", "river", "robin", "rocky", "romantic", + "romp", "roster", "round", "royal", "ruin", "ruler", + "rumor", "sack", "safari", "salary", "salon", "salt", + "satisfy", "satoshi", "saver", "says", "scandal", "scared", + "scatter", "scene", "scholar", "science", "scout", "scramble", + "screw", "script", "scroll", "seafood", "season", "secret", + "security", "segment", "senior", "shadow", "shaft", "shame", + "shaped", "sharp", "shelter", "sheriff", "short", "should", + "shrimp", "sidewalk", "silent", "silver", "similar", "simple", + "single", "sister", "skin", "skunk", "slap", "slavery", + "sled", "slice", "slim", "slow", "slush", "smart", + "smear", "smell", "smirk", "smith", "smoking", "smug", + "snake", "snapshot", "sniff", "society", "software", "soldier", + "solution", "soul", "source", "space", "spark", "speak", + "species", "spelling", "spend", "spew", "spider", "spill", + "spine", "spirit", "spit", "spray", "sprinkle", "square", + "squeeze", "stadium", "staff", "standard", "starting", "station", + "stay", "steady", "step", "stick", "stilt", "story", + "strategy", "strike", "style", "subject", "submit", "sugar", + "suitable", "sunlight", "superior", "surface", "surprise", "survive", + "sweater", "swimming", "swing", "switch", "symbolic", "sympathy", + "syndrome", "system", "tackle", "tactics", "tadpole", "talent", + "task", "taste", "taught", "taxi", "teacher", "teammate", + "teaspoon", "temple", "tenant", "tendency", "tension", "terminal", + "testify", "texture", "thank", "that", "theater", "theory", + "therapy", "thorn", "threaten", "thumb", "thunder", "ticket", + "tidy", "timber", "timely", "ting", "tofu", "together", + "tolerate", "total", "toxic", "tracks", "traffic", "training", + "transfer", "trash", "traveler", "treat", "trend", "trial", + "tricycle", "trip", "triumph", "trouble", "true", "trust", + "twice", "twin", "type", "typical", "ugly", "ultimate", + "umbrella", "uncover", "undergo", "unfair", "unfold", "unhappy", + "union", "universe", "unkind", "unknown", "unusual", "unwrap", + "upgrade", "upstairs", "username", "usher", "usual", "valid", + "valuable", "vampire", "vanish", "various", "vegan", "velvet", + "venture", "verdict", "verify", "very", "veteran", "vexed", + "victim", "video", "view", "vintage", "violence", "viral", + "visitor", "visual", "vitamins", "vocal", "voice", "volume", + "voter", "voting", "walnut", "warmth", "warn", "watch", + "wavy", "wealthy", "weapon", "webcam", "welcome", "welfare", + "western", "width", "wildlife", "window", "wine", "wireless", + "wisdom", "withdraw", "wits", "wolf", "woman", "work", + "worthy", "wrap", "wrist", "writing", "wrote", "year", + "yelp", "yield", "yoga", "zero", +}; + +/** + * This array contains number representations of SLIP-39 words. + * These numbers are determined how the words were entered on a + * T9 keyboard with the following layout: + * ab (1) cd (2) ef (3) + * ghij (4) klm (5) nopq (6) + * rs (7) tuv (8) wxyz (9) + * + * Each word is uniquely defined by four buttons. + */ +static const struct { + uint16_t sequence; + uint16_t index; +} words_button_seq[WORDS_COUNT] = { + {1212, 0}, // academic + {1216, 7}, // adapt + {1236, 8}, // adequate + {1242, 1}, // acid + {1248, 9}, // adjust + {1254, 10}, // admit + {1263, 2}, // acne + {1267, 11}, // adorn + {1268, 3}, // acquire + {1276, 4}, // acrobat + {1281, 13}, // advance + {1284, 5}, // activity + {1285, 12}, // adult + {1286, 14}, // advocate + {1287, 6}, // actress + {1315, 67}, // beam + {1317, 68}, // beard + {1318, 69}, // beaver + {1326, 70}, // become + {1327, 71}, // bedroom + {1341, 72}, // behavior + {1346, 73}, // being + {1354, 74}, // believe + {1356, 75}, // belong + {1363, 76}, // benefit + {1371, 15}, // afraid + {1378, 77}, // best + {1396, 78}, // beyond + {1414, 16}, // again + {1417, 23}, // ajar + {1423, 19}, // aide + {1436, 17}, // agency + {1453, 79}, // bike + {1465, 80}, // biology + {1472, 20}, // aircraft + {1473, 18}, // agree + {1474, 82}, // bishop + {1475, 21}, // airline + {1476, 22}, // airport + {1478, 81}, // birthday + {1512, 83}, // black + {1514, 35}, // ambition + {1516, 84}, // blanket + {1517, 24}, // alarm + {1518, 25}, // album + {1519, 34}, // amazing + {1526, 26}, // alcohol + {1537, 85}, // blessing + {1543, 27}, // alien + {1545, 86}, // blimp + {1546, 87}, // blind + {1548, 28}, // alive + {1564, 29}, // alpha + {1568, 36}, // amount + {1573, 30}, // already + {1583, 88}, // blue + {1585, 32}, // aluminum + {1586, 31}, // alto + {1587, 37}, // amuse + {1591, 33}, // always + {1615, 38}, // analysis + {1617, 48}, // apart + {1618, 39}, // anatomy + {1623, 40}, // ancestor + {1624, 41}, // ancient + {1629, 89}, // body + {1643, 42}, // angel + {1645, 44}, // animal + {1647, 43}, // angry + {1658, 90}, // bolt + {1674, 91}, // boring + {1676, 92}, // born + {1679, 45}, // answer + {1681, 49}, // aquatic + {1683, 46}, // antenna + {1684, 93}, // both + {1686, 94}, // boundary + {1694, 47}, // anxiety + {1712, 95}, // bracelet + {1716, 96}, // branch + {1718, 97}, // brave + {1721, 50}, // arcade + {1731, 98}, // breathe + {1736, 51}, // arena + {1743, 99}, // briefing + {1748, 52}, // argue + {1753, 53}, // armed + {1763, 56}, // aspect + {1765, 100}, // broken + {1768, 101}, // brother + {1769, 102}, // browser + {1784, 54}, // artist + {1789, 55}, // artwork + {1824, 104}, // budget + {1825, 103}, // bucket + {1828, 57}, // auction + {1837, 60}, // average + {1841, 61}, // aviation + {1845, 105}, // building + {1848, 58}, // august + {1851, 106}, // bulb + {1854, 107}, // bulge + {1856, 108}, // bumpy + {1862, 109}, // bundle + {1864, 62}, // avoid + {1868, 59}, // aunt + {1872, 110}, // burden + {1876, 111}, // burning + {1879, 112}, // busy + {1893, 113}, // buyer + {1917, 63}, // award + {1919, 64}, // away + {1947, 65}, // axis + {1953, 66}, // axle + {2143, 114}, // cage + {2147, 185}, // daisy + {2151, 186}, // damage + {2152, 115}, // calcium + {2153, 116}, // camera + {2156, 117}, // campus + {2161, 119}, // capacity + {2162, 187}, // dance + {2164, 120}, // capital + {2168, 121}, // capture + {2169, 118}, // canyon + {2171, 122}, // carbon + {2172, 123}, // cards + {2173, 124}, // careful + {2174, 125}, // cargo + {2175, 188}, // darkness + {2176, 126}, // carpet + {2178, 127}, // carve + {2181, 189}, // database + {2183, 128}, // category + {2184, 190}, // daughter + {2187, 129}, // cause + {2312, 191}, // deadline + {2315, 192}, // deal + {2317, 193}, // debris + {2318, 194}, // debut + {2323, 195}, // decent + {2324, 196}, // decision + {2325, 197}, // declare + {2326, 198}, // decorate + {2327, 199}, // decrease + {2345, 130}, // ceiling + {2351, 201}, // demand + {2354, 200}, // deliver + {2361, 204}, // depart + {2363, 205}, // depend + {2364, 206}, // depict + {2365, 207}, // deploy + {2367, 202}, // density + {2368, 131}, // center + {2369, 203}, // deny + {2371, 132}, // ceramic + {2372, 208}, // describe + {2373, 209}, // desert + {2374, 210}, // desire + {2375, 211}, // desktop + {2378, 212}, // destroy + {2381, 213}, // detailed + {2383, 214}, // detect + {2384, 215}, // device + {2386, 216}, // devote + {2414, 217}, // diagnose + {2415, 133}, // champion + {2416, 134}, // change + {2417, 135}, // charity + {2428, 218}, // dictate + {2432, 136}, // check + {2435, 137}, // chemical + {2437, 138}, // chest + {2438, 219}, // diet + {2439, 139}, // chew + {2453, 220}, // dilemma + {2454, 221}, // diminish + {2463, 141}, // cinema + {2464, 222}, // dining + {2465, 223}, // diploma + {2471, 224}, // disaster + {2472, 225}, // discuss + {2473, 226}, // disease + {2474, 227}, // dish + {2475, 228}, // dismiss + {2476, 229}, // display + {2478, 230}, // distance + {2481, 140}, // chubby + {2483, 231}, // dive + {2484, 142}, // civil + {2486, 232}, // divorce + {2517, 143}, // class + {2519, 144}, // clay + {2531, 145}, // cleanup + {2543, 146}, // client + {2545, 147}, // climate + {2546, 148}, // clinic + {2562, 149}, // clock + {2564, 150}, // clogs + {2567, 151}, // closet + {2568, 152}, // clothes + {2581, 153}, // club + {2587, 154}, // cluster + {2615, 155}, // coal + {2617, 156}, // coastal + {2624, 157}, // coding + {2628, 233}, // document + {2651, 234}, // domain + {2653, 235}, // domestic + {2654, 236}, // dominant + {2656, 159}, // company + {2658, 158}, // column + {2676, 160}, // corner + {2678, 161}, // costume + {2683, 164}, // cover + {2684, 237}, // dough + {2686, 162}, // counter + {2687, 163}, // course + {2691, 165}, // cowboy + {2696, 238}, // downtown + {2712, 166}, // cradle + {2713, 167}, // craft + {2714, 239}, // dragon + {2715, 240}, // dramatic + {2719, 168}, // crazy + {2731, 241}, // dream + {2732, 169}, // credit + {2737, 242}, // dress + {2742, 170}, // cricket + {2743, 243}, // drift + {2745, 171}, // criminal + {2746, 244}, // drink + {2747, 172}, // crisis + {2748, 173}, // critical + {2768, 245}, // drove + {2769, 174}, // crowd + {2782, 175}, // crucial + {2784, 246}, // drug + {2786, 176}, // crunch + {2787, 177}, // crush + {2793, 247}, // dryer + {2797, 178}, // crystal + {2814, 179}, // cubic + {2825, 248}, // duckling + {2853, 249}, // duke + {2858, 180}, // cultural + {2871, 250}, // duration + {2874, 181}, // curious + {2875, 182}, // curly + {2878, 183}, // custody + {2917, 251}, // dwarf + {2954, 184}, // cylinder + {2961, 252}, // dynamic + {3124, 323}, // facility + {3128, 324}, // fact + {3145, 325}, // failure + {3146, 326}, // faint + {3153, 327}, // fake + {3154, 329}, // family + {3156, 330}, // famous + {3157, 328}, // false + {3162, 331}, // fancy + {3164, 332}, // fangs + {3168, 333}, // fantasy + {3173, 255}, // easel + {3175, 253}, // early + {3178, 254}, // earth + {3179, 256}, // easy + {3181, 334}, // fatal + {3184, 335}, // fatigue + {3186, 336}, // favorite + {3196, 337}, // fawn + {3243, 260}, // edge + {3246, 257}, // echo + {3248, 261}, // editor + {3254, 258}, // eclipse + {3265, 259}, // ecology + {3282, 262}, // educate + {3413, 338}, // fiber + {3428, 339}, // fiction + {3458, 340}, // filter + {3461, 341}, // finance + {3462, 342}, // findings + {3464, 343}, // finger + {3472, 346}, // fiscal + {3473, 344}, // firefly + {3474, 347}, // fishing + {3475, 345}, // firm + {3484, 263}, // either + {3486, 348}, // fitness + {3514, 273}, // email + {3515, 349}, // flame + {3516, 264}, // elbow + {3517, 350}, // flash + {3518, 351}, // flavor + {3523, 265}, // elder + {3531, 352}, // flea + {3532, 266}, // election + {3534, 267}, // elegant + {3535, 268}, // element + {3536, 269}, // elephant + {3537, 274}, // emerald + {3538, 270}, // elevator + {3539, 353}, // flexible + {3546, 354}, // flip + {3547, 275}, // emission + {3548, 271}, // elite + {3561, 355}, // float + {3563, 276}, // emperor + {3564, 277}, // emphasis + {3565, 278}, // employer + {3567, 356}, // floral + {3568, 279}, // empty + {3573, 272}, // else + {3583, 357}, // fluff + {3624, 280}, // ending + {3625, 281}, // endless + {3626, 282}, // endorse + {3628, 358}, // focus + {3635, 283}, // enemy + {3636, 285}, // enforce + {3637, 284}, // energy + {3641, 286}, // engage + {3642, 292}, // epidemic + {3646, 287}, // enjoy + {3647, 293}, // episode + {3651, 288}, // enlarge + {3671, 359}, // forbid + {3672, 360}, // force + {3673, 361}, // forecast + {3674, 362}, // forget + {3675, 363}, // formal + {3678, 364}, // fortune + {3679, 365}, // forward + {3681, 294}, // equation + {3683, 290}, // envelope + {3684, 295}, // equip + {3686, 366}, // founder + {3687, 289}, // entrance + {3689, 291}, // envy + {3712, 367}, // fraction + {3714, 368}, // fragment + {3717, 296}, // eraser + {3721, 298}, // escape + {3736, 369}, // frequent + {3737, 370}, // freshman + {3741, 371}, // friar + {3742, 372}, // fridge + {3743, 373}, // friendly + {3762, 297}, // erode + {3767, 374}, // frost + {3768, 375}, // froth + {3769, 376}, // frozen + {3781, 299}, // estate + {3784, 300}, // estimate + {3815, 301}, // evaluate + {3836, 302}, // evening + {3842, 303}, // evidence + {3845, 304}, // evil + {3853, 377}, // fumes + {3862, 378}, // funding + {3865, 305}, // evoke + {3873, 380}, // fused + {3875, 379}, // furl + {3912, 306}, // exact + {3915, 307}, // example + {3923, 308}, // exceed + {3924, 309}, // exchange + {3925, 310}, // exclude + {3928, 311}, // excuse + {3931, 322}, // eyebrow + {3932, 312}, // execute + {3937, 313}, // exercise + {3941, 314}, // exhaust + {3961, 316}, // expand + {3963, 317}, // expect + {3965, 318}, // explain + {3967, 319}, // express + {3968, 315}, // exotic + {3983, 320}, // extend + {3987, 321}, // extra + {4125, 483}, // jacket + {4147, 420}, // hairy + {4151, 381}, // galaxy + {4153, 382}, // game + {4157, 421}, // hamster + {4162, 422}, // hand + {4164, 423}, // hanger + {4171, 383}, // garbage + {4172, 384}, // garden + {4175, 385}, // garlic + {4176, 386}, // gasoline + {4178, 424}, // harvest + {4183, 425}, // have + {4184, 387}, // gather + {4186, 426}, // havoc + {4191, 428}, // hazard + {4195, 427}, // hawk + {4231, 452}, // idea + {4236, 453}, // identify + {4253, 454}, // idle + {4312, 429}, // headset + {4315, 430}, // health + {4317, 431}, // hearing + {4318, 432}, // heat + {4356, 433}, // helpful + {4363, 388}, // general + {4364, 389}, // genius + {4365, 392}, // geology + {4367, 390}, // genre + {4368, 391}, // genuine + {4371, 434}, // herald + {4372, 435}, // herd + {4374, 436}, // hesitate + {4375, 484}, // jerky + {4378, 393}, // gesture + {4393, 485}, // jewelry + {4512, 394}, // glad + {4514, 455}, // image + {4516, 395}, // glance + {4517, 396}, // glasses + {4536, 397}, // glen + {4545, 398}, // glimpse + {4561, 456}, // impact + {4565, 457}, // imply + {4567, 458}, // improve + {4568, 459}, // impulse + {4616, 437}, // hobo + {4618, 399}, // goat + {4623, 463}, // index + {4624, 464}, // indicate + {4625, 460}, // include + {4626, 461}, // income + {4627, 462}, // increase + {4628, 465}, // industry + {4631, 466}, // infant + {4636, 467}, // inform + {4643, 468}, // inherit + {4646, 486}, // join + {4648, 469}, // injury + {4651, 470}, // inmate + {4652, 400}, // golden + {4653, 440}, // home + {4654, 438}, // holiday + {4659, 439}, // holy + {4673, 471}, // insect + {4674, 472}, // inside + {4675, 441}, // hormone + {4676, 442}, // hospital + {4678, 473}, // install + {4681, 476}, // invasion + {4683, 474}, // intend + {4684, 475}, // intimate + {4686, 477}, // involve + {4687, 443}, // hour + {4712, 401}, // graduate + {4716, 402}, // grant + {4717, 403}, // grasp + {4718, 404}, // gravity + {4719, 405}, // gray + {4731, 406}, // greatest + {4743, 407}, // grief + {4745, 408}, // grill + {4746, 409}, // grin + {4747, 478}, // iris + {4751, 479}, // island + {4762, 410}, // grocery + {4765, 480}, // isolate + {4767, 411}, // gross + {4768, 412}, // group + {4769, 413}, // grownup + {4785, 414}, // grumpy + {4817, 415}, // guard + {4824, 487}, // judicial + {4835, 481}, // item + {4837, 416}, // guest + {4842, 488}, // juice + {4843, 444}, // huge + {4845, 417}, // guilt + {4848, 418}, // guitar + {4851, 445}, // human + {4854, 446}, // humidity + {4856, 489}, // jump + {4857, 419}, // gums + {4862, 490}, // junction + {4864, 491}, // junior + {4865, 492}, // junk + {4867, 482}, // ivory + {4868, 447}, // hunting + {4871, 448}, // husband + {4874, 449}, // hush + {4875, 450}, // husky + {4878, 494}, // justice + {4879, 493}, // jury + {4917, 451}, // hybrid + {5123, 502}, // laden + {5124, 549}, // machine + {5125, 503}, // ladle + {5129, 504}, // ladybug + {5141, 550}, // magazine + {5142, 551}, // maiden + {5145, 552}, // mailman + {5146, 553}, // main + {5147, 505}, // lair + {5151, 556}, // mama + {5153, 554}, // makeup + {5154, 555}, // making + {5156, 506}, // lamp + {5161, 557}, // manager + {5162, 558}, // mandate + {5164, 507}, // language + {5167, 559}, // mansion + {5168, 560}, // manual + {5171, 561}, // marathon + {5172, 562}, // march + {5173, 509}, // laser + {5174, 508}, // large + {5175, 563}, // market + {5176, 565}, // mason + {5178, 564}, // marvel + {5183, 566}, // material + {5184, 567}, // math + {5186, 510}, // laundry + {5194, 568}, // maximum + {5196, 569}, // mayor + {5197, 511}, // lawsuit + {5312, 512}, // leader + {5313, 513}, // leaf + {5316, 570}, // meaning + {5317, 514}, // learn + {5318, 515}, // leaves + {5321, 571}, // medal + {5324, 572}, // medical + {5328, 516}, // lecture + {5341, 517}, // legal + {5343, 518}, // legend + {5347, 519}, // legs + {5351, 573}, // member + {5356, 574}, // memory + {5362, 520}, // lend + {5364, 521}, // length + {5368, 575}, // mental + {5372, 576}, // merchant + {5374, 577}, // merit + {5376, 495}, // kernel + {5383, 522}, // level + {5384, 578}, // method + {5387, 579}, // metric + {5391, 496}, // keyboard + {5413, 523}, // liberty + {5417, 524}, // library + {5423, 525}, // license + {5426, 497}, // kidney + {5427, 580}, // midst + {5438, 526}, // lift + {5451, 528}, // lilac + {5452, 581}, // mild + {5453, 527}, // likely + {5454, 582}, // military + {5459, 529}, // lily + {5462, 498}, // kind + {5463, 583}, // mineral + {5464, 584}, // minister + {5467, 530}, // lips + {5468, 531}, // liquid + {5471, 585}, // miracle + {5478, 532}, // listen + {5482, 499}, // kitchen + {5483, 533}, // literary + {5484, 534}, // living + {5491, 535}, // lizard + {5493, 586}, // mixed + {5498, 587}, // mixture + {5613, 537}, // lobe + {5614, 588}, // mobile + {5616, 536}, // loan + {5621, 538}, // location + {5623, 589}, // modern + {5624, 590}, // modify + {5643, 500}, // knife + {5647, 591}, // moisture + {5648, 501}, // knit + {5653, 592}, // moment + {5674, 539}, // losing + {5676, 593}, // morning + {5678, 594}, // mortgage + {5682, 540}, // loud + {5683, 598}, // move + {5684, 595}, // mother + {5686, 596}, // mountain + {5687, 597}, // mouse + {5691, 541}, // loyalty + {5824, 599}, // much + {5825, 542}, // luck + {5853, 600}, // mule + {5858, 601}, // multiple + {5861, 543}, // lunar + {5862, 544}, // lunch + {5864, 545}, // lungs + {5872, 602}, // muscle + {5873, 603}, // museum + {5874, 604}, // music + {5878, 605}, // mustang + {5898, 546}, // luxury + {5946, 547}, // lying + {5974, 548}, // lyrics + {6123, 636}, // paces + {6124, 637}, // pacific + {6125, 638}, // package + {6137, 618}, // obesity + {6141, 641}, // pajamas + {6142, 639}, // paid + {6143, 619}, // object + {6145, 606}, // nail + {6146, 640}, // painting + {6161, 644}, // papa + {6162, 642}, // pancake + {6163, 645}, // paper + {6168, 643}, // pants + {6172, 646}, // parcel + {6173, 620}, // observe + {6174, 617}, // oasis + {6175, 647}, // parking + {6178, 648}, // party + {6181, 621}, // obtain + {6183, 649}, // patent + {6184, 607}, // national + {6187, 650}, // patrol + {6195, 651}, // payment + {6197, 652}, // payroll + {6231, 622}, // ocean + {6312, 653}, // peaceful + {6316, 654}, // peanut + {6317, 655}, // peasant + {6321, 656}, // pecan + {6325, 608}, // necklace + {6341, 609}, // negative + {6361, 657}, // penalty + {6362, 658}, // pencil + {6372, 659}, // percent + {6373, 660}, // perfect + {6375, 661}, // permit + {6378, 610}, // nervous + {6383, 623}, // often + {6384, 662}, // petition + {6389, 611}, // network + {6397, 612}, // news + {6416, 663}, // phantom + {6417, 664}, // pharmacy + {6425, 668}, // pickup + {6428, 669}, // picture + {6432, 670}, // piece + {6453, 671}, // pile + {6463, 673}, // pipeline + {6465, 672}, // pink + {6468, 665}, // photo + {6471, 666}, // phrase + {6478, 674}, // pistol + {6482, 675}, // pitch + {6497, 667}, // physics + {6514, 676}, // plains + {6516, 677}, // plan + {6517, 678}, // plastic + {6518, 679}, // platform + {6519, 680}, // playoff + {6531, 681}, // pleasure + {6548, 625}, // omit + {6568, 682}, // plot + {6586, 683}, // plunge + {6595, 624}, // olympic + {6712, 684}, // practice + {6714, 628}, // orbit + {6715, 626}, // oral + {6716, 627}, // orange + {6719, 685}, // prayer + {6723, 629}, // order + {6724, 630}, // ordinary + {6731, 686}, // preach + {6732, 687}, // predator + {6734, 688}, // pregnant + {6735, 689}, // premium + {6736, 690}, // prepare + {6737, 691}, // presence + {6738, 692}, // prevent + {6741, 631}, // organize + {6743, 693}, // priest + {6745, 694}, // primary + {6746, 695}, // priority + {6747, 696}, // prisoner + {6748, 697}, // privacy + {6749, 698}, // prize + {6761, 699}, // problem + {6762, 700}, // process + {6763, 701}, // profile + {6764, 702}, // program + {6765, 703}, // promise + {6767, 704}, // prospect + {6768, 705}, // provide + {6786, 706}, // prune + {6815, 707}, // public + {6816, 716}, // quantity + {6817, 717}, // quarter + {6825, 613}, // nuclear + {6836, 633}, // oven + {6837, 634}, // overall + {6842, 718}, // quick + {6843, 719}, // quiet + {6851, 614}, // numb + {6853, 615}, // numerous + {6856, 709}, // pumps + {6857, 708}, // pulse + {6861, 712}, // pupal + {6862, 632}, // ounce + {6864, 710}, // punish + {6869, 711}, // puny + {6872, 713}, // purchase + {6876, 714}, // purple + {6956, 616}, // nylon + {6963, 635}, // owner + {6984, 715}, // python + {7121, 722}, // radar + {7123, 720}, // race + {7124, 721}, // racism + {7125, 775}, // sack + {7131, 776}, // safari + {7145, 723}, // railroad + {7146, 724}, // rainbow + {7147, 725}, // raisin + {7151, 777}, // salary + {7156, 778}, // salon + {7158, 779}, // salt + {7162, 726}, // random + {7164, 728}, // rapids + {7165, 727}, // ranked + {7176, 729}, // raspy + {7183, 782}, // saver + {7184, 780}, // satisfy + {7186, 781}, // satoshi + {7197, 783}, // says + {7216, 784}, // scandal + {7217, 785}, // scared + {7218, 786}, // scatter + {7236, 787}, // scene + {7243, 789}, // science + {7246, 788}, // scholar + {7268, 790}, // scout + {7271, 791}, // scramble + {7273, 792}, // screw + {7274, 793}, // script + {7276, 794}, // scroll + {7312, 730}, // reaction + {7313, 795}, // seafood + {7315, 731}, // realize + {7316, 732}, // rebound + {7317, 796}, // season + {7318, 733}, // rebuild + {7321, 734}, // recall + {7323, 735}, // receiver + {7326, 736}, // recover + {7327, 797}, // secret + {7328, 798}, // security + {7343, 739}, // reject + {7345, 799}, // segment + {7347, 737}, // regret + {7348, 738}, // regular + {7351, 740}, // relate + {7353, 741}, // remember + {7354, 742}, // remind + {7356, 743}, // remove + {7361, 745}, // repair + {7362, 744}, // render + {7363, 746}, // repeat + {7364, 800}, // senior + {7365, 747}, // replace + {7368, 748}, // require + {7372, 749}, // rescue + {7373, 750}, // research + {7374, 751}, // resident + {7376, 752}, // response + {7378, 753}, // result + {7381, 754}, // retailer + {7383, 757}, // revenue + {7384, 758}, // review + {7386, 756}, // reunion + {7387, 755}, // retreat + {7391, 759}, // reward + {7412, 801}, // shadow + {7413, 802}, // shaft + {7415, 803}, // shame + {7416, 804}, // shaped + {7417, 805}, // sharp + {7423, 811}, // sidewalk + {7424, 762}, // rich + {7435, 806}, // shelter + {7437, 807}, // sheriff + {7453, 812}, // silent + {7454, 814}, // similar + {7456, 815}, // simple + {7458, 813}, // silver + {7464, 816}, // single + {7467, 808}, // short + {7468, 809}, // should + {7474, 810}, // shrimp + {7478, 817}, // sister + {7481, 763}, // rival + {7483, 764}, // river + {7495, 760}, // rhyme + {7498, 761}, // rhythm + {7516, 820}, // slap + {7517, 827}, // smart + {7518, 821}, // slavery + {7531, 828}, // smear + {7532, 822}, // sled + {7535, 829}, // smell + {7542, 823}, // slice + {7545, 824}, // slim + {7546, 818}, // skin + {7547, 830}, // smirk + {7548, 831}, // smith + {7565, 832}, // smoking + {7569, 825}, // slow + {7584, 833}, // smug + {7586, 819}, // skunk + {7587, 826}, // slush + {7612, 843}, // space + {7614, 765}, // robin + {7615, 834}, // snake + {7616, 835}, // snapshot + {7617, 844}, // spark + {7624, 837}, // society + {7625, 766}, // rocky + {7631, 845}, // speak + {7632, 846}, // species + {7635, 847}, // spelling + {7636, 848}, // spend + {7638, 838}, // software + {7639, 849}, // spew + {7642, 850}, // spider + {7643, 836}, // sniff + {7645, 851}, // spill + {7646, 852}, // spine + {7647, 853}, // spirit + {7648, 854}, // spit + {7651, 767}, // romantic + {7652, 839}, // soldier + {7656, 768}, // romp + {7658, 840}, // solution + {7671, 855}, // spray + {7674, 856}, // sprinkle + {7678, 769}, // roster + {7681, 857}, // square + {7683, 858}, // squeeze + {7685, 841}, // soul + {7686, 770}, // round + {7687, 842}, // source + {7691, 771}, // royal + {7812, 859}, // stadium + {7813, 860}, // staff + {7814, 873}, // subject + {7815, 874}, // submit + {7816, 861}, // standard + {7817, 862}, // starting + {7818, 863}, // station + {7819, 864}, // stay + {7831, 865}, // steady + {7836, 866}, // step + {7841, 875}, // sugar + {7842, 867}, // stick + {7845, 868}, // stilt + {7846, 772}, // ruin + {7848, 876}, // suitable + {7853, 773}, // ruler + {7856, 774}, // rumor + {7863, 878}, // superior + {7865, 877}, // sunlight + {7867, 869}, // story + {7871, 870}, // strategy + {7873, 879}, // surface + {7874, 871}, // strike + {7876, 880}, // surprise + {7878, 881}, // survive + {7895, 872}, // style + {7931, 882}, // sweater + {7945, 883}, // swimming + {7946, 884}, // swing + {7948, 885}, // switch + {7951, 886}, // symbolic + {7956, 887}, // sympathy + {7962, 888}, // syndrome + {7978, 889}, // system + {8125, 890}, // tackle + {8126, 892}, // tadpole + {8128, 891}, // tactics + {8153, 893}, // talent + {8154, 965}, // valid + {8156, 967}, // vampire + {8158, 966}, // valuable + {8164, 968}, // vanish + {8174, 969}, // various + {8175, 894}, // task + {8178, 895}, // taste + {8184, 896}, // taught + {8194, 897}, // taxi + {8312, 898}, // teacher + {8315, 899}, // teammate + {8317, 900}, // teaspoon + {8341, 970}, // vegan + {8356, 901}, // temple + {8358, 971}, // velvet + {8361, 902}, // tenant + {8362, 903}, // tendency + {8367, 904}, // tension + {8368, 972}, // venture + {8372, 973}, // verdict + {8374, 974}, // verify + {8375, 905}, // terminal + {8378, 906}, // testify + {8379, 975}, // very + {8383, 976}, // veteran + {8393, 977}, // vexed + {8398, 907}, // texture + {8416, 908}, // thank + {8418, 909}, // that + {8423, 979}, // video + {8425, 917}, // ticket + {8428, 978}, // victim + {8429, 918}, // tidy + {8431, 910}, // theater + {8436, 911}, // theory + {8437, 912}, // therapy + {8439, 980}, // view + {8451, 919}, // timber + {8453, 920}, // timely + {8459, 946}, // ugly + {8464, 921}, // ting + {8465, 982}, // violence + {8467, 913}, // thorn + {8468, 981}, // vintage + {8471, 983}, // viral + {8473, 914}, // threaten + {8474, 984}, // visitor + {8478, 985}, // visual + {8481, 986}, // vitamins + {8485, 915}, // thumb + {8486, 916}, // thunder + {8517, 948}, // umbrella + {8584, 947}, // ultimate + {8621, 987}, // vocal + {8623, 950}, // undergo + {8626, 949}, // uncover + {8631, 951}, // unfair + {8636, 952}, // unfold + {8638, 922}, // tofu + {8641, 953}, // unhappy + {8642, 988}, // voice + {8643, 923}, // together + {8646, 954}, // union + {8647, 960}, // upgrade + {8648, 955}, // universe + {8653, 924}, // tolerate + {8654, 956}, // unkind + {8656, 957}, // unknown + {8658, 989}, // volume + {8678, 961}, // upstairs + {8681, 925}, // total + {8683, 990}, // voter + {8684, 991}, // voting + {8687, 958}, // unusual + {8694, 926}, // toxic + {8697, 959}, // unwrap + {8712, 927}, // tracks + {8713, 928}, // traffic + {8714, 929}, // training + {8716, 930}, // transfer + {8717, 931}, // trash + {8718, 932}, // traveler + {8731, 933}, // treat + {8736, 934}, // trend + {8737, 962}, // username + {8741, 935}, // trial + {8742, 936}, // tricycle + {8743, 963}, // usher + {8746, 937}, // trip + {8748, 938}, // triumph + {8768, 939}, // trouble + {8781, 964}, // usual + {8783, 940}, // true + {8787, 941}, // trust + {8942, 942}, // twice + {8946, 943}, // twin + {8963, 944}, // type + {8964, 945}, // typical + {9156, 992}, // walnut + {9175, 993}, // warmth + {9176, 994}, // warn + {9182, 995}, // watch + {9189, 996}, // wavy + {9312, 999}, // webcam + {9315, 997}, // wealthy + {9316, 998}, // weapon + {9317, 1019}, // year + {9352, 1000}, // welcome + {9353, 1001}, // welfare + {9356, 1020}, // yelp + {9376, 1023}, // zero + {9378, 1002}, // western + {9428, 1003}, // width + {9435, 1021}, // yield + {9452, 1004}, // wildlife + {9462, 1005}, // window + {9463, 1006}, // wine + {9472, 1008}, // wisdom + {9473, 1007}, // wireless + {9484, 1009}, // withdraw + {9487, 1010}, // wits + {9641, 1022}, // yoga + {9651, 1012}, // woman + {9653, 1011}, // wolf + {9675, 1013}, // work + {9678, 1014}, // worthy + {9716, 1015}, // wrap + {9747, 1016}, // wrist + {9748, 1017}, // writing + {9768, 1018}, // wrote +}; + +#endif diff --git a/trezor-crypto/include/TrezorCrypto/schnorr.h b/trezor-crypto/include/TrezorCrypto/zilliqa.h similarity index 77% rename from trezor-crypto/include/TrezorCrypto/schnorr.h rename to trezor-crypto/include/TrezorCrypto/zilliqa.h index 4091c807446..46ada660b06 100644 --- a/trezor-crypto/include/TrezorCrypto/schnorr.h +++ b/trezor-crypto/include/TrezorCrypto/zilliqa.h @@ -20,8 +20,8 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef __SCHNORR_H__ -#define __SCHNORR_H__ +#ifndef __ZILLIQA_H__ +#define __ZILLIQA_H__ #include @@ -38,11 +38,16 @@ typedef struct { // sign/verify returns 0 if operation succeeded +int zil_schnorr_sign(const ecdsa_curve *curve, const uint8_t *priv_key, + const uint8_t *msg, const uint32_t msg_len, uint8_t *sig); +int zil_schnorr_verify(const ecdsa_curve *curve, const uint8_t *pub_key, + const uint8_t *sig, const uint8_t *msg, const uint32_t msg_len); + // k is a random from [1, ..., order-1] -int schnorr_sign(const ecdsa_curve *curve, const uint8_t *priv_key, +int zil_schnorr_sign_k(const ecdsa_curve *curve, const uint8_t *priv_key, const bignum256 *k, const uint8_t *msg, const uint32_t msg_len, schnorr_sign_pair *result); -int schnorr_verify(const ecdsa_curve *curve, const uint8_t *pub_key, +int zil_schnorr_verify_pair(const ecdsa_curve *curve, const uint8_t *pub_key, const uint8_t *msg, const uint32_t msg_len, const schnorr_sign_pair *sign); #ifdef __cplusplus diff --git a/walletconsole/CMakeLists.txt b/walletconsole/CMakeLists.txt index 16f80e331f3..1c6bba462d5 100644 --- a/walletconsole/CMakeLists.txt +++ b/walletconsole/CMakeLists.txt @@ -1,17 +1,16 @@ +# 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. + # walletconsole executable file(GLOB walletconsole_sources *.cpp) add_executable(walletconsole ${walletconsole_sources}) -#target_link_libraries(tests gtest_main TrezorCrypto TrustWalletCore ${Protobuf_LIBRARIES} Boost::boost) + + target_link_libraries(walletconsole walletconsolelib TrezorCrypto TrustWalletCore ${Protobuf_LIBRARIES} Boost::boost) -target_include_directories(walletconsole PRIVATE ${CMAKE_SOURCE_DIR}/walletconsole/lib ${CMAKE_SOURCE_DIR}/src) -if(NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "MSVC")) - target_compile_options(walletconsole PRIVATE "-Wall") -endif() -set_target_properties(walletconsole - PROPERTIES - CXX_STANDARD 17 - CXX_STANDARD_REQUIRED ON -) +target_include_directories(walletconsole PRIVATE ${CMAKE_SOURCE_DIR}/walletconsole/lib ${CMAKE_SOURCE_DIR}/src) -INSTALL(TARGETS walletconsole DESTINATION ${CMAKE_INSTALL_BINDIR}) \ No newline at end of file +INSTALL(TARGETS walletconsole DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/walletconsole/lib/Address.cpp b/walletconsole/lib/Address.cpp index 48f6aa9e65c..7a082f7413c 100644 --- a/walletconsole/lib/Address.cpp +++ b/walletconsole/lib/Address.cpp @@ -53,7 +53,7 @@ bool Address::addrPri(const string& coinid, const string& prikey_in, string& res return true; } -bool Address::addr(const string& coinid, const string& addrStr, string& res) { +bool Address::addr(const string& coinid, const string& addrStr, [[maybe_unused]] string& res) { Coin coin; if (!_coins.findCoin(coinid, coin)) { return false; } auto ctype = (TWCoinType)coin.c; diff --git a/walletconsole/lib/Buffer.cpp b/walletconsole/lib/Buffer.cpp index 3c79030c7b2..27f40a7e596 100644 --- a/walletconsole/lib/Buffer.cpp +++ b/walletconsole/lib/Buffer.cpp @@ -43,7 +43,7 @@ bool Buffer::prepareInput(const string& in, string& in_out) { int n = std::stoi(in2.substr(1)); // of the form #n int idx = n - 1; - if (idx < 0 || idx >= _prev.size()) { + if (idx < 0 || idx >= static_cast(_prev.size())) { _out << "Requested " << in2 << ", but out of range of buffers (n=" << _prev.size() << ")." << endl; return false; } @@ -58,7 +58,7 @@ bool Buffer::prepareInput(const string& in, string& in_out) { void Buffer::buffer() const { _out << "Last value: " << _last.get() << endl; _out << _prev.size() << " previous values:" << endl; - for (int i = 0; i < _prev.size(); ++i) { + for (auto i = 0ul; i < _prev.size(); ++i) { _out << " #" << i + 1 << " " << _prev[i].get() << endl; } } diff --git a/walletconsole/lib/CMakeLists.txt b/walletconsole/lib/CMakeLists.txt index 0dec5ad513b..b14eb51ec08 100644 --- a/walletconsole/lib/CMakeLists.txt +++ b/walletconsole/lib/CMakeLists.txt @@ -1,13 +1,12 @@ +# 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. + # walletconsolelib library file(GLOB_RECURSE walletconsolelib_sources *.cpp) add_library(walletconsolelib ${walletconsolelib_sources}) #target_link_libraries(tests gtest_main TrezorCrypto TrustWalletCore ${Protobuf_LIBRARIES} Boost::boost) target_link_libraries(walletconsolelib TrezorCrypto TrustWalletCore ${Protobuf_LIBRARIES} Boost::boost) target_include_directories(walletconsolelib PRIVATE ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/src) -target_compile_options(walletconsolelib PRIVATE "-Wall") - -set_target_properties(walletconsolelib - PROPERTIES - CXX_STANDARD 17 - CXX_STANDARD_REQUIRED ON -) diff --git a/walletconsole/lib/CommandExecutor.cpp b/walletconsole/lib/CommandExecutor.cpp index e20be755518..973c2cf4dd7 100644 --- a/walletconsole/lib/CommandExecutor.cpp +++ b/walletconsole/lib/CommandExecutor.cpp @@ -189,7 +189,7 @@ string CommandExecutor::parseLine(const string& line, vector& params) { return cmd; } -bool CommandExecutor::checkMinParams(const vector& params, int n) const { +bool CommandExecutor::checkMinParams(const vector& params, std::size_t n) const { if (params.size() - 1 >= n) { return true; } diff --git a/walletconsole/lib/CommandExecutor.h b/walletconsole/lib/CommandExecutor.h index 1177d987266..a722e47c1fc 100644 --- a/walletconsole/lib/CommandExecutor.h +++ b/walletconsole/lib/CommandExecutor.h @@ -46,7 +46,7 @@ class CommandExecutor { static string parseLine(const string& line, vector& params); bool prepareInputs(const vector& p_in, vector& p_out); bool setCoin(const string& coin, bool force); - bool checkMinParams(const vector& params, int n) const; + bool checkMinParams(const vector& params, std::size_t n) const; void help() const; }; diff --git a/walletconsole/lib/Keys.cpp b/walletconsole/lib/Keys.cpp index 2c6158e4139..974e3f46e38 100644 --- a/walletconsole/lib/Keys.cpp +++ b/walletconsole/lib/Keys.cpp @@ -31,10 +31,6 @@ Keys::Keys(ostream& out, const Coins& coins) : _out(out), _coins(coins) { void privateKeyToResult(const PrivateKey& priKey, string& res_out) { // take the key, but may need to take extension as well res_out = hex(priKey.bytes); - if (priKey.extensionBytes.size() > 0) { - res_out += hex(priKey.extensionBytes); - res_out += hex(priKey.chainCodeBytes); - } } bool Keys::newKey(const string& coinid, string& res) { @@ -69,7 +65,7 @@ bool Keys::pubPri(const string& coinid, const string& p, string& res) { } } -bool Keys::priPub(const string& p, string& res) { +bool Keys::priPub([[maybe_unused]] const string& p, [[maybe_unused]] string& res) { _out << "Not yet implemented! :)" << endl; return false; } @@ -81,7 +77,7 @@ void Keys::setMnemonic(const vector& param) { } // concatenate string mnem = ""; - for (int i = 1; i < param.size(); ++i) { + for (auto i = 1ul; i < param.size(); ++i) { if (i > 1) mnem += " "; mnem += param[i]; } diff --git a/walletconsole/lib/Util.cpp b/walletconsole/lib/Util.cpp index eea581e34e5..86dd938dc84 100644 --- a/walletconsole/lib/Util.cpp +++ b/walletconsole/lib/Util.cpp @@ -48,7 +48,7 @@ bool Util::base64Decode(const string& p, string& res) { } } -bool Util::fileW(const string& fileName, const string& data, string& res) { +bool Util::fileW(const string& fileName, const string& data, [[maybe_unused]] string& res) { if (fileExists(fileName)) { _out << "Warning: File '" << fileName << "' already exists, not overwriting to be safe." << endl; return false; diff --git a/wasm/.gitignore b/wasm/.gitignore new file mode 100644 index 00000000000..c1327fb6af0 --- /dev/null +++ b/wasm/.gitignore @@ -0,0 +1,5 @@ +dist/ +generated/ +wallet-core.d.ts +lib/wallet-core.js +lib/wallet-core.wasm diff --git a/wasm/.mocharc.json b/wasm/.mocharc.json new file mode 100644 index 00000000000..6a7296561e4 --- /dev/null +++ b/wasm/.mocharc.json @@ -0,0 +1,8 @@ +{ + "require": "ts-node/register", + "extensions": ["ts", "tsx"], + "spec":[ + "tests/*.test.ts", + "tests/**/*.test.*" + ] +} diff --git a/wasm/CMakeLists.txt b/wasm/CMakeLists.txt new file mode 100644 index 00000000000..379042f09e6 --- /dev/null +++ b/wasm/CMakeLists.txt @@ -0,0 +1,44 @@ +# 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. + +file(GLOB wasm_sources src/*.cpp src/generated/*.cpp) +file(GLOB wasm_headers src/*.h src/generated/*.h) +set(TARGET_NAME wallet-core) +set(CMAKE_EXECUTABLE_SUFFIX ".js") +add_executable(${TARGET_NAME} ${wasm_sources} ${wasm_headers}) + +target_link_libraries(${TARGET_NAME} TrustWalletCore) +target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/trezor-crypto/include) +target_compile_options(${TARGET_NAME} PRIVATE "-Wall") + +set_target_properties(${TARGET_NAME} + PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON +) + +# We use a set of COMPILE_FLAGS and LINK_FLAGS, see below links for more details. +# - https://emscripten.org/docs/optimizing/Optimizing-Code.html#how-to-optimize-code +# - https://github.com/emscripten-core/emscripten/blob/main/src/settings.js +# - https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html?highlight=lembind#embind + +# STRICT: Emscripten 'strict' build mode, disables AUTO_NATIVE_LIBRARIES and AUTO_JS_LIBRARIES etc. +# ASSERTIONS: Enable runtime assertions. + +# MODULARIZE=1: Emit generated JavaScript code wrapped in a function that returns a promise. +# ALLOW_MEMORY_GROWTH=1: Allowing allocating more memory from the system as necessary. +# DYNAMIC_EXECUTION=0: Do not emit eval() and new Function() in the generated JavaScript code. + +# -O2: good old code optimization level for release. +# --bind: Link Embind library. +# --no-entry: Skip main entry point because it's built as a library. +# --closure 1: Enable Emscripten closure compiler to minimize generated JavaScript code size. + +set_target_properties(${TARGET_NAME} + PROPERTIES + COMPILE_FLAGS "-O2 -sSTRICT -sUSE_BOOST_HEADERS=1" + LINK_FLAGS "--bind --no-entry --closure 1 -O2 -sSTRICT -sASSERTIONS -sMODULARIZE=1 -sALLOW_MEMORY_GROWTH=1 -sDYNAMIC_EXECUTION=0" +) diff --git a/wasm/README.md b/wasm/README.md new file mode 100644 index 00000000000..63385904724 --- /dev/null +++ b/wasm/README.md @@ -0,0 +1,7 @@ +Trust Wallet Core is an open source, cross platform and cross blockchain library, it adds beta support for WebAssembly recently, You can try it out now: + +```js +npm install @trustwallet/wallet-core +``` + +Documentation will be added to [developer.trustwallet.com](https://developer.trustwallet.com/wallet-core) later, please check out [tests](https://github.com/trustwallet/wallet-core/tree/master/wasm/tests) here for API usages. diff --git a/wasm/index.ts b/wasm/index.ts new file mode 100644 index 00000000000..2e298c52d53 --- /dev/null +++ b/wasm/index.ts @@ -0,0 +1,15 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +import * as Loader from "./lib/wallet-core"; +import { TW } from "./generated/core_proto"; +import { WalletCore } from "./src/wallet-core"; +import * as KeyStore from "./src/keystore"; + +declare function load(): Promise; + +export const initWasm: typeof load = Loader; +export { TW, WalletCore, KeyStore }; diff --git a/wasm/package-lock.json b/wasm/package-lock.json new file mode 100644 index 00000000000..8697fe149e1 --- /dev/null +++ b/wasm/package-lock.json @@ -0,0 +1,2351 @@ +{ + "name": "@trustwallet/wallet-core", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@trustwallet/wallet-core", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "protobufjs": ">=6.11.3" + }, + "devDependencies": { + "@types/chai": "^4.3.0", + "@types/mocha": "^9.1.0", + "@types/node": "^18.7.18", + "@types/webextension-polyfill": "^0.9.0", + "buffer": "^6.0.3", + "chai": "^4.3.6", + "mocha": "^9.2.2", + "ts-node": "^10.7.0", + "typescript": "^4.6.3" + } + }, + "node_modules/@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-consumer": "0.8.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, + "node_modules/@types/chai": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz", + "integrity": "sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==", + "dev": true + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, + "node_modules/@types/mocha": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.0.tgz", + "integrity": "sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.7.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz", + "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==" + }, + "node_modules/@types/webextension-polyfill": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@types/webextension-polyfill/-/webextension-polyfill-0.9.0.tgz", + "integrity": "sha512-HG1y1o2hK8ag6Y7dfkrAbfKmMIP+B0E6SwAzUfmQ1dDxEIdLTtMyrStY26suHBPrAL7Xw/chlDW02ugc3uXWtQ==", + "dev": true + }, + "node_modules/@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chai": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, + "engines": { + "node": ">=4.x" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "node_modules/loupe": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", + "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.0" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/minimatch": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", + "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", + "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", + "dev": true, + "dependencies": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.3", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "4.2.1", + "ms": "2.1.3", + "nanoid": "3.3.1", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "workerpool": "6.2.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", + "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-node": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", + "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.0", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/typescript": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", + "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz", + "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==", + "dev": true + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/workerpool": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", + "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true + }, + "@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "requires": { + "@cspotcode/source-map-consumer": "0.8.0" + } + }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, + "@types/chai": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz", + "integrity": "sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==", + "dev": true + }, + "@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, + "@types/mocha": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.0.tgz", + "integrity": "sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg==", + "dev": true + }, + "@types/node": { + "version": "18.7.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz", + "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==" + }, + "@types/webextension-polyfill": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@types/webextension-polyfill/-/webextension-polyfill-0.9.0.tgz", + "integrity": "sha512-HG1y1o2hK8ag6Y7dfkrAbfKmMIP+B0E6SwAzUfmQ1dDxEIdLTtMyrStY26suHBPrAL7Xw/chlDW02ugc3uXWtQ==", + "dev": true + }, + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "dev": true + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "chai": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "dependencies": { + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "loupe": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", + "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "dev": true, + "requires": { + "get-func-name": "^2.0.0" + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "minimatch": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", + "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mocha": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", + "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", + "dev": true, + "requires": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.3", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "4.2.1", + "ms": "2.1.3", + "nanoid": "3.3.1", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "workerpool": "6.2.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "nanoid": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", + "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + } + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-node": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", + "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.0", + "yn": "3.1.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "typescript": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", + "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", + "dev": true + }, + "v8-compile-cache-lib": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz", + "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "workerpool": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", + "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/wasm/package.json b/wasm/package.json new file mode 100644 index 00000000000..8ae7e3f0b3b --- /dev/null +++ b/wasm/package.json @@ -0,0 +1,46 @@ +{ + "name": "@trustwallet/wallet-core", + "version": "1.0.0", + "description": "wallet core wasm and protobuf models", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "test": "mocha --trace-warnings", + "generate": "npm run codegen:js && npm run codegen:ts", + "codegen:js": "pbjs -t static-module '../src/proto/*.proto' --no-delimited --force-long -o generated/core_proto.js", + "codegen:js-browser": "pbjs -t static-module '../src/proto/*.proto' -w closure --no-delimited --force-long -o ../samples/wasm/core_proto.js", + "codegen:ts": "pbts -o generated/core_proto.d.ts generated/core_proto.js", + "clean": "rm -rf dist generated && mkdir -p dist/generated generated", + "build": "npm run clean && npm run generate && cp -R generated lib dist && tsc && cp src/wallet-core.d.ts dist/src", + "build-and-test": "npm run copy:wasm && npm run build && npm test", + "copy:wasm": "mkdir -p lib && cp ../wasm-build/wasm/wallet-core.* lib", + "copy:wasm-sample": "cp ../wasm-build/wasm/wallet-core.* ../samples/wasm/" + }, + "repository": { + "type": "git", + "url": "git://github.com/trustwallet/wallet-core.git" + }, + "author": "hewigovens", + "license": "MIT", + "bugs": { + "url": "https://github.com/trustwallet/wallet-core/issues" + }, + "homepage": "https://github.com/trustwallet/wallet-core#readme", + "files": [ + "dist" + ], + "dependencies": { + "protobufjs": ">=6.11.3" + }, + "devDependencies": { + "@types/chai": "^4.3.0", + "@types/mocha": "^9.1.0", + "@types/node": "^18.7.18", + "@types/webextension-polyfill": "^0.9.0", + "buffer": "^6.0.3", + "chai": "^4.3.6", + "mocha": "^9.2.2", + "ts-node": "^10.7.0", + "typescript": "^4.6.3" + } +} diff --git a/wasm/src/AnySigner.cpp b/wasm/src/AnySigner.cpp new file mode 100644 index 00000000000..cbddc1a8bf1 --- /dev/null +++ b/wasm/src/AnySigner.cpp @@ -0,0 +1,44 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// + +#include +#include + +#include "WasmData.h" +#include "Coin.h" + +using namespace emscripten; + +namespace TW::Wasm { + +class AnySigner { + public: + static auto sign(const std::string& string, TWCoinType coin) { + Data out; + TW::anyCoinSign(coin, TW::data(string), out); + return DataToVal(out); + } + + static auto supportsJSON(TWCoinType coin) { + return TW::supportsJSONSigning(coin); + } + + static auto plan(const std::string& string, TWCoinType coin) { + Data out; + TW::anyCoinPlan(coin, TW::data(string), out); + return DataToVal(out); + } +}; + +EMSCRIPTEN_BINDINGS(Wasm_TWAnySigner) { + class_("AnySigner") + .class_function("sign", &AnySigner::sign) + .class_function("plan", &AnySigner::plan) + .class_function("supportsJSON", &AnySigner::supportsJSON); +} + +} // namespace TW::Wasm diff --git a/wasm/src/AnySigner.d.ts b/wasm/src/AnySigner.d.ts new file mode 100644 index 00000000000..b188bc2f479 --- /dev/null +++ b/wasm/src/AnySigner.d.ts @@ -0,0 +1,5 @@ +export class AnySigner { + static sign(data: Uint8Array | Buffer, coin: CoinType): Uint8Array; + static plan(data: Uint8Array | Buffer, coin: CoinType): Uint8Array; + static supportsJSON(coin: CoinType): boolean; +} diff --git a/wasm/src/HexCoding.cpp b/wasm/src/HexCoding.cpp new file mode 100644 index 00000000000..1a1c58f57c9 --- /dev/null +++ b/wasm/src/HexCoding.cpp @@ -0,0 +1,35 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// + +#include + +#include "WasmData.h" +#include "HexCoding.h" + +using namespace emscripten; + +namespace TW::Wasm { + +class HexCoding { + public: + static auto parseHex(const std::string& string) { + return DataToVal(TW::parse_hex(string, true)); + } + + static auto hexEncoded(const std::string& string) { + auto data = TW::data(string); + return TW::hexEncoded(data); + } +}; + +EMSCRIPTEN_BINDINGS(Wasm_HexCoding) { + class_("HexCoding") + .class_function("decode", &HexCoding::parseHex) + .class_function("encode", &HexCoding::hexEncoded); +} + +} // namespace TW::Wasm diff --git a/wasm/src/HexCoding.d.ts b/wasm/src/HexCoding.d.ts new file mode 100644 index 00000000000..f15943f9d56 --- /dev/null +++ b/wasm/src/HexCoding.d.ts @@ -0,0 +1,4 @@ +export namespace HexCoding { + export function decode(hex: string): Uint8Array; + export function encode(buffer: Uint8Array | Buffer): string; +} diff --git a/wasm/src/Random.cpp b/wasm/src/Random.cpp new file mode 100644 index 00000000000..d0ea0b2557f --- /dev/null +++ b/wasm/src/Random.cpp @@ -0,0 +1,25 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// + +#include +#include +#include +#include + +extern "C" { +uint32_t random32(void) { + std::mt19937 rng(std::random_device{}()); + return rng(); +} + +void random_buffer(uint8_t* buf, size_t len) { + std::mt19937 rng(std::random_device{}()); + std::generate_n(buf, len, [&rng]() -> uint8_t { return rng() & 0x000000ff; }); + return; +} + +} // extern "C" diff --git a/wasm/src/WasmData.cpp b/wasm/src/WasmData.cpp new file mode 100644 index 00000000000..7426d443675 --- /dev/null +++ b/wasm/src/WasmData.cpp @@ -0,0 +1,30 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// + +#include "WasmData.h" +#include "Defer.h" + +using namespace emscripten; + +namespace TW::Wasm { + +auto DataToVal(Data data) -> val { + auto view = val(typed_memory_view(data.size(), data.data())); + auto jsArray = val::global("Uint8Array").new_(data.size()); + jsArray.call("set", view); + return jsArray; +} + +auto TWDataToVal(TWData* _Nonnull data) -> val { + defer { + TWDataDelete(data); + }; + auto* v = reinterpret_cast(data); + return DataToVal(*v); +} + +} // namespace TW::Wasm diff --git a/wasm/src/WasmData.h b/wasm/src/WasmData.h new file mode 100644 index 00000000000..133f9b69f73 --- /dev/null +++ b/wasm/src/WasmData.h @@ -0,0 +1,22 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// + +#pragma once + +#include +#include + +#include "Data.h" + +namespace TW::Wasm { + +auto DataToVal(Data data) -> emscripten::val; + +/// Converts a TWData * to Uint8Array, deleting the TWData * when done. +auto TWDataToVal(TWData *_Nonnull data) -> emscripten::val; + +} // namespace TW::Wasm diff --git a/wasm/src/WasmString.cpp b/wasm/src/WasmString.cpp new file mode 100644 index 00000000000..2ea9f9cec79 --- /dev/null +++ b/wasm/src/WasmString.cpp @@ -0,0 +1,22 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// + +#include "WasmString.h" +#include "Defer.h" + +namespace TW::Wasm { + +auto TWStringToStd(TWString *_Nonnull string) -> std::string { + defer { + TWStringDelete(string); + }; + auto* s = reinterpret_cast(string); + auto result = *s; + return result; +} + +} // namespace TW::Wasm diff --git a/wasm/src/WasmString.h b/wasm/src/WasmString.h new file mode 100644 index 00000000000..ae58e22daa8 --- /dev/null +++ b/wasm/src/WasmString.h @@ -0,0 +1,18 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. +// + +#pragma once + +#include +#include + +namespace TW::Wasm { + +/// Converts a TWString * to std::string, deleting the TWString * when done. +auto TWStringToStd(TWString *_Nonnull string) -> std::string; + +} // namespace TW::Wasm diff --git a/wasm/src/keystore/default-impl.ts b/wasm/src/keystore/default-impl.ts new file mode 100644 index 00000000000..91da9b40f16 --- /dev/null +++ b/wasm/src/keystore/default-impl.ts @@ -0,0 +1,159 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +import { WalletCore, CoinType, PrivateKey, StoredKey } from "../wallet-core"; +import * as Types from "./types"; + +export class Default implements Types.IKeyStore { + private readonly core: WalletCore; + private readonly storage: Types.IStorage; + + constructor(core: WalletCore, storage: Types.IStorage) { + this.core = core; + this.storage = storage; + } + + hasWallet(id: string): Promise { + return this.storage + .get(id) + .then((wallet) => true) + .catch((error) => false); + } + + load(id: string): Promise { + return this.storage.get(id); + } + + loadAll(): Promise { + return this.storage.loadAll(); + } + + delete(id: string, password: string): Promise { + return this.storage.delete(id, password); + } + + mapWallet(storedKey: StoredKey): Types.Wallet { + const json = storedKey.exportJSON(); + return JSON.parse(Buffer.from(json).toString()) as Types.Wallet; + } + + mapStoredKey(wallet: Types.Wallet): StoredKey { + const json = Buffer.from(JSON.stringify(wallet)); + return this.core.StoredKey.importJSON(json); + } + + importWallet(wallet: Types.Wallet): Promise { + return this.storage.set(wallet.id, wallet); + } + + import( + mnemonic: string, + name: string, + password: string, + coins: CoinType[] + ): Promise { + return new Promise((resolve, reject) => { + const { Mnemonic, StoredKey, HDWallet } = this.core; + + if (!Mnemonic.isValid(mnemonic)) { + throw Types.Error.InvalidMnemonic; + } + let pass = Buffer.from(password); + let storedKey = StoredKey.importHDWallet(mnemonic, name, pass, coins[0]); + let hdWallet = HDWallet.createWithMnemonic(mnemonic, ""); + coins.forEach((coin) => { + storedKey.accountForCoin(coin, hdWallet); + }); + let wallet = this.mapWallet(storedKey); + + storedKey.delete(); + hdWallet.delete(); + + this.importWallet(wallet) + .then(() => resolve(wallet)) + .catch((error) => reject(error)); + }); + } + + importKey( + key: Uint8Array, + name: string, + password: string, + coin: CoinType + ): Promise { + return new Promise((resolve, reject) => { + const { StoredKey, PrivateKey, Curve } = this.core; + + // FIXME: get curve from coin + if ( + !PrivateKey.isValid(key, Curve.secp256k1) || + !PrivateKey.isValid(key, Curve.ed25519) + ) { + throw Types.Error.InvalidKey; + } + let pass = Buffer.from(password); + let storedKey = StoredKey.importPrivateKey(key, name, pass, coin); + let wallet = this.mapWallet(storedKey); + storedKey.delete(); + this.importWallet(wallet) + .then(() => resolve(wallet)) + .catch((error) => reject(error)); + }); + } + + addAccounts( + id: string, + password: string, + coins: CoinType[] + ): Promise { + return this.load(id).then((wallet) => { + let storedKey = this.mapStoredKey(wallet); + let hdWallet = storedKey.wallet(Buffer.from(password)); + coins.forEach((coin) => { + storedKey.accountForCoin(coin, hdWallet); + }); + let newWallet = this.mapWallet(storedKey); + storedKey.delete(); + hdWallet.delete(); + return this.importWallet(newWallet).then(() => newWallet); + }); + } + + getKey( + id: string, + password: string, + account: Types.ActiveAccount + ): Promise { + return this.load(id).then((wallet) => { + let storedKey = this.mapStoredKey(wallet); + let hdWallet = storedKey.wallet(Buffer.from(password)); + let coin = (this.core.CoinType as any).values["" + account.coin]; + let privateKey = hdWallet.getKey(coin, account.derivationPath); + storedKey.delete(); + hdWallet.delete(); + return privateKey; + }); + } + + export(id: string, password: string): Promise { + return this.load(id).then((wallet) => { + let storedKey = this.mapStoredKey(wallet); + let value: string | Uint8Array; + switch (wallet.type) { + case Types.WalletType.Mnemonic: + value = storedKey.decryptMnemonic(Buffer.from(password)); + break; + case Types.WalletType.PrivateKey: + value = storedKey.decryptPrivateKey(Buffer.from(password)); + break; + default: + throw Types.Error.InvalidJSON; + } + storedKey.delete(); + return value; + }); + } +} diff --git a/wasm/src/keystore/extension-storage.ts b/wasm/src/keystore/extension-storage.ts new file mode 100644 index 00000000000..3056a4b8681 --- /dev/null +++ b/wasm/src/keystore/extension-storage.ts @@ -0,0 +1,72 @@ +// 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 { Storage } from "webextension-polyfill"; +import * as Types from "./types"; + +// Extension KeyStore +export class ExtensionStorage implements Types.IStorage { + readonly storage: Storage.StorageArea; + readonly walletIdsKey: string; + + constructor(walletIdsKey: string, storage: Storage.StorageArea) { + this.walletIdsKey = walletIdsKey; + this.storage = storage; + } + + get(id: string): Promise { + return this.storage.get(id).then((object) => { + let wallet = object[id]; + if (wallet === undefined) { + throw Types.Error.WalletNotFound; + } + return wallet as Types.Wallet; + }); + } + + set(id: string, wallet: Types.Wallet): Promise { + return this.getWalletIds().then((ids) => { + if (ids.indexOf(id) === -1) { + ids.push(id); + } + return this.storage.set({ + [id]: wallet, + [this.walletIdsKey]: ids, + }); + }); + } + + loadAll(): Promise { + return this.getWalletIds().then((ids) => { + if (ids.length === 0) { + return []; + } + return this.storage + .get(ids) + .then((wallets) => Object.keys(wallets).map((key) => wallets[key])); + }); + } + + delete(id: string, password: string): Promise { + return this.getWalletIds().then((ids) => { + let index = ids.indexOf(id); + if (index === -1) { + return; + } + ids.splice(index, 1); + return this.storage + .remove(id) + .then(() => this.storage.set({ [this.walletIdsKey]: ids })); + }); + } + + private getWalletIds(): Promise { + return this.storage.get(this.walletIdsKey).then((object) => { + let ids = object[this.walletIdsKey] as string[]; + return ids === undefined ? [] : ids; + }); + } +} diff --git a/wasm/src/keystore/fs-storage.ts b/wasm/src/keystore/fs-storage.ts new file mode 100644 index 00000000000..3689be747e3 --- /dev/null +++ b/wasm/src/keystore/fs-storage.ts @@ -0,0 +1,45 @@ +// 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 * as Types from "./types"; +import * as fs from "fs/promises"; + +// FileSystem Storage +export class FileSystemStorage implements Types.IStorage { + private readonly directory: string; + + constructor(directory: string) { + this.directory = directory.endsWith("/") ? directory : directory + "/"; + } + + getFilename(id): string { + return this.directory + id + ".json"; + } + + get(id: string): Promise { + return fs + .readFile(this.getFilename(id)) + .then((data) => JSON.parse(data.toString()) as Types.Wallet); + } + + set(id: string, wallet: Types.Wallet): Promise { + return fs.writeFile(this.getFilename(id), JSON.stringify(wallet)); + } + + loadAll(): Promise { + return fs.readdir(this.directory).then((files) => { + return Promise.all( + files + .filter((file) => file.endsWith(".json")) + .map((file) => this.get(file.replace(".json", ""))) + ); + }); + } + + delete(id: string, password: string): Promise { + return fs.unlink(this.getFilename(id)); + } +} diff --git a/src/Zcash/TAddress.cpp b/wasm/src/keystore/index.ts similarity index 51% rename from src/Zcash/TAddress.cpp rename to wasm/src/keystore/index.ts index 9ebb2471ef5..68931378db0 100644 --- a/src/Zcash/TAddress.cpp +++ b/wasm/src/keystore/index.ts @@ -1,9 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "TAddress.h" - -using namespace TW::Zcash; +export { Default } from "./default-impl"; +export * from "./types"; +export { FileSystemStorage } from "./fs-storage"; +export { ExtensionStorage } from "./extension-storage"; diff --git a/wasm/src/keystore/types.ts b/wasm/src/keystore/types.ts new file mode 100644 index 00000000000..cc0ce8a21fd --- /dev/null +++ b/wasm/src/keystore/types.ts @@ -0,0 +1,93 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +import { CoinType, PrivateKey } from "../wallet-core"; + +export enum WalletType { + Mnemonic = "mnemonic", + PrivateKey = "privateKey", + WatchOnly = "watchOnly", + Hardware = "hardware", +} + +export enum Error { + WalletNotFound = "wallet not found", + AccountNotFound = "account not found", + InvalidPassword = "invalid password", + InvalidMnemonic = "invalid mnemonic", + InvalidJSON = "invalid JSON", + InvalidKey = "invalid key", +} + +export interface ActiveAccount { + address: string; + coin: number; + publicKey: string; + derivationPath: string; + extendedPublicKey?: string; +} + +export interface Wallet { + id: string; + + type: WalletType; + name: string; + version: number; + activeAccounts: ActiveAccount[]; +} + +export interface IKeyStore { + // Check if wallet id exists + hasWallet(id: string): Promise; + + // Load a wallet by wallet id + load(id: string): Promise; + + // Load all wallets + loadAll(): Promise; + + // Import a wallet by mnemonic, name, password and initial active accounts (from coinTypes) + import( + mnemonic: string, + name: string, + password: string, + coins: CoinType[] + ): Promise; + + // Import a wallet by private key, name and password + importKey( + key: Uint8Array, + name: string, + password: string, + coin: CoinType + ): Promise; + + // Import a Wallet object directly + importWallet(wallet: Wallet): Promise; + + // Add active accounts to a wallet by wallet id, password, coin + addAccounts(id: string, password: string, coins: CoinType[]): Promise; + + // Get private key of an account by wallet id, password, coin and derivation path + getKey( + id: string, + password: string, + account: ActiveAccount + ): Promise; + + // Delete a wallet by wallet id and password.aq1aq + delete(id: string, password: string): Promise; + + // Export a wallet by wallet id and password, returns mnemonic or private key + export(id: string, password: string): Promise; +} + +export interface IStorage { + get(id: string): Promise; + set(id: string, wallet: Wallet): Promise; + loadAll(): Promise; + delete(id: string, password: string): Promise; +} diff --git a/wasm/tests/AES.test.ts b/wasm/tests/AES.test.ts new file mode 100644 index 00000000000..9a44fb1b337 --- /dev/null +++ b/wasm/tests/AES.test.ts @@ -0,0 +1,46 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +import "mocha"; +import { assert } from "chai"; +import { Buffer } from "buffer"; + +describe("AES", () => { + + it("test decrypting", () => { + const { AES, HexCoding, AESPaddingMode } = globalThis.core; + + const key = HexCoding.decode( + "5caa3a74154cee16bd1b570a1330be46e086474ac2f4720530662ef1a469662c" + ); + const iv = HexCoding.decode("89ef1d6728bac2f1dcde2ef9330d2bb8"); + const cipher = HexCoding.decode( + "1b3db3674de082d65455eba0ae61cfe7e681c8ef1132e60c8dbd8e52daf18f4fea42cc76366c83351dab6dca52682ff81f828753f89a21e1cc46587ca51ccd353914ffdd3b0394acfee392be6c22b3db9237d3f717a3777e3577dd70408c089a4c9c85130a68c43b0a8aadb00f1b8a8558798104e67aa4ff027b35d4b989e7fd3988d5dcdd563105767670be735b21c4" + ); + + const decrypted = AES.decryptCBC(key, cipher, iv, AESPaddingMode.pkcs7); + + assert.equal(Buffer.from(decrypted).toString(), + '{"id":1554098597199736,"jsonrpc":"2.0","method":"wc_sessionUpdate","params":[{"approved":false,"chainId":null,"accounts":null}]}' + ); + }); + + it("test encrypting", () => { + const { AES, HexCoding, AESPaddingMode } = globalThis.core; + + const key = HexCoding.decode( + "bbc82a01ebdb14698faee4a9e5038de72c995a9f6bcdb21903d62408b0c5ca96" + ); + const iv = HexCoding.decode("5b3a1a561e395d7ad7fe9c92abdacd17"); + const plain = Buffer.from('{"jsonrpc":"2.0","id":1554343834752446,"error":{"code":-32000,"message":"Session Rejected"}}'); + + const encrypted = AES.encryptCBC(key, plain, iv, AESPaddingMode.pkcs7); + + assert.equal(HexCoding.encode(encrypted), + '0x6a93788fcd6d266d06ccff35d1ed328d634605a7f2734f1519256b9950d86d6ca34fe4a13ff0ed513025b49427e6fe15268c84d6dfad6c0c8a21abc9306a5308f545b08d946a2a24b7cd18526bcefd6d9740db9b8e21f4511df148d9b9b03ad9' + ); + }); +}); diff --git a/wasm/tests/AnyAddress.test.ts b/wasm/tests/AnyAddress.test.ts new file mode 100644 index 00000000000..8d0de564223 --- /dev/null +++ b/wasm/tests/AnyAddress.test.ts @@ -0,0 +1,30 @@ +// 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"; + +describe("AnyAddress", () => { + it("test validating Solana address", () => { + const { AnyAddress, HexCoding, CoinType } = globalThis.core; + + var address = AnyAddress.createWithString( + "7v91N7iZ9mNicL8WfG6cgSCKyRXydQjLh6UYBWwm6y1Q", + CoinType.solana + ); + + assert.equal(address.coin().value, 501); + assert.equal(address.description(), "7v91N7iZ9mNicL8WfG6cgSCKyRXydQjLh6UYBWwm6y1Q"); + + const data = address.data(); + + assert.equal(data.length, 32); + assert.typeOf(data, "Uint8Array"); + assert.equal(HexCoding.encode(data), "0x66c2f508c9c555cacc9fb26d88e88dd54e210bb5a8bce5687f60d7e75c4cd07f"); + + address.delete(); + }).timeout(5000); +}); diff --git a/wasm/tests/Base32.test.ts b/wasm/tests/Base32.test.ts new file mode 100644 index 00000000000..36915d1d41b --- /dev/null +++ b/wasm/tests/Base32.test.ts @@ -0,0 +1,55 @@ +// 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 { assert } from "chai"; +import { Buffer } from "buffer"; + +describe("Base32", () => { + + it("test decrypting", () => { + const { Base32 } = globalThis.core; + + const decoded = Base32.decode("JBSWY3DPK5XXE3DE"); + + assert.equal(Buffer.from(decoded).toString(), "HelloWorld"); + }); + + it("test decrypting with alphabet", () => { + const { Base32 } = globalThis.core; + + const decoded = Base32.decodeWithAlphabet( + "g52w64jworydimrxov5hmn3gpj2gwyttnzxdmndjo5xxiztsojuxg5dxobzhs6i", + "abcdefghijklmnopqrstuvwxyz234567" + ); + + assert.equal( + Buffer.from(decoded).toString(), + "7uoq6tp427uzv7fztkbsnn64iwotfrristwpryy" + ); + }); + + it("test encrypting", () => { + const { Base32 } = globalThis.core; + + const encoded = Base32.encode(Buffer.from("HelloWorld")); + + assert.equal(encoded, "JBSWY3DPK5XXE3DE"); + }); + + it("test encrypting with alphabet", () => { + const { Base32 } = globalThis.core; + + const encoded = Base32.encodeWithAlphabet( + Buffer.from("7uoq6tp427uzv7fztkbsnn64iwotfrristwpryy"), + "abcdefghijklmnopqrstuvwxyz234567" + ); + + assert.equal( + encoded, + "g52w64jworydimrxov5hmn3gpj2gwyttnzxdmndjo5xxiztsojuxg5dxobzhs6i" + ); + }); +}); diff --git a/wasm/tests/Base64.test.ts b/wasm/tests/Base64.test.ts new file mode 100644 index 00000000000..b2f6297627b --- /dev/null +++ b/wasm/tests/Base64.test.ts @@ -0,0 +1,42 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +import { assert } from "chai"; +import { Buffer } from "buffer"; + +describe("Base64", () => { + + it("test decoding", () => { + const { Base64 } = globalThis.core; + + const decoded = Base64.decode("SGVsbG9Xb3JsZA=="); + + assert.equal(Buffer.from(decoded).toString(), "HelloWorld"); + }); + + it("test encoding", () => { + const { Base64 } = globalThis.core; + + const encoded = Base64.encode(Buffer.from("HelloWorld")); + + assert.equal(encoded, "SGVsbG9Xb3JsZA=="); + }); + + it("test encoding (URL-safe)", () => { + const { Base64 } = globalThis.core; + + const encoded = Base64.encodeUrl(Buffer.from("==?=")); + + assert.equal(encoded, "PT0_PQ=="); + }); + + it("test decoding (URL-safe)", () => { + const { Base64 } = globalThis.core; + const decoded = Base64.decodeUrl("PT0_PQ=="); + + assert.equal(Buffer.from(decoded).toString(), "==?="); + }); +}); diff --git a/wasm/tests/Blockchain/Bitcoin.test.ts b/wasm/tests/Blockchain/Bitcoin.test.ts new file mode 100644 index 00000000000..4cad6e61e5d --- /dev/null +++ b/wasm/tests/Blockchain/Bitcoin.test.ts @@ -0,0 +1,16 @@ +// 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 { TW } from "../../dist"; + +describe("Bitcoin", () => { + it("test Bitcoin SigningInput / SigningOutput", () => { + assert.isNotNull(TW.Bitcoin.Proto.SigningInput); + assert.isNotNull(TW.Binance.Proto.SigningOutput); + }); +}); diff --git a/wasm/tests/Blockchain/Ethereum.test.ts b/wasm/tests/Blockchain/Ethereum.test.ts new file mode 100644 index 00000000000..660b6e43afc --- /dev/null +++ b/wasm/tests/Blockchain/Ethereum.test.ts @@ -0,0 +1,168 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +import "mocha"; +import { assert } from "chai"; +import { Buffer } from "buffer"; +import { TW } from "../../dist"; + +describe("Ethereum", () => { + + it("test address", () => { + const { PrivateKey, HexCoding, AnyAddress, CoinType, Curve } = globalThis.core; + + const data = HexCoding.decode("727f677b390c151caf9c206fd77f77918f56904b5504243db9b21e51182c4c06"); + + assert.isTrue(PrivateKey.isValid(data, Curve.secp256k1)); + + const key = PrivateKey.createWithData(data); + const pubKey = key.getPublicKeySecp256k1(false); + + assert.equal( + HexCoding.encode(pubKey.data()), + "0x043182a24fdefe5711d735a434e983bf32a63fd99d214d63936b312643c325c6e33545c4aaff6b923544044d363d73668ec8724b7e62b54d17d49879405cf20648" + ); + + const address = AnyAddress.createWithPublicKey(pubKey, CoinType.smartChain); + + assert.equal(address.description(), "0xf3d468DBb386aaD46E92FF222adDdf872C8CC064"); + + key.delete(); + pubKey.delete(); + address.delete(); + }); + + it("test signing transfer tx", () => { + const { HexCoding, AnySigner, CoinType } = globalThis.core;; + const input = TW.Ethereum.Proto.SigningInput.create({ + toAddress: "0x3535353535353535353535353535353535353535", + chainId: Buffer.from("01", "hex"), + nonce: Buffer.from("09", "hex"), + gasPrice: Buffer.from("04a817c800", "hex"), + gasLimit: Buffer.from("5208", "hex"), + transaction: TW.Ethereum.Proto.Transaction.create({ + transfer: TW.Ethereum.Proto.Transaction.Transfer.create({ + amount: Buffer.from("0de0b6b3a7640000", "hex"), + }), + }), + privateKey: HexCoding.decode( + "4646464646464646464646464646464646464646464646464646464646464646" + ), + }); + + const encoded = TW.Ethereum.Proto.SigningInput.encode(input).finish(); + assert.equal( + HexCoding.encode(encoded), + "0x0a0101120109220504a817c8002a025208422a3078333533353335333533353335333533353335333533353335333533353335333533353335333533354a204646464646464646464646464646464646464646464646464646464646464646520c0a0a0a080de0b6b3a7640000" + ); + + const outputData = AnySigner.sign(encoded, CoinType.ethereum); + const output = TW.Ethereum.Proto.SigningOutput.decode(outputData); + assert.equal( + HexCoding.encode(output.encoded), + "0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83" + ); + }); + + it("test signing eip1559 erc20 transfer tx", () => { + const { HexCoding, AnySigner, CoinType } = globalThis.core;; + + const input = TW.Ethereum.Proto.SigningInput.create({ + toAddress: "0x6b175474e89094c44da98b954eedeac495271d0f", + chainId: Buffer.from("01", "hex"), + nonce: Buffer.from("00", "hex"), + txMode: TW.Ethereum.Proto.TransactionMode.Enveloped, + maxInclusionFeePerGas: Buffer.from("0077359400", "hex"), + maxFeePerGas: Buffer.from("00b2d05e00", "hex"), + gasLimit: Buffer.from("0130B9", "hex"), + transaction: TW.Ethereum.Proto.Transaction.create({ + erc20Transfer: TW.Ethereum.Proto.Transaction.ERC20Transfer.create({ + to: "0x5322b34c88ed0691971bf52a7047448f0f4efc84", + amount: Buffer.from("1bc16d674ec80000", "hex"), + }), + }), + privateKey: HexCoding.decode( + "0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151" + ), + }); + + const encoded = TW.Ethereum.Proto.SigningInput.encode(input).finish(); + const outputData = AnySigner.sign(encoded, CoinType.ethereum); + const output = TW.Ethereum.Proto.SigningOutput.decode(outputData); + assert.equal( + HexCoding.encode(output.encoded), + "0x02f8b00180847735940084b2d05e00830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000c080a0adfcfdf98d4ed35a8967a0c1d78b42adb7c5d831cf5a3272654ec8f8bcd7be2ea011641e065684f6aa476f4fd250aa46cd0b44eccdb0a6e1650d658d1998684cdf" + ); + }); + + it("test signing personal message", () => { + const { HexCoding, Hash, PrivateKey, Curve } = globalThis.core;; + const message = Buffer.from("Some data"); + const prefix = Buffer.from("\x19Ethereum Signed Message:\n" + message.length); + const hash = Hash.keccak256(Buffer.concat([prefix, message])); + + assert.equal(HexCoding.encode(hash), "0x1da44b586eb0729ff70a73c326926f6ed5a25f5b056e7f47fbc6e58d86871655"); + + var key = PrivateKey.createWithData(HexCoding.decode("1fcb84974220eb76e619d7208e1446ae9c0f755e97fb220a8f61c7dc03a0dfce")); + + const signature = key.sign(hash, Curve.secp256k1); + + assert.equal(HexCoding.encode(signature), "0x58156c371347613642e94b66abc4ced8e36011fb3233f5372371aa5ad321671b1a10c0b88f47ce543fd4c455761f5fbf8f61d050f57dcba986640011da794a9000"); + + key.delete(); + }); + + it("test signing EIP712 message", () => { + const { EthereumAbi, HexCoding, Hash, PrivateKey, Curve } = globalThis.core;; + + const key = PrivateKey.createWithData(Hash.keccak256(Buffer.from("cow"))); + const message = { + types: { + EIP712Domain: [ + { name: "name", type: "string" }, + { name: "version", type: "string" }, + { name: "chainId", type: "uint256" }, + { name: "verifyingContract", type: "address" }, + ], + Person: [ + { name: "name", type: "string" }, + { name: "wallet", type: "address" }, + ], + Mail: [ + { name: "from", type: "Person" }, + { name: "to", type: "Person" }, + { name: "contents", type: "string" }, + ], + }, + primaryType: "Mail", + domain: { + name: "Ether Mail", + version: "1", + chainId: "0x01", + verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + }, + message: { + from: { + name: "Cow", + wallet: "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + }, + to: { + name: "Bob", + wallet: "bBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + }, + contents: "Hello, Bob!", + }, + }; + + const hash = EthereumAbi.encodeTyped(JSON.stringify(message)); + const signature = key.sign(hash, Curve.secp256k1); + + assert.equal(HexCoding.encode(hash), "0xbe609aee343fb3c4b28e1df9e632fca64fcfaede20f02e86244efddf30957bd2"); + assert.equal(HexCoding.encode(signature), "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b9156201"); + + key.delete(); + }); +}); diff --git a/wasm/tests/CoinType.test.ts b/wasm/tests/CoinType.test.ts new file mode 100644 index 00000000000..d18fdf4c60e --- /dev/null +++ b/wasm/tests/CoinType.test.ts @@ -0,0 +1,22 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +import "mocha"; +import { assert } from "chai"; + +describe("CoinType", () => { + it("test raw value", () => { + const { CoinType } = globalThis.core; + + assert.equal(CoinType.bitcoin.value, 0); + assert.equal(CoinType.litecoin.value, 2); + assert.equal(CoinType.dogecoin.value, 3); + assert.equal(CoinType.ethereum.value, 60); + assert.equal(CoinType.binance.value, 714); + assert.equal(CoinType.cosmos.value, 118); + assert.equal(CoinType.solana.value, 501); + }); +}); diff --git a/wasm/tests/HDWallet.test.ts b/wasm/tests/HDWallet.test.ts new file mode 100644 index 00000000000..567517cc74d --- /dev/null +++ b/wasm/tests/HDWallet.test.ts @@ -0,0 +1,34 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +import "mocha"; +import { assert } from "chai"; + +describe("HDWallet", () => { + + it("test creating 24 words", () => { + const { HDWallet, Mnemonic } = globalThis.core; + + var wallet = HDWallet.create(256, "password"); + const mnemonic = wallet.mnemonic(); + + assert.equal(mnemonic.split(" ").length, 24); + assert.isTrue(Mnemonic.isValid(mnemonic)); + + wallet.delete(); + }); + + it("test deriving Ethereum address", () => { + const { HDWallet, CoinType } = globalThis.core; + + var wallet = HDWallet.createWithMnemonic("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal", "TREZOR"); + const address = wallet.getAddressForCoin(CoinType.ethereum); + + assert.equal(address, "0x27Ef5cDBe01777D62438AfFeb695e33fC2335979"); + + wallet.delete(); + }); +}); diff --git a/wasm/tests/HRP.test.ts b/wasm/tests/HRP.test.ts new file mode 100644 index 00000000000..99d596e5f98 --- /dev/null +++ b/wasm/tests/HRP.test.ts @@ -0,0 +1,18 @@ +// 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"; + +describe("HRP", () => { + it("test string value", () => { + const { HRP, describeHRP } = globalThis.core; + + assert.equal(describeHRP(HRP.bitcoin), "bc"); + assert.equal(describeHRP(HRP.binance), "bnb"); + + }); +}); diff --git a/wasm/tests/Hash.test.ts b/wasm/tests/Hash.test.ts new file mode 100644 index 00000000000..47c26e87e81 --- /dev/null +++ b/wasm/tests/Hash.test.ts @@ -0,0 +1,42 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +import "mocha"; +import { assert } from "chai"; +import { Buffer } from "buffer"; + +describe("Hash", () => { + it("test keccak256", () => { + const { Hash, HexCoding } = globalThis.core; + + const sha3Hash = Hash.keccak256(Buffer.from("Test keccak-256")); + + assert.equal( + HexCoding.encode(sha3Hash), + "0x9aeb50f48121c80b2ff73ad48b5f197d940f748d936d35c992367370c1abfb18" + ); + }); + + it("test sha256", () => { + const { Hash, HexCoding } = globalThis.core; + + const sha256Hash = Hash.sha256(Buffer.from("Test hash")); + assert.equal( + HexCoding.encode(sha256Hash), + "0xf250fc8f40aeea3297c0158ec1bfa07b503805f2a822530bd63c50625bb9376b" + ); + }); + + it("test sha512_256", () => { + const { Hash, HexCoding } = globalThis.core; + + const hash = Hash.sha512_256(Buffer.from("hello")); + assert.equal( + HexCoding.encode(hash), + "0xe30d87cfa2a75db545eac4d61baf970366a8357c7f72fa95b52d0accb698f13a" + ); + }); +}); diff --git a/wasm/tests/HexCoding.test.ts b/wasm/tests/HexCoding.test.ts new file mode 100644 index 00000000000..0bacf5d67ce --- /dev/null +++ b/wasm/tests/HexCoding.test.ts @@ -0,0 +1,23 @@ +// 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"; + +describe("HexCoding", () => { + it("test encoding / decoding hex string", () => { + const { HexCoding } = globalThis.core; + + const expected = new Uint8Array([0x52, 0x8]); + const decoded = HexCoding.decode("0x5208"); + + assert.deepEqual(decoded, expected); + + const encoded = HexCoding.encode(expected); + + assert.equal(encoded, "0x5208"); + }); +}); diff --git a/wasm/tests/KeyStore+extension.test.ts b/wasm/tests/KeyStore+extension.test.ts new file mode 100644 index 00000000000..fe22c5b7091 --- /dev/null +++ b/wasm/tests/KeyStore+extension.test.ts @@ -0,0 +1,68 @@ +// 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 { KeyStore } from "../dist"; +import { ChromeStorageMock } from "./mock"; + +describe("KeyStore", async () => { + it("test ExtensionStorage", async () => { + const { CoinType, HexCoding } = globalThis.core; + const mnemonic = globalThis.mnemonic as string; + const password = globalThis.password as string; + + const walletIdsKey = "all-wallet-ids"; + const storage = new KeyStore.ExtensionStorage( + walletIdsKey, + new ChromeStorageMock() + ); + const keystore = new KeyStore.Default(globalThis.core, storage); + + var wallet = await keystore.import(mnemonic, "Coolw", password, [ + CoinType.bitcoin, + ]); + + assert.equal(wallet.name, "Coolw"); + assert.equal(wallet.type, "mnemonic"); + assert.equal(wallet.version, 3); + + const account = wallet.activeAccounts[0]; + const key = await keystore.getKey(wallet.id, password, account); + + assert.equal( + HexCoding.encode(key.data()), + "0xd2568511baea8dc347f14c4e0479eb8ebe29eb5f664ed796e755896250ffd11f" + ); + assert.equal(account.address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); + assert.equal( + account.extendedPublicKey, + "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn" + ); + assert.equal( + account.publicKey, + "02df6fc590ab3101bbe0bb5765cbaeab9b5dcfe09ac9315d707047cbd13bc7e006" + ); + + wallet = await keystore.addAccounts(wallet.id, password, [ + CoinType.ethereum, + CoinType.binance, + ]); + + assert.equal(wallet.activeAccounts.length, 3); + assert.isTrue(await keystore.hasWallet(wallet.id)); + assert.isFalse(await keystore.hasWallet("invalid-id")); + + const exported = await keystore.export(wallet.id, password); + assert.equal(exported, mnemonic); + + const wallets = await keystore.loadAll(); + + await wallets.forEach((w) => { + keystore.delete(w.id, password); + }); + }).timeout(10000); +}); diff --git a/wasm/tests/KeyStore+fs.test.ts b/wasm/tests/KeyStore+fs.test.ts new file mode 100644 index 00000000000..743a9a8e4b4 --- /dev/null +++ b/wasm/tests/KeyStore+fs.test.ts @@ -0,0 +1,70 @@ +// 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 * as fs from "fs"; +import { KeyStore } from "../dist"; + +describe("KeyStore", async () => { + it("test FileSystemStorage", async () => { + const { CoinType, HexCoding } = globalThis.core; + const mnemonic = globalThis.mnemonic as string; + const password = globalThis.password as string; + const testDir = "/tmp/wasm-test"; + + fs.mkdirSync(testDir, { recursive: true }); + + const storage = new KeyStore.FileSystemStorage(testDir); + const keystore = new KeyStore.Default(globalThis.core, storage); + + var wallet = await keystore.import(mnemonic, "Coolw", password, [ + CoinType.bitcoin, + ]); + const stats = fs.statSync(storage.getFilename(wallet.id)); + + assert.isTrue(stats.isFile()); + assert.isTrue(stats.size > 0); + assert.equal(wallet.name, "Coolw"); + assert.equal(wallet.type, "mnemonic"); + assert.equal(wallet.version, 3); + + const account = wallet.activeAccounts[0]; + const key = await keystore.getKey(wallet.id, password, account); + + assert.equal( + HexCoding.encode(key.data()), + "0xd2568511baea8dc347f14c4e0479eb8ebe29eb5f664ed796e755896250ffd11f" + ); + assert.equal(account.address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); + assert.equal( + account.extendedPublicKey, + "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn" + ); + assert.equal( + account.publicKey, + "02df6fc590ab3101bbe0bb5765cbaeab9b5dcfe09ac9315d707047cbd13bc7e006" + ); + + wallet = await keystore.addAccounts(wallet.id, password, [ + CoinType.ethereum, + CoinType.binance, + ]); + + assert.equal(wallet.activeAccounts.length, 3); + assert.isTrue(await keystore.hasWallet(wallet.id)); + assert.isFalse(await keystore.hasWallet("invalid-id")); + + const exported = await keystore.export(wallet.id, password); + assert.equal(exported, mnemonic); + + const wallets = await keystore.loadAll(); + + await wallets.forEach((w) => { + keystore.delete(w.id, password); + }); + }).timeout(10000); +}); diff --git a/wasm/tests/Mnemonic.test.ts b/wasm/tests/Mnemonic.test.ts new file mode 100644 index 00000000000..690c614aa95 --- /dev/null +++ b/wasm/tests/Mnemonic.test.ts @@ -0,0 +1,43 @@ +// 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"; + +describe("Mnemonic", () => { + + it("test isValid", () => { + const { Mnemonic } = globalThis.core; + + assert.isTrue( + Mnemonic.isValid( + "credit expect life fade cover suit response wash pear what skull force" + ) + ); + assert.isFalse( + Mnemonic.isValid( + "ripple scissors hisc mammal hire column oak again sun offer wealth tomorrow" + ) + ); + }); + + it("test isValidWord", () => { + const { Mnemonic } = globalThis.core; + + assert.isTrue(Mnemonic.isValidWord("credit")); + + assert.isFalse(Mnemonic.isValidWord("di")); + assert.isFalse(Mnemonic.isValidWord("cr")); + assert.isFalse(Mnemonic.isValidWord("hybridous")); + }); + + it("test suggest", () => { + const { Mnemonic } = globalThis.core; + + assert.equal(Mnemonic.suggest("air"), "air airport"); + assert.equal(Mnemonic.suggest("rob"), "robot robust"); + }); +}); diff --git a/wasm/tests/PBKDF2.test.ts b/wasm/tests/PBKDF2.test.ts new file mode 100644 index 00000000000..138ff504963 --- /dev/null +++ b/wasm/tests/PBKDF2.test.ts @@ -0,0 +1,24 @@ +// 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"; + +describe("PBKDF2", () => { + it("test sha256 hmac", () => { + const { PBKDF2, HexCoding } = globalThis.core; + + const password = Buffer.from("password"); + const salt = Buffer.from("salt"); + + const key = PBKDF2.hmacSha256(password, salt, 1, 20); + const key2 = PBKDF2.hmacSha256(password, salt, 4096, 20); + + assert.equal(HexCoding.encode(key), "0x120fb6cffcf8b32c43e7225256c4f837a86548c9"); + assert.equal(HexCoding.encode(key2), "0xc5e478d59288c841aa530db6845c4c8d962893a0"); + }); +}); diff --git a/wasm/tests/StoredKey.test.ts b/wasm/tests/StoredKey.test.ts new file mode 100644 index 00000000000..a24f8d8479f --- /dev/null +++ b/wasm/tests/StoredKey.test.ts @@ -0,0 +1,40 @@ +// 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"; + +describe("StoredKey", () => { + it("test importing mnemonic", () => { + const { StoredKey, CoinType } = globalThis.core; + + const mnemonic = "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; + const password = Buffer.from("password"); + const storedKey = StoredKey.importHDWallet( + mnemonic, + "test wallet", + password, + CoinType.bitcoin + ); + + assert.isTrue(storedKey.isMnemonic()); + + const jsonData = storedKey.exportJSON(); + const json = JSON.parse(Buffer.from(jsonData).toString()); + + assert.equal(json.type, "mnemonic"); + assert.equal(json.name, "test wallet"); + + const account = json.activeAccounts[0]; + + assert.equal(account.address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); + assert.equal(account.extendedPublicKey, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); + assert.equal(account.publicKey, "02df6fc590ab3101bbe0bb5765cbaeab9b5dcfe09ac9315d707047cbd13bc7e006"); + + storedKey.delete(); + }).timeout(5000); +}); diff --git a/wasm/tests/initWasm.test.ts b/wasm/tests/initWasm.test.ts new file mode 100644 index 00000000000..0fb9dd84fef --- /dev/null +++ b/wasm/tests/initWasm.test.ts @@ -0,0 +1,19 @@ +// 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 { initWasm } from "../dist"; + +describe("Module", () => { + it("load test", (done) => { + initWasm().then((WalletCore) => { + assert.isDefined(WalletCore); + assert.isNotNull(WalletCore); + done(); + }); + }).timeout(5000); +}); diff --git a/wasm/tests/mock.ts b/wasm/tests/mock.ts new file mode 100644 index 00000000000..99451f70d76 --- /dev/null +++ b/wasm/tests/mock.ts @@ -0,0 +1,53 @@ +// 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 { Events, Storage } from "webextension-polyfill"; + +export class ChromeStorageMock implements Storage.StorageArea { + object = {}; + + get( + keys?: string | string[] | Record | null | undefined + ): Promise> { + var ids: string[] = []; + if (typeof keys === "string") { + ids.push(keys); + } else if (keys instanceof Array) { + ids = ids.concat(keys); + } + + var result: Record = {}; + ids.forEach((id) => { + result[id] = this.object[id]; + }); + return Promise.resolve(result); + } + + set(items: Record): Promise { + Object.keys(items).forEach((key) => { + this.object[key] = items[key]; + }); + return Promise.resolve(); + } + + remove(keys: string | string[]): Promise { + var ids: string[] = []; + if (typeof keys === "string") { + ids.push(keys); + } + ids = ids.concat(keys); + ids.forEach((id) => delete this.object[id]); + return Promise.resolve(); + } + + clear(): Promise { + throw new Error("Method not implemented."); + } + + onChanged: Events.Event< + (changes: Storage.StorageAreaOnChangedChangesType) => void + > = {} as any; +} diff --git a/wasm/tests/setup.test.ts b/wasm/tests/setup.test.ts new file mode 100644 index 00000000000..54d6664beee --- /dev/null +++ b/wasm/tests/setup.test.ts @@ -0,0 +1,9 @@ +import "mocha"; +import { initWasm } from "../dist"; + +before(async () => { + globalThis.mnemonic = + "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; + globalThis.password = "password"; + globalThis.core = await initWasm(); +}); diff --git a/wasm/tsconfig.json b/wasm/tsconfig.json new file mode 100644 index 00000000000..bcca79e9e1b --- /dev/null +++ b/wasm/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "moduleResolution": "node", + "declaration": true, + "outDir": "./dist", + "strict": true, + "skipLibCheck": true, + "typeRoots": [ + "./node_modules/@types" + ], + "types": [ + "node", + "mocha" + ], + "noImplicitAny": false + }, + "exclude": [ + "node_modules", + "./tests/**/*.ts" + ], +} diff --git a/windows-bootstrap.ps1 b/windows-bootstrap.ps1 new file mode 100644 index 00000000000..0b29e2d3d1c --- /dev/null +++ b/windows-bootstrap.ps1 @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +# > powershell .\windows-bootstrap + +# Ported from the `bootstrap.sh` bash script +# Initializes the workspace with dependencies, then performs full build + +# Fail if any commands fails +$ErrorActionPreference = "Stop" + +#Set the position of the toolchain = $toolsPath +$toolsPath = Join-Path $PSScriptRoot "tools" + +Write-Host "#### Initializing workspace with dependencies ... ####" +& $toolsPath\windows-dependencies.ps1 + +Write-Host "#### Building and running tests ... ####" +& $toolsPath\windows-build-and-test.ps1 + + From b8ff8668a753c53a91e2caf531dab7dfd3dd8c67 Mon Sep 17 00:00:00 2001 From: ozzxzzo Date: Mon, 17 Oct 2022 19:00:31 +0800 Subject: [PATCH 2/3] Modifying a perl Script. --- tools/windows-replace-file.pl | 234 ++++-------------- tools/windows-replace/.vs/slnx.sqlite | Bin 90112 -> 90112 bytes .../.vs/windows-replace/v16/Browse.VC.db | Bin 655360 -> 655360 bytes .../powerShell/windows-replace-file.pl | 234 ++++-------------- tools/windows-replace/windows-dragon-king.pl | 38 +-- 5 files changed, 120 insertions(+), 386 deletions(-) diff --git a/tools/windows-replace-file.pl b/tools/windows-replace-file.pl index c357c93c27e..2680505d77a 100644 --- a/tools/windows-replace-file.pl +++ b/tools/windows-replace-file.pl @@ -1,5 +1,4 @@ #!/usr/bin/perl - if ($#ARGV+1 != 1 ) { print "Enter the command: -e or -r,\n"; @@ -10,113 +9,52 @@ $op=$ARGV[0]; -@trezor_crypto_file = ( - "trezor-crypto/crypto/rand.c", - "trezor-crypto/crypto/base32.c", - "trezor-crypto/crypto/base58.c", - "trezor-crypto/crypto/bignum.c", - "trezor-crypto/crypto/blake2b.c", - "trezor-crypto/crypto/blake2s.c", - "trezor-crypto/crypto/cardano.c", - "trezor-crypto/crypto/shamir.c", - "trezor-crypto/crypto/sha3.c", - "trezor-crypto/crypto/tests/test_check.c", - "trezor-crypto/crypto/tests/CMakeLists.txt", - "trezor-crypto/crypto/monero/base58.c", - "trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-donna-portable.h", - "trezor-crypto/include/TrezorCrypto/groestl_internal.h", - "trezor-crypto/include/TrezorCrypto/nist256p1.h", - "trezor-crypto/include/TrezorCrypto/rand.h", - "trezor-crypto/include/TrezorCrypto/aes.h", - "trezor-crypto/include/TrezorCrypto/endian.h", - "trezor-crypto/CMakeLists.txt" -); - -@src_file = ( - "src/Base32.h", - "src/BinaryCoding.h", - "src/HDWallet.cpp", - "src/Aptos/MoveTypes.cpp", - "src/Everscale/Cell.h", - "src/Everscale/Cell.cpp", - "src/Everscale/CellSlice.h", - "src/NULS/Address.cpp", - "src/Ethereum/ABI/ParamFactory.cpp", - "src/interface/TWBitcoinScript.cpp", - "src/Keystore/EncryptionParameters.cpp" -); - -@tests_file = ( - "tests/chains/Bitcoin/MessageSignerTests.cpp", - "tests/chains/Cardano/SigningTests.cpp", - "tests/chains/Cardano/TWCardanoAddressTests.cpp", - "tests/chains/Polkadot/SignerTests.cpp", - "tests/chains/Zilliqa/SignerTests.cpp", - "tests/chains/Aptos/MoveTypesTests.cpp", - "tests/common/BCSTests.cpp", - "tests/CMakeLists.txt", - "tests/main.cpp" -); - - -@include_file = ( - "include/TrustWalletCore/TWBase.h", - "include/TrustWalletCore/TWString.h", - "include/TrustWalletCore/TWAnySigner.h" -); - -@walletconsole_file = ( - "walletconsole/CMakeLists.txt", - "walletconsole/lib/CMakeLists.txt" -); - -@cmake_file = ( - "cmake/Protobuf.cmake", - "cmake/CompilerWarnings.cmake" - -); - -@wallet_cmakeLists_file = ( - "CMakeLists.txt" -); - -@protobuf_plugin_file = ( - "protobuf-plugin/CMakeLists.txt" -); - -@windows_tools_file =( - "tools/windows-build.ps1", - "tools/windows-build-and-test.ps1", - "tools/windows-dependencies.ps1", - "tools/windows-dependencies-version.ps1", - "tools/windows-download-dependencies.ps1", - "tools/windows-doxygen_convert_comments.ps1", - "tools/windows-generate.ps1", - "tools/windows-replace-file.p1", - "tools/windows-samples.ps1", - "tools/windows-tests.ps1", -); -#ÍêÕû·¾¶ -sub addFullPath -{ - @pathAndCMD = @_; - - $prefixPathName = $_[0]; +$searchPoint = "windows-replace/"; +$searchPointLength = length($searchPoint); +$searchPointLength = $searchPointLength - 1; +sub recure{ + + my $file = shift @_; + + if( -d $file ){ + + my @sub_files = <$file/*>; + + + foreach $sub_file (@sub_files ) + { + if(($sub_file ne $powerShellDir) && ($sub_file ne $READMEfile) && ($sub_file ne $dragonFile) ) + { + &recure($sub_file); + } + } + }else { + $cnt_file++; + + $windows_replaceFiles[$cnt_file] = $sub_file; + } +} - for($a = 1;$a < $#pathAndCMD + 1;$a = $a + 1) +sub replace_head +{ + my @toolsFiles = @_; + @TWFiles; + for($a = 0;$a < $#toolsFiles + 1;$a = $a + 1) { - $pathAndCMD[$a] = join( "", $pathAndCMD[0] ,$pathAndCMD[$a] ); + $toolsFilesString = join("",$toolsFiles[$a]); + my $stringPos = rindex($toolsFilesString,$searchPoint); + $stringPos += $searchPointLength; + my $filesName = substr($toolsFilesString,$stringPos + 1); + my $TWFileNameString = join( "",$TWdir ,$filesName ); + $TWFiles[$a] = $TWFileNameString; } - return @pathAndCMD; + return @TWFiles; } -#¿½±´Îļþ sub copyFile { - #my($walletPath,$toolsPath) = @_; my($fromPath,$toPath) = @_; - use File::Copy; for($a = 1;$a < @$fromPath;$a = $a + 1) { @@ -130,103 +68,40 @@ sub copyFile } } - #path--> /wallet-core-win use Cwd; $TWdir = getcwd; -$TWdir = join( "", $TWdir ,"/" ); +$windowsToolkitName = "windows-replace"; -#path--> tools/windows-replace/ -$toolsDir = join( "", $TWdir,"tools/windows-replace/" ); +#path--> tools/$windowsToolkitName +$windows_replaceDir = join( "", $TWdir,"/tools/$windowsToolkitName" ); -#trezor_cryptoÎļþ -@wallet_trezor_crypto_path = addFullPath($TWdir,@trezor_crypto_file); -@tools_trezor_crypto_path = addFullPath($toolsDir,@trezor_crypto_file); +#path--> tools/$windowsToolkitName/powerShell/ +$powerShellDir = join( "", $TWdir,"/tools/$windowsToolkitName/powerShell" ); -#src -@wallet_src_path = addFullPath($TWdir,@src_file); -@tools_src_path = addFullPath($toolsDir,@src_file); +#file--> tools/$windowsToolkitName/README.md +$READMEfile = join( "", $TWdir,"/tools/$windowsToolkitName/README.md" ); -#tests -@wallet_tests_path = addFullPath($TWdir,@tests_file); -@tools_tests_path = addFullPath($toolsDir,@tests_file); +#file--> tools/$windowsToolkitName/windows-dragon-king.pl +$dragonFile = join( "", $TWdir,"/tools/$windowsToolkitName/windows-dragon-king.pl" ); -#include -@wallet_include_path = addFullPath($TWdir,@include_file); -@tools_include_path = addFullPath($toolsDir,@include_file); - -#walletconsole -@wallet_walletconsole_path = addFullPath($TWdir,@walletconsole_file); -@tools_walletconsole_path = addFullPath($toolsDir,@walletconsole_file); - -#protobuf_plugin -@wallet_protobuf_plugin_path = addFullPath($TWdir,@protobuf_plugin_file); -@tools_protobuf_plugin_path = addFullPath($toolsDir,@protobuf_plugin_file); - -#cmake -@wallet_cmake_path = addFullPath($TWdir,@cmake_file); -@tools_cmake_path = addFullPath($toolsDir,@cmake_file); - -# wallet_cmakeLists -@wallet_cmakeLists_path = addFullPath($TWdir,@wallet_cmakeLists_file); -@tools_wallet_cmakeLists_path = addFullPath($toolsDir,@wallet_cmakeLists_file); +#file--> tools/$windowsToolkitName/*.* +@windows_replaceFiles; +&recure($windows_replaceDir); +#file--> tools/windows-*.ps1 & tools/windows-*.pl +@TWFiles = replace_head(@windows_replaceFiles); if ($op eq "-e") { - - #copy trezor_cryptoÎļþ - copyFile(\@wallet_trezor_crypto_path ,\@tools_trezor_crypto_path); - - #copy src - copyFile(\@wallet_src_path ,\@tools_src_path); - - #copy test - copyFile(\@wallet_tests_path ,\@tools_tests_path); - - #copy include - copyFile(\@wallet_include_path ,\@tools_include_path); - - #copy walletconsole - copyFile(\@wallet_walletconsole_path ,\@tools_walletconsole_path); - - #copy protobuf_plugin_ - copyFile(\@wallet_protobuf_plugin_path ,\@tools_protobuf_plugin_path); - - #copy cmake - copyFile(\@wallet_cmake_path ,\@tools_cmake_path); - - #copy cmakeLists - copyFile(\@wallet_cmakeLists_path ,\@tools_wallet_cmakeLists_path); + copyFile(\@TWFiles,\@windows_replaceFiles); + } elsif($op eq "-r") { - #toolsÌæ»»Ô´Âë - #copy trezor_cryptoÎļþ - copyFile(\@tools_trezor_crypto_path,\@wallet_trezor_crypto_path ); - - #copy src - copyFile(\@tools_src_path,\@wallet_src_path ); - - #copy test - copyFile(\@tools_tests_path,\@wallet_tests_path ); - - #copy include - copyFile(\@tools_include_path,\@wallet_include_path); - - #copy walletconsole - copyFile(\@tools_walletconsole_path,\@wallet_walletconsole_path ); - - #copy protobuf_plugin_ - copyFile(\@tools_protobuf_plugin_path,\@wallet_protobuf_plugin_path); - - #copy cmake - copyFile(\@tools_cmake_path,\@wallet_cmake_path ); - - #copy cmakeLists - copyFile(\@tools_wallet_cmakeLists_path,\@wallet_cmakeLists_path); + copyFile(\@windows_replaceFiles,\@TWFiles); } else{ @@ -238,4 +113,3 @@ sub copyFile - diff --git a/tools/windows-replace/.vs/slnx.sqlite b/tools/windows-replace/.vs/slnx.sqlite index 7185ef315c6350688c1b12c1d8d6aa7d4fb15ac2..01e9ae03a618fd108d47721ed5be85252feeb45c 100644 GIT binary patch delta 601 zcmYk3O=uHA6vt<>JDW|jvoEbAEykn`LizmM#){AJ3c&Uf%u1f<(6O-1K^w7|P z6wE=a9Yio-1o7ZOY#9%YC`k`Jq$0V9+8k8ypch*|4uV*4#)8U&$Kx?G{~y1Zu9N9H znGIwAjcrGYRen5sJ9dAnmeA(4BL9^(Z-RTZdh!$@V&+slYH?+hX|&D&^4pH+gs*1jum-AvFkvIPA(6b2i*}1-LgZ4FO_9x`D9G2Qzebi`W!}6n zk{3p9`5(}SY%)J!=v6vHv;MLyk@eNfD#b{$@vd}WOkR*$Ke{@N!22$u$HT zjT9!xSCB}diIo*0V?m52cLfDl&}bA3HKCA(K%}to5@wa^<-Owl-dkvjg{D{vKs>*2 z8^pR=?gyoh`%~$Qj4>s$3=i-tzQfnJh?9692k|m`sl(J36^h-kMAwT?xVR#+x-(tk z=g%sK1;QU%jJTt2dydp&npZ_(3n0RqwT55bretdpGYF=7mPI0QJ46~+aa(+r`d}c{%ZgG5W zeu|Nafw8Fc; + + + foreach $sub_file (@sub_files ) + { + if(($sub_file ne $powerShellDir) && ($sub_file ne $READMEfile) && ($sub_file ne $dragonFile) ) + { + &recure($sub_file); + } + } + }else { + $cnt_file++; + + $windows_replaceFiles[$cnt_file] = $sub_file; + } +} - for($a = 1;$a < $#pathAndCMD + 1;$a = $a + 1) +sub replace_head +{ + my @toolsFiles = @_; + @TWFiles; + for($a = 0;$a < $#toolsFiles + 1;$a = $a + 1) { - $pathAndCMD[$a] = join( "", $pathAndCMD[0] ,$pathAndCMD[$a] ); + $toolsFilesString = join("",$toolsFiles[$a]); + my $stringPos = rindex($toolsFilesString,$searchPoint); + $stringPos += $searchPointLength; + my $filesName = substr($toolsFilesString,$stringPos + 1); + my $TWFileNameString = join( "",$TWdir ,$filesName ); + $TWFiles[$a] = $TWFileNameString; } - return @pathAndCMD; + return @TWFiles; } -#¿½±´Îļþ sub copyFile { - #my($walletPath,$toolsPath) = @_; my($fromPath,$toPath) = @_; - use File::Copy; for($a = 1;$a < @$fromPath;$a = $a + 1) { @@ -130,103 +68,40 @@ sub copyFile } } - #path--> /wallet-core-win use Cwd; $TWdir = getcwd; -$TWdir = join( "", $TWdir ,"/" ); +$windowsToolkitName = "windows-replace"; -#path--> tools/windows-replace/ -$toolsDir = join( "", $TWdir,"tools/windows-replace/" ); +#path--> tools/$windowsToolkitName +$windows_replaceDir = join( "", $TWdir,"/tools/$windowsToolkitName" ); -#trezor_cryptoÎļþ -@wallet_trezor_crypto_path = addFullPath($TWdir,@trezor_crypto_file); -@tools_trezor_crypto_path = addFullPath($toolsDir,@trezor_crypto_file); +#path--> tools/$windowsToolkitName/powerShell/ +$powerShellDir = join( "", $TWdir,"/tools/$windowsToolkitName/powerShell" ); -#src -@wallet_src_path = addFullPath($TWdir,@src_file); -@tools_src_path = addFullPath($toolsDir,@src_file); +#file--> tools/$windowsToolkitName/README.md +$READMEfile = join( "", $TWdir,"/tools/$windowsToolkitName/README.md" ); -#tests -@wallet_tests_path = addFullPath($TWdir,@tests_file); -@tools_tests_path = addFullPath($toolsDir,@tests_file); +#file--> tools/$windowsToolkitName/windows-dragon-king.pl +$dragonFile = join( "", $TWdir,"/tools/$windowsToolkitName/windows-dragon-king.pl" ); -#include -@wallet_include_path = addFullPath($TWdir,@include_file); -@tools_include_path = addFullPath($toolsDir,@include_file); - -#walletconsole -@wallet_walletconsole_path = addFullPath($TWdir,@walletconsole_file); -@tools_walletconsole_path = addFullPath($toolsDir,@walletconsole_file); - -#protobuf_plugin -@wallet_protobuf_plugin_path = addFullPath($TWdir,@protobuf_plugin_file); -@tools_protobuf_plugin_path = addFullPath($toolsDir,@protobuf_plugin_file); - -#cmake -@wallet_cmake_path = addFullPath($TWdir,@cmake_file); -@tools_cmake_path = addFullPath($toolsDir,@cmake_file); - -# wallet_cmakeLists -@wallet_cmakeLists_path = addFullPath($TWdir,@wallet_cmakeLists_file); -@tools_wallet_cmakeLists_path = addFullPath($toolsDir,@wallet_cmakeLists_file); +#file--> tools/$windowsToolkitName/*.* +@windows_replaceFiles; +&recure($windows_replaceDir); +#file--> tools/windows-*.ps1 & tools/windows-*.pl +@TWFiles = replace_head(@windows_replaceFiles); if ($op eq "-e") { - - #copy trezor_cryptoÎļþ - copyFile(\@wallet_trezor_crypto_path ,\@tools_trezor_crypto_path); - - #copy src - copyFile(\@wallet_src_path ,\@tools_src_path); - - #copy test - copyFile(\@wallet_tests_path ,\@tools_tests_path); - - #copy include - copyFile(\@wallet_include_path ,\@tools_include_path); - - #copy walletconsole - copyFile(\@wallet_walletconsole_path ,\@tools_walletconsole_path); - - #copy protobuf_plugin_ - copyFile(\@wallet_protobuf_plugin_path ,\@tools_protobuf_plugin_path); - - #copy cmake - copyFile(\@wallet_cmake_path ,\@tools_cmake_path); - - #copy cmakeLists - copyFile(\@wallet_cmakeLists_path ,\@tools_wallet_cmakeLists_path); + copyFile(\@TWFiles,\@windows_replaceFiles); + } elsif($op eq "-r") { - #toolsÌæ»»Ô´Âë - #copy trezor_cryptoÎļþ - copyFile(\@tools_trezor_crypto_path,\@wallet_trezor_crypto_path ); - - #copy src - copyFile(\@tools_src_path,\@wallet_src_path ); - - #copy test - copyFile(\@tools_tests_path,\@wallet_tests_path ); - - #copy include - copyFile(\@tools_include_path,\@wallet_include_path); - - #copy walletconsole - copyFile(\@tools_walletconsole_path,\@wallet_walletconsole_path ); - - #copy protobuf_plugin_ - copyFile(\@tools_protobuf_plugin_path,\@wallet_protobuf_plugin_path); - - #copy cmake - copyFile(\@tools_cmake_path,\@wallet_cmake_path ); - - #copy cmakeLists - copyFile(\@tools_wallet_cmakeLists_path,\@wallet_cmakeLists_path); + copyFile(\@windows_replaceFiles,\@TWFiles); } else{ @@ -238,4 +113,3 @@ sub copyFile - diff --git a/tools/windows-replace/windows-dragon-king.pl b/tools/windows-replace/windows-dragon-king.pl index 1d9512255ad..becb8409ba2 100644 --- a/tools/windows-replace/windows-dragon-king.pl +++ b/tools/windows-replace/windows-dragon-king.pl @@ -30,50 +30,35 @@ sub replace_head $TWdir = join( "", $TWdir ,"/" ); +$projectName = "windows-replace-3.0.3"; + #path--> tools/ $tools_Dir = join( "", $TWdir,"tools/" ); - -#path--> tools/windows-replace/ -$tools_windowsReplace_Dir = join( "", $TWdir,"tools/windows-replace/" ); - - -#file--> tools/windows-replace/powerShell/* -$powerShellAllDir = join( "", $TWdir,"tools/windows-replace/powerShell/*" ); - #path--> tools/windows-replace/powerShell/ -$powerShellDir = join( "", $TWdir,"tools/windows-replace/powerShell/" ); +$powerShellDir = join( "", $TWdir,"tools/$projectName/powerShell" ); + #file--> tools/windows-replace/powerShell/*.* -@powerShellFiles = glob( $powerShellAllDir ); +@powerShellFiles = <$powerShellDir/*>; -#file--> tools/* +#file--> tools/windows-*.ps1 & tools/windows-*.pl @TWtoolsFile = replace_head(@powerShellFiles); -#file--> tools/windows-replace/powerShell/windows-bootstrap.ps1 -$windowsBootstrapFile = join( "", $powerShellDir,"windows-bootstrap.ps1" ); - #file--> ./windows-bootstrap.ps1 $TWwindowsBootstrapFile = join( "", $TWdir,"windows-bootstrap.ps1" ); +#file--> tools/windows-replace/powerShell/windows-bootstrap.ps1 +$windowsBootstrapFile = join( "", $powerShellDir,"/windows-bootstrap.ps1" ); if ($op eq "-spouting") { use File::Copy; + #Copy the windowsBootstrap file to the home directory. + copy $windowsBootstrapFile ,$TWdir or warn 'copy failed.'; for($a = 0;$a < @powerShellFiles;$a = $a + 1) { - - if($windowsBootstrapFile eq $powerShellFiles[$a] ) - { - #å°†windowsBootstrap文件拷è´åˆ°ä¸»ç›®å½•ä¸‹ - copy $windowsBootstrapFile ,$TWdir or warn 'copy failed.'; - } - else - { - #æ‹·è´åˆ°tools目录下 - copy $powerShellFiles[$a] ,$tools_Dir or warn 'copy failed.'; - } - + copy $powerShellFiles[$a] ,$tools_Dir or warn 'copy failed.'; } } @@ -94,3 +79,4 @@ sub replace_head exit; } + From f96ab03a1538bb1107b53ed6e2e23f2d0b7106b3 Mon Sep 17 00:00:00 2001 From: ozzxzzo Date: Mon, 17 Oct 2022 19:13:58 +0800 Subject: [PATCH 3/3] Modifying a perl Script. --- tools/windows-replace/.vs/CMake Overview | 0 tools/windows-replace/.vs/ProjectSettings.json | 3 --- tools/windows-replace/.vs/slnx.sqlite | Bin 90112 -> 0 bytes .../.vs/windows-replace/v16/Browse.VC.db | Bin 655360 -> 0 bytes .../.vs/windows-replace/v16/Browse.VC.db-shm | Bin 32768 -> 0 bytes .../.vs/windows-replace/v16/Browse.VC.db-wal | 0 .../.vs/windows-replace/v16/Browse.VC.opendb | Bin 16 -> 0 bytes 7 files changed, 3 deletions(-) delete mode 100644 tools/windows-replace/.vs/CMake Overview delete mode 100644 tools/windows-replace/.vs/ProjectSettings.json delete mode 100644 tools/windows-replace/.vs/slnx.sqlite delete mode 100644 tools/windows-replace/.vs/windows-replace/v16/Browse.VC.db delete mode 100644 tools/windows-replace/.vs/windows-replace/v16/Browse.VC.db-shm delete mode 100644 tools/windows-replace/.vs/windows-replace/v16/Browse.VC.db-wal delete mode 100644 tools/windows-replace/.vs/windows-replace/v16/Browse.VC.opendb diff --git a/tools/windows-replace/.vs/CMake Overview b/tools/windows-replace/.vs/CMake Overview deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tools/windows-replace/.vs/ProjectSettings.json b/tools/windows-replace/.vs/ProjectSettings.json deleted file mode 100644 index 940645c5d23..00000000000 --- a/tools/windows-replace/.vs/ProjectSettings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "CurrentProjectSetting": "x64-Debug" -} \ No newline at end of file diff --git a/tools/windows-replace/.vs/slnx.sqlite b/tools/windows-replace/.vs/slnx.sqlite deleted file mode 100644 index 01e9ae03a618fd108d47721ed5be85252feeb45c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90112 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 diff --git a/tools/windows-replace/.vs/windows-replace/v16/Browse.VC.db b/tools/windows-replace/.vs/windows-replace/v16/Browse.VC.db deleted file mode 100644 index 98a2462ff9836f7c6321f970d014ae2770b86f4b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 655360 zcmeFa2YeLO+CM&LXLic&&I}pS_t^PM?!%5%P_%$#SQ zIa_r?sW;$p)VKJW+yO@}*M~EixZ@lSj^hTw|Eu7C@NYC62*DFT6n5O*;UF$+jKd69 zP5L1mCr3Z5A5trAcUxbvt+58I{pE7YLFr5BGHH;QV7|_rAS^MxB}_G)ZOYMq=`K?v z{C|*vS$5H$V@G~(bDd|oce$(H+vxGT8a?iMSF^jxLr(h_Ryy;mosN?7BIg`O=SYr< zatDc+-jOb&JYKY?*-;b8w`~c_wjV_uc&sE&nzu<6jqd1RafShlvg`i7c+ez z+KkGQvi!=qj_J<1nT~+B$>R^Wn_8n&XF6KlKEDTFclbQ+#*S<=9qziiu7c~l_4Qqa z!hU@F&{)YnZzyV? z9c<}VU(3m!+JMXFsrS2TTbk>=i(KA1IQ6$QF7?!rZWSJoTy;Mb1(u%rAxcRfYLQP8e&UE=0R`)L4#;K}J-k7!l3S6wX+oOZNOk z)SepbtW1%PR)mg!}4Qbi|8sW zus2gLiwcuMydYS{?dM`}Ec)HDWfWoe(mbx~$W5S)Ic}4i`Cez_rUfbAK=W(^V16@>$YU}bg z_aaig50qQh;#=Ii=q;@QZ%ebk3o0Ak0sLgxLpH6!XuU1BBSE(%vkNXL9p4u`K2LM4 zr?Z0LY#cdm#z>opM{+l}s9;~nVF&7o7STR389@`l=!55sV0Q?g?EThmhI>S0JXC@K z>Gq-ndT%qgr~(n{>eNyvZPM&z2;6axz+z6FzPGgSpBPV=x%nn?V?i+i4Vb zH4r*KVq`k}4Q`(Y9z3G@eMYt{+6$ArYnYB=bAm@ody**Gt45&q;$Y*{w!pA~XO1S< zVhHk+85xEVJb%%rov7y!l$&IHeI&C1E=1k+40TC%1w%X|yBK zIJ9KH5K(Ow2-WSCVo+(8B0`~v=kl&PrN5!Y7a)xqS?P?C!jYziU|Birz}&7P$)1ve z+C9M`*6Q&EydFHr*pVr+ejVXZ#m<_AlaA}w5u557QLm-$#T~Smqv%ILH+&Qy3_^zPL<6Gn@gjZIL z-r9gq5aQb1*m_%ZObxt&3)Iwk8a)9|%_480+_S8Db`8jyW$s1@99i4q^Nd^u)~c3e z{u*L8u3={*eIBrn)_Q8Pmijw|EzKEMQ{aO%o~&7gS#=BJc|OBl+vvrrF7^2QV8_iF zmz9$fW8xDNLjj{k;oJm#Z61sM9Vh9lIQ><9i@r*~SASN&PXDB5qKy~^ z0|o;I0|o;I0|o;I0|o;I0|o;I0|o;I0|o=VWuPygfg7&|mAV?nXR=0fPa9 z0fPa90fPa90fPa90fPa90fPa9fxiO-nP#CZF0Np#b7DbJ?u2ngxy~^KV~Zw^9_yTt zKPI5`?nk7#7>vUFySRnfg(F7Pwzg)9Snp9i{57{VHsb#l{asl9r*e9#e!qS`SOA*- z4sB=D!C=5(z+k{&z+k{&z+k{&z+k{&z+k{&z+m7{GcbfNLp`jfM~OI6PqqScSfp-E z1VUJ(ZtVhmSfp+Z0{C73ke(FB@Bi}ziPPWEZvy}SYJI5oqqbW+Ut6fx<#F# z>b3*68*IyMg*MIlzV#048f%p`(ei`kDa%EcMV4bNn)0r4i?Upqs>I2k$oI%=3ivMX^XT#%9Tvw0r6&WwOA=S%-@ zc>0xFoG}XL+vr~C>9IFtXJbm*U_;YI zE_b8X?e8h?5m{J~zzV3Ir@p7i*&}i8tu1ZMbuLdc?D5%Cf~-v3*;stT1Z9yQ9$5V+)y6g}ymogS*Nx4swIk4g(E)ar9DYI65fMAooSl^eay zJytn;Xh8|A=S(F72^7ffXi!cC1oTV3Q1^vG+*Ks?AiOX1_wo^;PlCVhzRKeeK#*dvn! zZV=3e?#33p6>Lw@GZS&wsR%N@0dOIjaiFjIUYhK*kvaH$Fn_)51jBDKSY}OM&>4Lz;ks9|DGlR!7 zXj^kHhVpO|rUkljTWycMI0NBqdo@rju;$1UWX@V`O|spn?>B!Z-Xf+5vPHLSHLura zYPFWh);n!)3S)(zgeUZ2=3U~O=9$WS%B_|kEl+C~Xpai#32yUH+X`EeHby#MdO~u` zpUOkzO6dosSXm+8E3Z}JEe+!7VwHZgCaN#1C#ZeY^QAHRYVBVVZ~efgTi5>2EqsiU z4F(JbI4)C2EQ3#xdbR$~8zCf=q24{`P9br7xBj0zMi^0s{^a`q*wI2-P?=t?|0iV& zDZwPYSpy$K;`e_2KXH_hT893_`hUVWA-fFy>Gl73TJ=A({vStF`xEQ`u{pv}+%bB; z{vSiSQZLv4xx)o}8S2&ge>Cf&y;}e0kbHZ!{vSms`4j8^Y@$dn*Z)~0_dl`zA4xh} z@7MpC5%GIo|AQF?8uZVs|A&thl0zwae547haIe0t`>ZvCGYR^{HT|A&RC)T{OX z(69u(TK^9Tt3&VR#Z)qO|J3?_FwyEytp8JpR)1#w?;uUx+x7n-GRS(h{vSyDP%qd2 z$yvf6JRkOQ{hvfK@9p|OFvQKY?i9k*=o!)1PSH+x5Sl z#P01)`{-mU**2}vD^dLpSWsYvhF|1qTP{`~sC&v8Ow z849k1{|oDXJu-tozy8;b6()si@Tb=QDtVCTP`|h9e;esk!DKzPg_Sg2uh;(;(saFF z|10!~u~+MVnLaW8x%Iz9ro}(A{ufD&d%gZQX9#g+2-mnLDg`ow_GXX;3>XX;3>XX;3>XX;3>XX;3>XX;3>Xaj4H@8hMWO5egPeZw zZ&*R2N(KW40|o;I0|o;I0|o;I0|o;I0|o;I0|o>C*BFTBbCoXq1L*qyT@HQ*!1yy5 zFc>fxFc>fxFc>fxFc>fxFc>fxFc>fxFc|n>$UrJzru5|hPrv{Fg44hFUsypyWrG2O z0fPa90fPa90fPa90fPa90fPa90fPa9fhY$0p+1VN#Bp-RA9e>YzWfx zFc>fxFc>fxFc>fxFc>fxFc>fx_?s~h`u^Y8|LxcwgaU8|37p3&wrDO8PzZtFc>fxFc>fxFc>fxFc>fxFc>fxFc>fxFc=6jAPWjz z|Kt7txIV#P0}TcY1`Gxa1`Gxa1`Gxa1`Gxa1`Gxa1`Gxa2L6r=;P?N=`v33PvPPW@ z1`Gxa1`Gxa1`Gxa1`Gxa1`Gxa1`Gxa20{!N>;DiJ#*x8*!GOVl!GOVl!GOVl!GOVl z!GOVl!GOWQ-<1Jl{r`7uS)*PC0|o;I0|o;I0|o;I0|o;I0|o;I0|o;I10e>?aESE7 zIG|QPtQOiXv;JhAX4$BGt`x}^OK(g4#76T=W>qNU&on)UenrP}r{l;+>#u!cycG$O zB)7Xq$Yt@hW0!rLw{X$YQSU#UoL4e--iRw7J6v>3O|`SCx~9lk>a2Fw6qi(&J7-tV zt^rvyJHHeHM;2C8I!DefDKDy+T~$+EQBhh|!_G!lI%kyT7dlB6RW*gv@=MCAYECRE zEiE}Azox3BxZGKZbFa!OoG~Nnv|y}rVnI>v=y654&M^gJizbd9>zt53CbwYx#0jHE z6^v;=CXVS|3%bYEj$Jw^Z^hc@hphZ?dS0dQX@Pc0$zP!RjEd6f`9&4g|9_P)>&KLD z%ahCc)fV4#UEZu~-nwVfyA^pw2QTl_IDhY7pnPF|Wl?^4MNRc=c9dUKROzg$>S%&) zFZ9?LlVXF7F;Xsztu4Oy#??bO@e%v*5prb|5+kI5@oHz9bt8a+%!e3C6{2A31RW)T5vz*m)X8`rV*{E|! zkNPL+O#iXTa+zN1RG-bOsej#n`(bllb=?l%)88F)H1#ioxfD;ELF`ezp&C;!KUFT% zY72jyo>%ROd2RE&%)IiSHlBMZIsNFUS5;Y9Q$7=J0&I4{N`@WwsAr)X>;{wNGPQP0 z(v5iy!xs78_|TVEI_BV4mlS0mJw2y8=T=q2?W@LFURXJIMs-O=`HcL^{4%)JRg#-h zci}zO;ussN#k^d(%vM|YuMv6kp4c~Z{WVkbDxQ1pWA*eOj$SQF%0b!e zl_fK(G4<7*gL+hcxRohC9lDCOc8vXzyb|S+{JQe_dBxM0I_xieTY4mxInh%$l z=EZ4pnN(Z6rZlg%rs2M8cE6i<{Limj_O;A8dYVVwF|fWJH;Z1;JXmDPP8=$iiM55_ zmF4*~;rjM0Z(j8^55E4wQ=5*SveSy_LpoM0{J2L(hnbm@lQZNpGmNU%yt$K(X}I&U z{Ji{s?Zfw3jjjxpJ0VS&@ysQ6O9b5s>AAqD36y>;Z|uhx$KE#Y zo4lH>6CP1U9i?aa?v=*;!jtf9iajbk9$6K#imipw5BffpS8!~>j;3Ayys6W-eel%J zaep<*`Q>xzBQRDv?BocV!xDaBJbwS5uJ`5i|LBMG@ANP9PxSZoH}%)_7xg{*WBNn- zJ^D`lM*SN7a(%15SwBZVLtmvY*L`}UUav3EPt>dRa($X!pdYJ`(?{vU^&$E|y+34V z{22@w3>XX;3>XX;3>XX;3>XX;3>XX;3>XajKh1!dM<_iV%d|8shYiDW=uj+&48bxr z70bbcu}n$9(&4~z&>$=a4#YA!8Ox+3EE5y4954XOgaj=6_s7z1$1*-1%eXi!`}M;z zHWtgieX)#*!Lm;uEOi}AO~X=Ev9#H+v|6#WSg=$SEM*x>Ny1VTu{4{p6a*}J9!ryn z=M}{SZGrR+xJ9ngH>mmAe2ug1v-xa&t+%PyS&y^4Y-v%hQ%1_WUs+z&wEVJ? z%B(_1z0h9q7Z;S9RXFIcE~YZSyeO-1pjT+0`B&D4d46OSCjZqX6_n;rcaDa)s7e1l zB^6{9CjQl(t^hnuW|n0Y4*1IpV#6rmzoQ^{rBT?wPH3Ngv^pMkqNpjXD1$knrl2t7 zRAE1wlMpTBh%ROEtwMY4(XA9MuRAxexD`Ts+h0*&kFHz&{;H}5OOAG`iv6qFvKzms zzDM)%AiVDc%j?=}Ddw-@HR?5ThvQM7xkCHcquPan9zwcHXwN@NId|pcqa8)6aQ;0y z=ctWB`@ExAp(Fay*pA`^iEG!T-;4DqH&J9ES<@_Tq1|~D8H>svoD7ts+$PQ=`H9F2 zh4$j3Se>XbiJc~-c|!ZdqgX_^6NY$_&_4Mn<{jxdVV*6t=N!%4nbU*NB($G&l=2NN z4ZHJO;1>(+bN}+v3i4s$eC1J`zvu#tIlOoKv_RhftFt*>Q}5Oa^gY^Gb+1;TrR#TV z12miZyWXtV>Q(A&eW1ES-2{FBNPAh|tbL)b)>o=OssZ(%>N{FMTcGXG9#(hh-|8Rf zleDw+*Z=EQ03)TrfWd&lfWd&lfWd&lfWd&lfWd&l!2c=+lK9EUSu}d=*qn(Yiz>>? z^GD98sI1N}CZ%1f%MM~@vhBPVOxuy{TPb@5)(#wVkWw}lFC zNAw+DAATsp%e;;^Y&c%W>o0uL@rnwU2`Bh9Qb+NH166(y3cZfOXQ5Xnp)>qigXA21 z^$=c6Lhyu`@Ucb_=Jr^A1d94LGro(kxMV&Lb^Gmazkd8kl1um}wy`)HT>G$3Rr?O$ zC!%hwsF>Q0uVg~DPON^1c@j5A*oT4|X~u58%Tw)qE~wn;Yd0ItB<$-kE9RSg05#2m z3k!Y=rQk|Me`X}d@M$3}iS-9l8ey{O%V(e{J4;v_OcOLd1@sDjb;09n!S5JMefTsb z;`;y`X_ff!aI_xh!CM`NBEoNAB(6E@H(LqCFhC~MqiAjwPN{tyD9W*#5B|0c2 z#t|Llh#3?eG$>|ZbkM+<;7d zjSjNLSfYb0F-mlh5+g?k$uUxNkQ5_E2Z=G}=pb{95FI4M@XWR@&~izG7Qr4Osij<(7lem(pd@AThywojE~R!V9LiOlO;N zAd&I+f0F_I7t?V$cI5Xq*LjwEm%Hk{jUK3|IxopT(W zBRML{9VB9UN4kvic+s9_M@=N(wk0gzjx6mg%bDdRC(I1y91$&)DNdA;5hvOw+Yv|d z^Dk-i20SizTcCxUv)prt67g$J?kCypcC;=x;9l5Bt3a;Au|kRAmTK%scX;a@3%!fH z%>hSwMYRL=87p-ZR+NLi4Yot8cC;>LnM1T0l_h1ct&9WQ2s0f4ZIUCW;)z;bzKG5dF$)D3da?91U$<_nq@jV7>>T+4g`GOg>3; zEb{om3MLx)T#MSE77-bSSG7J^)$rJr&Z)4gQhA}XidM_#PY=7Ck)dIuxxfA%h}3^YG)Ch#?Yw1x89oZqZ#nsuzVP3$>gh ztSix+{OR7h46HNV3f57W58JjCIbi^Wx)ANNQ3EtG1{o%uVnj4QQ#fOVF4^-FQG05z zvob|GS`j)b&W;85>85LTlL}XbU2=3ZySFZrOkfq7XdjV?V4|TDZ7`3p;`HcN4v!O- zc{<6^t%~;XiD-3nhC%YYbu78oUACc^;hBd*GuHLDO7_G=bdE$i4a<*#EuyQiz~0QK zEGkS2kuz$8+usn~vqCL>=vL`2_H96tawm+Dw95Duip}iS|!ML49VOIm8 z^K(O{!{6ZcdEjXxs^4d1%c8w7xx0qxC^jc}q_iiAlD%pKYA+5pPHhVe8+gELaxI1+ zKbetX7{NmoeT0d+E_Fj~RJ2an&`B?%C(>zuRqU);IO(`<9kHpd5%pT?ZfuLX?PTPdM0;_{5e5)b%7upz zJLX~WD&qyLYWaeTK{s()fAulBN_t;v5#Qn;5U=AaO`oc@wohz}tedO}x)0rC8)4nW z&9J(TyzI4nVY+C1a_AugucKT|?$*#_T`(kzaTKvgjEss0Trhg*K|KS?$iWS;rj0a! z+wX6wb>TZ~=xA6(1BAk%0YU?&$0vkPy0BpesUjXs*%0aWK><%mon}3FR=~@&h^1A= z%E6M|3H>UW%xkdX=up+~YF#sNm}E~)MQ2P5tF*t>-R!4YlEaKHs!cBZSL#g?hIGI) z%h9Mftbt(|*0waZH8pp!@}!p=z0IC3E_#~lx=)EOc9||?@gT`Q35b=tA{OpS&G5(u z55K{d930&};vhfpD*qd^g!AruaM;mAEd{I25fbVCX1)oB40qNdX&G6te=WMJMnk_n>oL zDtx4v;c9msue0*q$LlJ$=C-DVuqg63xf|g!EQ}gkmcgSMsdfWi6h+R9xyeVm!G$`u z3!dJCW7yFin<&{Uz-p3520c8&vlSHXzu|T!)}O<#4rC)I+SnK#Czu@=0D>c;V>M4L zfZ?_!7A2!;0L(<7RM<>J>|Io;E>ubGJVSI&K<9{1czUone?pYysOuVphbA4dKo%3` zkEqIq-JaL=>rX64XD?<`Q%JG!f)b*pr%)_%vm&QQo~Jt2@XX>GQZC{?g>&lm?#!5q zURs7flSLO3OcGWI4(hfDZ&Pcdx7G{2I%?^Y3FhA5(zf%p+LoqP$hNCzhu|eM2n+$sA3QU}2A>(ac^C`@r$vvP&?T5Gf-5OHbV8S4;Djz^ zw9iZ>xAp&1kG8=_DzbX-pu;~jn$OUs&|?@$6Zwn;rqp1LnfSpY?1)DM~ zqo_Mi=*o~Ilpd8RviS6n*O^&j-F^?&Lg!!H25rSH>U)}Pm(hTi~qSieudQ@>Te0lp2mLcc`6 zK;NjJ4Zj1hT0cc^(_8h$@Jj%0y+)s{SL$W(TL6Xn6n)~6tpvtZg8_p9g8_p9g8_p9 zg8_p9g8_p9g8_p9gMt4g3<$h}(y>UxVi*=fu^56yDi(vWNWsE^#ULyOVv&qR5*CSA z48S4*i~d;Hv53ba4vT(R#A4AGix@2WV4-87VWDDS!@`P%1q%fW84C#u5eqXG0v0?L zWdDET_y7Kv^fyCIg8_p9g8_p9g8_p9g8_p9g8_p9g8_qqqs##QZM^?GzyC+p|JoOv zepvq(y#4=7|3H6Je-+;RKdCGda3S& zH~r&bm%t2tu%4**(^Xy6{?LBWzK0BrKZ5~-0fPa90fPa90fPa90fPa90fPa90fT}6 z5e7s-!3%o=X=wz84I?mgD1jkE2&ASG7(AFjN(upogTSCc1O^Tykeo~)DTzR0B7p$| z2qYvB=-;1!-A*7rohR zCTYB+i*Jc{iav3y`H=ZR^I7IO=0U=D!V|)ULcMSd{}O*Y@8@Uo!Ibx28JSSFEKi zP~;A{9m53#VEx8IXHS})SU?6MG!hqZH%Kt?DgDnSf)y3m)eo~;*Ib(8FvTqyiD;oF)u5VcxSElo+KB4w6nKF=dr#9@0oXmCv18oV2o= zt1J23yyKEi>E`MweD1_!l2&wcbp@ZBJ0@v)H&>VQxtSxAmUVM=8J{~WEoo^tSC{g+ zj+CUfZmzcRxd{V~36QHOc_{4>2(cCj@LAAMRir1;uC=rUJ{Ls&lKS=Qm)I|`%pXYd zcayW9&$X%(d^G2T=$w5dXUuwdzUlzApPHZsmesklrrUA3uC6rLMjzD*SCXQ7C%NDvbTS;6#IXZ5u33oae zL5<#pSq%$g7vjUrmTqFV1Y^TD;#m#uSU1G>W;S;dyEzyeK9tXDSP;7aV%LpqqOk`> zYtj^qjmKhEgR38WH($4Kd?SgDvPpEz4Qs1LlY@7aw>37xr?vi8Pc7c&t8QN6JY`J=9M3VnU&7s z@x@AUd@BXVEZP3{XjZfdTQ+dmhl8*?H}*Qr;W7?E!3%<{rfDWjB8@uP66;d3Ds^tsfic8i;9nqzUf zX63u`D~qdK@VC@GW==OH%!w$$Sw6FDWb#O5Wc)}{pV{5iXLdv?*kpEQVfF9Gm z6*;HoL$5KVPEEC>4orpAOOLJYCUteUsZ$&&mXv`hICXXvP2Fjxs|u=I?W`fgKfS-wKc+uPa6&f;P6$sBpB}Ho$HbEaGrCDIBRoNDY^)L+6N?j! zt)K}y(Owarz*UH+UAV(4svYjKF3wAZ*yx~pn5iWbRO<1 zr*|?3${00e)G_cMU!G9{$z`OBD2{r{+7{My0_BbT=nXBRO_{Mx2(LmOiq$%ZC?5EuI?v@G>-gLb`J*$%~%^&*b!vb^fXG zeK`23vRTbsz2|fLobTvIS9jlb(?6Op(It!vpou9HKg`ZSJuWI@f<%%Dj}h@ZSVirxen}abu$zq_DtpvcCm*q>qh`uolTqai$c&e|&vb z0jaZ+J3Xq-1>r4%+XYuPyj42qYjXHg5>rOPU{?+|WlaHhEJ9Vt% z^1AI<{ipOl2L6+dl}D@5sbl3uS0lU%(OuPKpVdF1e;Ujc(6OeF96NWcDY)ANG&JqeyRI4J>~+IH&N^cu$Qfs3fGk^!!>6sy0qI;509mkR0?7O|2;_0? zSpU3s0V`+Lp90Bkfz|&%Vdeh{toplL`)hafy!zME!51J&kJtO?7G2PO2XBBMw6CV4`?b%(lL-3q@xxL#eQE>)Y=dUd`! zOD$KYs#DZ)YL+@o9jL~unkw3Uw;i&5WBbhZzHPs4uWgTQx9vXLPTLOKcH36lM%#MZ zD%(<9v#s7X-!{uuZkuYGVjE}6vJJBhw8h&rn`r&rddT{X^)u`H*8SGK);-qU*88kG ztvjsSty`@dt?R9;tV^xU)_UuF>nv-zb*goWb(}TJI?OuI8gJFCqUCqXAgX?z8N)?67RNY_)8(thcPPEVVRS>Mipvvn=J7sg^00ah5F0Fv~zoyhXE! z%J0e{{jkmb}BoR?aEeVqq1IEr7TsNm3n2qGD|5}rYcjEaY~jl zOc|)eE1Dw8zsrZ@Z{*M9_vQWaUU`qaTfR@;DesWC%Uk7*@_Kodyi{(M>*e|KEV*2s zDo>He$yxF+d7vCGYqBW)E*+A-kv@YxBKJ#sr9IMa={{+vv_sl1ZIw1k>!nrFQmI+0 zm*z{eq;hGhG({RGWl6)Nfl|DrNuv0>cu4$4{7igb+%N7G_lUd2`^25%4spA&>goOU=#Zdh>kqEOWVes(FfeoH@%p%skK>Z`QyB_PcOM_(u3lcwg8r z>=pJ1yM_CNox%=byRcQ*D6AJ&2}^}$uuRPtW(noORAGuRPRJ652?K?AK@&v&cm5Fn z4gVScKEI#e%kSZL^Y`&P`5pXrek;F`U(c`Nm-5YgJwKnHCGjQ``T%`E@O|_?!S~R6 z1m8vP5_|`}L-1|%Ho>>hTLj-kZxTF!4iMar_7i*qy+Lpv+DGtp^g6-U&}#%=MXwTk z1-(M>W%M$^y=X7Nm(WWDUqmkwd;z^c@Okt+!ROF(1fNCE5_|?dLvRn;L-1+zG{L9P zQv{zxPZE3rJwfns^fHjMW^^;bo6t=JZ$vi|yaC-na0l8!@OpGT!Ryd<1g}Nc61)aoL-1;JHNmUURRpg@ zR}$QgwiCPpT|w}2bUDFoXdA)H&}9TKMVAu11YJV#VstUVt!OL3i_k>`FGLp-yZ~K5 z@O*SW!7XSD!Odth!A)or!HsAm!Sm301kXk165N0`5IhIAYsdZdY;-okv(Q-t&qQYu zT#wchT!+>XJOiCUa4lL(a1B~RupPFw!}*+!PA9k;ttPk%ts;0DI*s6|=v0C$(Mp1+ zpi>C0Kr09?N6QH=L(2#*MN0{`p*Dg66d>qFeu6&aBe(=DA=rvq3AUgXg3YLzU=wO0 z*oYblE=G$9o{UZ==tW+F4XAFf3m*5;Uhu~~9o8T-oi{MN&lVCNfCRl~42v(v> zf+wI82+lw=2v(p9g5{{3U>PbSSc*ysPDj%T9*>SESb|CjPD9fO7NcTPCb=oo?%(L{n1&;)|x z(RhO6&^Ur)(O80G&=`WbD3{=9G@4)z${{!kjUt$hvI%CPEP^A^NP?Ltli&z6g5Yp8 zoL~mZAefHQ38tYmg2T`-f{f~hE#;9xYEUbQGbGVWG5Jp;t9r~ID-99KZ3C+mSA7hmtYKvAqYS6L{LXM zK@DjHRiqNMAsay}vJ$i)3qg3%Ku|_9K?z9&MI;h5BQrq(2?Tk>6NGmy1Yu=DkV70s z?hoz{g1>XW6a0<)jo^Q{{}4RP9VYm1?%xD|<$fjj3-=4bpShn29^wuW{E7RC;J>(k z5&V(+k>C&94+OvGz9;w{_Z`7+xo-)6!+k^WYwl};UvXa%{F3{U;1}E%1P^it3I3D& zC&ACT&k26UeMaz8?o)!FaGwzTnEROEN8CpQ|H1u(;D_9Y1V7+DAoxD_KEe06_Xxhr zy-V;N?j3?}b8i!Ti+hXUo7|fO4{!$v?&tOse1m&~;6832!PmLh3BJa?M(|bcRf4Z@ zuMm8hdzs)~ZZE-?xR(gN$h}DL1?~lc&vVZce2#mL;IrJb1fSuaA-IRzL-1+tX@XC2 zPZ4~Qdy?Q2+!F*J=N>2c821>#N4ZA{?&fwA+{Nu8_z3q1!H2nr2|mO!fheAncGZo6Ss-rMs6d)^SJW}p39v}a09o2;5pnm1kdKqCU_Qi z7Qr*QGYPKe))QREts{5_cLu?=+**QbxHSaZxpso5bEjh;`qitkT(t_z(@w+k)Kjrs zxf07$PQh};3M`i|$8y;+ESE0DvaJovKmbd>A4{JP%Oy*&oIV}P)>bTATCi+x#}&r{t$cz zaEE@2zC(Mmw>|*9umbGT?$_?rZqcsSuGB8oF3`@?&eYnqm0FwDqItmzFi)%2%D@Vc zr%ljuwBcH+mZZgj9YE4J^;h)=^-J{=^*yizysSQ}KCV8b-mTuMUJtf_i`C8Q+3Ff~ zr5aG1z#8CE=cp&B$E$_vvFcc`2c)SEu*mjRt*T)A4J-oR+Wu+#hwW|K>$VqdPlHY1 ze%l?kn{3zEw%IPUod;Hd)wbofCAO1owYD1DOt1?S+m5qMw2iinunn;#gJnRs$u?yD zxAjNsSJqF#HgLfDiuF0`6V`{V_gHTO>%f)PORQV0=UCTTPqnszePEGwfpxC6(mLH* zWStBaf^2KLHN`r>8f&##&0r(=+47y`pyeaWJC=Qxm%vK!sO166otB#|*IF*OTm*K4 zGcBiER#<$N#g;nDNnk0cuuQY$TaK~hS~4v|!B$|m^a1;?Nja?iOZghC1s^DHDz7Tf zD^DtqDEETB;0EO?S-DW!@NYzE_%QA&m~SV>g+DJobE z{*Zr>zn8y|KbGH>-vGP8GxB5dgYsSSE%J5p6<|5oB%dX>%csbGxl#6j?O?V%LoSgE zCTVuILL1n(L1Z(vdQ z*8ETNKg@5NUpK#Kej02F_nYr9-(HtTrz`%JCK-d(BvBA4m??mb0$<}Pm?$6$B)Jv+LC(-d9}0&l{F}nB6n>%b zGX@JfMByh2|Dy0Cg&!zPvH#;`zX9l;WY}cQh0^J%M|uvP|!;h zUZn5>h36?eN8wot&rsMy;b{s_QFxNV6BHh&@ECq0mm@bPB5}tfFulg;Oc4q;Lv_6%>|JSVmzfg*FNS z3VsX{@=;hqp_M`lg=Pv(6dEZkrf@O^FNFpQizw7n@KC6uP)lJU1viBS6kHVMQ#gr2 z4TX6WPNXoG!W;^-Da@iUlR`CxDhibpPM|P@LIs6#3S|^ZDNLtuJcSYp(2ug#rrs6po{iM_~$u$rO&IFp0u37(_IY!UPKADU72qmckecxfDiI$e}QbLNFNGKieJJP@GzuyO8wD!`3k8LOOhKX`QZQ2xDDV_a6c7cD0QU!l-zoe? z;Xf1(Q}{Q9Un%@T;b#hmDEvg>Ule|%@B@YKDSSuaTMFM$_?p636uzYJ1%-nY{z>6; z3ZGHQ>Yt0-JaVLOE@C|piq8->d#TuR{*3Kvt@O5q|37gD%@!ub@oP}odi z6NQZw&ZBTHg$)$Wp>Q?^fjf)BnH1JjSV!Ru3Tr8>q0ml&m;?l35)g<+Rt4zfAY(P4NAHEqMJ` z>E(I}_@C#2*Z)|3xIPFR(RFy|Z`L{OuyzRc0QgG#T>D6SPkT*!3EuiYsqNMt)b7!4 zhd2M%YgcL8w5{3}ZG+~775+pmR~xCNX(?Kw?OxmMu>QW*c7^R?+ZNjf+d5c*udw-T zO}0fgw{0G*!7FSfwnFPV>uTje`LO(x{H-)m%9Tb+X;O-mD8-4{VuqM1!f!^2F|f)r zoBuHXYW`7JC#(iLh+k-e6_{I?ryqb_4)(yW8$6`nu3xDgfUg*`^&xtKwiav<^EAJ< zSgX~lwCP%`dKhdCud7e0x2W6H4XR(A2eyP9Q65$91RH@@$yZXqCF~~UYGsRZw$iSw zP?mtLV6Jk4Qld;$a==cI06zRk{#^b*zF)pru8_yWdS8;hl@3UcN;gQGU^VZOisb|H z%V0NnRK8ulLB0|!2CKnJaHhOWZjl$sF1c9Fle6VC*)HqSAJWfYA^1RgS$amgA8Z3x zN*7CKN~@(7X^}KrssL+%Dw)OK#IIrP|B|>%yiJ@e{v>`Xz9Bv>-V5vhi^S7l)jw0b zLOf4gC$@Ri!Nm$q4E!-kpD{K=E3ttKs2pfbd zSj!g*lZ0GhgpevE!b%>t;p7kVKk{GlAHzESZvI++1HY1goqvIUl7E=Lh2O?sz^~E#REIv_PMtgCyRkh`h9bbMn#x zki4{jc;ux8AbDv4_%SP&ytIIG^3nqELl#b6TEIDZX#q%HS^$!l7J%fX1t5880Z3k2 z0Fsv$Xg_mWcmwS>^RfxOLF7InUnlZ4B3~u)6(V0IaxW?W1?Hsi0&`N>#hes&F(-vx z%t>Jvb5hvFoD_C3Cxu|#y|S2HJtdzh2L zL(ECx9_FNQKXX#JpE)Vq!<-cEWljqBFeinpnUlg@%t>Jfb5hvBoD^u4YaO*D@!CYnhY6)yzrZYUZSH6}pc61knoD~PM3UDL=tAa>u#&kWY+>#QTbMh-dghL>k+~ym zWbO#-nLEO{%pGApb4NIvxg(s-+!59@cZ4&UJHmSAjq95mqvHgf+|^VI^}% zIGwp8tYq#8tC%~&O6HDmDsx9z$=nf6VeSYk5Il!K&xPj@knkJ=5}rdq!gB~ncn$#x z&mkcFq|Yv44hWN(141)%Kxk$T2$Puu!eZutu$Va@OlA%U4a@UaR*A_?c2;{i_{ z4Il4-PWNgD;rl!57T&;2`$}Zbj}O zk>vi(ea4&)USUoLA2X+ekD1fKE6nNO1Lkz_0dqR|fH@t!!kiA?W=;ohGpB>MnbX1B z%<142=5(-+IUVd{P6zv#)4@LGbg+*(9lXMv4qjzW2d^@xgI73mC+EnWoFjK~ZZE0N zOGLg%oe3Ud&IAuJXM)Yl znc#lrOmIJQCfLlJ3GQXi1e=*N!ClOm;4bD&u$eg%+`*g)HZx~}+n6)KZOoZqGjk@m zg*g*!X3hjRGG~GtnKQv==1g!sb0)Z+ITLJV&IH#mXM$^(Gr?x&OmG!*CfLlJ3AQt5 zg3ZjCU>kEL*v6a*HZx~}OPMplX68(=l{ph^WzGbfnKQwK%$Z;_b0*lroC&rtXM)Y# z7Q*soA~zAak;wCiJeSA~M4m$=bskvHoCnr3=Ye+SJg|m253FI%18bP`Ks$3DIGt-J zxtuQWGFpwrDlAUJ;#4eFVsQ!uKt`=tv|!PU zMH3c{SS-fkWGuW`G+?m^3t|qa^WZ~b4Om!<4~a2gfg2w#z`}*ad@N4Fq6Uk3SP(0~ zoVoaL4i>Yqn1#hmEUK}n!lDw36R?G#_#HnWX`HR*leD}D{E*-n<{TfeiG!28h)%?;)p`6;>F ze23a-eZq1GekE`i{664PunGKVJ5K+|HdcSbma6YjZQyVJgY6M*n&wb{Q~#;Hs@|(! z3@iSJv|Hf|fX%RH;JfgRz_YMc9-}VRmTEQD{qSD+a_Mx*BaMrjKDCfdcEX)@-T2I9D75 z?{}YpmD?2pZ+^}4I;@)KS&(v%vRpY<{@r@Ba-HQ#rNMHhl4+SPf2DkDJzH1?zT9I? zuZvb8h5wenm%l{xSsRsSET1VGEH^6^mS!c!5@)^4JVdNBoo6ali`8`TEAdv7jju8N z8{YQjfxq}(;YRZ>;yBZZrfvKO@Sb|DScVQMpUa;p2jqS58;VI_N%6>|%qNN;!8*3k zWCHK?82H?34Dd=Yw;QMxy5RE2)+v_P_`*9CDnGCtrh$bW+}3Hy*1U^U-(ko zWUaNlXSv$qfv*A9C{rzImeE$5cqRWVpKjVC4mW?L)|>Gb4;nwn1mE?0-HqN;JZP8| zwp?g%`x`tyG*sc^OwF*N0UFYWPc_xUE(RW7D{N*EK&b=x@umfC_&Rw-Q%jqFB>oLj zf2+IJlZFQ4RITnn14_Y1_`7iAQ2At&XL$gA9o$og1`Xu%Op82$89wh)_&MO|o)uGl zEltkg<$)a;BzI(x6p=waX)D zu&n|q7N6TY@{h;STHAcBEq)J*bMS>Y`=CZMg2m*sO#6kG5F1`X->4E|I!fphUV@$i zT`|0b4km&{Xi*ud9U0mB@@cdZp8UExpU3Y<){cvo&KH$%{p60A5-tE#sBCEo0E0-3 zd0rw;3*2Cidfct1i)bB&te;1YNTO6^e+0)e0?5*X#iAeT! zRNSxdRoFqq)4T}C`7$Ez7ZGO%O38j5%+djs<$vPZGMldm5fI2&9H9G9f+Erxcwa^zY#INar?rue7z&mYtiXn z?RI|SUcm*|LX$w>{Tw=6duABiR;qn% z{s8Uhg)Kf$cJ=Izi4X7a0nzp(@sqlUHm!07Tznc&%24GN-->KjbJsGTyS3HhgR4*B z<_q>tIDZni1KvCYj-SBU7I*{rR*_xhS+vX>U^5$Bd>k_ryjTb~rqHFwaJMY*HoJW* z3R~)+IUxF@xJTg)M#fHnv)#DV;E;s#UHF{*wsJ{xMOy$b5Mtn*dl=6Sb!77&`cW(Q z5bhf=A-K5*Wj+>jBHd3e`D(fQaK1GCxG@Dz?%s~n_uwc?-HqJcI0{Wo zqTCfsduJ$Z?r2DRM@QP*gK2k$(vm2*1=HRdN}Dqd(%#aM_U2&Pn?h+xlpFDR7zN=Bykmvd{t1*D?@6I z8&m4!ws+)wMNrMlLu!&J+k$B?3#H8+4QVg!NP9^z?Zu(AB+Axc+KWPIbH+j13p>(Y z5KMc1C@qNse$p|pMGdzZ_oLt^?A#{YTbFuj11&yoBd&0ut+mm^ori8)xfoY;ABagxkqkK`qY2QE2Dj*5kP-7{vvha~+))J9ehx z&IsvTha;@TscG-y*5G5@|8qxUpKDT=)2)0WV!Jo3PTYvwde?`>M^@)cNtG#J_F|-wu4Mv*yY;JmtoncqvdXe zwbKG)0E|d zH$6E~=TpJwy<gMKhgKA z|5krezk@g5pQ-;)-!)%pUI%YUPcV-+C!0Rke$r;CLo}CGX1PsTAq*4K;LYvpqSO2j z^-T3-@V;&mYb}rSPpG$B-S9@b%vuPm+V@Pa%l}Xx=QX~+(xl{AC&HWiC&8lNm1oK$ zrSGMCrSsss{Ze>SJ=Vf2?|{$xGfJ|e$k$5kgxzsg^NkNP(7Nq5Q9y)`dwj~eVR$nYx!5VQc_}MRkx6PCJ7y0AmeAx{C?=Q)>!@Kb` zC02acVi!M_o|CSYx9|<{HoeL+0{qkmi9cHxD|f?t@(oI@;Id7#uC`6Fw!!Zw`1ud{ zQ~6B&dLduGLXi1?!S~{q@n-8#YqqjWX$RXxg_vVLAZ!sQnP1{J3o|X3T2@+8!0u5j zer>+pd=b2xc7we~$74iY;7HO?^pFCbV zY*{CrVE#jlwbj|avOQuu*QUWY2|LBr;G_PjR-{eRMr#??ZOWDA>*3A!67y{HWO&!D z2ww}Y!q@QI)B-Wt{2)JARLoxr&kMJ!&x04iZZ&+TiCR$_E3F?DtlDJHm z2)+`hXiKyP*ay-L{u33j2jpbf0dfTF{UE{44_|2?!=4YXg73!-+C|!^G;u^7D883;4SJI(|LxiEzcbYW+q}`$Xf9$;nd>mJ~K73}jXm@sJ zN4?7$^Wm&N#cU#&fOSYP&l`Ywcv+FpC6AU;7NCQ6h+3yxc4K% z%Rl!%M2B&vgWh69;dYt34*lTR3~<*Xf-zBmy9N;rJ+FnnR|!4Fy+;duH=@y*scG(< z=Jzf{IM2dejc7b@B*I;#g`U;?u0%xhy#f&qPPoev(Gr|NL`!fPqS2YaGZaX5%Vh^@F(?u2T!$OwkA&A)6GxQLY0Kq!QuU5z&;}5YgOPmC#A9 zMGN(7Xd?}U6+*cU8d|UPo^|Lm1BK$&YGG>-;Q*Oy){sv_UPL&F!FdotcOU1P5aEm; z*QmvFYw=u4ya}!W5uNIEBEp#pt{xH1tqu`RQ*gD2aEgMf(c-N}gfkOdH6omBOGiX?(-2YJR76xa1rgOvMnrWbL{!(I z#S^u7cFoVG`3ahz6%oy2(TpYfL3-1iS&L^vMBB)ygdX7xTIizLNG+6J1K3<7)rGl8 z@>f-3@ob~2LEfVApx2QTtp^LPpoomG5N(|7_A9y8>-h;Vw2dbBqfT|PZF%5lNL*LTSqZ)cdLl0}{A%tY2^PzMEk)#h;VjhVQLC}aBzRYDJcxZc>}UA6{W?$lOmkLT9|~= z;x9};Y4Ja+#b20s(&8^nIcf1frNv*EY|`T2j)+!7n_$uuKdvc$3=x}HLPTqFDX*RjTT;aep z1Hk~B)4A98rA^BY>491EVS!QNbY<3?slfmWbjr z5>|aes*f_0f=*lncS!LcA5=i(pb8GCV806XsbDVxT&s1D>a$w~yHv1K0mlYZ(60vd zsbGf+E>c0S3bw1DM+Mtd(2W2WGu*;_0u%6PUqqvRk=v|#fnmRh)^2Isk;r^I`rU4{s|#J=h!}D0z#Gto{eeFUu1nB06Aer!C4-BQEtpDZ z&Jy6IS4fg{Ej}auFdhBh4CDVFz)Zlku{-*I}SQ}4Xcee2?bF4s~x)>U5-{5?KZ-D1?7%H zN4E43tYq+6>2_Gb;2P>KS(_9D9o*8IN(Oa3Qp_kbP0RrnQH>|Y`bfYtr~2(Jq-fDQj+!Z(Gxg^vq2 z3D0kGPywU$^Vs|nuQ|Apl#u)Dw0atm1JzuR(|Wggzq z-)Ff9Z0^@vs=!u1!@_~Z{i{j;1@Gd=!M?sC$(r;8*xr93>62i4|GuPmfd&6~QYdLp zQb*EScn`lIDb4&pV0&L^=D?o*0n=@!t6*Meo2lNE0#@?hH$P>*#XN6*#Qb%z#{U$& zi~nx0dzduugEulF@D~1Nu-12*Gt4&A8>UxH&zQagR`{PYeHbk7FE!1=Ovf&;xot0{3wZeM||>l(DyKOPT=0H`JH3vDuH{KhOX8^ zuVUy*fjg`DUCGc%7k33iX9Sp7P|J6Qq0<6)ndWzzp_stMHNO}`rv&a&&F>UL7Yp1a zn%~6?ok)e51)9fvD$Fb}zUQf|hsEF+&&L&cOp&vSj4E=5rEpZ>rWsnF$sJ{ALV(!> znm(XOEp&pRBLYk)&`>~QTIdmmMg;D#hDNo}5r!fHcSu9ST4;o!pumMS6w*S23=Ik} zw?Oj+G^B+NGPGZS`2`vZ=ztcwpP@Yh%rVeVKzp^&Jq+y>V4i`70@|g8?qsM>fVl=5 z3aDQT?PI7{fcXX*3g{v&w3ngHnK0i#L$?TAkA}8ss9Qr@83K(h40Q@H-#~T2ualwm zE^aeJZJ99NKtlm_XsBI_*T#@v;951*!qCP{&d<;W7q^iiNO=Q8>r!FiIo7AvDSc(F zBG<4OJ^|($Xx_z{oR6U z|EPX2(}3~7d~JKtlojRztth;=RhyF9q^z4gE?B{Uz-;E?5>1kDe^@G7I^+ z0J97<)$eAKpELB5Kz^p7pK75mG4z}OvkX)N&`mVLtoZH?_%gqfqY5xyOW{M3*?I$`hphvd4@hKkk4s;pJnI{fqX_opVmU} zVCYi<`47$SQw-fMkbl?EC$-Sq8Tz)0vj7tbwC2FU z%?y1+ApfTMeT1Q#1oB}GeMk$viJ=<>@#!UV@%9a zQ5R*d8RnW!GQ!%RUBXuCEI!J_6cdw7OfWG{g*?W@5he~ZG0MaU6Ni`>rfG+T2y=$1 zGbn_ZGf167LV!7km>6W@AQK0e*w4g1CiYUr-NGK`+|9%;CU!D0z(hY2eN60N;vyz` znb^)m4-?y%=%!h830tT$vx^FMXA+E7uBMHYbfu7V7F-9Yi-U`GA-F&dhabjGfLXWY zerVxd0@uYfp6P(aGz_kB>N0#>Fx&<%`hz9Uprzd>z_FF4l4+sF;Rfk<82)bQEm#5I zchU^J-`@x0e795r@ATUpzlC@AZ-l4)TVO^0a)%Ap+WWM4E3C734xXkTh4JzLtgg2K z-iWUd^TlNQo3O(EWA=OOx54}Sm)j@pg6+q)|FF&3%7t%RPg$Hv|8DL!-ec@E^6(b4 zhkt?}(8X+0(aeOHa#=!|fs0p`PQ7 zyIVH3ADV8Q-F;}Pc$Xi`yxK4d+V~JPG6>H>D~p>Of(_n=V6dUd7jcE%zDD10z~v4! z4>yEdO$|QRa1i{R?gn?z6>5kC8=GAX&EbGI;B|%~&CZa!xa#CaOnAmH0tsujo0W^Q zaL)N6fnjH3LsNst=k>ZhZkH$23=?Y&-iWJdIMn3wc|8qoPqRDJ+~8_PmnRTt z^fosR18DRWSJf=D3+-rkH8nLfSFPPZDLKR_*&7YJw{B?-r48vG*%@}@h(Ou2HVR*_Ic0t9xk#M8W z?eV%i4Pl?V(K+mC^akC-p@1vg40Q`Pc|)#eO;BcVpGci8Lk_(Gmw zFy!_QVJsuB~ z!UIK!xFf?MXpcZR;Bi4Fku{jfW!6buI=ZfD5T1b-oCz#R&P8+;AJ z!#=2ltGO}YX$}uZ!eN)U8F+&CHyQ(8543wjLlb1v-i+Cp3?|5?xH-@a{UXpDfbuuE z0!_|lkiKEM4>vTq8l9nLS0LzhH3ytQZ>TX4XlQH}(1_XOA>BM8XlDCmixX4`}oS+)W-&b2HS$ z>jM%SeIe)uK5tX7A>s@>-Jw7r5_U#B-cTeIYIHfhP2o_aA><9f(b*7!L$lG@v6i>%?%-s&*y6N`5FQd=&NvKH+cdL0dEsDsyo>1ar<21Aat^b+uP)6jQAP@ zO^pri&~RWFkHl~=3<*Y>A|Y=>U^oy7H2cEP-G>{R-0qOe6&ZFyH-}#6g9D+V*$KTT z=<>RK!>s&nU%1H~2zcNSa>7Z{>Gd^2V>z3Ht{|vE7A{{SoPMC}!wq4Vt0CfXN4(C) zU@+`#gv!D()!6823J(W^&W3<1)D&_yy28*)8@x_8RBqVU9Dps-0Jcwh!xQ}9S760VVhvr!qIvqY$ObYyz+_(btq`4Tx4h=xYw%0-`B)lw)2^(x3~ zQ5ZgqflskQvK@2sdM1_pw#^(`dx5Bd5WEINqt+cC2}CQa)JTEZ=%h?Td*Q2sqZo&} z>SktxkWHnth9zGIR(|m6Mr9Rff;DAFTmN9kMU{&bm3R=W)uyhl4hz1BnV!k047N3x zBqaoZQf_1+ZfF8_Pn-B(BeM&ioW%zNYvn_elZVD4Yu4aXk;&lf@R~KV(b2IrYqr6w zLxDq){>d#6t&cKONx$G#BbTEll!ztq}h;5v~zkE`r?9jx}q8PZLxULVL>G^A)m2(Hb@*tjvZ{t`<*@P)F4Y_!jM z;djRv2Q2S8cp#1m;?rDAgz4Ay;zb+ZS!=;JKa_H^(k;$`X@ffU_R-KFn2$x*L9sU| z8fYY6c>x8?3D|TqHh=d2TQ>2VVgF0m-0cD~03@xNS(7(yY`pX=24>ep# z(d9Jq)(HQdl@pvx(5b5jodf)1i8^=~Y^bCf2$ei)>LC6e{?X;}(O8VeMo?0s#17Ts z6}*lAG+awBe0LTYj=&vCz}pyx`vX@~2reU@pa*Uv!k&hJrzzxi!9~j#ak}6#7V*M; zEZk_o4NZeH7zj2t4dcy5dJWt^R=_K+>*bTqIcGx+?3|Y*uLj>jevYp!hL6?ug^Q~k z)!>uI4?(=*@j$3Jy^87$qWegA7!3)>XNnz_G}b}Iw>YZ;+_n4xbcaT#;PqA|VoEs# z6!H5P0*W)rz^jVix8M~8LzZHp6ui@T0^Uw*3E&j&CH~|kLNR)CFH-L!>iq(G%hdaM z>Rm{^pF{6e)cYss?I-}8$2|*pI50L75%Ou+GvIC%^8n{?PXlgny4==WaHepNLJHO# z3LnA5vZ=zun3#}7@k5BWXJUKhaSwt6&w!J|m95*`zyy7BS6kKU2Kd|2v1zccYj4K| z!1hg>`a0mtu8Tu+G9d8=?tz8$CWnV-BGJKURc1N_t>*4u2!hKY_zP*YJfFn!q@@Bb z<8DPf7i@||nnPZfF9kd&$;;rWz8419q`=&+$?5P+b!BT;ue%wo<_C9e-_bq@w)30& zJ6F4_R=b>)$wtoFcWAJ8I&f$_P+1&;v-Tkw9&gW%N6;7p%?xC?C>2*#OAvF{@H@v0 zMh=Zm6j$XrAZ~Mba1e})2GQ04z7|}m$SSi4fl|;Z+Cdf!u`_^N?XZEf@SyfqfL!!BAH26qw| zX67MG+IC3_hu;&tG>%XEj|5AOyiPhNT`rx1RRE`8g@B;6PwJPpfz9_u$tN|y3VPJ4#rkdj~pz&GKI z{$DwM3akJ9!0{ch0RP5P%Ya4p0K+h|u-kDF%r3M!);XHsi+>dk*^%Q&g?06euwK9) z#oxl30WXL@f+zIf79W7G{@o>hR{SK)K-?r=FTNYT`**202lEhzVYR*eFc;ASZ{z#n z`Me8O-76If;M;%jwPlgB{~4au|JwdD`1;=u?ccROV!sd8;QJyxxBmpZO>l$#y#1^_ z26p9B_7VG#eK)*I&!-+p=xRHj9l2e}c9BUk1YUpU!~J zfX;xEP(KpMN*`jZjFR?}6EMH`czFBTyi@sStz!rV8yo)XRX1SUz`er%J7JakC z*`jZjdba4BrIs!FW~pS0zF8{RqHmURw&A1vyJD`+}S$ z3m#GfZ6`2ea!K!IUyw_hXJ3#@I>6Q-OWMyCflJ!Q7J*CJ z%NBu4+Qk-uOWLV?w`_nd0+-Z}X6-zm)C&)#4Lp~${ZcA=PEoP#5-PecrefQdf+)u@e`>1$fFBQ-4q2j6CR6MzhipO?R@$CUB?t<0bA?YvmQE}A{Do$TS#gSer z4sWNz)kDSVZB(RpQ<1h6h4I&0sQ79Z75h4=*u0sFoJ~|@cTkbhPDOef3d7^AR2*)h zV$@H?$VMs-ZJ=U!Jr$94RD{=35n4lqshJ9+j|zhq1^*il6~Atx;^jsve&(j)DHjz_ zG*Iy!Cl!y?Q}JjW6`!u9V!nooxz$t*RZ}roMa98NDh^aovA>*(ePvXTQWV@vB~-jv zOvMXDR6H+J@mwJlKUqb^vjtQi&|67Ite+y_P657}{l(1!a10`B))alg-k`@KoHA28#-&xHFO z@P#q>{2j2;=)Z#XI1w z{tt`q0}BIOM}G#!_5pDZe8sN|M)p3@36=(hFs>Kj3x0pG{{cqzKehkZ{ynhP|EB#b z_RraGhY|hz;oE(e!+LqM@ZEq2*zXV6x7j=F>%lI+)?NnR?aKh`e3R{M+v~Ph!KVLN zSP9@;@XfxvVLX4E?IX7L+0Mbc2q$buVKhHz+iBYd-|AaybJ?n3ET3g_z<2uo0`J8C z5^VUNhSl{S6z&neD11t|72e)I4|e>ggjr!s2nl%I0E1%&V06p?438Or@i7B1KxP0&$PB;`nE@ChGXR5R24IxT z01T5EfN?SdFi>UyM#>DpP?-T3D>DFtWd>lh%m56R8G!LJ12AA_07lFVz>t{%7&9{f zgJuR`)XV@3n;C#{GXpSirT|CI48YKt0T??o0E1@+VD!uY44)Z*@iPN3fMx(j&KEHp8R`?7YDZV`?TJnPT$cNhTkfVDiCnChr+z@+(J}96k&M=QwkO zA7voSk1!D84>1trhZzX)5e9~MF!{z@vqQ|o$b->0dKdy!!dRQhw+ESg>mZYVI>6)~ z_ftjFi~K$YUf}mK@I1eVMR{sB^LUcq#XKI{$>g^OSioI;KlAufACp(@VDj`uEW#1K zmx06lb{695VRH307Ld+&GmyrEaW+%{hT=?qwTsDpolI`tOd}d|_)W|syMuXT@a+tw z^KBG>rE@C-hxrx;MtMI2Bm71N4)Gfp0JB^MBK$fA!u(nWLi`#AOnfr~M&8GOf%no< zz-XPxUpF!NawC&Jb2Isri^(S%nEZ~D$;axMe6)@x2=*tn49xR249xMX85rWL85rcN z7&ypRGH`&eU|>IA&cHsti~+)zQoy~$moV@mU(CP@d=UfB^D+a^@r4ZhgkQzLvwQ&q z&+z#SJk94Z@F<_lz$1JP0}u1r3_QeVG4LRt$-o1A1_Sr==?r|5Ph;R#K9vHnlSo11 z02uI7F)UFLc2E%%sTi_TaS*KeA?5*riv3n9_F1Udn?%KcnTkFW6+4VnTx6hP8;^zs z)@~85J{MaK66rPR1-d%hUD7AvS^v9Wg|;KoUKr>5;7$Kb$>jJ0tmgJ4JjegM<6|(Y zzru0cG2++@-^W`AZ`iMLNaA0`SH+)*kHh%=GvdwSd&N`Yn7CK$6g}`1K2;?4*X%FY zpMW>zKWD!MzUvo#OnYT@xRGo+URSiwzgz8M^%bGWf zf|`MQFqpP>ct~UurY&QRaE6TtOlp^a-m;#j4E!pX)nv$k|&NN!5{#B zhL73AY*9RtFaIBnFU=1j)>|Wio|5hmORTJQC1tkTJJxT&_|DT;Zoh$BEg84ga z*;U!%F4g&dn=RKS?o^$ZS-0hE69-i1X|Xe}Q|woracg@UPJ|{abTU@II zb8p+yveF!DlwcTsrBhP%U=lE46 zg(Z$A)%_Dbqd2SB(Wttg<fh{vDGues3 z&mE!kX4a*gWqAYa9K zOSz57t5u%|_-f9xmh&W6t3LPh66AM-6RRvS-DRGtRyfWWa>k?UaFX{I=72W z1x@xmaMCl_7FW7U%vA$B&1E@dVvg$EB<2_8i`lBPL(I(26th%kyE!>8*`5hbnv&n1 zV;3{j!1XpEO_0*ji4?BN;|%LLgOsLvRMjJ0B;Rcy-<48S4~IzZbCLU` zWYxnal6y*k7fJQ7isbGRa<}AAJNxfRbJHAa-&t4_4E+DIVmn z$>j1Tayfiuot2clWhb#b5)-ZH0YCDl{Y3Q%(V{xvut$rcVv_27-5e>2*v;U?*yIlf z(+=8As_V6$)E>K0b-mh>+G01Tu3y%t);oBmOTgqpL2X~H$U!L0G;V{}`|EmJzxW{6~6ShUh+uwQl(TR<$@i69p(xC>UhKPJI5=I7ajiv zwg8Vg9&~)&@ny&79RKe4sN+L0bMPL=m5!KW-ZAYs0u})W96KG`;md$6jy3Q_z#2yx z%q3(wk{wnDFTN%I0p=8bEumZs`aZ(%s z`+&Xh{lIRq16CsNf`vepSPbj)rGt%t8NL|sCzyTsrTrza68OITar;B|d+m3_Hvm3m z{}@zU|LF|q4CoB#4CoB#4CoB#4CoB#4E*n408OCH5o+&m4pV!7bBNmen}gKe-yER! z{^lWS?{79zdwDM@e;Kv+ z=TB36e?Cs_{rMQR_vcShdw>37YVXgVp!WX!Jhk`d=c&CvKS%BT`8jIu&mW`q{`@4h z_vgo{y+411+WYf|sl7ixOzr*oFtzvRgVf%iAENgD{2;aW=MT1E{rCga-k;x3?fvwn`*ROadw=eJYVXf|joSNjU!nH? z++Eb(pSzRV`*WY9_WsnOpc*8=)>q%7n!A!+#CMsSvQt?Xz6)*E918*g9I{vr)jKE6$zmr~; zo|m2m>;H$Pdtvo|wp#uT(s{7`k4baz*8d2M{C7*eQYTpd`y?lf`(-H`zV>E;H~#+w zqyCp+P5mD@9)~gie>%S8xC1N&ZgN};@A}8UT42&K45R&iuo!4{G&`K|mj5b8rbAq2 z|NjuI#eWxA5ZosI8>}$+UihBiDH!38ixKevyxYGG#`o*RCb1S)-_M89y-nm{y}{S) zFT+~>KY&#S?}vB#KM!Aty9HM5KX1R%ehTdW$Lt~aTHr`1^`P5?_*giZvlWk3VRCx2II_7{r&7E02q!l_vry9 zQ_|MP>2{Xc(DvHyp)5t;oztcA$z|6v8h4NSX_ zng7Guhwzykd`A2>v;OB^Qmp@B`9o&?&%K~n|8vjtURnq4Io`v-PxvMVp5+@Ec!qZ~ z@HEd}0f13F^LUhZGVlmr&%nbxdkcVjNO=nYmN#T?0l>P1>@5K9e!iNO?`u^|ex;Jh zyDFHxlf3}|HWBO%09cc-l!e>^pTfbKT~o~Ddy1GmCo_3fA(L0GV)A4GliTx|+>*y+ zXD*YQa+vJMX0kntNq;7j>ob^KmCj^t8k6a%Ogd7Sv?ViXm6$X;m^6q?5<8VJ=x6dR zfyp>bSIs-ZbIs-ZbIs-ZbIs-ZbIs-ZbIs-Zb$bdfn z*ZYCafX;xI~=%=nUu#=nUu# z=nUu#=nUu#=nUu#=nRPbXGl7^j@UAU%dCI4p0ie39<>yhe`?-fy4HA`;Q_-I{x0qb zevX?W*Kup5oBjce#mjt2Sw%(M>5OO~I2IWhorn$&kB&uV7SOu39UU$G9ddt5Yj=md z;4fFoqhUEXdI)snp6&f|&p>y#+_t@^uYX5NS5Ln@b!2cM_8`RV-O;tJWycDcYc~s52ld@W%L}_Z%E{m>~XqmTnc`GjqSJf_105$)r_@FB( zYxQdG^ek=jWhpFkdX}{PvOs8m>hsP=(eRO#=$3bMZ0gw2(bLw^M-!T<91T}t(c3$^ zJK%tAYw2riY450!kuo^mCgv9(sWr0JfO2qbGN>KGRq>GqOIF;_z@4t3^g?R|12d7q z&{$w*W^f9Qp^4~>;x!qL48pNE4xLj884NA*OVn#xp+6+X{QGji`X=himCSEZQEFJh zmlUrmUgb>68f@U=+trSgnAYD{ug2wF>+ehG!o{z=U|CD*ld|?fSvS8!Sre+ASPb{y zQPPBDRw!peysFclx}>az2JTE2Jv zN9(in7^;yk!2kbwju%Q~(ILAu&#HKJZBkaCfQyIJgN~jY#sgDJx-2**hDQ(K@ur+S z)M(BXjx06&ZzO{)tVWe9wZcJ0HCmQ6N|sgoYv9USuyAEn6_=N(etBP89&vH5+9@`; z+MMMr;4W4Bxu&EQMD?j#F?}uSk{mSOs67`XhMG=sTUQZcwBy~|nP%E3i_%yGtClp)qJY2AI?GN-ixig@aK#e> z7X9zNkVga4QAl=dc6>sHYkcHTWLmqN!*!kpkBv@5E)W=*2(PS(ffv-PirdQIp0H@q zJt3(tzFWH>~=6(3b%t$80~g&A;TqXFL+m6xCbQ^ zU}@eOA}4q&rD zJtDRCRPX-q>VpUGLO|JaJ{N-2`zJ*Q8C?jJ)mt6)&i<03Bpm3)3onrQ*yQoZ^dOE7 z>4}eVNkI>?_sK&>FaFfowUNE!^=g3?pfV3(gHnUdzve3o)ny=Cj~ciG{Ax z*Z=*0azV&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf z&>7Gf_$M=ln~qmzT;%3U$y zmr>OSJCsn?c8ZfRRSRhahrbeF3WRbr4uR@f&XP)SL0~dypn58_AG8<#{6kRk|G%b2VSg}8b2>GQyD1Zzg8Ipl^0R>hG ztyJK~2+5=<8H$B4cHRl80%lsk1#K>D3e=^^L0Akn0vusXNZl-hj)jX|3n4Ag8=xqa z7%9Ei?uU5y662=W;oaA##bXcx1z+|qldM)u3{kmWRqjkK7P z&-M!BH2)z&0v2K=ykGWr5>nb8J54&FQDl%Z;bLb}$Z4628=)iKi+RGv%P0*{Cb<(x zI9rJw8h%jFUJ#W|aybFsq3~Jdv7GwAFPz?B&;g1&nG^jvl!3xSeq)aOVL4Fcrbpsy=wfN})%vvu- z{Vb(^3%Q^6HtHt4A9@c$EIleinlajlF{BNCF{mn*zBrqF4C?$mF+#7GW!i(xG=>2y z#rgxJ!ee|HQ;o|*Mj9bT;Ak(7b7z76WGF!L_5KmF$&ZH_AvZ(n$xadj$Qy+=a(+ty zDkt;AD1+Cn-%h@lmM=NRr}qokKX! zk8LM!v{+<2*5o{}gJrtxQZwxVMs%PLlsJZ@V{Jj%=%*Ej1Ta+&e6$}1sXrD-uBL7d z74hfiNNF!EN{qUxAGFni3sZ$F3uv$+gMi%8MdVa}3{n^(Bp;o$fADG`cwpt=C<9bV z$PlQ(ZwPnn7n5Vq1JX&dpZHTQ_sg_;(6%WM8!l3O9BRX#sW#jPmllf=DooRXU9#Uw zpgfo#=zzf95{uy_&O-d)i$5867VH5$k15-M89zwac*w*^6)}l8_5c|GO^wLVuU7Gt zTZjx-i1uROh`PIEPA(=5m>T>nBt<69BC5r~QFq?H1$tWy{yAu>*v$|Pu91)i_CcjQ zs0U=yJu4x&?F@lG1o)xT<3$r&|K>O(1}R*2sfqRg1A!xpkZV(*#4#CDf>SCe)9!*H z?XdSD81MLWjkls?W86v^)4OzyBpX|UMV5B{OkM-fDjOLH(g#L(xj1k=t2G3gHjOA*PS1jX>PT+rt{Im=Qb#M~=m+5~cr$snIJqKgoh zz?0k{mIpl`ir)_>4Vr;5hSif{!*+mk03ZQlBCz<`ZB2|=@rv0Ao__cPeMkj1BT2^y z@Pi(3284_lV#Z=TpUQmfI3)Yr%}cpgk2eSvY)Yu<&B{S^ngSs zu*ovL4L~OzVepgbegUzJRZr~PvY+;M%pM}keyS4FH2g8bLCf%+kfa|^A$~@&QaKcO zJ%(OQPXW*Y?AV2|0~ql{3_l~*h5oY185ui(414%h4qXRzrV3rG6=K0Jrrg`ga4drh^JeD;3>?B8Tv#Dds)h~Yk5STi zj*^W&kcEMRZjATH^tKPu;rVp9;io^iq{sYFbQxHKpPV9>oIQ;#031LIZ2=`7is4Uz zju4Yup@`rUyBDtIXAtr+Mh7h_dJK{Kh>=5GLar}`-RTn89V>#}3|>eN<-u+c&m_CC zM4KJ3+hh@>KaxsGBrxv(v-B(JXVP=hGtyJiW70#?H>9shcS@g;J}G@v`mpo?=^E)> z(&f^n(!4YyjZ255fV58rlyamrNKpUj4CoB#4CoB#4CoB# z4CoB#4CoB#4CoB#4E%3pz+~XK%4*zKRN=n768B{lxGycoeMuSai%W4|RDyfC825!m zxL+mXzMv5I`KxfBSAhH6eB9^c;XXST_gOi(&&G-|lSCF9o(;3hi&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf z&>8p#GhjAyRv6b)QBg%jc_kHP6;za#Q&CbzMR6$=MI}_o#Z(j)QL##ZQR5+5U5G5+?4k~OS z6@r}#tBneaKt+<33bTa@QxX+MGZhAt$!KMxe-lT2c%%OPzkhIN)U(qW&>7Gf&>7Gf z&>7Gf&>7Gf&>7Gf&>7Gf_$M$x-$tNsEBq5O*R#|a&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf z&>7Gf&>8rLFrbhB{~}8PFNf8PFNf8PFNf8PFNf8PFNf8PFNf8PFN{U(JAzGg^(~q0Qe}*S=j|{2%TAyU9}q>HE?rq?1yQQ~=-b`?O=y zu~Gbmc$;`wTx0)l`~CLY>;v{pW0LJz+b3+}Hjm9Dd{6kOFfBA%|7iWX^-AkS)-ua) zEq7Tiv1~|sJL&GEb4lY#-XzKVjQMlsX>*h5kEXj#XHC0Jg~s0*?=xO+95${oJZAWe zVa70E@EUUXxAiPIG+{wTIU0r@m7kuTo|hgy zJ`>HKSs~>agWD$hrdi54%Tu1FDI@EP(-m{YbTLPa9uEiVwq{}fw2G^-k7{keiCepb z{#|X8qZ9q}QxQ1?pvBqmdEYo{DCYZjVF!gKPP0aj2Lm-zG_IjwdE6-; zI~^QBW23>kkziU7Va?qZ9C2O)aUJ4T~(^)xziqZ}<`ylfcknH?J&oSm4NiiAdoM^&?!k%4iTcAVJ}cLv_=nYu0>QD!K-z$H zAY}kESUbuT*rB;dVH6blcMZ1m^$l$680^{J(=*WBJ=oFHzP)g}b$h|~=<&h8`VpqQ zc6qf%E~wnvvAL_Kr>Mu;Q`m#*YY(lE_n`}_L*Cu(-PZ1cZkqS-3V9D-P?_evb<b>2}Eb7BPbffESsw3RSdIiI$Qb%H+8i15A5jJ z?A>hLoVgiNA1M#5ka~y@E=fJOLh8YU)H{khtR0yhn0i5gr7kTiyAw$>gRBJGI{G`ew-2^=^|iI^Xm4rl?ik$B z(KpcDzp;Fybz|v9D9`xDgDj1W%hNc>(&*UTyM0IhU|)Yre^=Wel%Tg`NB^F6HS4VF ziq=5_k@W{y0tx--z=aZEMQW~Ywl)_wqq=WDQ*T(F|NbTFYQ~;2kJXdwK~2v-rdhjO zbKeq8mZQ7GZFOh6QLk|?(@W?Tdza{K8t7?r7CEiXOebnp?*T37b0y2#En^SATNoVN z*)rI&V{_ji{Eh`0cdwAc?j<>N^bBmPEvU8DX4caB>{_8dyOwD6_3s#H>#xqOwpM3X zqh8Zarnh=I4?CCWwROYkpdzQjT9H+OYU>7=npebn;ZHfO4KTHxU431xUEN*%dj{J( zHnl*n;Y&7^2qmjZK>OIb{uQ+QSJW<&i-e+8MW|ih$Fvj9bbV@(`#W~@{?_!0tv@~m4N*XFOZD$Gz zv~ORmFxZBtUAV$p#VokW4o18^OfBJR(z8sBp7iW_cB?(dPV?KgLVnwpDMEfizF-xy z1e#y>3i)*}Q=|D=E38&)4qUI{xZc8U2eKMi?@;=OHDgPcFgh_c8|~jkZ=I_Ld)Sps z=Bk_h<9oR}hCztfrH ztbijhFp<|uQ%t-^>~vRD)^5Jw&5KpsylMH(OIf9_vSSlX%di&i$=Ofo{F^e93Ad<# zeX~~xV9W64fZD;Y*mU7CawYLgK zBJ+v$?=0F^v-{W#LdWuNT(M(iZ^&K) z8|_#dSv3+m*2d-4SX_nWU6s$;nVpke0p|+nSQ}`Hi5+W$TTHHB&$267es*0CH@I3q zPEJd7mVT8xZ^t{V~3;6M%s zYS*x+Wy{lDgHhGnbGY5kg2Oj7S=GEk%FPRR=9V-I-kD?9_mlHPx87NEN#*9Bm_KaZY0fddV7d`T{3*uo8$WCuFlHDYH=H+YGnn|#@Uwg)Z{&W! z-O3%}s>xr;<4^$Qw>I9+#m#k8u!5X{62_}#MX_7DI$)0*2G&_ zTz0dwsBy_RAa3hu6l2x*MGhSwjkbit(~+5(x)FP^JqRJbxSuH}jHJoEi{Q&(XM+lBi1Xjm4Fr9?HMg9|YU~JapqoXq% zwb#biv)GBO-2<^Z+jj-V#v(A{O3FwY2_Fw^h_8eAFu_t(wB&3A6D(gdk*~mk9FEL& zO>Ccy&WxUj3`TKQWZkiC$J$}zG0+=d%MwVbT9SY@g?yDIz=o;xzy;-+W6j5Wu#Hai z#n&*+vgIc-@)Z*~pULKS*ntM)w>4{`vr}V{b$Ir!q3#(vy50bjEyuPWD?C*!e1*eSCrYon*VT16saj#OrKCue2*#Ulf)3kXyWg0`;;~u2| z83Z(_+5l`OklkUJMx_B6J3U^<>gXkw0^ zY?u@#(_m%|`D|qRiF|(1L_Yscd=`%R#+qQGd^W}1N_C2sSLX|u&auGQLIG07^2c&v z1JCBTODTT>iJxB}5zDVoIJRcYJSL4X3LBI>6Da(giG21QDa;Rgf{m~Ng&X2dnhcK= zG7^=$4Dwklf;#i(b}G$oiDWE|GBawKy2856nVoj5GtyK(P2*c1-yl=>ua z_-RZ>;gIE@y|-j<5p3YOCSI#lAYtnA4psry6|n*ei+cyfy_tI%i8V^52_*i9iG1pv zNgVJDG{QznT&5(jpr4CD?l602D`5=s1b6S@5zN!-41`#RVZ z604N@B#`(?Oh+M+<)6L1WP1^8l*CG<0tqDEzL-R;fI{N-9pd)P?MUK=cm>Ne@icpz ziG2Kmr`hK2E!{1!DW}`P~)WtSL%al8H_6F%!A`$sUjQ3+j&*UFsTL*VeKY2Lj8fUBl%3_gzGS_1eJZF=kG97P znR;5n@$_#d@{xBuo-&&2n`>dC=k--eDH6`>A6aNtPhWE%MprlTBA?81`b$%jdwd=Gdw$Mcl(C!E)BT4+Hm zzj82n)_BYw$-_=bxk{c16n@Y|ZhS`y^J_dcjj+*EQVvb#0_XJ`u?P$2bw;9cUaxVd z)a2GM4zpP~5;?rVMBe{S9F~`EEL{hi!eN$Dp9Bs+faxe4D(CgmlF}mBz;jJJQ>j40 zDe3*J0_-?&yEGO{tHjdGQbuBil4$~o?=z9>-A;>lc&Q;9(>x z=k>y(l)~IXMq(N(M$oOQ4%B&I6$Ng(lBOh+M6Ij`rGP04TB{FHE|n@o$#2Eyo<&T!DR!^vG!CuqMEp{QuM2}=M@?6a zuV!P-dKsV7Ohu<#z=OxQ&G96bUYeF3y|IIP{3|g%=p9S1s(Ce4H5ITy!1}nE>hd{N z%Wn#=V2^{@u}3edj8C`%)wHJ9vB0hnO5sEs6LujtFtL>IBiGT z{xq#R2f@RD(Xs67$)iO2hBPOwas1VBpW}>Uv%?Of|69aq(Pw|devkcP`x@JyZC|#X zu&o#VB79vqCakso8J_!}vHGnh%jYdeEwxF%Px?&KY?3?4V1C^EK695j-SjQfrKT2> z-S{ozrN%C!!SHRv6^3s9ZT_45rTj+jkK6~j9!`L)|1bU4pJtEDtr=y|-1Lt%Oa{l9 zIU?Zbd||Fc<_gHU)#O|y-D6nGX?%jta|W0u=dBC+B!eSn@=7KgJ%=LEt&#cO>Ct0> zXao>64mn5On@P^)k#i7uq$_?YYoLtv%gR9h$4vezOa-DNO7Do!R7NK!dchAz$X;@J zFS&dh-DA8x@l!M{uA_cgdB|U}Jn*P~Ivkjo?287Dz}UZiNrFpVSkBAI)w$&ICUQBd zZHiyQ%2`ykq?|C|ecMdl0uu?fbTAR6VVSEVv33$$OZQ;9Fg?0Hez8*F<)o9pU_yzc z$5xYACBZ!D%Of!)-5Wo_%8c?wE^4CT6h77WaS?r!r4epMZjl zd*gE}735E5^2V})ltk;Jt6{@}w8f9Jf|Ml`uf z4w#vlsgY@zq+*B7;xi*0GgUnuJ?miOG2VvwG)pT_J3Fe?|2?J^VI^OBc24gp?~!4{ zKv(=It3p9S6<(uNSg1kIep^p!4^D=yjZd*$v&xo`4mJ3lnY_wKXL&N}d*Nx}KG+P% zeOG*PrTYICwf{!_8(Oxtw8KX0zbQV!(kfUs`;$71)Pgnf#N+tj0vXa+twAH89W65jdqo8ErEbKPR3K`PeU=sKh zvrBFBdP_b0WBiTr5gOl6pM`Ppr^bKToMj{ib(*<#cDQ??oVES6w%WW}B-k52#OjxL z#`=Ys{CwGoBCEQjx)3%zQTXG-OtWzH^1Od;Cg07(IFX*fxN^9t*MGjctXizjph@`R zkqc%H9rkBt^3t-*>)?r(2R0s4Zi$DPYR=MUSYR3VQ&c@p=ZRVWQhCEmTW00z%5vB+ zd_z3M(x@n0UW1pg25=%{C)^&GVd!kfJzTyj*Hv07(<|{T*BcMA1gh3E@)i1_{4bix zbBnHBcx6?NcI_IbU2QA&R&0aKfLAeJJg^caFJO|CRqYB=ScSDQW*iNS!NiL41h;&s++3bqjsY9udsbrL88i9b1sMpJ9VS zuo*C9V|=Gl@dWK3p!P7_5=N${z$i7kkizP+4P}k6@ff;3KEP7Qs9n%#Q@}o= z9?IDLa9EU8R#{dC8-{I+_cQ%M7|gJrdK&y5Qm+xe_?pe}Wpia>Sw6mk-J z;8SMu#0B{;E%lT(z{Vs0-uMos3QI|VDm;k^U@?_b2oydOra)G~hQ1r(7ctF}gqg4> zu<#e8k1zF=3Z?0#80d}nLIN;|#|L`W1&c|zXnfb4Y8ZkW^NIQK$=R9O2{;?iOa(%b zisIGiT@&9W2pt812Y zy|729f2mw=?cB1IKd99kJKDxHF0JR91pN?NiaUBu;mE=*9a}r6b=63ZdQM9}cnptf z7uSzoQ;;km=Tu}*6xWFO?eWv&e(d2`I+lul7X4myW3(*tO604Nlfs{ee-`cxkMv&m zF8Ahmq0oWQworxpq`Td%c0O>fbgCQ;tNgdxW3BhCJFV4LnfbhVqj@T3{C{U$V$>Rj z{tNvgeY*Bf?KZ7N>o1%Q=rnUT||8X7ryJ@HAlmPF6@7_A(wL)#wJI{IiGWMf)J zO{L=dbmV}5)<-+h)+XeApGIpdrf4fBGUON3WYkK`Mvpf4 zRBi5jhTQO(mQfR|J(H+{yzTx8AEwPcL7O|4p-Lh(&)PGI#*_17B~e0~J4Bo7GbB6H zGIGvULjx!;iN@uUh(19Bw*(Z-$aD)Sl?kP|}KrX~jbrl6iGTPQ_|yEahd%ahxT`Xf0{Q+VN4X zxQ|v$7OFC8TDE7t46(JcqvlJw)@!1MHHQSb(KI!an~3e6D2Pruc|yTGqS2)FD%E-o zVn`-c89Du`q2ZJlkur|RQPWh{dTB+T)~mPHi_9lyXeL%Rp5Vz56AxPQSdAyGK)aZl zPxCWs5|)No<-^TRs8y-04Z=9Xu{(Y1HA7sCUshs+?9Rxjnc2KyWBBKjfB7SrD>UOw z%~;J)C&M`zH8=TDf>(s(69YuR0^AfI}svN&)qp1{T{|Z?736Y+H})c46%O z3AA~h5MP$qA{#R^Y96>`DCsf%a=!Ey76-+(_+2Tn$}%-u+%wAx$(N&L*=D%U zii77%q_Rv-9*25nSpj)-tSmc49NZudwlP#$Mia@H+U!~I668^SmdzFimxzOl8LBL! z31)cEGt1)SLC-8ZK^&YZ4$fdmrl(|R!Wk7zO^+;#9VN?xd6T2(*+KDmkvKSB9Hcy( zm7&>bT4ma!Sr)am@G+BVqS!xA?5}1h=cqC?bKNtU@WG3aNBPM#MC`8+`^PdQv$_n; zWh0J5d&DuU;>cOnrdICeDdK){ph)a56Z^^1^bAdOA2lqVt%Z&fmU}A1J!1ty85WxP zK5AG(|AVmHBknB{_mqiyC@fVOH8W1?EdO#fkZWu9QPsIZ?5h#`PGX3~1Rb5zGio|K zPY02>4tbKFxJj{Ztk^e-A(_p}$SHB7v`6CFhZSh?#*H-dnwPTs#E*-_K40vEx^y&V z)ZBQ_9+G8i#u4g5L%_!x7OoJxPZqoD7_#zA&6qO7^4j%?pGn?!Z`YTL-DAb>kqpVC zHe;yl>M^Hm9F84gR<*5~`m~2X+w|4hE$-?ob|=Md#BXh5MwbEU89$w-w-47EUlhO5 z;_^k}@|g@dex7-Ywn_g;#6OFF6n`)NcKnU_%kk&qzmGo^e?0zh{1@^2;y;bw9seH2 z2X2Yq6u&OMJAPUGqWFdJZShX*7+4)|jxUcdi7$-T#jE4f<0r<)#Ye=4#0STVeL2kCnv+#)@JEv2e_a{ww-v^n>U-IHTaD=yTDh zqrZth9DN|VKlK z$W4*!BD*7(MJ|e57}*x-jI58Wjx<7|Y5k-A8AWP0Sp$hgRe$dJfjY~1P!VfZiML*Z9&dcrf|C&Q119}M4zy%hVxcZP2Z?+xD=zBYVicxN~pK0kamc37+p zw}qR+%fb!e`Qh2&%J9_igz%W~@NijpV7Mq;5Dw$?g@1XUdLMZ2cyD+wdCz%Id%y7> z_8u^vH=Z#L8Lt>mV#mdG=5F&c^CGOh*k*Q`>&?|>v$-7mFcxAhMzuNJJkcCyjxdLq zgUw>Iml-u3Q!_p@J~aMfJZd~>+-KgV|3m-CI?nQ~{@xF~8@=t`T5rBL!yDt3ct+?S zp$|jvhF&vv8b2_m8TH0$;~e7_W2E`H`M&vvF~~?5x_PI$-+VmuOz7dz&qCh|-5k0) zv?FwWs3WvIG!t9R28DXLe{-L4f9Brl?sczrFLKXvTihk?47bd+okPxp&R*vdXDxP= z)Hq|DGN;&yJC^;K{TKTc`)T`^_K)q`>>KSX?ThSodx7QJGwcd`s9j>mtxv62tjDY$ zTQ^x*j7BZ9=2%m$k=+NJEF-GBlNe5j8Uyvpd3CcF)XZ#h$93bzbmKc_jugi*%gnKjjmm(@4Ar8z;(0x9r| zjR|_?%$ZGi!GgM``3q;(G*ws5o7dnDjv0g5Ey)_(K@p>b+3^!5x&t{1QHEiLA%f0N z8SZ2FeTF|~csIj;G5nn2-x+?w@FRwQWB5M9zcPH6;oA)V%7i#!>|`+L=AoPTASCkX`gpz|GPW8msF=_3kHtbwyxLCEioqRH5*##qzqp2 z(}76=EGofxus)A+{%B@;%P~!w`xv8gZrWDWH*Z_Fv3Zrg{;1M3jw)?q=_&O!)s5JJ zSzS}#ShsM2{%#m&vCys?gzKA!Y3;X%X|dBYiZyBHe9DlSOXkeyWO6=;kb?X{1x7Ma>-B|6Pl=fs=i;+9+kL(Y)(QCB7A3=YG-b%K#*8=L* zfO;mNe#mZGHe#2V{xPLmwVZ$$BX_Yv`nP^YMIcp;z>_Or@Bx)oW!=yy1$Zz1f`v`< zD^J1E3@GC>k@B_Hk0_^eW_2KzG+@_#O?`v)Sea3mZ?bsdk_9sxny?w4Em=?f5A6JU zfHC!0d(_1JM;gw@zP0AnZ9(jAqD;;0ZtJ!|O&p-wE@u?%ThqTm(N<#pyHrb7VNcrO zO={vk$}if4*4T+DB<<^zU&Ky40f}}?j-wrF;^&n5WL7a=bg{FA)kwYBb=BI<9IyqP zme8!a`IYl<+`;U+#s+gXM`!`cGO1xr)2zyd#wOZkT)B9edG8?OB)w`u)2xMy=T|m1 zQKh?ZNn`zz#-{4p%Eji}R4Y_fH;i6me8V+*5oz4Q{#7>AU?#S5UVUw4RZU~l;#t)v zOgv$d@i^D{I0C10%>0d5X@=PuD%ZP@D%PfOr9-y$YXef7ahRM=u{@lZo4CH0+X?jx zj%2=cBy%>UTIGUe_zcZnpud*m>gQE1(BGtJR94k#=h;SFueAF})Jvj*B4eCBdh{5q z4Z@a6g+{NjIpZL&Z96BoyHMXkVIvs|7NR)68>>Av(3no9IgNk{kwrOOrF&FLjx=rU zXy1fQg?fk*iVcob620@NV(mt%^vO1Kf;2?$pvu|#FNWV`css+}7=DxC%?$T2{5r!M z8Q#F~I)>LWyqe)|hF3DYoZ+PmcQCw|;dX`@hG~WuF#Hn3a~Ph*a0^2j&P`0OXSkMO zJHyos+ZeVoY-ZTRa2dk}hE)tx45u@k%5Vb1@eIc@9K~=1!(j}MV_3$JKXA@qrnw#A zlrUY)kUxA5fB2kUEGb|ZXUHuMhg%$uY;ia&w-_1>HG(#`HEeEc*q^cFQ-&Wi{E*=X z4Bun;7l!XJe2d{BhHo%@jo~W{Ut;(I!{-@3$M9K(&oF$N;ZqEsBxrG4#(0!y;}M1r zG5i(7pE10b;XMq0!th57?`F7<;SU&c+sgPJ(|0mt4K%*PH0z;p8`Ixn$hv3T#Pp2} zzsm4>hOCFiWlXca8LV%{B`mp!A#0t%T4!{zs73!{ZsU#u`JJ9>TDkVUi(huQ8bEK@0~lEMZv8uph&|4Er$b#jucJf+6d( z;WF(I)L&)z0K;3rcz5`s@R!0J z=n1b1pB6qjyf9pge(;oVMR;U*NO%x>!HIAvtb3n%e?uSmb?*i5_uiAeT4Rr{%dc#H_996MLiMvAoNP;sn7$VeW6>>E6s*FL#?5P zP<5yxRE7h~4EF=~756Fk0e7E!3wowmx6^HP8{BHQ0)5k7u7On#uQ*RR4>%6h=s zXWe35jb3V})oL|Z)mDX7WG56oB0r_2Y;edaCZ)n*p`_g1q3z4i*T41IOO_yB$U zr;G=Tedu#vZDetZVyn?$R2vmWnb8aDEk4j+(SMALJ?2B7)`t4pCY5mq?JsIQ4K9B!O9)C?9e^nm8E06zj z9)DRLzcY`&B#*x+kI&}uU3vV4c|3R8k0=l4<(Z$I$8XK!JM;L>dHlvaeqA1aW*)yL zk3S=iUzNwV_|xj{*i>{*VUT8fq)&*DdC8o@xwzH9A-D*T1&1qG?v$0u1Y(S~Jt`N23`H z)>kF^ibPjSbh$(qNW`Nc4c0l5J4>QYi8f2LQKIz{RZDb|MAIaiAkk2X$|Wk5Xs|>B zBM$zD8z$(*6zb3#$R(%9!ruI z#Je71=pi|^hh%=fME|sh zWG^+z>Nl=i+q`PymSDitSVBXghs6yOCI#bN#wj!u8kk|R2evSD(ve-p$uv+Em>hPo zl%kuH8_lE}_01SypVdrunKsHpaP z3KvG67dQ`4v6^4G7_(M1THn}2qwn^Cqe_2De(>ZwmZ9qnG~_q8p=n;tg4vC=*oM@N zJ+3=DghDm9W|?y$rQ}>^G6kRx^B(%LLSv|2*MPxPIq?Rq{hVqO+(@6BUh73EDko#y z>yMzXBTwt#;3LxGBSt5UJ^6ym^99TD1o|iWf_y*ptMY7UTUk(3Ig^IpQT1rQq=4tP zyCIMEQ-bAq@m@FXoo?KpyK#p|^Jgz!h)&8pj9l|@^}MFqrY84JO1|0FE{QS{O_pc` zI(cQY7B8G%TXPCkk;oYPmy``h%v2q_=KT$=_?RYc74d`do$*uSV`Kk{JrVmF`t~K! zKSjS2ZH^X29*SIzzIr75SoqTL+;Ggh-#gn&hMo`oFw_+4kG}HP-DU1Loc?#6v%x90 ze}{S5#dZ{@|6Oj)wjA>Zn0p?HdFiW+rG~EW*SqwQ+8a3euU?CY-{ZyqgJ)cZwiaW# zXpa-{v@LE@7WSYRyT$G%l=N{K+E{#4H=Q{*K?Q-yWpY!Q>5xk-O2*{vh|)S7ZL_xp zBm0fbtL54e8r23v+ZzX-JO12}_>uX-jM_syVk-6p>EsVDs=+A&n;mU>UtPIt#J+vH zF`)aB7JtLWwsTtBHf=dqd?k8r3Hw-;Q5%f&HeF(2-X=%e;H~HCMFuKY)ljG=o;CNZ zS@`K>p(>;H85dXNM{Of{LYrrs*JG+<%9LD(8~gnzZf8}TH5xy%us9<(7@MWD^R{?v z8z^ofE%c2zjm6;arw-;gBDp7S zuHip@OI1egIqn&&b>#NgvD#d*c{F}9R&ulPQDU|B-;CAfErpv)HdCx7W#mTWP`vx* zI>hQsM_ZGN)yaIGAm>*5O#Y)t(RO4k5$hSLc5-^`NVQkAkH$|%iuNe$+EF64=HHA| z`^Lidl6H#JjLd2ovV!iX&|+!!YDa5B$W+m2z|Nx9Evj@(Y^iIh#Sf?aS7p@h<(^SH zLway5Eo`Z18I7Nen%uv9l&H1+n^9|7UD#66LQ$KZQ9GLpx|a_`ZIz?71X1fAvlX=~ zX5+^(Yduz`wMzHLirI>a6{GQ!F>5(irnUT=FqCQ)V}8^4yEay8Ot4QnYU!z_(>BNPF{#8sC~^nOUkKa@z_yV zSg~+4eliMjfAdkIuiIiJuxTHt7NxY5Sl!A@>! z(Ya@6ov7vnHg_x@ziIqx{7h=SP07&yX}LUG`IcJ}J)b`@FU}C8QMsVM(5!CbM>@2l&X3|?!&AdZiD8Bn#FCM8@G0}QqxzSX;3m`^bj9EGOW(fc53A6;8FRxTARyNHJ#H& zE6A|OIcO0M8C5W(n2!Y*pP9|RxPznJauiMI*-*IgK_u)Uu_5%Mu+PiTermkBtcOC) zavVQ+SZc2yr$KWlQ{eUVRn`xo^c&=&z~pOu*@eHzju zO=c>ekabvdCEJ4e$&%=uNR%YV#<)ziOqPK?lBJrGCD-=16VU|yNZqDns@M(f5XsYL z?AlKC(C3OsWn>0^I_YyNQz;$yRLDwld<=ywiHwbu;YY?(Gb#2$8`wi3Q;v30-@{`g zhZP|bE5zby3jMguNym!uNscxnh_M@qN9adxMrLM6H}bdys#*F|ypBxO$OoIdTEBT(aBl>p*+BAm_Z-m{j7s09h zBQsOkX3ujTw5bl?dIj}BbIL7rQ8jf;%uJEi<>eqj3kO<1g{^Z)j$C2p3~(q!Q!|q} z3Zb4m6eio+q`VypMOsIh)-jkN?NFe^v2|NS{}ui`^qKRi^)Z!GwQ8^wwZg zXue4AHp#vwaPFf>ff7FWbRkhDy(%Gep_6NV$cF@2Qw9T^ZU|b@Eg~erqou?ikxp<3 zU4*7fXg+Mgff#0sR64z52RuoErH7p82p^suJ}B20hW}kNB@!$B(lq2N58$Ln;Dj*! zly1&^aF$tF1s37={2j*`mZteae1DgDVBmpLQ>5JV3F9^YOg9w~yLBHwrVIU&Vx#1&?mEFPr15SA;_Dbq)SYMPZ6rN~+m!z`If6GiFE4+7wWNWdH9 z3mnKu1fc>?780T~fnX7t-Zd7!f|jn37Mxv_737w^gIO9sfixnU@ZI*|#?{hS!K4z} zfS?Q`OTFtODcCz8%vtF(Pr0U7R~jY=czc)};V9ad6)I-T$-T~q0l5YQnoxRNme2^_g;=}Sr=4kA$iQG^16tVpLtt&ig~ zFLY4gFS)>y#Vv_cOdrq@4E+1}a zvhS0xUH(dwEu;lXd$LR0f#gph0Euf-Yiuh;#Y~7j@I5hBq(SPYS1oqUX-4FHU6?*Z zH~%_ufWwza>%AL2Qu#u>qU-V8FgJB7WOzxf@C!~sJyj{Z?WRk4 zof1|h(lZ4TkB*lYeA7Ru{hfgM`ZVSlx78KSD9t zh18)iWiPZ@1JKig001FQQ=(IB;n+-Z#Nh$?szDMwWR!cPp}3TJjU3XG(nNVZ2PMrs z%aB)xIr0@>7Pyc>2EyBk6MkVT4IfqtF@RXs4?SlH0Ywg_3{<%gE8z^!N&@mp`)O!E zZ;?oeRKZ0lpQDGoEr4x&r1UPtM&EwCaS)#t*F=QbAAm$EBoH2YAKUE@=QZ?1PLTWm3TS>3E6B4#|ry(f}U>`zCzo)gYbI7MAgan3+TxF<<9veo~Ah z-|)Diz!zgn*_DP;w<|IS+Lp$@hOA2Ofi-+1;RorU3JFJ|M zxxc3ip23Igg%)doAyBe}xT*jlPW$8&s#G{;bs>|PoZ&i*=t<#Mo`4OFI6Md!r0|ev zhR`y+%dba#)A;k#Qi?#Zd6lw6h&6y46A1rRDfUYE3AhpkE6dH{2#t9Hctjl{A>Sg) z>~y0$b5`8P`+p!CB&>l`E4J9eT6B=RgEs3Op%2?PbLcRR%P`)R9+7 zSJc}54xqZkbD%VBB8D#~(eu(ttFAk|JY7-$xS%`3O%$eL411)B7kUX^|3(NdrvQ06pD6 zpE7jQLR@TBNh3PXHCP%cm#yl5tpWa3he!2^T@ zmg%kOL=_bg3X|~dZxbSKd?b)h4B3HiDX9U>NXuhQpEQV68Ng4;)THpHNdOJFz_3DS z2bc!AQXc^#Q~3ElHv@>J5{AcTc$kte&Iw7m{S@mvy&jhR6gx==E>dZsA!qbjc$uOq zBqftrMUD`CkD=9E1wa8Jq{5^CCRN0EOo|IX{ls>k6u<|>Gi)XKmV0*KHT?LA?X}<5 zDftjub0wm-OTY{A&eM&t)as(Dk$r`VRlydXv})V>D9hmJ-Lh^V!<7W^VTGscj4~#b z=0wvqPHd9k6@~^irZzG+`{0kR_eNulAAHi&DFod|s^Req{IVSx$^oPSa%c`5=?F%u z0E&?Is}Mv;(g*M@-%g~SmdU|ECBaG@F=A@umclfQL}2j(JhcRos5*QZ3kr2*8WQ** z`~Q}R{xkd!H*AMYhdyKfdH#<|f$W7b{tiS;s4u=QP{K@F>9$Cf3oEr#riY792-MYQ zE|o$*(>wqgj!5A(U`RHSg(C(qv5A~krFDW5*!cu+fVrAV}T{Fxx&vYgzd^?F@Y`d~FS*7RLy3;JDTC1IYj zhh9!b{`Y}*!8?7J<_fP&jIAmyzZK5X=B@PR?TPx3NaKBJ9~S9Jbm=C(0~rgIK3G3} zfD0tmR%s5J7%oyUn2B~;iW_JG-ciwq6oZgyP}ICkLSUx#L~nhV^Z>@`)Yywq4N$_S z`~v-DRfVDu!wZpEgm5NM4G1&szdV5Y?LMWnjEOcTSq=~}gqiI2|j(q&X424lumP(jn z=jz2QbCW5?X@&$+sy`SAKyaIf;zGYPLQ0RH5ZjrMAs|y3JPKI1r#z6Pj+9ex06vmz zdul8PjgrJiwvcBeFwqjoRs6~m6DFvv)VUeIuA3go_j zrj|m?#tKoY{DgMoJt@y831S@>B~v63X`d02B_%&T9210qhBLZrKt?(CSp)D=1Q)dm zec>p;GP&T0qoxf5zEzVUO5tP3Pnw_}Xn>jcGF2sbG>A)4RMBE5zr;*vz^$5MJeD2nXTh7D(^ie$@bk1W%VQ z>~tCg+2ooNb>m7JerUEM|T=AAj>kp(nQGjx+dO|0(TtApo6fLzEp64L8Z)JupH_`ZP=CRG?~z_ zE`kMj+ID)skBVQp5QqgC9U!S#Ngsp+atH6#YfYqM3cldqCw){Zsq0kJmctH;5Czgl z?>tdS3M0(4h{-&tVgmqt<8O&LFJVotF80Hd7W4dFEiI2Uj6FaRFh#;DAC;+17|=NjFD|e*-`>YJ zp$YKZ)T+9~C98Ztdp+{$+ZsGW=A;gosqM4U6a|D+m>IuO&mcL{Btq;DA~dJ~;5~)d z=qpk_^4ZsXBuy&C(nTrZKQxxqx=@e+tgod@4KOLPlR`L=QoEqZKK^uQdPu;Ok^y=S zqjED1cl7AosYknJ&oG|_nJP37C_N~GUcqxJue{w}Xoy!r6NSwXU<0_w`i1*>2 zBP6QmgQKmCn6OC?e}&+)&z%Prp=f|JNd&uu1giWlpSVge{P=6qgfAbfvDX4|6r3aE z1P{urOM{(w4}6DrwNx}H|NX*Lnu0*77@{UX%7hm_TcZ5tL~)5PmoEpk5*aIV3lTlY zAAPbftW!?V3sVRd`sGO>O0P0fT_RK|%8$oKzpsyG64gCE(pyi{5Fq?cWJKy#aS8_U z69~tS9TD`|hG85?=l52@(*lu5N6rKR>nWiNs+m%FF9e9q6KPaT(0vQ8@sCG0(?Z*& z7t#O_$3B&=O4a*aRJMm9oBc|W{9_q8=SWm0u6<$%=oC+0r%TW5SHBlUC56`&rZM(} zPffoh=^F;xkt|dVrpKl(@0}Qi_ot~gfj0<9$~Q621EL(XIB2>Nx7I)f)Rl${JlP9F ztO1TN(qJi@k~_$NtRsDqHX;oM8o;6UL9541!3<9d5o0rG0Gv~eN@~j)Dv~Kk$O~Dy zkgucxoQ!NjfV7SBj)qc1hJuR6Ps~X(VUlH^a#oP{Dbpu$0>urF52{?GOLq}trU<6- z{}Aw1sNPYjrc$UoYFA>0GKCVZiD9}A&&VeXzoOxvs48_$8wsuX9FO9e!{Gy$Q6U!L zrDVA)LpAZR6g-EHprZB@WGO=$NR3E+I06PiJxJ13;bJZeya;I&9-;3MW)cA`-K8f6 z^qodA^tCEoJW-kKn(0#n(*`tPe7&9ljTpKJ>-OF#(-ng#Hw|A=K#p19$%|$A0*GoDOG@ z{XTZQ-+&fZ7q8>h zM}OvI<(cqCpGtI}_@S{1f%On~g|u%urtYl55+b?cICov|@wq!OeNDG&tvLg0{aUhh zVpg6IuNCp~QSyyF!^)=bhqUi`R?D_6Sb~FFV(8A_!kGhS7UM_4$ys?gyk5e~X9JfA z#`h=~y3@SstmX}^ZR$caYo;~Jo0*tN(i5}tO!%X`^}j;eo%wH_F>ppPe)QHUS$Q11 z(etf$s>pY=ZQk165xjPWRpre{%pmE6tUCU^SNF)`4DRoSwC{Kp?m5pzb;6VxQ>Njk zLsplSr@m{&CvkEgkw|y2yOynIoYAIkus=$Q_n9(&$|(HE+Kj9`{M{@r>*g1d?K>0! zP3X?zHs0}ec;$~Z#hMnI(rXG<#jKi?m50A;#oZ$T4{nDCuCwusGdkP0N_bi zsbCUGr)K4;@7mxVPQT4gWjt^-SK#`@J|{*_?0q5`)MbbAa+>^9z>9A?!u2ur&YD;@ zap1({MDlBNR-O7z*RghA<@l|TwpTY~5==PVoe-Tsu|6@Y4tOu=z5)-68NV6QZovhg z?W;Gmt&%J6jvq8(&=~x1EouAesoC7A?%nUwfrj5oE)SO#USWov9sLFk9aN4VnWVC1 zGSWkPtkb#$0mY3Q8`p2zx}^;#J0aO9@70yOgDSj1eFu^0jBJv+p?41I7&~8BjK$Wn*j8_^dqGT^rnEDc0VQwx`etHZ=9uu8sFE>feT=_=aZb z^!B4J$J!I3OxlIX1jCz~f;)9O&)C?pzUl1tRa4lj55wY;?rhrcH^-3G(%XEFHX#H0inK?8Z>C=b2ow0WqAs)iORyN30_-Lwf|~ zs~niE8#Z=eGi%$bruEI6BK;!7kIK@i>2M@(**kK5+YRg}cXoZaSD475Svnsb_Iu^M zq?dL*+lSX)%mW;mr6bT`v&UV-+I4KR6_-P{b!-V0hKL%TrGw59=^l3^YhOX6=|sTM zYn%e76~v-!?o9KA-KsH^s%u>h_g%|1pt3SJa((d;+q!0;tPNV`s!!m{KHDR)Tw4&+j_ZbS-uA!wU!Ohmi{edV|G>@Y2K&%jUlKiu6DKE zyzcpka48=UmL{=(tJvScP^U%DOS0-%^P&c7ohl%|si2ZQUJUg2^^}I*Kce&b^j~(&(VrQ$^xssuZxH|s4XT+~0pZ=#2FBd!K zh@G<-lAEfmJQQ6U*yBv=FXtkDIL<-dik;%#60tKWb|RW*Rb|!j=g436*XAh_D<~kUf;e2^^p9i=;w$_dY?1soc7L59o1QR z@OdaN2vD)hTy5vEgRo%Js!h%KaVl43b4Q-{h{&bn3oSK1QoFcF(U!Ogo91q+#g8m3 z%*u1mwZV1yU(%f%k@d}M`QFP_Qg`xHbOF3rMK?KYI%~wHicMs>DyvRE&x;(2_6}FO ziLPmVdhCe2BnZ|~L&Sv!0UE#Z`0=C0Q-CI9M60qmq5#Q~uQ*8CtgB^?owmh8#|#~b zAEoV-tS-}*b(f1kt^<(a(6o1Q?Lh%ix;u1ubZF7g_Ri*xs;qXb5OiU=;ITt6B3YT7 zfggonURKBul=R5mG>3q)t!~4XFU;WNkh0|PBx_ve!efQ$LRY)s*kKwuuxj8*_)(bV zXD*Oo8rUOD7X)Ei@_!eofqvP*;|Iz>oqw!Ao$qR2I(DFndynZo5aPq+TMx_wy9wj851FE6EkN?zj`*z&SJlEp%_DN zCWqkxVUmr`Y?Tox>k){p*!F(JX$hgep*7fR?%92?TB<|)&-wrV|3ClV+wc9@y9*QD zo@pBYG(IujH{Ld0H(oUUfSVW}#~92(<7dW?jPD!YF>W=!Zd`9%Wm)FG%ujLN;5+6U z=1b;t=F{eH%!hIE;C>^6!$co8Cpf#E%P{716HZm!>0Rh;#Vr8M-cp>+f1NqiTVU?9 z!(NRy)f?vx^#*x;ys)Q*J`VjgbSU(4=(*7ELXU-h>1+?}$4QH~hrSWIHguVJcBm_K zc4$3LTwE4f7^(?P4UNNX00VLEVhHyYbm6p!R&yC{DX1}zH#)5Qth=o{ti9HC))hD> zahtW#YO|JGi*T~Ubextr+!}24vBK8J);l;+@mcFPw&woJyxshUd9CT2eT^N?xy~l% z4EIC#E%zndHSieDbiBv?p8HMrMt8S+iF-ayc5HW>aO=Qax6(b)9fk89i`}?uIiET2 zId3@6Zx7o|>MfPlaI?jC@ZV$Hm$bl|81EsmGv&rSdQQZl2q^DcKQuO0pJ{r~eGfAB{ z8UDX?%s#)1k2H0D9E4^}K#dB*J<=wtbWmm=y{T#I26uR1G%TRX0xB6$r2*wLwQ5z9 zJ2;RH3aEihQDXEDsG@-CO;i(7(2Y?z+$h5^LwOeQzle4|XZUx9pD_H0;olg(&+xAd z`AlHvZGHn~Ojkgi7f@RRsxzS02h{3-Y6+;*0&00cEe)uKfT|Cu`2jU2pk@VBRY08- zP}2g6&)an;1^nd!by+}N8c^AQ$^_KaL^a{sZTDJ?A1|W80^?JXG4*jky&q8T2GpAY z^>RSH5Kw;%C_aIR{d+2q{U)Fu&r$j#GGWpdll22sr+qP-5_Lg9 zZOc*S5JjyvD-_jcvWCL#DzlH0tuk4+AZs;Q8$mUh#mcnF>=#h1jcnSNP9p+kUz8)x z&k^S+!Yp^mA&t_7I3P^P0z$GR9puv-;`57h7fz6arlyYOP+U?Q+Cw4s1S1*xp91Q+ zfZ~*;aSr`ymZ3SXah_4bUgMNy>Y|*fP_or$>AR#1l+>Dz)vfxKO4h1N{lP{<{aTh0 zwL3>?GXiRQKuuw4!?q@!^`e<5zGI;o7h;=#VN+0s$JDwlZAL^=o7UJXbCekkC}nTc z8q>(hjE@59oq&2Hpk4|nnO5sItuda-$;6I;lBt1A!0;+%5eDrstk2Y{4*M4Yb$>wp zJfQ9kDCr-L=(ktpWQME}Ak#0%$@DJ;)Y$>GDWGOEg-x^C+<>YKDAq#SQmge+vNmld zDHz(s@?8IBdk!m-Jl?;V4jP79*#(TOizFJ&$oxL}LAPmY$s8q~AgZZzOSAP`*XW}) zZP>bQU5+@D&2Xi;_E|vvE=P$grL~P4)-us_R`W74!%*RR`@4!-qvLormf_lf9HntB zKtqapr;@ek9RbDp&ZeyUL}`3_wsSYv+xr;u+1k#3G5w$O|HpGWjAh7Ysylq9y2Gcb zJO4TVpTG70IsYF`YqXL6bN;{afBgCXxCGz?{a3dB7yUKkBx8~>&N$vE^BkkUQDCew znv9c;`;DiKL&oP`y*JC7=8gA;MX!xshI{qS#u@*u(PhzvxKD3tbR6!|8yLkHPUK&a z4{?XyOISJZSmc4Q8SZC%;Pt~)&ELJh8b8MEaxd6FvhQ?G3nwG@;9k9Ns(#fZXS7r9 z^v7t*AHCmt4|zY2-01z#`;Jj(Tx9>rZFZNs^KkPP&T}?9tM%vfPmS}8&BmQLCI1HFN^@E0v(S5?H*j*{Q@E4v zzR=yFJ3@Oy*M+XY4FKCh8$)flnQl>Nc4#{80T><{9O@Gahcx$N_Z{2@@T~hA_d)kw z_b$h_|ACbbZ`#-6q{Bz-1NOQ0Ci`YHX@A+?ZXa*^c3(SUpJA`S8HlyGlkNmtw?47n zwO+OUU_D{|()yY81MAz?*R5-;ORcnZmbK1mu}-lTnyrzmtQy=?H_jSr4a6M)A@s{X zG~Y5`HJ>;C8-4Nz&3nze%-e9U-__<$-08Q~T!;G_-^N{yzxQ6(pA5%vTjMWqQ{(q> zgTvS2SH*YW7KbhIGvh1cC&%Z-tKyU5qvPd<5$_*Q#O>HWV(-V^#GMYmi#-xM5c^T= zyRn;N*T=pb+a5bNwkdW-Y(;EwtTr|yc0%lU{a3M4v)-I-&M+sKBXL(?!gO#8V>D*q z2FAZcU&HN-Pey-*n-}**Z;yV%z0JJ|Hzw?KFLbxMYrUJjuXT&+{2qIiyo=7?~P2;~Ty zol!i`CJ{2k`YW{#X$JN}GHb;xY8JQun0hRb{W_oy1k{gml%eG){ci#FL5>ptq-<%m zE|F*~Bl8`Jenz%vagFuU9Hnm$sKS7T3bT9RXVXfJGAYvJFk=nk|Yt$CRDg4NYg8i7n!%TW`ec)n2K-DU0 zllf^teH>7?DQct19VCQhy~*7jQ0vS;Dbw}lH37xN7^dy!!9aGY!>QZaY23mx+@@&B zK?d?=o3ht#{xhJsv%-Zkmj>I=Dj_mUiQ>^7HmwRwX9U#LfI1YfkvJjDCtS1>~AJ^batqEjj zDC!LJ-GF*Kpx8gSSY^tBN|X#KQLiX_t4t0H+gqt*t!6r)wg!}}nKv}Gnkxd?az(Y6 zrz@%%*Wt=AH=BTq9hfJy*nW`Mz*lJCe_K+Vp2b4?~%A*?s*|h<6 zRX|-9Q0E8Kxt>Q=WD7>&DR}g0NCs3GUs=2L9OfrX`Zos994z<5r}#~P%>>YFt1aA5g;siYqemnd{N5 zXM~E$sHJ^V1)AYRVdjd=^lj29*2=k?H$^Ox6e_doz%67VyaQ z3xVv90rmTU;uZ_r`%NH|r5gVI?-{qI8c^e;Wr~xQsS_y#OjQIFt2N75afuRt&zXu( z0_u%`;(Cwmy%xw`3aCF)cA#ab$p#wctOZBB6^kAOS0+qd$_}uE)nLuUQj;wPSIeMQ znP;&nQ5}+6ze#V*Q5t{ETd|}~e@n_h$>5Rf)|^c91Bxp}a-ngfm#IWx8VM*bp!kEt z_GAMRZpA-xrsANaR<-M=GS$9mjec4{tzZfr1U;Lh#76=3S4lOu>s<5DszF_68By|W zpfp*x5cSWTsU|aqWHRZAdNnX*^&|hZX9C%;1ByQzYjlUN^6n`oHZ2Zai!}0s$_r&jt-)>~>so@9Wd*e66cjJD5 zuK2e2=6HL&Ile5uC|(z@j8Be_iw}>NqF>o79*JAA&to6P-if^ydp`CwZVY@dwm)`v z?9SLXV>iXFja?DDD0Y5qYixb2Ep}RLNo+oL8BC8&h>eO3i4BbPL7&o!ejfc0HwnHL zeLnhh^s(rJ=vUqyy)*jF=uH@NxFUK{^!(^nj5@SMPs81U^D*u)9rp{4iVlemH105N zF-veeUKqF7zlHnpp2H1!j~X}P*1V~>H*bVlhMV>-!yNK?xNUDO?%P|A8~5ho&b_Ia zM;?KD_e#*~#w4Qo8G7BXU?%)2^HI!$--|o=?!Ya4H=^HtnRy;=;#-UP>E*a@Z#l*m zF2fCfci@h{8!_X%7I*wD$1Q(zanD~F?xF8xhjG*2XSnO{E!_2YFYfxg1GoL%h+F&a zH4WV6Hy5+U_u8-EcER&73%wRM3@*nVgL7SUr7-*af%6vb7<>-%&yPCy;-0}fFt%|c z?i#!dw+)_$`v%wI#=+$nIhpHJk z#@N(@k^PaoBX>r=8M!HPtuYn1H;%x}eu>j7e6KS#+9w)~T9MB&mi12LHQcbc7PI&x zjH_|C<8!zJ@@n@o5q=bVHv76a0K34;9n;nHzRoWden?`gNZ~%l)v#Y=^=7`v>ZRnx zjCBilWsrNT#2x|vimV??-aJF$xeC`x>^vf|^)rQkqRdw*+@kPGg-=&_g~ZOU6@QAv z)&a#gD!fSH1qyFfc$32G6+TnpH4nR^hb5=PP`k!rLUaZdUjvg>O*!T7|EY7)w*6{>@PG%M|~t z!p|tVdXIIk;?GuC<*(JDc$LrA2F0(lpJV-9%Y2a~^If-2Q|5~mR(fj9Qhb%dxo}OD zyrcBa$+W`#E@yiQ@M_kC>@ZaWuF zrDygf(w^O=@Rt-;@4@Gi{kN6B_WO$etHSRptkTW?v*J~{*{@3;I}c?zg);xA!v9eC z?+SmS@J9;&O=3ssg`@PsQF`Ghy>OIXI7%-ZTe*|@)7KfK_yG#{SGb?TN)H{ShmO)? zO!CNZCl&S;9sXtME@0{*l5zRQN81 zzo+n>3V%o8Z!7#Qg;l+PrI0c{-%$8#3V&7M>lOZr!q+H#mBPCe{<6ZCDZEo*rB9gF zlJ8O94W~=BJ7l;FcX(vF=!bes<)5qa4~rS4JEb3JTuS~^g+EsKLxn$3_&tSHd3IEJ zM#EIPKg7!!U&I@iN{QQ`aJ|Cw6`rH;EQPBSK1pGfgYIO-pQ!NF3SX|Uin*&wuB%F{ z+o{agE4*6a7KKk^Tx80cvdHYCc&V91#^aLLuTWT~c#%*g&Q#$uRk+Lww%6BG8fy+y ze3`t4_?P4jl_Q4A5kuvOp=t)>U1d*&$9P%sFDU#+g@3Q`Qwsk^ z;l&Cslvr0etG}XnRnzN#Qv7oYEC2MT6|Xc@zf|!mUv-tQdbN_L6rQfIDyh0kKYfCd zt8~;=$=1gx`6z`)D6G;~KTh$g2GLawqN^H2SLv<~Q1(>%>-`ij^S!TLp!k@=5gQt< z^{ta=mPAz&NgXXTZf3iM#?=b1R9Hx?-=VN9-G%yA#mlrW)czrPO{Qg`wny>X6qY(x zsL41LiYKI8$l9S$q?y@{w%Zm@P+JFPCP?cQv)Tg}!otnRL}Dy_+w3msI}6650|;^pyy@xJkR z+>L9ozsEj^z5V~i$VB&^hC~KN`b44;EBtx*!|*%d*TT<-pEjzE zx$#fpe>EE7Z(ptl|?B0*n`**pwyL;Umuzr81+vRR^H)92VGj5<< z#HAF^MvpS7RF8vpz4d+fXH+wHx^Zeyp> zWo$D#jJ2``@Lk!S@ZFzq3G>|p3O}eY9ZE7<8@+`K-sldNCf)BT{9g*oRzlKMbyVnh zmiGM_h^;Ntq|9wM+%3hptQApN-MWLvYhm?Is zVbyvF>5_NVH_QE8@#@>|bB^+X{bE;ae2mqwv=izER;D6qetu zzV81sZK+_i>wjC@uX`&tY1VK&lIBc>Wm_X@%I{y&tW@%p!o3uh`kpjpc}kk{`&7_l8W@r11^KU#0MFh2{4-X#!p49)Y_)is<@!wKdmW!l*tKwz(Na{B$UgfK<@>N&)s>^bd)NfMuWO+&I zD!+A=-}+8vzC+=Q6~0K}Eedxk+@bJhg*Pd@Na1>g7b>jsQ&;(^tNhf%%G^^pq_8Wo zrt(8m`Jt)&&{TeCPbqu9RrpDTpHNuVlS%Dy#jEttRC;J%Q*v3aB(<+9Ue+&3?Rv#u zr?C2tX=f|`EQPl!tkOwS>4Yn>s8^Z9T~~~S3Xf3X5vqI&RX&BRx0B)#Wnbx&Q2Hd4 zJ_)5yLg|xG`XnAu?hYz^zrqI;zE9zwE4*LfFDtCdw@~F1n6szg&Hn#_KeVpAifQK=<$vec4wP^l%;uGEtDeD{ZJ&v)-s zSmu%M$~^MjYn5Dv-FIc!eK(`zT?&s=c#OiM6dtDVP=$|Ec!N~P- z^_@l~mu1vnSWu5U|S_3fW3f6h?2pTdU3*1r_~Twz(a`~M;zvkqzOC@R3aj)tReG2zT>87xTvzt>Hx>Vy!YUlPth;<& z))Bt0!lkS9(o{UO-${4kN-jSBI*Hawbf!e@60MPFwM1t~v`V5DiB?L~B+=;-ohH!= ziIz)rszj$qv{a&#C0Zg;qeKl7EtY7JMD-FalxTrO^Cgn1R^&<*DRNKXC>Ggm3a?VQ zRpAzen;HA=L4}oyxId-i4W^5qiunHcMd&@dSP#BAHZ1xcPX9Y6IwkT+3Q;p;FL;7C5MK9N$)-KnkiTCm9|HWrQR^CRh^*rY8T~B+v+c9_Z zDwb3<;77;Y&CbdT=e0gPj=6h>-O(Z>c~;6%4!Ij$QCBewKe9AGE3co|%BHKsB7(an z-}ba;oSIc_ICrWY+da=!N2MG-mZlo(I_N+et72(IvSMTfS)G-YH_>Y)Jx+3Y%hUes zS#29yH?C@1b<~4gMva{{HiaMAnU|Hf&ugVU0`_MP7##;zg#}9!Psl&JBna2o+Of&8 zBgT@=sabg=y;j=eRJuc+_L^sPwBcMpytu7%aoZW4zE(T(!9{2mc!OkfsI2P*gDCG?jw`63+ z$g%k8WMxWL-aW4smv+l6oJsRK`-2sQ-Ojf2M)n)o3qQ!$jmyeA=k-2(Wi)w^I~U|N zN>JUQbVF{AY#(KfbVt&#S++Ttm)kfql&y)_YiNSivxoPR_RA><=79RMf-i%j|Sj+ommRD0$@JeHPBj zi483rO5!QmX6ZIBi}Bi**lm6mTSNK|@rLvoLh`BEmC|uu4#V+_Img{|*eWkBkCyi? zrwpE%mG{NxXE0p9AcK9(47SSrvi@ZQ%Vhq_8{+fx7vB7dy~)pCE7>m@OZG}q{!Y%y z8{@UX-8CGJpJ&Hs;21qB#_H%rtF)xlD=jP~@zU%H75k;67xd(GoVMA<)9UGz|V#kKq5JmBQ&iy1I$p-yi*ZaHP>;2=$#onKj*_qihbI$CX zvvbb<6pqQw%kSq*=$PP1@FmbWF3+swaPlHh|C>b%h!1P`qzu?7CpF$~s*?3{Hf!6= z)6Cn9cG9owh0&Sxs_2+be<>Lj;>giQmJCY(~4|n zsMf5>%De7Fy~c$$G9uE742vwJiPN1bRUmlUBGg3k9XG5NRaHnXp?J?U&$*;Sr3&#* ztB0BtzD=v=&?S*v;t%($nx9*#qP&y;;qVa8pSRp+jECkgnO9LtWs`{gL(M;rZ5_uh< zswQPta$t7ZD!$$>=rVlO5>Fd4e|G7-dDVHSu>lr&PBAuId5TvMmOBY zE3^+YKu*5Jf70wyw#c;)2+=Za=MG0b|NQDari<~?&`>4<9)k~?Wcb(|^F08xPK)Fa#u#Yu$8PXM>=sYVPkc6%hlH&xvau@*(oxch)i2# zMv6@JW_D#wRQHfQ>{WY6)ba=AX6EFMDOA3#XZDiOM)YqcGTMoZ5^~kesjLp>-l}=S ze7sn_myvmS`MCwy%b71P(Y=r8o*=rn7u}VwaAsw7BzO7(Y;ZU)RPTnqcpL5`y}L+H z5b13M8T`(sFF_9CE}DfL(DSs9lmOcr7ostwZqrVYmLSqRA}vCsvAJ;;T}bLCAF_kz zumhAu%@?Rsq{fR>k4W{4RP{#p%1qumy#Cf!xOmnQd)&^2+WJqmA=xd2UkLTafJzSK-pFP6j3suvovO6YRCSescG{`# z+Nm_CdUt3gM|PJny&InX-6{)m;0&6eH>=Sv25OmUr<~e9Ewoc@v{P!m{*?&)Zi#fS zB`p+Z%{TbdmbgW4Y&Y5;Vw|D<5vTp3n)SUa`|!DJ7T<6WPg&yOXj#){s6HiPnz-5) z(*k0edLzA3MTe*H;h`n*q$QqkpDBoFF`=25&=k6`ykDgX6EDe(IOJ2GP@hUZb1HLa ztkit)1Tn=H6C%X~^-j0S>TvO)-KNJa@tB)>NHy2dz|v8rBj}}iO?j0)IKQF2rd?_? zbtB^PtGRw^zpfHRhtizVOnRx6hE-K9_MQIwXioW7|MmWBd^`N>X=eFF{`34x{blsTeU^Wk ze}aDu%`ngN5AgT$ccWS6?fotNP3ft7nBVPp_)hyy(p>ZJeP8*G_&%ih=5P4+`JVGV zMRU&Y@!jsbiDsQ&<-5$c#%x5_)2^;X!iLy-w0nG%|Gw%OZK((CHSI!KA+2b z+Izx#%=?x1u=jxX4ews>ZttVs?cO`RH+eUC*L&A^S9zCu7kFoTr+LSDM|ktR{k+}1 z$==AaJ-OuiBC)4co1UuUH*)E!&eu91n`O0&cW~jg6+3VTudDOF=o~PgB+2mPIv(s03 zmeSMo*)%_WoM(h5&(n`)s3&{cdJ;U*9-qhMKJ7l?KIZ<)eb{}#{f2w5d$;>h_jdQ4 z^ozml_bT^N_X789_cZr7_XzS@=;!Y4PIk8?U%hB@PjFeMtrONU>nn=AaKL)Q z+H38$9<{bxcUm`Do2>QL8f%rc)LLN8wx(I*tPxh8)z9i~C0lK+1hRVcSuXRmdBQwq zeq|mu514P5d(GYEqvm$=PV**nleykpW3DonnhVU?<}`DhIl{~{`wxPG*Iw6d@`2dyy3=)&YZJLAtZ}V!Ep;s*?}TZt zajp@rJaSLy?n-vGbtRCeg3sl0o_3xfCxx$^hn)wUZ;+S5Zs()U?an*NO<|LBy>pFo z75OPFaL#s4bB=S4aOOGtIlDWPoo$^7&S~-vRJnGo) zxYKcyW0PaOV~t~#W2s|-W42?OW1M4zBaa*px;v5`Z5;`YXorttSe({R=*P%U@vwe? z+!XifyU9y&yM8A*DQ=?Z7HjlX`ci#?K3kurkE4rfv-m`OV(1ij-3`vc+t_#O=&Qh&coKWr+#Ll1Qn-xhC!Z;7|S{bE1( zrg#&4gC6Rr^;|8vo3zTuQ{pM`N%183gm?mcTs#i$61%|1#AD#2;!*Gs z@d)^^co=+0JOu6(JHZFVgWwLa1AIU{0B#rC!TZJi;C7#5Lg6;%e|JaTR!_xDwnTHh@=% zE5P+)J$Sje9K1|i23{&I1uqeofESC4!F6ICxK^wM*N8P>m8b%PA_!KBO7J3a5qP1v z5L_)*gBOSkz*S-uc)mCvJWrelo-57;SBjP33b6uQE|!DK=;5I{4@<>TaEVv~R)`9) zoE{#k_ZN%B;3BaIEE8qmLa`8BAQpi0#e8s{mjbfTP7|aFiGYjua!o5n=>5Tnq?itxeMMiekLUyTriZ@j zxO&k;U&WrHC)h*u05e1e*j;o7(?vSiO>_g(L>ibXQo*jGE7(PJ0aHW@NQ17x&Z0Be zNpu1`ijH8CNCG>E4q$uH9&9Jtfo(-wu#IQ~wid0yR-zTyk_K(6{I?J-z(kPpBH%RSX&=e+U2m^Ep7w8mD&>0v^^5gCA=jgNL+3;78g=;D_3W;0M|V;6d#m z_`dc&ctASokZ$@iiJm zq4+8dqELK=22m)!ti25Gqd^qv@0Vy0h2mZsM4|X14Wdwdfd)}1K2L)v6ra$me(iqnKJ7m6UhQ7+9_=3RZtZSxo3;(SOS=oa zQ@azqL%RdKUArB;O}h=eRl60uMY{#us%-^t)@}xG(ryB8)NTZ~Xj{M=v>U+dwd=v_ zwCljl+Gg-t?OJe?wh7#*Z3M5;t^u#st_H8tt^%*rt^_yGpm9|$S7=v&>$UaZ<=W-o zWi)6-y?-eUT2Z`2y9B&gyBJ)jtpnF;Yr!?z8n8;M0)tu*tkf#Oi?oZt3$+Wu)!J&O zF2n^FC|$Km>G|g?J?}iF=bo!{G}LJXz_aNlGV9R61dT((&V!jvJ?R>{z7*1xoYt zm5v#sbo6MYqedwmIa29}5lV*-S2}E%(jh~X4j!y@&>*D)2P)0WQ<|HrG$%)CcDB;2 zETx&5N(T&3+P}Zje*Kj8?W?p;AEmu}EA7=wY0sW+d%q~w2WVBt=$G3~( z2yXLjr5^qU-#Y5wFDGZY5^J-y!CGgnww7CER*5y;nqZBx23Z5F9#$8ty_IOi(jx$q zVsM@`kDEtne8E9;zq!xcW9~9{nA^;)=4Nw)xz1c|E;q}}5_7sa!5n1{G6$GF%r0ho zGtrDS!|2(9W}GyR8%K>J#zAAhvCr6J>@s#3+l;NoW@Ce~&RA_MH_B+d!gQL1I?5Pi z3@~~aU5xfdq7h5e;Y>qwopc>1kG~_XgRcFqedO}D%eBL`&9#+W|2DYRxmLTDlj|Ru z6}u+5Mv>3o09OxJ7gu|7|BH2nxhUu}dHx-D9(5jZ9wgVlea=13UCtfk`?uA(*}1{F zj-3CNJIkCU>a+H;UpE4siBxc5$|MCOTuCVNTPjIZir`JB~VzI1W1YJN7yDICeR9 zIJP;qIyO5tIMz8RaeFAy&4Wdw-Jse#e?aA{#mi+llheqE2$MvJ+{C`m2 zPrm|Vj@NDyJ zrP&1=JnKBGJCxOL-N)TW-ACL9-TTS! zagTeKdxv|Qd#ii1dxLwOd$oJHyUbnUp6;GN&Xa@O1Kd5_UEJ;6iR2|2<~H4$b&}!? z9JP*E2d(|qK5LJ)%i3XWv$p!C`zH8Ck$>R;Uk_gwUwdDoFV+|4Gby6wN$+v*QSTA& zLGOO=KJOmyE*j;s&AZjR*}K8J&byjMyp(xMywklCyraB>yaT*Fyj{HQy@}pfZaDDql~JBr*SaYvCGCGIHl8i_lKyjtRpBCn!huWG+n(y&*> z4H9=0d4$Tbpo z6j>#4N0C8^JBqB7xTDC6B>m*wDMxfom|7lCE63|uG|f(zsVaK4-m&Xe=N zxpFRejywk}m8D>bECJ`pIpAzL8!VQ^;4C={oGE95Gvo}gNEU(9<#e!67J}2{G;pe% z3Qmzzz{zqlI7v0bqaGAM7Xlfqi9Pun$dpQ`_~Hy}@3x7ub`g!KvjvWDhWdrXZ@nyUXri zI!#4Xe|MAJz%-c#rpi>XD@{#Q?{|@1z!aLIsQylt$zW&M8SErGfgNQ>Fi9qX9b^Zv zy=)J*lkLE^G?h{9r;Tg_wwA5IRZty1CYymx zY09JeJ6^_vaWW2UBAbA*GzC(QV=37a%?70x9yb zqWDMr1D+PA!N0}d;9oSoQoVnQhJGskNyFh4|DfUUiYI9}yyEXP9A5E+I0619egl6M zzk91doYh;1A*l@O$w+_?`F;{8oGmej~mCkBXz<*WzpN zEAbWhrT7y3LVN*!EC=cDGqdat{j`GmSpa`F<>nIN@7L*5d_)#7@1^8C) z%YP)wL!PGTBI@tICCWqoMbkyp-=`$XL;gwAMbzJaNR)>>DN!EscbYDumY7ZOkVoVZEdNA) z0v@KR(`vns<;UP5c?kSSegu9fKLkIJAAkqtLGXR~K6pSL0N-!J!rZ_*HG_4ga{4e)gu3a$QrO}+-cDqjU(p`p=g`O7rzMsc6q2fid< z0{6*-)Oy&IUOr-7+@D%e%;3U<-EfGK(kn5-v*o%POOC%qHcQSS&Q z=}BM*y#v@@Zx6Q9+ktKMwqP5*4cJ<54Ytx-fi3lxU<CF9uLOpabOd@2^g!#f-!mw7_CQxQF;^@sYikVJ)kI#2wXS+_sQ7*f1Zq~K`*_v zEgCugUFIL<`^NW>ZBi?@pz8 z-8-zsRu=WsA0&7FX7psZ(&$gmdEX=tfnS!Itga^GeEj$VtKYEV6?oPsjY3i%ZKY z66*{-i659VFpFNaZe~!$g{Lu<=_(j5_En=wI&XTJ{(KdT^Xk`qO%)RCz}=%una zB*>xdMgLX}FNuWEES||*!FUW4Ori-asp(c~i&Q>!D(t<=U&yu5%$806J%2RpHhDmD zZ+fYGP0V=qpTpu zyccQB8=k4S&>ZF08Ph70I+;mvNh&{C!6w!DNvr>b7A2e5GPXKD4ZesH=`1&G+p}#q zdT~x-X(x1bW`tC$*wCEh6wfQJD6Sp}*tWf^ZCqQGlWxHn?8NHWa4fxyu^WCBCbl*c zqZ9drMxX73M%yy#zfNdc;<&`o^iqeBAB^I|=%3#3ghquPMmCLAt0>N1Shk{(vm+B* zH%aV@40a1fp6$3ILyjvh!HkQJQ^(ak7~tcIYF4jcM3fBJGTg0(&eaT`n-n)Iu83Yz zEyxT;@P;uVB^seNRPiRtm(bw59)r^Skrep~u5==02l zml4Pc^rx2^J0`xA8{Ez5TP6_OB#@3gbqjjWb|~JEL-B>1Go#%qonc5&dwwiuXT2Z{XsAAz_vZz(b z-a$9#q;JFN8q#fJCP?++r{tH;QTHen;5@m6%z0G+yzW7Zw@wS$+G?;hU1Vi6zk|mA zsSWdjD&9TKz!({_p^0o3EM8JxF}`RXeP3iRTwI*e;OLV}j7~-ctbbb6%2?W}jLiI{{|3C-!Hmi7%+2exk$^0^N6 zxlRgNuSUt@io%_ZK02XIf;%B9f!r^?kuY3h_Qm~B<*#!r&X|OlxR_=!YK<mN<({;*oW=>aFM3@y87N+KOb*NOa=xK}42~&T1TFPw7adL?_+^e$Q zv65rZH!`{E50CnmXfU~omEUG ze{X|c>Ii04F6K;ho!BrFKUZghF4UZ2)i$9Jw~O&zfay|YMWu^tXKYpVHX zbBaIrf$trP67Z7mS>KZsC*T3!-M-r>QoyyoD}9$ztbhxAD}3b?EntqX$Tykd1&s6! z_GMGVfDB((Uq^}=(A?L=7oeyChEI4;QQUwZz2A60r^o^CQ$)H~DR#g!-p45_-TmIX zytjC7p!fk-crW%=QUrlz-bLQI6hokpVi)F96oG->Op0Ha?oIJ_@V25DhB0)>ddU0z zANvpc7jl39ntlQKh#ceJv|qNLC(roD><8_8$u<6F`#Sq-^6pz}Uud68F7pfRQhO%( z%#XK6+e67|zQ5g*-03^p?d%rhPakFbZHpZ0|MvXu`H4L0zw~_K`G8#NU-!J^dDinJ zx$`~Xxtm<_Z}ePC?tGVesyr8XR*+NveEQ9$h`jR0dPb5%U$&>OCxbltI(pi8nv+Xk zz~l88lP^AmFQd&hj;e2IMho-`jZA0TJH+bDkEwdC!0saZua z1XoaGg!$w!QADv3#+oC^;V;|lYi5wgUq^}p*_>Sd0;bnA$mj2r@tg4@IsJWZd~Cc= zUVpC|FB;E~+uuVJ74k0f`@6x|Xk0;#f0f4h#xi4(G1n+I3d!{^-xzKTB;UW@M!Jz= zbTC>O&5RiG{__}4*FWU`_lxTX*Vp9#_mS&8`d#Z~iaPML>oM{Gyw`Py>t^!IyxMiS zYc08Ep6goTT1dW`GhI_%Cp*VFN0KjQ zwzID@gPbutI@>s#lQ(9->2(_9{&2aRvEuRyxjiEF)*mxsGB-A$fD=JBB+3k~?Q_itd;~{+z8G%^WeBNT6?pl`a}m zWJ(teAR?vVLZlRBp0o!v1DXa+fp&zpgSLjYfHs3RfyP3kp^?xCXc*K7wV`gP33Wmp zPFUG4EiXP;$G9SKMZ{cx)b^! zbO-bS=yvG+(EFhGLhpgz4ZRh5BlJ4xM(9=0E1?^pmqIUwUIaZKN`v?)KQwTUbP04Z zv<$icIuAM*O2hGJ%~I%8C=I5gB{Z0hlm^q0(qKB$G0>q<8uCU z?6?a{ZijA#Zh>xwUIRtRIxa{066hLeC3H0uX=@n3vuLC9Pn2#kWO-BjRv!Sz~ zGoaI<)1WA2$7G}@K*vK1prfH9peS+2AfyLE^PsuV9B4Ka^}&&e^Z;ldXa+P5nhb3Z zZ3RUgaKs^vI^c*!8a2Uz?VtZqCKRL_0w<*TE7b(F8JsdXgIp*)TB-_XCHIIHqcq;YQLNu+UxCBFaVZ&>mx z^cUz)P@H-BJ<>S;@=K&~ZAyH%%fndm5fo)1-$NQDBHuz9PTId>R6?6%-0$L7T3|$1pwJ%ZDax|8Vf}-3d%3Y$|CCXi*#3f2x zqLgJW*2ncLasA2xSkfQb7mBM`_CmTRGy|FrO@nrYra(JGJ3`w-+d^AITS60|3DBlc zd;`c>q+_5_P+avg9BEwX(ucGcYD00oOE=OM)Kn@^KLzTiK>ZY`p91w$pneL}CGjrS zM~xD1BmEY1KNPh~yn!@IT%g1SN?f4C#TLAK1N3_6bZ1JSB6LoTH>vNr{pagn63FD@GQ{-^6#nxlQx^_lAs&C=da5$<20dD^>NJ6-qD zOzo|%>s{B-TQ z(7f%F&Yzv%)6DH7&JUgM(%kKR&gYy@(d_LV&U>7<)BNqt|BL&;|3UA89sl7zFv>T? zmqY#oJ$$LYPUJw4=!^43k_Un5lit6`h2XgNTkjX-LvYahw)Zu1BG}`7!uv3J5p46` z>fJ))CN_94@dn9{V7Yg(cOE$sO!rRm7LX^wAa53psOV0k%9FgU$(JD38{xIRE*e?> zr~NB=6CAZavk#Fw!G8M{`vvkR*k$ju?<0qTt@icyHRMsS&c4V#k6a4M>~ri{p`u zdZha=axpmW{+65|57Wr~x81LilffSM6Yht}%V3-PR`(WiGuYt1#2q9*gXQkU?s?>B zFx@@LT|k}&gWOr}KICf9#hv7CO}+-P?g+Q-cF|b=KdoQM+u*46nRSTV4fb2FSTB&j z!7gj3bssq#Y_+bpt|5!7{W(rQX> z2Vs`ma**G_N%Lp(dvZKDVt#18OP&Y&%;(If$n{`{d5?KJ`5tUGuQD$q=Y!SeO0$Bz z4@%4#<`i;27-bGIbIAXohnZ@2A_s&-GtP`84+PVc#$V)uaNPLT_=0>84jOM$7`?qT zzvKzW^^RMeS2-^uAA;4+mCg!sA}Ddra84mFf>F*P&KzwUq>I2W| z(?HxRE!>W*7I+`GbgMbixUpODNaL1nMI()yxfOvlZs`_o>6RZ$aAUV{W4CZ)w{T;( zaAUV{W48>f>4Zw9=0DKCp}5tXxV@XdVaYGhpPb1^qm&%NAGJ?C;m^&HDJ)pI1*RL_yTsCsVXMb&d7 zFRGp!d13Y3$qTFJNUqi{RP*j;6)T-FQ|a^~r4uG99Xu2|L}}hYrCC%5NOPdsO8aL* z2dF%oXf~Q?l$mIhnK(KVM`z;b%vZ7ID^N7bOdPL?<2BLvGWXzJG_}kpklqD-6#6jq zA?Qx%gU}t&2cX-b_e1Z2ZiC(dy$!k*ie{{N1JY=;nwybEqt!&C)!c|BS3}XnGcQH@ z5-6H@W+l=WLFug0**F)9hOJqS^nBa&qLGOX0RvULBjk;~zhV(7a zo1v)T#ulWnhoXiXsN2RhSaKC~19Uy~awzJ#ff{bC#gZy$CG=M=R#LPS3s9R zaTOTlNH2megf4*2gPsE|fzF1`g5pXsrXxKKIu$wvIvF|%IsrN!Iu1G(S^ymb#g%1@ zM0x~tICL0vD0B#PFmw=fAT$q}3(bLMLo=ZRp#7k@HjQ3Lo4e^(38*;&|jfHLytp`LBEH73q1<`3i<`~Gw2cMVdx>~htPx21JHM&Z$tM( zUx&U5eFge5bRYC3=w2wUHP;@bah18AK>Bg$F6d*>N1=~EABN(JbL~VL*PUwz(zyCu z+mXgK=(-Q-d!hG0?}l!J-UYoAimTFfJJPr|UAH2QE7Y|WX$}5uv+p|J)xOJpYke2`&h;(vE%cR=3*cm5fp0ju0QRR|cvoMNua&Q< zFN*r$2F>IC!~3)MJL-XdOiqAr(oF7WssH_u_df3(o<8@GU_Y?My)a(A#{?LBMevKl+@202wJM6pdTkRXD z$9*}u0$xD#;1^MUd!{{wBFK-h2U2glr=3bs=UY>3zG!-?Z`#81C&iuro}TF+_PkHC z;a{eH_LH86J@=nA+TIw7e4j(l^1FLdXij_!>R$&uw#P{xfxlDl`WyFW?vLE>x?iWh z^)oat{y~~?aU1olHIB$1Gxo$Li6L_qW<*r)>GCatNd4z> zbDmjD-htz(_dM9lGJBid%+AzzZcd&I;ilWvsptF~`3HVYt_&Ygzxh?;1>#>Ga!F zJ924g;)-y2=$EO#$x-k}@@e>#ewTWO`T{SKr{H7stJK}jTb(yJuW?>Zz4;5st6`Dz z9Oq2x%a0{r!GYw~(35)d9i6S6&B$5MPyKk|_|x%=<9o-K)Qf+g92;JCJm+|l`tbLY zyWq{_*>I)f5_6ym%(SqyWv6o9@T9Z>uhPdbU`Bq4Tg*1WRORpdXJ`aDoBQ4Xn^>KahmZr z<1fZ3#-EHo7$+IOE4al8#&3*Y8NV=oX8goB&iIjWjPV2Gd&YN+ZyDb(jxxSxe8u>Z z@de{^#%GLA8AlkOFb*?5W*lOC#Q2c$0plR!eZ~RCdyID(?=aqGyv5khc$4vlf+b#O zyvBHy@e1Q*#y-YNjJ=E(880xNXFSJvma&KN3}ZLrX~t8GCmByL9%t-gJjQsG@d)E# z#zTyqj0YJz7!NSEGwx^H$GDeq594meHpX3yI~jK{ZfD%axRr4WV=Lok#!ZYH8Cw`P zFs^4@$Jorcma$2}6dM`WFs^1?#ki8OfpG<6J>zo5WsFN1moP47tYfTYtYK6!f{aSW zMT`p>s~HzCRx!?JoX0qqv68WZv7E7tv6QidQNbuzFvMcUB1RcwA!7k!K4TtZF5?_V zDWiljhcTN`%$UWP$(X??VoYZgGNv)6GNv#lGbS-6GA1y_GsZE-G71>^j4_PSj8Tk{ zj1i3CjA4wSj3JD{j6sZnj66mzBZraA$YNwN1~B?F`Z4-4`Y?JkdMUU>Peu<$2BSM8 zozabv#zAVEo4TmGKMXXU0#Ajbz1*i5D<4ML7jK>+f7>_X?Wjw-o znDG!}C*wiJ4#oqF?Tq^w_c88e+{3t=v5j#T<4(pMjN2KvF>Ynt!r02VnQ;^2M#dJ# z4UFp<*D*FTu4QauY-C)+xSDYl<4VQ`#ube9jLR99F)n3X!njz$p{--AWvpRTF@lUr z#zl+^8LQQg)(-mNigXq9e5E>dg-Pj0EYg)w`VouL%c0aoru0(i5@-dKI?c3vF_a$9 zTVkk6$}%YRr767tIv+X@O1*1Zeh#!0N zzfDU0ZBpuQlTv@1l=|DG)ZZqh{x&J~w@D{J$3w?K$3hFB`B3VU)0(59qoCA3rzInx z!=dzW-x5QV&=Y=3J>j>+Kqd5;-x9e>a+G8%p{M?q$W$^w2|fR})boE!J^#1V^M6Y{ z|F_ihe~X^~+uw-w`XdrfjqShHcZ2U5>gTVak^Rf*`R>!?U;h9(w%tNccdxcCv#Q9!ei=R6EwyG?ldS@4 zI6c|zPtJf{tt6`zJ=cvQ7kh)e0so+-jm{hRbm_gV7^^C9y-^A38VdoB3` zUTj`uo=eYj=aZLxAvpw&rl+~tW*;-1-0a)Yv)m?Tgy}II#^3ZL_eXLG{M7i+c!!?j zzDSPtkC9K{-SnjO2ICsza`LplfS$81qUWqLjVTlsbOenRA7J#PC#@Zg)-+N)+VGQG zprBC`zqr14eM!#t@6(uxmtD`fo^(Czx}Qc&+)R#vSGq27RnmBgCFE{Dhdcu((rAgH zt{hii^0!Z+u@Ws@ajt;Nb~$OJ#P8%A_zgYd`iRC!yiOkX&yaKAgEUIwHj1XWkzDTA z($nP?&c)8T&RNc>^lW(~xd&#_K-e^TvfPH8?qkS5(4yzcryRdJeju;=Pw1)gTaH&8 z&pV!SJVMWu?;;Pu>l{}(E_DQHB=}OtLUIu-a!jIe;KLlbAu%}yZc5OlW+xj3SQ`5=`N=c3B?p+ zVLZ7C4yN%4z1`j1ohiyfa~h2h?smI%>o4m!8jJ8XISYPZy=}cp@i5Qv&7`OlV<{@d zKynrANuxqK`da&%(bQ5ujR_InKfS+rzxRGgBSPLMXTg`f&v~Du@gVn8Y>J!7TkuL6 z4N~bn&%1=8Q_P{UAQQb~yhFV?-o7*vB!&D1TX^HV0U8J5qzDzilf&RQ8X7AR677Tt ziFQJSL^~luqMZ;S(N2hvXeUHSv=bsE+6fWz3-y&7A<<5VkZ31FNVF3oB-#lP677Tt ziFQJSL^~luqMZ;S(N2hvXeUHSv=bsE+6fU7?Su%4c0zPKc0bCqziJ z6Cxzq2@w+Qgb0atLWD#+Awr^^5Fyb{h>&O}L`bv~A|%=g5fbf$2#Iz=ghV?bLcXAm zFI=LX5H8V92$yIlgiEv&!X?@X;S%kHaEW$8xI{Z4T%w&2F40a1muM%1OSBWhCE5w$ z677U=iFQJ`L^~l|qMZ;f(M|}LXeWeAv=hQ5+6mzj?SycNc0#yBJ0V=6oe(b3P6(H1 zCxlD16T&6h3E>j$gm8&=Lb$x0j}PsHaEW$8xI{Z4T%w&2F40a1muM%1OSBWhB-#mK z677UAiFQJmL^~l&qMZ;X(M|}HXeWe8v=hQ4+6iG2?SwFic0!m$JHao}PVh^#6Z{hG z1iwT(!7tHH@JqB4{1WX1zeGF1FVRl$OSBXG672-PL_5JJgX;Kv672+^L_5JJ(N6Hm z)%@NCj8%;D8Rs$1WvpbZU@T`WV=QGXVN@{68H*W<7-b4xxsb7dF`qGyF_&=;qm)s? zn8TROC}zxJ%w)`96fveV3K`QFQyEhjlNpm36B!d2;~C=^V;KdEe8w2YXvQeUNX7`p zaK)C>jBG|0Ba<e`VGKXR$M7<2hKJ!+h|*D|qI6WLC>>QQN=KE7(ovAVEo4TmGO&0g#4ND6XQ7JN5(P659;wrw0^DnD_Y;g*vPns zaW&&A#+8f>j4K%H8J9CIQ;5V&w(XN*r7M;Ml1d;~?XG#sS8AjCUFD zFy5x;|JrUjLnF`d_2h#+L-&%K>ksmfe4AWCcgu(6J^EAB-`hq}&aTlfA?Ns|`aFFG z`NoeRcg6%g2NOM@D+*`@QCj&n)SbyVgSzf75YZgQ~%z+6nf$x?K8-;{X6d= z@9Q+i{y`dDe-%C1UrD3t&+*QnG4*5UxqgBvR0O#d{z(!3-nO5hsD4-4 zL7J04iDu$=q51bNnstAO=GyP{TtX50(&<_Ido*hO2AU0DKz+bOn(zLd^%DISvB=7? znp32_wdC%V-q%a2osF)ajrn#BVzub8K;3NKxj7QcpWd|C2oW z_crv~VzVn{@ua+Hq_34e`0T5|r$UA&==~dCM|oz+s5m{N;oG?s^>S9ROmE~ZHVu=p zv3gp=t&pRpDSA=EHwKrV#Rf23ZzIXcXhG?MIR(Y#73I2YqNmIJ>L8_c<2Q?^J$e(F zUod9yh=KAgzutn+AzJ9EXXd*8p}a> zjg<`-tnlg0Wj>i2&8#RWnj?nP|8ro?p7fO3RHxchhuT!z+ElCBRAOzaS#2t=HWgEw zimXkA*QR{6DOXKOK3kjGU7LEcHnmHg(SpUrD;F-#Uc7?bOHyW(mZha8mpHed?Hz3& z%Dw(Ja~3R_pIqX)@vLt-??3BXjsc->6_ppK_n;kaIs02v&-#|b*XUd7uv|Bs^)3Ci zCVGabe=aB+YO7T-de{1Asi?S|c6NQ^%jJl8y=Q~^!v+^jEX>Kx%N#qbVC-4nDM-|FMZx&O;kgBa zM&=ae49?Ha9FvooH7vJqOm04HS{R{sP%8||%rDH#%`6x@CU;ioq8ZeBRcqx=D99Z% zqAispk-e034_0GKAsF8G? z)0ICqyI_1Xy??!hnFWKh3rCF^IVyKd!Nh6tdhgJ8`J7CR*1OhuuV#}e^*@Ezom_op zawp)U^Tv+Io>YHP?ufC&C)A!8td%`1Ge3V^bqP^&?C{*e5hF*87&~m3I*p}geP>Q} zt{ZHFQ(E#b@6GnPfOM3G`uT>tUs!*KdkK9 zUe7=KCUy3m@~PUo`YtnA!cAA0sZ@`|!!BYPXRaOPb8z`TWvGx!77Ruwj<}ziHC&r%1bzbDX7O!=I(%X8$ki*QsLz8mT^K_Ft!1`gYVO zHart&R;*vpz1r!c|A%#D2JR>I3rh}eY+-I=3$q$q*uSxbeHvTXv$2ISjV+99Y++bq z3%&eBmPNPe_UoTdR3>HK$ioW2y*GYCRP(30zXmF-#SSL@igb zn!&BrTv1JmD4>?B7=7Ss)y`Gz+Wf*XBS#j<%NzW?Ry9i1`#lTOyQUgb&it90-rX2y z>+#j^b~Q%Syw?M(PCoOeT6JvAsyzz38pCVe%fPCW&ituXEvQ*FgH|1;8s#bj-DVg= zYSTr=U^kg!V!1K2CYfdo^6Jg7G_^2whLKn2uOcJ2X1Da7G4RYEU5%WYgYMb|`I%bh zZA(K!P?aJ+SI+Zscp5XJ8Dx~ zYE#$MrZ&~4F0M_jsZCYZrdHRc&aX|atW7PeO;yyU7S*N})TZXvrpDB!M%Jb>Yg7Gd zQ@v|bJ!(_wwW(BPFxXSON26Z*RDT5d@Rj+($UAScH;hKXPqU-wiR@fY3O!rBlDy(# zt%vFPe7gAx&6*xfe*d=`b6lq=GTus8e|l=YgJwPFIz1Fy{Yu9Sib&_6r^Oq{^*@2) zz3iq~V8dht#VX$-3PlI<{>O-BHLCf4{nsxzgWVC+!yDQp&+y8kXh|Jf)%hu1xL{Nf zIpmY=_2P0Nle)zuB_$Qpd%c52*nyc22V4~apvM2d$Sde2FL(86DG_z<)#ll3nZT~s^zyH~KV+MTh%R#CFBdaJp!W+T7l zylva0wt=?sZE0y)T5#IgK62Wbjd`;+32g#xqT0}AeS=eZt2QAYIkkGLnWbeV#f$5H zYU?ho1FaKUt2Z)&Q+WF(A=^)>w>^JwtJbXotzuiLEqetgpY32LS8u7#B$ZL|EZ(?f z$CiPX@h#QT)Zip+ELt@1X^oPTsB|LqY%=j&Foz-wl((Q66Ok?G*9$VJo`{Xis20_R znbi>@qU1!coZuZyb)bUmlNV5RNEDvPn8-+aDHrBe!3n&jw?So4o5=!CWNak;?-d-6 zwX8sgdixgvIiA*XQ>&AWZdJoOv2CJAj_MQDi(WdtlNuaXy>XL<8;?^P7cZEl@|P%D zM|F(qKrcy4x&_DbMltm_BBS83YNHv&b4nNRX0cH%qMFf5E$S64z-F>Vh$(yloxNx? zYv#iFWlJiGhi7IZuR=z~d!wSG)Z(ted~6KoTZ>FyYpQmX%=gMM5xP3*WQkfFp>6d? zL`TrEmA4Cyp>1kxb?a;sF2{J4tuC$WiLfIomURk_#+rIO&U9UW?9pE3d^@wKoSaB2 zd=WmC>J=Qtt7n9)J_@VX>)V&3I+4q6qPK#6%jgvxdA5y5hHhLogDw~PVSr*;A!SjTXqT#$CmIV7NYJU4)?;+TQ$bXiaSzSahLQA4&&8(hJ4U4 zTD`IFq2v;yg`qB!?!lq7Erm4jrjg3pkFvaSs4a)M&vg6}RX`Ng1MHyPHbA>AKRARx zbZ{#Q%cgcAYrg%4gc_hX=seVMwbpLa?r>|jwbO1>=I1?vgRxf?N1(x82Zy+^Q@od3 zOl?cFwnc54793Q4BpvG=iSWunw#;jIBr~)vOSCO=P;ek`6`!uQsyz(4J_f3-in!-i z(`T+53MNn6QmAd22vw_<2J`rg+Pz#_fwhe>s?EwwEX-{Cu-d2`Ioigt+Qwl}xH9PcB&va3$gSwpQ(u5o_`5S_;rqP{YY*7Fioj?rzAMwY{xj@e>lc7{kukwkuusuTc5=l z?GfyMwqxucdW>te%S>%;w6>N@vmYO0XleGdW#2|iv#+*xgtj&ps!Fpc*q4tqKcqDK z{@aoMM=?@9n`mo$b3S_n`<(3%`-C3iYOTuDR!3{Axfpx%A%+%XZ(H_iP>ch#)kC$_ zgP^JyX9aul!A%S~xL*HuaP@lAjTRwgvWd33A7`>#uxIV@H3$aPGvxSItTI0!$ZcNu<(I#WsetQfo^hhA#=lwbxQ+31j%7#aW5ku`QyD^@jG z(F<7{6zpD|wUF>T-EEovU$I%5muHptrI)H;dBJqfX-2DtP3`p1oYMV@9wePPW?8<> zDsNVDdx3lG`n=RA+D^LCA7R{YPFO{cR!8FbjeT6kBu(Z%Th5YVVx9+0>L{SNr z%$rvqJQOivA_aHi?IrNL=V`L|5}`}v(*6quYo znX{UFwyY-Gvh#mswR8Tc{Gs$xSuF^5uFh&mE$#eoS)~(8E!g@uOqoB?%I}!ZIqY<{ z9Cos0$N$P|_=nHAg zw)DJ8zVr(`{XI5$%daNSc+GmvT1|7to#tM0Yae2IX!QKWG;2GK+ykE=PxLXaSm*oX z?zhmHPA+FJlb6|CavAr~yym<0^YmeQ97SKfM^2Kh#Lr@nxIz?(bYW3O{tx_1t*VZ! zr-ED6Z;sy?-m>9iyRf)0ilbD~p-RQiYb=(Ths=NOP5mMEb71|mv~}4W%OrqRdrap(2v+kTMQ~* z&OL1kITY>fr2@*SZWB84 zwWUCvjV&}(+O7IYGz1{0w7hIy(TeD#R!K6YsycXFgH5QXb%WX@lb*Ae4_~;nSZ!PG zN(v-(u2M1Ls4pCaU#L{wtLb80Ph+kr#3cQSO20_;N^&HrDwkAM9S+Wmcpr5$bsY`W zb`M`TYstLg!Ly<%C0?N*2wQ_^(P<`rLrDD7_nw@LwWXBunb$C^FI#)&W z`YMc!%KVw^Z=$i{5xQEb+|wtT+$wnesI#rVkp`7V=*W_%kDt6Pis)put>V~hp^MxoXEDr{7z zVw=&Xs@UDDIPjVtTCuOhCOCkU+A^k2cvC|iNk)|lr$%KFRX-XIg^SxjgXG;si{~tz zSyHq(F^OXLMRrQ)6i+XymZnzKL{y8x39dQE)PB68ddryw zEsB1Tt6wnd{n2uMQ7H|sQ4yy)^>KDGJE_0>RdJLw(V`u1aHcFT_s8g}BJ%SFM7Sx( z^W{X56qDplibmbcs8aFL8a>3zXsmyOLkv#{kEd7lA*y(2YRiyAytI1D>O%|<5BJfF z53xEZTIeBOQtuGMTR6kbaCL|st2h!`XpAIzv0v2^KB{)%?NlPAsyh6c`gyVbJz*Wz zo?SYxxSW3GDxTjqs;&B?UzLh;M&CD~r*Q4rPoY1?@AXIfRR!%>rJ|itjSRg@tf`Yh zx=*MKrdFx&W{p<*Dy&_v(z{|^>hB>{9N5fi-nU_O3i@Ov!VhLHUR*@SHLF)Ign{a# z0-|+N5tjPV@0Ibxdk;^ems;4TildzA%~~|9W|cm+sN&#edQ`~gUs$~Z^;_CDBW*(mkj`p{b7!`Tcn%KDxAgT+zJJ zS=AANqmuklQBkGkON#~tRa`U*3D}(a8~m^4p1FcHq{k|{y4V*KR}7xjuo9FX3+%EuiQa?f1qlS}>|_TB_Os_Oh7KKGt8Gk5OHWQOc)GK76k z*g-Lq4T7>nh>A*0LNY)kBryq!wv}PY1g#>93%H_Hp=uSaR%>lVtF6{9+FC_xt8J}Y zTeWskTU+1n^PD?3lT7mS_xZf-`+48@^S_Y!KKHEOv)=7I=gF2>8?-udY8Y1ITDP=x zj^DKf!7-;~7^5?{WYHxsa%vzFjC7q}vWrZnA>gEp>8dMBzftbJPYVtW(re|}yT~pY z;$&XVI(3|K7#ZuhAa#2e7!e@DkkfaOVKhX`yl{3n(Qso*>-ca#ND&t7A`@qrsq}X= z_qGLhwC85&8@sfXi1EgzPTVH4P${_u9vE}VwYpwEN@2t z_m*uTGfq>|Ih}2-tu1XEH#fAlE?h`w^h)x8xT#1S7$**p!_r-?$ds-~i}1_;xR2qY z+Zr-A+Z0Ck@O}wle2o?R7Kwce`Io|-xN9?~WlS5=pV4MwlzMv~r^O~MZ4FHzvrk-` zBlZ=Eee?pqbXSWeGjdY@0pAibH`(p@26BE|N0TPRPyL_~t(#Rh6@TQrVAm#20ai3I z(PGT?UfVZ0mC_XF?;;!<3hUyE!n)(?$np4H=VY}4hJ4dQdH-^l0|6`w?I=O0cIsPaeisY4l6`_9zKK1{@_p#^i zIQc)v|EYohQv?6Mt%04}ay^E>=b~)kjl!_Y4q=GxiI@;d#DrmvRfS@rsE<!hQoMkg+vC;#8X!pVB#w2-1kw2vHx29gUhonD{ed__IW=Rn9^5PlO{B zCL$sdGZI2sg3^fyI2iR&%R&s5OPtIE@-ZObKoI&f6cK@!Fvp4pD?xq{;f+MDNr>H$ z=rq#(^v0qg2u=BnjTDP3#JJtUikreN-<4|@S*kK9A}HH#RKt+iXfUuSW)y#vTBXQI z1eHhxkqaFvLF~;0F;>b%5s5)%LW(sBQI1SNf^!BvLnMwSB2m+b?L@YT-KNO(T3mq0 zV(^I_@rYs+W4BNT4ddXk(I(|=s_HUj7=?zYNeD62N{GGD^1_%A5mo}pqZ*=q{FWo5 zNOwYQC(1&^km+rrn1rK3Bmgl5=ct8vL7^f_)QEB~0>y+?C=y6nz&^yzZ8_WnhA1;J z0%^k)sZ=7qS#Ct5hZU+JhU9IkaQKgQ!U$>yQ|yi+3;ai@L{u20Lxxh*PZaHARwkk` z$Rd{DKqS^ggh!b$v_U605=baABnqhz)n>-fqDU`+1d2r>2Cx$TdiWA=bWrC&SEB?h zV>Eup3Nc#kGDH;NV+ctxMdbE(3wOga0vC}Ri=x)32gJlwq>swLfemu(9U_uLMS`0r zTWl}0B9YoM2~XWCDOg09(H;9?=dTbgfwG_sPl|&of+9W+639JaMo{ufWEvA@wntD4 zUnvriqap@^+Y!S;Dp7&J2%1ad1m|J}6047Pi-VSdB-H33<~Z;qA4G`75yuabR=E_D zQK0fd_!L^jDkicMhPYT@dqZMwh}?r>Gy>x6NFo=3kigLdD43K`jG9bZmHD{3ix|cR zysKjqqryB0mxPHPkj*_HA|(1357T<8I2+!b>)z*7(A>fC3CkA#9 zVhY1vaq9Yw zqdQFM0h}*}G#sfDQqA(!5kcvQ;-VyK;KFIgI5Z~l7|y{+lZODwf20wNkq!{ zmwuGBOL$^LvM4*2fJ4+$JYc7wLSUZnu#k-G*Rwc1>jUEL`|${JM~5yBT>UDib)e5I4rdt z!NCOo{czhQ))fi^q{z)qB=8GY{HJ;WRfh_6UVI5Xm|!RL zfH3!f5VrP%UlDRlBs|)W5~j2$q2k@}LDaGAg*qFJ0_BPXd<_wcvTH(MIANAWK(X6E zWDyh52FFVl!xt&V1SOw9!k~}XrpOa}r4liW{{*;$gxXC$GzTOwH;N!#R;jPkttuTc zo(~bQm`{mQR2Mb|hTTGhKQF(@5enk#eFnf26J6T~cE>5J}vZsVKSOsw3l)< zgb@`ukZqZ+%-v$NVbmi9yx-cs7dMH9RZSsqX9->)q=0;W7zN||6j5Uyr8_1NMxUq2 z)-co#arAHk>6>D-D5g0en+JFz8jFhAmFEn}rF&hm!-^W>;D^)$a2mBLDI+6>dp|_Q zTwnCVVR^Xsz$t1DeHa|6T12E;BoR>+@ku1tTZllTabgNn^NW(aOgxE*F;R}9qMul( zmV_y`BOE>dSBu9)_|b4@xH25h`hC{@SzTFkvOLhazdp1nG%I8TpNFOUjlp?=&jLS! z&A77zlVJny5j$zGvh)4F_225R_fNt8@ON5YvQ}Wncj{pkgey%Q4i?LnE_pp~hvH5rNK4=D85&F0Uc&)Y_G0_F{ioc3IPw&F$m6 z^(#7mUf;y=Uh5CoyUnJB0g`?ucI)?X{-GHG-=%|iTOi$#JIOTLgpLT%g#Qpt+{DL z$*7X@ZhG-$8U9K5%%qQ0i~lY=e?z>h7N6c~+fv=r>D7mxM}&l^{shyl$;a z;?L9@_${`|J|pE)tTWg{W&?K*5 zBg8?Pq-;d(h~@a>CRyIC^-}zK%bh;z>ZQC)N%X$g)@_2d0`aE0_O6Z-bibibvm-{F zIO2HxQP>l@Nn3?_T}JyH>n##iIHWhvh*6;t!$#;5IchAKo$Mv-;y6f&mKLrpJQaUj zqEosZWtO3*^e@qiltkut!t^q&=C6oUs%YWj!ZQ3(_*ggTx}a1g8KwF~Z>e+su%{)`o7Sz>Sq%MEpHfWCKhGmQy?aUC+B|<=7JnHX)lK>=1E%;K z_QgbsQ@gd&ib3mc`ZD|}QapWCS({-q;Ctl{IO2x3whb-u8SNcyU2W^Ote=7FehVj3M-tZN zO$kwunca(ZI+@FozOU1vRUfHkNx{M(tmcD%t+osyI%&x4p=`?J}v;KnP z#PyTK_2za<9&=tdX;Dxd)>d-69_sN? zisP)E>{p9>plMRlt(7PSVEFBHhI{aWd5?lx3Tm;qn^Y$#w@EaOxVfRFAHfc{PA#s8 zZ@{KY=fy{~OlmRbbdwr{CYT-t1iwX51|--rv}Jm?)`QSX9bx*WcNTm&jb4kmv_-WL zx9Q!a{6HCv>(A{WY_!Y0&}CLXp&rZ}bp}*jKW5*g2m>;!A6h@Hn{*fORXlSM=7VX> z>d&cH^+aq&x1*wf;gOyXD(wMm_S<)Oj94^r5txxC!hm)54T>=!vqeJ}P3zXG1p_d< zKaJUO53>`78n-={KSZDX9 zF~jYM8bQoX=$_8u<(Lc=;d>CC7VT{4U3sikeTQ`p3l7U4wgF3!kMEwQvm-^zz6%(> zj$M;~mb^;U!*Ld$LtHc%KWq@|~mIphFKPw(VK%Y9bi(@Av zqv4vDok!B*?uk11%8Uv9FbB`PB)ChyYH^XBz7Y$aXl5_#o}d$|%1G$YAPH6AAx@`} zJt3RYps9HYbUMR758sR(0HRrM!7AScS#z`O(95AiuwY+-UCEz;HN3N7VcvjcyKd~h zF6`&*o9xZ@a9EeS!5_2!YJJbT0@l-KTf+Ba-vM9TH_7`qSY^8md)<%oyzBXUtgc3l55pTNF@XPU^(I+1xBulUZZ6?ip7{h@jN`Ax@Wxc&GoZ9R|H_~jd~Og3HtM?K2vYSga|r%~Mg9AC*X zUMWdBV>{#CK~7EQ_=@pc;}vDRLLBEOX>jZ5I6WWK;S}u6+mGWiT>Tm^$TyyyWIPLs zG}{m?Gz|Z#7@C| zDALP=6v;zVj6)zvqkKS;6DV?jl817PLnX=agOMDcPV$iPjB!XAhlu37nrLhS!?A-g97{3#Gu)J8Y${2P8I0kWbcUOZ+l@`i*hCDA zlcXF&H8Bpqy`vHgi?EF{Gfdq>&~W_4u@0JeiX&0IpWijWUn$zzi~1wjiUGzyTN>_#HU2DB$-l(>HnxAhhn4aK3x#p z-YODrfm^R$Oh0ag6sJG8w{pZ=CCOgRn10*}(z(4Q{vqB{;w|E~AgQ%u2AB%-vEMdV z-}2_pPOO&hz4gf#$0mzo;Fvd96X#Lf{v40xh+`$m+`*bSH=W}#@q2MhiDSgEIGN+* zlQBSYu>Ci4#CC9v@x}3YzSuun><6{%!Kh_Zl>XHA=ZO6!$svPL8TN^4WPNq@=IS-o@#;m<^$jhZ z@eMdt6z+^5=aL$F zgR*d;b6dntcul-yL*9~cODOR4q@~$pJOoNBM|$nm@fN)3;dq_-V@8ibXYe@@`aaR3 zGx#V%zs`W2h^8gInsmnFpY*16hA~xR)EMdu)yg zYuccUae;oI8GYvH;OG&f$#+pw=|t#xh#xOf?*yfC5@|cX)oK3{9X%>`MuXA(C_Ea< zl2XT?k`Z4z@$uN?D00?^s9~eXb52sL->@QRpT{Bz3jjv8bXtX?caD@L zg(Js|?8N1$IH|R6jEu)AY2-+OeV6pTE~11-=8bH`*@UD3-GLsbq{yMiDTED-436wN ze_=N%*3ipWZa*4hsdqu2$sl;9n(rqF z-aA`C@3jnbrLfB2=tv>wwnrkYFQ( zSU0J?px!Dn>h14?)SJ_d@LF>`zt)`7y+vo2x!cm;kR88_oso*vz7DT5NBYTeR(F?< zr@#2R_abOW11vC3O04@%8vx zd}sJhz>fDG@4MdTy$^eD@Fu+H(th{mP39HmF7tfvQg6&V57yPkKru1oH9a4B{^)rf zwiaLTJPn)b58&JX6`ozNquv0EbqhUFPdNN8>fW`p9}4RtSHBo&7&i@2TIV+1k5KT?kw64XRcxF&{MVG9QDj_gS#4Hwr6q zC2Z>b3H$#4OnzTJAn%k1J%rcR)sWZk4sX3Zp@>r*G>!&`1PA}Q-C+P;#d zy|gbVNp;N#elZ1a#Aw8XDZY|AX^O8Gm*mSCX2$J`^pimV+>dp2w6(OvJ4)m^oN`-h zlRD3pI%qG_-re)e+H5%?b&gk!%i^uZS9M@)vit{EdMhOvYzu%^PZ%Ryw|rNMxKhxS zY*+HRl97_cr({tfeG5nP3$!h(b}`p-l{k93=1#5!I)hH#9@;mRRd~hUd&LiQ5Y~=0 z6SNU*W4xuMlXeIpJ6I4J|F+a%xkm6>{`0q-v)#FZT|TOds_CNYoqOOp|}D<<`y%+tmmzXES8) zu3jPbIN+HfHp^Mm>tQHkmUY-~83w zzNbZGV?q0FXx~_eL65DZ>bQ@pd42sb2lV=#KzrKjczq*HO!MdyV>)eePBCW9bYe8s zwZvODbZwL$aU-XD)#0wRVSQY_H$ooYHxjr(ZxJhUI+XQ4c*<9~ozv5lzfEJHsksb3 zNn_x!b{Kq^mWf-Q6obJ3;3;2AW1wTox4Cm^QuDjo=G1jGoUhaXj`A1}gx-+yv;Gc0 zPji^l)iz)Lv%kYnxgX3yM(faJhH(c$C%Dj?>=iHS6)#}11>5j@yL!cTJ$~R?M|&1{ zbtmRf!<|cu=Yy2+4tFk$dfxNE)V-WGemB0$K3mq~bw3S}!#phb)VM26K193i1u{ncw=)XCY%|K?d`b6bkVW`YUNVUlrOq zHrJis+|-3lKM~=^S+aT12&fz=W!J68d*3?hpR^kg;@mJvo|=l&vZ1X5)_^w8zzD)T zh?NTQrshW6#c{~;5?4fqzt+%Zbz58n`;@-t6!5O-4vu3JVhZMVk^@{X!kgn!QwzUpVIjvpX zg+Jk*=D4VjQvPk{$2;n%2#1_XX={zECdaoGO@yx%7sl07Jc?TDbc?rKP>0&xLX&S- zyj|@l<1na^exB-vJUOk8drMpE26dHFSE3pP`624=48&N8yi~It4kbeMDq$K zaEq?53!E${<+`Tk^SC3bUpuwY+^HKJCPFa3w$|aCinp=X8~d4bs&gDh@Q$CmrJ+S_ z=HZU;t>@uVhEle)ov$A3=eWM1vr9cOkmF+mIX;}qRHv!_-S7OL4#)LW=UwVOhZE(( z?Q@&sfI^}k=FC^Wrl<$nn6QH-S5WRK)X`m)tIVx zxHPuXx`(*h;&j-?&28tYcBl3mTP{><)8w<9%o1)#a_IX^uSaxeC5 zAsaFJ<^+jAb)BCa>$no)9i9&EqfSb6lT7xJJXdnlIIgF*V_{v~b2ZnX<3{&Nx2DtU zb8GeG^a7F(1^)^U9Vb3HdM@OaO}U)rIVYXHzO_2Q&7y;NZtV46=hT00F9$fK*FRfK zOWmiXe01#%?WMO)R~+i!lb*P)&Fu8V`_z9yZ~d=BrMmSWO;3c}i#^Bn);|U9U;o2< z>wg^@#i{?}d+UE)om2lOriXOzVo%2Nzme^^N%$N5XZTO?FZGxC7x+v3ll(>g;n=P^ z`?j2r zAMYpr7W_l-H^G;JKMsB`_^sf*!P~LB;=yc2jW@C)p~_;}!>X~l8|^dfm39^E9+ub>?2&ek z?Z;b=bgTEz_#g8>=)Vj05U=%L=HKbR0Co|ZJ&$=F^xWmS#dEFaGS5!W1=!m$?m632 z>p962^UU*1_l$#;!yJ$8G1SNEef6gLm3l!vtDaEbQb*Jwb(6YUU8;8C&B(cGqpDM< z!UkfQny03zF>0s^E06qC{zblvHzO~}A45OnTk>9cyS!drA$#OSvP+&L>*Zz9b#fQpYCm zQCE6|Zd^Avv>W@i)26t=MnpE_0o3Sm-Pm*pyIrK+np-V?SwOoUnR`-h<|VFlflj#5 ztS2XWk!b#zoUl6Hd@UusniANQVGGnEyc;+;o{D@mDap%E{`91z*c>5C{4#8 zQo@{+P?8d+rG!b2;L#NoZ}q(ExESxHgm0yUZ={5~bx|AEd)B!UzrAUR&)n>RS>v$T zVofHmO}GGC6I^M$D~)3bCKb%pj@070!;uJ@@P^nwnk3pv$seSwu@x3xKRSYN1nZNO z@b{GPeoFXbO89+B_-#t~RZ8FjZgG!q!rK;OHZvfJZC0`yTdHI!!+q*n?lZsN}9k$1b+umh3zKtE;A8KDn&!i-it>Dlzt+&&0YxBla zl5$dMY?HrAxe2xeL@@+g4`u0y>wA|g-R?>^xe~v4U(?+!<5Rv)s2rPtm| zUFj>Xw9}Qgxf0v|A+-Z9TlitA+4a?RPrk*j+f-MYn35FFV-#1h)gP7?xW4mUX{IZc zxDt0wjxot~;@b}L2ydph*_RO!mbg=~^d;Ag z-$Sz-cM+Djl38kW!<|i|xEWU=*hvqY%^uhoCz!^dV0av73Z}*hxC7EBKY5Viv&3DN zr881)=Ett&hBL2BxfxrugdNVl&r;L*4dTzP#7(`C+}?HF-f^YxXsNZ$^OP$+!4lTa zdxxbY#T}ol0;nmj8&8GgW|pPgOg3^wZpQCYZpI8(a^o7~Qf`8$28toLL$kyknm!B5 zTPPe$Twa#yT(=b|$^4fqaUD=NlRF?w+{IYx=5TmnVcx?Mv`Ne#u(W>W9Pdh3TINdC zu2jWRyz4w;k0W)NTuLZ1b{JmAt=%}zmAL-k+iEJut<{t%$>70HzJiAyq|F8!1S3g& z;)L5QK60g0Zkvo$Dw_-*)QGW3{L={+H*#Gm%aNLl4;`t&xWbjVpCfLad|bP=#wE&1~ zo;q0?!{Oj2UUa4B9jU?K-i&Y!2KN@09@V}rEr!k)8^xNtX#!bKYhhA?=GoZV<$0CE z;~R&k-jysZwREa3D&TtcU2=$T?TQ=ITxpam4Npnpms)CXQ-5)!xQ~gG8mR9VDL3;2 zS9;HtUU#KeT9~-@hcxxTbd`CU z_H67hH@nh$SBkq5w;gSRXEwNQXSouO0s0W&ZFAh(ys2<)DwuMUxB^(?!0nl@l-)?;%QxM!`FJK` zX|n4(j#@shzD5qDF>a>r&^&xYiyJ)8x7n|GL)<&pm1erqbXVd;n-I=(cFN7e(?V0c zGj9Bm-AH1R>^3#!CSI|Yuk#PXD&XPp!SMd@ z-tcZbLvIbYhc|}n!)wAd;p%WKJU?6CjO;S04!-4jl~b$CLH$Py*Tr?V*jK`p}wC4Zi!wLi0n#`1T(S6^0@q zXzvF<4t@~)S@4I!CxVXz@5d^DTZ7jH-@%%ISIi%nKZSO{YqCIwrO*7#{K$OYd`qsE zXUo&^y?rU3DUX-40qNP52_eM<(&qd7f-lci_qO8ns6y)kW$&e6hb9U-s9l zv(;&81$HtxQO#A;)p&KB%2z>U;@kdv@(uZld=6jtAH_Fvd9LzY;z@YU^R#%@dDeJVcq*|bqS!MYPtVyNOZ{8@RlTcTS1+m`;o13Nb&vmS zf35!{f6PDEKNas~3jJCC?|ds0crb8R;1<}4xGb6YLo^>YqBEN8IO96Ik+I%xU|eIL!MM~u*};<-7u!o1%k3%$D;=z0 zEVauVbjoe{opS!m!Jj$!h=aPhX8G=R&TrQYzix|rUFjYz-DQirwRG4Pce>Kov;>QF zhg|6nE!}F1+g$0ZS~_TpTeNhOEpFD*^|rXtm2S||0b5+BrE6@l-<7V_(p9#&T1!{h zc+TMZg>#lT{D;;(vr(Tfqt*+ZaSGviSu6Lyau5^tn zUFF{>^gt@|7+m8;>U8!shS|nQR~q3;!?l!a8$(^G(3J{YDc_ayw3KBVIj)rLN<&;J z;!0sH1#AQFqG=@_q@b4kwqa|@Ya5m;`LrZ$!{bUyOTsoxS29=<|F*?vTKcDr_Y>S& zkUrJYC${*9mOiq@$6ETEE&i^hKilFjuJoan{$z^}Tq@_IrPsCeiY3JDyX*$i^E8W(DbCEj?(9 zZ))j&TRh-O-_X(#Til1O+h&@^OBUX2QCyOAy^y5qg(S_2Bwa%!=^7$Q*9J+tHjI~i zhHfpAbpInsw;M^i|B5(p)1$;MK&G;nCP>%@ncu|k(Q3y z#^YN0u5CP~rSHrzzUN9$IFe=j(3PHXrKer#2d?yeS9;2oo^&K1p0$|EcYL_RPf6d^ z(j&H^8^(CZHgv;~q#MR~&^C0#7!Twdx+zK06EH~+=~N);3D~&bHgvNY_u7V@fQ_%) zhQ7=gciP6?ItC;?6&rV0hQ7>@q$g#P^aaPb!)M&DV?eskmGlLSeD%~$(w*A(tG02w zE9py=am!3YcPrzT0^?TgdrLkR9&&BmVj@Fsz*~&(Xs3hOcz$Egt8C+HEnQ(7`&{Wt zE$y+5y{>e*mM*o8%d~W{ZG6R*F40ntZS2-kG9M51oCl;mu5_s@UF=FdTH0kB-CEjV z8#}d>u#N3ny2v)RY3WP0@nwAl@<$yU<=}A+4skHzV9>#UgEr%I<7U2jn%KoG{uf8!Y^6DGYBCo^O>+tn{-HE5~jEcN>Ip=pe_%#O)J9x;!I~=^-!P^}C zs)M&Wc#DGv9lY5=edk)_z0os5u9sH$(PJ4Tt_VzgK?Qz=MX6y-X5pDJx+Ujoc8uO?d@^e+vBvi z$0@hRDYwTdx5p{BCv|`4^kWZi&9Yd0TZDfU-i4?75qNsP8_(&bS%J_?c=B$+GqxXJ zt1k*3ANX6~NT5A1)qV%-(ACh);6oc%J#kPyVrM~ZwbEV z|G;~rx7j=2^G|$%-|CsA{-PdG=c)P1g7*JSSnGa*wD3%RySdq1fZhLoYFuw@Fvf`Y zLFRvVlq5;-WrS=jKkBe8t z<4QbE3**X?q36FncMH1{t7iU0rTPRk4cF7!(EJ>0c&VcN?(%B`k%}-K^B(a;5BrP1u zXBM6>(>JJ*oq6)yKKi|N^JX?R%~_Yfe&Twl0Xj-J{Wi9N8Xy)Ro!g(^`keLClcag0 z+4a*2JvWWrda-N0T2ItUl8&McDy|>3_B`3zhg#*z+We}qRiNc4+GNsdqd5JlttzK# zTCz2b7F+&s6oO=H8m+3PDpjQ`Z1Z3hwmDC3>cgsT!TP4+xij-eO&kSgEl#`)y~!4e z*N@q#oKe$~n+9XHDUI2vicxA5F*`najt)P@)uBz5Om)z-dcQF4QhJ|Tnp29Az(d$f$Lc4J4|`7@mahAkUrWBVHNDo?K0@k=t| zujlxreuqf_tJYHW>?AUD>ri{>@S)@%OU88~(=rl?_e-P?kHYf`?ZTmjl*FQB(_rOj z>X*dQm8%^J1)T+UL4EdUu9Y8C6*8T)AG3~w zR%ct+^yFDOVun_yJPQ%sZjVjt@5Va*d9#AfqsAC=2H@JR_~{panYGrWT%)JYC1l&;pgGr^j{bTx)?M z-n`^#I$nkiGI?5hysE9VV!pGv5t^W*EXr|q@>Cr&V;ezvYI@9+0x3*>QnXpgT12Bu z*9@9(T(wi&1k~orRklNrc5gWyDm(G6A(KW=GAAcjJF(bMs2d9xuGJK)_Vfy^0p*>P zHEBw6m1dTyy1ptmb!ReZ!6Y?_`tsc5DNfRT_eTSvQ*!0XbV6>g7?L|S7o2Ja<5WWt zdpYH14~cS3`D7+d3vB~RAeGE|o3$;>Ttusdyo!8bmO6urxm zC+Q@rGCKN6gCtR1se1#)))jEEL#=5K? zB);4HFabR=K#5c&m*_;6W)x(}V2Pwn6!s+6;Ep91>r^spbnzgmxHak@&wEprC#wc4 zP1PVtr0L&dy|FG$TF@@3&j zS%_QaawqttKEW}v%PF{1Qkq4uyud557foSFa<6S;F^Uaqhn=gaog2CE& z0mbdl@#P%z<&xz5!8p!O=lHVuC-Y@xzDyjelJlH=R`%yOFGtSl%aOJrakr4Fn>(|3 z-Mo@~^V+%QwctBHn83TuLQoH-l9=N>z^6?({eSibNIs~*M$ z43Rhp+X7LJ34;wnB%%UV87x@TfK?6c&Sng78pEGRz;FQ}RuPd^B-o57#}6E=DAI|M z7s4n;m@iVC5S<|~`50^oLno4rV6j{d8zmbONFa)E2#G8d3``h^V!}T`)-&jcu=TJg zB-j**#VB8WB=*8)L?Qy?2%=aWooGfPB1&H2V>PIc5W3Imk33OgIJ(l`2XGAtDL5Bsfb5wMZKIn*b0be!CLIh9|-4P+CYe ziqNw;0?HGE)sXrz+hHIAUI}6sJ&L)CWHLVmgC=tQyIJm~`rUS)ZA zqZ&Xe3bSg8?UNwASg{yEb|nymk0@v)#Awhl6dV$$i&bPZf-96#L#2tH)&lf^%h%@N zEDFn7=?BG1B_?9q9j^q8TDY!kI7Ziu5UB8|m2iS_1~GjSL*^uz=w5Jw374^Sh8h8| zDUc{kdO#M{aCD9cf<-}_ZH7?LA-bbcJbjQE6*`CyDlCeorErvJ0{?1&G5ncGgQWzA zq;lfFAvUU5lygYHgv`-Gs$9y!ikYJPxg1#AqLhn-3I?O-C(-nP~teE%*dcF5hl|`3XuuPJAyw75W`S`kvyDGe;6Xl zVK@W^Q^>ar@#t8EGLQ&kL1<-DRC^E^4ZMTf03J0M41z)=@JCTM0#LhbA%&cr0~z3m z>NBcLR1oSqbx;Zh(gn*XS$Sfgm`*hcL>3eTvFJZFCJrkaB@pOiEKpVGBSyeswC$iM zO(bkT2KELcu_*f{TL)>TMUhNsbDnrYaFX1~Dpj zfE!Yv_M`M;NKz;-g)`9f;+aT08cblkjit!}6|mzmiG;*SlmkS)2}U6<)n>KGIf^nfVa|xMajRPgHsNMKq(7MI>-A5-Oi(^g`MIVy zk-)Tv5C;<=NQ4ZUg%GuY3Tc@Mlue>|I`JrcER1o5fIPMUWYGx!DI5G8j)B*h804rp z%0}$SJr<2oumt}nFGKC3_Cf%-k-JVZ5+!Rq=t*oxFor%32@!Ev=#au1jZ;Xd$EoBM zqjrgrH>ZV5!Rug02l&EVAnk?|MI4@M3KLYn7-0H00EWA7r7JyiNbV` z5KRZSi_w;th>9FNS{y9jP6i_l1c;$Hk+N7BdKFH^E^*Qc6vskFB6}~!evBj-ITS{v zA*Sk4SZs|Yx}KWLl3xlY7R=rap6^@3lHCj zh>hl8^fOV%azar_rI?!=KasKxzm2 zLrIW^7<@rqM#l{t|tEc`VxoKaoKmnQ611(43jlSA(p-6Nkqwh zW$K8=(z&Kwfl*4ssW?i|;YGxe@DRA5VC)!!EXwGIv!%d%q-B|M5(!8#{-2g2+;$~6 zDT-TzzXbm9>$HbAim8WZNEDQP@IytT91{4`N;^8B2n~u95D__m(3`j>lkhwJ8q7j=|R5wLZ^8~iGgz<;b=a70n=LQyGVF+QzVr-^9dN!?lCRD{-y>UzSJAQ7c*+l0|c{7`}Q0tXl# zgtI875fh~uid`lIwK9Z-KN8!85rGFb18xgidddasYi)XOl(K; ziOUkvY_x5Htgc3>-AvI9Ut)ludd%<~Qw|5+2>=SgqNvmu%eXb;mcy7ph~3rr6(WHk z6p)&PSV!>(|4M*vC5mOiYAJkUa6|+?Vu(ZOz=0CLgΦ*t=5``@q;lBNDSNpEztp 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%0AyRhVTZ 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 F;xF*hHoO1; diff --git a/tools/windows-replace/.vs/windows-replace/v16/Browse.VC.db-shm b/tools/windows-replace/.vs/windows-replace/v16/Browse.VC.db-shm deleted file mode 100644 index 7fb0c5a30de462d3f05775667234b9abe463352a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 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