/// <summary> /// Create a P2PKH wallet with one key /// </summary> /// <param name="rootKey">The master key to use</param> public WalletCreation(BitcoinExtPubKey rootKey) { Network = rootKey.Network; RootKeys = new[] { rootKey.ExtPubKey }; SignatureRequired = 1; UseP2SH = false; }
/// <summary> /// Create a P2PKH wallet with one key /// </summary> /// <param name="rootKey">The master key to use</param> public WalletCreation(BitcoinExtPubKey rootKey) { Network = rootKey.Network; RootKeys = new[] { rootKey.ExtPubKey }; SignatureRequired = 1; UseP2SH = false; Name = Guid.NewGuid().ToString(); }
public DerivationStrategyBase Parse(string str) { if (str == null) { throw new ArgumentNullException(nameof(str)); } str = str.Trim(); HashSet <string> hintedLabels = new HashSet <string>(); var hintDestination = HintScriptPubKey?.GetDestination(); if (hintDestination != null) { if (hintDestination is KeyId) { hintedLabels.Add("legacy"); } if (hintDestination is ScriptId) { hintedLabels.Add("p2sh"); } } try { var result = new DerivationStrategyFactory(Network).Parse(str); return(FindMatch(hintedLabels, result)); } catch { } Dictionary <uint, string[]> electrumMapping = new Dictionary <uint, string[]>(); //Source https://github.com/spesmilo/electrum/blob/9edffd17542de5773e7284a8c8a2673c766bb3c3/lib/bitcoin.py var standard = 0x0488b21eU; electrumMapping.Add(standard, new[] { "legacy" }); var p2wpkh_p2sh = 0x049d7cb2U; electrumMapping.Add(p2wpkh_p2sh, new string[] { "p2sh" }); var p2wpkh = 0x4b24746U; electrumMapping.Add(p2wpkh, Array.Empty <string>()); var parts = str.Split('-'); for (int i = 0; i < parts.Length; i++) { if (IsLabel(parts[i])) { hintedLabels.Add(parts[i].Substring(1, parts[i].Length - 2).ToLowerInvariant()); continue; } try { var data = Encoders.Base58Check.DecodeData(parts[i]); if (data.Length < 4) { continue; } var prefix = Utils.ToUInt32(data, false); var standardPrefix = Utils.ToBytes(ChainType == NBXplorer.ChainType.Main ? 0x0488b21eU : 0x043587cf, false); for (int ii = 0; ii < 4; ii++) { data[ii] = standardPrefix[ii]; } var derivationScheme = new BitcoinExtPubKey(Encoders.Base58Check.EncodeData(data), Network).ToString(); electrumMapping.TryGetValue(prefix, out string[] labels); if (labels != null) { foreach (var label in labels) { hintedLabels.Add(label.ToLowerInvariant()); } } parts[i] = derivationScheme; } catch { continue; } } if (hintDestination != null) { if (hintDestination is WitKeyId) { hintedLabels.Remove("legacy"); hintedLabels.Remove("p2sh"); } } str = string.Join('-', parts.Where(p => !IsLabel(p))); foreach (var label in hintedLabels) { str = $"{str}-[{label}]"; } return(FindMatch(hintedLabels, new DerivationStrategyFactory(Network).Parse(str))); }
/// <inheritdoc /> public async Task TumbleAsync(string originWalletName, string destinationWalletName, string originWalletPassword) { // Make sure it won't start new tumbling round if already started if (this.State == TumbleState.Tumbling) { this.logger.LogDebug("Tumbler is already running"); throw new Exception("Tumbling is already running"); } this.TumblingState.TumblerUri = new Uri(this.TumblerAddress); // Check if in initial block download if (!this.chain.IsDownloaded()) { this.logger.LogDebug("Chain is still being downloaded: " + this.chain.Tip); throw new Exception("Chain is still being downloaded"); } Wallet originWallet = this.walletManager.GetWallet(originWalletName); Wallet destinationWallet = this.walletManager.GetWallet(destinationWalletName); // Check if password is valid before starting any cycles try { HdAddress tempAddress = originWallet.GetAccountsByCoinType(this.TumblingState.CoinType).First() .GetFirstUnusedReceivingAddress(); originWallet.GetExtendedPrivateKeyForAddress(originWalletPassword, tempAddress); } catch (Exception) { this.logger.LogDebug("Origin wallet password appears to be invalid"); throw new Exception("Origin wallet password appears to be invalid"); } // Update the state and save this.TumblingState.DestinationWallet = destinationWallet ?? throw new Exception($"Destination wallet not found. Have you created a wallet with name {destinationWalletName}?"); this.TumblingState.DestinationWalletName = destinationWalletName; this.TumblingState.OriginWallet = originWallet ?? throw new Exception($"Origin wallet not found. Have you created a wallet with name {originWalletName}?"); this.TumblingState.OriginWalletName = originWalletName; this.TumblingState.OriginWalletPassword = originWalletPassword; var accounts = this.TumblingState.DestinationWallet.GetAccountsByCoinType(this.TumblingState.CoinType); // TODO: Possibly need to preserve destination account name in tumbling state. Default to first account for now string accountName = accounts.First().Name; HdAccount destAccount = this.TumblingState.DestinationWallet.GetAccountByCoinType(accountName, this.TumblingState.CoinType); string key = destAccount.ExtendedPubKey; KeyPath keyPath = new KeyPath("0"); // Stop and dispose onlymonitor if (this.broadcasterJob != null && this.broadcasterJob.Started) { await this.broadcasterJob.Stop().ConfigureAwait(false); } this.runtime?.Dispose(); // Bypass Tor for integration tests FullNodeTumblerClientConfiguration config; if (this.TumblerAddress.Contains("127.0.0.1")) { config = new FullNodeTumblerClientConfiguration(this.TumblingState, onlyMonitor: false, connectionTest: false, useProxy: false); } else { config = new FullNodeTumblerClientConfiguration(this.TumblingState, onlyMonitor: false, connectionTest: false, useProxy: true); } this.runtime = await TumblerClientRuntime.FromConfigurationAsync(config).ConfigureAwait(false); // Check if origin wallet has a sufficient balance to begin tumbling at least 1 cycle if (!runtime.HasEnoughFundsForCycle(true)) { this.logger.LogDebug("Insufficient funds in origin wallet"); throw new Exception("Insufficient funds in origin wallet"); } BitcoinExtPubKey extPubKey = new BitcoinExtPubKey(key, this.runtime.Network); if (key != null) { this.runtime.DestinationWallet = new ClientDestinationWallet(extPubKey, keyPath, this.runtime.Repository, this.runtime.Network); } this.TumblerParameters = this.runtime.TumblerParameters; // Run onlymonitor mode this.broadcasterJob = this.runtime.CreateBroadcasterJob(); this.broadcasterJob.Start(); // Run tumbling mode this.stateMachine = new StateMachinesExecutor(this.runtime); this.stateMachine.Start(); }
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); } }
private void CreateCoin(TransactionBuilder builder, List <Transaction> knownTransactions, ScriptPubKeyType addressType, Money money, BitcoinExtPubKey xpub, string path) { var pubkey = xpub.Derive(new KeyPath(path)).ExtPubKey.PubKey; if (addressType == ScriptPubKeyType.Legacy) { var prevTx = xpub.Network.CreateTransaction(); prevTx.Inputs.Add(RandomOutpoint(), new Key().ScriptPubKey); var txout = prevTx.Outputs.Add(money, pubkey.GetScriptPubKey(addressType)); var coin = new Coin(new OutPoint(prevTx, 0), txout); builder.AddCoins(coin); knownTransactions.Add(prevTx); } if (addressType == ScriptPubKeyType.Segwit) { var outpoint = RandomOutpoint(); var txout = xpub.Network.Consensus.ConsensusFactory.CreateTxOut(); txout.Value = money; txout.ScriptPubKey = pubkey.GetScriptPubKey(addressType); var coin = new Coin(outpoint, txout); builder.AddCoins(coin); } if (addressType == ScriptPubKeyType.SegwitP2SH) { var outpoint = RandomOutpoint(); var txout = xpub.Network.Consensus.ConsensusFactory.CreateTxOut(); txout.Value = money; txout.ScriptPubKey = pubkey.GetScriptPubKey(addressType); var coin = new Coin(outpoint, txout).ToScriptCoin(pubkey.GetScriptPubKey(ScriptPubKeyType.Segwit)); builder.AddCoins(coin); } }
private async Task FetchTransactionDetails(DerivationSchemeSettings derivationSchemeSettings, WalletPSBTReadyViewModel vm, BTCPayNetwork network) { var psbtObject = PSBT.Parse(vm.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); } }
/// <summary> /// WARNING: ONLY CHECKS CONFIRMED KEYPATHS /// </summary> public static IEnumerable <BitcoinWitPubKeyAddress> GetUnusedBech32Keys(int count, bool isInternal, BitcoinExtPubKey bitcoinExtPubKey, IEnumerable <FilterModel> filters) { var change = isInternal ? 1 : 0; var filterArray = filters.ToArray(); var startingKey = 0; // Where to start getting keys one by one. int i = 0; while (true) { if (Match(bitcoinExtPubKey, change, i, filterArray, out _)) { startingKey = i + 1; if (i == 0) { i = 1; } else { i *= 2; } } else { break; } } i = startingKey; int returnedNum = 0; while (true) { if (!Match(bitcoinExtPubKey, change, i, filterArray, out PubKey pubKey)) { yield return(pubKey.GetSegwitAddress(bitcoinExtPubKey.Network)); returnedNum++; if (returnedNum >= count) { yield break; } } i++; } }
/// <inheritdoc /> public async Task TumbleAsync(string originWalletName, string destinationWalletName, string originWalletPassword) { // Make sure it won't start new tumbling round if already started if (this.State == TumbleState.Tumbling) { this.logger.LogDebug("Tumbler is already running"); throw new Exception("Tumbling is already running"); } this.tumblingState.TumblerUri = new Uri(this.TumblerAddress); // Check if in initial block download if (!this.chain.IsDownloaded()) { this.logger.LogDebug("Chain is still being downloaded: " + this.chain.Tip); throw new Exception("Chain is still being downloaded"); } Wallet destinationWallet = this.walletManager.GetWallet(destinationWalletName); Wallet originWallet = this.walletManager.GetWallet(originWalletName); // Check if origin wallet has a balance WalletBalanceModel model = new WalletBalanceModel(); var originWalletAccounts = this.walletManager.GetAccounts(originWallet.Name).ToList(); var originConfirmed = new Money(0); var originUnconfirmed = new Money(0); foreach (var originAccount in originWallet.GetAccountsByCoinType(this.tumblingState.CoinType)) { var result = originAccount.GetSpendableAmount(); originConfirmed += result.ConfirmedAmount; originUnconfirmed += result.UnConfirmedAmount; } // Should ideally take network transaction fee into account too, but that is dynamic if ((originConfirmed + originUnconfirmed) <= (this.TumblerParameters.Denomination + this.TumblerParameters.Fee)) { this.logger.LogDebug("Insufficient funds in origin wallet"); throw new Exception("Insufficient funds in origin wallet"); } // TODO: Check if password is valid before starting any cycles // Update the state and save this.tumblingState.DestinationWallet = destinationWallet ?? throw new Exception($"Destination wallet not found. Have you created a wallet with name {destinationWalletName}?"); this.tumblingState.DestinationWalletName = destinationWalletName; this.tumblingState.OriginWallet = originWallet ?? throw new Exception($"Origin wallet not found. Have you created a wallet with name {originWalletName}?"); this.tumblingState.OriginWalletName = originWalletName; this.tumblingState.OriginWalletPassword = originWalletPassword; var accounts = this.tumblingState.DestinationWallet.GetAccountsByCoinType(this.tumblingState.CoinType); // TODO: Possibly need to preserve destination account name in tumbling state. Default to first account for now string accountName = null; foreach (var account in accounts) { if (account.Index == 0) { accountName = account.Name; } } var destAccount = this.tumblingState.DestinationWallet.GetAccountByCoinType(accountName, this.tumblingState.CoinType); var key = destAccount.ExtendedPubKey; var keyPath = new KeyPath("0"); // Stop and dispose onlymonitor if (this.broadcasterJob != null && this.broadcasterJob.Started) { await this.broadcasterJob.Stop().ConfigureAwait(false); } this.runtime?.Dispose(); var config = new FullNodeTumblerClientConfiguration(this.tumblingState, onlyMonitor: false); this.runtime = await TumblerClientRuntime.FromConfigurationAsync(config).ConfigureAwait(false); var extPubKey = new BitcoinExtPubKey(key, this.runtime.Network); if (key != null) { this.runtime.DestinationWallet = new ClientDestinationWallet(extPubKey, keyPath, this.runtime.Repository, this.runtime.Network); } this.TumblerParameters = this.runtime.TumblerParameters; // Run onlymonitor mode this.broadcasterJob = this.runtime.CreateBroadcasterJob(); this.broadcasterJob.Start(); // Run tumbling mode this.stateMachine = new StateMachinesExecutor(this.runtime); this.stateMachine.Start(); this.State = TumbleState.Tumbling; return; }
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; int? pin = null; var websocketHelper = new WebSocketHelper(websocket); async Task <bool> RequireDeviceUnlocking() { if (deviceEntry == null) { await websocketHelper.Send("{ \"error\": \"need-device\"}", cancellationToken); return(true); } if (deviceEntry.NeedsPinSent is true && pin is null) { await websocketHelper.Send("{ \"error\": \"need-pin\"}", cancellationToken); return(true); } if (deviceEntry.NeedsPassphraseSent is true && password == null) { 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; } 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; } await device.PromptPinAsync(cancellationToken); await websocketHelper.Send("{ \"info\": \"prompted, please input the pin\"}", cancellationToken); pin = int.Parse(await websocketHelper.NextMessageAsync(cancellationToken), CultureInfo.InvariantCulture); if (await device.SendPinAsync(pin.Value, cancellationToken)) { await websocketHelper.Send("{ \"info\": \"the pin is correct\"}", cancellationToken); } else { await websocketHelper.Send("{ \"error\": \"incorrect-pin\"}", cancellationToken); continue; } break; case "ask-xpubs": if (await RequireDeviceUnlocking()) { continue; } JObject result = new JObject(); var factory = network.NBXplorerNetwork.DerivationStrategyFactory; var keyPath = new KeyPath("84'").Derive(network.CoinType).Derive(0, true); BitcoinExtPubKey xpub = await device.GetXPubAsync(keyPath); if (fingerprint is null) { fingerprint = (await device.GetXPubAsync(new KeyPath("44'"), cancellationToken)).ExtPubKey.ParentFingerprint; } result["fingerprint"] = fingerprint.Value.ToString(); var strategy = factory.CreateDirectDerivationStrategy(xpub, new DerivationStrategyOptions() { ScriptPubKeyType = ScriptPubKeyType.Segwit }); AddDerivationSchemeToJson("segwit", result, keyPath, xpub, strategy); keyPath = new KeyPath("49'").Derive(network.CoinType).Derive(0, true); xpub = await device.GetXPubAsync(keyPath); strategy = factory.CreateDirectDerivationStrategy(xpub, new DerivationStrategyOptions() { ScriptPubKeyType = ScriptPubKeyType.SegwitP2SH }); AddDerivationSchemeToJson("segwitWrapped", result, keyPath, xpub, strategy); keyPath = new KeyPath("44'").Derive(network.CoinType).Derive(0, true); xpub = await device.GetXPubAsync(keyPath); strategy = factory.CreateDirectDerivationStrategy(xpub, new DerivationStrategyOptions() { ScriptPubKeyType = ScriptPubKeyType.Legacy }); AddDerivationSchemeToJson("legacy", result, keyPath, xpub, strategy); await websocketHelper.Send(result.ToString(), cancellationToken); break; case "ask-device": password = null; pin = null; deviceEntry = null; device = null; var entries = (await hwi.EnumerateEntriesAsync(cancellationToken)).ToList(); deviceEntry = entries.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 (Exception ex) { JObject obj = new JObject(); obj.Add("error", "unknown-error"); obj.Add("details", ex.ToString()); try { await websocketHelper.Send(obj.ToString(), cancellationToken); } catch { } } finally { await websocketHelper.DisposeAsync(cancellationToken); } } return(new EmptyResult()); }
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()), })); }
/// <summary> /// Create a taproot signature derivation strategy from public key /// </summary> /// <param name="publicKey">The public key of the wallet</param> /// <param name="options">Derivation options</param> /// <returns></returns> public TaprootDerivationStrategy CreateTaprootDerivationStrategy(BitcoinExtPubKey publicKey, ReadOnlyDictionary <string, bool> options = null) { return(new TaprootDerivationStrategy(publicKey, options)); }
/// <summary> /// Create a new wallet P2PKH with one key /// </summary> /// <param name="rootKey"></param> /// <param name="keyPoolSize"></param> public Wallet(BitcoinExtPubKey rootKey, int keyPoolSize = 500) #pragma warning disable CS0612 // Type or member is obsolete : this(new WalletCreation(rootKey), keyPoolSize) #pragma warning restore CS0612 // Type or member is obsolete { }
public DerivationStrategyBase Parse(string str) { if (str == null) { throw new ArgumentNullException(nameof(str)); } str = str.Trim(); HashSet <string> hintedLabels = new HashSet <string>(); var hintDestination = HintScriptPubKey?.GetDestination(); if (hintDestination != null) { if (hintDestination is KeyId) { hintedLabels.Add("legacy"); } if (hintDestination is ScriptId) { hintedLabels.Add("p2sh"); } } if (!Network.Consensus.SupportSegwit) { hintedLabels.Add("legacy"); } try { var result = BtcPayNetwork.NBXplorerNetwork.DerivationStrategyFactory.Parse(str); return(FindMatch(hintedLabels, result)); } catch { } var parts = str.Split('-'); bool hasLabel = false; for (int i = 0; i < parts.Length; i++) { if (IsLabel(parts[i])) { if (!hasLabel) { hintedLabels.Clear(); if (!Network.Consensus.SupportSegwit) { hintedLabels.Add("legacy"); } } hasLabel = true; hintedLabels.Add(parts[i].Substring(1, parts[i].Length - 2).ToLowerInvariant()); continue; } try { var data = Network.GetBase58CheckEncoder().DecodeData(parts[i]); if (data.Length < 4) { continue; } var prefix = Utils.ToUInt32(data, false); var standardPrefix = Utils.ToBytes(0x0488b21eU, false); for (int ii = 0; ii < 4; ii++) { data[ii] = standardPrefix[ii]; } var derivationScheme = new BitcoinExtPubKey(Network.GetBase58CheckEncoder().EncodeData(data), Network.Main).ToNetwork(Network).ToString(); if (BtcPayNetwork.ElectrumMapping.TryGetValue(prefix, out var type)) { switch (type) { case DerivationType.Legacy: hintedLabels.Add("legacy"); break; case DerivationType.SegwitP2SH: hintedLabels.Add("p2sh"); break; } } parts[i] = derivationScheme; } catch { continue; } } if (hintDestination != null) { if (hintDestination is WitKeyId) { hintedLabels.Remove("legacy"); hintedLabels.Remove("p2sh"); } } str = string.Join('-', parts.Where(p => !IsLabel(p))); foreach (var label in hintedLabels) { str = $"{str}-[{label}]"; } return(FindMatch(hintedLabels, BtcPayNetwork.NBXplorerNetwork.DerivationStrategyFactory.Parse(str))); }
/// <summary> /// Create a new wallet P2PKH with one key /// </summary> /// <param name="rootKey"></param> /// <param name="keyPoolSize"></param> public Wallet(BitcoinExtPubKey rootKey, int keyPoolSize = 500) : this(new WalletCreation(rootKey), keyPoolSize) { }
public KeyPair(ExtPubKey pubKey) { PubKey = new BitcoinExtPubKey(pubKey, Network.TestNet); Address = PubKey.ScriptPubKey.GetDestinationAddress(Network.TestNet); }
public async Task <IActionResult> VaultBridgeConnection(string cryptoCode = null, [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId = null) { if (!HttpContext.WebSockets.IsWebSocketRequest) { return(NotFound()); } cryptoCode = cryptoCode ?? walletId.CryptoCode; bool versionChecked = false; 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; 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) { if (IsTrezorT(deviceEntry)) { await websocketHelper.Send("{ \"error\": \"need-passphrase-on-device\"}", cancellationToken); return(true); } else if (deviceEntry.NeedsPinSent is true) { await websocketHelper.Send("{ \"error\": \"need-pin\"}", cancellationToken); return(true); } else if (deviceEntry.NeedsPassphraseSent is true && password is null) { 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); 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 "display-address": if (await RequireDeviceUnlocking()) { continue; } var k = RootedKeyPath.Parse(await websocketHelper.NextMessageAsync(cancellationToken)); await device.DisplayAddressAsync(GetScriptPubKeyType(k), k.KeyPath, cancellationToken); await websocketHelper.Send("{ \"info\": \"ok\"}", 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) { 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)) { goto askdevice; } else { await websocketHelper.Send("{ \"error\": \"incorrect-pin\"}", cancellationToken); continue; } 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 "ask-passphrase": if (command == "ask-passphrase") { if (deviceEntry == null) { await websocketHelper.Send("{ \"error\": \"need-device\"}", cancellationToken); continue; } // The make the trezor T ask for password await device.GetXPubAsync(new KeyPath("44'"), cancellationToken); } goto askdevice; case "ask-device": askdevice: if (!versionChecked) { var version = await hwi.GetVersionAsync(cancellationToken); if (version.Major < 2) { await websocketHelper.Send("{ \"error\": \"vault-outdated\"}", cancellationToken); continue; } versionChecked = true; } password = null; deviceEntry = null; device = null; var entries = (await hwi.EnumerateEntriesAsync(cancellationToken)).ToList(); deviceEntry = entries.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()); }
// Constructor public WalletBase(BitcoinExtPubKey rootKey) : base(rootKey, 1000) { }
public BitcoinWallet(string extPubKeyString, bool isTestNetwork) { extPubKey = new BitcoinExtPubKey(extPubKeyString); network = isTestNetwork ? Network.TestNet : Network.Main; }
public abstract Task <PSBT> SignTransactionAsync(PSBT psbt, RootedKeyPath accountKeyPath, BitcoinExtPubKey accountKey, Script changeHint, CancellationToken cancellationToken);
public async Task <IActionResult> UpdateWallet(WalletSetupViewModel vm) { var checkResult = IsAvailable(vm.CryptoCode, out var store, out var network); if (checkResult != null) { return(checkResult); } vm.Network = network; DerivationSchemeSettings strategy = null; var wallet = _WalletProvider.GetWallet(network); if (wallet == null) { return(NotFound()); } if (vm.WalletFile != null) { if (!DerivationSchemeSettings.TryParseFromWalletFile(await ReadAllText(vm.WalletFile), network, out strategy)) { ModelState.AddModelError(nameof(vm.WalletFile), "Wallet file was not in the correct format"); return(View(vm.ViewName, vm)); } } else if (!string.IsNullOrEmpty(vm.WalletFileContent)) { if (!DerivationSchemeSettings.TryParseFromWalletFile(vm.WalletFileContent, network, out strategy)) { ModelState.AddModelError(nameof(vm.WalletFileContent), "QR import was not in the correct format"); return(View(vm.ViewName, vm)); } } else if (!string.IsNullOrEmpty(vm.DerivationScheme)) { try { strategy = ParseDerivationStrategy(vm.DerivationScheme, network); strategy.Source = "ManualDerivationScheme"; if (!string.IsNullOrEmpty(vm.AccountKey)) { var accountKey = new BitcoinExtPubKey(vm.AccountKey, network.NBitcoinNetwork); var accountSettings = strategy.AccountKeySettings.FirstOrDefault(a => a.AccountKey == accountKey); if (accountSettings != null) { accountSettings.AccountKeyPath = vm.KeyPath == null ? null : KeyPath.Parse(vm.KeyPath); accountSettings.RootFingerprint = string.IsNullOrEmpty(vm.RootFingerprint) ? (HDFingerprint?)null : new HDFingerprint( NBitcoin.DataEncoders.Encoders.Hex.DecodeData(vm.RootFingerprint)); } } vm.DerivationScheme = strategy.AccountDerivation.ToString(); ModelState.Remove(nameof(vm.DerivationScheme)); } catch { ModelState.AddModelError(nameof(vm.DerivationScheme), "Invalid wallet format"); return(View(vm.ViewName, vm)); } } else if (!string.IsNullOrEmpty(vm.Config)) { if (!DerivationSchemeSettings.TryParseFromJson(UnprotectString(vm.Config), network, out strategy)) { ModelState.AddModelError(nameof(vm.Config), "Config file was not in the correct format"); return(View(vm.ViewName, vm)); } } if (strategy is null) { ModelState.AddModelError(nameof(vm.DerivationScheme), "Please provide your extended public key"); return(View(vm.ViewName, vm)); } vm.Config = ProtectString(strategy.ToJson()); ModelState.Remove(nameof(vm.Config)); PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike); var storeBlob = store.GetStoreBlob(); if (vm.Confirmation) { try { await wallet.TrackAsync(strategy.AccountDerivation); store.SetSupportedPaymentMethod(paymentMethodId, strategy); storeBlob.SetExcluded(paymentMethodId, false); storeBlob.PayJoinEnabled = strategy.IsHotWallet && !(vm.SetupRequest?.PayJoinEnabled is false); store.SetStoreBlob(storeBlob); } catch { ModelState.AddModelError(nameof(vm.DerivationScheme), "Invalid derivation scheme"); return(View(vm.ViewName, vm)); } await _Repo.UpdateStore(store); _EventAggregator.Publish(new WalletChangedEvent { WalletId = new WalletId(vm.StoreId, vm.CryptoCode) }); TempData[WellKnownTempData.SuccessMessage] = $"Wallet settings for {network.CryptoCode} have been updated."; // This is success case when derivation scheme is added to the store return(RedirectToAction(nameof(WalletSettings), new { storeId = vm.StoreId, cryptoCode = vm.CryptoCode })); } return(ConfirmAddresses(vm, strategy)); }
public async Task <FileContentResult> Sync( [ModelBinder(BinderType = typeof(DestinationModelBinder))] BitcoinExtPubKey extPubKey, [ModelBinder(BinderType = typeof(UInt256ModelBinding))] uint256 lastBlockHash = null, [ModelBinder(BinderType = typeof(UInt256ModelBinding))] uint256 unconfirmedHash = null, bool noWait = false) { lastBlockHash = lastBlockHash ?? uint256.Zero; var actualLastBlockHash = uint256.Zero; var waitingTransaction = noWait ? Task.FromResult(false) : WaitingTransaction(extPubKey); Runtime.Repository.MarkAsUsed(new KeyInformation(extPubKey)); UTXOChanges changes = null; UTXOChanges previousChanges = null; List <TrackedTransaction> cleanList = null; var getKeyPath = GetKeyPaths(extPubKey); while (true) { cleanList = new List <TrackedTransaction>(); HashSet <uint256> conflictedUnconf = new HashSet <uint256>(); changes = new UTXOChanges(); List <AnnotatedTransaction> transactions = GetAnnotatedTransactions(extPubKey); var unconf = transactions.Where(tx => tx.Height == MempoolHeight); var conf = transactions.Where(tx => tx.Height != MempoolHeight); conf = conf.TopologicalSort(DependsOn(conf.ToList())).ToList(); unconf = unconf.TopologicalSort(DependsOn(unconf.ToList())).ToList(); foreach (var item in conf.Concat(unconf)) { var record = item.Record; if (record.BlockHash == null) { if ( //A parent conflicted with the current utxo record.Transaction.Inputs.Any(i => conflictedUnconf.Contains(i.PrevOut.Hash)) || //Conflict with the confirmed utxo changes.Confirmed.HasConflict(record.Transaction)) { cleanList.Add(record); conflictedUnconf.Add(record.Transaction.GetHash()); continue; } if (changes.Unconfirmed.HasConflict(record.Transaction)) { Logs.Explorer.LogInformation($"Conflicts in the mempool. {record.Transaction.GetHash()} ignored"); continue; } changes.Unconfirmed.LoadChanges(record.Transaction, getKeyPath); } else { if (changes.Confirmed.HasConflict(record.Transaction)) { Logs.Explorer.LogError("A conflict among confirmed transaction happened, this should be impossible"); throw new InvalidOperationException("The impossible happened"); } changes.Unconfirmed.LoadChanges(record.Transaction, getKeyPath); changes.Confirmed.LoadChanges(record.Transaction, getKeyPath); changes.Confirmed.Hash = record.BlockHash; actualLastBlockHash = record.BlockHash; if (record.BlockHash == lastBlockHash) { previousChanges = changes.Clone(); } } } changes.Unconfirmed = changes.Unconfirmed.Diff(changes.Confirmed); changes.Unconfirmed.Hash = changes.Unconfirmed.GetHash(); if (changes.Unconfirmed.Hash == unconfirmedHash) { changes.Unconfirmed.Clear(); } else { changes.Unconfirmed.Reset = true; } if (actualLastBlockHash == lastBlockHash) { changes.Confirmed.Clear(); } else if (previousChanges != null) { changes.Confirmed.Reset = false; changes.Confirmed = changes.Confirmed.Diff(previousChanges.Confirmed); } else { changes.Confirmed.Reset = true; changes.Confirmed.SpentOutpoints.Clear(); } if (changes.HasChanges || !(await waitingTransaction)) { break; } waitingTransaction = Task.FromResult(false); //next time, will not wait } Runtime.Repository.CleanTransactions(extPubKey.ExtPubKey, cleanList); return(new FileContentResult(changes.ToBytes(), "application/octet-stream")); }