From da736c8cef812f41f10c76d861abf06acbb4b6af Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Thu, 21 Aug 2025 11:43:19 +0200 Subject: [PATCH] use map of scriptpubkeys instead of transaction outputs for tweak computation --- .../silentpayments/SilentPaymentUtils.java | 31 ++++++++++--------- .../SilentPaymentUtilsTest.java | 24 +++++++------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/sparrowwallet/drongo/silentpayments/SilentPaymentUtils.java b/src/main/java/com/sparrowwallet/drongo/silentpayments/SilentPaymentUtils.java index 90e5024..3f0fe2a 100644 --- a/src/main/java/com/sparrowwallet/drongo/silentpayments/SilentPaymentUtils.java +++ b/src/main/java/com/sparrowwallet/drongo/silentpayments/SilentPaymentUtils.java @@ -18,35 +18,35 @@ public class SilentPaymentUtils { private static final List SCRIPT_TYPES = List.of(ScriptType.P2TR, ScriptType.P2WPKH, ScriptType.P2SH_P2WPKH, ScriptType.P2PKH); - public static boolean isEligible(Transaction tx, Map spentOutputs) { + public static boolean isEligible(Transaction tx, Map spentScriptPubKeys) { if(!containsTaprootOutput(tx)) { return false; } - if(getInputPubKeys(tx, spentOutputs).isEmpty()) { + if(getInputPubKeys(tx, spentScriptPubKeys).isEmpty()) { return false; } - if(spendsInvalidSegwitOutput(tx, spentOutputs)) { + if(spendsInvalidSegwitOutput(tx, spentScriptPubKeys)) { return false; } return true; } - public static List getInputPubKeys(Transaction tx, Map spentOutputs) { + public static List getInputPubKeys(Transaction tx, Map spentScriptPubKeys) { List keys = new ArrayList<>(); for(TransactionInput input : tx.getInputs()) { HashIndex hashIndex = new HashIndex(input.getOutpoint().getHash(), input.getOutpoint().getIndex()); - TransactionOutput output = spentOutputs.get(hashIndex); - if(output == null) { - throw new IllegalStateException("No output found for input " + input.getOutpoint()); + Script scriptPubKey = spentScriptPubKeys.get(hashIndex); + if(scriptPubKey == null) { + throw new IllegalStateException("No scriptPubKey found for input " + input.getOutpoint().getHash() + ":" + input.getOutpoint().getIndex()); } for(ScriptType scriptType : SCRIPT_TYPES) { - if(scriptType.isScriptType(output.getScript())) { + if(scriptType.isScriptType(scriptPubKey)) { switch(scriptType) { case P2TR: - keys.add(ScriptType.P2TR.getPublicKeyFromScript(output.getScript())); + keys.add(ScriptType.P2TR.getPublicKeyFromScript(scriptPubKey)); break; case P2WPKH: case P2SH_P2WPKH: @@ -84,10 +84,13 @@ public class SilentPaymentUtils { return false; } - public static boolean spendsInvalidSegwitOutput(Transaction tx, Map spentOutputs) { + public static boolean spendsInvalidSegwitOutput(Transaction tx, Map spentScriptPubKeys) { for(TransactionInput input : tx.getInputs()) { HashIndex hashIndex = new HashIndex(input.getOutpoint().getHash(), input.getOutpoint().getIndex()); - Script scriptPubKey = spentOutputs.get(hashIndex).getScript(); + Script scriptPubKey = spentScriptPubKeys.get(hashIndex); + if(scriptPubKey == null) { + throw new IllegalStateException("No scriptPubKey found for input " + input.getOutpoint().getHash() + ":" + input.getOutpoint().getIndex()); + } List chunks = scriptPubKey.getChunks(); if(chunks.size() == 2 && chunks.getFirst().isOpCode() && chunks.get(1).getData() != null && chunks.getFirst().getOpcode() >= ScriptOpCodes.OP_2 && chunks.getFirst().getOpcode() <= ScriptOpCodes.OP_16) { @@ -98,16 +101,16 @@ public class SilentPaymentUtils { return false; } - public static byte[] getTweak(Transaction tx, Map spentOutputs) { + public static byte[] getTweak(Transaction tx, Map spentScriptPubKeys) { if(tx.getOutputs().stream().noneMatch(output -> ScriptType.P2TR.isScriptType(output.getScript()))) { return null; } - if(spendsInvalidSegwitOutput(tx, spentOutputs)) { + if(spendsInvalidSegwitOutput(tx, spentScriptPubKeys)) { return null; } - List inputKeys = getInputPubKeys(tx, spentOutputs); + List inputKeys = getInputPubKeys(tx, spentScriptPubKeys); if(inputKeys.isEmpty()) { return null; } diff --git a/src/test/java/com/sparrowwallet/drongo/silentpayments/SilentPaymentUtilsTest.java b/src/test/java/com/sparrowwallet/drongo/silentpayments/SilentPaymentUtilsTest.java index 285329a..9bb3a46 100644 --- a/src/test/java/com/sparrowwallet/drongo/silentpayments/SilentPaymentUtilsTest.java +++ b/src/test/java/com/sparrowwallet/drongo/silentpayments/SilentPaymentUtilsTest.java @@ -17,15 +17,15 @@ public class SilentPaymentUtilsTest { transaction.addInput(Sha256Hash.wrap("a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d"), 0, new Script(Utils.hexToBytes("48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792"))); transaction.addOutput(1000L, ScriptType.P2TR.getOutputScript(ECKey.fromPublicOnly(Utils.hexToBytes("3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1")))); - Map spentOutputs = new HashMap<>(); + Map spentScriptPubKeys = new HashMap<>(); HashIndex hashIndex0 = new HashIndex(transaction.getInputs().getFirst().getOutpoint().getHash(), transaction.getInputs().getFirst().getOutpoint().getIndex()); - TransactionOutput spentOutput0 = new TransactionOutput(new Transaction(), 400L, new Script(Utils.hexToBytes("76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac"))); + Script spentScriptPubKey0 = new Script(Utils.hexToBytes("76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac")); HashIndex hashIndex1 = new HashIndex(transaction.getInputs().getLast().getOutpoint().getHash(), transaction.getInputs().getLast().getOutpoint().getIndex()); - TransactionOutput spentOutput1 = new TransactionOutput(new Transaction(), 400L, new Script(Utils.hexToBytes("76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac"))); - spentOutputs.put(hashIndex0, spentOutput0); - spentOutputs.put(hashIndex1, spentOutput1); + Script spentScriptPubKey1 = new Script(Utils.hexToBytes("76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac")); + spentScriptPubKeys.put(hashIndex0, spentScriptPubKey0); + spentScriptPubKeys.put(hashIndex1, spentScriptPubKey1); - byte[] tweak = SilentPaymentUtils.getTweak(transaction, spentOutputs); + byte[] tweak = SilentPaymentUtils.getTweak(transaction, spentScriptPubKeys); Assertions.assertNotNull(tweak); Assertions.assertEquals("024ac253c216532e961988e2a8ce266a447c894c781e52ef6cee902361db960004", Utils.bytesToHex(tweak)); } @@ -37,15 +37,15 @@ public class SilentPaymentUtilsTest { transaction.addInput(Sha256Hash.wrap("f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16"), 0, new Script(Utils.hexToBytes("48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792"))); transaction.addOutput(1000L, ScriptType.P2TR.getOutputScript(ECKey.fromPublicOnly(Utils.hexToBytes("3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1")))); - Map spentOutputs = new HashMap<>(); + Map spentScriptPubKeys = new HashMap<>(); HashIndex hashIndex0 = new HashIndex(transaction.getInputs().getFirst().getOutpoint().getHash(), transaction.getInputs().getFirst().getOutpoint().getIndex()); - TransactionOutput spentOutput0 = new TransactionOutput(new Transaction(), 400L, new Script(Utils.hexToBytes("76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac"))); + Script spentScriptPubKey0 = new Script(Utils.hexToBytes("76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac")); HashIndex hashIndex1 = new HashIndex(transaction.getInputs().getLast().getOutpoint().getHash(), transaction.getInputs().getLast().getOutpoint().getIndex()); - TransactionOutput spentOutput1 = new TransactionOutput(new Transaction(), 400L, new Script(Utils.hexToBytes("76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac"))); - spentOutputs.put(hashIndex0, spentOutput0); - spentOutputs.put(hashIndex1, spentOutput1); + Script spentScriptPubKey1 = new Script(Utils.hexToBytes("76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac")); + spentScriptPubKeys.put(hashIndex0, spentScriptPubKey0); + spentScriptPubKeys.put(hashIndex1, spentScriptPubKey1); - byte[] tweak = SilentPaymentUtils.getTweak(transaction, spentOutputs); + byte[] tweak = SilentPaymentUtils.getTweak(transaction, spentScriptPubKeys); Assertions.assertNotNull(tweak); Assertions.assertEquals("024ac253c216532e961988e2a8ce266a447c894c781e52ef6cee902361db960004", Utils.bytesToHex(tweak)); }