예제 #1
0
        public void CanManageWallet()
        {
            using (var s = SeleniumTester.Create())
            {
                s.Start();
                s.RegisterNewUser();
                s.CreateNewStore();

                // In this test, we try to spend from a manual seed. We import the xpub 49'/0'/0', then try to use the seed
                // to sign the transaction
                var mnemonic = "usage fever hen zero slide mammal silent heavy donate budget pulse say brain thank sausage brand craft about save attract muffin advance illegal cabbage";
                var root     = new Mnemonic(mnemonic).DeriveExtKey();
                s.AddDerivationScheme("ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD");
                var tx = s.Server.ExplorerNode.SendToAddress(BitcoinAddress.Create("bcrt1qmxg8fgnmkp354vhe78j6sr4ut64tyz2xyejel4", Network.RegTest), Money.Coins(3.0m));
                s.Server.ExplorerNode.Generate(1);

                s.Driver.FindElement(By.Id("Wallets")).Click();
                s.Driver.FindElement(By.LinkText("Manage")).Click();

                s.ClickOnAllSideMenus();

                // We setup the fingerprint and the account key path
                s.Driver.FindElement(By.Id("WalletSettings")).ForceClick();
                s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).SendKeys("8bafd160");
                s.Driver.FindElement(By.Id("AccountKeys_0__AccountKeyPath")).SendKeys("m/49'/0'/0'" + Keys.Enter);

                // Check the tx sent earlier arrived
                s.Driver.FindElement(By.Id("WalletTransactions")).ForceClick();
                var walletTransactionLink = s.Driver.Url;
                Assert.Contains(tx.ToString(), s.Driver.PageSource);


                void SignWith(string signingSource)
                {
                    // Send to bob
                    s.Driver.FindElement(By.Id("WalletSend")).Click();
                    var bob = new Key().PubKey.Hash.GetAddress(Network.RegTest);

                    s.Driver.FindElement(By.Id("Destination")).SendKeys(bob.ToString());
                    s.Driver.FindElement(By.Id("Amount")).SendKeys("1");
                    s.Driver.ScrollTo(By.Id("SendMenu"));
                    s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
                    s.Driver.FindElement(By.CssSelector("button[value=seed]")).Click();

                    // Input the seed
                    s.Driver.FindElement(By.Id("SeedOrKey")).SendKeys(signingSource + Keys.Enter);

                    // Broadcast
                    Assert.Contains(bob.ToString(), s.Driver.PageSource);
                    Assert.Contains("1.00000000", s.Driver.PageSource);
                    s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
                    Assert.Equal(walletTransactionLink, s.Driver.Url);
                }

                SignWith(mnemonic);
                var accountKey = root.Derive(new KeyPath("m/49'/0'/0'")).GetWif(Network.RegTest).ToString();
                SignWith(accountKey);
            }
        }
        public void DeriveHdWalletKeys(string hdSeed, int keyNumber, out string publicAddress, out string privateKey)
        {
            ExtKey masterKey = new Mnemonic(hdSeed).DeriveExtKey();
            ExtKey key       = masterKey.Derive(new KeyPath("m/44'/0'/0'/0/" + keyNumber));

            publicAddress = key.PrivateKey.PubKey.GetAddress(net).ToString();
            privateKey    = key.PrivateKey.GetBitcoinSecret(net).ToString();
        }
예제 #3
0
        public async Task CanManageWallet()
        {
            using (var s = SeleniumTester.Create())
            {
                await s.StartAsync();

                s.RegisterNewUser(true);
                var storeId = s.CreateNewStore();

                // In this test, we try to spend from a manual seed. We import the xpub 49'/0'/0', then try to use the seed
                // to sign the transaction
                var mnemonic = s.GenerateWallet("BTC", "", true, false);

                var invoiceId = s.CreateInvoice(storeId.storeId);
                var invoice   = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);

                var address = invoice.EntityToDTO().Addresses["BTC"];

                var result = await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));

                Assert.True(result.IsWatchOnly);
                s.GoToStore(storeId.storeId);
                mnemonic = s.GenerateWallet("BTC", "", true, true);

                var root = new Mnemonic(mnemonic).DeriveExtKey();
                invoiceId = s.CreateInvoice(storeId.storeId);
                invoice   = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);

                address = invoice.EntityToDTO().Addresses["BTC"];
                result  = await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));

                Assert.False(result.IsWatchOnly);
                var tx = s.Server.ExplorerNode.SendToAddress(BitcoinAddress.Create(address, Network.RegTest), Money.Coins(3.0m));
                s.Server.ExplorerNode.Generate(1);

                s.Driver.FindElement(By.Id("Wallets")).Click();
                s.Driver.FindElement(By.LinkText("Manage")).Click();

                s.ClickOnAllSideMenus();

                // Make sure we can rescan, because we are admin!
                s.Driver.FindElement(By.Id("WalletRescan")).ForceClick();
                Assert.Contains("The batch size make sure", s.Driver.PageSource);

                // We setup the fingerprint and the account key path
                s.Driver.FindElement(By.Id("WalletSettings")).ForceClick();
//                s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).SendKeys("8bafd160");
//                s.Driver.FindElement(By.Id("AccountKeys_0__AccountKeyPath")).SendKeys("m/49'/0'/0'" + Keys.Enter);

                // Check the tx sent earlier arrived
                s.Driver.FindElement(By.Id("WalletTransactions")).ForceClick();
                var walletTransactionLink = s.Driver.Url;
                Assert.Contains(tx.ToString(), s.Driver.PageSource);


                void SignWith(string signingSource)
                {
                    // Send to bob
                    s.Driver.FindElement(By.Id("WalletSend")).Click();
                    var bob = new Key().PubKey.Hash.GetAddress(Network.RegTest);

                    SetTransactionOutput(0, bob, 1);
                    s.Driver.ScrollTo(By.Id("SendMenu"));
                    s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
                    s.Driver.FindElement(By.CssSelector("button[value=seed]")).Click();

                    // Input the seed
                    s.Driver.FindElement(By.Id("SeedOrKey")).SendKeys(signingSource + Keys.Enter);

                    // Broadcast
                    Assert.Contains(bob.ToString(), s.Driver.PageSource);
                    Assert.Contains("1.00000000", s.Driver.PageSource);
                    s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
                    Assert.Equal(walletTransactionLink, s.Driver.Url);
                }

                void SetTransactionOutput(int index, BitcoinAddress dest, decimal amount, bool subtract = false)
                {
                    s.Driver.FindElement(By.Id($"Outputs_{index}__DestinationAddress")).SendKeys(dest.ToString());
                    var amountElement = s.Driver.FindElement(By.Id($"Outputs_{index}__Amount"));

                    amountElement.Clear();
                    amountElement.SendKeys(amount.ToString());
                    var checkboxElement = s.Driver.FindElement(By.Id($"Outputs_{index}__SubtractFeesFromOutput"));

                    if (checkboxElement.Selected != subtract)
                    {
                        checkboxElement.Click();
                    }
                }

                SignWith(mnemonic);
                var accountKey = root.Derive(new KeyPath("m/84'/1'/0'")).GetWif(Network.RegTest).ToString();
                SignWith(accountKey);
            }
        }
예제 #4
0
        public async Task CanSetupWallet()
        {
            using (var tester = CreateServerTester())
            {
                tester.ActivateLTC();
                tester.ActivateLightning();
                await tester.StartAsync();

                var user       = tester.NewAccount();
                var cryptoCode = "BTC";
                await user.GrantAccessAsync(true);

                user.RegisterDerivationScheme(cryptoCode);
                user.RegisterDerivationScheme("LTC");
                user.RegisterLightningNode(cryptoCode, LightningConnectionType.CLightning);
                var btcNetwork = tester.PayTester.Networks.GetNetwork <BTCPayNetwork>(cryptoCode);
                var invoice    = await user.BitPay.CreateInvoiceAsync(
                    new Invoice
                {
                    Price             = 1.5m,
                    Currency          = "USD",
                    PosData           = "posData",
                    OrderId           = "orderId",
                    ItemDesc          = "Some description",
                    FullNotifications = true
                }, Facade.Merchant);

                Assert.Equal(3, invoice.CryptoInfo.Length);

                // Setup Lightning
                var controller  = user.GetController <UIStoresController>();
                var lightningVm = (LightningNodeViewModel)Assert.IsType <ViewResult>(await controller.SetupLightningNode(user.StoreId, cryptoCode)).Model;
                Assert.True(lightningVm.Enabled);
                var response = await controller.SetLightningNodeEnabled(user.StoreId, cryptoCode, false);

                Assert.IsType <RedirectToActionResult>(response);

                // Get enabled state from settings
                LightningSettingsViewModel lnSettingsModel;
                response        = controller.LightningSettings(user.StoreId, cryptoCode).GetAwaiter().GetResult();
                lnSettingsModel = (LightningSettingsViewModel)Assert.IsType <ViewResult>(response).Model;
                Assert.NotNull(lnSettingsModel?.ConnectionString);
                Assert.False(lnSettingsModel.Enabled);

                // Setup wallet
                WalletSetupViewModel setupVm;
                var storeId = user.StoreId;
                response = await controller.GenerateWallet(storeId, cryptoCode, WalletSetupMethod.GenerateOptions, new WalletSetupRequest());

                Assert.IsType <ViewResult>(response);

                // Get enabled state from settings
                response = controller.WalletSettings(user.StoreId, cryptoCode).GetAwaiter().GetResult();
                var onchainSettingsModel = (WalletSettingsViewModel)Assert.IsType <ViewResult>(response).Model;
                Assert.NotNull(onchainSettingsModel?.DerivationScheme);
                Assert.True(onchainSettingsModel.Enabled);

                // Disable wallet
                onchainSettingsModel.Enabled = false;
                response = controller.UpdateWalletSettings(onchainSettingsModel).GetAwaiter().GetResult();
                Assert.IsType <RedirectToActionResult>(response);
                response             = controller.WalletSettings(user.StoreId, cryptoCode).GetAwaiter().GetResult();
                onchainSettingsModel = (WalletSettingsViewModel)Assert.IsType <ViewResult>(response).Model;
                Assert.NotNull(onchainSettingsModel?.DerivationScheme);
                Assert.False(onchainSettingsModel.Enabled);

                var oldScheme = onchainSettingsModel.DerivationScheme;

                invoice = await user.BitPay.CreateInvoiceAsync(
                    new Invoice
                {
                    Price             = 1.5m,
                    Currency          = "USD",
                    PosData           = "posData",
                    OrderId           = "orderId",
                    ItemDesc          = "Some description",
                    FullNotifications = true
                }, Facade.Merchant);

                Assert.Single(invoice.CryptoInfo);
                Assert.Equal("LTC", invoice.CryptoInfo[0].CryptoCode);

                // Removing the derivation scheme, should redirect to store page
                response = controller.ConfirmDeleteWallet(user.StoreId, cryptoCode).GetAwaiter().GetResult();
                Assert.IsType <RedirectToActionResult>(response);

                // Setting it again should show the confirmation page
                response = await controller.UpdateWallet(new WalletSetupViewModel { StoreId = storeId, CryptoCode = cryptoCode, DerivationScheme = oldScheme });

                setupVm = (WalletSetupViewModel)Assert.IsType <ViewResult>(response).Model;
                Assert.True(setupVm.Confirmation);

                // The following part posts a wallet update, confirms it and checks the result

                // cobo vault file
                var content = "{\"ExtPubKey\":\"xpub6CEqRFZ7yZxCFXuEWZBAdnC8bdvu9SRHevaoU2SsW9ZmKhrCShmbpGZWwaR15hdLURf8hg47g4TpPGaqEU8hw5LEJCE35AUhne67XNyFGBk\",\"MasterFingerprint\":\"7a7563b5\",\"DerivationPath\":\"M\\/84'\\/0'\\/0'\",\"CoboVaultFirmwareVersion\":\"1.2.0(BTC-Only)\"}";
                response = await controller.UpdateWallet(new WalletSetupViewModel { StoreId = storeId, CryptoCode = cryptoCode, WalletFile = TestUtils.GetFormFile("cobovault.json", content) });

                setupVm = (WalletSetupViewModel)Assert.IsType <ViewResult>(response).Model;
                Assert.True(setupVm.Confirmation);
                response = await controller.UpdateWallet(setupVm);

                Assert.IsType <RedirectToActionResult>(response);
                response = await controller.WalletSettings(storeId, cryptoCode);

                var settingsVm = (WalletSettingsViewModel)Assert.IsType <ViewResult>(response).Model;
                Assert.Equal("CoboVault", settingsVm.Source);

                // wasabi wallet file
                content  = "{\r\n  \"EncryptedSecret\": \"6PYWBQ1zsukowsnTNA57UUx791aBuJusm7E4egXUmF5WGw3tcdG3cmTL57\",\r\n  \"ChainCode\": \"waSIVbn8HaoovoQg/0t8IS1+ZCxGsJRGFT21i06nWnc=\",\r\n  \"MasterFingerprint\": \"7a7563b5\",\r\n  \"ExtPubKey\": \"xpub6CEqRFZ7yZxCFXuEWZBAdnC8bdvu9SRHevaoU2SsW9ZmKhrCShmbpGZWwaR15hdLURf8hg47g4TpPGaqEU8hw5LEJCE35AUhne67XNyFGBk\",\r\n  \"PasswordVerified\": false,\r\n  \"MinGapLimit\": 21,\r\n  \"AccountKeyPath\": \"84'/0'/0'\",\r\n  \"BlockchainState\": {\r\n    \"Network\": \"RegTest\",\r\n    \"Height\": \"0\"\r\n  },\r\n  \"HdPubKeys\": []\r\n}";
                response = await controller.UpdateWallet(new WalletSetupViewModel { StoreId = storeId, CryptoCode = cryptoCode, WalletFile = TestUtils.GetFormFile("wasabi.json", content) });

                setupVm = (WalletSetupViewModel)Assert.IsType <ViewResult>(response).Model;
                Assert.True(setupVm.Confirmation);
                response = await controller.UpdateWallet(setupVm);

                Assert.IsType <RedirectToActionResult>(response);
                response = await controller.WalletSettings(storeId, cryptoCode);

                settingsVm = (WalletSettingsViewModel)Assert.IsType <ViewResult>(response).Model;
                Assert.Equal("WasabiFile", settingsVm.Source);

                // Can we upload coldcard settings? (Should fail, we are giving a mainnet file to a testnet network)
                content  = "{\"keystore\": {\"ckcc_xpub\": \"xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw\", \"xpub\": \"ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}";
                response = await controller.UpdateWallet(new WalletSetupViewModel { StoreId = storeId, CryptoCode = cryptoCode, WalletFile = TestUtils.GetFormFile("coldcard-ypub.json", content) });

                setupVm = (WalletSetupViewModel)Assert.IsType <ViewResult>(response).Model;
                Assert.False(setupVm.Confirmation); // Should fail, we are giving a mainnet file to a testnet network

                // And with a good file? (upub)
                content  = "{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"upub5DBYp1qGgsTrkzCptMGZc2x18pquLwGrBw6nS59T4NViZ4cni1mGowQzziy85K8vzkp1jVtWrSkLhqk9KDfvrGeB369wGNYf39kX8rQfiLn\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}";
                response = await controller.UpdateWallet(new WalletSetupViewModel { StoreId = storeId, CryptoCode = cryptoCode, WalletFile = TestUtils.GetFormFile("coldcard-upub.json", content) });

                setupVm = (WalletSetupViewModel)Assert.IsType <ViewResult>(response).Model;
                Assert.True(setupVm.Confirmation);
                response = await controller.UpdateWallet(setupVm);

                Assert.IsType <RedirectToActionResult>(response);
                response = await controller.WalletSettings(storeId, cryptoCode);

                settingsVm = (WalletSettingsViewModel)Assert.IsType <ViewResult>(response).Model;
                Assert.Equal("ElectrumFile", settingsVm.Source);

                // Now let's check that no data has been lost in the process
                var store      = tester.PayTester.StoreRepository.FindStore(storeId).GetAwaiter().GetResult();
                var onchainBTC = store.GetSupportedPaymentMethods(tester.PayTester.Networks)
#pragma warning disable CS0618 // Type or member is obsolete
                                 .OfType <DerivationSchemeSettings>().First(o => o.PaymentId.IsBTCOnChain);
#pragma warning restore CS0618 // Type or member is obsolete
                DerivationSchemeSettings.TryParseFromWalletFile(content, onchainBTC.Network, out var expected);
                Assert.Equal(expected.ToJson(), onchainBTC.ToJson());

                // Let's check that the root hdkey and account key path are taken into account when making a PSBT
                invoice = await user.BitPay.CreateInvoiceAsync(
                    new Invoice
                {
                    Price             = 1.5m,
                    Currency          = "USD",
                    PosData           = "posData",
                    OrderId           = "orderId",
                    ItemDesc          = "Some description",
                    FullNotifications = true
                }, Facade.Merchant);

                tester.ExplorerNode.Generate(1);
                var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo.First(c => c.CryptoCode == cryptoCode).Address,
                                                           tester.ExplorerNode.Network);
                tester.ExplorerNode.SendToAddress(invoiceAddress, Money.Coins(1m));
                TestUtils.Eventually(() =>
                {
                    invoice = user.BitPay.GetInvoice(invoice.Id);
                    Assert.Equal("paid", invoice.Status);
                });
                var wallet = tester.PayTester.GetController <UIWalletsController>();
                var psbt   = wallet.CreatePSBT(btcNetwork, onchainBTC,
                                               new WalletSendModel()
                {
                    Outputs = new List <WalletSendModel.TransactionOutput>
                    {
                        new WalletSendModel.TransactionOutput
                        {
                            Amount             = 0.5m,
                            DestinationAddress = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, btcNetwork.NBitcoinNetwork)
                                                 .ToString(),
                        }
                    },
                    FeeSatoshiPerByte = 1
                }, default).GetAwaiter().GetResult();

                Assert.NotNull(psbt);

                var root = new Mnemonic(
                    "usage fever hen zero slide mammal silent heavy donate budget pulse say brain thank sausage brand craft about save attract muffin advance illegal cabbage")
                           .DeriveExtKey().AsHDKeyCache();
                var account = root.Derive(new KeyPath("m/49'/0'/0'"));
                Assert.All(psbt.PSBT.Inputs, input =>
                {
                    var keyPath = input.HDKeyPaths.Single();
                    Assert.False(keyPath.Value.KeyPath.IsHardened);
                    Assert.Equal(account.Derive(keyPath.Value.KeyPath).GetPublicKey(), keyPath.Key);
                    Assert.Equal(keyPath.Value.MasterFingerprint,
                                 onchainBTC.AccountKeySettings[0].AccountKey.GetPublicKey().GetHDFingerPrint());
                });
            }
        }