3
0
mirror of https://github.com/Qortal/AT.git synced 2025-02-11 17:55:52 +00:00

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.
This commit is contained in:
catbref 2020-03-09 13:29:34 +00:00
parent 36029c132f
commit 92281a1d04
13 changed files with 236 additions and 464 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -1,90 +1,54 @@
import static common.TestUtils.hexToBytes; import static common.TestUtils.hexToBytes;
import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets;
import java.nio.ByteOrder;
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.ciyam.at.API;
import org.ciyam.at.ExecutionException; import org.ciyam.at.ExecutionException;
import org.ciyam.at.FunctionCode; import org.ciyam.at.FunctionCode;
import org.ciyam.at.MachineState; import org.ciyam.at.MachineState;
import org.ciyam.at.OpCode; import org.ciyam.at.OpCode;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import common.TestAPI; import common.ExecutableTest;
import common.TestLogger; import common.TestUtils;
public class DisassemblyTests { public class DisassemblyTests extends ExecutableTest {
public TestLogger logger; private static final String message = "The quick, brown fox jumped over the lazy dog.";
public API api; private static final byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);
public MachineState state;
public ByteBuffer codeByteBuffer;
@BeforeClass
public static void beforeClass() {
Security.insertProviderAt(new BouncyCastleProvider(), 0);
}
@Before
public void beforeTest() {
logger = new TestLogger();
api = new TestAPI();
codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN);
}
@After
public void afterTest() {
codeByteBuffer = null;
api = null;
logger = null;
}
@Test @Test
public void testMD160disassembly() throws ExecutionException { public void testRMD160disassembly() throws ExecutionException {
// MD160 of ffffffffffffffffffffffffffffffffffffffffffffffff is 90e735014ea23aa89190121b229c06d58fc71e83 // Data addr 0 for setting values
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("ffffffffffffffff")); dataByteBuffer.putLong(0L);
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0); // Data addr 1 for results
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0); dataByteBuffer.putLong(0L);
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); // Data addr 2 has start of message bytes (address 4)
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("90e735014ea23aa8")); dataByteBuffer.putLong(4L);
// Data addr 3 has length of message bytes
dataByteBuffer.putLong(messageBytes.length);
// 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.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.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_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_RET_DAT_2.value).putShort(FunctionCode.CHECK_RMD160_WITH_B.value).putInt(1).putInt(2).putInt(3);
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.ECHO.value).putInt(1); codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.ECHO.value).putInt(1);
codeByteBuffer.put(OpCode.FIN_IMD.value); 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 = TestUtils.HEADER_BYTES;
byte[] headerBytes = hexToBytes("0200" + "0000" + "0002" + "2000" + "1000" + "1000" + "0000000000000000");
byte[] codeBytes = codeByteBuffer.array(); byte[] codeBytes = codeByteBuffer.array();
byte[] dataBytes = new byte[0]; byte[] dataBytes = dataByteBuffer.array();
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
System.out.println(state.disassemble());
}
@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[] codeBytes = codeByteBuffer.array();
byte[] dataBytes = new byte[0];
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes); state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);

View File

@ -20,42 +20,42 @@ public class FunctionCodeTests extends ExecutableTest {
@Test @Test
public void testMD5() throws ExecutionException { 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 @Test
public void testCHECK_MD5() throws ExecutionException { 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 @Test
public void testRMD160() throws ExecutionException { 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 @Test
public void testCHECK_RMD160() throws ExecutionException { 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 @Test
public void testSHA256() throws ExecutionException { 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 @Test
public void testCHECK_SHA256() throws ExecutionException { 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 @Test
public void testHASH160() throws ExecutionException { 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 @Test
public void testCHECK_HASH160() throws ExecutionException { 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 @Test
@ -107,49 +107,32 @@ public class FunctionCodeTests extends ExecutableTest {
assertTrue(state.getHadFatalError()); 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 // Data addr 0 for setting values
dataByteBuffer.putLong(0L); dataByteBuffer.putLong(0L);
// Data addr 1 for results // Data addr 1 for results
dataByteBuffer.putLong(0L); dataByteBuffer.putLong(0L);
// Data addr 2+ for message // 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); dataByteBuffer.put(messageBytes);
// MD5 data start // Actual hash function
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(2L); codeByteBuffer.put(OpCode.EXT_FUN_DAT_2.value).putShort(hashFunction.value).putInt(2).putInt(3);
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0);
// MD5 data length // Hash functions usually put result into B, but we need it in A
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(messageBytes.length); codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.SWAP_A_AND_B.value);
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 // Expected result goes into B
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.CLEAR_B.value); loadHashIntoB(expected);
// 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); // Check actual hash output (in A) with expected result (in B) and save equality output into address 1
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes(hexChars)); codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_A_EQUALS_B.value).putInt(1);
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); codeByteBuffer.put(OpCode.FIN_IMD.value);
@ -160,4 +143,56 @@ public class FunctionCodeTests extends ExecutableTest {
assertEquals(hashName + " hashes do not match", 1L, getData(1)); 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);
// 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);
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 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);
}
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,16 +1,19 @@
package common; package common;
import java.math.BigInteger; import java.math.BigInteger;
import java.nio.ByteBuffer;
import org.ciyam.at.MachineState; import org.ciyam.at.MachineState;
public class TestUtils { public class TestUtils {
// v3 constants replicated due to private scope in MachineState public static final short VERSION = 2;
public static final int CODE_PAGE_SIZE = 1; public static final short NUM_CODE_PAGES = 0x0200;
public static final int DATA_PAGE_SIZE = MachineState.VALUE_SIZE; public static final short NUM_DATA_PAGES = 0x0200;
public static final int CALL_STACK_PAGE_SIZE = MachineState.ADDRESS_SIZE; public static final short NUM_CALL_STACK_PAGES = 0x0010;
public static final int USER_STACK_PAGE_SIZE = MachineState.VALUE_SIZE; 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) { public static byte[] hexToBytes(String hex) {
byte[] output = new byte[hex.length() / 2]; byte[] output = new byte[hex.length() / 2];
@ -26,4 +29,31 @@ public class TestUtils {
return output; 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();
}
} }