diff --git a/core/src/main/java/org/bitcoinj/script/ScriptPattern.java b/core/src/main/java/org/bitcoinj/script/ScriptPattern.java index 613ac0d1..aa28fc6c 100644 --- a/core/src/main/java/org/bitcoinj/script/ScriptPattern.java +++ b/core/src/main/java/org/bitcoinj/script/ScriptPattern.java @@ -19,8 +19,11 @@ package org.bitcoinj.script; import org.bitcoinj.core.LegacyAddress; import org.bitcoinj.core.SegwitAddress; +import org.bitcoinj.core.Sha256Hash; +import org.spongycastle.util.encoders.Hex; import java.math.BigInteger; +import java.util.Arrays; import java.util.List; import static org.bitcoinj.script.Script.decodeFromOpN; @@ -274,4 +277,31 @@ public class ScriptPattern { List chunks = script.chunks; return chunks.size() > 0 && chunks.get(0).equalsOpCode(ScriptOpCodes.OP_RETURN); } + + private static final byte[] SEGWIT_COMMITMENT_HEADER = Hex.decode("aa21a9ed"); + + /** + * Returns whether this script matches the pattern for a segwit commitment (in an output of the coinbase + * transaction). + */ + public static boolean isSegwitCommitment(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; + } + + /** + * Retrieves the hash from a segwit commitment (in an output of the coinbase transaction). + */ + public static Sha256Hash extractSegwitCommitmentHash(Script script) { + return Sha256Hash.wrap(Arrays.copyOfRange(script.chunks.get(1).data, 4, 36)); + } } diff --git a/core/src/test/java/org/bitcoinj/core/BlockTest.java b/core/src/test/java/org/bitcoinj/core/BlockTest.java index 2c63758a..5db06ce3 100644 --- a/core/src/test/java/org/bitcoinj/core/BlockTest.java +++ b/core/src/test/java/org/bitcoinj/core/BlockTest.java @@ -24,12 +24,15 @@ import org.bitcoinj.params.MainNetParams; import org.bitcoinj.params.TestNet2Params; import org.bitcoinj.params.TestNet3Params; import org.bitcoinj.params.UnitTestParams; +import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptOpCodes; +import org.bitcoinj.script.ScriptPattern; import org.bitcoinj.wallet.Wallet; import org.bitcoinj.wallet.Wallet.BalanceType; import org.junit.Before; import org.junit.Test; +import java.io.InputStreamReader; import java.math.BigInteger; import java.util.Arrays; import java.util.EnumSet; @@ -242,6 +245,18 @@ public class BlockTest { assertEquals(Coin.ZERO, wallet.getBalance(BalanceType.AVAILABLE)); } + @Test + public void testBlock481815_segwitCommitmentInCoinbase() throws Exception { + Block block481815 = MAINNET.getDefaultSerializer().makeBlock(ByteStreams.toByteArray( + getClass().getResourceAsStream("block481815.dat"))); + assertEquals(2097, block481815.getTransactions().size()); + Transaction coinbase = block481815.getTransactions().get(0); + final Script segwitCommitment = coinbase.getOutput(1).getScriptPubKey(); + assertTrue(ScriptPattern.isSegwitCommitment(segwitCommitment)); + assertEquals("3d03076733467c45b08ec503a0c5d406647b073e1914d35b5111960ed625f3b7", + ScriptPattern.extractSegwitCommitmentHash(segwitCommitment).toString()); + } + @Test public void isBIPs() throws Exception { final Block genesis = MAINNET.getGenesisBlock(); diff --git a/core/src/test/resources/org/bitcoinj/core/block481815.dat b/core/src/test/resources/org/bitcoinj/core/block481815.dat new file mode 100644 index 00000000..ed358c20 Binary files /dev/null and b/core/src/test/resources/org/bitcoinj/core/block481815.dat differ