mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-24 12:46: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) {
|
||||
addTransactionTab(null, null, blockTransaction.getTransaction(), null, blockTransaction, initialView, initialIndex);
|
||||
}
|
||||
|
@ -2588,7 +2592,11 @@ public class AppController implements Initializable {
|
|||
@Subscribe
|
||||
public void viewTransaction(ViewTransactionEvent event) {
|
||||
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.P2WPKHAddress;
|
||||
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.ScriptType;
|
||||
import com.sparrowwallet.drongo.protocol.Transaction;
|
||||
import com.sparrowwallet.drongo.psbt.PSBT;
|
||||
import com.sparrowwallet.drongo.psbt.PSBTParseException;
|
||||
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.hummingbird.LegacyURDecoder;
|
||||
import com.sparrowwallet.hummingbird.registry.*;
|
||||
|
@ -257,6 +256,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
|||
BitcoinURI bitcoinURI;
|
||||
Address address;
|
||||
ExtendedKey extendedKey;
|
||||
DeterministicSeed seed;
|
||||
try {
|
||||
extendedKey = ExtendedKey.fromDescriptor(qrtext);
|
||||
result = new Result(extendedKey);
|
||||
|
@ -334,6 +334,22 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
|||
//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);
|
||||
}
|
||||
}
|
||||
|
@ -401,6 +417,14 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
|||
CryptoAccount cryptoAccount = (CryptoAccount)ur.decodeFromRegistry();
|
||||
List<Wallet> wallets = getWallets(cryptoAccount);
|
||||
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 {
|
||||
log.error("Unsupported UR type " + urRegistryType);
|
||||
return new Result(new URException("UR type " + urRegistryType + " is not supported"));
|
||||
|
@ -523,6 +547,14 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
|||
|
||||
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 {
|
||||
|
@ -626,6 +658,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
|||
public final ExtendedKey extendedKey;
|
||||
public final OutputDescriptor outputDescriptor;
|
||||
public final List<Wallet> wallets;
|
||||
public final DeterministicSeed seed;
|
||||
public final String payload;
|
||||
public final Throwable exception;
|
||||
|
||||
|
@ -636,6 +669,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
|||
this.extendedKey = null;
|
||||
this.outputDescriptor = null;
|
||||
this.wallets = null;
|
||||
this.seed = null;
|
||||
this.payload = null;
|
||||
this.exception = null;
|
||||
}
|
||||
|
@ -647,6 +681,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
|||
this.extendedKey = null;
|
||||
this.outputDescriptor = null;
|
||||
this.wallets = null;
|
||||
this.seed = null;
|
||||
this.payload = null;
|
||||
this.exception = null;
|
||||
}
|
||||
|
@ -658,6 +693,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
|||
this.extendedKey = null;
|
||||
this.outputDescriptor = null;
|
||||
this.wallets = null;
|
||||
this.seed = null;
|
||||
this.payload = null;
|
||||
this.exception = null;
|
||||
}
|
||||
|
@ -669,6 +705,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
|||
this.extendedKey = null;
|
||||
this.outputDescriptor = null;
|
||||
this.wallets = null;
|
||||
this.seed = null;
|
||||
this.payload = null;
|
||||
this.exception = null;
|
||||
}
|
||||
|
@ -680,6 +717,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
|||
this.extendedKey = extendedKey;
|
||||
this.outputDescriptor = null;
|
||||
this.wallets = null;
|
||||
this.seed = null;
|
||||
this.payload = null;
|
||||
this.exception = null;
|
||||
}
|
||||
|
@ -691,6 +729,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
|||
this.extendedKey = null;
|
||||
this.outputDescriptor = outputDescriptor;
|
||||
this.wallets = null;
|
||||
this.seed = null;
|
||||
this.payload = null;
|
||||
this.exception = null;
|
||||
}
|
||||
|
@ -702,6 +741,19 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
|||
this.extendedKey = null;
|
||||
this.outputDescriptor = null;
|
||||
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.exception = null;
|
||||
}
|
||||
|
@ -713,6 +765,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
|||
this.extendedKey = null;
|
||||
this.outputDescriptor = null;
|
||||
this.wallets = null;
|
||||
this.seed = null;
|
||||
this.payload = payload;
|
||||
this.exception = null;
|
||||
}
|
||||
|
@ -724,6 +777,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
|||
this.extendedKey = null;
|
||||
this.outputDescriptor = null;
|
||||
this.wallets = null;
|
||||
this.seed = null;
|
||||
this.payload = null;
|
||||
this.exception = exception;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.sparrowwallet.sparrow.event;
|
||||
|
||||
import com.sparrowwallet.drongo.protocol.Transaction;
|
||||
import com.sparrowwallet.drongo.wallet.BlockTransaction;
|
||||
import com.sparrowwallet.sparrow.transaction.TransactionView;
|
||||
import com.sparrowwallet.sparrow.wallet.HashIndexEntry;
|
||||
|
@ -7,10 +8,19 @@ import javafx.stage.Window;
|
|||
|
||||
public class ViewTransactionEvent {
|
||||
private final Window window;
|
||||
private final Transaction transaction;
|
||||
private final BlockTransaction blockTransaction;
|
||||
private final TransactionView initialView;
|
||||
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) {
|
||||
this(window, blockTransaction, TransactionView.HEADERS, null);
|
||||
}
|
||||
|
@ -21,6 +31,7 @@ public class ViewTransactionEvent {
|
|||
|
||||
public ViewTransactionEvent(Window window, BlockTransaction blockTransaction, TransactionView initialView, Integer initialIndex) {
|
||||
this.window = window;
|
||||
this.transaction = blockTransaction.getTransaction();
|
||||
this.blockTransaction = blockTransaction;
|
||||
this.initialView = initialView;
|
||||
this.initialIndex = initialIndex;
|
||||
|
@ -30,6 +41,10 @@ public class ViewTransactionEvent {
|
|||
return window;
|
||||
}
|
||||
|
||||
public Transaction getTransaction() {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
public BlockTransaction getBlockTransaction() {
|
||||
return blockTransaction;
|
||||
}
|
||||
|
|
|
@ -60,6 +60,9 @@ import java.time.*;
|
|||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
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 {
|
||||
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.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) {
|
||||
|
|
|
@ -432,7 +432,7 @@ public class TransactionController implements Initializable {
|
|||
|
||||
@Subscribe
|
||||
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());
|
||||
if(existingItem != null && !(existingItem.getValue() instanceof PageForm)) {
|
||||
setTreeSelection(event.getInitialView(), event.getInitialIndex());
|
||||
|
|
Loading…
Reference in a new issue