mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 06:24:24 +01:00
Wallet & PSBT: Sign with seed or key (#840)
* Allow signing a PSBT with an extkey/wif or mnemonic seed * reword things * small text
This commit is contained in:
committed by
Nicolas Dorier
parent
cf436e11ae
commit
eb54a18fcd
@@ -61,22 +61,25 @@ namespace BTCPayServer.Controllers
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
if (command == null)
|
||||
switch (command)
|
||||
{
|
||||
case null:
|
||||
vm.Decoded = psbt.ToString();
|
||||
vm.FileName = string.Empty;
|
||||
return View(vm);
|
||||
}
|
||||
else if (command == "ledger")
|
||||
{
|
||||
case "ledger":
|
||||
return ViewWalletSendLedger(psbt);
|
||||
}
|
||||
else if (command == "broadcast")
|
||||
{
|
||||
if (!psbt.IsAllFinalized() && !psbt.TryFinalize(out var errors))
|
||||
case "seed":
|
||||
return RedirectToAction("SignWithSeed", new
|
||||
{
|
||||
psbt = psbt.ToBase64(),
|
||||
walletId,
|
||||
send = false
|
||||
});
|
||||
case "broadcast" when !psbt.IsAllFinalized() && !psbt.TryFinalize(out var errors):
|
||||
return ViewPSBT(psbt, errors);
|
||||
}
|
||||
case "broadcast":
|
||||
{
|
||||
var transaction = psbt.ExtractTransaction();
|
||||
try
|
||||
{
|
||||
@@ -92,17 +95,15 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
return await RedirectToWalletTransaction(walletId, transaction);
|
||||
}
|
||||
else if (command == "combine")
|
||||
{
|
||||
case "combine":
|
||||
ModelState.Remove(nameof(vm.PSBT));
|
||||
return View(nameof(WalletPSBTCombine), new WalletPSBTCombineViewModel() { OtherPSBT = psbt.ToBase64() });
|
||||
}
|
||||
else if (command == "save-psbt")
|
||||
{
|
||||
case "save-psbt":
|
||||
return FilePSBT(psbt, vm.FileName);
|
||||
}
|
||||
default:
|
||||
return View(vm);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{walletId}/psbt/ready")]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.WebSockets;
|
||||
@@ -21,6 +22,7 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Internal;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
@@ -245,16 +247,22 @@ namespace BTCPayServer.Controllers
|
||||
return View(vm);
|
||||
}
|
||||
derivationScheme.RebaseKeyPaths(psbt.PSBT);
|
||||
if (command == "ledger")
|
||||
switch (command)
|
||||
{
|
||||
case "ledger":
|
||||
return ViewWalletSendLedger(psbt.PSBT, psbt.ChangeAddress);
|
||||
}
|
||||
else if (command == "analyze-psbt")
|
||||
case "seed":
|
||||
return RedirectToAction("SignWithSeed", new
|
||||
{
|
||||
psbt = psbt.PSBT.ToBase64(),
|
||||
send = true
|
||||
});
|
||||
case "analyze-psbt":
|
||||
return ViewPSBT(psbt.PSBT, $"Send-{vm.Amount.Value}-{network.CryptoCode}-to-{destination[0].ToString()}.psbt");
|
||||
}
|
||||
default:
|
||||
return View(vm);
|
||||
}
|
||||
}
|
||||
|
||||
private ViewResult ViewWalletSendLedger(PSBT psbt, BitcoinAddress hintChange = null)
|
||||
{
|
||||
@@ -267,6 +275,90 @@ namespace BTCPayServer.Controllers
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("{walletId}/psbt/seed")]
|
||||
public IActionResult SignWithSeed([ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId,string psbt, bool send)
|
||||
{
|
||||
return View(new SignWithSeedViewModel()
|
||||
{
|
||||
PSBT = psbt,
|
||||
Send = send
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("{walletId}/psbt/seed")]
|
||||
public async Task<IActionResult> SignWithSeed([ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId, SignWithSeedViewModel viewModel)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(viewModel);
|
||||
}
|
||||
var network = NetworkProvider.GetNetwork(walletId.CryptoCode);
|
||||
if (network == null)
|
||||
throw new FormatException("Invalid value for crypto code");
|
||||
var isMnemonic = false;
|
||||
var isExtKey = false;
|
||||
ExtKey extKey = null;
|
||||
try
|
||||
{
|
||||
var mnemonic = new Mnemonic(viewModel.SeedOrKey);
|
||||
isMnemonic = true;
|
||||
extKey = mnemonic.DeriveExtKey(viewModel.Passphrase);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
if (!isMnemonic)
|
||||
{
|
||||
try
|
||||
{
|
||||
extKey = ExtKey.Parse(viewModel.SeedOrKey, network.NBitcoinNetwork);
|
||||
isExtKey = true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (!isMnemonic && !isExtKey)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.SeedOrKey),
|
||||
"Seed or Key was not in a valid format. It is either the 12/24 words or starts with xprv");
|
||||
}
|
||||
|
||||
var psbt = PSBT.Parse(viewModel.PSBT, network.NBitcoinNetwork);
|
||||
|
||||
if (!psbt.IsReadyToSign())
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.PSBT), "PSBT is not ready to be signed");
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
|
||||
var signedpsbt = psbt.SignAll(extKey);
|
||||
|
||||
if (viewModel.Send)
|
||||
{
|
||||
return await WalletPSBTReady(walletId, new WalletPSBTReadyViewModel()
|
||||
{
|
||||
PSBT = signedpsbt.ToBase64()
|
||||
}, "broadcast");
|
||||
}
|
||||
|
||||
return RedirectToAction("WalletPSBTReady", new
|
||||
{
|
||||
walletId,
|
||||
psbt = signedpsbt.ToBase64()
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private IDestination[] ParseDestination(string destination, Network network)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace BTCPayServer.Models.WalletViewModels
|
||||
{
|
||||
public class SignWithSeedViewModel
|
||||
{
|
||||
[Required]
|
||||
public string PSBT { get; set; }
|
||||
[Required][Display(Name = "Seed(12/24 word mnemonic seed) Or HD private key(xprv...)")]
|
||||
public string SeedOrKey { get; set; }
|
||||
|
||||
[Display(Name = "Optional seed passphrase")]
|
||||
public string Passphrase { get; set; }
|
||||
|
||||
public bool Send { get; set; }
|
||||
}
|
||||
}
|
||||
35
BTCPayServer/Views/Wallets/SignWithSeed.cshtml
Normal file
35
BTCPayServer/Views/Wallets/SignWithSeed.cshtml
Normal file
@@ -0,0 +1,35 @@
|
||||
@model SignWithSeedViewModel
|
||||
@{
|
||||
Layout = "../Shared/_NavLayout.cshtml";
|
||||
ViewData["Title"] = "Sign PSBT With an HD private key or mnemonic seed";
|
||||
ViewData.SetActivePageAndTitle(Model.Send ? WalletsNavPages.Send : WalletsNavPages.PSBT);
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<form method="post">
|
||||
|
||||
<input type="hidden" asp-for="PSBT"/>
|
||||
<input type="hidden" asp-for="Send"/>
|
||||
<div class="form-group">
|
||||
<label asp-for="SeedOrKey"></label>
|
||||
<input asp-for="SeedOrKey" class="form-control"/>
|
||||
<span asp-validation-for="SeedOrKey" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label asp-for="Passphrase"></label>
|
||||
<input asp-for="Passphrase" class="form-control"/>
|
||||
<span asp-validation-for="Passphrase" class="text-danger"></span>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Sign</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<link rel="stylesheet" href="~/vendor/highlightjs/default.min.css">
|
||||
<script src="~/vendor/highlightjs/highlight.min.js"></script>
|
||||
<script>hljs.initHighlightingOnLoad();</script>
|
||||
}
|
||||
@@ -35,6 +35,7 @@
|
||||
<div class="dropdown-menu" aria-labelledby="SendMenu">
|
||||
<button name="command" type="submit" class="dropdown-item" value="ledger">... your Ledger Wallet device</button>
|
||||
<button name="command" type="submit" class="dropdown-item" value="save-psbt">... a wallet supporting PSBT files</button>
|
||||
<button name="command" type="submit" class="dropdown-item" value="seed">... an HD private key or mnemonic seed</button>
|
||||
</div>
|
||||
<button name="command" type="submit" class="btn btn-secondary" value="broadcast">Broadcast</button>
|
||||
<button name="command" type="submit" class="btn btn-secondary" value="combine">Combine with another PSBT</button>
|
||||
|
||||
@@ -89,6 +89,7 @@
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="SendMenu">
|
||||
<button name="command" type="submit" class="dropdown-item" value="ledger">... your Ledger Wallet device</button>
|
||||
<button name="command" type="submit" class="dropdown-item" value="seed">... an HD private key or mnemonic seed</button>
|
||||
<button name="command" type="submit" class="dropdown-item" value="analyze-psbt">... a wallet supporting PSBT</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user