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()));
+ }
}