From 8a96492c806c8e72339e49a2e06cd54cd3ef9725 Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Tue, 29 Sep 2015 01:26:59 +0100 Subject: [PATCH] Modify parsing of nonce value from AuxPoW coinbase transaction script to use long values rather than int, to support Java correctly. Add unit tests for parsing of nonce values and calculating expected slot index for AuxPoW headers. --- src/main/java/org/bitcoinj/core/AuxPoW.java | 54 +++++++++++++----- .../java/org/bitcoinj/core/AuxPoWTest.java | 32 +++++++++++ .../org/bitcoinj/core/DogecoinBlockTest.java | 30 ++++++++++ .../bitcoinj/core/dogecoin_block894863.bin | Bin 0 -> 3381 bytes .../bitcoinj/core/dogecoin_block894863.hex | 1 + 5 files changed, 102 insertions(+), 15 deletions(-) create mode 100644 src/test/resources/org/bitcoinj/core/dogecoin_block894863.bin create mode 100644 src/test/resources/org/bitcoinj/core/dogecoin_block894863.hex diff --git a/src/main/java/org/bitcoinj/core/AuxPoW.java b/src/main/java/org/bitcoinj/core/AuxPoW.java index be770102..8e77f9a7 100644 --- a/src/main/java/org/bitcoinj/core/AuxPoW.java +++ b/src/main/java/org/bitcoinj/core/AuxPoW.java @@ -351,21 +351,9 @@ public class AuxPoW extends ChildMessage { return false; } - byte[] nonceBytes = Utils.reverseBytes(Arrays.copyOfRange(script, pc + 4, pc + 8)); - int nonce = ByteBuffer.wrap(nonceBytes).getInt(); + long nonce = getNonceFromScript(script, pc); - // Choose a pseudo-random slot in the chain merkle tree - // but have it be fixed for a size/nonce/chain combination. - // - // This prevents the same work from being used twice for the - // same chain while reducing the chance that two chains clash - // for the same slot. - long rand = nonce; - rand = rand * 1103515245 + 12345; - rand += ((AuxPoWNetworkParameters) params).getChainID(); - rand = rand * 1103515245 + 12345; - - if (getChainMerkleBranch().getIndex() != (rand % branchSize)) { + if (getChainMerkleBranch().getIndex() != getExpectedIndex(nonce, ((AuxPoWNetworkParameters) params).getChainID(), getChainMerkleBranch().size())) { if (throwException) { throw new VerificationException("Aux POW wrong index"); } @@ -386,6 +374,42 @@ public class AuxPoW extends ChildMessage { return true; } + /** + * Get the nonce value from the coinbase transaction script. + * + * @param script the transaction script to extract the nonce from. + * @param pc offset of the merkle branch size within the script (this is 4 + * bytes before the start of the nonce value). Range checks should be + * performed before calling this method. + * @return the nonce value. + */ + protected static long getNonceFromScript(final byte[] script, int pc) { + // Note that the nonce value is packed as platform order (typically + // little-endian) so we have to convert to big-endian for Java + final byte[] nonceBytes = Utils.reverseBytes(Arrays.copyOfRange(script, pc + 4, pc + 8)); + + return ByteBuffer.wrap(nonceBytes).getInt() & 0xffffffffl; + } + + /** + * Get the expected index of the slot within the chain merkle tree. + * + * This prevents the same work from being used twice for the + * same chain while reducing the chance that two chains clash + * for the same slot. + */ + protected static int getExpectedIndex(final long nonce, final int chainId, final int merkleHeight) { + // Choose a pseudo-random slot in the chain merkle tree + // but have it be fixed for a size/nonce/chain combination. + + long rand = nonce; + rand = rand * 1103515245 + 12345; + rand += chainId; + rand = rand * 1103515245 + 12345; + + return (int) (rand % (1L << merkleHeight)); + } + public Transaction getTransaction() { return transaction; } @@ -398,7 +422,7 @@ public class AuxPoW extends ChildMessage { * @param subArray the shorter array to test for presence in the longer array. * @return true if the shorter array is present at the offset, false otherwise. */ - private boolean arrayMatch(byte[] script, int offset, byte[] subArray) { + static boolean arrayMatch(byte[] script, int offset, byte[] subArray) { for (int matchIdx = 0; matchIdx + offset < script.length && matchIdx < subArray.length; matchIdx++) { if (script[offset + matchIdx] != subArray[matchIdx]) { return false; diff --git a/src/test/java/org/bitcoinj/core/AuxPoWTest.java b/src/test/java/org/bitcoinj/core/AuxPoWTest.java index 0aa08a09..a0094612 100644 --- a/src/test/java/org/bitcoinj/core/AuxPoWTest.java +++ b/src/test/java/org/bitcoinj/core/AuxPoWTest.java @@ -1,7 +1,10 @@ package org.bitcoinj.core; +import java.io.OutputStream; +import java.math.BigInteger; import java.util.Arrays; import java.util.Collections; +import static org.bitcoinj.core.AuxPoW.MERGED_MINING_HEADER; import org.libdohj.core.AltcoinSerializer; import org.libdohj.core.AuxPoWNetworkParameters; @@ -13,6 +16,7 @@ import static org.bitcoinj.core.Utils.reverseBytes; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -360,4 +364,32 @@ public class AuxPoWTest { auxpow.setCoinbaseBranch(new MerkleBranch(params, auxpow, Collections.singletonList(revisedCoinbaseHash), MERKLE_ROOT_COINBASE_INDEX)); } + + /** + * Test extraction of a nonce value from the coinbase transaction pubscript. + * This test primarily exists to ensure that byte order is correct, and that + * a nonce value above Integer.MAX_VALUE is still returned as a positive + * integer. + */ + @Test + public void testGetNonceFromScript() { + final byte[] script = Utils.HEX.decode("03251d0de4b883e5bda9e7a59ee4bb99e9b1bcfabe6d6dc6c83f297ee373df0d826f3148f218e4e4eb349e0bba715ad793ccc2d6beb6df40000000f09f909f4d696e65642062792079616e6779616e676368656e00000000000000000000000000000000"); + final int pc = 55; + final long expResult = 0x9f909ff0L; + final long result = AuxPoW.getNonceFromScript(script, pc); + assertEquals(expResult, result); + } + + /** + * Test of getExpectedIndex method, of class AuxPoW. + */ + @Test + public void testGetExpectedIndex() { + final long nonce = 0x9f909ff0L; + final int chainId = 98; + final int merkleHeight = 6; + final int expResult = 40; + final int result = AuxPoW.getExpectedIndex(nonce, chainId, merkleHeight); + assertEquals(expResult, result); + } } diff --git a/src/test/java/org/bitcoinj/core/DogecoinBlockTest.java b/src/test/java/org/bitcoinj/core/DogecoinBlockTest.java index 3744ced4..16e7a5b6 100644 --- a/src/test/java/org/bitcoinj/core/DogecoinBlockTest.java +++ b/src/test/java/org/bitcoinj/core/DogecoinBlockTest.java @@ -7,6 +7,7 @@ package org.bitcoinj.core; import org.libdohj.core.AltcoinSerializer; import java.io.IOException; +import java.math.BigInteger; import org.libdohj.params.DogecoinMainNetParams; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -116,6 +117,35 @@ public class DogecoinBlockTest { assertArrayEquals(expected, coinbaseMerkleBranch.getHashes().toArray(new Sha256Hash[coinbaseMerkleBranch.size()])); assertEquals(6, block.getTransactions().size()); + + assertTrue(auxpow.checkProofOfWork(block.getHash(), block.getDifficultyTargetAsInteger(), false)); + } + + /** + * Confirm parsing of block with a nonce value above Integer.MAX_VALUE. + * See https://github.com/rnicoll/libdohj/issues/5 + * + * @throws IOException + */ + @Test + public void shouldParseBlock894863() throws IOException { + byte[] payload = Util.getBytes(getClass().getResourceAsStream("dogecoin_block894863.bin")); + AltcoinSerializer serializer = (AltcoinSerializer)params.getDefaultSerializer(); + final AltcoinBlock block = (AltcoinBlock)serializer.makeBlock(payload); + assertEquals("93a207e6d227f4d60ee64fad584b47255f654b0b6378d78e774123dd66f4fef9", block.getHashAsString()); + assertEquals(0, block.getNonce()); + + // Check block version values + assertEquals(2, block.getVersion()); + assertEquals(98, block.getChainID()); + assertTrue(block.getVersionFlags().get(0)); + + final AuxPoW auxpow = block.getAuxPoW(); + assertNotNull(auxpow); + final Transaction auxpowCoinbase = auxpow.getCoinbase(); + assertEquals("c84431cf41f592373cc70db07f6804f945202f5f7baad31a8bbab89aaecb7b8b", auxpowCoinbase.getHashAsString()); + + assertTrue(auxpow.checkProofOfWork(block.getHash(), block.getDifficultyTargetAsInteger(), true)); } /** diff --git a/src/test/resources/org/bitcoinj/core/dogecoin_block894863.bin b/src/test/resources/org/bitcoinj/core/dogecoin_block894863.bin new file mode 100644 index 0000000000000000000000000000000000000000..fa1c64c80d9cc8bbd554b9af2bf48bca93b4e912 GIT binary patch literal 3381 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 Gqx}tyyR6p$ literal 0 HcmV?d00001 diff --git a/src/test/resources/org/bitcoinj/core/dogecoin_block894863.hex b/src/test/resources/org/bitcoinj/core/dogecoin_block894863.hex new file mode 100644 index 00000000..c06ca50e --- /dev/null +++ b/src/test/resources/org/bitcoinj/core/dogecoin_block894863.hex @@ -0,0 +1 @@ +02016200f24da529ad02f22db8b2206bc8a13e990aeb7d732717b3f9eedcc0648560f7a768a0e12ca98fe316c1798c8b27c4fa3c7d0df809d4aeff8744b81944c11f5fb0572409564819031b0000000001000000010000000000000000000000000000000000000000000000000000000000000000ffffffff6403251d0de4b883e5bda9e7a59ee4bb99e9b1bcfabe6d6dc6c83f297ee373df0d826f3148f218e4e4eb349e0bba715ad793ccc2d6beb6df40000000f09f909f4d696e65642062792079616e6779616e676368656e000000000000000000000000000000000000000c0100f90295000000001976a914aa3750aa18b8a0f3f0590731e1fab934856680cf88acede8a23812aa15714c5616de24d0eee8c2e0af8d9fd9ef1bf36f7c18e9f50200000000000000000000062900000000000000000000000000000000000000000000000000000000000000463ceed131958d98aee29089d1cf38b9728b224512e51ca3a8b1189d5ed03d0709b68fd6e328528f2a29ec7fb077c834fbf0f14c371fafcfb27444017fbf5b26fdb884bed8ad6a4bded36fc89ed8b05a6c6c0ae1cfd5fe37eb3021b32a1e29042b7a2e142329e7d0d0bffcb5cc338621a576b49d4d32991000b8d4ac793bc1f5a8333358a936ee3634858177dc1508c23e004276906540bceb927ef8feb201b32800000003000000fa6ad1b1a7dc415f598fdd76dd332453af98ebf7855263edad2f1373c1d303ce8b7bcbae9ab8ba8b1ad3aa7b5f2f2045f904687fb00dc73c3792f541cf3144c85f2409567d69011ba556f0590701000000010000000000000000000000000000000000000000000000000000000000000000ffffffff35038fa70d0fe4b883e5bda9e7a59ee4bb99e9b1bc205b323031352d30392d32385431313a32383a32342e3937303432363335325a5dffffffff01005668f8e800000023210214a5f15a73686b64cf27405e018e2f06e0501b52f4ff98282badd9d6948fb57dac0000000001000000017859d0ff1a92ea43ff4743012626832abee769003d64368f7667702738209c64010000008b483045022100ae5a992241cd5142ef17e0864b726b6e6ab11763794ef947d4f7cd3cc3a9019f022016c7618b92fb1437560d85059adbc98fb380d01977dc46fdd544d65fbf4987e9014104602a01b7bfed07cc64ead23d3d7caa37f6c6e0de60137a255d5dd1414b32653fe4136c85d2d832cb3fa2eca83de87b782500d251a331028feb47454d67da5026ffffffff0200e87648170000001976a914fdc4b05332c27c67566483265da71948a08e497388acabeeb73a090000001976a914a4d86cbd7991a44f4c4fd2eb1617a9fd6c23bd0f88ac000000000100000001b4c77d6147eb709b89940b98b3dce38905e8fda3731017ae0d8bf1cdb1995ee2010000006b4830450221009615014a8b164d283ce2821bfc647e1d8f36992e3f4fff75f0976d93c2b3f661022071a569760f5987759d28c51a422f90760abe82eb3e6775cfb0e13826d41fbacf01210363181dbf895dcb94e042ed4ef11c8e81d41a605bf4667a67c750394743632f38feffffff0200a0724e180900001976a91423b30ee802c85794357b601118df906d8895fade88ac0063332f3a0000001976a914ea592985d9fd5bb3ec0f338e92481607ee20598688ac81a70d000100000002edb4a047875f0094175b91edcba6c5542017943a27c4793d7fc6b595c09ea5d236000000da00483045022100c7478375781e0ba196b7f5ebc29fcd23de4b3863c3a640a13eca23990be2e50f02206b736c5d2857656c9c9e1be1525e0c08b55e9c601331a3434ad11f4413ad05a90147304402200f6d0fc8674d05c34bcfd51ee8146caa4cb474b15adafd81b31c9fab009c53350220550fdbd35abe04e727cbf856213971b7ae7bf3fd13c3b2d64530aca49432988c0147522102ec2d3fe84bd61ed4418d4388a95c603d5ead879cd22ef08d75c74f014d9cb58d2103aa1b1d45d98419b67c30d01f6e582c62158845329a4f064247b8d7a9bfa53bd152aeffffffffbdf13084cb7dff5c74235a36d7bcb0cd7f487d2c9e30f49e09726856e3aa948a36000000db00483045022100a3027442a84c45e6e03f41ec9ca6ad4af71b0f04e99024a53b5db4635508b2fa02203118b1ce6282779d970af3f91d47671b80881425b0304e051d0b9ea6aba8dc55014830450221009d5376c47f3d09849bf5c4021d2fd0de470982d85905cc105655c8e17b25436f022064b016bd218492b3428fb12d620af2e148e497d1aa0a0432d0d03c1a4952d3910147522102ec2d3fe84bd61ed4418d4388a95c603d5ead879cd22ef08d75c74f014d9cb58d2103aa1b1d45d98419b67c30d01f6e582c62158845329a4f064247b8d7a9bfa53bd152aeffffffff02c0f87a23080000001976a91454a00e6fb1a8d707b3dbb3c1540ad6f82245877988ac2082fd050000000017a9143ec0a1a92c148e703169486cc8a6053cdf29246f87000000000100000003c43fe5756224f000aeb5f8bf42bc66aa04bae3ad99ceaf850fde8e5375451a16000000006a47304402203a0269600054b8db230bd15d838c49a8052e5c72928b31d447392558abc479a0022028fd39174567d7c38b7de5d68a1d1be27709b16bb438f278f5022d6d689ad20d012102a081b81372de4163ef2389fa45db496273a2226cd6e1566a621eefd9aa01ba45ffffffff063e1c724b08cbd97f915cea207e5c05013f19e1e41381b9148fca7a81136ac4000000006a473044022034c61d7594dcaa07c378b3bd45d8b54097f7e1cac6ffc8db605da22814fec93502200ac4a8509d9e5a21854228f4d9f705ca584c807b8c7f816b36506ad5f46a2d3901210309f0b3561357ad7b61eed6b7206bf06be1883be2244582f4bd2f82639f8b2d8effffffffb13dd12cac55f64d04fd78256621f11459f841bb22523439267b95872a1b28b4000000006b483045022100e2711892b33d49dfa369947e2f8852a948f6efef42d263f3af369e48d36cc047022061bcba806f366250f6eb39fabc5a99d12ada01e69fa23aca411e6900a44b958e0121034a3aa67fc0e3679191f0cc079351093e979a390f0a2a4397d20110862cad7df2ffffffff020005a1902d0000001976a9142c33219753dcd501e002542cc250b01a0edea4ad88acb91e521b000000001976a914ee15705187d162a6a41e8f051c9457a0d720902388ac000000000100000002b7d5cba56f51facb13ca99f9188cc00346411ceed2c7a4908e1e665c20eb03d2010000008a47304402205f59cf4937d30a5bad1442d2092a63f50b5ad3202e59df3366040bac7b71311a022033476029a9ef0323ae52de926a69d912251a8e6db7e0155aaa3c50007d308d100141047970ba8aa787466fe4a52b6c2a7b29ff2ea56271e9a1596154f30d9daf2e9155cb7f613d6d0faaa51d57f0b885ffd4ebc2da7c801183718f22f09c1e9be683dbffffffffb480cbcee4018f2e14c4c078448a3ff8a4628d51e0d2dff3284ac49b5a7e7e691d0000008b483045022100bd1f49700e0aeed8888ab1b45bff88cea21110f63d03089e11347b35dfcc78dc02202f0b20015c98bc7a0889735a1f7e3533780b72276bca9c09638f9be6f3a0454c0141047970ba8aa787466fe4a52b6c2a7b29ff2ea56271e9a1596154f30d9daf2e9155cb7f613d6d0faaa51d57f0b885ffd4ebc2da7c801183718f22f09c1e9be683dbffffffff0200a6f75f020000001976a9141cef487ab28793ed3e31d84ae09cf6979f9e5e0a88ac20fd0136000000001976a914f6a7204e300452a3a598c8851b7d218c3c3b076688ac000000000100000003ab692b8d152ec9b374161a77293204c93f8afc79da5ef62646eefa2e69126051010000008a4730440220348433b365b55413671948eeeb42c7a53ed88aca56e37b598287e8e60266b8a402206b03b3afc2a29cae19015cda64bf474476f7544fd15cde7e5dd65c280f5bbb4d014104faea7b76f8cc5512ca9f67f7c10c8b44daa9f456cb5bbd89d72807a31cc1e15b9d794f0e328d3fb8799d0c82be140b99cd9535d456f5073f5281ca42c972afc3ffffffffd84874c069baf7fcf606c914363ed05bb6fff1a9c3b9834a25b5d3b38fdbc3aa590700008b483045022100ea94de8e0b84c45d81590fe27460824b62458cc53c7e73d10117a076ad765dcd02204058476173680b8d3671a517282987e2bc22dd7f730a95e427f34d6e73aa9793014104faea7b76f8cc5512ca9f67f7c10c8b44daa9f456cb5bbd89d72807a31cc1e15b9d794f0e328d3fb8799d0c82be140b99cd9535d456f5073f5281ca42c972afc3ffffffffd4ef1069cac74af75f373075787242f268632e772e16d1e872d6829bf21e93cd080000008b483045022100a05e7cea49f6efa07c3650d70ece9150e6560b9ea2fbe9be76f60df246096a320220009c007302dfef6a724a623104fcba4ef3dc221ac62d9543f80d475ba1d08335014104faea7b76f8cc5512ca9f67f7c10c8b44daa9f456cb5bbd89d72807a31cc1e15b9d794f0e328d3fb8799d0c82be140b99cd9535d456f5073f5281ca42c972afc3ffffffff02007841cb020000001976a91497ad1a640cfac036b7a4bae5b0fb20a509afaf1388acc0dd9a44000000001976a91458d577bc6fb70e0088f025cbc846750e94a5712488ac00000000 \ No newline at end of file