use map of scriptpubkeys instead of transaction outputs for tweak computation

This commit is contained in:
Craig Raw 2025-08-21 11:43:19 +02:00
parent a4d86f9ee3
commit da736c8cef
2 changed files with 29 additions and 26 deletions

View file

@ -18,35 +18,35 @@ public class SilentPaymentUtils {
private static final List<ScriptType> SCRIPT_TYPES = List.of(ScriptType.P2TR, ScriptType.P2WPKH, ScriptType.P2SH_P2WPKH, ScriptType.P2PKH);
public static boolean isEligible(Transaction tx, Map<HashIndex, TransactionOutput> spentOutputs) {
public static boolean isEligible(Transaction tx, Map<HashIndex, Script> 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<ECKey> getInputPubKeys(Transaction tx, Map<HashIndex, TransactionOutput> spentOutputs) {
public static List<ECKey> getInputPubKeys(Transaction tx, Map<HashIndex, Script> spentScriptPubKeys) {
List<ECKey> 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<HashIndex, TransactionOutput> spentOutputs) {
public static boolean spendsInvalidSegwitOutput(Transaction tx, Map<HashIndex, Script> 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<ScriptChunk> 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<HashIndex, TransactionOutput> spentOutputs) {
public static byte[] getTweak(Transaction tx, Map<HashIndex, Script> 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<ECKey> inputKeys = getInputPubKeys(tx, spentOutputs);
List<ECKey> inputKeys = getInputPubKeys(tx, spentScriptPubKeys);
if(inputKeys.isEmpty()) {
return null;
}

View file

@ -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<HashIndex, TransactionOutput> spentOutputs = new HashMap<>();
Map<HashIndex, Script> 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<HashIndex, TransactionOutput> spentOutputs = new HashMap<>();
Map<HashIndex, Script> 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));
}