Browse Source

Conversion to big-endian. Hashing functions use 2 data, not A.

Conversion to big-endian to allow reuse of hash output without
having to swap endian. e.g. saving output of HASH160 into data
segment, and then hashing more of data segment to produce P2SH address.

Also changed hashing functions to fetch data start address and data
byte length from data segment, rather than loading values into A.

Tidied up some tests.
Remove obsolete ACCT test.
master
catbref 5 years ago
parent
commit
92281a1d04
  1. 5
      Java/src/main/java/org/ciyam/at/API.java
  2. 121
      Java/src/main/java/org/ciyam/at/FunctionCode.java
  3. 25
      Java/src/main/java/org/ciyam/at/MachineState.java
  4. 9
      Java/src/test/java/CallStackOpCodeTests.java
  5. 92
      Java/src/test/java/DisassemblyTests.java
  6. 109
      Java/src/test/java/FunctionCodeTests.java
  7. 9
      Java/src/test/java/MiscTests.java
  8. 7
      Java/src/test/java/SerializationTests.java
  9. 221
      Java/src/test/java/TestACCT.java
  10. 10
      Java/src/test/java/UserStackOpCodeTests.java
  11. 12
      Java/src/test/java/common/ACCTAPI.java
  12. 28
      Java/src/test/java/common/ExecutableTest.java
  13. 40
      Java/src/test/java/common/TestUtils.java

5
Java/src/main/java/org/ciyam/at/API.java

@ -4,7 +4,6 @@ import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toMap;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Map;
/**
@ -191,9 +190,7 @@ public abstract class API {
}
public void setA(MachineState state, byte[] bytes) {
// Enforce endian
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
state.a1 = byteBuffer.getLong();
state.a2 = byteBuffer.getLong();
@ -218,9 +215,7 @@ public abstract class API {
}
public void setB(MachineState state, byte[] bytes) {
// Enforce endian
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
state.b1 = byteBuffer.getLong();
state.b2 = byteBuffer.getLong();

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

@ -1,7 +1,6 @@
package org.ciyam.at;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
@ -451,22 +450,20 @@ public enum FunctionCode {
}
},
/**
* 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 data into B<br>
* <tt>0x0200 start-addr byte-length</tt>
* MD5 hash stored in B1 and B2. B3 and B4 are zeroed.
*/
MD5_A_TO_B(0x0200, 0, false) {
MD5_INTO_B(0x0200, 2, false) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
byte[] message = getHashData(state);
byte[] message = getHashData(functionData, state);
try {
MessageDigest digester = MessageDigest.getInstance("MD5");
byte[] digest = digester.digest(message);
ByteBuffer digestByteBuffer = ByteBuffer.wrap(digest);
digestByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
state.b1 = digestByteBuffer.getLong();
state.b2 = digestByteBuffer.getLong();
@ -478,23 +475,21 @@ public enum FunctionCode {
}
},
/**
* 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>
* Check MD5 of data matches B<br>
* <tt>0x0201 start-addr byte-length</tt><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) {
CHECK_MD5_WITH_B(0x0201, 2, true) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
byte[] message = getHashData(state);
byte[] message = getHashData(functionData, state);
try {
MessageDigest digester = MessageDigest.getInstance("MD5");
byte[] actualDigest = digester.digest(message);
ByteBuffer digestByteBuffer = ByteBuffer.allocate(2 * MachineState.VALUE_SIZE);
digestByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
ByteBuffer digestByteBuffer = ByteBuffer.allocate(digester.getDigestLength());
digestByteBuffer.putLong(state.b1);
digestByteBuffer.putLong(state.b2);
@ -511,26 +506,24 @@ public enum FunctionCode {
}
},
/**
* 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.
* RIPE-MD160 data into B<br>
* <tt>0x0202 start-addr byte-length</tt>
* RIPE-MD160 hash stored in LSB of B1 and all of B2 and B3. B4 is zeroed.
*/
RMD160_A_TO_B(0x0202, 0, false) {
RMD160_INTO_B(0x0202, 2, false) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
byte[] message = getHashData(state);
byte[] message = getHashData(functionData, state);
try {
MessageDigest digester = MessageDigest.getInstance("RIPEMD160");
byte[] digest = digester.digest(message);
ByteBuffer digestByteBuffer = ByteBuffer.wrap(digest);
digestByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
state.b1 = digestByteBuffer.getLong();
state.b1 = (long) digestByteBuffer.getInt() & 0xffffffffL;
state.b2 = digestByteBuffer.getLong();
state.b3 = (long) digestByteBuffer.getInt() & 0xffffffffL;
state.b3 = digestByteBuffer.getLong();
state.b4 = 0L;
} catch (NoSuchAlgorithmException e) {
throw new ExecutionException("No RIPEMD160 message digest service available", e);
@ -538,27 +531,25 @@ public enum FunctionCode {
}
},
/**
* 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.
* Check RIPE-MD160 of data matches B<br>
* <tt>0x0203 start-addr byte-length</tt><br>
* Other RIPE-MD160 hash is in LSB of B1 and all of B2 and B3. B4 is ignored.
* Returns 1 if true, 0 if false
*/
CHECK_RMD160_A_WITH_B(0x0203, 0, true) {
CHECK_RMD160_WITH_B(0x0203, 2, true) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
byte[] message = getHashData(state);
byte[] message = getHashData(functionData, state);
try {
MessageDigest digester = MessageDigest.getInstance("RIPEMD160");
byte[] actualDigest = digester.digest(message);
ByteBuffer digestByteBuffer = ByteBuffer.allocate(digester.getDigestLength());
digestByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
digestByteBuffer.putLong(state.b1);
digestByteBuffer.putInt((int) (state.b1 & 0xffffffffL));
digestByteBuffer.putLong(state.b2);
digestByteBuffer.putInt((int) (state.b3 & 0xffffffffL));
digestByteBuffer.putLong(state.b3);
// NOTE: b4 ignored
byte[] expectedDigest = digestByteBuffer.array();
@ -573,22 +564,20 @@ public enum FunctionCode {
}
},
/**
* 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 data into B<br>
* <tt>0x0204 start-addr byte-length</tt>
* SHA256 hash is stored in B1 through B4.
*/
SHA256_A_TO_B(0x0204, 0, false) {
SHA256_INTO_B(0x0204, 2, false) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
byte[] message = getHashData(state);
byte[] message = getHashData(functionData, state);
try {
MessageDigest digester = MessageDigest.getInstance("SHA-256");
byte[] digest = digester.digest(message);
ByteBuffer digestByteBuffer = ByteBuffer.wrap(digest);
digestByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
state.b1 = digestByteBuffer.getLong();
state.b2 = digestByteBuffer.getLong();
@ -600,23 +589,21 @@ public enum FunctionCode {
}
},
/**
* 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>
* Check SHA256 of data matches B<br>
* <tt>0x0205 start-addr byte-length</tt><br>
* Other SHA256 hash is in B1 through B4.
* Returns 1 if true, 0 if false
*/
CHECK_SHA256_A_WITH_B(0x0205, 0, true) {
CHECK_SHA256_WITH_B(0x0205, 2, true) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
byte[] message = getHashData(state);
byte[] message = getHashData(functionData, state);
try {
MessageDigest digester = MessageDigest.getInstance("SHA-256");
byte[] actualDigest = digester.digest(message);
ByteBuffer digestByteBuffer = ByteBuffer.allocate(4 * MachineState.VALUE_SIZE);
digestByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
ByteBuffer digestByteBuffer = ByteBuffer.allocate(digester.getDigestLength());
digestByteBuffer.putLong(state.b1);
digestByteBuffer.putLong(state.b2);
@ -635,16 +622,15 @@ public enum FunctionCode {
}
},
/**
* HASH160 data (offset A1, length A2) into B<br>
* <tt>0x0206</tt>
* HASH160 data into B<br>
* <tt>0x0206 start-addr byte-length</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 hash stored in LSB of B1 and all of B2 and B3. B4 is zeroed.
*/
HASH160_A_TO_B(0x0206, 0, false) {
HASH160_INTO_B(0x0206, 2, false) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
byte[] message = getHashData(state);
byte[] message = getHashData(functionData, state);
try {
MessageDigest sha256Digester = MessageDigest.getInstance("SHA-256");
@ -654,11 +640,10 @@ public enum FunctionCode {
byte[] rmd160Digest = rmd160Digester.digest(sha256Digest);
ByteBuffer digestByteBuffer = ByteBuffer.wrap(rmd160Digest);
digestByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
state.b1 = digestByteBuffer.getLong();
state.b1 = (long) digestByteBuffer.getInt() & 0xffffffffL;
state.b2 = digestByteBuffer.getLong();
state.b3 = (long) digestByteBuffer.getInt() & 0xffffffffL;
state.b3 = digestByteBuffer.getLong();
state.b4 = 0L;
} catch (NoSuchAlgorithmException e) {
throw new ExecutionException("No SHA-256 or RIPEMD160 message digest service available", e);
@ -666,16 +651,15 @@ public enum FunctionCode {
}
},
/**
* 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>
* Check HASH160 of data matches B<br>
* <tt>0x0207 start-addr byte-length</tt><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) {
CHECK_HASH160_WITH_B(0x0207, 2, true) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
byte[] message = getHashData(state);
byte[] message = getHashData(functionData, state);
try {
MessageDigest sha256Digester = MessageDigest.getInstance("SHA-256");
@ -685,11 +669,10 @@ public enum FunctionCode {
byte[] rmd160Digest = rmd160Digester.digest(sha256Digest);
ByteBuffer digestByteBuffer = ByteBuffer.allocate(rmd160Digester.getDigestLength());
digestByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
digestByteBuffer.putLong(state.b1);
digestByteBuffer.putInt((int) (state.b1 & 0xffffffffL));
digestByteBuffer.putLong(state.b2);
digestByteBuffer.putInt((int) (state.b3 & 0xffffffffL));
digestByteBuffer.putLong(state.b3);
// NOTE: b4 ignored
byte[] expectedDigest = digestByteBuffer.array();
@ -1019,17 +1002,17 @@ public enum FunctionCode {
// TODO: public abstract String disassemble();
protected byte[] getHashData(MachineState state) throws ExecutionException {
protected byte[] getHashData(FunctionData functionData, 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");
if (functionData.value1 < 0L || functionData.value1 > Integer.MAX_VALUE || functionData.value1 >= state.numDataPages)
throw new ExecutionException(this.name() + " data start address 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");
if (functionData.value2 < 0L || functionData.value2 > Integer.MAX_VALUE || functionData.value1 + byteLengthToDataLength(functionData.value2) > state.numDataPages)
throw new ExecutionException(this.name() + " data length invalid");
final int dataStart = (int) (state.a1 & 0x7fffffffL);
final int dataLength = (int) (state.a2 & 0x7fffffffL);
final int dataStart = (int) (functionData.value1 & 0x7fffffffL);
final int dataLength = (int) (functionData.value2 & 0x7fffffffL);
byte[] message = new byte[dataLength];

25
Java/src/main/java/org/ciyam/at/MachineState.java

@ -3,7 +3,6 @@ package org.ciyam.at;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@ -143,7 +142,6 @@ public class MachineState {
// Parsing header bytes
ByteBuffer byteBuffer = ByteBuffer.wrap(this.headerBytes);
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
this.version = byteBuffer.getShort();
if (this.version < 1)
@ -174,14 +172,14 @@ public class MachineState {
this.minActivationAmount = byteBuffer.getLong();
// Header OK - set up code and data buffers
this.codeByteBuffer = ByteBuffer.allocate(this.numCodePages * this.constants.CODE_PAGE_SIZE).order(ByteOrder.LITTLE_ENDIAN);
this.dataByteBuffer = ByteBuffer.allocate(this.numDataPages * this.constants.DATA_PAGE_SIZE).order(ByteOrder.LITTLE_ENDIAN);
this.codeByteBuffer = ByteBuffer.allocate(this.numCodePages * this.constants.CODE_PAGE_SIZE);
this.dataByteBuffer = ByteBuffer.allocate(this.numDataPages * this.constants.DATA_PAGE_SIZE);
// Set up stacks
this.callStackByteBuffer = ByteBuffer.allocate(this.numCallStackPages * this.constants.CALL_STACK_PAGE_SIZE).order(ByteOrder.LITTLE_ENDIAN);
this.callStackByteBuffer = ByteBuffer.allocate(this.numCallStackPages * this.constants.CALL_STACK_PAGE_SIZE);
this.callStackByteBuffer.position(this.callStackByteBuffer.limit()); // Downward-growing stack, so start at the end
this.userStackByteBuffer = ByteBuffer.allocate(this.numUserStackPages * this.constants.USER_STACK_PAGE_SIZE).order(ByteOrder.LITTLE_ENDIAN);
this.userStackByteBuffer = ByteBuffer.allocate(this.numUserStackPages * this.constants.USER_STACK_PAGE_SIZE);
this.userStackByteBuffer.position(this.userStackByteBuffer.limit()); // Downward-growing stack, so start at the end
this.api = api;
@ -346,7 +344,6 @@ public class MachineState {
public byte[] getA() {
ByteBuffer byteBuffer = ByteBuffer.allocate(4 * 8);
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
byteBuffer.putLong(this.a1);
byteBuffer.putLong(this.a2);
@ -374,7 +371,6 @@ public class MachineState {
public byte[] getB() {
ByteBuffer byteBuffer = ByteBuffer.allocate(4 * 8);
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
byteBuffer.putLong(this.b1);
byteBuffer.putLong(this.b2);
@ -455,7 +451,6 @@ public class MachineState {
byte[] creationBytes = new byte[creationBytesLength];
ByteBuffer byteBuffer = ByteBuffer.wrap(creationBytes);
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
// Header bytes:
@ -574,7 +569,7 @@ public class MachineState {
/** For restoring a previously serialized machine state */
public static MachineState fromBytes(API api, LoggerInterface logger, byte[] bytes, byte[] codeBytes) {
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
byte[] headerBytes = new byte[HEADER_LENGTH];
byteBuffer.get(headerBytes);
@ -680,15 +675,15 @@ public class MachineState {
}
}
/** Convert int to little-endian byte array */
/** Convert int to big-endian byte array */
private byte[] toByteArray(int value) {
return new byte[] { (byte) (value), (byte) (value >> 8), (byte) (value >> 16), (byte) (value >> 24) };
return new byte[] { (byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), (byte) (value) };
}
/** Convert long to little-endian byte array */
/** Convert long to big-endian byte array */
private byte[] toByteArray(long value) {
return new byte[] { (byte) (value), (byte) (value >> 8), (byte) (value >> 16), (byte) (value >> 24), (byte) (value >> 32), (byte) (value >> 40),
(byte) (value >> 48), (byte) (value >> 56) };
return new byte[] { (byte) (value >> 56), (byte) (value >> 48), (byte) (value >> 40), (byte) (value >> 32),
(byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), (byte) (value) };
}
/**

9
Java/src/test/java/CallStackOpCodeTests.java

@ -1,4 +1,3 @@
import static common.TestUtils.*;
import static org.junit.Assert.*;
import org.ciyam.at.ExecutionException;
@ -28,7 +27,7 @@ public class CallStackOpCodeTests extends ExecutableTest {
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
int expectedCallStackPosition = (state.numCallStackPages - 1) * CALL_STACK_PAGE_SIZE;
int expectedCallStackPosition = (state.numCallStackPages - 1) * MachineState.ADDRESS_SIZE;
assertEquals("Call stack pointer incorrect", expectedCallStackPosition, getCallStackPosition());
assertEquals("Return address does not match", returnAddress, getCallStackEntry(expectedCallStackPosition));
@ -62,7 +61,7 @@ public class CallStackOpCodeTests extends ExecutableTest {
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
int expectedCallStackPosition = (state.numCallStackPages - 1 - 1) * CALL_STACK_PAGE_SIZE;
int expectedCallStackPosition = (state.numCallStackPages - 1 - 1) * MachineState.ADDRESS_SIZE;
assertEquals("Call stack pointer incorrect", expectedCallStackPosition, getCallStackPosition());
assertEquals("Return address does not match", returnAddress2, getCallStackEntry(expectedCallStackPosition));
@ -106,7 +105,7 @@ public class CallStackOpCodeTests extends ExecutableTest {
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
int expectedCallStackPosition = (state.numCallStackPages - 1 + 1) * CALL_STACK_PAGE_SIZE;
int expectedCallStackPosition = (state.numCallStackPages - 1 + 1) * MachineState.ADDRESS_SIZE;
assertEquals("Call stack pointer incorrect", expectedCallStackPosition, getCallStackPosition());
assertEquals("Return address not cleared", 0L, getCallStackEntry(expectedCallStackPosition - MachineState.ADDRESS_SIZE));
@ -143,7 +142,7 @@ public class CallStackOpCodeTests extends ExecutableTest {
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
int expectedCallStackPosition = (state.numCallStackPages - 1 - 1 + 1 + 1) * CALL_STACK_PAGE_SIZE;
int expectedCallStackPosition = (state.numCallStackPages - 1 - 1 + 1 + 1) * MachineState.ADDRESS_SIZE;
assertEquals("Call stack pointer incorrect", expectedCallStackPosition, getCallStackPosition());
assertEquals("Return address not cleared", 0L, getCallStackEntry(expectedCallStackPosition - MachineState.ADDRESS_SIZE));

92
Java/src/test/java/DisassemblyTests.java

@ -1,90 +1,54 @@
import static common.TestUtils.hexToBytes;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.Security;
import java.nio.charset.StandardCharsets;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.ciyam.at.API;
import org.ciyam.at.ExecutionException;
import org.ciyam.at.FunctionCode;
import org.ciyam.at.MachineState;
import org.ciyam.at.OpCode;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import common.TestAPI;
import common.TestLogger;
import common.ExecutableTest;
import common.TestUtils;
public class DisassemblyTests {
public class DisassemblyTests extends ExecutableTest {
public TestLogger logger;
public API api;
public MachineState state;
public ByteBuffer codeByteBuffer;
private static final String message = "The quick, brown fox jumped over the lazy dog.";
private static final byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);
@BeforeClass
public static void beforeClass() {
Security.insertProviderAt(new BouncyCastleProvider(), 0);
}
@Test
public void testRMD160disassembly() throws ExecutionException {
// Data addr 0 for setting values
dataByteBuffer.putLong(0L);
// Data addr 1 for results
dataByteBuffer.putLong(0L);
@Before
public void beforeTest() {
logger = new TestLogger();
api = new TestAPI();
codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN);
}
// Data addr 2 has start of message bytes (address 4)
dataByteBuffer.putLong(4L);
@After
public void afterTest() {
codeByteBuffer = null;
api = null;
logger = null;
}
// Data addr 3 has length of message bytes
dataByteBuffer.putLong(messageBytes.length);
@Test
public void testMD160disassembly() throws ExecutionException {
// MD160 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);
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A4.value).putInt(0);
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("90e735014ea23aa8"));
// Data addr 4+ for message
dataByteBuffer.put(messageBytes);
// RMD160 of "The quick, brown fox jumped over the lazy dog." is b5a4b1898af3745dbbb5becb83e72787df9952c9
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("00000000b5a4b189"));
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.SET_VAL.value).putInt(0).put(hexToBytes("8af3745dbbb5becb"));
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.SET_VAL.value).putInt(0).put(hexToBytes("83e72787df9952c9"));
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B3.value).putInt(0);
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);
// 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");
byte[] codeBytes = codeByteBuffer.array();
byte[] dataBytes = new byte[0];
codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.value).putShort(FunctionCode.CHECK_RMD160_WITH_B.value).putInt(1).putInt(2).putInt(3);
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.ECHO.value).putInt(1);
System.out.println(state.disassemble());
}
codeByteBuffer.put(OpCode.FIN_IMD.value);
@Test
public void testACCTdisassembly() throws ExecutionException {
codeByteBuffer.put(hexToBytes("3501030900000006040000000900000029302009000000040000000f1ab4000000330403090000003525010a000000260a00"));
codeByteBuffer.put(hexToBytes("0000320903350703090000003526010a0000001b0a000000cd32280133160100000000331701010000003318010200000033"));
codeByteBuffer.put(hexToBytes("1901030000003505020a0000001b0a000000a1320b033205041e050000001833000509000000320a033203041ab400000033"));
codeByteBuffer.put(hexToBytes("160105000000331701060000003318010700000033190108000000320304320b033203041ab7000000000000000000000000"));
codeByteBuffer.put(hexToBytes("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"));
codeByteBuffer.put(hexToBytes("000000000000"));
// 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");
byte[] headerBytes = TestUtils.HEADER_BYTES;
byte[] codeBytes = codeByteBuffer.array();
byte[] dataBytes = new byte[0];
byte[] dataBytes = dataByteBuffer.array();
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);

109
Java/src/test/java/FunctionCodeTests.java

@ -20,42 +20,42 @@ public class FunctionCodeTests extends ExecutableTest {
@Test
public void testMD5() throws ExecutionException {
testHash("MD5", FunctionCode.MD5_A_TO_B, FunctionCode.CHECK_A_EQUALS_B, "1388a82384756096e627e3671e2624bf");
testHash("MD5", FunctionCode.MD5_INTO_B, "1388a82384756096e627e3671e2624bf");
}
@Test
public void testCHECK_MD5() throws ExecutionException {
testHash("MD5", null, FunctionCode.CHECK_MD5_A_WITH_B, "1388a82384756096e627e3671e2624bf");
checkHash("MD5", FunctionCode.CHECK_MD5_WITH_B, "1388a82384756096e627e3671e2624bf");
}
@Test
public void testRMD160() throws ExecutionException {
testHash("RIPE-MD160", FunctionCode.RMD160_A_TO_B, FunctionCode.CHECK_A_EQUALS_B, "b5a4b1898af3745dbbb5becb83e72787df9952c9");
testHash("RIPE-MD160", FunctionCode.RMD160_INTO_B, "b5a4b1898af3745dbbb5becb83e72787df9952c9");
}
@Test
public void testCHECK_RMD160() throws ExecutionException {
testHash("RIPE-MD160", null, FunctionCode.CHECK_RMD160_A_WITH_B, "b5a4b1898af3745dbbb5becb83e72787df9952c9");
checkHash("RIPE-MD160", FunctionCode.CHECK_RMD160_WITH_B, "b5a4b1898af3745dbbb5becb83e72787df9952c9");
}
@Test
public void testSHA256() throws ExecutionException {
testHash("SHA256", FunctionCode.SHA256_A_TO_B, FunctionCode.CHECK_A_EQUALS_B, "c01d63749ebe5d6b16f7247015cac2e49a5ac4fb6c7f24bed07b8aa904da97f3");
testHash("SHA256", FunctionCode.SHA256_INTO_B, "c01d63749ebe5d6b16f7247015cac2e49a5ac4fb6c7f24bed07b8aa904da97f3");
}
@Test
public void testCHECK_SHA256() throws ExecutionException {
testHash("SHA256", null, FunctionCode.CHECK_SHA256_A_WITH_B, "c01d63749ebe5d6b16f7247015cac2e49a5ac4fb6c7f24bed07b8aa904da97f3");
checkHash("SHA256", FunctionCode.CHECK_SHA256_WITH_B, "c01d63749ebe5d6b16f7247015cac2e49a5ac4fb6c7f24bed07b8aa904da97f3");
}
@Test
public void testHASH160() throws ExecutionException {
testHash("HASH160", FunctionCode.HASH160_A_TO_B, FunctionCode.CHECK_A_EQUALS_B, "54d54a03fd447996ab004dee87fab80bf9477e23");
testHash("HASH160", FunctionCode.HASH160_INTO_B, "54d54a03fd447996ab004dee87fab80bf9477e23");
}
@Test
public void testCHECK_HASH160() throws ExecutionException {
testHash("HASH160", null, FunctionCode.CHECK_HASH160_A_WITH_B, "54d54a03fd447996ab004dee87fab80bf9477e23");
checkHash("HASH160", FunctionCode.CHECK_HASH160_WITH_B, "54d54a03fd447996ab004dee87fab80bf9477e23");
}
@Test
@ -107,49 +107,61 @@ public class FunctionCodeTests extends ExecutableTest {
assertTrue(state.getHadFatalError());
}
private void testHash(String hashName, FunctionCode hashFunction, FunctionCode checkFunction, String expected) throws ExecutionException {
private void testHash(String hashName, FunctionCode hashFunction, 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);
// Data addr 2 has start of message bytes (address 4)
dataByteBuffer.putLong(4L);
// 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);
// Data addr 3 has length of message bytes
dataByteBuffer.putLong(messageBytes.length);
// 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);
// Data addr 4+ for message
dataByteBuffer.put(messageBytes);
// A3 unused
// A4 unused
// Actual hash function
codeByteBuffer.put(OpCode.EXT_FUN_DAT_2.value).putShort(hashFunction.value).putInt(2).putInt(3);
// 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);
}
// 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);
loadHashIntoB(expected);
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
// Check actual hash output (in A) with expected result (in B) and save equality output into address 1
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_A_EQUALS_B.value).putInt(1);
final FunctionCode bSettingFunction = bSettingFunctions[bWord];
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(bSettingFunction.value).putInt(0);
}
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));
}
private void checkHash(String hashName, 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 has start of message bytes (address 4)
dataByteBuffer.putLong(4L);
// Data addr 3 has length of message bytes
dataByteBuffer.putLong(messageBytes.length);
// Data addr 4+ for message
dataByteBuffer.put(messageBytes);
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(checkFunction.value).putInt(1);
// Expected result goes into B
loadHashIntoB(expected);
codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.value).putShort(checkFunction.value).putInt(1).putInt(2).putInt(3);
codeByteBuffer.put(OpCode.FIN_IMD.value);
@ -160,4 +172,27 @@ public class FunctionCodeTests extends ExecutableTest {
assertEquals(hashName + " hashes do not match", 1L, getData(1));
}
private void loadHashIntoB(String expected) {
// 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)
int numLongs = (expected.length() + 15) / 16;
for (int longIndex = 0; longIndex < numLongs; ++longIndex) {
final int endIndex = expected.length() - (numLongs - longIndex - 1) * 16;
final int beginIndex = Math.max(0, endIndex - 16);
String hexChars = expected.substring(beginIndex, endIndex);
codeByteBuffer.put(OpCode.SET_VAL.value);
codeByteBuffer.putInt(0); // addr 0
codeByteBuffer.put(new byte[8 - hexChars.length() / 2]); // pad LSB with zeros
codeByteBuffer.put(hexToBytes(hexChars));
final FunctionCode bSettingFunction = bSettingFunctions[longIndex];
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(bSettingFunction.value).putInt(0);
}
}
}

9
Java/src/test/java/MiscTests.java

@ -1,4 +1,3 @@
import static common.TestUtils.hexToBytes;
import static org.junit.Assert.*;
import org.ciyam.at.ExecutionException;
@ -8,6 +7,7 @@ import org.ciyam.at.OpCode;
import org.junit.Test;
import common.ExecutableTest;
import common.TestUtils;
public class MiscTests extends ExecutableTest {
@ -52,17 +52,16 @@ public class MiscTests extends ExecutableTest {
@Test
public void testMinActivation() throws ExecutionException {
long minActivation = 12345L; // 0x0000000000003039
long minActivationAmount = 12345L; // 0x0000000000003039
// version 0002, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4, minActivation = 12345L
byte[] headerBytes = hexToBytes("0200" + "0000" + "0002" + "2000" + "1000" + "1000" + "3930000000000000");
byte[] headerBytes = TestUtils.toHeaderBytes(TestUtils.VERSION, TestUtils.NUM_CODE_PAGES, TestUtils.NUM_DATA_PAGES, TestUtils.NUM_CALL_STACK_PAGES, TestUtils.NUM_USER_STACK_PAGES, minActivationAmount);
byte[] codeBytes = codeByteBuffer.array();
byte[] dataBytes = new byte[0];
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
assertTrue(state.getIsFrozen());
assertEquals((Long) (minActivation - 1L), state.getFrozenBalance());
assertEquals((Long) (minActivationAmount - 1L), state.getFrozenBalance());
}
}

7
Java/src/test/java/SerializationTests.java

@ -2,7 +2,6 @@ import static common.TestUtils.hexToBytes;
import static org.junit.Assert.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import org.ciyam.at.ExecutionException;
@ -15,6 +14,7 @@ import org.junit.Test;
import common.TestAPI;
import common.TestLogger;
import common.TestUtils;
public class SerializationTests {
@ -27,7 +27,7 @@ public class SerializationTests {
public void beforeTest() {
logger = new TestLogger();
api = new TestAPI();
codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN);
codeByteBuffer = ByteBuffer.allocate(512);
}
@After
@ -38,8 +38,7 @@ public class SerializationTests {
}
private byte[] simulate() {
// 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");
byte[] headerBytes = TestUtils.HEADER_BYTES;
byte[] codeBytes = codeByteBuffer.array();
byte[] dataBytes = new byte[0];

221
Java/src/test/java/TestACCT.java

@ -1,221 +0,0 @@
import static common.TestUtils.hexToBytes;
import static org.junit.Assert.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.ciyam.at.ExecutionException;
import org.ciyam.at.FunctionCode;
import org.ciyam.at.MachineState;
import org.ciyam.at.OpCode;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import common.ACCTAPI;
import common.TestLogger;
public class TestACCT {
public TestLogger logger;
public ACCTAPI api;
public MachineState state;
public ByteBuffer codeByteBuffer;
public ByteBuffer dataByteBuffer;
@BeforeClass
public static void beforeClass() {
Security.insertProviderAt(new BouncyCastleProvider(), 0);
}
@Before
public void beforeTest() {
logger = new TestLogger();
api = new ACCTAPI();
codeByteBuffer = ByteBuffer.allocate(0x0200 * 1).order(ByteOrder.LITTLE_ENDIAN);
dataByteBuffer = ByteBuffer.allocate(0x0020 * 8).order(ByteOrder.LITTLE_ENDIAN);
}
@After
public void afterTest() {
dataByteBuffer = null;
codeByteBuffer = null;
api = null;
logger = null;
}
private byte[] simulate() {
// 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");
byte[] codeBytes = codeByteBuffer.array();
byte[] dataBytes = dataByteBuffer.array();
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
return executeAndCheck(state);
}
private byte[] continueSimulation(byte[] savedState) {
byte[] codeBytes = codeByteBuffer.array();
state = MachineState.fromBytes(api, logger, savedState, codeBytes);
return executeAndCheck(state);
}
private byte[] executeAndCheck(MachineState state) {
state.execute();
api.setCurrentBalance(state.getCurrentBalance());
byte[] stateBytes = state.toBytes();
byte[] codeBytes = state.getCodeBytes();
MachineState restoredState = MachineState.fromBytes(api, logger, stateBytes, codeBytes);
byte[] restoredStateBytes = restoredState.toBytes();
byte[] restoredCodeBytes = state.getCodeBytes();
assertTrue("Serialization->Deserialization->Reserialization error", Arrays.equals(stateBytes, restoredStateBytes));
assertTrue("Serialization->Deserialization->Reserialization error", Arrays.equals(codeBytes, restoredCodeBytes));
return stateBytes;
}
@Test
public void testACCT() throws ExecutionException {
// DATA
final int addrHashPart1 = 0x0;
final int addrHashPart2 = 0x1;
final int addrHashPart3 = 0x2;
final int addrHashPart4 = 0x3;
final int addrAddressPart1 = 0x4;
final int addrAddressPart2 = 0x5;
final int addrAddressPart3 = 0x6;
final int addrAddressPart4 = 0x7;
final int addrRefundMinutes = 0x8;
final int addrRefundTimestamp = 0x9;
final int addrLastTimestamp = 0xa;
final int addrBlockTimestamp = 0xb;
final int addrTxType = 0xc;
final int addrComparator = 0xd;
final int addrAddressTemp1 = 0xe;
final int addrAddressTemp2 = 0xf;
final int addrAddressTemp3 = 0x10;
final int addrAddressTemp4 = 0x11;
byte[] secret = new byte[32];
new SecureRandom().nextBytes(secret);
try {
MessageDigest digester = MessageDigest.getInstance("SHA-256");
byte[] digest = digester.digest(secret);
dataByteBuffer.put(digest);
} catch (NoSuchAlgorithmException e) {
throw new ExecutionException("No SHA-256 message digest service available", e);
}
// Destination address (based on "R" for "Responder", where "R" is 0x52)
dataByteBuffer.put(hexToBytes("5200000000000000520000000000000052000000000000005200000000000000"));
// Expiry in minutes (but actually blocks in this test case)
dataByteBuffer.putLong(8L);
// Code labels
final int addrTxLoop = 0x36;
final int addrCheckTx = 0x4b;
final int addrCheckSender = 0x64;
final int addrCheckMessage = 0xab;
final int addrPayout = 0xdf;
final int addrRefund = 0x102;
int tempPC;
// init:
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_CREATION_TIMESTAMP.value).putInt(addrRefundTimestamp);
codeByteBuffer.put(OpCode.SET_DAT.value).putInt(addrLastTimestamp).putInt(addrRefundTimestamp);
codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.value).putShort(FunctionCode.ADD_MINUTES_TO_TIMESTAMP.value).putInt(addrRefundTimestamp)
.putInt(addrRefundTimestamp).putInt(addrRefundMinutes);
codeByteBuffer.put(OpCode.SET_PCS.value);
// loop:
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_BLOCK_TIMESTAMP.value).putInt(addrBlockTimestamp);
tempPC = codeByteBuffer.position();
codeByteBuffer.put(OpCode.BLT_DAT.value).putInt(addrBlockTimestamp).putInt(addrRefundTimestamp).put((byte) (addrTxLoop - tempPC));
codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(addrRefund);
// txloop:
assertEquals(addrTxLoop, codeByteBuffer.position());
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PUT_TX_AFTER_TIMESTAMP_IN_A.value).putInt(addrLastTimestamp);
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_A_IS_ZERO.value).putInt(addrComparator);
tempPC = codeByteBuffer.position();
codeByteBuffer.put(OpCode.BZR_DAT.value).putInt(addrComparator).put((byte) (addrCheckTx - tempPC));
codeByteBuffer.put(OpCode.STP_IMD.value);
// checkTx:
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A.value).putInt(addrLastTimestamp);
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_TYPE_FROM_TX_IN_A.value).putInt(addrTxType);
tempPC = codeByteBuffer.position();
codeByteBuffer.put(OpCode.BNZ_DAT.value).putInt(addrTxType).put((byte) (addrCheckSender - tempPC));
codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(addrTxLoop);
// checkSender
assertEquals(addrCheckSender, codeByteBuffer.position());
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_ADDRESS_FROM_TX_IN_A_INTO_B.value);
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B1.value).putInt(addrAddressTemp1);
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B2.value).putInt(addrAddressTemp2);
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B3.value).putInt(addrAddressTemp3);
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B4.value).putInt(addrAddressTemp4);
tempPC = codeByteBuffer.position();
codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp1).putInt(addrAddressPart1).put((byte) (addrTxLoop - tempPC));
tempPC = codeByteBuffer.position();
codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp2).putInt(addrAddressPart2).put((byte) (addrTxLoop - tempPC));
tempPC = codeByteBuffer.position();
codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp3).putInt(addrAddressPart3).put((byte) (addrTxLoop - tempPC));
tempPC = codeByteBuffer.position();
codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp4).putInt(addrAddressPart4).put((byte) (addrTxLoop - tempPC));
// checkMessage:
assertEquals(addrCheckMessage, codeByteBuffer.position());
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B.value);
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.SWAP_A_AND_B.value);
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B1.value).putInt(addrHashPart1);
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B2.value).putInt(addrHashPart2);
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B3.value).putInt(addrHashPart3);
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B4.value).putInt(addrHashPart4);
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_SHA256_A_WITH_B.value).putInt(addrComparator);
tempPC = codeByteBuffer.position();
codeByteBuffer.put(OpCode.BNZ_DAT.value).putInt(addrComparator).put((byte) (addrPayout - tempPC));
codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(addrTxLoop);
// payout:
assertEquals(addrPayout, codeByteBuffer.position());
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B1.value).putInt(addrAddressPart1);
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B2.value).putInt(addrAddressPart2);
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B3.value).putInt(addrAddressPart3);
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B4.value).putInt(addrAddressPart4);
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.MESSAGE_A_TO_ADDRESS_IN_B.value);
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PAY_ALL_TO_ADDRESS_IN_B.value);
codeByteBuffer.put(OpCode.FIN_IMD.value);
// refund:
assertEquals(addrRefund, codeByteBuffer.position());
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_CREATOR_INTO_B.value);
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PAY_ALL_TO_ADDRESS_IN_B.value);
codeByteBuffer.put(OpCode.FIN_IMD.value);
byte[] savedState = simulate();
while (!state.getIsFinished()) {
((ACCTAPI) state.getAPI()).generateNextBlock(secret);
savedState = continueSimulation(savedState);
}
}
}

10
Java/src/test/java/UserStackOpCodeTests.java

@ -1,7 +1,7 @@
import static common.TestUtils.*;
import static org.junit.Assert.*;
import org.ciyam.at.ExecutionException;
import org.ciyam.at.MachineState;
import org.ciyam.at.OpCode;
import org.junit.Test;
@ -20,7 +20,7 @@ public class UserStackOpCodeTests extends ExecutableTest {
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
int expectedUserStackPosition = (state.numUserStackPages - 1) * USER_STACK_PAGE_SIZE;
int expectedUserStackPosition = (state.numUserStackPages - 1) * MachineState.VALUE_SIZE;
assertEquals("User stack pointer incorrect", expectedUserStackPosition, getUserStackPosition());
assertEquals("Data does not match", 4444L, getUserStackEntry(expectedUserStackPosition));
}
@ -38,7 +38,7 @@ public class UserStackOpCodeTests extends ExecutableTest {
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
int expectedUserStackPosition = (state.numUserStackPages - 2) * USER_STACK_PAGE_SIZE;
int expectedUserStackPosition = (state.numUserStackPages - 2) * MachineState.VALUE_SIZE;
assertEquals("User stack pointer incorrect", expectedUserStackPosition, getUserStackPosition());
assertEquals("Data does not match", 3333L, getUserStackEntry(expectedUserStackPosition));
}
@ -95,7 +95,7 @@ public class UserStackOpCodeTests extends ExecutableTest {
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
int expectedUserStackPosition = (state.numUserStackPages - 1 + 1) * USER_STACK_PAGE_SIZE;
int expectedUserStackPosition = (state.numUserStackPages - 1 + 1) * MachineState.VALUE_SIZE;
assertEquals("User stack pointer incorrect", expectedUserStackPosition, getUserStackPosition());
assertEquals("Data does not match", 4444L, getData(1));
// Following test is not applicable when using serialized state:
@ -117,7 +117,7 @@ public class UserStackOpCodeTests extends ExecutableTest {
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
int expectedUserStackPosition = (state.numUserStackPages - 1 - 1 + 1 + 1) * USER_STACK_PAGE_SIZE;
int expectedUserStackPosition = (state.numUserStackPages - 1 - 1 + 1 + 1) * MachineState.VALUE_SIZE;
assertEquals("User stack pointer incorrect", expectedUserStackPosition, getUserStackPosition());
assertEquals("Data does not match", 3333L, getData(2));
assertEquals("Data does not match", 4444L, getData(3));

12
Java/src/test/java/common/ACCTAPI.java

@ -129,17 +129,17 @@ public class ACCTAPI extends API {
this.blockchain.add(block);
}
/** Convert long to little-endian byte array */
/** Convert long to big-endian byte array */
@SuppressWarnings("unused")
private byte[] toByteArray(long value) {
return new byte[] { (byte) (value), (byte) (value >> 8), (byte) (value >> 16), (byte) (value >> 24), (byte) (value >> 32), (byte) (value >> 40),
(byte) (value >> 48), (byte) (value >> 56) };
return new byte[] { (byte) (value >> 56), (byte) (value >> 48), (byte) (value >> 40), (byte) (value >> 32),
(byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), (byte) (value) };
}
/** Convert part of little-endian byte[] to long */
/** Convert part of big-endian byte[] to long */
private long fromBytes(byte[] bytes, int start) {
return (bytes[start] & 0xffL) | (bytes[start + 1] & 0xffL) << 8 | (bytes[start + 2] & 0xffL) << 16 | (bytes[start + 3] & 0xffL) << 24
| (bytes[start + 4] & 0xffL) << 32 | (bytes[start + 5] & 0xffL) << 40 | (bytes[start + 6] & 0xffL) << 48 | (bytes[start + 7] & 0xffL) << 56;
return (bytes[start] & 0xffL) << 56 | (bytes[start + 1] & 0xffL) << 48 | (bytes[start + 2] & 0xffL) << 40 | (bytes[start + 3] & 0xffL) << 32
| (bytes[start + 4] & 0xffL) << 24 | (bytes[start + 5] & 0xffL) << 16 | (bytes[start + 6] & 0xffL) << 8 | (bytes[start + 7] & 0xffL);
}
private String getRandomAccount() {

28
Java/src/test/java/common/ExecutableTest.java

@ -1,9 +1,6 @@
package common;
import static common.TestUtils.hexToBytes;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
@ -14,10 +11,8 @@ 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 DATA_STACK_SIZE = 0x0200;
public static final int CALL_STACK_OFFSET = DATA_OFFSET + DATA_STACK_SIZE * 8;
private static final int DATA_OFFSET = MachineState.HEADER_LENGTH; // code bytes are not present
private static final int CALL_STACK_OFFSET = DATA_OFFSET + TestUtils.NUM_DATA_PAGES * MachineState.VALUE_SIZE;
public TestLogger logger;
public TestAPI api;
@ -38,8 +33,8 @@ public abstract class ExecutableTest {
public void beforeTest() {
logger = new TestLogger();
api = new TestAPI();
codeByteBuffer = ByteBuffer.allocate(CODE_STACK_SIZE).order(ByteOrder.LITTLE_ENDIAN);
dataByteBuffer = ByteBuffer.allocate(DATA_STACK_SIZE).order(ByteOrder.LITTLE_ENDIAN);
codeByteBuffer = ByteBuffer.allocate(TestUtils.NUM_CODE_PAGES * MachineState.OPCODE_SIZE);
dataByteBuffer = ByteBuffer.allocate(TestUtils.NUM_DATA_PAGES * MachineState.VALUE_SIZE);
stateByteBuffer = null;
}
@ -53,8 +48,7 @@ public abstract class ExecutableTest {
}
protected void execute(boolean onceOnly) {
// 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[] headerBytes = TestUtils.HEADER_BYTES;
byte[] codeBytes = codeByteBuffer.array();
byte[] dataBytes = dataByteBuffer.array();
@ -98,9 +92,9 @@ public abstract class ExecutableTest {
byte[] stateBytes = state.toBytes();
// We know how the state will be serialized so we can extract values
// header(6) + data(size * 8) + callStack length(4) + callStack + userStack length(4) + userStack
// header + data(size * 8) + callStack length(4) + callStack + userStack length(4) + userStack
stateByteBuffer = ByteBuffer.wrap(stateBytes).order(ByteOrder.LITTLE_ENDIAN);
stateByteBuffer = ByteBuffer.wrap(stateBytes);
callStackSize = stateByteBuffer.getInt(CALL_STACK_OFFSET);
userStackOffset = CALL_STACK_OFFSET + 4 + callStackSize;
userStackSize = stateByteBuffer.getInt(userStackOffset);
@ -112,20 +106,20 @@ public abstract class ExecutableTest {
}
protected int getCallStackPosition() {
return 0x0010 * MachineState.ADDRESS_SIZE - callStackSize;
return TestUtils.NUM_CALL_STACK_PAGES * MachineState.ADDRESS_SIZE - callStackSize;
}
protected int getCallStackEntry(int address) {
int index = CALL_STACK_OFFSET + 4 + address - 0x0010 * MachineState.ADDRESS_SIZE + callStackSize;
int index = CALL_STACK_OFFSET + 4 + address - TestUtils.NUM_CALL_STACK_PAGES * MachineState.ADDRESS_SIZE + callStackSize;
return stateByteBuffer.getInt(index);
}
protected int getUserStackPosition() {
return 0x0010 * MachineState.VALUE_SIZE - userStackSize;
return TestUtils.NUM_USER_STACK_PAGES * MachineState.VALUE_SIZE - userStackSize;
}
protected long getUserStackEntry(int address) {
int index = userStackOffset + 4 + address - 0x0010 * MachineState.VALUE_SIZE + userStackSize;
int index = userStackOffset + 4 + address - TestUtils.NUM_USER_STACK_PAGES * MachineState.VALUE_SIZE + userStackSize;
return stateByteBuffer.getLong(index);
}

40
Java/src/test/java/common/TestUtils.java

@ -1,16 +1,19 @@
package common;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import org.ciyam.at.MachineState;
public class TestUtils {
// v3 constants replicated due to private scope in MachineState
public static final int CODE_PAGE_SIZE = 1;
public static final int DATA_PAGE_SIZE = MachineState.VALUE_SIZE;
public static final int CALL_STACK_PAGE_SIZE = MachineState.ADDRESS_SIZE;
public static final int USER_STACK_PAGE_SIZE = MachineState.VALUE_SIZE;
public static final short VERSION = 2;
public static final short NUM_CODE_PAGES = 0x0200;
public static final short NUM_DATA_PAGES = 0x0200;
public static final short NUM_CALL_STACK_PAGES = 0x0010;
public static final short NUM_USER_STACK_PAGES = 0x0010;
public static final long MIN_ACTIVATION_AMOUNT = 0L;
public static final byte[] HEADER_BYTES = toHeaderBytes(VERSION, NUM_CODE_PAGES, NUM_DATA_PAGES, NUM_CALL_STACK_PAGES, NUM_USER_STACK_PAGES, MIN_ACTIVATION_AMOUNT);
public static byte[] hexToBytes(String hex) {
byte[] output = new byte[hex.length() / 2];
@ -26,4 +29,31 @@ public class TestUtils {
return output;
}
public static byte[] toHeaderBytes(short version, short numCodePages, short numDataPages, short numCallStackPages, short numUserStackPages, long minActivationAmount) {
ByteBuffer byteBuffer = ByteBuffer.allocate(MachineState.HEADER_LENGTH);
// Version
byteBuffer.putShort(version);
// Reserved
byteBuffer.putShort((short) 0);
// Code length
byteBuffer.putShort(numCodePages);
// Data length
byteBuffer.putShort(numDataPages);
// Call stack length
byteBuffer.putShort(numCallStackPages);
// User stack length
byteBuffer.putShort(numUserStackPages);
// Minimum activation amount
byteBuffer.putLong(minActivationAmount);
return byteBuffer.array();
}
}

Loading…
Cancel
Save