From 13a1478d7aea69bede7dd0eefb16d02502a88247 Mon Sep 17 00:00:00 2001 From: Keith <67933144+safestak-keith@users.noreply.github.com> Date: Fri, 10 Jun 2022 22:03:20 +1000 Subject: [PATCH] New commands: DeriveAccountKeyCommand and ConvertVerificationKeyCommand * New commands: DeriveAccountKeyCommand and ConvertVerificationKeyCommand --- README.md | 101 ++++-- Src/ConsoleTool/BackendGateway.cs | 23 +- Src/ConsoleTool/CommandParser.cs | 2 + Src/ConsoleTool/Constants.cs | 12 +- Src/ConsoleTool/Crypto/DecodeBech32Command.cs | 2 +- Src/ConsoleTool/Crypto/EncodeBech32Command.cs | 4 +- Src/ConsoleTool/KeyUtils.cs | 18 +- .../Query/QueryTransactionInfoCommand.cs | 2 - Src/ConsoleTool/ShowBaseHelpCommand.cs | 4 +- .../Wallet/ConvertVerificationKeyCommand.cs | 82 +++++ .../Wallet/DeriveAccountKeyCommand.cs | 68 +++++ .../Wallet/DerivePaymentKeyCommand.cs | 6 +- .../Wallet/DeriveRootKeyCommand.cs | 2 +- .../Wallet/DeriveStakeKeyCommand.cs | 2 +- .../CommandParserShould.cs | 42 +++ .../ConvertVerificationKeyCommandShould.cs | 90 ++++++ .../DeriveAccountKeyCommandShould.cs | 289 ++++++++++++++++++ 17 files changed, 683 insertions(+), 66 deletions(-) create mode 100644 Src/ConsoleTool/Wallet/ConvertVerificationKeyCommand.cs create mode 100644 Src/ConsoleTool/Wallet/DeriveAccountKeyCommand.cs create mode 100644 Tests/ConsoleTool.UnitTests/ConvertVerificationKeyCommandShould.cs create mode 100644 Tests/ConsoleTool.UnitTests/DeriveAccountKeyCommandShould.cs diff --git a/README.md b/README.md index c5e18ae..f22aa9a 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ dotnet tool install --global --add-source ./nupkg cscli --version 0.1.0-local-br ### Overview and Help ```console $ cscli --help -cscli v0.1.0 +cscli v0.3.0 A lightweight cross-platform tool for building and serialising Cardano wallet entities (i.e. recovery-phrases, keys, addresses and transactions), querying the chain and submitting transactions to the testnet or mainnet networks. Please refer to https://github.com/CardanoSharp/cscli for further documentation. USAGE: cscli (OPTION | COMMAND) @@ -80,16 +80,18 @@ Options: Wallet commands: wallet recovery-phrase generate --size [--language ] wallet key root derive --recovery-phrase "" [--language ] [--passphrase ""] + wallet key account derive --recovery-phrase "" [--language ] [--passphrase ""] [--account-index ] wallet key stake derive --recovery-phrase "" [--account-index ] [--address-index ] wallet key payment derive --recovery-phrase "" [--account-index ] [--address-index ] wallet key policy derive --recovery-phrase "" [--policy-index ] + wallet key verification convert --signing-key "" wallet address stake derive --recovery-phrase "" --network [--account-index ] [--address-index ] wallet address payment derive --recovery-phrase "" --network --payment-address-type [--account-index ] [--address-index ] [--stake-account-index ] [--stake-address-index ] Query commands: query tip --network query protocol-parameters --network - query info account --network [--stake-address ][--address ] + query info account --network [--stake-address | --address ] query asset account --network --stake-address query info address --network --address query info transaction --network --tx-id @@ -113,7 +115,7 @@ Arguments: ### Generate Recovery Phrase ```console -$ cscli wallet recovery-phrase generate | tee phrase.prv +$ cscli wallet recovery-phrase generate | tee phrase.en.prv more enjoy seminar food bench online render dry essence indoor crazy page eight fragile mango zoo burger exhibit crouch drop rocket property alter uphold ``` @@ -128,20 +130,35 @@ solución aborto víspera puma molino ático ética feroz hacer orador saler ### Derive Root Key ```console -$ cscli wallet key root derive --recovery-phrase "$(cat phrase.prv)" | tee root.xsk +$ cscli wallet key root derive --recovery-phrase "$(cat phrase.en.prv)" | tee root.en.xsk root_xsk12qpr53a6r7dpjpu2mr6zh96vp4whx2td4zccmplq3am6ph6z4dga6td8nph4qpcnlkdcjkd96p83t23mplvh2w42n6yc3urav8qgph3d9az6lc0px7xq7sau4r4dsfp9h0syfkhge8e6muhd69vz9j6fggdhgd4e ``` +### Derive Account Key +```console +$ cscli wallet key account derive --recovery-phrase "$(cat phrase.en.prv)" | tee acct_0.en.xsk +acct_xsk13pfkzdyzuagmsquy0xjvszdxdjt84x49yrmvt2f3z8ndp6zz4dgka03j3ctm4gne9s5gullvjd7kynxxkny4qwyuuup2mcjfztctswdu3zp4s3ps5dskaq929vrp6cw8z3u77x7mymgntjw46f4l9kh3mcvg78y9 +``` + +
+ Account Key with Specific Index + +```console +$ cscli wallet key account derive --recovery-phrase "$(cat phrase.en.prv)" --account-index 96884067 | tee acct_96884067.en.xsk +acct_xsk1vzcpqwahy0asxuua4gswzjagmt5awjepy9clhmvtr8tgpejz4dglkfl7zhunx0dcrvljtmgcx59yzv728wlxllp646qudhgkuaj3xycu4pkysaaau0lm4z2s8t2yum7nyfn99e3xxrwgaz5yt367r8638uetazlu +``` +
+ ### Derive Payment Key ```console -$ cscli wallet key payment derive --recovery-phrase "$(cat phrase.prv)" | tee pay_0_0.xsk +$ cscli wallet key payment derive --recovery-phrase "$(cat phrase.en.prv)" | tee pay_0_0.en.xsk addr_xsk1fzw9r482t0ekua7rcqewg3k8ju5d9run4juuehm2p24jtuzz4dg4wpeulnqhualvtx9lyy7u0h9pdjvmyhxdhzsyy49szs6y8c9zwfp0eqyrqyl290e6dr0q3fvngmsjn4aask9jjr6q34juh25hczw3euust0dw ```
Payment Key with Custom Indexes ```console -$ cscli wallet key payment derive --recovery-phrase "$(cat phrase.prv)" --account-index 569 --address-index 6949 | tee pay_569_6949.xsk +$ cscli wallet key payment derive --recovery-phrase "$(cat phrase.en.prv)" --account-index 569 --address-index 6949 | tee pay_569_6949.en.xsk addr_xsk1kzjky39hv28q30qecg46f3cag3nwsjnnvn5uf0jtkrsxau2z4dgssyrv8jfwdh6frfkd0hskhszcf98xskje0c6ttcnz7k2cwdmc62uv7k6w7nwdcngkwn0semehjsdaajlv2nr5c0rg077dnsyjwxm05vhkuqet ```
@@ -149,15 +166,15 @@ addr_xsk1kzjky39hv28q30qecg46f3cag3nwsjnnvn5uf0jtkrsxau2z4dgssyrv8jfwdh6frfkd0hs Payment Key with cardano-cli Output Key Files ```console -$ cscli wallet key payment derive --recovery-phrase "$(cat phrase.prv)" --signing-key-file pay_0_0.skey --verification-key-file pay_0_0.vkey | tee pay_0_0.xsk +$ cscli wallet key payment derive --recovery-phrase "$(cat phrase.en.prv)" --signing-key-file pay_0_0.en.skey --verification-key-file pay_0_0.en.vkey | tee pay_0_0.en.xsk addr_xsk1kzjky39hv28q30qecg46f3cag3nwsjnnvn5uf0jtkrsxau2z4dgssyrv8jfwdh6frfkd0hskhszcf98xskje0c6ttcnz7k2cwdmc62uv7k6w7nwdcngkwn0semehjsdaajlv2nr5c0rg077dnsyjwxm05vhkuqet -$ cat pay_0_0.skey +$ cat pay_0_0.en.skey { "type": "PaymentExtendedSigningKeyShelley_ed25519_bip32", "description": "Payment Signing Key", "cborHex": "5880489c51d4ea5bf36e77c3c032e446c79728d28f93acb9ccdf6a0aab25f042ab5157073cfcc17e77ec598bf213dc7dca16c99b25ccdb8a04254b0143443e0a2724de9503426759fa18624657f5bcc932f38220ec9eceb262907caf2d198b6e0faa2fc8083013ea2bf3a68de08a59346e129d7bd858b290f408d65cbaa97c09d1cf" } -$ cat pay_0_0.vkey +$ cat pay_0_0.en.vkey { "type": "PaymentExtendedVerificationKeyShelley_ed25519_bip32", "description": "Payment Verification Key", @@ -188,7 +205,7 @@ $ cat pay_0_0.es.vkey ### Derive Stake Key ```console -$ cscli wallet key stake derive --recovery-phrase "$(cat phrase.prv)" | tee stake_0_0.xsk +$ cscli wallet key stake derive --recovery-phrase "$(cat phrase.en.prv)" | tee stake_0_0.en.xsk stake_xsk1xr5c8423vymrfvrqz58wqqtpekg8cl2s7zvuedeass77emzz4dgs32nfp944ljxw86h7wkxcrut8gr8qmql8gvc9slc8nj9x47a6jtaqqxf9ywd4wfhrzv4c54vcjp827fytdzrxs3gdh5f0a0s7hcf8a5e4ay8g ``` @@ -196,7 +213,7 @@ stake_xsk1xr5c8423vymrfvrqz58wqqtpekg8cl2s7zvuedeass77emzz4dgs32nfp944ljxw86h7wk Stake Key with Custom Indexes ```console -$ cscli wallet key stake derive --recovery-phrase "$(cat phrase.prv)" --account-index 968 --address-index 83106 | tee stake_968_83106.xsk +$ cscli wallet key stake derive --recovery-phrase "$(cat phrase.en.prv)" --account-index 968 --address-index 83106 | tee stake_968_83106.en.xsk stake_xsk14p0lhj3txvfcj8j08dk3ur954hmcfz6u6t00q0a3vnrsd7zz4dgcy9dwcxgf67v4rdp4mk9tkeqw70y4m7va73thnel7jwyx0achc5tyyx8r2au5x3pw37zhznj03v2cajc96paltxlh8hpefssucyecus24q26n ``` @@ -204,15 +221,15 @@ stake_xsk14p0lhj3txvfcj8j08dk3ur954hmcfz6u6t00q0a3vnrsd7zz4dgcy9dwcxgf67v4rdp4mk Stake Key with cardano-cli Output Key Files ```console -$ cscli wallet key stake derive --recovery-phrase "$(cat phrase.prv)" --signing-key-file stake_0_0.skey --verification-key-file stake_0_0.vkey | tee stake_0_0.xsk +$ cscli wallet key stake derive --recovery-phrase "$(cat phrase.en.prv)" --signing-key-file stake_0_0.en.skey --verification-key-file stake_0_0.en.vkey | tee stake_0_0.en.xsk stake_xsk14p0lhj3txvfcj8j08dk3ur954hmcfz6u6t00q0a3vnrsd7zz4dgcy9dwcxgf67v4rdp4mk9tkeqw70y4m7va73thnel7jwyx0achc5tyyx8r2au5x3pw37zhznj03v2cajc96paltxlh8hpefssucyecus24q26n -$ cat stake_0_0.skey +$ cat stake_0_0.en.skey { "type": "StakeExtendedSigningKeyShelley_ed25519_bip32", "description": "Stake Signing Key", "cborHex": "588030e983d551613634b060150ee00161cd907c7d50f099ccb73d843decec42ab5108aa69096b5fc8ce3eafe758d81f16740ce0d83e74330587f079c8a6afbba92f1bd85ec71d2d8ce0180138310983aafffa4585486db1576bc385b0ae350562e6a001925239b5726e3132b8a5598904eaf248b688668450dbd12febe1ebe127ed" } -$ cat stake_0_0.vkey +$ cat stake_0_0.en.vkey { "type": "StakeVerificationKeyShelley_ed25519", "description": "Stake Verification Key", @@ -223,7 +240,7 @@ $ cat stake_0_0.vkey ### Derive Stake/Reward Address ```console -$ cscli wallet address stake derive --recovery-phrase "$(cat phrase.prv)" --network mainnet | tee stake_0_0.addr +$ cscli wallet address stake derive --recovery-phrase "$(cat phrase.en.prv)" --network mainnet | tee stake_0_0.en.addr stake1u9wqktpz964g6jaemt5wr5tspy9cqxpdkw98d022d85kxxc2n2yxj ``` @@ -231,7 +248,7 @@ stake1u9wqktpz964g6jaemt5wr5tspy9cqxpdkw98d022d85kxxc2n2yxj Stake Address with Custom Indexes ```console -$ cscli wallet address stake derive --recovery-phrase "$(cat phrase.prv)" --network mainnet --account-index 1 --address-index 7 | tee stake_1_7.addr +$ cscli wallet address stake derive --recovery-phrase "$(cat phrase.en.prv)" --network mainnet --account-index 1 --address-index 7 | tee stake_1_7.en.addr stake1u87phtdn9shvp39c44elyfdduuqg7wz072vs0vjvc20hvaqym7xan ``` @@ -246,14 +263,15 @@ stake_test1uztkvps54v3yrwvxhvfz9uph8g6e2zd8jcg2cyss45g7xqclj4scq ### Derive Payment Enterprise Address ```console -$ cscli wallet address payment derive --recovery-phrase "$(cat phrase.prv)" --payment-address-type enterprise --network mainnet | tee pay_0_0.addr +$ cscli wallet address payment derive --recovery-phrase "$(cat phrase.en.prv)" --payment-address-type enterprise --network mainnet | tee pay_0_0.en.addr addr1vy5zuhh9685fup86syuzmu3e6eengzv8t46mfqxg086cvqqrukl6w ```
Payment Enterprise Address with Custom Indexes + ```console -$ cscli wallet address payment derive --recovery-phrase "$(cat phrase.prv)" --payment-address-type enterprise --network mainnet --account-index 1387 --address-index 12 | tee pay_1387_12.addr +$ cscli wallet address payment derive --recovery-phrase "$(cat phrase.en.prv)" --payment-address-type enterprise --network mainnet --account-index 1387 --address-index 12 | tee pay_1387_12.en.addr addr1vy3y89nnzdqs4fmqv49fmpqw24hjheen3ce7tch082hh6xcc8pzd9 ``` @@ -261,14 +279,14 @@ addr1vy3y89nnzdqs4fmqv49fmpqw24hjheen3ce7tch082hh6xcc8pzd9 ### Derive Payment Base Address ```console -$ cscli wallet address payment derive --recovery-phrase "$(cat phrase.prv)" --payment-address-type base --network mainnet | tee pay_0_0_0_0.addr +$ cscli wallet address payment derive --recovery-phrase "$(cat phrase.en.prv)" --payment-address-type base --network mainnet | tee pay_0_0_0_0.en.addr addr1qy5zuhh9685fup86syuzmu3e6eengzv8t46mfqxg086cvqzupvkzyt42349mnkhgu8ghqzgtsqvzmvu2w675560fvvdspma4ht ```
Payment Base Address with Custom Indexes ```console -$ cscli wallet address payment derive --recovery-phrase "$(cat phrase.prv)" --payment-address-type base --network mainnet --account-index 1387 --address-index 12 --stake-account-index 968 --stake-address-index 83106 | tee pay_1387_12_968_83106.addr +$ cscli wallet address payment derive --recovery-phrase "$(cat phrase.en.prv)" --payment-address-type base --network mainnet --account-index 1387 --address-index 12 --stake-account-index 968 --stake-address-index 83106 | tee pay_1387_12_968_83106.en.addr addr1qy3y89nnzdqs4fmqv49fmpqw24hjheen3ce7tch082hh6x7nwwgg06dngunf9ea4rd7mu9084sd3km6z56rqd7e04ylslhzn9h ```
@@ -283,14 +301,14 @@ addr_test1qpvttg5263dnutj749k5dcr35yk5mr94fxx0q2zs2xeuxq5hvcrpf2ezgxucdwcjytcrww ### Derive Policy Key ```console -$ cscli wallet key policy derive --recovery-phrase "$(cat phrase.prv)" | tee policy_0.sk +$ cscli wallet key policy derive --recovery-phrase "$(cat phrase.en.prv)" | tee policy_0.en.sk policy_sk1trt3shjrd4gy70q4m2ejgjgsdzwej4whc4r2trrcwedlpm6z4dglxl4nycrd8fptxrkye3tl3q29euxlqj7zndk9cfg4tskqlnp90uqwjqz02 ```
Policy Key with Custom Indexes ```console -$ cscli wallet key policy derive --recovery-phrase "$(cat phrase.prv)" --policy-index 88 | tee policy_88.xsk +$ cscli wallet key policy derive --recovery-phrase "$(cat phrase.en.prv)" --policy-index 88 | tee policy_88.en.xsk policy_sk1tz5k03lravcx7ecjveg6j0ndyydma2a89ny4zkmvzvpz4u6z4dgkxctdpcvhjvjl3j4peywe4l25zu4672eg5qsluz36z5mgm4n2ftg3nhmyd ```
@@ -298,7 +316,7 @@ policy_sk1tz5k03lravcx7ecjveg6j0ndyydma2a89ny4zkmvzvpz4u6z4dgkxctdpcvhjvjl3j4pey Policy Key with cardano-cli Output Key Files ```console -$ cscli wallet key policy derive --recovery-phrase "$(cat phrase.prv)" --signing-key-file policy_0.skey --verification-key-file policy_0.vkey | tee policy_0.xsk +$ cscli wallet key policy derive --recovery-phrase "$(cat phrase.en.prv)" --signing-key-file policy_0.skey --verification-key-file policy_0.vkey | tee policy_0.xsk policy_sk1trt3shjrd4gy70q4m2ejgjgsdzwej4whc4r2trrcwedlpm6z4dglxl4nycrd8fptxrkye3tl3q29euxlqj7zndk9cfg4tskqlnp90uqwjqz02 $ cat policy_0.skey { @@ -315,6 +333,27 @@ $ cat policy_0.vkey ```
+### Convert Verification Key +```console +$ cscli wallet key verification convert --signing-key $(cat pay_0_0.en.xsk) | tee pay_0_0.en.xvk +addr_xvk1m62sxsn8t8apscjx2l6mejfj7wpzpmy7e6ex9yru4uk3nzmwp74zljqgxqf752ln56x7pzjex3hp98tmmpvt9y85prt9ew4f0syarncveq5jl +``` + +
+ Verification Key with cardano-cli Output Key Files + +```console +$ cscli wallet key verification convert --signing-key $(cat stake_0_0.en.xsk) --verification-key-file stake_0_0.en.vkey | tee stake_0_0.en.xvk +stake_xvk1r0v9a3ca9kxwqxqp8qcsnqa2llaytp2gdkc4w67rskc2udg9vtn2qqvj2gum2unwxyet3f2e3yzw4ujgk6yxdpzsm0gjl6lpa0sj0mg4tq9sj +$ cat stake_0_0.en.vkey +{ + "type": "StakeVerificationKeyShelley_ed25519", + "description": "Stake Verification Key", + "cborHex": "58201bd85ec71d2d8ce0180138310983aafffa4585486db1576bc385b0ae350562e6" +} +``` +
+ ### Submit Transaction ```console $ cscli transaction submit --network testnet --cbor-hex 84a600818258207f1d24706e65b3eaef608d6ba5adf8b2bf69254bbd1e1532fa7c601a1d6aca3d000d8001828258390058b5a28ad45b3e2e5ea96d46e071a12d4d8cb5498cf0285051b3c30297660614ab2241b986bb1222f0373a359509a79610ac1210ad11e3031a05f5e10082581d60f3a76db98805ebfb391d8a7fa176e0a4da4d20955c47a5d35936353c1a35a23dbb021a0002ab45031a03831a6f0e80a1008182582047a69a1a41541c00a1e62ab8d78c1870e4f04c0507530b90c7dfde2a144d0cfa58406f50cd131250768a3b707e5eb5797e1dc519157e8c7ac27a72ac472fb546bc4604d3b51b2460e4517e28aea5fd0d19ddf8d95d9bf223e59f0306db0a7794d40af5f6 @@ -325,12 +364,12 @@ $ cscli transaction submit --network testnet --cbor-hex 84a600818258207f1d24706e ```console $ cscli query tip --network mainnet { - "hash": "ae5514780cb47a920cd219a4635a54c9ce517a89c65889325c1fd6d166cfdcaa", - "epoch_no": 339, - "abs_slot": 61499734, - "epoch_slot": 414934, - "block_no": 7271096, - "block_time": "2022-05-20T17:00:25" + "hash": "bc0e501e50c42ed6d2964e418b0a626d6026f86d7c205c27fe5b96cb34d36ad9", + "epoch_no": 343, + "abs_slot": 63207567, + "epoch_slot": 394767, + "block_no": 7353437, + "block_time": "2022-06-09T11:24:18" } ``` @@ -338,7 +377,7 @@ $ cscli query tip --network mainnet ```console $ cscli query protocol-parameters --network mainnet { - "epoch_no": 339, + "epoch_no": 343, "min_fee_a": 44, "min_fee_b": 155381, ... @@ -437,7 +476,7 @@ $ cscli query info transaction --network testnet --txid 4fe73db7e345f6853ade214b ### Bech32 Decode ```console -$ cscli bech32 decode --value "$(cat pay_0_0.addr)" +$ cscli bech32 decode --value "$(cat pay_0_0.en.addr)" 61282e5ee5d1e89e04fa81382df239d6733409875d75b480c879f58600 ``` diff --git a/Src/ConsoleTool/BackendGateway.cs b/Src/ConsoleTool/BackendGateway.cs index 67f21ef..0dc5bf7 100644 --- a/Src/ConsoleTool/BackendGateway.cs +++ b/Src/ConsoleTool/BackendGateway.cs @@ -1,18 +1,17 @@ using CardanoSharp.Wallet.Enums; using Refit; -namespace Cscli.ConsoleTool.Koios +namespace Cscli.ConsoleTool.Koios; + +public static class BackendGateway { - public static class BackendGateway - { - public static T GetBackendClient(NetworkType networkType) => - RestService.For(GetBaseUrlForNetwork(networkType)); + public static T GetBackendClient(NetworkType networkType) => + RestService.For(GetBaseUrlForNetwork(networkType)); - private static string GetBaseUrlForNetwork(NetworkType networkType) => networkType switch - { - NetworkType.Mainnet => "https://api.koios.rest/api/v0", - NetworkType.Testnet => "https://testnet.koios.rest/api/v0", - _ => throw new ArgumentException($"{nameof(networkType)} {networkType} is invalid", nameof(networkType)) - }; - } + private static string GetBaseUrlForNetwork(NetworkType networkType) => networkType switch + { + NetworkType.Mainnet => "https://api.koios.rest/api/v0", + NetworkType.Testnet => "https://testnet.koios.rest/api/v0", + _ => throw new ArgumentException($"{nameof(networkType)} {networkType} is invalid", nameof(networkType)) + }; } diff --git a/Src/ConsoleTool/CommandParser.cs b/Src/ConsoleTool/CommandParser.cs index 2c2f8c1..850a45f 100644 --- a/Src/ConsoleTool/CommandParser.cs +++ b/Src/ConsoleTool/CommandParser.cs @@ -55,11 +55,13 @@ private static ICommand ParseWalletCommands(string intent, string[] args) => { "wallet recovery-phrase generate" => BuildCommand(args), "wallet key root derive" => BuildCommand(args), + "wallet key account derive" => BuildCommand(args), "wallet key payment derive" => BuildCommand(args), "wallet key stake derive" => BuildCommand(args), "wallet key policy derive" => BuildCommand(args), "wallet address payment derive" => BuildCommand(args), "wallet address stake derive" => BuildCommand(args), + "wallet key verification convert" or "wallet key public convert" => BuildCommand(args), _ => new ShowInvalidArgumentCommand(intent) }; diff --git a/Src/ConsoleTool/Constants.cs b/Src/ConsoleTool/Constants.cs index 217a573..cefbb09 100644 --- a/Src/ConsoleTool/Constants.cs +++ b/Src/ConsoleTool/Constants.cs @@ -9,9 +9,14 @@ public static class Constants public const int DefaultMnemonicCount = 24; public const int MaxDerivationPathIndex = Int32.MaxValue; // 2^31 - 1 // Bech32 Prefixes https://cips.cardano.org/cips/cip5/ - public const string RootKeyExtendedBech32Prefix = "root_xsk"; - public const string PaymentSigningKeyBech32Prefix = "addr_xsk"; - public const string StakeSigningKeyBech32Prefix = "stake_xsk"; + public const string RootExtendedSigningKeyBech32Prefix = "root_xsk"; + public const string RootSigningKeyBech32Prefix = "root_sk"; + public const string AccountExtendedSigningKeyBech32Prefix = "acct_xsk"; + public const string AccountSigningKeyBech32Prefix = "acct_sk"; + public const string PaymentExtendedSigningKeyBech32Prefix = "addr_xsk"; + public const string PaymentSigningKeyBech32Prefix = "addr_sk"; + public const string StakeExtendedSigningKeyBech32Prefix = "stake_xsk"; + public const string StakeSigningKeyBech32Prefix = "stake_sk"; public const string PolicySigningKeyBech32Prefix = "policy_sk"; // JSON CBOR text envelopes from cardano-cli public const string PaymentSKeyJsonTypeField = "PaymentSigningKeyShelley_ed25519"; @@ -50,6 +55,7 @@ public static class Constants { "--stake-address", "stakeAddress" }, { "--cbor-hex", "cborHex" }, { "--tx-id", "txId" }, + { "--signing-key", "signingKey" }, //{ "--output-format", "outputFormat" }, }; } \ No newline at end of file diff --git a/Src/ConsoleTool/Crypto/DecodeBech32Command.cs b/Src/ConsoleTool/Crypto/DecodeBech32Command.cs index 87ae257..78edd3e 100644 --- a/Src/ConsoleTool/Crypto/DecodeBech32Command.cs +++ b/Src/ConsoleTool/Crypto/DecodeBech32Command.cs @@ -23,7 +23,7 @@ public ValueTask ExecuteAsync(CancellationToken ct) try { var hex = Bech32 - .Decode(Value, out var ver, out var prefix) + .Decode(Value, out _, out _) .ToStringHex(); var result = CommandResult.Success(hex); return ValueTask.FromResult(result); diff --git a/Src/ConsoleTool/Crypto/EncodeBech32Command.cs b/Src/ConsoleTool/Crypto/EncodeBech32Command.cs index 9cfddec..654d3e5 100644 --- a/Src/ConsoleTool/Crypto/EncodeBech32Command.cs +++ b/Src/ConsoleTool/Crypto/EncodeBech32Command.cs @@ -18,8 +18,8 @@ public ValueTask ExecuteAsync(CancellationToken ct) try { var rawBytesValue = Convert.FromHexString(Value); - var hex = Bech32.Encode(rawBytesValue, Prefix); - var result = CommandResult.Success(hex); + var bech32Value = Bech32.Encode(rawBytesValue, Prefix); + var result = CommandResult.Success(bech32Value); return ValueTask.FromResult(result); } catch (FormatException ex) diff --git a/Src/ConsoleTool/KeyUtils.cs b/Src/ConsoleTool/KeyUtils.cs index f37116c..d4474fc 100644 --- a/Src/ConsoleTool/KeyUtils.cs +++ b/Src/ConsoleTool/KeyUtils.cs @@ -9,16 +9,16 @@ public static byte[] BuildNonExtendedSkeyWithVerificationKeyBytes(this PrivateKe { var pubKey = prvKey.GetPublicKey(false); var skeyBytes = new byte[prvKey.Key.Length + pubKey.Key.Length]; - Array.Copy(prvKey.Key, skeyBytes, prvKey.Key.Length); - Array.Copy(pubKey.Key, 0, skeyBytes, prvKey.Key.Length, pubKey.Key.Length); + Buffer.BlockCopy(prvKey.Key, 0, skeyBytes, 0, prvKey.Key.Length); + Buffer.BlockCopy(pubKey.Key, 0, skeyBytes, prvKey.Key.Length, pubKey.Key.Length); return skeyBytes; } public static byte[] BuildExtendedSkeyBytes(this PrivateKey prvKey) { var extendedKeyBytes = new byte[prvKey.Key.Length + prvKey.Chaincode.Length]; - Array.Copy(prvKey.Key, extendedKeyBytes, prvKey.Key.Length); - Array.Copy(prvKey.Chaincode, 0, extendedKeyBytes, prvKey.Key.Length, prvKey.Chaincode.Length); + Buffer.BlockCopy(prvKey.Key, 0, extendedKeyBytes, 0, prvKey.Key.Length); + Buffer.BlockCopy(prvKey.Chaincode, 0, extendedKeyBytes, prvKey.Key.Length, prvKey.Chaincode.Length); return extendedKeyBytes; } @@ -26,17 +26,17 @@ public static byte[] BuildExtendedSkeyWithVerificationKeyBytes(this PrivateKey p { var pubKey = prvKey.GetPublicKey(false); var extendedKeyBytes = new byte[prvKey.Key.Length + pubKey.Key.Length + prvKey.Chaincode.Length]; - Array.Copy(prvKey.Key, extendedKeyBytes, prvKey.Key.Length); - Array.Copy(pubKey.Key, 0, extendedKeyBytes, prvKey.Key.Length, pubKey.Key.Length); - Array.Copy(prvKey.Chaincode, 0, extendedKeyBytes, prvKey.Key.Length + pubKey.Key.Length, prvKey.Chaincode.Length); + Buffer.BlockCopy(prvKey.Key, 0, extendedKeyBytes, 0, prvKey.Key.Length); + Buffer.BlockCopy(pubKey.Key, 0, extendedKeyBytes, prvKey.Key.Length, pubKey.Key.Length); + Buffer.BlockCopy(prvKey.Chaincode, 0, extendedKeyBytes, prvKey.Key.Length + pubKey.Key.Length, prvKey.Chaincode.Length); return extendedKeyBytes; } public static byte[] BuildExtendedVkeyBytes(this PublicKey pubKey) { var extendedKeyBytes = new byte[pubKey.Key.Length + pubKey.Chaincode.Length]; - Array.Copy(pubKey.Key, extendedKeyBytes, pubKey.Key.Length); - Array.Copy(pubKey.Chaincode, 0, extendedKeyBytes, pubKey.Key.Length, pubKey.Chaincode.Length); + Buffer.BlockCopy(pubKey.Key, 0, extendedKeyBytes, 0, pubKey.Key.Length); + Buffer.BlockCopy(pubKey.Chaincode, 0, extendedKeyBytes, pubKey.Key.Length, pubKey.Chaincode.Length); return extendedKeyBytes; } diff --git a/Src/ConsoleTool/Query/QueryTransactionInfoCommand.cs b/Src/ConsoleTool/Query/QueryTransactionInfoCommand.cs index a548bb7..cdae001 100644 --- a/Src/ConsoleTool/Query/QueryTransactionInfoCommand.cs +++ b/Src/ConsoleTool/Query/QueryTransactionInfoCommand.cs @@ -1,7 +1,5 @@ using CardanoSharp.Koios.Sdk; -using CardanoSharp.Wallet.Encoding; using CardanoSharp.Wallet.Enums; -using CardanoSharp.Wallet.Models.Addresses; using Cscli.ConsoleTool.Koios; using System.Text.Json; using static Cscli.ConsoleTool.Constants; diff --git a/Src/ConsoleTool/ShowBaseHelpCommand.cs b/Src/ConsoleTool/ShowBaseHelpCommand.cs index 51c10da..cc5f549 100644 --- a/Src/ConsoleTool/ShowBaseHelpCommand.cs +++ b/Src/ConsoleTool/ShowBaseHelpCommand.cs @@ -21,16 +21,18 @@ public ValueTask ExecuteAsync(CancellationToken ct) Wallet commands: wallet recovery-phrase generate --size [--language ] wallet key root derive --recovery-phrase """" [--language ] [--passphrase """"] + wallet key account derive --recovery-phrase """" [--language ] [--passphrase """"] [--account-index ] wallet key stake derive --recovery-phrase """" [--language ] [--passphrase """"] [--account-index ] [--address-index ] [--verification-key-file ] [--signing-key-file ] wallet key payment derive --recovery-phrase """" [--language ] [--passphrase """"] [--account-index ] [--address-index ] [--verification-key-file ] [--signing-key-file ] wallet key policy derive --recovery-phrase """" [--language ] [--passphrase """"] [--policy-index ] [--verification-key-file ] [--signing-key-file ] + wallet key verification convert --signing-key """" [--verification-key-file ] wallet address stake derive --recovery-phrase """" --network [--language ] [--passphrase """"] [--account-index ] [--address-index ] wallet address payment derive --recovery-phrase """" --network --payment-address-type [--language ] [--passphrase """"] [--account-index ] [--address-index ] [--stake-account-index ] [--stake-address-index ] Query commands: query tip --network query protocol-parameters --network - query info account --network [--stake-address ][--address ] + query info account --network [--stake-address | --address ] query asset account --network --stake-address query info address --network --address query info transaction --network --tx-id diff --git a/Src/ConsoleTool/Wallet/ConvertVerificationKeyCommand.cs b/Src/ConsoleTool/Wallet/ConvertVerificationKeyCommand.cs new file mode 100644 index 0000000..488a562 --- /dev/null +++ b/Src/ConsoleTool/Wallet/ConvertVerificationKeyCommand.cs @@ -0,0 +1,82 @@ +using CardanoSharp.Wallet.Encoding; +using CardanoSharp.Wallet.Extensions.Models; +using CardanoSharp.Wallet.Models.Keys; +using System.Text.Json; +using static Cscli.ConsoleTool.Constants; + +namespace Cscli.ConsoleTool.Wallet; + +public class ConvertVerificationKeyCommand : ICommand +{ + private static readonly HashSet SupportedSigningKeyPrefixes = new() + { + RootExtendedSigningKeyBech32Prefix, RootSigningKeyBech32Prefix, + AccountExtendedSigningKeyBech32Prefix, AccountSigningKeyBech32Prefix, + PaymentExtendedSigningKeyBech32Prefix, PaymentSigningKeyBech32Prefix, + StakeExtendedSigningKeyBech32Prefix, StakeSigningKeyBech32Prefix, + PolicySigningKeyBech32Prefix + }; + + public string? SigningKey { get; init; } + public string? VerificationKeyFile { get; init; } = null; + + public async ValueTask ExecuteAsync(CancellationToken ct) + { + if (string.IsNullOrWhiteSpace(SigningKey)) + return CommandResult.FailureInvalidOptions( + "Invalid option --sigining-key is required"); + + if (!Bech32.IsValid(SigningKey)) + return CommandResult.FailureInvalidOptions( + "Invalid option --sigining-key is not in bech32 format - please see https://cips.cardano.org/cips/cip5/"); + + var signingKeyBytes = Bech32.Decode(SigningKey, out _, out var sKeyPrefix); + if (!SupportedSigningKeyPrefixes.Contains(sKeyPrefix)) + return CommandResult.FailureInvalidOptions( + $"Invalid option --sigining-key with prefix '{sKeyPrefix}' is not supported"); + + var signingKey = new PrivateKey(signingKeyBytes[..64], signingKeyBytes[64..]); + var verificationKey = signingKey.GetPublicKey(false); + + var vKeyPrefix = sKeyPrefix.Replace("sk", "vk"); + var bech32VKey = verificationKey.Chaincode.Length == 0 + ? Bech32.Encode(verificationKey.Key, vKeyPrefix) + : Bech32.Encode(verificationKey.BuildExtendedVkeyBytes(), vKeyPrefix); + if (!string.IsNullOrWhiteSpace(VerificationKeyFile)) + { + var vkeyCborTextEnvelope = BuildTextEnvelope(sKeyPrefix, verificationKey); + if (vkeyCborTextEnvelope != null) + await File.WriteAllTextAsync(VerificationKeyFile, JsonSerializer.Serialize(vkeyCborTextEnvelope, SerialiserOptions), ct).ConfigureAwait(false); + } + + return CommandResult.Success(bech32VKey); + } + + private static TextEnvelope? BuildTextEnvelope(string sKeyPrefix, PublicKey vKey) => sKeyPrefix switch + { + PaymentExtendedSigningKeyBech32Prefix => new TextEnvelope( + PaymentExtendedVKeyJsonTypeField, + PaymentVKeyJsonDescriptionField, + KeyUtils.BuildCborHexPayload(vKey.BuildExtendedVkeyBytes())), + PaymentSigningKeyBech32Prefix => new TextEnvelope( + PaymentVKeyJsonTypeField, + PaymentVKeyJsonDescriptionField, + KeyUtils.BuildCborHexPayload(vKey.Key)), + // cardano-cli compatibility requires us to use non-extended verification keys + StakeExtendedSigningKeyBech32Prefix => new TextEnvelope( + StakeVKeyJsonTypeField, + StakeVKeyJsonDescriptionField, + KeyUtils.BuildCborHexPayload(vKey.Key)), + // cardano-cli compatibility requires us to use non-extended verification keys + StakeSigningKeyBech32Prefix => new TextEnvelope( + StakeVKeyJsonTypeField, + StakeVKeyJsonDescriptionField, + KeyUtils.BuildCborHexPayload(vKey.Key)), + // cardano-cli compatibility requires us to use extended payment verification keys for policy keys + PolicySigningKeyBech32Prefix => new TextEnvelope( + PaymentExtendedVKeyJsonTypeField, + PaymentVKeyJsonDescriptionField, + KeyUtils.BuildCborHexPayload(vKey.BuildExtendedVkeyBytes())), + _ => null + }; +} diff --git a/Src/ConsoleTool/Wallet/DeriveAccountKeyCommand.cs b/Src/ConsoleTool/Wallet/DeriveAccountKeyCommand.cs new file mode 100644 index 0000000..7d1e9c8 --- /dev/null +++ b/Src/ConsoleTool/Wallet/DeriveAccountKeyCommand.cs @@ -0,0 +1,68 @@ +using CardanoSharp.Wallet; +using CardanoSharp.Wallet.Encoding; +using CardanoSharp.Wallet.Enums; +using CardanoSharp.Wallet.Extensions.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static Cscli.ConsoleTool.Constants; + +namespace Cscli.ConsoleTool.Wallet; + +public class DeriveAccountKeyCommand : ICommand +{ + public string? Mnemonic { get; init; } + public string Language { get; init; } = DefaultMnemonicLanguage; + public string Passphrase { get; init; } = string.Empty; + public int AccountIndex { get; init; } = 0; + + public ValueTask ExecuteAsync(CancellationToken ct) + { + if (string.IsNullOrWhiteSpace(Mnemonic)) + { + return ValueTask.FromResult(CommandResult.FailureInvalidOptions( + $"Invalid option --recovery-phrase is required")); + } + if (!Enum.TryParse(Language, ignoreCase: true, out var wordlist)) + { + return ValueTask.FromResult(CommandResult.FailureInvalidOptions( + $"Invalid option --language {Language} is not supported")); + } + var wordCount = Mnemonic.Split(' ', StringSplitOptions.TrimEntries).Length; + if (!ValidMnemonicSizes.Contains(wordCount)) + { + return ValueTask.FromResult(CommandResult.FailureInvalidOptions( + $"Invalid option --recovery-phrase must have the following word count ({string.Join(", ", ValidMnemonicSizes)})")); + } + if (AccountIndex < 0 || AccountIndex > MaxDerivationPathIndex) + { + return ValueTask.FromResult(CommandResult.FailureInvalidOptions( + $"Invalid option --account-index must be between 0 and {MaxDerivationPathIndex}")); + } + + var mnemonicService = new MnemonicService(); + try + { + var rootPrvKey = mnemonicService.Restore(Mnemonic, wordlist) + .GetRootKey(Passphrase); + var accountSkey = rootPrvKey.Derive($"m/1852'/1815'/{AccountIndex}'"); + var accountVkey = accountSkey.GetPublicKey(false); + var accountSkeyExtendedBytes = accountSkey.BuildExtendedSkeyBytes(); + var bech32AccountkeyExtended = Bech32.Encode(accountSkeyExtendedBytes, AccountExtendedSigningKeyBech32Prefix); + var result = CommandResult.Success(bech32AccountkeyExtended); + return ValueTask.FromResult(result); + } + catch (ArgumentException ex) + { + return ValueTask.FromResult( + CommandResult.FailureInvalidOptions(ex.Message)); + } + catch (Exception ex) + { + return ValueTask.FromResult( + CommandResult.FailureUnhandledException("Unexpected error", ex)); + } + } +} diff --git a/Src/ConsoleTool/Wallet/DerivePaymentKeyCommand.cs b/Src/ConsoleTool/Wallet/DerivePaymentKeyCommand.cs index 4f60698..bdc43a1 100644 --- a/Src/ConsoleTool/Wallet/DerivePaymentKeyCommand.cs +++ b/Src/ConsoleTool/Wallet/DerivePaymentKeyCommand.cs @@ -29,12 +29,12 @@ public async ValueTask ExecuteAsync(CancellationToken ct) var mnemonicService = new MnemonicService(); try { - var rootKey = mnemonicService.Restore(Mnemonic, wordList) + var rootPrvKey = mnemonicService.Restore(Mnemonic, wordList) .GetRootKey(Passphrase); - var paymentSkey = rootKey.Derive($"m/1852'/1815'/{AccountIndex}'/0/{AddressIndex}"); + var paymentSkey = rootPrvKey.Derive($"m/1852'/1815'/{AccountIndex}'/0/{AddressIndex}"); var paymentVkey = paymentSkey.GetPublicKey(false); var paymentSkeyExtendedBytes = paymentSkey.BuildExtendedSkeyBytes(); - var bech32PaymentSkeyExtended = Bech32.Encode(paymentSkeyExtendedBytes, PaymentSigningKeyBech32Prefix); + var bech32PaymentSkeyExtended = Bech32.Encode(paymentSkeyExtendedBytes, PaymentExtendedSigningKeyBech32Prefix); var result = CommandResult.Success(bech32PaymentSkeyExtended); // Write output to CBOR JSON file outputs if optional file paths are supplied if (!string.IsNullOrWhiteSpace(SigningKeyFile)) diff --git a/Src/ConsoleTool/Wallet/DeriveRootKeyCommand.cs b/Src/ConsoleTool/Wallet/DeriveRootKeyCommand.cs index 469ead2..0f2df0a 100644 --- a/Src/ConsoleTool/Wallet/DeriveRootKeyCommand.cs +++ b/Src/ConsoleTool/Wallet/DeriveRootKeyCommand.cs @@ -37,7 +37,7 @@ public ValueTask ExecuteAsync(CancellationToken ct) var rootPrvKey = mnemonicService.Restore(Mnemonic, wordlist) .GetRootKey(Passphrase); var rootKeyExtendedBytes = rootPrvKey.BuildExtendedSkeyBytes(); - var bech32ExtendedRootKey = Bech32.Encode(rootKeyExtendedBytes, RootKeyExtendedBech32Prefix); + var bech32ExtendedRootKey = Bech32.Encode(rootKeyExtendedBytes, RootExtendedSigningKeyBech32Prefix); return ValueTask.FromResult(CommandResult.Success(bech32ExtendedRootKey)); } catch (ArgumentException ex) diff --git a/Src/ConsoleTool/Wallet/DeriveStakeKeyCommand.cs b/Src/ConsoleTool/Wallet/DeriveStakeKeyCommand.cs index c49c974..4d1fd31 100644 --- a/Src/ConsoleTool/Wallet/DeriveStakeKeyCommand.cs +++ b/Src/ConsoleTool/Wallet/DeriveStakeKeyCommand.cs @@ -34,7 +34,7 @@ public async ValueTask ExecuteAsync(CancellationToken ct) var stakeSkey = rootKey.Derive($"m/1852'/1815'/{AccountIndex}'/2/{AddressIndex}"); var stakeVkey = stakeSkey.GetPublicKey(false); var stakeSkeyExtendedBytes = stakeSkey.BuildExtendedSkeyBytes(); - var bech32StakeSkeyExtended = Bech32.Encode(stakeSkeyExtendedBytes, StakeSigningKeyBech32Prefix); + var bech32StakeSkeyExtended = Bech32.Encode(stakeSkeyExtendedBytes, StakeExtendedSigningKeyBech32Prefix); var result = CommandResult.Success(bech32StakeSkeyExtended); // Write output to CBOR JSON file outputs if optional file paths are supplied if (!string.IsNullOrWhiteSpace(SigningKeyFile)) diff --git a/Tests/ConsoleTool.UnitTests/CommandParserShould.cs b/Tests/ConsoleTool.UnitTests/CommandParserShould.cs index ce2b9ec..7e1f397 100644 --- a/Tests/ConsoleTool.UnitTests/CommandParserShould.cs +++ b/Tests/ConsoleTool.UnitTests/CommandParserShould.cs @@ -91,6 +91,32 @@ public void ParseArgs_Correctly_To_DeriveRootKeyCommand_When_Options_Are_Valid( ((DeriveRootKeyCommand)command).Passphrase.Should().Be(expectedPassPhrase); } + [Theory] + [InlineData( + "wallet key account derive --recovery-phrase {MNEMONIC}", + "rapid limit bicycle embrace speak column spoil casino become evolve unknown worry letter team laptop unknown false elbow bench analyst dilemma engage pulse plug", + Constants.DefaultMnemonicLanguage, + "", 0)] + [InlineData( + "wallet key account derive --recovery-phrase {MNEMONIC} --passphrase screenfuryidentifyworldsailarenadevoteonlygas --language Italian --account-index 1234567", + "svagare sprecare dirupo scandalo pedonale midollo indagine oste proroga fessura partire snervato pulsante eremita poesia esito esofago coltivato affisso cravatta lombare pupazzo colore spruzzo", + "Italian", + "screenfuryidentifyworldsailarenadevoteonlygas", 1234567)] + public void ParseArgs_Correctly_To_DeriveAccountCommand_When_Options_Are_Valid( + string flatArgs, string expectedMnemonic, string expectedLanguage, string expectedPassPhrase, int expectedAccountIndex) + { + var args = GenerateArgs(flatArgs, expectedMnemonic); + + var command = CommandParser.ParseArgsToCommand(args); + + var acctKeyCommand = (DeriveAccountKeyCommand)command; + command.Should().BeOfType(); + acctKeyCommand.Mnemonic.Should().Be(expectedMnemonic); + acctKeyCommand.Language.Should().Be(expectedLanguage); + acctKeyCommand.Passphrase.Should().Be(expectedPassPhrase); + acctKeyCommand.AccountIndex.Should().Be(expectedAccountIndex); + } + [Theory] [InlineData( "wallet key payment derive --recovery-phrase {MNEMONIC}", @@ -171,6 +197,22 @@ public void ParseArgs_Correctly_To_DeriveStakeKeyCommand_When_Options_Are_Valid( stakeKeyCommand.AddressIndex.Should().Be(expectedAddressIndex); } + [Theory] + [InlineData( + "wallet key verification convert --signing-key addr_xsk1fzw9r482t0ekua7rcqewg3k8ju5d9run4juuehm2p24jtuzz4dg4wpeulnqhualvtx9lyy7u0h9pdjvmyhxdhzsyy49szs6y8c9zwfp0eqyrqyl290e6dr0q3fvngmsjn4aask9jjr6q34juh25hczw3euust0dw", + "addr_xsk1fzw9r482t0ekua7rcqewg3k8ju5d9run4juuehm2p24jtuzz4dg4wpeulnqhualvtx9lyy7u0h9pdjvmyhxdhzsyy49szs6y8c9zwfp0eqyrqyl290e6dr0q3fvngmsjn4aask9jjr6q34juh25hczw3euust0dw", null)] + [InlineData( + "wallet key verification convert --signing-key policy_sk1trt3shjrd4gy70q4m2ejgjgsdzwej4whc4r2trrcwedlpm6z4dglxl4nycrd8fptxrkye3tl3q29euxlqj7zndk9cfg4tskqlnp90uqwjqz02 --verification-key-file pay.vkey", + "policy_sk1trt3shjrd4gy70q4m2ejgjgsdzwej4whc4r2trrcwedlpm6z4dglxl4nycrd8fptxrkye3tl3q29euxlqj7zndk9cfg4tskqlnp90uqwjqz02", "pay.vkey")] + public void ParseArgs_Correctly_To_ConvertVerificationKeyCommand_When_Options_Are_Valid( + string args, string expectedSigningKey, string expectedVerificationKeyFile) + { + var command = CommandParser.ParseArgsToCommand(args.Split(' ')); + command.Should().BeOfType(); + ((ConvertVerificationKeyCommand)command).SigningKey.Should().Be(expectedSigningKey); + ((ConvertVerificationKeyCommand)command).VerificationKeyFile.Should().Be(expectedVerificationKeyFile); + } + [Theory] [InlineData( "wallet address stake derive --recovery-phrase {MNEMONIC} --network Testnet", diff --git a/Tests/ConsoleTool.UnitTests/ConvertVerificationKeyCommandShould.cs b/Tests/ConsoleTool.UnitTests/ConvertVerificationKeyCommandShould.cs new file mode 100644 index 0000000..ec31c35 --- /dev/null +++ b/Tests/ConsoleTool.UnitTests/ConvertVerificationKeyCommandShould.cs @@ -0,0 +1,90 @@ +using Cscli.ConsoleTool.Wallet; +using FluentAssertions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Cscli.ConsoleTool.UnitTests; + +public class ConvertVerificationKeyCommandShould +{ + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Mnemonic_Is_Not_Supplied( + string invalidSigningKey) + { + var command = new ConvertVerificationKeyCommand() + { + SigningKey = invalidSigningKey + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().Be("Invalid option --sigining-key is required"); + } + + [Theory] + [InlineData("KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ")] + [InlineData("1E99423A4ED27608A15A2616A2B0E9E52CED330AC530EDCC32C8FFC6A526AEDD")] + [InlineData("0x983110309620D911731Ac0932219af06091b6744")] + [InlineData("some_sk1aaa3shjrd4gy70q4m2ejgjgsdzwej4whc4r2trrcwedlpm6z4dglxl4nycrd8fptxrkye3tl3q29euxlqj7zndk9cfg4tskqlnp90uqwjqz02")] + [InlineData("addr_xsk1fzw9r482t0ekua7rcqewg3k8ju5d9run4juuehm2p24jtuzz4dg4wpeulnqhualvtx9lyy7u0h9pdjvmyhxdhzsyy49szs6y8c9zwfp0eqyrqyl290e6dr0q3fvngmsjn4aask9jjr6q34juh25hczw3euust000")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_SigningKey_Is_Not_Valid_Bech32( + string invalidSigningKey) + { + var command = new ConvertVerificationKeyCommand() + { + SigningKey = invalidSigningKey + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().Be("Invalid option --sigining-key is not in bech32 format - please see https://cips.cardano.org/cips/cip5/"); + } + + [Theory] + [InlineData("addr_test1qpvttg5263dnutj749k5dcr35yk5mr94fxx0q2zs2xeuxq5hvcrpf2ezgxucdwcjytcrww34j5y609ss4sfpptg3uvpsxmcdtf", "addr_test")] + [InlineData("addr1vy5zuhh9685fup86syuzmu3e6eengzv8t46mfqxg086cvqqrukl6w", "addr")] + [InlineData("stake_test1uztkvps54v3yrwvxhvfz9uph8g6e2zd8jcg2cyss45g7xqclj4scq", "stake_test")] + [InlineData("stake1u9wqktpz964g6jaemt5wr5tspy9cqxpdkw98d022d85kxxc2n2yxj", "stake")] + [InlineData("addr_xvk1m62sxsn8t8apscjx2l6mejfj7wpzpmy7e6ex9yru4uk3nzmwp74zljqgxqf752ln56x7pzjex3hp98tmmpvt9y85prt9ew4f0syarncveq5jl", "addr_xvk")] + [InlineData("policy_vk17s29wgt93lj3m830qhlpx8rx5shwmtljhdswdjyjetprhu5yaahqwzt9lc", "policy_vk")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_SigningKey_Is_Not_Supported( + string invalidSigningKey, string derivedPrefix) + { + var command = new ConvertVerificationKeyCommand() + { + SigningKey = invalidSigningKey + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().Be($"Invalid option --sigining-key with prefix '{derivedPrefix}' is not supported"); + } + + [Theory] + [InlineData("addr_xsk1fzw9r482t0ekua7rcqewg3k8ju5d9run4juuehm2p24jtuzz4dg4wpeulnqhualvtx9lyy7u0h9pdjvmyhxdhzsyy49szs6y8c9zwfp0eqyrqyl290e6dr0q3fvngmsjn4aask9jjr6q34juh25hczw3euust0dw", "addr_xvk1m62sxsn8t8apscjx2l6mejfj7wpzpmy7e6ex9yru4uk3nzmwp74zljqgxqf752ln56x7pzjex3hp98tmmpvt9y85prt9ew4f0syarncveq5jl")] + [InlineData("stake_xsk1xr5c8423vymrfvrqz58wqqtpekg8cl2s7zvuedeass77emzz4dgs32nfp944ljxw86h7wkxcrut8gr8qmql8gvc9slc8nj9x47a6jtaqqxf9ywd4wfhrzv4c54vcjp827fytdzrxs3gdh5f0a0s7hcf8a5e4ay8g", "stake_xvk1r0v9a3ca9kxwqxqp8qcsnqa2llaytp2gdkc4w67rskc2udg9vtn2qqvj2gum2unwxyet3f2e3yzw4ujgk6yxdpzsm0gjl6lpa0sj0mg4tq9sj")] + [InlineData("policy_sk1trt3shjrd4gy70q4m2ejgjgsdzwej4whc4r2trrcwedlpm6z4dglxl4nycrd8fptxrkye3tl3q29euxlqj7zndk9cfg4tskqlnp90uqwjqz02", "policy_vk17s29wgt93lj3m830qhlpx8rx5shwmtljhdswdjyjetprhu5yaahqwzt9lc")] + public async Task Execute_Successfully_With_Correct_Public_Verification_Key_When_Signing_Key_Is_Supported( + string signingKey, string expectedVerificationKey) + { + var command = new ConvertVerificationKeyCommand() + { + SigningKey = signingKey + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.Success); + executionResult.Result.Should().Be(expectedVerificationKey); + } +} diff --git a/Tests/ConsoleTool.UnitTests/DeriveAccountKeyCommandShould.cs b/Tests/ConsoleTool.UnitTests/DeriveAccountKeyCommandShould.cs new file mode 100644 index 0000000..a4ded72 --- /dev/null +++ b/Tests/ConsoleTool.UnitTests/DeriveAccountKeyCommandShould.cs @@ -0,0 +1,289 @@ +using Cscli.ConsoleTool.Wallet; +using FluentAssertions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Cscli.ConsoleTool.UnitTests; + +public class DeriveAccountKeyCommandShould +{ + [Theory] + [InlineData("English", null)] + [InlineData("English", "")] + [InlineData("Spanish", null)] + [InlineData("Spanish", "")] + [InlineData("Japanese", null)] + [InlineData("Japanese", "")] + [InlineData("ChineseSimplified", null)] + [InlineData("ChineseSimplified", "")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Mnemonic_Is_Not_Supplied( + string language, string mnemonic) + { + var command = new DeriveAccountKeyCommand() + { + Mnemonic = mnemonic, + Language = language, + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().Be("Invalid option --recovery-phrase is required"); + } + + [Theory] + [InlineData("English", "shoe")] + [InlineData("English", "baby laundry")] + [InlineData("English", "gloom fatigue little")] + [InlineData("English", "learn venue harvest fossil")] + [InlineData("English", "daring please route manual orphan")] + [InlineData("English", "once phrase win hawk before observe")] + [InlineData("English", "anchor elite rare young flash chaos loop")] + [InlineData("English", "salon expand attack move drip amateur second machine")] + [InlineData("English", "script scale minute dial radar tissue spray another bubble benefit")] + [InlineData("English", "eternal coin garment topic flag waste arch hobby tube great laptop donate lottery chief parade junior surge fortune zebra runway obey physical unknown logic fence")] + [InlineData("Spanish", "bello")] + [InlineData("Japanese", "なれる")] + [InlineData("Italian", "fuggente")] + [InlineData("ChineseSimplified", "厉")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Mnemonic_Is_Not_Of_Valid_Length( + string language, string mnemonic) + { + var command = new DeriveAccountKeyCommand() + { + Mnemonic = mnemonic, + Language = language, + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().Be("Invalid option --recovery-phrase must have the following word count (9, 12, 15, 18, 21, 24)"); + } + + [Theory] + [InlineData("invalid", "shoe follow blossom remain learn venue harvest fossil found")] + [InlineData("Australian", "shoe follow blossom remain learn venue harvest fossil found")] + [InlineData("en-GB", "shoe follow blossom remain learn venue harvest fossil found")] + [InlineData("Klingon", "shoe follow blossom remain learn venue harvest fossil found")] + [InlineData("eenglish", "rapid limit bicycle embrace speak column spoil casino become evolve unknown worry letter team laptop unknown false elbow bench analyst dilemma engage pulse plug")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Language_Is_Not_Supported( + string language, string mnemonic) + { + var command = new DeriveAccountKeyCommand() + { + Mnemonic = mnemonic, + Language = language, + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().Be($"Invalid option --language {language} is not supported"); + } + + [Theory] + [InlineData("English", "dueño misil favor koala pudor aplicar guitarra fracaso talar")] + [InlineData("Spanish", "rapid limit bicycle embrace speak column spoil casino become evolve unknown worry letter team laptop unknown false elbow bench analyst dilemma engage pulse plug")] + [InlineData("Japanese", "exigente pioneiro garrafa tarja genial dominado aclive tradutor fretar")] + [InlineData("Italian", "りけん ぞんび こんすい たまる めだつ すんぽう つまらない せつりつ けんない")] + [InlineData("French", "趋 富 吸 献 树 吾 秒 举 火 侦 佛 疑 机 察 统")] + [InlineData("ChineseSimplified", "danseur prouesse sauvage exquis cirque endosser saumon cintrer ratisser rompre pièce achat opinion cloporte orageux")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Words_Are_Invalid_For_Language_Word_List( + string language, string mnemonic) + { + var command = new DeriveAccountKeyCommand() + { + Mnemonic = mnemonic, + Language = language, + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().Contain("invalid words"); + } + + [Theory] + [InlineData(-1, "English", "rapid limit bicycle embrace speak column spoil casino become evolve unknown worry letter team laptop unknown false elbow bench analyst dilemma engage pulse plug")] + [InlineData(-2147483647, "English", "rapid limit bicycle embrace speak column spoil casino become evolve unknown worry letter team laptop unknown false elbow bench analyst dilemma engage pulse plug")] + [InlineData(-4, "Spanish", "dueño misil favor koala pudor aplicar guitarra fracaso talar")] + [InlineData(-80765454, "Italian", "exigente pioneiro garrafa tarja genial dominado aclive tradutor fretar")] + [InlineData(-12, "ChineseSimplified", "趋 富 吸 献 树 吾 秒 举 火 侦 佛 疑 机 察 统")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Account_Index_Is_Out_Of_Bounds( + int accountIndex, string language, string mnemonic) + { + var command = new DeriveAccountKeyCommand() + { + Mnemonic = mnemonic, + Language = language, + AccountIndex = accountIndex, + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().Be("Invalid option --account-index must be between 0 and 2147483647"); + } + + [Theory] + [InlineData("fitness juice ankle box prepare gallery purse narrow miracle next soccer category", + "acct_xsk1hqh2sk2jc6cwt20qypzdq6qldkxwyq8ydu97sxk0xwm6artsl3frtqnk4w2zlyr906g7udqy8hpz3axuc4yu4qd5uwhzmht2sg82ljhjf7vtdg3kajfural6u9exd234payu4s782fqqssmca0xle5r46cumaetd")] + [InlineData("trap report remind insane change toy rotate suggest misery vault language mind bone hen mountain", + "acct_xsk1gzspgayu90wczfueqz0m33mppwu5txdljsur0p7r0vnygnxj5dzjaztfrute8p27acsnxrfhhwzupsrjh5md2tm9pdf4rfc80h5ph5gwe9m6nhdxtuwhe034r6nwxmyz7yschrqre9epyak7u0l0q76tccrpw8sv")] + [InlineData("since cook close prosper slush luggage observe neglect fit arm twelve grief evolve illegal seven destroy joke hand useless knee silent wasp protect purity", + "acct_xsk1vzdtzrrw0x0veeru95aa9savc5lak2kx5qhcdpgt9davpuegy3vnjwpy25q3lwmjklezqjtrga5ftfgjfj2tdwers780rs05qz5rtgpt8vsh08uxzvlyum529fgwgsgam8xnsthjcrvapw8fx43vu5n5hup0ynz8")] + public async Task Derive_Correct_Bech32_Extended_Account_Signing_Key_Defaulting_To_English_When_Passphrase_And_Language_Are_Not_Supplied_And_Mnemonic_Is_Valid_English( + string mnemonic, string expectedBech32Key) + { + var command = new DeriveAccountKeyCommand() + { + Mnemonic = mnemonic + }; + var englishSpecificCommand = new DeriveAccountKeyCommand() + { + Mnemonic = mnemonic, + Language = "English" + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + var englishSpecificCommandResult = await englishSpecificCommand.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.Success); + executionResult.Result.Should().Be(expectedBech32Key); + executionResult.Result.Should().Be(englishSpecificCommandResult.Result); + } + + [Theory] + [InlineData("English", "afford attack bag emotion ensure detect effort bean luggage", + "acct_xsk1mqx3kdpwhwa4ul877eztp5wurww4d4nt9k2f389y6lyed8w6j3fg9zuvggwe2qgmvphm8zsw0r9zy8g7a4s4yfwk6vyqxpla6uxv3yvg6p35tqptkag6wrhjm8qdk2835symvpg8vwv90m5mywg4whrpg5mncf0q")] + [InlineData("English", "panther cave tornado remain snap leisure gown vote strategy apple away room shrug action reunion", + "acct_xsk1xpj8x86n78t2ashqqewejgnc89ns7gpvdp7zak8lehsazjwcw49e5rxudltazn57xmv6kms3pcceuamjnmef04dsm2qgrwk7ctdk7ccvzctg4pqe3lkj7sqj6gemhxrtvqwxp2ac66t2cnxffx7exxzuaya8w6la")] + [InlineData("English", "safe mixture exhaust worth smart level donkey orphan hello ski want runway example fame gaze thumb voyage swift wire common move injury promote media", + "acct_xsk10prx2emv8as0e8q4ny922f6q6f9x6t60xp9vd6f5qgpukkl5h99ucqsvvlhnjwmdjpaznjrkpp57sgc9x6sn8pxtq34rdfypg3xvefzespd2pg2pm6nxhgkugfs5d4rwyuh02ywwdftlzzwtm8k6429dzqdghxje")] + [InlineData("Spanish", "vitamina banda araña rincón queja copa guiso camello limón fase objeto martes náusea matar urgente sello emitir melón acuerdo herir litio pausa dar paella", + "acct_xsk1vp4zls6xkmkh4526cmw302dq3u8c3037esfruqal0tr454raearutg3tu8dlu3ulfw448z9ln0hjnnwfh2kgu4xgwgqhcn7wzzqhnan6dqvgr80k7vm82enupwg3mrt8tlxkwxmpd45p40n7h55pl7uxxqntznjn")] + [InlineData("Japanese", "おうふく いってい にんか さとおや すばらしい ほせい てはい めんどう ぎいん にちじょう ほうりつ しなん げきとつ ききて ねんおし むしあつい げんそう えんそく ぎっちり さつたば ちきゅう あんい すわる せんか", + "acct_xsk1azcqldnrvn6k32cju3ygz4t2n88tvve7c76ae7jf2g3l2p69j4d4mt04v0gt8p68p6azueypdp3a4hks5p4der9wxflplklmsxcs4aw02tamgauq9snvsyvafyv2g20am56hdlkpf42w6w4999ae6dd8pytq2k0s")] + [InlineData("ChineseSimplified", "驻 歇 职 熙 盐 剪 带 泰 轴 枯 奥 兵 曰 桂 导 睡 抵 宁 容 读 幻 悉 默 新", + "acct_xsk1zpzsel0fwpxr9v33zw9fw04almj3tsus7qj9ej3q2m8tp0asjedc6r9e2mj78sey0yn7hs9h0vw4zyms8yqttvlyg0uav7urshgufh3gc47zuflhreffg4grqt6s7hv6qr8ts8evqj3rgvzdpxayq6wqayj5q3rk")] + public async Task Derive_Correct_Bech32_Extended_Account_Signing_Key_Defaulting_To_Zero_Account_Index_When_Index_Is_Not_Supplied_And_Mnemonic_Is_Valid_For_Language( + string language, string mnemonic, string expectedBech32Key) + { + var command = new DeriveAccountKeyCommand() + { + Language = language, + Mnemonic = mnemonic + }; + var zeroIndexCommand = new DeriveAccountKeyCommand() + { + Language = language, + Mnemonic = mnemonic, + AccountIndex = 0, + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + var englishSpecificCommandResult = await zeroIndexCommand.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.Success); + executionResult.Result.Should().Be(expectedBech32Key); + executionResult.Result.Should().Be(englishSpecificCommandResult.Result); + } + + [Theory] + [InlineData( + "English", "rapid limit bicycle embrace speak column spoil casino become evolve unknown worry letter team laptop unknown false elbow bench analyst dilemma engage pulse plug", + "acct_xsk1gp8ejcw00et7q3h49hxca7w80d8r76m8qnu6ffw7mdaxht75sa8uk9d9lt0hm28zl8f3k0pqh4szgk5tqzj3nzl82kdnmgr4yv90sshylp6ypc3jtv5tht5z0phvg7avjxlv79dgv77e0h44mhrqn60chc4zevx2")] + [InlineData( + "Spanish", "pensar leve rosca hueso ombligo cerrar guion molde bonito misa recaer amargo sodio noria tosco", + "acct_xsk1fpz0qtjpql9l4zy4ztxaxtrlw5f9yyzgjmhxtgx40hk3vvupr4wyujvkgkln63q5jwzpsazfvr6pykqazxt3y68payk2r0c4kzlu2t0uqpl3wm3m8zdkcp6ppxl29j9szy6u2jnn77lq3kadd7598m36jgacc9zl")] + [InlineData( + "Japanese", "たたかう しのぐ きひん てまえ むさぼる なれる しあさって りゆう ろめん げいのうじん したて たんか さんみ けんとう うけもつ", + "acct_xsk1yzkyv5ydk797hv74n0y8gter6lmn5dym8fmwtnejuv4ulc65gewgxfre5pt6gwh7rszqey8t5ef3nhuykf0syul9asrcvflt6pjyspq2vekcw07wku59dg3p2nrk5c40ge0jqq4rl6sxgnvnkvv6hpzhkspjep6p")] + [InlineData( + "Italian", "sfacelo quadro bordo ballata pennuto scultore ridurre tesserato triturato palazzina ossidare riportare sgarbato vedova targato", + "acct_xsk1pzw7ssh3gnheynsqmh5aquehm9ykzj58qeupatmunc3npedaadwnee80zxxrj42sj5wkvyl9l9ldhlrleda2ydj2jvnhchz689xs2y4zt9u95mt30r57gq6c3fqx9ajgag6ejaznmqyeg7eq9gwx6ajkqvpwl6r3")] + [InlineData( + "ChineseSimplified", "胁 粒 了 对 拆 端 泼 由 烈 苏 筹 如 技 防 厚", + "acct_xsk12rhhtunuvaq0j9yskhcfvc7fxqgadel00qal9n2p5etpdg6tnfwu298gpqlrdp2yayey55ucutz9j7j4gj725veyew9wg8xpn25qd2h6np8hf7pweapg5cqj0jgjtsh39lpqqnzf3rkvx7awxrqh3hdl65uljq9n")] + public async Task Derive_Correct_Bech32_Extended_Account_Signing_Key_Defaulting_To_Empty_Passphrase_When_Passphrase_Is_Not_Supplied_And_Mnemonic_Is_Valid_For_Language( + string language, string mnemonic, string expectedBech32Key) + { + var command = new DeriveAccountKeyCommand() + { + Mnemonic = mnemonic, + Language = language, + }; + var emptyPassSpecificCommand = new DeriveAccountKeyCommand() + { + Mnemonic = mnemonic, + Language = language, + Passphrase = "", + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + var emptyPassCommandResult = await emptyPassSpecificCommand.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.Success); + executionResult.Result.Should().Be(expectedBech32Key); + executionResult.Result.Should().Be(emptyPassCommandResult.Result); + } + + [Theory] + [InlineData("pass", "English", "fitness juice ankle box prepare gallery purse narrow miracle next soccer category", + "acct_xsk12zf85pyr96s6fwctaeys44kjqgg5r9defkse37ewjq02rqf2qazccn0qkphmkacyvlpk9zct7nthjsgxjshnjcr37j7vfckcjn8twhvk6qsy4qn6htjlmelcpwrks9y475vgjz3az3ql4etup6rql2y6eulg6nkx")] + [InlineData("Gxxk3A32Qch8ZcwtUib3g9gSs&#abSx01&Hfpf!VAcz72bz2G5", "English", "fitness juice ankle box prepare gallery purse narrow miracle next soccer category", + "acct_xsk1ezrpfuv42zn3fp344e6hc43dq6ttdef44dta6ukjsvhevnsl2pz66ktwgtwfgww468z0ayrj2trwy0drljmws6uw3ledrpe0es8hs5xpq6u7xhd5552uzr54uvft2encf8smmcmgdwsuqjg5gmjl63hk2stk2j37")] + [InlineData("pass", "Spanish", "baba ancho crear casero quitar canica cabeza aliento aprobar broca adicto letal anciano peldaño voto", + "acct_xsk1mplqu264p8dnnqzs00s2e6sfrkv3x7jqrmdvrf8y0etkmxvgndytr3kjuj8cukkp85dt7u70sxkg006zfl6wkhlxaemx2crnc3f9u7y2lm3f3myv7u989xsnaf9cyfmzq5f592zmqpj9gwrr8lvk9863gqruslr5")] + [InlineData("#SCVh0%vyMWmBj!BR4EYIrUBB4UM%c5yvS8&S!m0W2PNFJX4di", "Spanish", "baba ancho crear casero quitar canica cabeza aliento aprobar broca adicto letal anciano peldaño voto", + "acct_xsk1fpw5p4vlhhyhpt76shc7eeh3zqzp9qsy9qlga3puvvrhut9udffcg7rr3hj69284vrsxa54wta6ragk7xcdrracx3y7uunm4hfeymdh2v8f5wc5c0nnl5slxzskn3pm6gtwaaz7nmckcdu76h50wnztv3s7l9m5k")] + [InlineData("pass", "Japanese", "あまい るいさい はづき になう ことがら ふしぎ がっさん えんそく きうい みかん うんどう いだく せっけん にってい とおい", + "acct_xsk10z5pxvp3t59435nu2jrakjjwgp0zxmeclxr39jh5x95fdmlgn3vga8ywwxzg5gyeaq7gz9gd4zczcsx85f69als5zgdmmqwky87l78qasuw487mw6hj4khjrkyjzxgt604zqdpqv80hzanha3uhgk9lcfu05azq5")] + [InlineData("AhkYZ!!*ng@x56#cN5wE83ugz9JD8Xju#fm3De9AWKYCyDSDZE", "Japanese", "あまい るいさい はづき になう ことがら ふしぎ がっさん えんそく きうい みかん うんどう いだく せっけん にってい とおい", + "acct_xsk1grhz4lc2t2masglaqa94p4rcscrr2n7hegg57tjlj7jrucewvdv3jy87d8pknqs6a37kr9yqpe43s5mmaunp9v0t07lsdr0s86n4rnqreudtffv2fkw0lefpsakt32p6lkc25e23en8y2np4wss55e7c2g45myq4")] + public async Task Derive_Correct_Bech32_Extended_Account_Signing_Key_When_Passphrase_And_Language_Are_Supplied_And_Mnemonic_Is_Valid_For_Language( + string passPhrase, string language, string mnemonic, string expectedBech32Key) + { + var command = new DeriveAccountKeyCommand() + { + Mnemonic = mnemonic, + Language = language, + Passphrase = passPhrase, + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.Success); + executionResult.Result.Should().Be(expectedBech32Key); + } + + [Theory] + [InlineData(0, "English", "fitness juice ankle box prepare gallery purse narrow miracle next soccer category", + "acct_xsk1hqh2sk2jc6cwt20qypzdq6qldkxwyq8ydu97sxk0xwm6artsl3frtqnk4w2zlyr906g7udqy8hpz3axuc4yu4qd5uwhzmht2sg82ljhjf7vtdg3kajfural6u9exd234payu4s782fqqssmca0xle5r46cumaetd")] + [InlineData(1, "English", "fitness juice ankle box prepare gallery purse narrow miracle next soccer category", + "acct_xsk1fqnh8vhpul5tza40nhhlgyk47yalmyru2rmavzywatyg3rtsl3fzmu3rxkh4f3vuyssj26steekduhcgj0s3lw9fu8xthdjc5mk6964ky2nj7rnp5ngjwtfnkd590q2c26gxqyj57ruc2xpgnz9gm2v34u03pzwz")] + [InlineData(280916, "English", "fitness juice ankle box prepare gallery purse narrow miracle next soccer category", + "acct_xsk1vz6xkyt2sqwa5rzcz3jms4dve2fff8f4j8fppaqz3pdytznsl3fvjnzxgyxlkf2k03qgpq8qwudgaaxh5k8njnfgfme9waq74xpnng2nmyudx4s8efvk5qfcx2yu0akvezecja2jp35eew3d5lqw8ztzq5c3hcjv")] + [InlineData(2147483647, "English", "fitness juice ankle box prepare gallery purse narrow miracle next soccer category", + "acct_xsk1gp3kuxrvu7ug6u9x0v0zuse395ejhusfzf66axk423r5lztsl3ffp2aucmhad088la4gftscfflzqs4ldsfsv3470296qc3qt33ey5rqayc6fw39nmy4dg24d26sxncfp93lfj4sgfuvyjg5lx0qcfrevgewdxll")] + public async Task Derive_Correct_Bech32_Extended_Account_Signing_Key_When_Account_And_Address_Index_Is_Supplied_And_Mnemonic_Is_Valid_For_Language( + int accountIndex, string language, string mnemonic, string expectedBech32Key) + { + var command = new DeriveAccountKeyCommand() + { + Mnemonic = mnemonic, + Language = language, + AccountIndex = accountIndex, + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.Success); + executionResult.Result.Should().Be(expectedBech32Key); + } +}