From 74a551ed01f0ff6549e16734b191ead16d99cc3a Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Wed, 7 Feb 2024 09:19:55 +0200 Subject: [PATCH] add support for satschip nfc card --- drongo | 2 +- .../com/sparrowwallet/sparrow/io/CardApi.java | 2 +- .../sparrow/io/ckcard/CardStatus.java | 8 ++++--- .../sparrow/io/ckcard/CkCardApi.java | 18 +++++++++------- .../sparrow/io/ckcard/Satschip.java | 20 ++++++++++++++++++ .../sparrow/io/ckcard/Tapsigner.java | 6 +++--- .../keystoreimport/HwAirgappedController.java | 3 ++- .../sparrow/wallet/KeystoreController.java | 2 +- .../resources/image/satschip-icon-invert.svg | 9 ++++++++ src/main/resources/image/satschip-icon.svg | 9 ++++++++ src/main/resources/image/satschip.png | Bin 0 -> 852 bytes src/main/resources/image/satschip@2x.png | Bin 0 -> 1697 bytes src/main/resources/image/satschip@3x.png | Bin 0 -> 3963 bytes 13 files changed, 61 insertions(+), 18 deletions(-) create mode 100644 src/main/java/com/sparrowwallet/sparrow/io/ckcard/Satschip.java create mode 100644 src/main/resources/image/satschip-icon-invert.svg create mode 100644 src/main/resources/image/satschip-icon.svg create mode 100644 src/main/resources/image/satschip.png create mode 100644 src/main/resources/image/satschip@2x.png create mode 100644 src/main/resources/image/satschip@3x.png diff --git a/drongo b/drongo index d2559136..3dbcdfcf 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit d255913654b29a33c772dead9b66721c70fe7950 +Subproject commit 3dbcdfcf4ebeb478d6da28f17f5dc74dba244fe1 diff --git a/src/main/java/com/sparrowwallet/sparrow/io/CardApi.java b/src/main/java/com/sparrowwallet/sparrow/io/CardApi.java index 1ea660c6..6d7dc298 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/CardApi.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/CardApi.java @@ -62,7 +62,7 @@ public abstract class CardApi { } public static CardApi getCardApi(WalletModel walletModel, String pin) throws CardException { - if(walletModel == WalletModel.TAPSIGNER || walletModel == WalletModel.SATSCARD) { + if(walletModel == WalletModel.TAPSIGNER || walletModel == WalletModel.SATSCHIP || walletModel == WalletModel.SATSCARD) { return new CkCardApi(walletModel, pin); } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/ckcard/CardStatus.java b/src/main/java/com/sparrowwallet/sparrow/io/ckcard/CardStatus.java index 7e471b01..1af96ffe 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/ckcard/CardStatus.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/ckcard/CardStatus.java @@ -15,6 +15,7 @@ public class CardStatus extends CardResponse { String ver; BigInteger birth; boolean tapsigner; + boolean satschip; List path; BigInteger num_backups; List slots; @@ -24,7 +25,7 @@ public class CardStatus extends CardResponse { boolean testnet; public boolean isInitialized() { - return (getCardType() == WalletModel.TAPSIGNER && path != null) || (getCardType() == WalletModel.SATSCARD && addr != null); + return ((getCardType() == WalletModel.TAPSIGNER || getCardType() == WalletModel.SATSCHIP) && path != null) || (getCardType() == WalletModel.SATSCARD && addr != null); } public String getIdentifier() { @@ -42,11 +43,11 @@ public class CardStatus extends CardResponse { } public boolean requiresBackup() { - return num_backups == null || num_backups.intValue() == 0; + return !satschip && (num_backups == null || num_backups.intValue() == 0); } public WalletModel getCardType() { - return tapsigner ? WalletModel.TAPSIGNER : WalletModel.SATSCARD; + return satschip ? WalletModel.SATSCHIP : (tapsigner ? WalletModel.TAPSIGNER : WalletModel.SATSCARD); } public int getCurrentSlot() { @@ -72,6 +73,7 @@ public class CardStatus extends CardResponse { ", ver='" + ver + '\'' + ", birth=" + birth + ", tapsigner=" + tapsigner + + ", satschip=" + satschip + ", path=" + path + ", num_backups=" + num_backups + ", slots=" + slots + diff --git a/src/main/java/com/sparrowwallet/sparrow/io/ckcard/CkCardApi.java b/src/main/java/com/sparrowwallet/sparrow/io/ckcard/CkCardApi.java index 653db879..6455bf4f 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/ckcard/CkCardApi.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/ckcard/CkCardApi.java @@ -32,10 +32,6 @@ public class CkCardApi extends CardApi { private final CardProtocol cardProtocol; private String cvc; - public CkCardApi(String cvc) throws CardException { - this(WalletModel.TAPSIGNER, cvc); - } - public CkCardApi(WalletModel cardType, String cvc) throws CardException { this.cardType = cardType; this.cardProtocol = new CardProtocol(); @@ -137,6 +133,8 @@ public class CkCardApi extends CardApi { public Service getInitializationService(byte[] entropy, StringProperty messageProperty) { if(cardType == WalletModel.TAPSIGNER) { return new CardImportPane.CardInitializationService(new Tapsigner(), cvc, entropy, messageProperty); + } else if(cardType == WalletModel.SATSCHIP) { + return new CardImportPane.CardInitializationService(new Satschip(), cvc, entropy, messageProperty); } return new CardInitializationService(entropy, messageProperty); @@ -144,6 +142,10 @@ public class CkCardApi extends CardApi { @Override public Service getImportService(List derivation, StringProperty messageProperty) { + if(cardType == WalletModel.SATSCHIP) { + return new CardImportPane.CardImportService(new Satschip(), cvc, derivation, messageProperty); + } + return new CardImportPane.CardImportService(new Tapsigner(), cvc, derivation, messageProperty); } @@ -155,11 +157,11 @@ public class CkCardApi extends CardApi { ExtendedKey derivedXpubkey = ExtendedKey.fromDescriptor(Base58.encodeChecked(derivedXpub.xpub)); Keystore keystore = new Keystore(); - keystore.setLabel(WalletModel.TAPSIGNER.toDisplayString()); + keystore.setLabel(cardType.toDisplayString()); keystore.setKeyDerivation(keyDerivation); keystore.setSource(KeystoreSource.HW_AIRGAPPED); keystore.setExtendedPublicKey(derivedXpubkey); - keystore.setWalletModel(WalletModel.TAPSIGNER); + keystore.setWalletModel(cardType); return keystore; } @@ -404,7 +406,7 @@ public class CkCardApi extends CardApi { @Override protected PSBT call() throws Exception { CardStatus cardStatus = getStatus(); - if(cardStatus.getCardType() != WalletModel.TAPSIGNER) { + if(cardStatus.getCardType() != WalletModel.TAPSIGNER && cardStatus.getCardType() != WalletModel.SATSCHIP) { throw new IllegalStateException("Please use a " + WalletModel.TAPSIGNER.toDisplayString() + " to sign transactions."); } @@ -465,7 +467,7 @@ public class CkCardApi extends CardApi { @Override protected String call() throws Exception { CardStatus cardStatus = getStatus(); - if(cardStatus.getCardType() != WalletModel.TAPSIGNER) { + if(cardStatus.getCardType() != WalletModel.TAPSIGNER && cardStatus.getCardType() != WalletModel.SATSCHIP) { throw new IllegalStateException("Please use a " + WalletModel.TAPSIGNER.toDisplayString() + " to sign messages."); } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/ckcard/Satschip.java b/src/main/java/com/sparrowwallet/sparrow/io/ckcard/Satschip.java new file mode 100644 index 00000000..08dee12a --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/io/ckcard/Satschip.java @@ -0,0 +1,20 @@ +package com.sparrowwallet.sparrow.io.ckcard; + +import com.sparrowwallet.drongo.wallet.WalletModel; + +public class Satschip extends Tapsigner { + @Override + public String getKeystoreImportDescription(int account) { + return "Import the keystore from your Satschip by placing it on the card reader."; + } + + @Override + public String getName() { + return "Satschip"; + } + + @Override + public WalletModel getWalletModel() { + return WalletModel.SATSCHIP; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/io/ckcard/Tapsigner.java b/src/main/java/com/sparrowwallet/sparrow/io/ckcard/Tapsigner.java index a526506d..b85c0a82 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/ckcard/Tapsigner.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/ckcard/Tapsigner.java @@ -16,7 +16,7 @@ public class Tapsigner implements KeystoreCardImport { public boolean isInitialized() throws CardException { CkCardApi cardApi = null; try { - cardApi = new CkCardApi(null); + cardApi = new CkCardApi(getWalletModel(), null); return cardApi.isInitialized(); } finally { if(cardApi != null) { @@ -37,7 +37,7 @@ public class Tapsigner implements KeystoreCardImport { CkCardApi cardApi = null; try { - cardApi = new CkCardApi(pin); + cardApi = new CkCardApi(getWalletModel(), pin); CardStatus cardStatus = cardApi.getStatus(); if(cardStatus.isInitialized()) { throw new IllegalStateException("Card is already initialized."); @@ -63,7 +63,7 @@ public class Tapsigner implements KeystoreCardImport { CkCardApi cardApi = null; try { - cardApi = new CkCardApi(pin); + cardApi = new CkCardApi(getWalletModel(), pin); CardStatus cardStatus = cardApi.getStatus(); if(!cardStatus.isInitialized()) { throw new IllegalStateException("Card is not initialized."); diff --git a/src/main/java/com/sparrowwallet/sparrow/keystoreimport/HwAirgappedController.java b/src/main/java/com/sparrowwallet/sparrow/keystoreimport/HwAirgappedController.java index 1cb4b71b..8e4707e3 100644 --- a/src/main/java/com/sparrowwallet/sparrow/keystoreimport/HwAirgappedController.java +++ b/src/main/java/com/sparrowwallet/sparrow/keystoreimport/HwAirgappedController.java @@ -5,6 +5,7 @@ import com.sparrowwallet.sparrow.control.CardImportPane; import com.sparrowwallet.sparrow.control.FileKeystoreImportPane; import com.sparrowwallet.sparrow.control.TitledDescriptionPane; import com.sparrowwallet.sparrow.io.*; +import com.sparrowwallet.sparrow.io.ckcard.Satschip; import com.sparrowwallet.sparrow.io.ckcard.Tapsigner; import com.sparrowwallet.sparrow.io.satochip.Satochip; import javafx.fxml.FXML; @@ -39,7 +40,7 @@ public class HwAirgappedController extends KeystoreImportDetailController { } } - List cardImporters = List.of(new Tapsigner(), new Satochip()); + List cardImporters = List.of(new Tapsigner(), new Satochip(), new Satschip()); for(KeystoreCardImport importer : cardImporters) { if(!importer.isDeprecated() || Config.get().isShowDeprecatedImportExport()) { CardImportPane importPane = new CardImportPane(getMasterController().getWallet(), importer, getMasterController().getRequiredDerivation()); diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/KeystoreController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/KeystoreController.java index 9fe828ea..1f12ae02 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/KeystoreController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/KeystoreController.java @@ -493,7 +493,7 @@ public class KeystoreController extends WalletFormController implements Initiali log.error("Error communicating with card", e); AppServices.showErrorDialog("Error communicating with card", e.getMessage()); }); - ServiceProgressDialog serviceProgressDialog = new ServiceProgressDialog("Authentication Delay", "Waiting for authentication delay to clear...", "/image/tapsigner.png", authDelayService); + ServiceProgressDialog serviceProgressDialog = new ServiceProgressDialog("Authentication Delay", "Waiting for authentication delay to clear...", "/image/" + cardApi.getCardType().getType() + ".png", authDelayService); serviceProgressDialog.initOwner(cardServiceButtons.getScene().getWindow()); AppServices.moveToActiveWindowScreen(serviceProgressDialog); authDelayService.start(); diff --git a/src/main/resources/image/satschip-icon-invert.svg b/src/main/resources/image/satschip-icon-invert.svg new file mode 100644 index 00000000..af7147ce --- /dev/null +++ b/src/main/resources/image/satschip-icon-invert.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/main/resources/image/satschip-icon.svg b/src/main/resources/image/satschip-icon.svg new file mode 100644 index 00000000..fb59fa47 --- /dev/null +++ b/src/main/resources/image/satschip-icon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/main/resources/image/satschip.png b/src/main/resources/image/satschip.png new file mode 100644 index 0000000000000000000000000000000000000000..9bbd127045740e07944d4b2200580c0c8c8fd6e7 GIT binary patch literal 852 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 KB2+?7<@^^lgxq=n literal 0 HcmV?d00001 diff --git a/src/main/resources/image/satschip@3x.png b/src/main/resources/image/satschip@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..bed902a1c97c80735f9b7a12949491a02e0482df GIT binary patch literal 3963 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 NqE&R1OBAgF{{!S>23i0B literal 0 HcmV?d00001