WIP - testnet support

This commit is contained in:
Daniel Newton 2020-09-27 22:05:46 +13:00
parent 2cb667a671
commit 4c94e52ef7
19 changed files with 98 additions and 42 deletions

2
drongo

@ -1 +1 @@
Subproject commit e8d8fa61268ec8ac4dd5c14e6715d4a4bde2fe49
Subproject commit 6d156505587bbbcddb5bea001cf21fbe6195e863

View file

@ -9,6 +9,7 @@ import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.crypto.InvalidPasswordException;
import com.sparrowwallet.drongo.crypto.Key;
import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.drongo.protocol.Network;
import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.protocol.Sha256Hash;
import com.sparrowwallet.drongo.protocol.Transaction;
@ -551,7 +552,8 @@ public class AppController implements Initializable {
}
public void openTransactionFromQR(ActionEvent event) {
QRScanDialog qrScanDialog = new QRScanDialog();
//TODO: is wallet always valid???
QRScanDialog qrScanDialog = new QRScanDialog(getWalletForCurrentTab().getNetwork());
Optional<QRScanDialog.Result> optionalResult = qrScanDialog.showAndWait();
if(optionalResult.isPresent()) {
QRScanDialog.Result result = optionalResult.get();
@ -746,7 +748,8 @@ public class AppController implements Initializable {
if(walletName.isPresent()) {
File walletFile = Storage.getWalletFile(walletName.get());
Storage storage = new Storage(walletFile);
Wallet wallet = new Wallet(walletName.get(), PolicyType.SINGLE, ScriptType.P2WPKH);
//TODO: get from settings or user input
Wallet wallet = new Wallet(Network.BITCOIN, walletName.get(), PolicyType.SINGLE, ScriptType.P2WPKH);
Tab tab = addWalletTab(storage, wallet);
tabs.getSelectionModel().select(tab);
}
@ -911,18 +914,23 @@ public class AppController implements Initializable {
PreferencesDialog preferencesDialog = new PreferencesDialog();
preferencesDialog.showAndWait();
}
public void signVerifyMessage(ActionEvent event) {
MessageSignDialog messageSignDialog = null;
public Wallet getWalletForCurrentTab() {
Tab tab = tabs.getSelectionModel().getSelectedItem();
if(tab != null && tab.getUserData() instanceof WalletTabData) {
WalletTabData walletTabData = (WalletTabData)tab.getUserData();
Wallet wallet = walletTabData.getWallet();
if(wallet.getKeystores().size() == 1 &&
(wallet.getKeystores().get(0).hasSeed() || wallet.getKeystores().get(0).getSource() == KeystoreSource.HW_USB)) {
//Can sign and verify
messageSignDialog = new MessageSignDialog(wallet);
}
return walletTabData.getWallet();
}
return null;
}
public void signVerifyMessage(ActionEvent event) {
MessageSignDialog messageSignDialog = null;
Wallet wallet = getWalletForCurrentTab();
if(wallet != null && wallet.getKeystores().size() == 1 &&
(wallet.getKeystores().get(0).hasSeed() || wallet.getKeystores().get(0).getSource() == KeystoreSource.HW_USB)) {
//Can sign and verify
messageSignDialog = new MessageSignDialog(wallet);
}
if(messageSignDialog == null) {
@ -1001,7 +1009,9 @@ public class AppController implements Initializable {
private Tab addTransactionTab(String name, byte[] bytes) throws PSBTParseException, ParseException, TransactionParseException {
if(PSBT.isPSBT(bytes)) {
PSBT psbt = new PSBT(bytes);
//TODO: test this is called from a wallet tab
Wallet wallet = getWalletForCurrentTab();
PSBT psbt = new PSBT(wallet.getNetwork(), bytes);
return addTransactionTab(name, psbt);
}

View file

@ -4,6 +4,7 @@ import com.google.common.io.ByteStreams;
import com.google.gson.JsonParseException;
import com.sparrowwallet.drongo.policy.Policy;
import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.drongo.protocol.Network;
import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.wallet.Keystore;
import com.sparrowwallet.drongo.wallet.Wallet;
@ -57,7 +58,8 @@ public class FileWalletKeystoreImportPane extends FileImportPane {
ByteArrayInputStream bais = new ByteArrayInputStream(fileBytes);
Keystore keystore = importer.getKeystore(scriptType, bais, "");
Wallet wallet = new Wallet();
//TODO: use user input here
Wallet wallet = new Wallet(Network.BITCOIN);
wallet.setName(fileName);
wallet.setPolicyType(PolicyType.SINGLE);
wallet.setScriptType(scriptType);

View file

@ -191,7 +191,8 @@ public class MessageSignDialog extends Dialog<ButtonBar.ButtonData> {
}
private Address getAddress()throws InvalidAddressException {
return Address.fromString(address.getText());
//TODO: is wallet always valid???
return Address.fromString(wallet.getNetwork(), address.getText());
}
private boolean isValidAddress() {
@ -298,7 +299,8 @@ public class MessageSignDialog extends Dialog<ButtonBar.ButtonData> {
throw new IllegalArgumentException("Only single signature P2PKH, P2SH-P2WPKH or P2WPKH addresses can verify messages.");
}
Address signedMessageAddress = scriptType.getAddress(signedMessageKey);
//TODO: wallet is not always valid!!!
Address signedMessageAddress = scriptType.getAddress(wallet.getNetwork(), signedMessageKey);
return providedAddress.equals(signedMessageAddress);
}

View file

@ -5,6 +5,7 @@ import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.address.Address;
import com.sparrowwallet.drongo.protocol.Base43;
import com.sparrowwallet.drongo.protocol.Transaction;
import com.sparrowwallet.drongo.protocol.Network;
import com.sparrowwallet.drongo.psbt.PSBT;
import com.sparrowwallet.drongo.uri.BitcoinURI;
import com.sparrowwallet.sparrow.AppController;
@ -22,13 +23,15 @@ import javafx.scene.layout.StackPane;
import org.controlsfx.tools.Borders;
public class QRScanDialog extends Dialog<QRScanDialog.Result> {
private final Network network;
private final URDecoder decoder;
private final WebcamService webcamService;
private boolean isUr;
private QRScanDialog.Result result;
public QRScanDialog() {
public QRScanDialog(Network network) {
this.network = network;
this.decoder = new URDecoder();
this.webcamService = new WebcamService(WebcamResolution.VGA);
@ -78,7 +81,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
//TODO: Confirm once UR type registry is updated
if(urResult.ur.getType().contains(UR.BYTES_TYPE) || urResult.ur.getType().equals(UR.CRYPTO_PSBT_TYPE)) {
try {
PSBT psbt = new PSBT(urResult.ur.toBytes());
PSBT psbt = new PSBT(network, urResult.ur.toBytes());
result = new Result(psbt);
return;
} catch(Exception e) {
@ -107,7 +110,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
BitcoinURI bitcoinURI;
Address address;
try {
bitcoinURI = new BitcoinURI(qrtext);
bitcoinURI = new BitcoinURI(network, qrtext);
result = new Result(bitcoinURI);
return;
} catch(Exception e) {
@ -115,15 +118,15 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
}
try {
address = Address.fromString(qrtext);
result = new Result(address);
address = Address.fromString(network, qrtext);
result = new Result(network, address);
return;
} catch(Exception e) {
//Ignore, not an address
}
try {
psbt = PSBT.fromString(qrtext);
psbt = PSBT.fromString(network, qrtext);
result = new Result(psbt);
return;
} catch(Exception e) {
@ -131,7 +134,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
}
try {
psbt = new PSBT(qrResult.getRawBytes());
psbt = new PSBT(network, qrResult.getRawBytes());
result = new Result(psbt);
return;
} catch(Exception e) {
@ -156,7 +159,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
//Try Base43 used by Electrum
try {
psbt = new PSBT(Base43.decode(qrResult.getText()));
psbt = new PSBT(network, Base43.decode(qrResult.getText()));
result = new Result(psbt);
return;
} catch(Exception e) {
@ -207,10 +210,10 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
this.exception = null;
}
public Result(Address address) {
public Result(Network network, Address address) {
this.transaction = null;
this.psbt = null;
this.uri = BitcoinURI.fromAddress(address);
this.uri = BitcoinURI.fromAddress(network, address);
this.error = null;
this.exception = null;
}

View file

@ -20,6 +20,6 @@ public class SettingsChangedEvent {
}
public enum Type {
POLICY, SCRIPT_TYPE, MUTLISIG_THRESHOLD, MULTISIG_TOTAL, KEYSTORE_LABEL, KEYSTORE_FINGERPRINT, KEYSTORE_DERIVATION, KEYSTORE_XPUB, GAP_LIMIT;
NETWORK, POLICY, SCRIPT_TYPE, MUTLISIG_THRESHOLD, MULTISIG_TOTAL, KEYSTORE_LABEL, KEYSTORE_FINGERPRINT, KEYSTORE_DERIVATION, KEYSTORE_XPUB, GAP_LIMIT;
}
}

View file

@ -7,6 +7,7 @@ import com.sparrowwallet.drongo.KeyDerivation;
import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.policy.Policy;
import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.drongo.protocol.Network;
import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.wallet.Keystore;
import com.sparrowwallet.drongo.wallet.KeystoreSource;
@ -78,7 +79,8 @@ public class ColdcardMultisig implements WalletImport, KeystoreFileImport, Walle
@Override
public Wallet importWallet(InputStream inputStream, String password) throws ImportException {
Wallet wallet = new Wallet();
//TODO: get from settings or user input
Wallet wallet = new Wallet(Network.BITCOIN);
wallet.setPolicyType(PolicyType.MULTI);
int threshold = 2;

View file

@ -2,6 +2,7 @@ package com.sparrowwallet.sparrow.io;
import com.google.gson.*;
import com.sparrowwallet.drongo.BitcoinUnit;
import com.sparrowwallet.drongo.protocol.Network;
import com.sparrowwallet.sparrow.Mode;
import com.sparrowwallet.sparrow.Theme;
import org.slf4j.Logger;
@ -17,6 +18,7 @@ public class Config {
public static final String CONFIG_FILENAME = "config";
private Network network = Network.BITCOIN;
private Mode mode;
private BitcoinUnit bitcoinUnit;
private Currency fiatCurrency;
@ -74,6 +76,10 @@ public class Config {
return INSTANCE;
}
public Network getNetwork() {
return network;
}
public Mode getMode() {
return mode;

View file

@ -8,6 +8,7 @@ import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.crypto.*;
import com.sparrowwallet.drongo.policy.Policy;
import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.drongo.protocol.Network;
import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.protocol.Sha256Hash;
import com.sparrowwallet.drongo.protocol.Transaction;
@ -117,7 +118,8 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport
}
}
Wallet wallet = new Wallet();
//TODO: get from settings or user input
Wallet wallet = new Wallet(Network.BITCOIN);
ScriptType scriptType = null;
for(ElectrumKeystore ek : ew.keystores.values()) {

View file

@ -70,6 +70,8 @@ public class Hwi {
}
public String getXpub(Device device, String passphrase, String derivationPath) throws ImportException {
//TODO: use --testnet if using testnet
try {
String output;
if(passphrase != null && !passphrase.isEmpty() && device.getModel().externalPassphraseEntry()) {
@ -91,6 +93,7 @@ public class Hwi {
}
public String displayAddress(Device device, String passphrase, ScriptType scriptType, String derivationPath) throws DisplayAddressException {
//TODO: use --testnet if using testnet
try {
if(!List.of(ScriptType.P2PKH, ScriptType.P2SH_P2WPKH, ScriptType.P2WPKH).contains(scriptType)) {
throw new IllegalArgumentException("Cannot display address for script type " + scriptType + ": Only single sig types supported");
@ -169,7 +172,7 @@ public class Hwi {
JsonObject result = JsonParser.parseString(output).getAsJsonObject();
if(result.get("psbt") != null) {
String strPsbt = result.get("psbt").getAsString();
return PSBT.fromString(strPsbt);
return PSBT.fromString(psbt.getNetwork(), strPsbt);
} else {
JsonElement error = result.get("error");
if(error != null && error.getAsString().equals("sign_tx canceled")) {

View file

@ -3,6 +3,7 @@ package com.sparrowwallet.sparrow.io;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.sparrowwallet.drongo.OutputDescriptor;
import com.sparrowwallet.drongo.protocol.Network;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.drongo.wallet.WalletModel;
@ -53,7 +54,8 @@ public class Specter implements WalletImport, WalletExport {
SpecterWallet specterWallet = gson.fromJson(new InputStreamReader(inputStream), SpecterWallet.class);
if(specterWallet.descriptor != null) {
OutputDescriptor outputDescriptor = OutputDescriptor.getOutputDescriptor(specterWallet.descriptor);
//TODO: get from settings or user input
OutputDescriptor outputDescriptor = OutputDescriptor.getOutputDescriptor(Network.BITCOIN, specterWallet.descriptor);
Wallet wallet = outputDescriptor.toWallet();
wallet.setName(specterWallet.label);

View file

@ -4,6 +4,7 @@ import com.google.gson.Gson;
import com.sparrowwallet.drongo.address.Address;
import com.sparrowwallet.drongo.address.InvalidAddressException;
import com.sparrowwallet.drongo.crypto.ECKey;
import com.sparrowwallet.drongo.protocol.Network;
import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.sparrow.MainApp;
import com.sparrowwallet.sparrow.event.VersionUpdatedEvent;
@ -61,8 +62,8 @@ public class VersionCheckService extends ScheduledService<VersionUpdatedEvent> {
String signature = versionCheck.signatures.get(addressString);
ECKey signedMessageKey = ECKey.signedMessageToKey(versionCheck.version, signature, false);
Address providedAddress = Address.fromString(addressString);
Address signedMessageAddress = ScriptType.P2PKH.getAddress(signedMessageKey);
Address providedAddress = Address.fromString(Network.BITCOIN, addressString);
Address signedMessageAddress = ScriptType.P2PKH.getAddress(Network.BITCOIN, signedMessageKey);
if(providedAddress.equals(signedMessageAddress)) {
return true;

View file

@ -870,7 +870,7 @@ public class HeadersController extends TransactionFormController implements Init
if(headersForm.getPsbt().isSigned()) {
if(headersForm.getSigningWallet() == null) {
//As no signing wallet is available, but we want to show the PSBT has been signed and automatically finalize it, construct a special wallet with default named keystores
Wallet signedWallet = new FinalizingPSBTWallet(headersForm.getPsbt());
Wallet signedWallet = new FinalizingPSBTWallet(getTransactionForm().getSigningWallet().getNetwork(), headersForm.getPsbt());
headersForm.setSigningWallet(signedWallet);
}
@ -920,7 +920,7 @@ public class HeadersController extends TransactionFormController implements Init
if(headersForm.getSigningWallet() != null) {
updateSignedKeystores(headersForm.getSigningWallet());
} else if(headersForm.getPsbt().isSigned()) {
Wallet signedWallet = new FinalizingPSBTWallet(headersForm.getPsbt());
Wallet signedWallet = new FinalizingPSBTWallet(getTransactionForm().getSigningWallet().getNetwork(), headersForm.getPsbt());
headersForm.setSigningWallet(signedWallet);
finalizePSBT();
EventManager.get().post(new FinalizeTransactionEvent(headersForm.getPsbt(), signedWallet));

View file

@ -197,7 +197,7 @@ public class InputController extends TransactionFormController implements Initia
if (output != null) {
spends.setValue(output.getValue());
try {
Address[] addresses = output.getScript().getToAddresses();
Address[] addresses = output.getScript().getToAddresses(getTransactionForm().getSigningWallet().getNetwork());
from.setVisible(true);
if (addresses.length == 1) {
address.setAddress(addresses[0]);

View file

@ -71,7 +71,7 @@ public class OutputController extends TransactionFormController implements Initi
value.setValue(txOutput.getValue());
to.setVisible(false);
try {
Address[] addresses = txOutput.getScript().getToAddresses();
Address[] addresses = txOutput.getScript().getToAddresses(getTransactionForm().getSigningWallet().getNetwork());
to.setVisible(true);
if(addresses.length == 1) {
address.setAddress(addresses[0]);

View file

@ -32,7 +32,7 @@ public abstract class TransactionFormController extends BaseController {
TransactionOutput output = outputs.get(i);
String name = "#" + i;
try {
Address[] addresses = output.getScript().getToAddresses();
Address[] addresses = output.getScript().getToAddresses(getTransactionForm().getSigningWallet().getNetwork());
if(addresses.length == 1) {
name = name + " " + addresses[0].getAddress();
} else {

View file

@ -371,7 +371,7 @@ public class SendController extends WalletFormController implements Initializabl
}
private Address getRecipientAddress() throws InvalidAddressException {
return Address.fromString(address.getText());
return Address.fromString(getWalletForm().getWallet().getNetwork(), address.getText());
}
private Long getRecipientValueSats() {
@ -504,7 +504,7 @@ public class SendController extends WalletFormController implements Initializabl
try {
address = getRecipientAddress();
} catch(InvalidAddressException e) {
address = new P2PKHAddress(new byte[20]);
address = new P2PKHAddress(getWalletForm().getWallet().getNetwork(), new byte[20]);
}
TransactionOutput txOutput = new TransactionOutput(new Transaction(), 1L, address.getOutputScript());
@ -531,7 +531,7 @@ public class SendController extends WalletFormController implements Initializabl
}
public void scanQrAddress(ActionEvent event) {
QRScanDialog qrScanDialog = new QRScanDialog();
QRScanDialog qrScanDialog = new QRScanDialog(getWalletForm().getWallet().getNetwork());
Optional<QRScanDialog.Result> optionalResult = qrScanDialog.showAndWait();
if(optionalResult.isPresent()) {
QRScanDialog.Result result = optionalResult.get();

View file

@ -6,6 +6,7 @@ import com.sparrowwallet.drongo.SecureString;
import com.sparrowwallet.drongo.crypto.*;
import com.sparrowwallet.drongo.policy.Policy;
import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.drongo.protocol.Network;
import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.wallet.Keystore;
import com.sparrowwallet.drongo.wallet.KeystoreSource;
@ -46,6 +47,9 @@ import java.util.stream.Collectors;
public class SettingsController extends WalletFormController implements Initializable {
private static final Logger log = LoggerFactory.getLogger(SettingsController.class);
@FXML
private ComboBox<Network> network;
@FXML
private ComboBox<PolicyType> policyType;
@ -92,6 +96,12 @@ public class SettingsController extends WalletFormController implements Initiali
keystoreTabs = new TabPane();
keystoreTabsPane.getChildren().add(keystoreTabs);
network.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, network) -> {
walletForm.getWallet().setNetwork(network);
EventManager.get().post(new SettingsChangedEvent(walletForm.getWallet(), SettingsChangedEvent.Type.NETWORK));
});
policyType.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, policyType) -> {
walletForm.getWallet().setPolicyType(policyType);
@ -190,6 +200,8 @@ public class SettingsController extends WalletFormController implements Initiali
}
private void setFieldsFromWallet(Wallet wallet) {
network.getSelectionModel().select(walletForm.getWallet().getNetwork());
if(wallet.getPolicyType() == null) {
wallet.setPolicyType(PolicyType.SINGLE);
wallet.setScriptType(ScriptType.P2WPKH);
@ -260,7 +272,7 @@ public class SettingsController extends WalletFormController implements Initiali
Optional<String> text = dialog.showAndWait();
if(text.isPresent() && !text.get().isEmpty() && !text.get().equals(outputDescriptorString)) {
try {
OutputDescriptor editedOutputDescriptor = OutputDescriptor.getOutputDescriptor(text.get());
OutputDescriptor editedOutputDescriptor = OutputDescriptor.getOutputDescriptor(walletForm.getWallet().getNetwork(), text.get());
Wallet editedWallet = editedOutputDescriptor.toWallet();
editedWallet.setName(getWalletForm().getWallet().getName());

View file

@ -8,6 +8,7 @@
<?import org.controlsfx.control.RangeSlider?>
<?import com.sparrowwallet.sparrow.control.CopyableLabel?>
<?import com.sparrowwallet.drongo.policy.PolicyType?>
<?import com.sparrowwallet.drongo.protocol.Network?>
<?import com.sparrowwallet.drongo.protocol.ScriptType?>
<?import com.sparrowwallet.sparrow.control.DescriptorArea?>
<?import org.fxmisc.flowless.VirtualizedScrollPane?>
@ -29,6 +30,16 @@
<Form GridPane.columnIndex="0" GridPane.rowIndex="0">
<Fieldset inputGrow="SOMETIMES" text="Settings">
<Field text="Network:">
<ComboBox fx:id="network">
<items>
<FXCollections fx:factory="observableArrayList">
<Network fx:constant="BITCOIN" />
<Network fx:constant="TESTNET" />
</FXCollections>
</items>
</ComboBox>
</Field>
<Field text="Policy Type:">
<ComboBox fx:id="policyType">
<items>