From ce8e74739f1fc8f2154451cd312ec7a1e282b623 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20A=2EP?= <53834183+Jossec101@users.noreply.github.com> Date: Fri, 13 Jan 2023 10:47:51 +0100 Subject: [PATCH] Fixed #65 (#66) * Fixed #65 Now withdrawals PSBTs will support the Global Xpubs field and include derivation path information for each input --- src/Helpers/LightningHelper.cs | 58 ++++++++++++++++++++++++++++++++ src/Services/BitcoinService.cs | 3 ++ src/Services/LightningService.cs | 42 +++-------------------- 3 files changed, 66 insertions(+), 37 deletions(-) diff --git a/src/Helpers/LightningHelper.cs b/src/Helpers/LightningHelper.cs index e8af484a..ba93a382 100644 --- a/src/Helpers/LightningHelper.cs +++ b/src/Helpers/LightningHelper.cs @@ -1,9 +1,11 @@ using AutoMapper; using FundsManager.Data.Models; +using FundsManager.Services; using Google.Protobuf; using NBitcoin; using NBXplorer; using NBXplorer.Models; +using Key = FundsManager.Data.Models.Key; namespace FundsManager.Helpers { @@ -18,6 +20,62 @@ public static void RemoveDuplicateUTXOs(this UTXOChanges utxoChanges) utxoChanges.Confirmed.UTXOs = utxoChanges.Confirmed.UTXOs.DistinctBy(x => x.Outpoint).ToList(); utxoChanges.Unconfirmed.UTXOs = utxoChanges.Unconfirmed.UTXOs.DistinctBy(x => x.Outpoint).ToList(); } + + /// + /// Helper that adds global xpubs fields and derivation paths in the PSBT inputs to allow hardware wallets or the remote signer to find the right key to sign + /// + /// + /// + /// + /// + /// + /// + public static void AddDerivationData(ILogger logger, IEnumerable keys , (PSBT?, bool) result, List selectedUtxOs, + List multisigCoins) + { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + if (keys == null) throw new ArgumentNullException(nameof(keys)); + if (selectedUtxOs == null) throw new ArgumentNullException(nameof(selectedUtxOs)); + if (multisigCoins == null) throw new ArgumentNullException(nameof(multisigCoins)); + + var nbXplorerNetwork = CurrentNetworkHelper.GetCurrentNetwork(); + foreach (var key in keys) + { + var bitcoinExtPubKey = new BitcoinExtPubKey(key.XPUB, nbXplorerNetwork); + + var masterFingerprint = HDFingerprint.Parse(key.MasterFingerprint); + var rootedKeyPath = new RootedKeyPath(masterFingerprint, new KeyPath(key.Path)); + + //Global xpubs field addition + result.Item1.GlobalXPubs.Add( + bitcoinExtPubKey, + rootedKeyPath + ); + + foreach (var selectedUtxo in selectedUtxOs) + { + var utxoDerivationPath = KeyPath.Parse(key.Path).Derive(selectedUtxo.KeyPath); + var derivedPubKey = bitcoinExtPubKey.Derive(selectedUtxo.KeyPath).GetPublicKey(); + + var input = result.Item1.Inputs.FirstOrDefault(input => + input?.GetCoin()?.Outpoint == selectedUtxo.Outpoint); + var addressRootedKeyPath = new RootedKeyPath(masterFingerprint, utxoDerivationPath); + var multisigCoin = multisigCoins.FirstOrDefault(x => x.Outpoint == selectedUtxo.Outpoint); + + if (multisigCoin != null && input != null && multisigCoin.Redeem.GetAllPubKeys().Contains(derivedPubKey)) + { + input.AddKeyPath(derivedPubKey, addressRootedKeyPath); + } + else + { + var errorMessage = $"Invalid derived pub key for utxo:{selectedUtxo.Outpoint}"; + logger.LogError(errorMessage); + throw new ArgumentException(errorMessage, nameof(derivedPubKey)); + } + } + } + } + /// /// Generates the ExplorerClient for using nbxplorer based on a bitcoin networy type diff --git a/src/Services/BitcoinService.cs b/src/Services/BitcoinService.cs index bdcde6b6..f825fa61 100644 --- a/src/Services/BitcoinService.cs +++ b/src/Services/BitcoinService.cs @@ -193,6 +193,9 @@ public BitcoinService(ILogger logger, } result.Item1 = builder.BuildPSBT(false); + + //Additional fields to support PSBT signing with a HW or the Remote Signer + LightningHelper.AddDerivationData(_logger,walletWithdrawalRequest.Wallet.Keys, result, selectedUTXOs, scriptCoins); } catch (Exception e) { diff --git a/src/Services/LightningService.cs b/src/Services/LightningService.cs index d43d34bc..cc216aac 100644 --- a/src/Services/LightningService.cs +++ b/src/Services/LightningService.cs @@ -21,6 +21,7 @@ using Microsoft.EntityFrameworkCore; using AddressType = Lnrpc.AddressType; using Channel = FundsManager.Data.Models.Channel; +using Key = FundsManager.Data.Models.Key; using ListUnspentRequest = Lnrpc.ListUnspentRequest; using Transaction = NBitcoin.Transaction; using UTXO = NBXplorer.Models.UTXO; @@ -822,47 +823,14 @@ private void CancelPendingChannel(Node source, Lightning.LightningClient client, result.Item1 = builder.BuildPSBT(false); - //TODO Remove hack when https://github.com/MetacoSA/NBitcoin/issues/1112 is fixed + //Hack, see https://github.com/MetacoSA/NBitcoin/issues/1112 for details foreach (var input in result.Item1.Inputs) { input.SighashType = SigHash.None; } - //Additional fields to support PSBT signing with a HW - foreach (var key in channelOperationRequest.Wallet.Keys) - { - var bitcoinExtPubKey = new BitcoinExtPubKey(key.XPUB, nbXplorerNetwork); - - var masterFingerprint = HDFingerprint.Parse(key.MasterFingerprint); - var rootedKeyPath = new RootedKeyPath(masterFingerprint, new KeyPath(key.Path)); - - //Global xpubs field addition - result.Item1.GlobalXPubs.Add( - bitcoinExtPubKey, - rootedKeyPath - ); - - foreach (var selectedUtxo in selectedUtxOs) - { - var utxoDerivationPath = KeyPath.Parse(key.Path).Derive(selectedUtxo.KeyPath); - var derivedPubKey = bitcoinExtPubKey.Derive(selectedUtxo.KeyPath).GetPublicKey(); - - var input = result.Item1.Inputs.FirstOrDefault(input => input?.GetCoin()?.Outpoint == selectedUtxo.Outpoint); - var addressRootedKeyPath = new RootedKeyPath(masterFingerprint, utxoDerivationPath); - var multisigCoin = multisigCoins.FirstOrDefault(x => x.Outpoint == selectedUtxo.Outpoint); - - if (multisigCoin != null && input != null && multisigCoin.Redeem.GetAllPubKeys().Contains(derivedPubKey)) - { - input.AddKeyPath(derivedPubKey, addressRootedKeyPath); - } - else - { - var errorMessage = $"Invalid derived pub key for utxo:{selectedUtxo.Outpoint}"; - _logger.LogError(errorMessage); - throw new ArgumentException(errorMessage, nameof(derivedPubKey)); - } - } - } + //Additional fields to support PSBT signing with a HW or the Remote Signer + LightningHelper.AddDerivationData(_logger,channelOperationRequest.Wallet.Keys, result, selectedUtxOs, multisigCoins); } catch (Exception e) { @@ -880,7 +848,6 @@ private void CancelPendingChannel(Node source, Lightning.LightningClient client, } // The template PSBT is saved for later reuse - if (result.Item1 != null) { var psbt = new ChannelOperationRequestPSBT @@ -903,6 +870,7 @@ private void CancelPendingChannel(Node source, Lightning.LightningClient client, return result; } + /// /// Gets UTXOs confirmed from the wallet of the request ///