diff --git a/core/src/main/java/org/bitcoinj/script/ScriptPattern.java b/core/src/main/java/org/bitcoinj/script/ScriptPattern.java index 692b8dee582..437b0e2ac06 100644 --- a/core/src/main/java/org/bitcoinj/script/ScriptPattern.java +++ b/core/src/main/java/org/bitcoinj/script/ScriptPattern.java @@ -266,25 +266,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.program(); + 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.program(), 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 8f729cf719d..6f7bad2cd20 100644 --- a/core/src/test/java/org/bitcoinj/script/ScriptPatternTest.java +++ b/core/src/test/java/org/bitcoinj/script/ScriptPatternTest.java @@ -19,6 +19,7 @@ package org.bitcoinj.script; import com.google.common.collect.Lists; +import org.bitcoinj.base.Sha256Hash; import org.bitcoinj.base.internal.ByteUtils; import org.bitcoinj.crypto.DumpedPrivateKey; import org.bitcoinj.crypto.ECKey; @@ -112,4 +113,55 @@ public void p2shScriptHashFromKeys() { byte[] p2shScriptHash = ScriptPattern.extractHashFromP2SH(p2shScript); assertEquals("defdb71910720a2c854529019189228b4245eddd", ByteUtils.formatHex(p2shScriptHash)); } + + @Test + public void isWitnessCommitment() { + // OP_RETURN <1-byte length 36> <4-byte commitment header> <32 bytes commitment> + String hex = "6a24aa21a9ed0000000000000000000000000000000000000000000000000000000000000000"; + Script script = Script.parse(ByteUtils.parseHex(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 = Script.parse(ByteUtils.parseHex(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 = Script.parse(ByteUtils.parseHex(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 = Script.parse(ByteUtils.parseHex(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 = Script.parse(ByteUtils.parseHex(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 = Script.parse(ByteUtils.parseHex(hex)); + Sha256Hash hash = ScriptPattern.extractWitnessCommitmentHash(script); + assertEquals("0000000000000000000000000000000000000000000000000000000000000000", + ByteUtils.formatHex(hash.getBytes())); + } }