diff --git a/Java/src/main/java/org/ciyam/at/FunctionCode.java b/Java/src/main/java/org/ciyam/at/FunctionCode.java index 1a5e1bf..c37c284 100644 --- a/Java/src/main/java/org/ciyam/at/FunctionCode.java +++ b/Java/src/main/java/org/ciyam/at/FunctionCode.java @@ -451,19 +451,15 @@ public enum FunctionCode { } }, /** - * MD5 A into B
+ * MD5 data (offset A1, length A2) into B
* 0x0200 + * MD5 message data starts at address in A1 and byte-length is in A2.
+ * MD5 hash stored in B1 and B2. B3 and B4 are zeroed. */ MD5_A_TO_B(0x0200, 0, false) { @Override protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { - ByteBuffer messageByteBuffer = ByteBuffer.allocate(2 * MachineState.VALUE_SIZE); - messageByteBuffer.order(ByteOrder.LITTLE_ENDIAN); - - messageByteBuffer.putLong(state.a1); - messageByteBuffer.putLong(state.a2); - - byte[] message = messageByteBuffer.array(); + byte[] message = getHashData(state); try { MessageDigest digester = MessageDigest.getInstance("MD5"); @@ -482,20 +478,16 @@ public enum FunctionCode { } }, /** - * Check MD5 of A matches B
+ * Check MD5 of data (offset A1, length A2) matches B
* 0x0201
+ * MD5 message data starts at address in A1 and byte-length is in A2.
+ * Other MD5 hash is in B1 and B2. B3 and B4 are ignored. * Returns 1 if true, 0 if false */ CHECK_MD5_A_WITH_B(0x0201, 0, true) { @Override protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { - ByteBuffer messageByteBuffer = ByteBuffer.allocate(2 * MachineState.VALUE_SIZE); - messageByteBuffer.order(ByteOrder.LITTLE_ENDIAN); - - messageByteBuffer.putLong(state.a1); - messageByteBuffer.putLong(state.a2); - - byte[] message = messageByteBuffer.array(); + byte[] message = getHashData(state); try { MessageDigest digester = MessageDigest.getInstance("MD5"); @@ -519,20 +511,15 @@ public enum FunctionCode { } }, /** - * HASH160 A into B
+ * RIPE-MD160 data (offset A1, length A2) into B
* 0x0202 + * RIPE-MD160 message data starts at address in A1 and byte-length is in A2.
+ * RIPE-MD160 hash stored in B1, B2 and LSB of B3. B4 is zeroed. */ - HASH160_A_TO_B(0x0202, 0, false) { + RMD160_A_TO_B(0x0202, 0, false) { @Override protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { - ByteBuffer messageByteBuffer = ByteBuffer.allocate(3 * MachineState.VALUE_SIZE); - messageByteBuffer.order(ByteOrder.LITTLE_ENDIAN); - - messageByteBuffer.putLong(state.a1); - messageByteBuffer.putLong(state.a2); - messageByteBuffer.putLong(state.a3); - - byte[] message = messageByteBuffer.array(); + byte[] message = getHashData(state); try { MessageDigest digester = MessageDigest.getInstance("RIPEMD160"); @@ -551,21 +538,16 @@ public enum FunctionCode { } }, /** - * Check HASH160 of A matches B
+ * Check RIPE-MD160 of data (offset A1, length A2) matches B
* 0x0203
+ * RIPE-MD160 message data starts at address in A1 and byte-length is in A2.
+ * Other RIPE-MD160 hash is in B1, B2 and LSB of B3. B4 is ignored. * Returns 1 if true, 0 if false */ - CHECK_HASH160_A_WITH_B(0x0203, 0, true) { + CHECK_RMD160_A_WITH_B(0x0203, 0, true) { @Override protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { - ByteBuffer messageByteBuffer = ByteBuffer.allocate(3 * MachineState.VALUE_SIZE); - messageByteBuffer.order(ByteOrder.LITTLE_ENDIAN); - - messageByteBuffer.putLong(state.a1); - messageByteBuffer.putLong(state.a2); - messageByteBuffer.putLong(state.a3); - - byte[] message = messageByteBuffer.array(); + byte[] message = getHashData(state); try { MessageDigest digester = MessageDigest.getInstance("RIPEMD160"); @@ -591,21 +573,15 @@ public enum FunctionCode { } }, /** - * SHA256 A into B
+ * SHA256 data (offset A1, length A2) into B
* 0x0204 + * SHA256 message data starts at address in A1 and byte-length is in A2.
+ * SHA256 hash is stored in B1 through B4. */ SHA256_A_TO_B(0x0204, 0, false) { @Override protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { - ByteBuffer messageByteBuffer = ByteBuffer.allocate(4 * MachineState.VALUE_SIZE); - messageByteBuffer.order(ByteOrder.LITTLE_ENDIAN); - - messageByteBuffer.putLong(state.a1); - messageByteBuffer.putLong(state.a2); - messageByteBuffer.putLong(state.a3); - messageByteBuffer.putLong(state.a4); - - byte[] message = messageByteBuffer.array(); + byte[] message = getHashData(state); try { MessageDigest digester = MessageDigest.getInstance("SHA-256"); @@ -624,22 +600,16 @@ public enum FunctionCode { } }, /** - * Check SHA256 of A matches B
+ * Check SHA256 of data (offset A1, length A2) matches B
* 0x0205
+ * SHA256 message data starts at address in A1 and byte-length is in A2.
+ * Other SHA256 hash is in B1 through B4. * Returns 1 if true, 0 if false */ CHECK_SHA256_A_WITH_B(0x0205, 0, true) { @Override protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { - ByteBuffer messageByteBuffer = ByteBuffer.allocate(4 * MachineState.VALUE_SIZE); - messageByteBuffer.order(ByteOrder.LITTLE_ENDIAN); - - messageByteBuffer.putLong(state.a1); - messageByteBuffer.putLong(state.a2); - messageByteBuffer.putLong(state.a3); - messageByteBuffer.putLong(state.a4); - - byte[] message = messageByteBuffer.array(); + byte[] message = getHashData(state); try { MessageDigest digester = MessageDigest.getInstance("SHA-256"); @@ -664,6 +634,75 @@ public enum FunctionCode { } } }, + /** + * HASH160 data (offset A1, length A2) into B
+ * 0x0206 + * Bitcoin's HASH160 hash is equivalent to RMD160(SHA256(data)).
+ * HASH160 message data starts at address in A1 and byte-length is in A2.
+ * HASH160 hash stored in B1, B2 and LSB of B3. B4 is zeroed. + */ + HASH160_A_TO_B(0x0206, 0, false) { + @Override + protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { + byte[] message = getHashData(state); + + try { + MessageDigest sha256Digester = MessageDigest.getInstance("SHA-256"); + byte[] sha256Digest = sha256Digester.digest(message); + + MessageDigest rmd160Digester = MessageDigest.getInstance("RIPEMD160"); + byte[] rmd160Digest = rmd160Digester.digest(sha256Digest); + + ByteBuffer digestByteBuffer = ByteBuffer.wrap(rmd160Digest); + digestByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + + state.b1 = digestByteBuffer.getLong(); + state.b2 = digestByteBuffer.getLong(); + state.b3 = (long) digestByteBuffer.getInt() & 0xffffffffL; + state.b4 = 0L; + } catch (NoSuchAlgorithmException e) { + throw new ExecutionException("No SHA-256 or RIPEMD160 message digest service available", e); + } + } + }, + /** + * Check HASH160 of data (offset A1, length A2) matches B
+ * 0x0207
+ * HASH160 message data starts at address in A1 and byte-length is in A2.
+ * Other HASH160 hash is in B1, B2 and LSB of B3. B4 is ignored. + * Returns 1 if true, 0 if false + */ + CHECK_HASH160_A_WITH_B(0x0207, 0, true) { + @Override + protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { + byte[] message = getHashData(state); + + try { + MessageDigest sha256Digester = MessageDigest.getInstance("SHA-256"); + byte[] sha256Digest = sha256Digester.digest(message); + + MessageDigest rmd160Digester = MessageDigest.getInstance("RIPEMD160"); + byte[] rmd160Digest = rmd160Digester.digest(sha256Digest); + + ByteBuffer digestByteBuffer = ByteBuffer.allocate(rmd160Digester.getDigestLength()); + digestByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + + digestByteBuffer.putLong(state.b1); + digestByteBuffer.putLong(state.b2); + digestByteBuffer.putInt((int) (state.b3 & 0xffffffffL)); + // NOTE: b4 ignored + + byte[] expectedDigest = digestByteBuffer.array(); + + if (Arrays.equals(rmd160Digest, expectedDigest)) + functionData.returnValue = 1L; // true + else + functionData.returnValue = 0L; // false + } catch (NoSuchAlgorithmException e) { + throw new ExecutionException("No SHA-256 or RIPEMD160 message digest service available", e); + } + } + }, /** * 0x0300
* Returns current block's "timestamp" @@ -922,7 +961,7 @@ public enum FunctionCode { public final int paramCount; public final boolean returnsValue; - private final static Map map = Arrays.stream(FunctionCode.values()) + private static final Map map = Arrays.stream(FunctionCode.values()) .collect(Collectors.toMap(functionCode -> functionCode.value, functionCode -> functionCode)); private FunctionCode(int value, int paramCount, boolean returnsValue) { @@ -976,8 +1015,36 @@ public enum FunctionCode { } /** Actually execute function */ - abstract protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException; + protected abstract void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException; // TODO: public abstract String disassemble(); + protected byte[] getHashData(MachineState state) throws ExecutionException { + // Validate data offset in A1 + if (state.a1 < 0L || state.a1 > Integer.MAX_VALUE || state.a1 >= state.numDataPages) + throw new ExecutionException("MD5 data offset (A1) out of bounds"); + + // Validate data length in A2 + if (state.a2 < 0L || state.a2 > Integer.MAX_VALUE || state.a1 + byteLengthToDataLength(state.a2) > state.numDataPages) + throw new ExecutionException("MD5 data length (A2) invalid"); + + final int dataStart = (int) (state.a1 & 0x7fffffffL); + final int dataLength = (int) (state.a2 & 0x7fffffffL); + + byte[] message = new byte[dataLength]; + + ByteBuffer messageByteBuffer = state.dataByteBuffer.slice(); + messageByteBuffer.position(dataStart * MachineState.VALUE_SIZE); + messageByteBuffer.limit(dataStart * MachineState.VALUE_SIZE + dataLength); + + messageByteBuffer.get(message); + + return message; + } + + /** Returns the number of data-page values to contain specific length of bytes. */ + protected int byteLengthToDataLength(long byteLength) { + return (MachineState.VALUE_SIZE - 1 + (int) (byteLength & 0x7fffffffL)) / MachineState.VALUE_SIZE; + } + } diff --git a/Java/src/test/java/DisassemblyTests.java b/Java/src/test/java/DisassemblyTests.java index a7640a6..01887de 100644 --- a/Java/src/test/java/DisassemblyTests.java +++ b/Java/src/test/java/DisassemblyTests.java @@ -58,7 +58,7 @@ public class DisassemblyTests { codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B2.value).putInt(0); codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("8fc71e8300000000")); codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B3.value).putInt(0); - codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_HASH160_A_WITH_B.value).putInt(1); + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_RMD160_A_WITH_B.value).putInt(1); codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.ECHO.value).putInt(1); codeByteBuffer.put(OpCode.FIN_IMD.value); diff --git a/Java/src/test/java/FunctionCodeTests.java b/Java/src/test/java/FunctionCodeTests.java index f1e3ee6..934a763 100644 --- a/Java/src/test/java/FunctionCodeTests.java +++ b/Java/src/test/java/FunctionCodeTests.java @@ -1,6 +1,8 @@ import static common.TestUtils.hexToBytes; import static org.junit.Assert.*; +import java.nio.charset.StandardCharsets; + import org.ciyam.at.ExecutionException; import org.ciyam.at.FunctionCode; import org.ciyam.at.OpCode; @@ -11,178 +13,49 @@ import common.ExecutableTest; public class FunctionCodeTests extends ExecutableTest { + private static final String message = "The quick, brown fox jumped over the lazy dog."; + private static final byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8); + + private static final FunctionCode[] bSettingFunctions = new FunctionCode[] { FunctionCode.SET_B1, FunctionCode.SET_B2, FunctionCode.SET_B3, FunctionCode.SET_B4 }; + @Test public void testMD5() throws ExecutionException { - // MD5 of ffffffffffffffffffffffffffffffff is 8d79cbc9a4ecdde112fc91ba625b13c2 - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("ffffffffffffffff")); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0); - // A3 unused - // A4 unused - - codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.MD5_A_TO_B.value); - - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("8d79cbc9a4ecdde1")); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0); - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("12fc91ba625b13c2")); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0); - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("0000000000000000")); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A3.value).putInt(0); - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("0000000000000000")); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A4.value).putInt(0); - - codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_A_EQUALS_B.value).putInt(1); - - codeByteBuffer.put(OpCode.FIN_IMD.value); - - execute(true); - - assertTrue(state.getIsFinished()); - assertFalse(state.getHadFatalError()); - assertEquals("MD5 hashes do not match", 1L, getData(1)); + testHash("MD5", FunctionCode.MD5_A_TO_B, FunctionCode.CHECK_A_EQUALS_B, "1388a82384756096e627e3671e2624bf"); } @Test public void testCHECK_MD5() throws ExecutionException { - // MD5 of ffffffffffffffffffffffffffffffff is 8d79cbc9a4ecdde112fc91ba625b13c2 - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("ffffffffffffffff")); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0); - // A3 unused - // A4 unused - - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("8d79cbc9a4ecdde1")); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B1.value).putInt(0); - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("12fc91ba625b13c2")); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B2.value).putInt(0); - - codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_MD5_A_WITH_B.value).putInt(1); - - codeByteBuffer.put(OpCode.FIN_IMD.value); - - execute(true); - - assertTrue(state.getIsFinished()); - assertFalse(state.getHadFatalError()); - assertEquals("MD5 hashes do not match", 1L, getData(1)); + testHash("MD5", null, FunctionCode.CHECK_MD5_A_WITH_B, "1388a82384756096e627e3671e2624bf"); } @Test - public void testHASH160() throws ExecutionException { - // RIPEMD160 of ffffffffffffffffffffffffffffffffffffffffffffffff is 90e735014ea23aa89190121b229c06d58fc71e83 - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("ffffffffffffffff")); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A3.value).putInt(0); - // A4 unused - - codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.HASH160_A_TO_B.value); - - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("90e735014ea23aa8")); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0); - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("9190121b229c06d5")); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0); - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("8fc71e8300000000")); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A3.value).putInt(0); - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("0000000000000000")); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A4.value).putInt(0); - - codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_A_EQUALS_B.value).putInt(1); - - codeByteBuffer.put(OpCode.FIN_IMD.value); - - execute(true); - - assertTrue(state.getIsFinished()); - assertFalse(state.getHadFatalError()); - assertEquals("RIPEMD160 hashes do not match", 1L, getData(1)); + public void testRMD160() throws ExecutionException { + testHash("RIPE-MD160", FunctionCode.RMD160_A_TO_B, FunctionCode.CHECK_A_EQUALS_B, "b5a4b1898af3745dbbb5becb83e72787df9952c9"); } @Test - public void testCHECK_HASH160() throws ExecutionException { - // RIPEMD160 of ffffffffffffffffffffffffffffffffffffffffffffffff is 90e735014ea23aa89190121b229c06d58fc71e83 - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("ffffffffffffffff")); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A3.value).putInt(0); - // A4 unused - - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("90e735014ea23aa8")); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B1.value).putInt(0); - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("9190121b229c06d5")); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B2.value).putInt(0); - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("8fc71e8300000000")); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B3.value).putInt(0); - - codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_HASH160_A_WITH_B.value).putInt(1); - - codeByteBuffer.put(OpCode.FIN_IMD.value); - - execute(true); - - assertEquals("RIPEMD160 hashes do not match", 1L, getData(1)); - assertTrue(state.getIsFinished()); - assertFalse(state.getHadFatalError()); + public void testCHECK_RMD160() throws ExecutionException { + testHash("RIPE-MD160", null, FunctionCode.CHECK_RMD160_A_WITH_B, "b5a4b1898af3745dbbb5becb83e72787df9952c9"); } @Test public void testSHA256() throws ExecutionException { - // SHA256 of ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff is af9613760f72635fbdb44a5a0a63c39f12af30f950a6ee5c971be188e89c4051 - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("ffffffffffffffff")); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A3.value).putInt(0); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A4.value).putInt(0); - - codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.SHA256_A_TO_B.value); - - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("af9613760f72635f")); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0); - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("bdb44a5a0a63c39f")); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0); - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("12af30f950a6ee5c")); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A3.value).putInt(0); - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("971be188e89c4051")); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A4.value).putInt(0); - - codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_A_EQUALS_B.value).putInt(1); - - codeByteBuffer.put(OpCode.FIN_IMD.value); - - execute(true); - - assertTrue(state.getIsFinished()); - assertFalse(state.getHadFatalError()); - assertEquals("RIPEMD160 hashes do not match", 1L, getData(1)); + testHash("SHA256", FunctionCode.SHA256_A_TO_B, FunctionCode.CHECK_A_EQUALS_B, "c01d63749ebe5d6b16f7247015cac2e49a5ac4fb6c7f24bed07b8aa904da97f3"); } @Test public void testCHECK_SHA256() throws ExecutionException { - // SHA256 of ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff is af9613760f72635fbdb44a5a0a63c39f12af30f950a6ee5c971be188e89c4051 - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("ffffffffffffffff")); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A3.value).putInt(0); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A4.value).putInt(0); + testHash("SHA256", null, FunctionCode.CHECK_SHA256_A_WITH_B, "c01d63749ebe5d6b16f7247015cac2e49a5ac4fb6c7f24bed07b8aa904da97f3"); + } - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("af9613760f72635f")); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B1.value).putInt(0); - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("bdb44a5a0a63c39f")); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B2.value).putInt(0); - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("12af30f950a6ee5c")); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B3.value).putInt(0); - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("971be188e89c4051")); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B4.value).putInt(0); + @Test + public void testHASH160() throws ExecutionException { + testHash("HASH160", FunctionCode.HASH160_A_TO_B, FunctionCode.CHECK_A_EQUALS_B, "54d54a03fd447996ab004dee87fab80bf9477e23"); + } - codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_SHA256_A_WITH_B.value).putInt(1); - - codeByteBuffer.put(OpCode.FIN_IMD.value); - - execute(true); - - assertEquals("RIPEMD160 hashes do not match", 1L, getData(1)); - assertTrue(state.getIsFinished()); - assertFalse(state.getHadFatalError()); + @Test + public void testCHECK_HASH160() throws ExecutionException { + testHash("HASH160", null, FunctionCode.CHECK_HASH160_A_WITH_B, "54d54a03fd447996ab004dee87fab80bf9477e23"); } @Test @@ -234,4 +107,57 @@ public class FunctionCodeTests extends ExecutableTest { assertTrue(state.getHadFatalError()); } + private void testHash(String hashName, FunctionCode hashFunction, FunctionCode checkFunction, String expected) throws ExecutionException { + // Data addr 0 for setting values + dataByteBuffer.putLong(0L); + // Data addr 1 for results + dataByteBuffer.putLong(0L); + + // Data addr 2+ for message + dataByteBuffer.put(messageBytes); + + // MD5 data start + codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(2L); + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0); + + // MD5 data length + codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(messageBytes.length); + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0); + + // A3 unused + // A4 unused + + // Optional hash function + if (hashFunction != null) { + codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(hashFunction.value); + // Hash functions usually put result into B, but we need it in A + codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.SWAP_A_AND_B.value); + } + + // Expected result goes into B + codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.CLEAR_B.value); + // Each 16 hex-chars (8 bytes) fits into each B word (B1, B2, B3 and B4) + for (int bWord = 0; bWord < 4 && bWord * 16 < expected.length(); ++bWord) { + final int beginIndex = bWord * 16; + final int endIndex = Math.min(expected.length(), beginIndex + 16); + + String hexChars = expected.substring(beginIndex, endIndex); + codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes(hexChars)); + codeByteBuffer.put(new byte[8 - hexChars.length() / 2]); // pad with zeros + + final FunctionCode bSettingFunction = bSettingFunctions[bWord]; + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(bSettingFunction.value).putInt(0); + } + + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(checkFunction.value).putInt(1); + + codeByteBuffer.put(OpCode.FIN_IMD.value); + + execute(true); + + assertTrue("MachineState isn't in finished state", state.getIsFinished()); + assertFalse("MachineState encountered fatal error", state.getHadFatalError()); + assertEquals(hashName + " hashes do not match", 1L, getData(1)); + } + } diff --git a/Java/src/test/java/common/ExecutableTest.java b/Java/src/test/java/common/ExecutableTest.java index 866b83f..2b41531 100644 --- a/Java/src/test/java/common/ExecutableTest.java +++ b/Java/src/test/java/common/ExecutableTest.java @@ -14,13 +14,16 @@ import org.junit.BeforeClass; public abstract class ExecutableTest { + public static final int CODE_STACK_SIZE = 0x0200; public static final int DATA_OFFSET = 6 * 2 + 8; - public static final int CALL_STACK_OFFSET = DATA_OFFSET + 0x0020 * 8; + public static final int DATA_STACK_SIZE = 0x0200; + public static final int CALL_STACK_OFFSET = DATA_OFFSET + DATA_STACK_SIZE * 8; public TestLogger logger; public TestAPI api; public MachineState state; public ByteBuffer codeByteBuffer; + public ByteBuffer dataByteBuffer; public ByteBuffer stateByteBuffer; public int callStackSize; public int userStackOffset; @@ -35,7 +38,8 @@ public abstract class ExecutableTest { public void beforeTest() { logger = new TestLogger(); api = new TestAPI(); - codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN); + codeByteBuffer = ByteBuffer.allocate(CODE_STACK_SIZE).order(ByteOrder.LITTLE_ENDIAN); + dataByteBuffer = ByteBuffer.allocate(DATA_STACK_SIZE).order(ByteOrder.LITTLE_ENDIAN); stateByteBuffer = null; } @@ -43,15 +47,16 @@ public abstract class ExecutableTest { public void afterTest() { stateByteBuffer = null; codeByteBuffer = null; + dataByteBuffer = null; api = null; logger = null; } protected void execute(boolean onceOnly) { - // version 0002, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4, minActivation = 0 - byte[] headerBytes = hexToBytes("0200" + "0000" + "0002" + "2000" + "1000" + "1000" + "0000000000000000"); + // version 0002, reserved 0000, code 0200 * 1, data 0200 * 8, call stack 0010 * 4, user stack 0010 * 4, minActivation = 0 + byte[] headerBytes = hexToBytes("0200" + "0000" + "0002" + "0002" + "1000" + "1000" + "0000000000000000"); byte[] codeBytes = codeByteBuffer.array(); - byte[] dataBytes = new byte[0]; + byte[] dataBytes = dataByteBuffer.array(); state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes); @@ -93,7 +98,7 @@ public abstract class ExecutableTest { byte[] stateBytes = state.toBytes(); // We know how the state will be serialized so we can extract values - // header(6) + data(0x0020 * 8) + callStack length(4) + callStack + userStack length(4) + userStack + // header(6) + data(size * 8) + callStack length(4) + callStack + userStack length(4) + userStack stateByteBuffer = ByteBuffer.wrap(stateBytes).order(ByteOrder.LITTLE_ENDIAN); callStackSize = stateByteBuffer.getInt(CALL_STACK_OFFSET);