internal PSBTHDKeyMatch(T psbtCoin, IHDKey accountKey, KeyPath addressKeyPath, KeyValuePair <PubKey, RootedKeyPath> kv) : base(psbtCoin, accountKey, addressKeyPath, kv) { if (psbtCoin == null) { throw new ArgumentNullException(nameof(psbtCoin)); } _Coin = psbtCoin; }
public HDKeyScriptPubKey(IHDKey hdKey, ScriptPubKeyType type) { if (hdKey == null) { throw new ArgumentNullException(nameof(hdKey)); } this.hdKey = hdKey; this.type = type; }
/// <summary> /// Filter the keys which contains the <paramref name="accountKey"/> and <paramref name="accountKeyPath"/>. /// </summary> /// <param name="accountKey">The account key that will be used to sign (ie. 49'/0'/0')</param> /// <param name="accountKeyPath">The account key path</param> /// <returns>HD Keys matching master root key</returns> public IEnumerable <PSBTHDKeyMatch> HDKeysFor(IHDKey accountKey, RootedKeyPath accountKeyPath = null) { if (accountKey == null) { throw new ArgumentNullException(nameof(accountKey)); } accountKey = accountKey.AsHDKeyCache(); return(Inputs.HDKeysFor(accountKey, accountKeyPath).OfType <PSBTHDKeyMatch>().Concat(Outputs.HDKeysFor(accountKey, accountKeyPath))); }
public async Task <PSBT> RequestPayjoin(PSBT originalTx, IHDKey accountKey, RootedKeyPath rootedKeyPath, HdPubKey changeHdPubKey, CancellationToken cancellationToken) { if (originalTx.IsAllFinalized()) { throw new InvalidOperationException("The original PSBT should not be finalized."); } var optionalParameters = new PayjoinClientParameters(); if (changeHdPubKey is { })
/// <summary> /// Deriving an HDKey is normally time consuming, this wrap the IHDKey in a new HD object which can cache derivations /// </summary> /// <param name="hdkey">The hdKey to wrap</param> /// <returns>An hdkey which cache derivations, of the parameter if it is already itself a cache</returns> public static IHDKey AsHDKeyCache(this IHDKey hdkey) { if (hdkey == null) { throw new ArgumentNullException(nameof(hdkey)); } if (hdkey is HDKeyCache c) { return(c); } return(new HDKeyCache(hdkey)); }
public RootedKeyPath(IHDKey masterKey, KeyPath keyPath) { if (masterKey == null) { throw new ArgumentNullException(nameof(masterKey)); } if (keyPath == null) { throw new ArgumentNullException(nameof(keyPath)); } _KeyPath = keyPath; _MasterFingerprint = masterKey.GetPublicKey().GetHDFingerPrint(); }
/// <summary> /// Get the balance change if you were signing this transaction. /// </summary> /// <param name="accountKey">The account key that will be used to sign (ie. 49'/0'/0')</param> /// <param name="masterFingerprint">The fingerprint of the master root key</param> /// <returns>The balance change</returns> public Money GetBalance(HDFingerprint?masterFingerprint, IHDKey accountKey) { if (accountKey == null) { throw new ArgumentNullException(nameof(accountKey)); } Money total = Money.Zero; foreach (var o in CoinsFor(masterFingerprint, accountKey)) { var amount = o.GetCoin()?.Amount; if (amount == null) { continue; } total += o is PSBTInput ? -amount : amount; } return(total); }
/// <summary> /// Get the balance change if you were signing this transaction. /// </summary> /// <param name="accountHDScriptPubKey">The hdScriptPubKey used to generate addresses</param> /// <param name="accountKey">The account key that will be used to sign (ie. 49'/0'/0')</param> /// <param name="accountKeyPath">The account key path</param> /// <returns>The balance change</returns> public Money GetBalance(IHDScriptPubKey accountHDScriptPubKey, IHDKey accountKey, RootedKeyPath accountKeyPath = null) { if (accountHDScriptPubKey == null) { throw new ArgumentNullException(nameof(accountHDScriptPubKey)); } Money total = Money.Zero; foreach (var o in CoinsFor(accountHDScriptPubKey, accountKey, accountKeyPath)) { var amount = o.GetCoin()?.Amount; if (amount == null) { continue; } total += o is PSBTInput ? -amount : amount; } return(total); }
/// <summary> /// Add keypath information to this PSBT for each input or output involving it /// </summary> /// <param name="masterKey">The master key of the keypaths</param> /// <param name="paths">The path of the public keys with their expected scriptPubKey</param> /// <returns>This PSBT</returns> public PSBT AddKeyPath(IHDKey masterKey, params Tuple <KeyPath, Script>[] paths) { if (masterKey == null) { throw new ArgumentNullException(nameof(masterKey)); } if (paths == null) { throw new ArgumentNullException(nameof(paths)); } masterKey = masterKey.AsHDKeyCache(); foreach (var path in paths) { var key = masterKey.Derive(path.Item1); AddKeyPath(masterKey.GetPublicKey().GetHDFingerPrint(), key.GetPublicKey(), path.Item1, path.Item2); } return(this); }
public void TrySign(IHDKey masterKey, SigHash sigHash = SigHash.All) { if (masterKey == null) { throw new ArgumentNullException(nameof(masterKey)); } var cache = masterKey.AsHDKeyCache(); foreach (var hdk in this.HDKeysFor(masterKey)) { if (((HDKeyCache)cache.Derive(hdk.KeyPath)).Inner is ExtKey k) { Sign(k.PrivateKey, sigHash); } else { throw new ArgumentException(paramName: nameof(masterKey), message: "This should be a private key"); } } }
/// <summary> /// Sign all inputs which derive addresses from <paramref name="accountHDScriptPubKey"/> and that need to be signed by <paramref name="accountKey"/>. /// </summary> /// <param name="accountHDScriptPubKey">The address generator</param> /// <param name="accountKey">The account key with which to sign</param> /// <param name="accountKeyPath">The account key path (eg. [masterFP]/49'/0'/0')</param> /// <param name="sigHash">The SigHash</param> /// <returns>This PSBT</returns> public PSBT SignAll(IHDScriptPubKey accountHDScriptPubKey, IHDKey accountKey, RootedKeyPath accountKeyPath, SigHash sigHash = SigHash.All) { if (accountKey == null) { throw new ArgumentNullException(nameof(accountKey)); } if (accountHDScriptPubKey == null) { throw new ArgumentNullException(nameof(accountHDScriptPubKey)); } accountHDScriptPubKey = accountHDScriptPubKey.AsHDKeyCache(); accountKey = accountKey.AsHDKeyCache(); Money total = Money.Zero; foreach (var o in Inputs.CoinsFor(accountHDScriptPubKey, accountKey, accountKeyPath)) { o.TrySign(accountHDScriptPubKey, accountKey, accountKeyPath, sigHash); } return(this); }
public PSBT SignAll(IHDKey masterKey, SigHash sigHash = SigHash.All) { if (masterKey == null) { throw new ArgumentNullException(nameof(masterKey)); } var cache = masterKey.AsHDKeyCache(); foreach (var o in Inputs.HDKeysFor(masterKey)) { if (((HDKeyCache)cache.Derive(o.KeyPath)).Inner is ExtKey k) { o.Coin.Sign(k.PrivateKey, sigHash); } else { throw new ArgumentException(paramName: nameof(masterKey), message: "This should be a private key"); } } return(this); }
/// <summary> /// Rebase the keypaths. /// If a PSBT updater only know the child HD public key but not the root one, another updater knowing the parent master key it is based on /// can rebase the paths. If the PSBT is all finalized this operation is a no-op /// </summary> /// <param name="accountKey">The current account key</param> /// <param name="newRoot">The KeyPath with the fingerprint of the new root key</param> /// <returns>This PSBT</returns> public PSBT RebaseKeyPaths(IHDKey accountKey, RootedKeyPath newRoot) { if (accountKey == null) { throw new ArgumentNullException(nameof(accountKey)); } if (newRoot == null) { throw new ArgumentNullException(nameof(newRoot)); } if (IsAllFinalized()) { return(this); } accountKey = accountKey.AsHDKeyCache(); var accountKeyFP = accountKey.GetPublicKey().GetHDFingerPrint(); foreach (var o in HDKeysFor(accountKey).GroupBy(c => c.Coin)) { if (o.Key is PSBTInput i && i.IsFinalized()) { continue; } foreach (var keyPath in o) { o.Key.HDKeyPaths.Remove(keyPath.PubKey); o.Key.HDKeyPaths.Add(keyPath.PubKey, newRoot.Derive(keyPath.RootedKeyPath.KeyPath)); } } foreach (var xpub in GlobalXPubs.ToList()) { if (xpub.Key.ExtPubKey.PubKey == accountKey.GetPublicKey()) { GlobalXPubs.Remove(xpub.Key); GlobalXPubs.Add(xpub.Key, newRoot.Derive(xpub.Value.KeyPath)); } } return(this); }
public static IHDKey Derive(this IHDKey hdkey, KeyPath keyPath) { if (hdkey == null) { throw new ArgumentNullException(nameof(hdkey)); } if (keyPath == null) { throw new ArgumentNullException(nameof(keyPath)); } if (keyPath.Indexes.Length == 0) { return(hdkey); } var key = hdkey; foreach (var index in keyPath.Indexes) { key = key.Derive(index); } return(key); }
/// <summary> /// Rebase the keypaths. /// If a PSBT updater only know the child HD public key but not the root one, another updater knowing the parent master key it is based on /// can rebase the paths. /// </summary> /// <param name="accountKey">The current account key</param> /// <param name="accountKeyPath">The path from the master key to the accountKey</param> /// <param name="masterFingerprint">The master key fingerprint</param> /// <returns></returns> public PSBT RebaseKeyPaths(IHDKey accountKey, KeyPath accountKeyPath, HDFingerprint masterFingerprint) { if (accountKey == null) { throw new ArgumentNullException(nameof(accountKey)); } if (accountKeyPath == null) { throw new ArgumentNullException(nameof(accountKeyPath)); } accountKey = accountKey.AsHDKeyCache(); var accountKeyFP = accountKey.GetPublicKey().GetHDFingerPrint(); foreach (var o in HDKeysFor(null, accountKey).GroupBy(c => c.Coin)) { foreach (var keyPath in o) { o.Key.HDKeyPaths.Remove(keyPath.PubKey); o.Key.HDKeyPaths.Add(keyPath.PubKey, Tuple.Create(masterFingerprint, accountKeyPath.Derive(keyPath.KeyPath))); } } return(this); }
/// <summary> /// Rebase the keypaths. /// If a PSBT updater only know the child HD public key but not the root one, another updater knowing the parent master key it is based on /// can rebase the paths. /// </summary> /// <param name="accountKey">The current account key</param> /// <param name="newRoot">The KeyPath with the fingerprint of the new root key</param> /// <returns>This PSBT</returns> public PSBT RebaseKeyPaths(IHDKey accountKey, RootedKeyPath newRoot) { if (accountKey == null) { throw new ArgumentNullException(nameof(accountKey)); } if (newRoot == null) { throw new ArgumentNullException(nameof(newRoot)); } accountKey = accountKey.AsHDKeyCache(); var accountKeyFP = accountKey.GetPublicKey().GetHDFingerPrint(); foreach (var o in HDKeysFor(accountKey).GroupBy(c => c.Coin)) { foreach (var keyPath in o) { o.Key.HDKeyPaths.Remove(keyPath.PubKey); o.Key.HDKeyPaths.Add(keyPath.PubKey, newRoot.Derive(keyPath.RootedKeyPath.KeyPath)); } } return(this); }
/// <summary> /// Get the balance change if you were signing this transaction. /// </summary> /// <returns>The balance change</returns> public Money GetBalance(IHDKey masterKey) { if (masterKey == null) { throw new ArgumentNullException(nameof(masterKey)); } var masterFP = masterKey.GetPublicKey().GetHDFingerPrint(); Money total = Money.Zero; DerivationCache derivationCache = new DerivationCache(masterKey); foreach (var o in Inputs.OfType <PSBTCoin>().Concat(Outputs)) { var amount = o.GetCoin()?.Amount; if (amount == null) { continue; } if (o is PSBTInput) { amount = -amount; } foreach (var hdk in o.HDKeyPaths) { var pubkey = hdk.Key; var keyPath = hdk.Value.Item2; var fp = hdk.Value.Item1; if ((fp == masterKey.GetPublicKey().GetHDFingerPrint() || fp == default) && (derivationCache.Derive(keyPath).GetPublicKey() == pubkey)) { total += amount; } } } return(total); }
internal IEnumerable <T> GetPSBTCoins(HDFingerprint?masterFingerprint, IHDKey accountKey) { return(GetHDKeys(masterFingerprint, accountKey) .Select(c => c.Coin) .Distinct()); }
/// <summary> /// Filter the hd keys which contains a HD Key path matching this masterFingerprint/account key /// </summary> /// <param name="masterFingerprint">The master root fingerprint</param> /// <param name="accountKey">The account key (ie. 49'/0'/0')</param> /// <returns>HD Keys matching master root key</returns> public IEnumerable <PSBTHDKeyMatch <T> > HDKeysFor(HDFingerprint?masterFingerprint, IHDKey accountKey) { return(GetHDKeys(masterFingerprint, accountKey)); }
internal IEnumerable <PSBTHDKeyMatch <T> > GetHDKeys(IHDScriptPubKey hdScriptPubKey, IHDKey accountKey, RootedKeyPath accountKeyPath = null) { if (accountKey == null) { throw new ArgumentNullException(nameof(accountKey)); } accountKey = accountKey.AsHDKeyCache(); hdScriptPubKey = hdScriptPubKey?.AsHDKeyCache(); var accountFingerprint = accountKey.GetPublicKey().GetHDFingerPrint(); foreach (var c in this) { foreach (var match in c.HDKeysFor(hdScriptPubKey, accountKey, accountKeyPath, accountFingerprint)) { yield return((PSBTHDKeyMatch <T>)match); } } }
internal IEnumerable <T> GetPSBTCoins(IHDScriptPubKey accountHDScriptPubKey, IHDKey accountKey, RootedKeyPath accountKeyPath = null) { return(GetHDKeys(accountHDScriptPubKey, accountKey, accountKeyPath) .Select(c => c.Coin) .Distinct()); }
/// <summary> /// Filter the keys which contains the <paramref name="accountKey"/> and <paramref name="accountKeyPath"/>. /// </summary> /// <param name="accountKey">The account key that will be used to sign (ie. 49'/0'/0')</param> /// <param name="accountKeyPath">The account key path</param> /// <returns>HD Keys matching master root key</returns> public IEnumerable <PSBTHDKeyMatch <T> > HDKeysFor(IHDKey accountKey, RootedKeyPath accountKeyPath = null) { return(GetHDKeys(null, accountKey, accountKeyPath)); }
protected override PSBTHDKeyMatch CreateHDKeyMatch(IHDKey accountKey, KeyPath addressKeyPath, KeyValuePair <PubKey, RootedKeyPath> kv) { return(new PSBTHDKeyMatch <PSBTInput>(this, accountKey, addressKeyPath, kv)); }
public void TrySign(IHDScriptPubKey accountHDScriptPubKey, IHDKey accountKey, RootedKeyPath accountKeyPath, SigHash sigHash = SigHash.All) { TrySign(accountHDScriptPubKey, accountKey, accountKeyPath, Parent.Normalize(new SigningOptions(sigHash))); }
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); } }
internal IEnumerable <PSBTHDKeyMatch <T> > GetHDKeys(HDFingerprint?masterFingerprint, IHDKey accountKey) { if (accountKey == null) { throw new ArgumentNullException(nameof(accountKey)); } accountKey = accountKey.AsHDKeyCache(); var accountFingerprint = accountKey.GetPublicKey().GetHDFingerPrint(); foreach (var c in this) { foreach (var match in c.HDKeysFor(masterFingerprint, accountKey, accountFingerprint)) { yield return((PSBTHDKeyMatch <T>)match); } } }
/// <summary> /// Filter the coins which contains a HD Key path matching this masterFingerprint/account key /// </summary> /// <param name="masterFingerprint">The master root fingerprint</param> /// <param name="accountKey">The account key (ie. 49'/0'/0')</param> /// <returns>Inputs with HD keys matching masterFingerprint and account key</returns> public IEnumerable <T> CoinsFor(HDFingerprint?masterFingerprint, IHDKey accountKey) { return(GetPSBTCoins(masterFingerprint, accountKey)); }
public async Task <PSBT> RequestPayjoin(PSBT originalTx, IHDKey accountKey, RootedKeyPath rootedKeyPath, CancellationToken cancellationToken) { Guard.NotNull(nameof(originalTx), originalTx); if (originalTx.IsAllFinalized()) { throw new InvalidOperationException("The original PSBT should not be finalized."); } var sentBefore = -originalTx.GetBalance(ScriptPubKeyType.Segwit, accountKey, rootedKeyPath); var oldGlobalTx = originalTx.GetGlobalTransaction(); if (!originalTx.TryGetEstimatedFeeRate(out var originalFeeRate) || !originalTx.TryGetVirtualSize(out var oldVirtualSize)) { throw new ArgumentException("originalTx should have utxo information", nameof(originalTx)); } var originalFee = originalTx.GetFee(); var cloned = originalTx.Clone(); if (!cloned.TryFinalize(out var _)) { return(null); } // We make sure we don't send unnecessary information to the receiver foreach (var finalized in cloned.Inputs.Where(i => i.IsFinalized())) { finalized.ClearForFinalize(); } foreach (var output in cloned.Outputs) { output.HDKeyPaths.Clear(); } cloned.GlobalXPubs.Clear(); var request = new HttpRequestMessage(HttpMethod.Post, PaymentUrl) { Content = new StringContent(cloned.ToHex(), Encoding.UTF8, "text/plain") }; HttpResponseMessage bpuResponse = await TorHttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); if (!bpuResponse.IsSuccessStatusCode) { var errorStr = await bpuResponse.Content.ReadAsStringAsync().ConfigureAwait(false); try { var error = JObject.Parse(errorStr); throw new PayjoinReceiverException((int)bpuResponse.StatusCode, error["errorCode"].Value <string>(), error["message"].Value <string>()); } catch (JsonReaderException) { // will throw bpuResponse.EnsureSuccessStatusCode(); throw; } } var hexOrBase64 = await bpuResponse.Content.ReadAsStringAsync().ConfigureAwait(false); var newPSBT = PSBT.Parse(hexOrBase64, originalTx.Network); // Checking that the PSBT of the receiver is clean if (newPSBT.GlobalXPubs.Any()) { throw new PayjoinSenderException("GlobalXPubs should not be included in the receiver's PSBT"); } if (newPSBT.Outputs.Any(o => o.HDKeyPaths.Count != 0) || newPSBT.Inputs.Any(o => o.HDKeyPaths.Count != 0)) { throw new PayjoinSenderException("Keypath information should not be included in the receiver's PSBT"); } if (newPSBT.CheckSanity() is IList <PSBTError> errors2 && errors2.Count != 0) { throw new PayjoinSenderException($"The PSBT of the receiver is insane ({errors2[0]})"); } // Do not trust on inputs order because the payjoin server should shuffle them. foreach (var input in originalTx.Inputs) { var newInput = newPSBT.Inputs.FindIndexedInput(input.PrevOut); if (newInput is { })
public void TrySign(IHDScriptPubKey accountHDScriptPubKey, IHDKey accountKey, SigHash sigHash = SigHash.All) { TrySign(accountHDScriptPubKey, accountKey, null, sigHash); }
/// <summary> /// Filter the coins which contains the <paramref name="accountKey"/> and <paramref name="accountKeyPath"/> in the HDKeys and derive /// the same scriptPubKeys as <paramref name="accountHDScriptPubKey"/>. /// </summary> /// <param name="accountHDScriptPubKey">The hdScriptPubKey used to generate addresses</param> /// <param name="accountKey">The account key that will be used to sign (ie. 49'/0'/0')</param> /// <param name="accountKeyPath">The account key path</param> /// <returns>Inputs with HD keys matching masterFingerprint and account key</returns> public IEnumerable <T> CoinsFor(IHDScriptPubKey accountHDScriptPubKey, IHDKey accountKey, RootedKeyPath accountKeyPath = null) { return(GetPSBTCoins(accountHDScriptPubKey, accountKey, accountKeyPath)); }