Browse Source

Changed FunctionCodes that perform hashes to use variable-length data.

Before, hashing functions (e.g. MD5_A_INTO_B) would hash immediate
data stored in A, putting the result into B.

Also, that hash function would only hash the same number of bits as
the hash output. For example, MD5_A_INTO_B would only hash the
16 bytes in A1 & A2.

Now, hash functions use data-page offset stored in A1 and byte-length
stored in A2.

Renamed HASH160 to RMD160 and created new HASH160 which performs
Bitcoin's double hash of RMD160(SHA256(data)).

Refactored & added tests to cover.
master
catbref 5 years ago
parent
commit
36029c132f
  1. 183
      Java/src/main/java/org/ciyam/at/FunctionCode.java
  2. 2
      Java/src/test/java/DisassemblyTests.java
  3. 226
      Java/src/test/java/FunctionCodeTests.java
  4. 17
      Java/src/test/java/common/ExecutableTest.java

183
Java/src/main/java/org/ciyam/at/FunctionCode.java

@ -451,19 +451,15 @@ public enum FunctionCode {
}
},
/**
* MD5 A into B<br>
* MD5 data (offset A1, length A2) into B<br>
* <tt>0x0200</tt>
* MD5 message data starts at address in A1 and byte-length is in A2.<br>
* 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<br>
* Check MD5 of data (offset A1, length A2) matches B<br>
* <tt>0x0201</tt><br>
* MD5 message data starts at address in A1 and byte-length is in A2.<br>
* 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<br>
* RIPE-MD160 data (offset A1, length A2) into B<br>
* <tt>0x0202</tt>
* RIPE-MD160 message data starts at address in A1 and byte-length is in A2.<br>
* 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<br>
* Check RIPE-MD160 of data (offset A1, length A2) matches B<br>
* <tt>0x0203</tt><br>
* RIPE-MD160 message data starts at address in A1 and byte-length is in A2.<br>
* 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<br>
* SHA256 data (offset A1, length A2) into B<br>
* <tt>0x0204</tt>
* SHA256 message data starts at address in A1 and byte-length is in A2.<br>
* 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<br>
* Check SHA256 of data (offset A1, length A2) matches B<br>
* <tt>0x0205</tt><br>
* SHA256 message data starts at address in A1 and byte-length is in A2.<br>
* 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<br>
* <tt>0x0206</tt>
* Bitcoin's HASH160 hash is equivalent to RMD160(SHA256(data)).<br>
* HASH160 message data starts at address in A1 and byte-length is in A2.<br>
* 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<br>
* <tt>0x0207</tt><br>
* HASH160 message data starts at address in A1 and byte-length is in A2.<br>
* 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);
}
}
},
/**
* <tt>0x0300</tt><br>
* Returns current block's "timestamp"
@ -922,7 +961,7 @@ public enum FunctionCode {
public final int paramCount;
public final boolean returnsValue;
private final static Map<Short, FunctionCode> map = Arrays.stream(FunctionCode.values())
private static final Map<Short, FunctionCode> 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;
}
}

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

226
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 {
@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);
private static final String message = "The quick, brown fox jumped over the lazy dog.";
private static final byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);
execute(true);
private static final FunctionCode[] bSettingFunctions = new FunctionCode[] { FunctionCode.SET_B1, FunctionCode.SET_B2, FunctionCode.SET_B3, FunctionCode.SET_B4 };
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("MD5 hashes do not match", 1L, getData(1));
@Test
public void testMD5() throws ExecutionException {
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);
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);
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_SHA256_A_WITH_B.value).putInt(1);
codeByteBuffer.put(OpCode.FIN_IMD.value);
testHash("SHA256", null, FunctionCode.CHECK_SHA256_A_WITH_B, "c01d63749ebe5d6b16f7247015cac2e49a5ac4fb6c7f24bed07b8aa904da97f3");
}
execute(true);
@Test
public void testHASH160() throws ExecutionException {
testHash("HASH160", FunctionCode.HASH160_A_TO_B, FunctionCode.CHECK_A_EQUALS_B, "54d54a03fd447996ab004dee87fab80bf9477e23");
}
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));
}
}

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

Loading…
Cancel
Save