From 2f1644ee16752b596dc64afb3320b2423aeea3b4 Mon Sep 17 00:00:00 2001 From: Toporin Date: Wed, 6 Sep 2023 09:07:40 +0100 Subject: [PATCH] Add satochip support: initial commit with debug traces todo: clean code --- .../sparrowwallet/drongo/crypto/ECKey.java | 110 ++++++++++++++++++ .../sparrowwallet/drongo/psbt/PSBTInput.java | 78 +++++++++++++ .../drongo/wallet/WalletModel.java | 15 ++- 3 files changed, 202 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/sparrowwallet/drongo/crypto/ECKey.java b/src/main/java/com/sparrowwallet/drongo/crypto/ECKey.java index 996456c..5b6fe67 100644 --- a/src/main/java/com/sparrowwallet/drongo/crypto/ECKey.java +++ b/src/main/java/com/sparrowwallet/drongo/crypto/ECKey.java @@ -307,6 +307,11 @@ public class ECKey { return pub.getEncoded(); } + // SATOCHIP + public byte[] getPubKey(Boolean compressed) { + return pub.getEncoded(compressed); + } + /** * Gets the x coordinate of the raw public key value. This appears in transaction scriptPubKeys for Taproot outputs. */ @@ -428,14 +433,119 @@ public class ECKey { return verify(sigHash.getBytes(), signature); } + public ECKey getTweakedOutputKey() { + log.debug("SATOCHIP ECKey getTweakedOutputKey START"); TaprootPubKey taprootPubKey = liftX(getPubKeyXCoord()); + log.debug("SATOCHIP ECKey getTweakedOutputKey taprootPubKey: " + taprootPubKey); + log.debug("SATOCHIP ECKey getTweakedOutputKey taprootPubKey.ecPoint: " + taprootPubKey.ecPoint); ECPoint internalKey = taprootPubKey.ecPoint; + log.debug("SATOCHIP ECKey getTweakedOutputKey internalKey: " + internalKey); + //debug + ECKey tmp2 = ECKey.fromPublicOnly(internalKey, true); + log.debug("SATOCHIP ECKey getTweakedOutputKey internalKey: " + Utils.bytesToHex(tmp2.getPubKey())); + //endbug + byte[] taggedHash = Utils.taggedHash("TapTweak", internalKey.getXCoord().getEncoded()); ECKey tweakValue = ECKey.fromPrivate(taggedHash); + log.debug("SATOCHIP ECKey getTweakedOutputKey tweakValue: " + Utils.bytesToHex(tweakValue.getPubKey())); ECPoint outputKey = internalKey.add(tweakValue.getPubKeyPoint()); + log.debug("SATOCHIP ECKey getTweakedOutputKey outputKey: " + outputKey); + + //debug + ECKey tmp = ECKey.fromPublicOnly(outputKey, true); + log.debug("SATOCHIP ECKey getTweakedOutputKey outputKey: " + Utils.bytesToHex(tmp.getPubKey())); + //endbug if(hasPrivKey()) { + log.debug("SATOCHIP ECKey getTweakedOutputKey PRIVKEY NEW VERSION"); + + // isEven => used to determine private key for tweaking + Boolean isEven = (getPubKey()[0] == 0x02); + log.debug("SATOCHIP ECKey getTweakedOutputKey getPubKey(): " + Utils.bytesToHex(getPubKey())); + log.debug("SATOCHIP ECKey getTweakedOutputKey isEven getPubKey()[0]: " + getPubKey()[0]); + log.debug("SATOCHIP ECKey getTweakedOutputKey isEven: " + isEven); + + BigInteger taprootPriv; + if (isEven){ + taprootPriv = priv; + } else { + taprootPriv = CURVE_PARAMS.getCurve().getOrder().subtract(priv); + } + BigInteger tweakedPrivKey = taprootPriv.add(tweakValue.getPrivKey()).mod(CURVE_PARAMS.getCurve().getOrder()); + + //debug + ECKey tmp3 = new ECKey(tweakedPrivKey, outputKey, true); + log.debug("SATOCHIP ECKey getTweakedOutputKey outputKey with private: " + Utils.bytesToHex(tmp3.getPubKey())); + log.debug("SATOCHIP ECKey getTweakedOutputKey outputKey private: " + tmp3.getPrivKey()); + //endbug + + +/* log.debug("SATOCHIP ECKey getTweakedOutputKey PRIVKEY NEW VERSION SWITCH EVENNESS"); + if (isEven){ + //taprootPriv = priv; + taprootPriv = CURVE_PARAMS.getCurve().getOrder().subtract(priv); + } else { + taprootPriv = priv; + //taprootPriv = CURVE_PARAMS.getCurve().getOrder().subtract(priv); + } + tweakedPrivKey = taprootPriv.add(tweakValue.getPrivKey()).mod(CURVE_PARAMS.getCurve().getOrder()); + + //debug + ECKey tmp5 = new ECKey(tweakedPrivKey, outputKey, true); + log.debug("SATOCHIP ECKey getTweakedOutputKey outputKey with private: " + Utils.bytesToHex(tmp5.getPubKey())); + log.debug("SATOCHIP ECKey getTweakedOutputKey outputKey private: " + tmp5.getPrivKey()); + //endbug*/ + + +/* // ORIGNAL VERSION + log.debug("SATOCHIP ECKey getTweakedOutputKey PRIVKEY OLD VERSION"); + taprootPriv = priv; + tweakedPrivKey = taprootPriv.add(tweakValue.getPrivKey()).mod(CURVE_PARAMS.getCurve().getOrder()); + //TODO: Improve on this hack. How do we know whether to negate the private key before tweaking it? + if(!ECKey.fromPrivate(tweakedPrivKey).getPubKeyPoint().equals(outputKey)) { + taprootPriv = CURVE_PARAMS.getCurve().getOrder().subtract(priv); + tweakedPrivKey = taprootPriv.add(tweakValue.getPrivKey()).mod(CURVE_PARAMS.getCurve().getOrder()); + } + //debug + ECKey tmp4 = new ECKey(tweakedPrivKey, outputKey, true); + log.debug("SATOCHIP ECKey getTweakedOutputKey outputKey with private: " + Utils.bytesToHex(tmp4.getPubKey())); + log.debug("SATOCHIP ECKey getTweakedOutputKey outputKey private: " + tmp4.getPrivKey()); + //endbug*/ + + + return new ECKey(tweakedPrivKey, outputKey, true); + } + + return ECKey.fromPublicOnly(outputKey, true); + } + + + public ECKey getTweakedOutputKeyOLD() { + log.debug("SATOCHIP ECKey getTweakedOutputKey START"); + TaprootPubKey taprootPubKey = liftX(getPubKeyXCoord()); + log.debug("SATOCHIP ECKey getTweakedOutputKey taprootPubKey: " + taprootPubKey); + log.debug("SATOCHIP ECKey getTweakedOutputKey taprootPubKey.ecPoint: " + taprootPubKey.ecPoint); + ECPoint internalKey = taprootPubKey.ecPoint; + log.debug("SATOCHIP ECKey getTweakedOutputKey internalKey: " + internalKey); + //debug + ECKey tmp2 = ECKey.fromPublicOnly(internalKey, true); + log.debug("SATOCHIP ECKey getTweakedOutputKey outputKey: " + Utils.bytesToHex(tmp2.getPubKey())); + //endbug + + byte[] taggedHash = Utils.taggedHash("TapTweak", internalKey.getXCoord().getEncoded()); + ECKey tweakValue = ECKey.fromPrivate(taggedHash); + log.debug("SATOCHIP ECKey getTweakedOutputKey tweakValue: " + Utils.bytesToHex(tweakValue.getPubKey())); + ECPoint outputKey = internalKey.add(tweakValue.getPubKeyPoint()); + log.debug("SATOCHIP ECKey getTweakedOutputKey outputKey: " + outputKey); + + //debug + ECKey tmp = ECKey.fromPublicOnly(outputKey, true); + log.debug("SATOCHIP ECKey getTweakedOutputKey outputKey: " + Utils.bytesToHex(tmp.getPubKey())); + //endbug + + if(hasPrivKey()) { + log.debug("SATOCHIP ECKey getTweakedOutputKey hasPrivKey(): true"); BigInteger taprootPriv = priv; BigInteger tweakedPrivKey = taprootPriv.add(tweakValue.getPrivKey()).mod(CURVE_PARAMS.getCurve().getOrder()); //TODO: Improve on this hack. How do we know whether to negate the private key before tweaking it? diff --git a/src/main/java/com/sparrowwallet/drongo/psbt/PSBTInput.java b/src/main/java/com/sparrowwallet/drongo/psbt/PSBTInput.java index eade24e..7681af4 100644 --- a/src/main/java/com/sparrowwallet/drongo/psbt/PSBTInput.java +++ b/src/main/java/com/sparrowwallet/drongo/psbt/PSBTInput.java @@ -250,6 +250,75 @@ public class PSBTInput { this.index = index; } + // satochip debug + public void printDebugInfo(){ + log.debug("SATOCHIP PSBTInput printDebugInfo() START"); + // partialSignatures + try{ + log.debug("SATOCHIP PSBTInput printDebugInfo() partialSignatures: Map"); + log.debug("SATOCHIP PSBTInput printDebugInfo() partialSignatures.size(): " + partialSignatures.size()); + log.debug("SATOCHIP PSBTInput printDebugInfo() partialSignatures: " + partialSignatures); + for (Map.Entry entry : partialSignatures.entrySet()) { + ECKey key = entry.getKey(); + TransactionSignature value = entry.getValue(); + log.debug("SATOCHIP PSBTInput printDebugInfo() partialSignatures pubkey: " + Utils.bytesToHex(key.getPubKey())); + log.debug("SATOCHIP PSBTInput printDebugInfo() partialSignatures sig: " + Utils.bytesToHex(value.encodeToBitcoin())); + } + log.debug("SATOCHIP PSBTInput printDebugInfo() partialSignatures END"); + } catch(Exception e) { + log.debug("SATOCHIP PSBTInput printDebugInfo() partialSignatures exception: " + e); + } + + // derivedPublicKeys + try{ + log.debug("SATOCHIP PSBTInput printDebugInfo() derivedPublicKeys: Map"); + log.debug("SATOCHIP PSBTInput printDebugInfo() derivedPublicKeys.size(): " + derivedPublicKeys.size()); + log.debug("SATOCHIP PSBTInput printDebugInfo() derivedPublicKeys: " + derivedPublicKeys); + for (Map.Entry entry : derivedPublicKeys.entrySet()) { + ECKey key = entry.getKey(); + KeyDerivation value = entry.getValue(); + log.debug("SATOCHIP PSBTInput printDebugInfo() derivedPublicKeys pubkey: " + Utils.bytesToHex(key.getPubKey())); + log.debug("SATOCHIP PSBTInput printDebugInfo() derivedPublicKeys derivation: " + value.getDerivationPath()); + } + log.debug("SATOCHIP PSBTInput printDebugInfo() derivedPublicKeys END"); + } catch(Exception e) { + log.debug("SATOCHIP PSBTInput printDebugInfo() derivedPublicKeys exception: " + e); + } + + // TransactionSignature tapKeyPathSignature; + if (tapKeyPathSignature!=null) { + log.debug("SATOCHIP PSBTInput printDebugInfo() tapKeyPathSignature: " + Utils.bytesToHex(tapKeyPathSignature.encodeToBitcoin())); + } else { + log.debug("SATOCHIP PSBTInput printDebugInfo() tapKeyPathSignature: null"); + } + // ECKey tapInternalKey + if (tapInternalKey!=null) { + log.debug("SATOCHIP PSBTInput printDebugInfo() tapInternalKey: " + Utils.bytesToHex(tapInternalKey.getPubKey())); + } else { + log.debug("SATOCHIP PSBTInput printDebugInfo() tapInternalKey: null"); + } + // tapDerivedPublicKeys + + try { + log.debug("SATOCHIP PSBTInput printDebugInfo() tapDerivedPublicKeys: Map>>"); + log.debug("SATOCHIP PSBTInput printDebugInfo() tapDerivedPublicKeys.size(): " + tapDerivedPublicKeys.size()); + log.debug("SATOCHIP PSBTInput printDebugInfo() tapDerivedPublicKeys: " + tapDerivedPublicKeys); + for (Map.Entry>> entry : tapDerivedPublicKeys.entrySet()) { + ECKey key = entry.getKey(); + Map> value = entry.getValue(); + log.debug("SATOCHIP PSBTInput printDebugInfo() tapDerivedPublicKeys pubkey: " + Utils.bytesToHex(key.getPubKey())); + log.debug("SATOCHIP PSBTInput printDebugInfo() tapDerivedPublicKeys map: " + value); + } + log.debug("SATOCHIP PSBTInput printDebugInfo() tapDerivedPublicKeys END"); + } catch(Exception e) { + log.debug("SATOCHIP PSBTInput printDebugInfo() tapDerivedPublicKeys exception: " + e); + } + + log.debug("SATOCHIP PSBTInput printDebugInfo() END"); + + } + // endbug + public List getInputEntries() { List entries = new ArrayList<>(); @@ -573,6 +642,15 @@ public class PSBTInput { if(isTaproot() && tapKeyPathSignature != null) { ECKey outputKey = P2TR.getPublicKeyFromScript(getUtxo().getScript()); if(!outputKey.verify(hash, tapKeyPathSignature)) { + log.error("SATOCHIP PSBTInput verifySignatures error: " + "Tweaked internal key does not verify against provided taproot keypath signature"); + log.error("SATOCHIP PSBTInput verifySignatures error: psbtinput: " + this); + log.error("SATOCHIP PSBTInput verifySignatures error: outputKey: " + Utils.bytesToHex(outputKey.getPubKey())); + log.error("SATOCHIP PSBTInput verifySignatures error: hash: " + Utils.bytesToHex(hash.getBytes())); + log.error("SATOCHIP PSBTInput verifySignatures error: tapKeyPathSignature: " + Utils.bytesToHex(tapKeyPathSignature.encodeToBitcoin())); + log.error("SATOCHIP PSBTInput verifySignatures error: getUtxo().getScript(): " + getUtxo().getScript()); + log.error("SATOCHIP PSBTInput verifySignatures error: getUtxo(): " + getUtxo()); + log.error("SATOCHIP PSBTInput verifySignatures error: getUtxo().getHash(): " + getUtxo().getHash()); + log.error("SATOCHIP PSBTInput verifySignatures error: getUtxo().getIndex(): " + getUtxo().getIndex()); throw new PSBTSignatureException("Tweaked internal key does not verify against provided taproot keypath signature"); } } else { diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/WalletModel.java b/src/main/java/com/sparrowwallet/drongo/wallet/WalletModel.java index 66586f1..05333f5 100644 --- a/src/main/java/com/sparrowwallet/drongo/wallet/WalletModel.java +++ b/src/main/java/com/sparrowwallet/drongo/wallet/WalletModel.java @@ -4,7 +4,7 @@ import java.util.Locale; public enum WalletModel { SEED, SPARROW, BITCOIN_CORE, ELECTRUM, TREZOR_1, TREZOR_T, COLDCARD, LEDGER_NANO_S, LEDGER_NANO_X, DIGITALBITBOX_01, KEEPKEY, SPECTER_DESKTOP, COBO_VAULT, - BITBOX_02, SPECTER_DIY, PASSPORT, BLUE_WALLET, KEYSTONE, SEEDSIGNER, CARAVAN, GORDIAN_SEED_TOOL, JADE, LEDGER_NANO_S_PLUS, EPS, TAPSIGNER, SATSCARD, LABELS, BSMS; + BITBOX_02, SPECTER_DIY, PASSPORT, BLUE_WALLET, KEYSTONE, SEEDSIGNER, CARAVAN, GORDIAN_SEED_TOOL, JADE, LEDGER_NANO_S_PLUS, EPS, TAPSIGNER, SATSCARD, SATOCHIP, LABELS, BSMS; public static WalletModel getModel(String model) { return valueOf(model.toUpperCase(Locale.ROOT)); @@ -70,6 +70,19 @@ public enum WalletModel { return (this == TAPSIGNER || this == SATSCARD); } + // for card devices that require a PIN code, returns the minimum size of valid PIN code + public int getMinPinLength() { + if (this == TAPSIGNER || this == SATSCARD){ + return 6; + } + else if (this == SATOCHIP){ + return 4; + } + else { + return 0; // default? + } + } + public static WalletModel fromType(String type) { for(WalletModel model : values()) { if(model.getType().equalsIgnoreCase(type)) {