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:
parent
36029c132f
commit
92281a1d04
@ -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();
|
||||||
|
@ -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];
|
||||||
|
|
||||||
|
@ -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) };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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));
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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];
|
||||||
|
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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));
|
||||||
|
@ -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() {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user