Implement pre-compiled contract
In order to improve performance of certain VM functionality Ethereum introduce plug-in mechanism called pre-compiled contracts. Supported functionality: ecRecover - (addr: 01) - recover address out of hash, v, r, s - parameters. sha256 - (addr: 02) - calculate hash value with sha256 algorithm ripempd160 - (addr:03) - calculate hash value with repimpd algorithm
This commit is contained in:
parent
620f365205
commit
b017df080b
|
@ -133,7 +133,10 @@ public class TestRunner {
|
|||
int postRepoSize = testCase.getPost().size();
|
||||
|
||||
if (postRepoSize > repoSize) {
|
||||
results.add("ERROR: Post repository contains more accounts than executed repository ");
|
||||
results.add("ERROR: Expected 'Post' repository contains more accounts than executed repository ");
|
||||
|
||||
logger.info("Full address set: " + fullAddressSet);
|
||||
|
||||
}
|
||||
|
||||
return results;
|
||||
|
|
|
@ -45,6 +45,10 @@ public class DataWord implements Comparable<DataWord> {
|
|||
this.data = data.array();
|
||||
}
|
||||
|
||||
public DataWord(String data){
|
||||
this(Hex.decode(data));
|
||||
}
|
||||
|
||||
public DataWord(byte[] data) {
|
||||
if (data == null)
|
||||
this.data = ByteUtil.EMPTY_BYTE_ARRAY;
|
||||
|
@ -312,4 +316,8 @@ public class DataWord implements Comparable<DataWord> {
|
|||
if (firstNonZero == -1) return 0;
|
||||
return 31 - firstNonZero + 1;
|
||||
}
|
||||
|
||||
public boolean isHex(String hex){
|
||||
return Hex.toHexString(data).equals(hex);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
package org.ethereum.vm;
|
||||
|
||||
import org.ethereum.crypto.ECKey;
|
||||
import org.ethereum.crypto.HashUtil;
|
||||
import org.ethereum.util.ByteUtil;
|
||||
|
||||
/**
|
||||
* @author Roman Mandeleil
|
||||
* Created on: 09/01/2015 08:05
|
||||
*/
|
||||
|
||||
public class PrecompiledContracts {
|
||||
|
||||
private static ECRecover ecRecover = new ECRecover();
|
||||
private static Sha256 sha256 = new Sha256();
|
||||
private static Ripempd160 ripempd160 = new Ripempd160();
|
||||
private static Identity identity = new Identity();
|
||||
|
||||
|
||||
public static PrecompiledContract getContractForAddress(DataWord address){
|
||||
|
||||
if (address == null) return identity;
|
||||
if (address.isHex("0000000000000000000000000000000000000000000000000000000000000001")) return ecRecover;
|
||||
if (address.isHex("0000000000000000000000000000000000000000000000000000000000000002")) return sha256;
|
||||
if (address.isHex("0000000000000000000000000000000000000000000000000000000000000003")) return ripempd160;
|
||||
if (address.isHex("0000000000000000000000000000000000000000000000000000000000000004")) return identity;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static abstract class PrecompiledContract{
|
||||
public abstract long getGasForData(byte[] data);
|
||||
public abstract byte[] execute(byte[] data);
|
||||
}
|
||||
|
||||
public static class Identity extends PrecompiledContract{
|
||||
|
||||
public Identity() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getGasForData(byte[] data) {
|
||||
if (data == null) return 1;
|
||||
return 1 + (data.length + 31) / 32 * 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] execute(byte[] data) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Sha256 extends PrecompiledContract{
|
||||
|
||||
|
||||
@Override
|
||||
public long getGasForData(byte[] data) {
|
||||
if (data == null) return 50;
|
||||
return 50 + (data.length + 31) / 32 * 50;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] execute(byte[] data) {
|
||||
|
||||
if (data == null) return HashUtil.sha256(ByteUtil.EMPTY_BYTE_ARRAY);
|
||||
return HashUtil.sha256(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class Ripempd160 extends PrecompiledContract{
|
||||
|
||||
|
||||
@Override
|
||||
public long getGasForData(byte[] data) {
|
||||
if (data == null) return 50;
|
||||
return 50 + (data.length + 31) / 32 * 50;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] execute(byte[] data) {
|
||||
|
||||
byte[] result = null;
|
||||
if (data == null) result = HashUtil.ripemd160(ByteUtil.EMPTY_BYTE_ARRAY);
|
||||
else result = HashUtil.ripemd160(data);
|
||||
|
||||
return new DataWord(result).getData();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class ECRecover extends PrecompiledContract{
|
||||
|
||||
@Override
|
||||
public long getGasForData(byte[] data) {
|
||||
return 500;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] execute(byte[] data) {
|
||||
|
||||
byte[] h = new byte[32];
|
||||
byte[] v = new byte[32];
|
||||
byte[] r = new byte[32];
|
||||
byte[] s = new byte[32];
|
||||
|
||||
DataWord out = null;
|
||||
|
||||
try{
|
||||
System.arraycopy(data, 0, h, 0, 32);
|
||||
System.arraycopy(data, 32, v, 0, 32);
|
||||
System.arraycopy(data, 64, r, 0, 32);
|
||||
System.arraycopy(data, 96, s, 0, 32);
|
||||
|
||||
|
||||
ECKey.ECDSASignature signature = ECKey.ECDSASignature.fromComponents(r, s, v[31]);
|
||||
|
||||
ECKey key = ECKey.signatureToKey(h, signature.toBase64());
|
||||
out = new DataWord(key.getAddress());
|
||||
} catch (Throwable any){}
|
||||
|
||||
if (out == null) out = new DataWord(0);
|
||||
|
||||
return out.getData();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -6,6 +6,7 @@ import org.ethereum.db.ContractDetails;
|
|||
import org.ethereum.facade.Repository;
|
||||
import org.ethereum.util.ByteUtil;
|
||||
import org.ethereum.vm.MessageCall.MsgType;
|
||||
import org.ethereum.vm.PrecompiledContracts.PrecompiledContract;
|
||||
import org.ethereum.vmtrace.Op;
|
||||
import org.ethereum.vmtrace.ProgramTrace;
|
||||
|
||||
|
@ -214,6 +215,14 @@ public class Program {
|
|||
public void memorySave(int addr, byte[] value) {
|
||||
memorySave(addr, value.length, value);
|
||||
}
|
||||
|
||||
public void memoryExpand(DataWord outDataOffs, DataWord outDataSize){
|
||||
|
||||
int maxAddress = outDataOffs.intValue() + outDataSize.intValue();
|
||||
if (getMemSize() < maxAddress){
|
||||
memorySave(maxAddress, new byte[]{0});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates a piece of memory and stores value at given offset address
|
||||
|
@ -420,7 +429,7 @@ public class Program {
|
|||
stackPushZero();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
byte[] data = memoryChunk(msg.getInDataOffs(), msg.getInDataSize()).array();
|
||||
|
||||
// FETCH THE SAVED STORAGE
|
||||
|
@ -900,6 +909,28 @@ public class Program {
|
|||
if (!jumpdest.contains(nextPC)) throw new BadJumpDestinationException();
|
||||
}
|
||||
|
||||
public void callToPrecompiledAddress(MessageCall msg, PrecompiledContract contract) {
|
||||
|
||||
byte[] data = this.memoryChunk( msg.getInDataOffs(), msg.getInDataSize()).array();
|
||||
|
||||
this.result.getRepository().addBalance(this.getOwnerAddress().getLast20Bytes(), msg.getEndowment().value().negate());
|
||||
this.result.getRepository().addBalance(msg.getCodeAddress().getLast20Bytes(), msg.getEndowment().value());
|
||||
|
||||
long requiredGas = contract.getGasForData(data);
|
||||
if (requiredGas > msg.getGas().longValue()){
|
||||
|
||||
this.spendGas(msg.getGas().longValue(), "call pre-compiled");
|
||||
this.stackPushZero();
|
||||
} else {
|
||||
|
||||
this.spendGas(requiredGas, "call pre-compiled");
|
||||
byte[] out = contract.execute(data);
|
||||
|
||||
this.memorySave( msg.getOutDataOffs().intValue(), out);
|
||||
this.stackPushOne();
|
||||
}
|
||||
}
|
||||
|
||||
public interface ProgramListener {
|
||||
public void output(String out);
|
||||
}
|
||||
|
|
|
@ -1042,14 +1042,25 @@ public class VM {
|
|||
program.getGas().value(),
|
||||
program.invokeData.getCallDeep(), hint);
|
||||
}
|
||||
|
||||
program.memoryExpand(outDataOffs, outDataSize);
|
||||
|
||||
MessageCall msg = new MessageCall(
|
||||
op.equals(CALL) ? MsgType.CALL : MsgType.STATELESS,
|
||||
gas, codeAddress, value, inDataOffs, inDataSize,
|
||||
outDataOffs, outDataSize);
|
||||
program.callToAddress(msg);
|
||||
|
||||
PrecompiledContracts.PrecompiledContract contract =
|
||||
PrecompiledContracts.getContractForAddress(codeAddress);
|
||||
|
||||
if (contract != null)
|
||||
program.callToPrecompiledAddress(msg, contract);
|
||||
else
|
||||
program.callToAddress(msg);
|
||||
|
||||
|
||||
program.step();
|
||||
|
||||
}
|
||||
break;
|
||||
case RETURN: {
|
||||
|
|
|
@ -19,7 +19,7 @@ public class GitHubStateTest {
|
|||
@Test
|
||||
public void stSingleTest() throws ParseException {
|
||||
String json = JSONReader.loadJSON("StateTests/stSystemOperationsTest.json");
|
||||
GitHubJSONTestSuite.runGitHubJsonStateTest(json, "CallToReturn1ForDynamicJump1");
|
||||
GitHubJSONTestSuite.runGitHubJsonStateTest(json, "CallRecursiveBombLog2");
|
||||
}
|
||||
|
||||
@Ignore
|
||||
|
@ -27,9 +27,10 @@ public class GitHubStateTest {
|
|||
public void runWithExcludedTest() throws ParseException {
|
||||
|
||||
Set<String> excluded = new HashSet<>();
|
||||
excluded.add("CallToReturn1ForDynamicJump1");
|
||||
excluded.add("CallSha256_5");
|
||||
|
||||
String json = JSONReader.loadJSON("StateTests/stSystemOperationsTest.json");
|
||||
|
||||
String json = JSONReader.loadJSON("StateTests/stPreCompiledContracts.json");
|
||||
GitHubJSONTestSuite.runGitHubJsonStateTest(json, excluded);
|
||||
}
|
||||
|
||||
|
@ -41,12 +42,17 @@ public class GitHubStateTest {
|
|||
GitHubJSONTestSuite.runGitHubJsonStateTest(json);
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
@Test // todo: fix: excluded test
|
||||
public void stInitCodeTest() throws ParseException { // [V]
|
||||
|
||||
Set<String> excluded = new HashSet<>();
|
||||
excluded.add("NotEnoughCashContractCreation");
|
||||
excluded.add("CallContractToCreateContractOOG");
|
||||
excluded.add("CallContractToCreateContractNoCash");
|
||||
excluded.add("CallContractToCreateContractWhichWouldCreateContractInInitCode");
|
||||
|
||||
String json = JSONReader.loadJSON("StateTests/stInitCodeTest.json");
|
||||
GitHubJSONTestSuite.runGitHubJsonStateTest(json);
|
||||
GitHubJSONTestSuite.runGitHubJsonStateTest(json, excluded);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -56,7 +62,6 @@ public class GitHubStateTest {
|
|||
GitHubJSONTestSuite.runGitHubJsonStateTest(json);
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void stPreCompiledContracts() throws ParseException {
|
||||
|
||||
|
@ -103,12 +108,16 @@ public class GitHubStateTest {
|
|||
}
|
||||
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
@Test // todo: fix: excluded test
|
||||
public void stTransactionTest() throws ParseException {
|
||||
|
||||
Set<String> excluded = new HashSet<>();
|
||||
excluded.add("EmptyTransaction");
|
||||
//todo: it goes OOG, because no gasLimit is given. So it does not change the state.
|
||||
|
||||
|
||||
String json = JSONReader.loadJSON("StateTests/stTransactionTest.json");
|
||||
GitHubJSONTestSuite.runGitHubJsonStateTest(json);
|
||||
GitHubJSONTestSuite.runGitHubJsonStateTest(json, excluded);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
package test.ethereum.vm;
|
||||
|
||||
import org.ethereum.util.ByteUtil;
|
||||
import org.ethereum.vm.DataWord;
|
||||
import org.ethereum.vm.PrecompiledContracts;
|
||||
import org.ethereum.vm.PrecompiledContracts.PrecompiledContract;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author: Roman Mandeleil
|
||||
* Created on: 09/01/2015 08:28
|
||||
*/
|
||||
|
||||
public class PrecompiledContractTest {
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void identityTest1(){
|
||||
|
||||
DataWord addr = new DataWord("0000000000000000000000000000000000000000000000000000000000000004");
|
||||
PrecompiledContract contract = PrecompiledContracts.getContractForAddress(addr);
|
||||
byte[] data = Hex.decode("112233445566");
|
||||
byte[] expected = Hex.decode("112233445566");
|
||||
|
||||
byte[] result = contract.execute(data);
|
||||
|
||||
assertArrayEquals(expected, result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void sha256Test1(){
|
||||
|
||||
DataWord addr = new DataWord("0000000000000000000000000000000000000000000000000000000000000002");
|
||||
PrecompiledContract contract = PrecompiledContracts.getContractForAddress(addr);
|
||||
byte[] data = null;
|
||||
String expected = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
||||
|
||||
byte[] result = contract.execute(data);
|
||||
|
||||
assertEquals(expected, Hex.toHexString(result));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sha256Test2(){
|
||||
|
||||
DataWord addr = new DataWord("0000000000000000000000000000000000000000000000000000000000000002");
|
||||
PrecompiledContract contract = PrecompiledContracts.getContractForAddress(addr);
|
||||
byte[] data = ByteUtil.EMPTY_BYTE_ARRAY;
|
||||
String expected = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
||||
|
||||
byte[] result = contract.execute(data);
|
||||
|
||||
assertEquals(expected, Hex.toHexString(result));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sha256Test3(){
|
||||
|
||||
DataWord addr = new DataWord("0000000000000000000000000000000000000000000000000000000000000002");
|
||||
PrecompiledContract contract = PrecompiledContracts.getContractForAddress(addr);
|
||||
byte[] data = Hex.decode("112233");
|
||||
String expected = "49ee2bf93aac3b1fb4117e59095e07abe555c3383b38d608da37680a406096e8";
|
||||
|
||||
byte[] result = contract.execute(data);
|
||||
|
||||
assertEquals(expected, Hex.toHexString(result));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void Ripempd160Test1(){
|
||||
|
||||
DataWord addr = new DataWord("0000000000000000000000000000000000000000000000000000000000000003");
|
||||
PrecompiledContract contract = PrecompiledContracts.getContractForAddress(addr);
|
||||
byte[] data = Hex.decode("0000000000000000000000000000000000000000000000000000000000000001");
|
||||
String expected = "000000000000000000000000ae387fcfeb723c3f5964509af111cf5a67f30661";
|
||||
|
||||
byte[] result = contract.execute(data);
|
||||
|
||||
assertEquals(expected, Hex.toHexString(result));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ecRecoverTest1(){
|
||||
|
||||
byte[] data = Hex.decode("18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000000000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549");
|
||||
DataWord addr = new DataWord("0000000000000000000000000000000000000000000000000000000000000001");
|
||||
PrecompiledContract contract = PrecompiledContracts.getContractForAddress(addr);
|
||||
String expected = "000000000000000000000000ae387fcfeb723c3f5964509af111cf5a67f30661";
|
||||
|
||||
byte[] result = contract.execute(data);
|
||||
|
||||
System.out.println(Hex.toHexString(result));
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue