mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-25 05:06:45 +00:00
sign psbt from a transient scanned seed (seedqr, compactseedqr, ur:crypto-seed, ur:crypto-bip39 supported)
This commit is contained in:
parent
66be5c43a6
commit
948d663fbf
6 changed files with 129 additions and 7 deletions
2
drongo
2
drongo
|
@ -1 +1 @@
|
||||||
Subproject commit eddd6406efc20b83e659d59faa16189417b0f5ed
|
Subproject commit 38deacaeec757acaaaedada7c17e9338564cb389
|
|
@ -1732,6 +1732,10 @@ public class AppController implements Initializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addTransactionTab(Transaction transaction, TransactionView initialView, Integer initialIndex) {
|
||||||
|
addTransactionTab(null, null, transaction, null, null, initialView, initialIndex);
|
||||||
|
}
|
||||||
|
|
||||||
private void addTransactionTab(BlockTransaction blockTransaction, TransactionView initialView, Integer initialIndex) {
|
private void addTransactionTab(BlockTransaction blockTransaction, TransactionView initialView, Integer initialIndex) {
|
||||||
addTransactionTab(null, null, blockTransaction.getTransaction(), null, blockTransaction, initialView, initialIndex);
|
addTransactionTab(null, null, blockTransaction.getTransaction(), null, blockTransaction, initialView, initialIndex);
|
||||||
}
|
}
|
||||||
|
@ -2588,7 +2592,11 @@ public class AppController implements Initializable {
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void viewTransaction(ViewTransactionEvent event) {
|
public void viewTransaction(ViewTransactionEvent event) {
|
||||||
if(tabs.getScene().getWindow().equals(event.getWindow())) {
|
if(tabs.getScene().getWindow().equals(event.getWindow())) {
|
||||||
addTransactionTab(event.getBlockTransaction(), event.getInitialView(), event.getInitialIndex());
|
if(event.getBlockTransaction() != null) {
|
||||||
|
addTransactionTab(event.getBlockTransaction(), event.getInitialView(), event.getInitialIndex());
|
||||||
|
} else {
|
||||||
|
addTransactionTab(event.getTransaction(), event.getInitialView(), event.getInitialIndex());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,15 +10,14 @@ import com.sparrowwallet.drongo.address.P2PKHAddress;
|
||||||
import com.sparrowwallet.drongo.address.P2SHAddress;
|
import com.sparrowwallet.drongo.address.P2SHAddress;
|
||||||
import com.sparrowwallet.drongo.address.P2WPKHAddress;
|
import com.sparrowwallet.drongo.address.P2WPKHAddress;
|
||||||
import com.sparrowwallet.drongo.crypto.*;
|
import com.sparrowwallet.drongo.crypto.*;
|
||||||
import com.sparrowwallet.drongo.policy.Policy;
|
|
||||||
import com.sparrowwallet.drongo.policy.PolicyType;
|
|
||||||
import com.sparrowwallet.drongo.protocol.Base43;
|
import com.sparrowwallet.drongo.protocol.Base43;
|
||||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||||
import com.sparrowwallet.drongo.protocol.Transaction;
|
import com.sparrowwallet.drongo.protocol.Transaction;
|
||||||
import com.sparrowwallet.drongo.psbt.PSBT;
|
import com.sparrowwallet.drongo.psbt.PSBT;
|
||||||
import com.sparrowwallet.drongo.psbt.PSBTParseException;
|
import com.sparrowwallet.drongo.psbt.PSBTParseException;
|
||||||
import com.sparrowwallet.drongo.uri.BitcoinURI;
|
import com.sparrowwallet.drongo.uri.BitcoinURI;
|
||||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
import com.sparrowwallet.drongo.wallet.DeterministicSeed;
|
||||||
|
import com.sparrowwallet.drongo.wallet.SeedQR;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.hummingbird.LegacyURDecoder;
|
import com.sparrowwallet.hummingbird.LegacyURDecoder;
|
||||||
import com.sparrowwallet.hummingbird.registry.*;
|
import com.sparrowwallet.hummingbird.registry.*;
|
||||||
|
@ -257,6 +256,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
BitcoinURI bitcoinURI;
|
BitcoinURI bitcoinURI;
|
||||||
Address address;
|
Address address;
|
||||||
ExtendedKey extendedKey;
|
ExtendedKey extendedKey;
|
||||||
|
DeterministicSeed seed;
|
||||||
try {
|
try {
|
||||||
extendedKey = ExtendedKey.fromDescriptor(qrtext);
|
extendedKey = ExtendedKey.fromDescriptor(qrtext);
|
||||||
result = new Result(extendedKey);
|
result = new Result(extendedKey);
|
||||||
|
@ -334,6 +334,22 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
//Ignore, not parseable as base43 decoded bytes
|
//Ignore, not parseable as base43 decoded bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
seed = SeedQR.getSeed(qrtext);
|
||||||
|
result = new Result(seed);
|
||||||
|
return;
|
||||||
|
} catch(Exception e) {
|
||||||
|
//Ignore, not parseable as a SeedQR
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
seed = SeedQR.getSeed(qrResult.getRawBytes());
|
||||||
|
result = new Result(seed);
|
||||||
|
return;
|
||||||
|
} catch(Exception e) {
|
||||||
|
//Ignore, not parseable as a CompactSeedQR
|
||||||
|
}
|
||||||
|
|
||||||
result = new Result(qrtext);
|
result = new Result(qrtext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -401,6 +417,14 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
CryptoAccount cryptoAccount = (CryptoAccount)ur.decodeFromRegistry();
|
CryptoAccount cryptoAccount = (CryptoAccount)ur.decodeFromRegistry();
|
||||||
List<Wallet> wallets = getWallets(cryptoAccount);
|
List<Wallet> wallets = getWallets(cryptoAccount);
|
||||||
return new Result(wallets);
|
return new Result(wallets);
|
||||||
|
} else if(urRegistryType.equals(RegistryType.CRYPTO_SEED)) {
|
||||||
|
CryptoSeed cryptoSeed = (CryptoSeed)ur.decodeFromRegistry();
|
||||||
|
DeterministicSeed seed = getSeed(cryptoSeed);
|
||||||
|
return new Result(seed);
|
||||||
|
} else if(urRegistryType.equals(RegistryType.CRYPTO_BIP39)) {
|
||||||
|
CryptoBip39 cryptoBip39 = (CryptoBip39)ur.decodeFromRegistry();
|
||||||
|
DeterministicSeed seed = getSeed(cryptoBip39);
|
||||||
|
return new Result(seed);
|
||||||
} else {
|
} else {
|
||||||
log.error("Unsupported UR type " + urRegistryType);
|
log.error("Unsupported UR type " + urRegistryType);
|
||||||
return new Result(new URException("UR type " + urRegistryType + " is not supported"));
|
return new Result(new URException("UR type " + urRegistryType + " is not supported"));
|
||||||
|
@ -523,6 +547,14 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DeterministicSeed getSeed(CryptoSeed cryptoSeed) {
|
||||||
|
return new DeterministicSeed(cryptoSeed.getSeed(), null, cryptoSeed.getBirthdate().getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
private DeterministicSeed getSeed(CryptoBip39 cryptoBip39) {
|
||||||
|
return new DeterministicSeed(cryptoBip39.getWords(), null, System.currentTimeMillis(), DeterministicSeed.Type.BIP39);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class QRScanListener implements WebcamListener {
|
private class QRScanListener implements WebcamListener {
|
||||||
|
@ -626,6 +658,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
public final ExtendedKey extendedKey;
|
public final ExtendedKey extendedKey;
|
||||||
public final OutputDescriptor outputDescriptor;
|
public final OutputDescriptor outputDescriptor;
|
||||||
public final List<Wallet> wallets;
|
public final List<Wallet> wallets;
|
||||||
|
public final DeterministicSeed seed;
|
||||||
public final String payload;
|
public final String payload;
|
||||||
public final Throwable exception;
|
public final Throwable exception;
|
||||||
|
|
||||||
|
@ -636,6 +669,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
this.extendedKey = null;
|
this.extendedKey = null;
|
||||||
this.outputDescriptor = null;
|
this.outputDescriptor = null;
|
||||||
this.wallets = null;
|
this.wallets = null;
|
||||||
|
this.seed = null;
|
||||||
this.payload = null;
|
this.payload = null;
|
||||||
this.exception = null;
|
this.exception = null;
|
||||||
}
|
}
|
||||||
|
@ -647,6 +681,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
this.extendedKey = null;
|
this.extendedKey = null;
|
||||||
this.outputDescriptor = null;
|
this.outputDescriptor = null;
|
||||||
this.wallets = null;
|
this.wallets = null;
|
||||||
|
this.seed = null;
|
||||||
this.payload = null;
|
this.payload = null;
|
||||||
this.exception = null;
|
this.exception = null;
|
||||||
}
|
}
|
||||||
|
@ -658,6 +693,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
this.extendedKey = null;
|
this.extendedKey = null;
|
||||||
this.outputDescriptor = null;
|
this.outputDescriptor = null;
|
||||||
this.wallets = null;
|
this.wallets = null;
|
||||||
|
this.seed = null;
|
||||||
this.payload = null;
|
this.payload = null;
|
||||||
this.exception = null;
|
this.exception = null;
|
||||||
}
|
}
|
||||||
|
@ -669,6 +705,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
this.extendedKey = null;
|
this.extendedKey = null;
|
||||||
this.outputDescriptor = null;
|
this.outputDescriptor = null;
|
||||||
this.wallets = null;
|
this.wallets = null;
|
||||||
|
this.seed = null;
|
||||||
this.payload = null;
|
this.payload = null;
|
||||||
this.exception = null;
|
this.exception = null;
|
||||||
}
|
}
|
||||||
|
@ -680,6 +717,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
this.extendedKey = extendedKey;
|
this.extendedKey = extendedKey;
|
||||||
this.outputDescriptor = null;
|
this.outputDescriptor = null;
|
||||||
this.wallets = null;
|
this.wallets = null;
|
||||||
|
this.seed = null;
|
||||||
this.payload = null;
|
this.payload = null;
|
||||||
this.exception = null;
|
this.exception = null;
|
||||||
}
|
}
|
||||||
|
@ -691,6 +729,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
this.extendedKey = null;
|
this.extendedKey = null;
|
||||||
this.outputDescriptor = outputDescriptor;
|
this.outputDescriptor = outputDescriptor;
|
||||||
this.wallets = null;
|
this.wallets = null;
|
||||||
|
this.seed = null;
|
||||||
this.payload = null;
|
this.payload = null;
|
||||||
this.exception = null;
|
this.exception = null;
|
||||||
}
|
}
|
||||||
|
@ -702,6 +741,19 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
this.extendedKey = null;
|
this.extendedKey = null;
|
||||||
this.outputDescriptor = null;
|
this.outputDescriptor = null;
|
||||||
this.wallets = wallets;
|
this.wallets = wallets;
|
||||||
|
this.seed = null;
|
||||||
|
this.payload = null;
|
||||||
|
this.exception = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result(DeterministicSeed seed) {
|
||||||
|
this.transaction = null;
|
||||||
|
this.psbt = null;
|
||||||
|
this.uri = null;
|
||||||
|
this.extendedKey = null;
|
||||||
|
this.outputDescriptor = null;
|
||||||
|
this.wallets = null;
|
||||||
|
this.seed = seed;
|
||||||
this.payload = null;
|
this.payload = null;
|
||||||
this.exception = null;
|
this.exception = null;
|
||||||
}
|
}
|
||||||
|
@ -713,6 +765,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
this.extendedKey = null;
|
this.extendedKey = null;
|
||||||
this.outputDescriptor = null;
|
this.outputDescriptor = null;
|
||||||
this.wallets = null;
|
this.wallets = null;
|
||||||
|
this.seed = null;
|
||||||
this.payload = payload;
|
this.payload = payload;
|
||||||
this.exception = null;
|
this.exception = null;
|
||||||
}
|
}
|
||||||
|
@ -724,6 +777,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
this.extendedKey = null;
|
this.extendedKey = null;
|
||||||
this.outputDescriptor = null;
|
this.outputDescriptor = null;
|
||||||
this.wallets = null;
|
this.wallets = null;
|
||||||
|
this.seed = null;
|
||||||
this.payload = null;
|
this.payload = null;
|
||||||
this.exception = exception;
|
this.exception = exception;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.sparrowwallet.sparrow.event;
|
package com.sparrowwallet.sparrow.event;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.protocol.Transaction;
|
||||||
import com.sparrowwallet.drongo.wallet.BlockTransaction;
|
import com.sparrowwallet.drongo.wallet.BlockTransaction;
|
||||||
import com.sparrowwallet.sparrow.transaction.TransactionView;
|
import com.sparrowwallet.sparrow.transaction.TransactionView;
|
||||||
import com.sparrowwallet.sparrow.wallet.HashIndexEntry;
|
import com.sparrowwallet.sparrow.wallet.HashIndexEntry;
|
||||||
|
@ -7,10 +8,19 @@ import javafx.stage.Window;
|
||||||
|
|
||||||
public class ViewTransactionEvent {
|
public class ViewTransactionEvent {
|
||||||
private final Window window;
|
private final Window window;
|
||||||
|
private final Transaction transaction;
|
||||||
private final BlockTransaction blockTransaction;
|
private final BlockTransaction blockTransaction;
|
||||||
private final TransactionView initialView;
|
private final TransactionView initialView;
|
||||||
private final Integer initialIndex;
|
private final Integer initialIndex;
|
||||||
|
|
||||||
|
public ViewTransactionEvent(Window window, Transaction transaction) {
|
||||||
|
this.window = window;
|
||||||
|
this.transaction = transaction;
|
||||||
|
this.blockTransaction = null;
|
||||||
|
this.initialView = TransactionView.HEADERS;
|
||||||
|
this.initialIndex = null;
|
||||||
|
}
|
||||||
|
|
||||||
public ViewTransactionEvent(Window window, BlockTransaction blockTransaction) {
|
public ViewTransactionEvent(Window window, BlockTransaction blockTransaction) {
|
||||||
this(window, blockTransaction, TransactionView.HEADERS, null);
|
this(window, blockTransaction, TransactionView.HEADERS, null);
|
||||||
}
|
}
|
||||||
|
@ -21,6 +31,7 @@ public class ViewTransactionEvent {
|
||||||
|
|
||||||
public ViewTransactionEvent(Window window, BlockTransaction blockTransaction, TransactionView initialView, Integer initialIndex) {
|
public ViewTransactionEvent(Window window, BlockTransaction blockTransaction, TransactionView initialView, Integer initialIndex) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
|
this.transaction = blockTransaction.getTransaction();
|
||||||
this.blockTransaction = blockTransaction;
|
this.blockTransaction = blockTransaction;
|
||||||
this.initialView = initialView;
|
this.initialView = initialView;
|
||||||
this.initialIndex = initialIndex;
|
this.initialIndex = initialIndex;
|
||||||
|
@ -30,6 +41,10 @@ public class ViewTransactionEvent {
|
||||||
return window;
|
return window;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Transaction getTransaction() {
|
||||||
|
return transaction;
|
||||||
|
}
|
||||||
|
|
||||||
public BlockTransaction getBlockTransaction() {
|
public BlockTransaction getBlockTransaction() {
|
||||||
return blockTransaction;
|
return blockTransaction;
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,9 @@ import java.time.*;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
import static com.sparrowwallet.sparrow.AppServices.showErrorDialog;
|
||||||
|
|
||||||
public class HeadersController extends TransactionFormController implements Initializable, DynamicUpdate {
|
public class HeadersController extends TransactionFormController implements Initializable, DynamicUpdate {
|
||||||
private static final Logger log = LoggerFactory.getLogger(HeadersController.class);
|
private static final Logger log = LoggerFactory.getLogger(HeadersController.class);
|
||||||
|
@ -829,7 +832,49 @@ public class HeadersController extends TransactionFormController implements Init
|
||||||
ToggleButton toggleButton = (ToggleButton)event.getSource();
|
ToggleButton toggleButton = (ToggleButton)event.getSource();
|
||||||
toggleButton.setSelected(false);
|
toggleButton.setSelected(false);
|
||||||
|
|
||||||
EventManager.get().post(new RequestQRScanEvent(toggleButton.getScene().getWindow()));
|
QRScanDialog qrScanDialog = new QRScanDialog();
|
||||||
|
Optional<QRScanDialog.Result> optionalResult = qrScanDialog.showAndWait();
|
||||||
|
if(optionalResult.isPresent()) {
|
||||||
|
QRScanDialog.Result result = optionalResult.get();
|
||||||
|
if(result.transaction != null) {
|
||||||
|
EventManager.get().post(new ViewTransactionEvent(toggleButton.getScene().getWindow(), result.transaction));
|
||||||
|
} else if(result.psbt != null) {
|
||||||
|
EventManager.get().post(new ViewPSBTEvent(toggleButton.getScene().getWindow(), null, null, result.psbt));
|
||||||
|
} else if(result.seed != null) {
|
||||||
|
signFromSeed(result.seed);
|
||||||
|
} else if(result.exception != null) {
|
||||||
|
log.error("Error scanning QR", result.exception);
|
||||||
|
showErrorDialog("Error scanning QR", result.exception.getMessage());
|
||||||
|
} else {
|
||||||
|
AppServices.showErrorDialog("Invalid QR Code", "Cannot parse QR code into a transaction or seed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void signFromSeed(DeterministicSeed seed) {
|
||||||
|
try {
|
||||||
|
String masterFingerprint = Keystore.fromSeed(seed, ScriptType.P2PKH.getDefaultDerivation()).getKeyDerivation().getMasterFingerprint();
|
||||||
|
Wallet walletCopy = headersForm.getSigningWallet().copy();
|
||||||
|
OptionalInt optIndex = IntStream.range(0, walletCopy.getKeystores().size())
|
||||||
|
.filter(i -> walletCopy.getKeystores().get(i).getKeyDerivation().getMasterFingerprint().equals(masterFingerprint)).findFirst();
|
||||||
|
if(optIndex.isPresent()) {
|
||||||
|
walletCopy.getKeystores().forEach(keystore -> {
|
||||||
|
keystore.setSeed(null);
|
||||||
|
keystore.setMasterPrivateExtendedKey(null);
|
||||||
|
});
|
||||||
|
Keystore original = walletCopy.getKeystores().get(optIndex.getAsInt());
|
||||||
|
Keystore replacement = Keystore.fromSeed(seed, original.getKeyDerivation().getDerivation());
|
||||||
|
walletCopy.getKeystores().set(optIndex.getAsInt(), replacement);
|
||||||
|
signUnencryptedKeystores(walletCopy);
|
||||||
|
} else {
|
||||||
|
AppServices.showErrorDialog("Invalid seed", "The QR code contains a seed that does not match any of the keystores in the signing wallet.");
|
||||||
|
}
|
||||||
|
} catch(MnemonicException e) {
|
||||||
|
log.error("Invalid seed", e);
|
||||||
|
AppServices.showErrorDialog("Invalid seed", e.getMessage());
|
||||||
|
} finally {
|
||||||
|
seed.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void savePSBT(ActionEvent event) {
|
public void savePSBT(ActionEvent event) {
|
||||||
|
|
|
@ -432,7 +432,7 @@ public class TransactionController implements Initializable {
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void viewTransaction(ViewTransactionEvent event) {
|
public void viewTransaction(ViewTransactionEvent event) {
|
||||||
if(txdata.getTransaction().getTxId().equals(event.getBlockTransaction().getTransaction().getTxId())) {
|
if(txdata.getTransaction().getTxId().equals(event.getTransaction().getTxId())) {
|
||||||
TreeItem<TransactionForm> existingItem = getTreeItem(event.getInitialView(), event.getInitialIndex());
|
TreeItem<TransactionForm> existingItem = getTreeItem(event.getInitialView(), event.getInitialIndex());
|
||||||
if(existingItem != null && !(existingItem.getValue() instanceof PageForm)) {
|
if(existingItem != null && !(existingItem.getValue() instanceof PageForm)) {
|
||||||
setTreeSelection(event.getInitialView(), event.getInitialIndex());
|
setTreeSelection(event.getInitialView(), event.getInitialIndex());
|
||||||
|
|
Loading…
Reference in a new issue