Exemplo n.º 1
0
        private void RunTest(TestVector test)
        {
            var       seed   = TestUtils.ParseHex(test.strHexMaster);
            ExtKey    key    = new ExtKey(seed);
            ExtPubKey pubkey = key.Neuter();

            foreach (TestDerivation derive in test.vDerive)
            {
                byte[] data = key.ToBytes();
                Assert.Equal(74, data.Length);
                data = pubkey.ToBytes();
                Assert.Equal(74, data.Length);
                // Test private key
                BitcoinExtKey b58key   = Network.Main.CreateBitcoinExtKey(key);
                var           a        = Encoders.Hex.EncodeData(Encoders.Base58Check.DecodeData(b58key.ToString()));
                var           expected = Encoders.Hex.EncodeData(Encoders.Base58Check.DecodeData(derive.prv));
                Assert.True(b58key.ToString() == derive.prv);
                // Test public key
                BitcoinExtPubKey b58pubkey = Network.Main.CreateBitcoinExtPubKey(pubkey);
                Assert.True(b58pubkey.ToString() == derive.pub);
                // Derive new keys
                ExtKey    keyNew    = key.Derive(derive.nChild);
                ExtPubKey pubkeyNew = keyNew.Neuter();
                if (!((derive.nChild & 0x80000000) != 0))
                {
                    // Compare with public derivation
                    ExtPubKey pubkeyNew2 = pubkey.Derive(derive.nChild);
                    Assert.True(pubkeyNew == pubkeyNew2);
                }
                key    = keyNew;
                pubkey = pubkeyNew;
            }
        }
Exemplo n.º 2
0
 private void AddDerivationSchemeToJson(string propertyName, JObject result, KeyPath keyPath, BitcoinExtPubKey xpub, DerivationStrategyBase strategy)
 {
     result.Add(new JProperty(propertyName, new JObject()
     {
         new JProperty("strategy", strategy.ToString()),
         new JProperty("accountKey", xpub.ToString()),
         new JProperty("keyPath", keyPath.ToString()),
     }));
 }
Exemplo n.º 3
0
        public DerivationStrategyBase ParseElectrum(string str)
        {
            if (str == null)
            {
                throw new ArgumentNullException(nameof(str));
            }
            str = str.Trim();
            var data = Network.GetBase58CheckEncoder().DecodeData(str);

            if (data.Length < 4)
            {
                throw new FormatException();
            }
            var prefix = Utils.ToUInt32(data, false);

            var standardPrefix = Utils.ToBytes(0x0488b21eU, false);

            for (int ii = 0; ii < 4; ii++)
            {
                data[ii] = standardPrefix[ii];
            }
            var extPubKey = new BitcoinExtPubKey(Network.GetBase58CheckEncoder().EncodeData(data), Network.Main).ToNetwork(Network);

            if (!ElectrumMappingDerivationType.TryGetValue(prefix, out var type))
            {
                throw new FormatException();
            }
            if (type == DerivationType.Segwit)
            {
                return new DirectDerivationStrategy(extPubKey)
                       {
                           Segwit = true
                       }
            }
            ;
            if (type == DerivationType.Legacy)
            {
                return new DirectDerivationStrategy(extPubKey)
                       {
                           Segwit = false
                       }
            }
            ;
            if (type == DerivationType.SegwitP2SH)
            {
                return(new DerivationStrategyFactory(Network).Parse(extPubKey.ToString() + "-[p2sh]"));
            }
            throw new FormatException();
        }
Exemplo n.º 4
0
        /// <summary>
        ///  Describe ext public key
        /// </summary>
        /// <param name="extPubKey"></param>
        /// <param name="keyPath">The root of the keypath to follow</param>
        /// <returns></returns>
        public static ScanTxoutPubkey ExtPubKey(BitcoinExtPubKey extPubKey, KeyPath keyPath)
        {
            if (extPubKey == null)
            {
                throw new ArgumentNullException(nameof(extPubKey));
            }

            StringBuilder builder = new StringBuilder();

            builder.Append(extPubKey.ToString());
            if (keyPath != null && keyPath.Indexes.Length != 0)
            {
                builder.Append(keyPath.ToString().Replace("m/", String.Empty));
            }
            return(new ScanTxoutPubkey($"{builder.ToString()}"));
        }
Exemplo n.º 5
0
        public async Task <IActionResult> VaultBridgeConnection(string cryptoCode = null,
                                                                [ModelBinder(typeof(WalletIdModelBinder))]
                                                                WalletId walletId = null)
        {
            if (!HttpContext.WebSockets.IsWebSocketRequest)
            {
                return(NotFound());
            }
            cryptoCode = cryptoCode ?? walletId.CryptoCode;
            using (var cts = new CancellationTokenSource(TimeSpan.FromMinutes(10)))
            {
                var cancellationToken = cts.Token;
                var network           = Networks.GetNetwork <BTCPayNetwork>(cryptoCode);
                if (network == null)
                {
                    return(NotFound());
                }
                var websocket = await HttpContext.WebSockets.AcceptWebSocketAsync();

                var hwi = new Hwi.HwiClient(network.NBitcoinNetwork)
                {
                    Transport = new HwiWebSocketTransport(websocket)
                };
                Hwi.HwiDeviceClient device      = null;
                HwiEnumerateEntry   deviceEntry = null;
                HDFingerprint?      fingerprint = null;
                string password        = null;
                bool   pinProvided     = false;
                var    websocketHelper = new WebSocketHelper(websocket);

                async Task <bool> RequireDeviceUnlocking()
                {
                    if (deviceEntry == null)
                    {
                        await websocketHelper.Send("{ \"error\": \"need-device\"}", cancellationToken);

                        return(true);
                    }
                    if (deviceEntry.Code is HwiErrorCode.DeviceNotInitialized)
                    {
                        await websocketHelper.Send("{ \"error\": \"need-initialized\"}", cancellationToken);

                        return(true);
                    }
                    if ((deviceEntry.Code is HwiErrorCode.DeviceNotReady || deviceEntry.NeedsPinSent is true) &&
                        !pinProvided)
                    {
                        if (!IsTrezorT(deviceEntry))
                        {
                            await websocketHelper.Send("{ \"error\": \"need-pin\"}", cancellationToken);

                            return(true);
                        }
                        else
                        {
                            try
                            {
                                // On trezor T this will prompt the password! (https://github.com/bitcoin-core/HWI/issues/283)
                                await device.GetXPubAsync(new KeyPath("44'"), cancellationToken);
                            }
                            catch (HwiException ex) when(ex.ErrorCode == HwiErrorCode.DeviceAlreadyUnlocked)
                            {
                                pinProvided = true;
                            }
                            await websocketHelper.Send("{ \"error\": \"need-passphrase-on-device\"}", cancellationToken);

                            return(true);
                        }
                    }
                    if ((deviceEntry.Code is HwiErrorCode.DeviceNotReady || deviceEntry.NeedsPassphraseSent is true) && password == null)
                    {
                        if (IsTrezorT(deviceEntry))
                        {
                            await websocketHelper.Send("{ \"error\": \"need-passphrase-on-device\"}", cancellationToken);
                        }
                        else
                        {
                            await websocketHelper.Send("{ \"error\": \"need-passphrase\"}", cancellationToken);
                        }
                        return(true);
                    }
                    return(false);
                }

                JObject o = null;
                try
                {
                    while (true)
                    {
                        var command = await websocketHelper.NextMessageAsync(cancellationToken);

                        switch (command)
                        {
                        case "set-passphrase":
                            device.Password = await websocketHelper.NextMessageAsync(cancellationToken);

                            password = device.Password;
                            break;

                        case "ask-sign":
                            if (await RequireDeviceUnlocking())
                            {
                                continue;
                            }
                            if (walletId == null)
                            {
                                await websocketHelper.Send("{ \"error\": \"invalid-walletId\"}", cancellationToken);

                                continue;
                            }
                            if (fingerprint is null)
                            {
                                fingerprint = (await device.GetXPubAsync(new KeyPath("44'"), cancellationToken)).ExtPubKey.ParentFingerprint;
                            }
                            await websocketHelper.Send("{ \"info\": \"ready\"}", cancellationToken);

                            o = JObject.Parse(await websocketHelper.NextMessageAsync(cancellationToken));
                            var authorization = await _authorizationService.AuthorizeAsync(User, Policies.CanModifyStoreSettings.Key);

                            if (!authorization.Succeeded)
                            {
                                await websocketHelper.Send("{ \"error\": \"not-authorized\"}", cancellationToken);

                                continue;
                            }
                            var psbt = PSBT.Parse(o["psbt"].Value <string>(), network.NBitcoinNetwork);
                            var derivationSettings = GetDerivationSchemeSettings(walletId);
                            derivationSettings.RebaseKeyPaths(psbt);
                            var signing = derivationSettings.GetSigningAccountKeySettings();
                            if (signing.GetRootedKeyPath()?.MasterFingerprint != fingerprint)
                            {
                                await websocketHelper.Send("{ \"error\": \"wrong-wallet\"}", cancellationToken);

                                continue;
                            }
                            var signableInputs = psbt.Inputs
                                                 .SelectMany(i => i.HDKeyPaths)
                                                 .Where(i => i.Value.MasterFingerprint == fingerprint)
                                                 .ToArray();
                            if (signableInputs.Length > 0)
                            {
                                var actualPubKey = (await device.GetXPubAsync(signableInputs[0].Value.KeyPath)).GetPublicKey();
                                if (actualPubKey != signableInputs[0].Key)
                                {
                                    await websocketHelper.Send("{ \"error\": \"wrong-keypath\"}", cancellationToken);

                                    continue;
                                }
                            }
                            try
                            {
                                psbt = await device.SignPSBTAsync(psbt, cancellationToken);
                            }
                            catch (Hwi.HwiException)
                            {
                                await websocketHelper.Send("{ \"error\": \"user-reject\"}", cancellationToken);

                                continue;
                            }
                            o = new JObject();
                            o.Add("psbt", psbt.ToBase64());
                            await websocketHelper.Send(o.ToString(), cancellationToken);

                            break;

                        case "ask-pin":
                            if (device == null)
                            {
                                await websocketHelper.Send("{ \"error\": \"need-device\"}", cancellationToken);

                                continue;
                            }
                            try
                            {
                                await device.PromptPinAsync(cancellationToken);
                            }
                            catch (HwiException ex) when(ex.ErrorCode == HwiErrorCode.DeviceAlreadyUnlocked)
                            {
                                pinProvided = true;
                                await websocketHelper.Send("{ \"error\": \"device-already-unlocked\"}", cancellationToken);

                                continue;
                            }
                            await websocketHelper.Send("{ \"info\": \"prompted, please input the pin\"}", cancellationToken);

                            var pin = int.Parse(await websocketHelper.NextMessageAsync(cancellationToken), CultureInfo.InvariantCulture);
                            if (await device.SendPinAsync(pin, cancellationToken))
                            {
                                pinProvided = true;
                                await websocketHelper.Send("{ \"info\": \"the pin is correct\"}", cancellationToken);
                            }
                            else
                            {
                                await websocketHelper.Send("{ \"error\": \"incorrect-pin\"}", cancellationToken);

                                continue;
                            }
                            break;

                        case "ask-xpub":
                            if (await RequireDeviceUnlocking())
                            {
                                continue;
                            }
                            await websocketHelper.Send("{ \"info\": \"ok\"}", cancellationToken);

                            var     askedXpub     = JObject.Parse(await websocketHelper.NextMessageAsync(cancellationToken));
                            var     addressType   = askedXpub["addressType"].Value <string>();
                            var     accountNumber = askedXpub["accountNumber"].Value <int>();
                            JObject result        = new JObject();
                            var     factory       = network.NBXplorerNetwork.DerivationStrategyFactory;
                            if (fingerprint is null)
                            {
                                fingerprint = (await device.GetXPubAsync(new KeyPath("44'"), cancellationToken)).ExtPubKey.ParentFingerprint;
                            }
                            result["fingerprint"] = fingerprint.Value.ToString();

                            DerivationStrategyBase strategy = null;
                            KeyPath          keyPath        = null;
                            BitcoinExtPubKey xpub           = null;

                            if (!network.NBitcoinNetwork.Consensus.SupportSegwit && addressType != "legacy")
                            {
                                await websocketHelper.Send("{ \"error\": \"segwit-notsupported\"}", cancellationToken);

                                continue;
                            }

                            if (addressType == "segwit")
                            {
                                keyPath = new KeyPath("84'").Derive(network.CoinType).Derive(accountNumber, true);
                                xpub    = await device.GetXPubAsync(keyPath);

                                strategy = factory.CreateDirectDerivationStrategy(xpub, new DerivationStrategyOptions()
                                {
                                    ScriptPubKeyType = ScriptPubKeyType.Segwit
                                });
                            }
                            else if (addressType == "segwitWrapped")
                            {
                                keyPath = new KeyPath("49'").Derive(network.CoinType).Derive(accountNumber, true);
                                xpub    = await device.GetXPubAsync(keyPath);

                                strategy = factory.CreateDirectDerivationStrategy(xpub, new DerivationStrategyOptions()
                                {
                                    ScriptPubKeyType = ScriptPubKeyType.SegwitP2SH
                                });
                            }
                            else if (addressType == "legacy")
                            {
                                keyPath = new KeyPath("44'").Derive(network.CoinType).Derive(accountNumber, true);
                                xpub    = await device.GetXPubAsync(keyPath);

                                strategy = factory.CreateDirectDerivationStrategy(xpub, new DerivationStrategyOptions()
                                {
                                    ScriptPubKeyType = ScriptPubKeyType.Legacy
                                });
                            }
                            else
                            {
                                await websocketHelper.Send("{ \"error\": \"invalid-addresstype\"}", cancellationToken);

                                continue;
                            }
                            result.Add(new JProperty("strategy", strategy.ToString()));
                            result.Add(new JProperty("accountKey", xpub.ToString()));
                            result.Add(new JProperty("keyPath", keyPath.ToString()));
                            await websocketHelper.Send(result.ToString(), cancellationToken);

                            break;

                        case "refresh-device":
                        case "ask-device":
                            DeviceSelector deviceSelector = (command == "refresh-device" && deviceEntry != null ? deviceEntry.DeviceSelector : null);
                            password    = null;
                            pinProvided = false;
                            deviceEntry = null;
                            device      = null;
                            var entries = (await hwi.EnumerateEntriesAsync(cancellationToken)).ToList();
                            deviceEntry = entries.Where(h => deviceSelector == null || SameSelector(deviceSelector, h.DeviceSelector)).FirstOrDefault();
                            if (deviceEntry == null)
                            {
                                await websocketHelper.Send("{ \"error\": \"no-device\"}", cancellationToken);

                                continue;
                            }
                            device      = new HwiDeviceClient(hwi, deviceEntry.DeviceSelector, deviceEntry.Model, deviceEntry.Fingerprint);
                            fingerprint = device.Fingerprint;
                            JObject json = new JObject();
                            json.Add("model", device.Model.ToString());
                            json.Add("fingerprint", device.Fingerprint?.ToString());
                            await websocketHelper.Send(json.ToString(), cancellationToken);

                            break;
                        }
                    }
                }
                catch (FormatException ex)
                {
                    JObject obj = new JObject();
                    obj.Add("error", "invalid-network");
                    obj.Add("details", ex.ToString());
                    try
                    {
                        await websocketHelper.Send(obj.ToString(), cancellationToken);
                    }
                    catch { }
                }
                catch (Exception ex)
                {
                    JObject obj = new JObject();
                    obj.Add("error", "unknown-error");
                    obj.Add("message", ex.Message);
                    obj.Add("details", ex.ToString());
                    try
                    {
                        await websocketHelper.Send(obj.ToString(), cancellationToken);
                    }
                    catch { }
                }
                finally
                {
                    await websocketHelper.DisposeAsync(cancellationToken);
                }
            }
            return(new EmptyResult());
        }
Exemplo n.º 6
0
        private async Task FetchTransactionDetails(DerivationSchemeSettings derivationSchemeSettings, WalletPSBTReadyViewModel vm, BTCPayNetwork network)
        {
            var psbtObject = PSBT.Parse(vm.SigningContext.PSBT, network.NBitcoinNetwork);

            if (!psbtObject.IsAllFinalized())
            {
                psbtObject = await ExplorerClientProvider.UpdatePSBT(derivationSchemeSettings, psbtObject) ?? psbtObject;
            }
            IHDKey        signingKey     = null;
            RootedKeyPath signingKeyPath = null;

            try
            {
                signingKey = new BitcoinExtPubKey(vm.SigningKey, network.NBitcoinNetwork);
            }
            catch { }
            try
            {
                signingKey = signingKey ?? new BitcoinExtKey(vm.SigningKey, network.NBitcoinNetwork);
            }
            catch { }

            try
            {
                signingKeyPath = RootedKeyPath.Parse(vm.SigningKeyPath);
            }
            catch { }

            if (signingKey == null || signingKeyPath == null)
            {
                var signingKeySettings = derivationSchemeSettings.GetSigningAccountKeySettings();
                if (signingKey == null)
                {
                    signingKey    = signingKeySettings.AccountKey;
                    vm.SigningKey = signingKey.ToString();
                }
                if (vm.SigningKeyPath == null)
                {
                    signingKeyPath    = signingKeySettings.GetRootedKeyPath();
                    vm.SigningKeyPath = signingKeyPath?.ToString();
                }
            }

            if (psbtObject.IsAllFinalized())
            {
                vm.CanCalculateBalance = false;
            }
            else
            {
                var balanceChange = psbtObject.GetBalance(derivationSchemeSettings.AccountDerivation, signingKey, signingKeyPath);
                vm.BalanceChange       = ValueToString(balanceChange, network);
                vm.CanCalculateBalance = true;
                vm.Positive            = balanceChange >= Money.Zero;
            }
            vm.Inputs = new List <WalletPSBTReadyViewModel.InputViewModel>();
            foreach (var input in psbtObject.Inputs)
            {
                var inputVm = new WalletPSBTReadyViewModel.InputViewModel();
                vm.Inputs.Add(inputVm);
                var mine           = input.HDKeysFor(derivationSchemeSettings.AccountDerivation, signingKey, signingKeyPath).Any();
                var balanceChange2 = input.GetTxOut()?.Value ?? Money.Zero;
                if (mine)
                {
                    balanceChange2 = -balanceChange2;
                }
                inputVm.BalanceChange = ValueToString(balanceChange2, network);
                inputVm.Positive      = balanceChange2 >= Money.Zero;
                inputVm.Index         = (int)input.Index;
            }
            vm.Destinations = new List <WalletPSBTReadyViewModel.DestinationViewModel>();
            foreach (var output in psbtObject.Outputs)
            {
                var dest = new WalletPSBTReadyViewModel.DestinationViewModel();
                vm.Destinations.Add(dest);
                var mine           = output.HDKeysFor(derivationSchemeSettings.AccountDerivation, signingKey, signingKeyPath).Any();
                var balanceChange2 = output.Value;
                if (!mine)
                {
                    balanceChange2 = -balanceChange2;
                }
                dest.Balance     = ValueToString(balanceChange2, network);
                dest.Positive    = balanceChange2 >= Money.Zero;
                dest.Destination = output.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork)?.ToString() ?? output.ScriptPubKey.ToString();
            }

            if (psbtObject.TryGetFee(out var fee))
            {
                vm.Destinations.Add(new WalletPSBTReadyViewModel.DestinationViewModel
                {
                    Positive    = false,
                    Balance     = ValueToString(-fee, network),
                    Destination = "Mining fees"
                });
            }
            if (psbtObject.TryGetEstimatedFeeRate(out var feeRate))
            {
                vm.FeeRate = feeRate.ToString();
            }

            var sanityErrors = psbtObject.CheckSanity();

            if (sanityErrors.Count != 0)
            {
                vm.SetErrors(sanityErrors);
            }
            else if (!psbtObject.IsAllFinalized() && !psbtObject.TryFinalize(out var errors))
            {
                vm.SetErrors(errors);
            }
        }
Exemplo n.º 7
0
        private async Task FetchTransactionDetails(WalletId walletId, WalletPSBTReadyViewModel vm, BTCPayNetwork network)
        {
            var           psbtObject     = PSBT.Parse(vm.PSBT, network.NBitcoinNetwork);
            IHDKey        signingKey     = null;
            RootedKeyPath signingKeyPath = null;

            try
            {
                signingKey = new BitcoinExtPubKey(vm.SigningKey, network.NBitcoinNetwork);
            }
            catch { }
            try
            {
                signingKey = signingKey ?? new BitcoinExtKey(vm.SigningKey, network.NBitcoinNetwork);
            }
            catch { }

            try
            {
                signingKeyPath = RootedKeyPath.Parse(vm.SigningKeyPath);
            }
            catch { }

            var derivationSchemeSettings = await GetDerivationSchemeSettings(walletId);

            if (signingKey == null || signingKeyPath == null)
            {
                var signingKeySettings = derivationSchemeSettings.GetSigningAccountKeySettings();
                if (signingKey == null)
                {
                    signingKey    = signingKeySettings.AccountKey;
                    vm.SigningKey = signingKey.ToString();
                }
                if (vm.SigningKeyPath == null)
                {
                    signingKeyPath    = signingKeySettings.GetRootedKeyPath();
                    vm.SigningKeyPath = signingKeyPath?.ToString();
                }
            }

            if (psbtObject.IsAllFinalized())
            {
                vm.CanCalculateBalance = false;
            }
            else
            {
                var balanceChange = psbtObject.GetBalance(derivationSchemeSettings.AccountDerivation, signingKey, signingKeyPath);
                vm.BalanceChange       = ValueToString(balanceChange, network);
                vm.CanCalculateBalance = true;
                vm.Positive            = balanceChange >= Money.Zero;
            }

            foreach (var output in psbtObject.Outputs)
            {
                var dest = new WalletPSBTReadyViewModel.DestinationViewModel();
                vm.Destinations.Add(dest);
                var mine           = output.HDKeysFor(derivationSchemeSettings.AccountDerivation, signingKey, signingKeyPath).Any();
                var balanceChange2 = output.Value;
                if (!mine)
                {
                    balanceChange2 = -balanceChange2;
                }
                dest.Balance     = ValueToString(balanceChange2, network);
                dest.Positive    = balanceChange2 >= Money.Zero;
                dest.Destination = output.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork)?.ToString() ?? output.ScriptPubKey.ToString();
            }

            if (psbtObject.TryGetFee(out var fee))
            {
                vm.Destinations.Add(new WalletPSBTReadyViewModel.DestinationViewModel()
                {
                    Positive    = false,
                    Balance     = ValueToString(-fee, network),
                    Destination = "Mining fees"
                });
            }
            if (psbtObject.TryGetEstimatedFeeRate(out var feeRate))
            {
                vm.FeeRate = feeRate.ToString();
            }

            if (!psbtObject.IsAllFinalized() && !psbtObject.TryFinalize(out var errors))
            {
                vm.SetErrors(errors);
            }
        }