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
///