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);