From 7aa33508611b8c3386612a04d539fbc9095d0385 Mon Sep 17 00:00:00 2001 From: Andreas Schildbach Date: Wed, 20 Nov 2024 17:58:46 +0100 Subject: [PATCH] ScriptPattern: simplify isWitnessCommitment() and extractWitnessCommitmentHash() It now operates on bytes, rather than chunks. This also adds a bit of JavaDoc and a couple of tests. --- .../org/bitcoinj/script/ScriptPattern.java | 22 ++++---- .../bitcoinj/script/ScriptPatternTest.java | 54 +++++++++++++++++++ 2 files changed, 64 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/script/ScriptPattern.java b/core/src/main/java/org/bitcoinj/script/ScriptPattern.java index a2d06be84cd..aa9b8053c87 100644 --- a/core/src/main/java/org/bitcoinj/script/ScriptPattern.java +++ b/core/src/main/java/org/bitcoinj/script/ScriptPattern.java @@ -258,25 +258,23 @@ public static boolean isOpReturn(Script script) { /** * Returns whether this script matches the pattern for a segwit commitment (in an output of the coinbase * transaction). + * See BIP141. */ public static boolean isWitnessCommitment(Script script) { - List chunks = script.chunks; - if (chunks.size() < 2) - return false; - if (!chunks.get(0).equalsOpCode(ScriptOpCodes.OP_RETURN)) - return false; - byte[] chunkData = chunks.get(1).data; - if (chunkData == null || chunkData.length != 36) - return false; - if (!Arrays.equals(Arrays.copyOfRange(chunkData, 0, 4), SEGWIT_COMMITMENT_HEADER)) - return false; - return true; + byte[] bytes = script.getProgram(); + return bytes.length >= 38 + && bytes[0] == ScriptOpCodes.OP_RETURN + && bytes[1] == 36 // length byte + && Arrays.equals(Arrays.copyOfRange(bytes, 2, 6), SEGWIT_COMMITMENT_HEADER); } /** * Retrieves the hash from a segwit commitment (in an output of the coinbase transaction). + * You will want to guard calls to this method with {@link #isWitnessCommitment(Script)}. + * See BIP141. */ public static Sha256Hash extractWitnessCommitmentHash(Script script) { - return Sha256Hash.wrap(Arrays.copyOfRange(script.chunks.get(1).data, 4, 36)); + byte[] hash = Arrays.copyOfRange(script.getProgram(), 6, 38); + return Sha256Hash.wrap(hash); } } diff --git a/core/src/test/java/org/bitcoinj/script/ScriptPatternTest.java b/core/src/test/java/org/bitcoinj/script/ScriptPatternTest.java index 2e078391764..2beb5eb1019 100644 --- a/core/src/test/java/org/bitcoinj/script/ScriptPatternTest.java +++ b/core/src/test/java/org/bitcoinj/script/ScriptPatternTest.java @@ -21,11 +21,14 @@ import com.google.common.collect.Lists; import org.bitcoinj.core.ECKey; +import org.bitcoinj.core.Sha256Hash; +import org.bitcoinj.core.Utils; import org.junit.Test; import java.util.List; import static org.bitcoinj.script.ScriptOpCodes.OP_CHECKMULTISIG; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -93,4 +96,55 @@ public void testCreateOpReturnScript() { ScriptBuilder.createOpReturnScript(new byte[10]) )); } + + @Test + public void isWitnessCommitment() { + // OP_RETURN <1-byte length 36> <4-byte commitment header> <32 bytes commitment> + String hex = "6a24aa21a9ed0000000000000000000000000000000000000000000000000000000000000000"; + Script script = new Script(Utils.HEX.decode(hex)); + assertTrue(ScriptPattern.isWitnessCommitment(script)); + assertEquals(Sha256Hash.ZERO_HASH, ScriptPattern.extractWitnessCommitmentHash(script)); + } + + @Test + public void isWitnessCommitment_tooShort() { + // OP_RETURN <1-byte length 35> <4-byte commitment header> <31 bytes commitment> + String hex = "6a23aa21a9ed00000000000000000000000000000000000000000000000000000000000000"; + Script script = new Script(Utils.HEX.decode(hex)); + assertFalse(ScriptPattern.isWitnessCommitment(script)); + } + + @Test + public void isWitnessCommitment_tooLong() { + // OP_RETURN <1-byte length 37> <4-byte commitment header> <33 bytes commitment> + String hex = "6a25aa21a9ed000000000000000000000000000000000000000000000000000000000000000000"; + Script script = new Script(Utils.HEX.decode(hex)); + assertFalse(ScriptPattern.isWitnessCommitment(script)); + } + + @Test + public void isWitnessCommitment_noOpReturn() { + // OP_NOP <1-byte length 36> <4-byte commitment header> <32 bytes commitment> + String hex = "6124aa21a9ed0000000000000000000000000000000000000000000000000000000000000000"; + Script script = new Script(Utils.HEX.decode(hex)); + assertFalse(ScriptPattern.isWitnessCommitment(script)); + } + + @Test + public void isWitnessCommitment_wrongCommitmentHeader() { + // OP_RETURN <1-byte length 36> <4-byte commitment header> <32 bytes commitment> + String hex = "6a24ffffffff0000000000000000000000000000000000000000000000000000000000000000"; + Script script = new Script(Utils.HEX.decode(hex)); + assertFalse(ScriptPattern.isWitnessCommitment(script)); + } + + @Test + public void extractWitnessCommitmentHash() { + // OP_RETURN <1-byte length 36> <4-byte commitment header> <32 bytes commitment> + String hex = "6a24aa21a9ed0000000000000000000000000000000000000000000000000000000000000000"; + Script script = new Script(Utils.HEX.decode(hex)); + Sha256Hash hash = ScriptPattern.extractWitnessCommitmentHash(script); + assertEquals("0000000000000000000000000000000000000000000000000000000000000000", + Utils.HEX.encode(hash.getBytes())); + } }