add support for satschip nfc card

This commit is contained in:
Craig Raw 2024-02-07 09:19:55 +02:00
parent 4de6bd5e83
commit 74a551ed01
13 changed files with 61 additions and 18 deletions

2
drongo

@ -1 +1 @@
Subproject commit d255913654b29a33c772dead9b66721c70fe7950 Subproject commit 3dbcdfcf4ebeb478d6da28f17f5dc74dba244fe1

View file

@ -62,7 +62,7 @@ public abstract class CardApi {
} }
public static CardApi getCardApi(WalletModel walletModel, String pin) throws CardException { 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); return new CkCardApi(walletModel, pin);
} }

View file

@ -15,6 +15,7 @@ public class CardStatus extends CardResponse {
String ver; String ver;
BigInteger birth; BigInteger birth;
boolean tapsigner; boolean tapsigner;
boolean satschip;
List<BigInteger> path; List<BigInteger> path;
BigInteger num_backups; BigInteger num_backups;
List<BigInteger> slots; List<BigInteger> slots;
@ -24,7 +25,7 @@ public class CardStatus extends CardResponse {
boolean testnet; boolean testnet;
public boolean isInitialized() { 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() { public String getIdentifier() {
@ -42,11 +43,11 @@ public class CardStatus extends CardResponse {
} }
public boolean requiresBackup() { public boolean requiresBackup() {
return num_backups == null || num_backups.intValue() == 0; return !satschip && (num_backups == null || num_backups.intValue() == 0);
} }
public WalletModel getCardType() { public WalletModel getCardType() {
return tapsigner ? WalletModel.TAPSIGNER : WalletModel.SATSCARD; return satschip ? WalletModel.SATSCHIP : (tapsigner ? WalletModel.TAPSIGNER : WalletModel.SATSCARD);
} }
public int getCurrentSlot() { public int getCurrentSlot() {
@ -72,6 +73,7 @@ public class CardStatus extends CardResponse {
", ver='" + ver + '\'' + ", ver='" + ver + '\'' +
", birth=" + birth + ", birth=" + birth +
", tapsigner=" + tapsigner + ", tapsigner=" + tapsigner +
", satschip=" + satschip +
", path=" + path + ", path=" + path +
", num_backups=" + num_backups + ", num_backups=" + num_backups +
", slots=" + slots + ", slots=" + slots +

View file

@ -32,10 +32,6 @@ public class CkCardApi extends CardApi {
private final CardProtocol cardProtocol; private final CardProtocol cardProtocol;
private String cvc; private String cvc;
public CkCardApi(String cvc) throws CardException {
this(WalletModel.TAPSIGNER, cvc);
}
public CkCardApi(WalletModel cardType, String cvc) throws CardException { public CkCardApi(WalletModel cardType, String cvc) throws CardException {
this.cardType = cardType; this.cardType = cardType;
this.cardProtocol = new CardProtocol(); this.cardProtocol = new CardProtocol();
@ -137,6 +133,8 @@ public class CkCardApi extends CardApi {
public Service<Void> getInitializationService(byte[] entropy, StringProperty messageProperty) { public Service<Void> getInitializationService(byte[] entropy, StringProperty messageProperty) {
if(cardType == WalletModel.TAPSIGNER) { if(cardType == WalletModel.TAPSIGNER) {
return new CardImportPane.CardInitializationService(new Tapsigner(), cvc, entropy, messageProperty); 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); return new CardInitializationService(entropy, messageProperty);
@ -144,6 +142,10 @@ public class CkCardApi extends CardApi {
@Override @Override
public Service<Keystore> getImportService(List<ChildNumber> derivation, StringProperty messageProperty) { public Service<Keystore> getImportService(List<ChildNumber> 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); 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)); ExtendedKey derivedXpubkey = ExtendedKey.fromDescriptor(Base58.encodeChecked(derivedXpub.xpub));
Keystore keystore = new Keystore(); Keystore keystore = new Keystore();
keystore.setLabel(WalletModel.TAPSIGNER.toDisplayString()); keystore.setLabel(cardType.toDisplayString());
keystore.setKeyDerivation(keyDerivation); keystore.setKeyDerivation(keyDerivation);
keystore.setSource(KeystoreSource.HW_AIRGAPPED); keystore.setSource(KeystoreSource.HW_AIRGAPPED);
keystore.setExtendedPublicKey(derivedXpubkey); keystore.setExtendedPublicKey(derivedXpubkey);
keystore.setWalletModel(WalletModel.TAPSIGNER); keystore.setWalletModel(cardType);
return keystore; return keystore;
} }
@ -404,7 +406,7 @@ public class CkCardApi extends CardApi {
@Override @Override
protected PSBT call() throws Exception { protected PSBT call() throws Exception {
CardStatus cardStatus = getStatus(); 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."); throw new IllegalStateException("Please use a " + WalletModel.TAPSIGNER.toDisplayString() + " to sign transactions.");
} }
@ -465,7 +467,7 @@ public class CkCardApi extends CardApi {
@Override @Override
protected String call() throws Exception { protected String call() throws Exception {
CardStatus cardStatus = getStatus(); 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."); throw new IllegalStateException("Please use a " + WalletModel.TAPSIGNER.toDisplayString() + " to sign messages.");
} }

View file

@ -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;
}
}

View file

@ -16,7 +16,7 @@ public class Tapsigner implements KeystoreCardImport {
public boolean isInitialized() throws CardException { public boolean isInitialized() throws CardException {
CkCardApi cardApi = null; CkCardApi cardApi = null;
try { try {
cardApi = new CkCardApi(null); cardApi = new CkCardApi(getWalletModel(), null);
return cardApi.isInitialized(); return cardApi.isInitialized();
} finally { } finally {
if(cardApi != null) { if(cardApi != null) {
@ -37,7 +37,7 @@ public class Tapsigner implements KeystoreCardImport {
CkCardApi cardApi = null; CkCardApi cardApi = null;
try { try {
cardApi = new CkCardApi(pin); cardApi = new CkCardApi(getWalletModel(), pin);
CardStatus cardStatus = cardApi.getStatus(); CardStatus cardStatus = cardApi.getStatus();
if(cardStatus.isInitialized()) { if(cardStatus.isInitialized()) {
throw new IllegalStateException("Card is already initialized."); throw new IllegalStateException("Card is already initialized.");
@ -63,7 +63,7 @@ public class Tapsigner implements KeystoreCardImport {
CkCardApi cardApi = null; CkCardApi cardApi = null;
try { try {
cardApi = new CkCardApi(pin); cardApi = new CkCardApi(getWalletModel(), pin);
CardStatus cardStatus = cardApi.getStatus(); CardStatus cardStatus = cardApi.getStatus();
if(!cardStatus.isInitialized()) { if(!cardStatus.isInitialized()) {
throw new IllegalStateException("Card is not initialized."); throw new IllegalStateException("Card is not initialized.");

View file

@ -5,6 +5,7 @@ import com.sparrowwallet.sparrow.control.CardImportPane;
import com.sparrowwallet.sparrow.control.FileKeystoreImportPane; import com.sparrowwallet.sparrow.control.FileKeystoreImportPane;
import com.sparrowwallet.sparrow.control.TitledDescriptionPane; import com.sparrowwallet.sparrow.control.TitledDescriptionPane;
import com.sparrowwallet.sparrow.io.*; import com.sparrowwallet.sparrow.io.*;
import com.sparrowwallet.sparrow.io.ckcard.Satschip;
import com.sparrowwallet.sparrow.io.ckcard.Tapsigner; import com.sparrowwallet.sparrow.io.ckcard.Tapsigner;
import com.sparrowwallet.sparrow.io.satochip.Satochip; import com.sparrowwallet.sparrow.io.satochip.Satochip;
import javafx.fxml.FXML; import javafx.fxml.FXML;
@ -39,7 +40,7 @@ public class HwAirgappedController extends KeystoreImportDetailController {
} }
} }
List<KeystoreCardImport> cardImporters = List.of(new Tapsigner(), new Satochip()); List<KeystoreCardImport> cardImporters = List.of(new Tapsigner(), new Satochip(), new Satschip());
for(KeystoreCardImport importer : cardImporters) { for(KeystoreCardImport importer : cardImporters) {
if(!importer.isDeprecated() || Config.get().isShowDeprecatedImportExport()) { if(!importer.isDeprecated() || Config.get().isShowDeprecatedImportExport()) {
CardImportPane importPane = new CardImportPane(getMasterController().getWallet(), importer, getMasterController().getRequiredDerivation()); CardImportPane importPane = new CardImportPane(getMasterController().getWallet(), importer, getMasterController().getRequiredDerivation());

View file

@ -493,7 +493,7 @@ public class KeystoreController extends WalletFormController implements Initiali
log.error("Error communicating with card", e); log.error("Error communicating with card", e);
AppServices.showErrorDialog("Error communicating with card", e.getMessage()); 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()); serviceProgressDialog.initOwner(cardServiceButtons.getScene().getWindow());
AppServices.moveToActiveWindowScreen(serviceProgressDialog); AppServices.moveToActiveWindowScreen(serviceProgressDialog);
authDelayService.start(); authDelayService.start();

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="15px" height="15px" viewBox="0 0 15 15" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(88.627451%,88.627451%,88.627451%);fill-opacity:1;" d="M 2.605469 2.015625 L 12.394531 2.015625 C 12.722656 2.015625 12.984375 2.277344 12.984375 2.605469 L 12.984375 12.394531 C 12.984375 12.722656 12.722656 12.984375 12.394531 12.984375 L 2.605469 12.984375 C 2.277344 12.984375 2.015625 12.722656 2.015625 12.394531 L 2.015625 2.605469 C 2.015625 2.277344 2.277344 2.015625 2.605469 2.015625 Z M 2.605469 2.015625 "/>
<path style="fill:none;stroke-width:80;stroke-linecap:butt;stroke-linejoin:round;stroke:rgb(17.254902%,17.254902%,17.254902%);stroke-opacity:1;stroke-miterlimit:10;" d="M 825.78125 308.072917 C 805.364583 371.145833 784.21875 434.21875 755.416667 494.010417 C 744.84375 515.885417 732.083333 537.03125 718.958333 557.447917 C 702.916667 582.239583 678.854167 592.8125 649.6875 590.989583 C 644.947917 590.625 639.479167 590.625 634.739583 591.354167 C 615.78125 593.541667 603.020833 604.114583 594.635417 620.520833 C 587.34375 634.739583 584.0625 650.052083 582.239583 665.729167 C 579.322917 692.34375 580.78125 718.229167 588.4375 743.75 C 591.71875 755.052083 596.822917 765.625 604.84375 774.010417 C 614.322917 784.21875 625.989583 788.59375 639.479167 789.322917 C 647.864583 789.6875 656.25 789.322917 664.270833 789.6875 C 687.96875 790.78125 704.375 803.541667 718.229167 821.40625 C 737.552083 846.927083 751.40625 875.729167 764.166667 905.260417 C 788.59375 959.947917 807.916667 1016.822917 826.510417 1073.697917 " transform="matrix(0.0107143,0,0,0.0107143,0,0)"/>
<path style="fill:none;stroke-width:50;stroke-linecap:butt;stroke-linejoin:round;stroke:rgb(17.254902%,17.254902%,17.254902%);stroke-opacity:1;stroke-miterlimit:10;" d="M 699.635417 302.239583 L 699.635417 1077.708333 " transform="matrix(0.0107143,0,0,0.0107143,0,0)"/>
<path style="fill:none;stroke-width:50;stroke-linecap:butt;stroke-linejoin:round;stroke:rgb(17.254902%,17.254902%,17.254902%);stroke-opacity:1;stroke-miterlimit:10;" d="M 316.822917 690.520833 L 1083.541667 690.520833 " transform="matrix(0.0107143,0,0,0.0107143,0,0)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="15px" height="15px" viewBox="0 0 15 15" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(23.137255%,23.137255%,23.137255%);fill-opacity:1;" d="M 2.605469 2.015625 L 12.394531 2.015625 C 12.722656 2.015625 12.984375 2.277344 12.984375 2.605469 L 12.984375 12.394531 C 12.984375 12.722656 12.722656 12.984375 12.394531 12.984375 L 2.605469 12.984375 C 2.277344 12.984375 2.015625 12.722656 2.015625 12.394531 L 2.015625 2.605469 C 2.015625 2.277344 2.277344 2.015625 2.605469 2.015625 Z M 2.605469 2.015625 "/>
<path style="fill:none;stroke-width:80;stroke-linecap:butt;stroke-linejoin:round;stroke:rgb(88.627451%,88.627451%,88.627451%);stroke-opacity:1;stroke-miterlimit:10;" d="M 825.78125 308.072917 C 805.364583 371.145833 784.21875 434.21875 755.416667 494.010417 C 744.84375 515.885417 732.083333 537.03125 718.958333 557.447917 C 702.916667 582.239583 678.854167 592.8125 649.6875 590.989583 C 644.947917 590.625 639.479167 590.625 634.739583 591.354167 C 615.78125 593.541667 603.020833 604.114583 594.635417 620.520833 C 587.34375 634.739583 584.0625 650.052083 582.239583 665.729167 C 579.322917 692.34375 580.78125 718.229167 588.4375 743.75 C 591.71875 755.052083 596.822917 765.625 604.84375 774.010417 C 614.322917 784.21875 625.989583 788.59375 639.479167 789.322917 C 647.864583 789.6875 656.25 789.322917 664.270833 789.6875 C 687.96875 790.78125 704.375 803.541667 718.229167 821.40625 C 737.552083 846.927083 751.40625 875.729167 764.166667 905.260417 C 788.59375 959.947917 807.916667 1016.822917 826.510417 1073.697917 " transform="matrix(0.0107143,0,0,0.0107143,0,0)"/>
<path style="fill:none;stroke-width:50;stroke-linecap:butt;stroke-linejoin:round;stroke:rgb(88.627451%,88.627451%,88.627451%);stroke-opacity:1;stroke-miterlimit:10;" d="M 699.635417 302.239583 L 699.635417 1077.708333 " transform="matrix(0.0107143,0,0,0.0107143,0,0)"/>
<path style="fill:none;stroke-width:50;stroke-linecap:butt;stroke-linejoin:round;stroke:rgb(88.627451%,88.627451%,88.627451%);stroke-opacity:1;stroke-miterlimit:10;" d="M 316.822917 690.520833 L 1083.541667 690.520833 " transform="matrix(0.0107143,0,0,0.0107143,0,0)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB