mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Save the fingerprint of the root of LedgerWallet, and use it. Simplify HardwareWallet
This commit is contained in:
@@ -43,6 +43,14 @@ namespace BTCPayServer.Controllers
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
class GetXPubs
|
||||
{
|
||||
public BitcoinExtPubKey ExtPubKey { get; set; }
|
||||
public DerivationStrategyBase DerivationScheme { get; set; }
|
||||
public HDFingerprint RootFingerprint { get; set; }
|
||||
public string Source { get; set; }
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{storeId}/derivations/{cryptoCode}/ledger/ws")]
|
||||
public async Task<IActionResult> AddDerivationSchemeLedger(
|
||||
@@ -73,7 +81,18 @@ namespace BTCPayServer.Controllers
|
||||
var k = KeyPath.Parse(keyPath);
|
||||
if (k.Indexes.Length == 0)
|
||||
throw new FormatException("Invalid key path");
|
||||
var getxpubResult = await hw.GetExtPubKey(network, k, normalOperationTimeout.Token);
|
||||
|
||||
var getxpubResult = new GetXPubs();
|
||||
getxpubResult.ExtPubKey = await hw.GetExtPubKey(network, k, normalOperationTimeout.Token);
|
||||
var segwit = network.NBitcoinNetwork.Consensus.SupportSegwit;
|
||||
var derivation = new DerivationStrategyFactory(network.NBitcoinNetwork).CreateDirectDerivationStrategy(getxpubResult.ExtPubKey, new DerivationStrategyOptions()
|
||||
{
|
||||
P2SH = segwit,
|
||||
Legacy = !segwit
|
||||
});
|
||||
getxpubResult.DerivationScheme = derivation;
|
||||
getxpubResult.RootFingerprint = (await hw.GetExtPubKey(network, new KeyPath(), normalOperationTimeout.Token)).ExtPubKey.PubKey.GetHDFingerPrint();
|
||||
getxpubResult.Source = hw.Device;
|
||||
result = getxpubResult;
|
||||
}
|
||||
}
|
||||
@@ -87,7 +106,7 @@ namespace BTCPayServer.Controllers
|
||||
if (result != null)
|
||||
{
|
||||
UTF8Encoding UTF8NOBOM = new UTF8Encoding(false);
|
||||
var bytes = UTF8NOBOM.GetBytes(JsonConvert.SerializeObject(result, MvcJsonOptions.Value.SerializerSettings));
|
||||
var bytes = UTF8NOBOM.GetBytes(JsonConvert.SerializeObject(result, network.NBXplorerNetwork.JsonSerializerSettings));
|
||||
await webSocket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, new CancellationTokenSource(2000).Token);
|
||||
}
|
||||
}
|
||||
@@ -185,6 +204,8 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
strategy = newStrategy;
|
||||
strategy.AccountKeyPath = vm.KeyPath == null ? null : KeyPath.Parse(vm.KeyPath);
|
||||
strategy.RootFingerprint = string.IsNullOrEmpty(vm.RootFingerprint)? (HDFingerprint?)null : new HDFingerprint(NBitcoin.DataEncoders.Encoders.Hex.DecodeData(vm.RootFingerprint));
|
||||
strategy.Source = vm.Source;
|
||||
vm.DerivationScheme = strategy.AccountDerivation.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -560,9 +560,9 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
|
||||
var strategy = GetDirectDerivationStrategy(derivationSettings.AccountDerivation);
|
||||
// Some deployment have the wallet root key path saved in the store blob
|
||||
// If it does, we only have to make 1 call to the hw to check if it can sign the given strategy,
|
||||
if (derivationSettings.AccountKeyPath == null || !await hw.CanSign(network, strategy, derivationSettings.AccountKeyPath, normalOperationTimeout.Token))
|
||||
|
||||
// Some deployment does not have the AccountKeyPath set, let's fix this...
|
||||
if (derivationSettings.AccountKeyPath == null)
|
||||
{
|
||||
// If the saved wallet key path is not present or incorrect, let's scan the wallet to see if it can sign strategy
|
||||
var foundKeyPath = await hw.FindKeyPath(network, strategy, normalOperationTimeout.Token);
|
||||
@@ -572,7 +572,34 @@ namespace BTCPayServer.Controllers
|
||||
storeData.SetSupportedPaymentMethod(derivationSettings);
|
||||
await Repository.UpdateStore(storeData);
|
||||
}
|
||||
// If it has the AccountKeyPath, let's check if we opened the right ledger
|
||||
else
|
||||
{
|
||||
// Checking if ledger is right with the RootFingerprint is faster as it does not need to make a query to the parent xpub,
|
||||
// but some deployment does not have it, so let's use AccountKeyPath instead
|
||||
if (derivationSettings.RootFingerprint == null)
|
||||
{
|
||||
|
||||
var actualPubKey = await hw.GetExtPubKey(network, derivationSettings.AccountKeyPath, normalOperationTimeout.Token);
|
||||
if (!derivationSettings.AccountDerivation.GetExtPubKeys().Any(p => p.GetPublicKey() == actualPubKey.GetPublicKey()))
|
||||
throw new HardwareWalletException($"This store is not configured to use this ledger");
|
||||
}
|
||||
// We have the root fingerprint, we can check the root from it
|
||||
else
|
||||
{
|
||||
var actualPubKey = await hw.GetExtPubKey(network, new KeyPath(), normalOperationTimeout.Token);
|
||||
if (actualPubKey.GetPublicKey().GetHDFingerPrint() != derivationSettings.RootFingerprint.Value)
|
||||
throw new HardwareWalletException($"This store is not configured to use this ledger");
|
||||
}
|
||||
}
|
||||
|
||||
// Some deployment does not have the RootFingerprint set, let's fix this...
|
||||
if (derivationSettings.RootFingerprint == null)
|
||||
{
|
||||
derivationSettings.RootFingerprint = (await hw.GetExtPubKey(network, new KeyPath(), normalOperationTimeout.Token)).GetPublicKey().GetHDFingerPrint();
|
||||
storeData.SetSupportedPaymentMethod(derivationSettings);
|
||||
await Repository.UpdateStore(storeData);
|
||||
}
|
||||
|
||||
var psbt = await CreatePSBT(network, derivationSettings, model, normalOperationTimeout.Token);
|
||||
signTimeout.CancelAfter(TimeSpan.FromMinutes(5));
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
|
||||
public string CryptoCode { get; set; }
|
||||
public string KeyPath { get; set; }
|
||||
public string RootFingerprint { get; set; }
|
||||
[Display(Name = "Hint address")]
|
||||
public string HintAddress { get; set; }
|
||||
public bool Confirmation { get; set; }
|
||||
@@ -37,5 +38,6 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
[Display(Name = "Coldcard Wallet File")]
|
||||
public IFormFile ColdcardPublicFile{ get; set; }
|
||||
public string Config { get; set; }
|
||||
public string Source { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,9 @@ namespace BTCPayServer.Services
|
||||
return _Ledger;
|
||||
}
|
||||
}
|
||||
|
||||
public string Device => "Ledger wallet";
|
||||
|
||||
WebSocketTransport _Transport = null;
|
||||
public HardwareWalletService(System.Net.WebSockets.WebSocket ledgerWallet)
|
||||
{
|
||||
@@ -89,19 +92,11 @@ namespace BTCPayServer.Services
|
||||
return new LedgerTestResult() { Success = true };
|
||||
}
|
||||
|
||||
public async Task<GetXPubResult> GetExtPubKey(BTCPayNetwork network, KeyPath keyPath, CancellationToken cancellation)
|
||||
public async Task<BitcoinExtPubKey> GetExtPubKey(BTCPayNetwork network, KeyPath keyPath, CancellationToken cancellation)
|
||||
{
|
||||
if (network == null)
|
||||
throw new ArgumentNullException(nameof(network));
|
||||
|
||||
var segwit = network.NBitcoinNetwork.Consensus.SupportSegwit;
|
||||
var pubkey = await GetExtPubKey(Ledger, network, keyPath, false, cancellation);
|
||||
var derivation = new DerivationStrategyFactory(network.NBitcoinNetwork).CreateDirectDerivationStrategy(pubkey, new DerivationStrategyOptions()
|
||||
{
|
||||
P2SH = segwit,
|
||||
Legacy = !segwit
|
||||
});
|
||||
return new GetXPubResult() { ExtPubKey = derivation.ToString(), KeyPath = keyPath };
|
||||
return await GetExtPubKey(Ledger, network, keyPath, false, cancellation);
|
||||
}
|
||||
|
||||
private static async Task<BitcoinExtPubKey> GetExtPubKey(LedgerClient ledger, BTCPayNetwork network, KeyPath account, bool onlyChaincode, CancellationToken cancellation)
|
||||
@@ -118,8 +113,12 @@ namespace BTCPayServer.Services
|
||||
if (network.NBitcoinNetwork.NetworkType == NetworkType.Mainnet)
|
||||
throw new Exception($"The opened ledger app does not seems to support {network.NBitcoinNetwork.Name}.");
|
||||
}
|
||||
var fingerprint = onlyChaincode ? default : (await ledger.GetWalletPubKeyAsync(account.Parent, cancellation: cancellation)).UncompressedPublicKey.Compress().GetHDFingerPrint();
|
||||
var extpubkey = new ExtPubKey(pubKey.UncompressedPublicKey.Compress(), pubKey.ChainCode, (byte)account.Indexes.Length, fingerprint, account.Indexes.Last()).GetWif(network.NBitcoinNetwork);
|
||||
var parentFP = onlyChaincode || account.Indexes.Length == 0 ? default : (await ledger.GetWalletPubKeyAsync(account.Parent, cancellation: cancellation)).UncompressedPublicKey.Compress().GetHDFingerPrint();
|
||||
var extpubkey = new ExtPubKey(pubKey.UncompressedPublicKey.Compress(),
|
||||
pubKey.ChainCode,
|
||||
(byte)account.Indexes.Length,
|
||||
parentFP,
|
||||
account.Indexes.Length == 0 ? 0 : account.Indexes.Last()).GetWif(network.NBitcoinNetwork);
|
||||
return extpubkey;
|
||||
}
|
||||
catch (FormatException)
|
||||
@@ -128,12 +127,6 @@ namespace BTCPayServer.Services
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> CanSign(BTCPayNetwork network, DirectDerivationStrategy strategy, KeyPath keyPath, CancellationToken cancellation)
|
||||
{
|
||||
var hwKey = await GetExtPubKey(Ledger, network, keyPath, true, cancellation);
|
||||
return hwKey.ExtPubKey.PubKey == strategy.Root.PubKey;
|
||||
}
|
||||
|
||||
public async Task<KeyPath> FindKeyPath(BTCPayNetwork network, DirectDerivationStrategy directStrategy, CancellationToken cancellation)
|
||||
{
|
||||
List<KeyPath> derivations = new List<KeyPath>();
|
||||
@@ -218,11 +211,4 @@ namespace BTCPayServer.Services
|
||||
public bool Success { get; set; }
|
||||
public string Error { get; set; }
|
||||
}
|
||||
|
||||
public class GetXPubResult
|
||||
{
|
||||
public string ExtPubKey { get; set; }
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.KeyPathJsonConverter))]
|
||||
public KeyPath KeyPath { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,8 @@
|
||||
</div>
|
||||
<input id="CryptoCurrency" asp-for="CryptoCode" type="hidden" />
|
||||
<input id="KeyPath" asp-for="KeyPath" type="hidden" />
|
||||
<input id="Source" asp-for="Source" type="hidden" />
|
||||
<input id="RootFingerprint" asp-for="RootFingerprint" type="hidden" />
|
||||
<input id="Config" asp-for="Config" type="hidden" />
|
||||
<div class="form-group">
|
||||
<label asp-for="DerivationScheme"></label>
|
||||
@@ -66,7 +68,6 @@
|
||||
</p>
|
||||
<div id="ledger-info" class="form-text text-muted display-when-ledger-connected">
|
||||
<span>A ledger wallet is detected, which account do you want to use? No need to paste manually xpub if your ledger device was detected. Just select derivation scheme from the list bellow and xpub will automatically populate.</span>
|
||||
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<button type="button" class="btn btn-primary mr-2 " data-toggle="modal" data-target="#coldcardimport">
|
||||
@@ -84,10 +85,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<span>BTCPay format memo</span>
|
||||
@@ -140,6 +139,8 @@
|
||||
</div>
|
||||
<input type="hidden" asp-for="Confirmation" />
|
||||
<input id="KeyPath" asp-for="KeyPath" type="hidden" />
|
||||
<input id="Source" asp-for="Source" type="hidden" />
|
||||
<input id="RootFingerprint" asp-for="RootFingerprint" type="hidden" />
|
||||
<input type="hidden" asp-for="DerivationScheme" />
|
||||
<input type="hidden" asp-for="Enabled" />
|
||||
<input id="Config" asp-for="Config" type="hidden" />
|
||||
|
||||
@@ -48,7 +48,9 @@
|
||||
|
||||
showFeedback("ledger-info");
|
||||
|
||||
$("#DerivationScheme").val(result.extPubKey);
|
||||
$("#DerivationScheme").val(result.derivationScheme);
|
||||
$("#RootFingerprint").val(result.rootFingerprint);
|
||||
$("#Source").val(result.source);
|
||||
$("#DerivationSchemeFormat").val("BTCPay");
|
||||
$("#KeyPath").val(keypath);
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user