diff --git a/build.gradle b/build.gradle index dd24ff0..8fa900e 100644 --- a/build.gradle +++ b/build.gradle @@ -44,6 +44,14 @@ test { useJUnitPlatform() } +sourceSets { + test { + resources { + srcDirs = ['src/test/resources'] + } + } +} + dependencies { implementation "com.google.guava:guava" implementation 'net.java.dev.jna:jna' diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/adapter/TrieKeyAdapter.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/adapter/TrieKeyAdapter.java index 38de471..a1d3af8 100644 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/adapter/TrieKeyAdapter.java +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/adapter/TrieKeyAdapter.java @@ -17,6 +17,9 @@ import org.hyperledger.besu.ethereum.trie.verkle.hasher.Hasher; +import java.util.ArrayList; +import java.util.List; + import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; @@ -38,14 +41,14 @@ public class TrieKeyAdapter { private final UInt256 VERKLE_NODE_WIDTH = UInt256.valueOf(256); private final UInt256 MAIN_STORAGE_OFFSET = UInt256.valueOf(256).pow(31); - private final Hasher hasher; + private final Hasher hasher; /** * Creates a TrieKeyAdapter with the provided hasher. * * @param hasher The hasher used for key generation. */ - public TrieKeyAdapter(Hasher hasher) { + public TrieKeyAdapter(Hasher hasher) { this.hasher = hasher; } @@ -56,51 +59,37 @@ public TrieKeyAdapter(Hasher hasher) { * @param subIndex The subIndex. * @return The modified key. */ - Bytes32 swapLastByte(Bytes32 base, UInt256 subIndex) { - Bytes32 key = - (Bytes32) Bytes.concatenate(base.slice(0, 31), Bytes.of(subIndex.toBytes().get(31))); - return key; + Bytes32 swapLastByte(Bytes32 base, Bytes subIndex) { + return (Bytes32) Bytes.concatenate(base.slice(0, 31), subIndex); } /** - * Generates the base key for a given address and treeIndex. + * Generates a storage key for a given address and storage key. * * @param address The address. - * @param treeIndex The tree index. - * @return The generated base key. + * @param storageKey The storage key. + * @return The generated storage key. */ - Bytes32 baseKey(Bytes32 address, UInt256 treeIndex) { - int type_encoding = 2; - UInt256 encoding = - UInt256.valueOf(type_encoding).add(VERKLE_NODE_WIDTH.multiply(UInt256.valueOf(16))); - - Bytes32[] input = - new Bytes32[] { - Bytes32.rightPad(encoding.toBytes().slice(16, 16).reverse()), - Bytes32.rightPad(address.slice(0, 16)), - Bytes32.rightPad(address.slice(16, 16)), - Bytes32.rightPad(treeIndex.toBytes().slice(16, 16).reverse()), - Bytes32.rightPad(treeIndex.toBytes().slice(0, 16).reverse()) - }; - Bytes32 key = hasher.commit(input); + public Bytes32 storageKey(Bytes address, Bytes32 storageKey) { + UInt256 index = UInt256.fromBytes(storageKey); + UInt256 headerOffset = CODE_OFFSET.subtract(HEADER_STORAGE_OFFSET); + UInt256 offset = + ((index.compareTo(headerOffset) < 0) ? HEADER_STORAGE_OFFSET : MAIN_STORAGE_OFFSET); + UInt256 pos = offset.add(index); + Bytes32 base = hasher.trieKeyHash(address, pos.divide(VERKLE_NODE_WIDTH)); + Bytes32 key = swapLastByte(base, pos.mod(VERKLE_NODE_WIDTH).toMinimalBytes()); return key; } /** - * Generates a storage key for a given address and storage key. + * Generates a code chunk key for a given address and chunkId. * * @param address The address. - * @param storageKey The storage key. - * @return The generated storage key. + * @param chunkId The chunk ID. + * @return The generated code chunk key. */ - public Bytes32 storageKey(Bytes32 address, UInt256 storageKey) { - UInt256 headerOffset = CODE_OFFSET.subtract(HEADER_STORAGE_OFFSET); - UInt256 offset = - ((storageKey.compareTo(headerOffset) < 0) ? HEADER_STORAGE_OFFSET : MAIN_STORAGE_OFFSET); - UInt256 pos = offset.add(storageKey); - Bytes32 base = baseKey(address, pos.divide(VERKLE_NODE_WIDTH)); - Bytes32 key = swapLastByte(base, pos.mod(VERKLE_NODE_WIDTH)); - return key; + public Bytes32 codeChunkKey(Bytes address, long chunkId) { + return codeChunkKey(address, UInt256.valueOf(chunkId)); } /** @@ -110,10 +99,10 @@ public Bytes32 storageKey(Bytes32 address, UInt256 storageKey) { * @param chunkId The chunk ID. * @return The generated code chunk key. */ - public Bytes32 codeChunkKey(Bytes32 address, UInt256 chunkId) { + public Bytes32 codeChunkKey(Bytes address, UInt256 chunkId) { UInt256 pos = CODE_OFFSET.add(chunkId); - Bytes32 base = baseKey(address, pos.divide(VERKLE_NODE_WIDTH)); - Bytes32 key = swapLastByte(base, pos.mod(VERKLE_NODE_WIDTH)); + Bytes32 base = hasher.trieKeyHash(address, pos.divide(VERKLE_NODE_WIDTH).toBytes()); + Bytes32 key = swapLastByte(base, pos.mod(VERKLE_NODE_WIDTH).toMinimalBytes()); return key; } @@ -124,9 +113,9 @@ public Bytes32 codeChunkKey(Bytes32 address, UInt256 chunkId) { * @param leafKey The leaf key. * @return The generated header key. */ - Bytes32 headerKey(Bytes32 address, UInt256 leafKey) { - Bytes32 base = baseKey(address, UInt256.valueOf(0)); - Bytes32 key = swapLastByte(base, leafKey); + Bytes32 headerKey(Bytes address, UInt256 leafKey) { + Bytes32 base = hasher.trieKeyHash(address, UInt256.valueOf(0).toBytes()); + Bytes32 key = swapLastByte(base, leafKey.toBytes().slice(31)); return key; } @@ -136,7 +125,7 @@ Bytes32 headerKey(Bytes32 address, UInt256 leafKey) { * @param address The address. * @return The generated version key. */ - public Bytes32 versionKey(Bytes32 address) { + public Bytes32 versionKey(Bytes address) { return headerKey(address, VERSION_LEAF_KEY); } @@ -146,7 +135,7 @@ public Bytes32 versionKey(Bytes32 address) { * @param address The address. * @return The generated balance key. */ - public Bytes32 balanceKey(Bytes32 address) { + public Bytes32 balanceKey(Bytes address) { return headerKey(address, BALANCE_LEAF_KEY); } @@ -156,7 +145,7 @@ public Bytes32 balanceKey(Bytes32 address) { * @param address The address. * @return The generated nonce key. */ - public Bytes32 nonceKey(Bytes32 address) { + public Bytes32 nonceKey(Bytes address) { return headerKey(address, NONCE_LEAF_KEY); } @@ -166,7 +155,7 @@ public Bytes32 nonceKey(Bytes32 address) { * @param address The address. * @return The generated code Keccak key. */ - public Bytes32 codeKeccakKey(Bytes32 address) { + public Bytes32 codeKeccakKey(Bytes address) { return headerKey(address, CODE_KECCAK_LEAF_KEY); } @@ -176,7 +165,55 @@ public Bytes32 codeKeccakKey(Bytes32 address) { * @param address The address. * @return The generated code size key. */ - public Bytes32 codeSizeKey(Bytes32 address) { - return headerKey(address, CODE_SIZE_LEAF_KEY); + public Bytes32 codeSizeKey(Bytes address) { + return (headerKey(address, CODE_SIZE_LEAF_KEY)); + } + + /** + * Chunk code's bytecode for insertion in the Trie. Each chunk code uses its position in the list + * as chunkId + * + * @param bytecode Code's bytecode + * @return List of 32-bytes code chunks + */ + public List chunkifyCode(Bytes bytecode) { + if (bytecode.isEmpty()) { + return new ArrayList(); + } + + // Chunking variables + int CHUNK_SIZE = 31; + int nChunks = 1 + ((bytecode.size() - 1) / CHUNK_SIZE); + int padSize = nChunks * CHUNK_SIZE - bytecode.size(); + Bytes code = Bytes.concatenate(bytecode, Bytes.repeat((byte) 0, padSize)); + List chunks = new ArrayList(nChunks); + + // OpCodes for PUSH's + int PUSH_OFFSET = 95; + int PUSH1 = PUSH_OFFSET + 1; + int PUSH32 = PUSH_OFFSET + 32; + + // Iterator data + int chunkPos = 0; // cursor position to start of current chunk + int posInChunk = 0; // cursor position relative to the current chunk + int nPushData = 0; // number of bytes in current push data + + // Create chunk iteratively + for (int chunkId = 0; chunkId < nChunks; ++chunkId) { + chunkPos = chunkId * CHUNK_SIZE; + posInChunk = nPushData; + while (posInChunk < CHUNK_SIZE) { + int opCode = Byte.toUnsignedInt(code.get(chunkPos + posInChunk)); + posInChunk += 1; + if (PUSH1 <= opCode && opCode <= PUSH32) { + posInChunk += opCode - PUSH_OFFSET; + } + } + chunks.add( + (Bytes32) Bytes.concatenate(Bytes.of(nPushData), code.slice(chunkPos, CHUNK_SIZE))); + nPushData = posInChunk - CHUNK_SIZE; + } + + return chunks; } } diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/hasher/Hasher.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/hasher/Hasher.java index 7f6ef1c..83f31cf 100644 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/hasher/Hasher.java +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/hasher/Hasher.java @@ -15,14 +15,11 @@ */ package org.hyperledger.besu.ethereum.trie.verkle.hasher; +import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; -/** - * Defines an interface for a Verkle Trie node hashing strategy. - * - * @param The type of values to be hashed. - */ -public interface Hasher { +/** Defines an interface for a Verkle Trie node hashing strategy. */ +public interface Hasher { /** * Calculates the commitment hash for an array of inputs. @@ -30,7 +27,14 @@ public interface Hasher { * @param inputs An array of values to be hashed. * @return The commitment hash calculated from the inputs. */ - public Bytes32 commit(V[] inputs); + public Bytes32 commit(Bytes32[] inputs); - // public Bytes32 commit_sparse(V[] input, int[] index) + /** + * Calculates the hash for an address and index. + * + * @param address Account address. + * @param index Index in storage. + * @return The trie-key hash + */ + public Bytes32 trieKeyHash(Bytes address, Bytes32 index); } diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/hasher/IPAHasher.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/hasher/PedersenHasher.java similarity index 78% rename from src/main/java/org/hyperledger/besu/ethereum/trie/verkle/hasher/IPAHasher.java rename to src/main/java/org/hyperledger/besu/ethereum/trie/verkle/hasher/PedersenHasher.java index daa7393..915b5aa 100644 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/hasher/IPAHasher.java +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/hasher/PedersenHasher.java @@ -27,7 +27,7 @@ *

This class implements the Hasher interface and provides a method to commit multiple Bytes32 * inputs using the IPA hashing algorithm. */ -public class IPAHasher implements Hasher { +public class PedersenHasher implements Hasher { /** * Commits an array of Bytes32 using the IPA hashing algorithm. * @@ -43,4 +43,18 @@ public Bytes32 commit(Bytes32[] inputs) { Bytes input_serialized = Bytes.concatenate(rev); return (Bytes32) Bytes32.wrap(LibIpaMultipoint.commit(input_serialized.toArray())).reverse(); } + + /** + * Calculates the hash for an address and index. + * + * @param address Account address. + * @param index Index in storage. + * @return The trie-key hash + */ + @Override + public Bytes32 trieKeyHash(Bytes address, Bytes32 index) { + Bytes32 addr = Bytes32.leftPad(address); + Bytes input = Bytes.concatenate(addr, index); + return Bytes32.wrap(LibIpaMultipoint.pedersenHash(input.toArray())); + } } diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/hasher/SHA256Hasher.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/hasher/SHA256Hasher.java index fd69728..0299c4b 100644 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/hasher/SHA256Hasher.java +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/hasher/SHA256Hasher.java @@ -29,7 +29,7 @@ * inputs using the SHA-256 hashing algorithm. It utilizes the Java built-in MessageDigest for * SHA-256. */ -public class SHA256Hasher implements Hasher { +public class SHA256Hasher implements Hasher { /** * Commits an array of Bytes32 using the SHA-256 hashing algorithm provided by the MessageDigest. * @@ -48,4 +48,16 @@ public Bytes32 commit(Bytes32[] inputs) { } return out; } + + /** + * Calculates the hash for an address and index. + * + * @param address Account address. + * @param index Index in storage. + * @return The trie-key hash + */ + @Override + public Bytes32 trieKeyHash(Bytes address, Bytes32 index) { + return commit(new Bytes32[] {Bytes32.leftPad(address), index}); + } } diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/HashVisitor.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/HashVisitor.java index 044979e..0959ee1 100644 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/HashVisitor.java +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/HashVisitor.java @@ -16,7 +16,7 @@ package org.hyperledger.besu.ethereum.trie.verkle.visitor; import org.hyperledger.besu.ethereum.trie.verkle.hasher.Hasher; -import org.hyperledger.besu.ethereum.trie.verkle.hasher.IPAHasher; +import org.hyperledger.besu.ethereum.trie.verkle.hasher.PedersenHasher; import org.hyperledger.besu.ethereum.trie.verkle.node.InternalNode; import org.hyperledger.besu.ethereum.trie.verkle.node.Node; import org.hyperledger.besu.ethereum.trie.verkle.node.StemNode; @@ -32,7 +32,7 @@ * @param The type of the node's value. */ public class HashVisitor implements PathNodeVisitor { - Hasher hasher = new IPAHasher(); + Hasher hasher = new PedersenHasher(); /** * Visits a internal node, computes its hash, and returns a new internal node with the updated diff --git a/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/TrieKeyAdapterTest.java b/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/TrieKeyAdapterTest.java index ab08e6a..052f54b 100644 --- a/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/TrieKeyAdapterTest.java +++ b/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/TrieKeyAdapterTest.java @@ -20,13 +20,22 @@ import org.hyperledger.besu.ethereum.trie.verkle.adapter.TrieKeyAdapter; import org.hyperledger.besu.ethereum.trie.verkle.hasher.SHA256Hasher; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class TrieKeyAdapterTest { - Bytes32 address = - Bytes32.fromHexString("0x000000000000000000000000112233445566778899aabbccddeeff00112233"); + Bytes address = Bytes.fromHexString("0x00112233445566778899aabbccddeeff00112233"); TrieKeyAdapter adapter = new TrieKeyAdapter(new SHA256Hasher()); @Test @@ -34,7 +43,7 @@ public void testStorageKey() { UInt256 storageKey = UInt256.valueOf(32); // Need to change this once commit is fixed Bytes32 expected = - Bytes32.fromHexString("0xc3552556138109254d3fb498d2364e85ed427986e389dff3fa5a514e2a3e5460"); + Bytes32.fromHexString("0x1719aec0fd8358bc50c95799bd3cd38da48f6519f78d64ccb2546f554d80a060"); assertThat(adapter.storageKey(address, storageKey)).isEqualTo(expected); } @@ -43,7 +52,7 @@ public void testCodeChunkKey() { UInt256 chunkId = UInt256.valueOf(24); // Need to change this once commit is fixed Bytes32 expected = - Bytes32.fromHexString("0xc3552556138109254d3fb498d2364e85ed427986e389dff3fa5a514e2a3e5498"); + Bytes32.fromHexString("0x1719aec0fd8358bc50c95799bd3cd38da48f6519f78d64ccb2546f554d80a098"); assertThat(adapter.codeChunkKey(address, chunkId)).isEqualTo(expected); } @@ -51,7 +60,7 @@ public void testCodeChunkKey() { public void testVersionKey() { // Need to change this once commit is fixed Bytes32 expected = - Bytes32.fromHexString("0xc3552556138109254d3fb498d2364e85ed427986e389dff3fa5a514e2a3e5400"); + Bytes32.fromHexString("0x1719aec0fd8358bc50c95799bd3cd38da48f6519f78d64ccb2546f554d80a000"); assertThat(adapter.versionKey(address)).isEqualTo(expected); } @@ -59,7 +68,7 @@ public void testVersionKey() { public void testBalanceKey() { // Need to change this once commit is fixed Bytes32 expected = - Bytes32.fromHexString("0xc3552556138109254d3fb498d2364e85ed427986e389dff3fa5a514e2a3e5401"); + Bytes32.fromHexString("0x1719aec0fd8358bc50c95799bd3cd38da48f6519f78d64ccb2546f554d80a001"); assertThat(adapter.balanceKey(address)).isEqualTo(expected); } @@ -67,7 +76,7 @@ public void testBalanceKey() { public void testNonceKey() { // Need to change this once commit is fixed Bytes32 expected = - Bytes32.fromHexString("0xc3552556138109254d3fb498d2364e85ed427986e389dff3fa5a514e2a3e5402"); + Bytes32.fromHexString("0x1719aec0fd8358bc50c95799bd3cd38da48f6519f78d64ccb2546f554d80a002"); assertThat(adapter.nonceKey(address)).isEqualTo(expected); } @@ -75,7 +84,7 @@ public void testNonceKey() { public void testCodeKeccakKey() { // Need to change this once commit is fixed Bytes32 expected = - Bytes32.fromHexString("0xc3552556138109254d3fb498d2364e85ed427986e389dff3fa5a514e2a3e5403"); + Bytes32.fromHexString("0x1719aec0fd8358bc50c95799bd3cd38da48f6519f78d64ccb2546f554d80a003"); assertThat(adapter.codeKeccakKey(address)).isEqualTo(expected); } @@ -83,7 +92,35 @@ public void testCodeKeccakKey() { public void testCodeSizeKey() { // Need to change this once commit is fixed Bytes32 expected = - Bytes32.fromHexString("0xc3552556138109254d3fb498d2364e85ed427986e389dff3fa5a514e2a3e5404"); + Bytes32.fromHexString("0x1719aec0fd8358bc50c95799bd3cd38da48f6519f78d64ccb2546f554d80a004"); assertThat(adapter.codeSizeKey(address)).isEqualTo(expected); } + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + public static List JsonData() throws IOException { + InputStream inputStream = TrieKeyAdapterTest.class.getResourceAsStream("/erc20CodeChunks.json"); + return objectMapper.readValue(inputStream, new TypeReference>() {}); + } + + static class TestData { + public String bytecode; + public ArrayList chunks; + } + + @ParameterizedTest + @MethodSource("JsonData") + public void TestChunkifyCode(TestData testData) { + Bytes bytecode = Bytes.fromHexString(testData.bytecode); + List result = adapter.chunkifyCode(bytecode); + assertThat(testData.chunks.size()).isEqualTo(result.size()); + Bytes32 value; + // @SuppressWarnings("ModifiedButNotUsed") + // List expected = new ArrayList(testData.chunks.size()); + for (int i = 0; i < testData.chunks.size(); ++i) { + value = Bytes32.fromHexString(testData.chunks.get(i)); + assertThat(value).isEqualByComparingTo(result.get(i)); + // expected.add(value); + } + } } diff --git a/src/test/resources/erc20CodeChunks.json b/src/test/resources/erc20CodeChunks.json new file mode 100644 index 0000000..25be671 --- /dev/null +++ b/src/test/resources/erc20CodeChunks.json @@ -0,0 +1,122 @@ +[ +{ + "bytecode": "6060604052341561000f57600080fd5b604051610dd1380380610dd18339810160405280805190602001909190805182019190602001805190602001909190805182019190505083600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508360008190555082600390805190602001906100a79291906100e3565b5081600460006101000a81548160ff021916908360ff16021790555080600590805190602001906100d99291906100e3565b5050505050610188565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061012457805160ff1916838001178555610152565b82800160010185558215610152579182015b82811115610151578251825591602001919060010190610136565b5b50905061015f9190610163565b5090565b61018591905b80821115610181576000816000905550600101610169565b5090565b90565b610c3a806101976000396000f3006060604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100b4578063095ea7b31461014257806318160ddd1461019c57806323b872dd146101c557806327e235e31461023e578063313ce5671461028b5780635c658165146102ba57806370a082311461032657806395d89b4114610373578063a9059cbb14610401578063dd62ed3e1461045b575b600080fd5b34156100bf57600080fd5b6100c76104c7565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101075780820151818401526020810190506100ec565b50505050905090810190601f1680156101345780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561014d57600080fd5b610182600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610565565b604051808215151515815260200191505060405180910390f35b34156101a757600080fd5b6101af610657565b6040518082815260200191505060405180910390f35b34156101d057600080fd5b610224600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061065d565b604051808215151515815260200191505060405180910390f35b341561024957600080fd5b610275600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506108f7565b6040518082815260200191505060405180910390f35b341561029657600080fd5b61029e61090f565b604051808260ff1660ff16815260200191505060405180910390f35b34156102c557600080fd5b610310600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610922565b6040518082815260200191505060405180910390f35b341561033157600080fd5b61035d600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610947565b6040518082815260200191505060405180910390f35b341561037e57600080fd5b610386610990565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103c65780820151818401526020810190506103ab565b50505050905090810190601f1680156103f35780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561040c57600080fd5b610441600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610a2e565b604051808215151515815260200191505060405180910390f35b341561046657600080fd5b6104b1600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610b87565b6040518082815260200191505060405180910390f35b60038054600181600116156101000203166002900480601f01602080910402602001604051908101604052809291908181526020018280546001816001161561010002031660029004801561055d5780601f106105325761010080835404028352916020019161055d565b820191906000526020600020905b81548152906001019060200180831161054057829003601f168201915b505050505081565b600081600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b60005481565b600080600260008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015801561072e5750828110155b151561073957600080fd5b82600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254019250508190555082600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8110156108865782600260008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055505b8373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef856040518082815260200191505060405180910390a360019150509392505050565b60016020528060005260406000206000915090505481565b600460009054906101000a900460ff1681565b6002602052816000526040600020602052806000526040600020600091509150505481565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60058054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610a265780601f106109fb57610100808354040283529160200191610a26565b820191906000526020600020905b815481529060010190602001808311610a0957829003601f168201915b505050505081565b600081600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151515610a7e57600080fd5b81600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a36001905092915050565b6000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050929150505600a165627a7a72305820df254047bc8f2904ad3e966b6db116d703bebd40efadadb5e738c836ffc8f58a0029", + "chunks": [ + "006060604052341561000f57600080fd5b604051610dd1380380610dd1833981", + "0001604052808051906020019091908051820191906020018051906020019091", + "0090805182019190505083600160003373ffffffffffffffffffffffffffffff", + "05ffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260", + "0120019081526020016000208190555083600081905550826003908051906020", + "0001906100a79291906100e3565b5081600460006101000a81548160ff021916", + "00908360ff16021790555080600590805190602001906100d99291906100e356", + "005b5050505050610188565b8280546001816001161561010002031660029004", + "0090600052602060002090601f016020900481019282601f1061012457805160", + "01ff1916838001178555610152565b8280016001018555821561015257918201", + "005b82811115610151578251825591602001919060010190610136565b5b5090", + "005061015f9190610163565b5090565b61018591905b80821115610181576000", + "00816000905550600101610169565b5090565b90565b610c3a80610197600039", + "006000f3006060604052600436106100af576000357c01000000000000000000", + "1300000000000000000000000000000000000000900463ffffffff16806306fd", + "02de03146100b4578063095ea7b31461014257806318160ddd1461019c578063", + "0423b872dd146101c557806327e235e31461023e578063313ce5671461028b57", + "0080635c658165146102ba57806370a082311461032657806395d89b41146103", + "0173578063a9059cbb14610401578063dd62ed3e1461045b575b600080fd5b34", + "00156100bf57600080fd5b6100c76104c7565b60405180806020018281038252", + "0083818151815260200191508051906020019080838360005b83811015610107", + "005780820151818401526020810190506100ec565b5050505090509081019060", + "011f1680156101345780820380516001836020036101000a0319168152602001", + "0091505b509250505060405180910390f35b341561014d57600080fd5b610182", + "00600480803573ffffffffffffffffffffffffffffffffffffffff1690602001", + "009091908035906020019091905050610565565b604051808215151515815260", + "01200191505060405180910390f35b34156101a757600080fd5b6101af610657", + "00565b6040518082815260200191505060405180910390f35b34156101d05760", + "010080fd5b610224600480803573ffffffffffffffffffffffffffffffffffff", + "02ffff1690602001909190803573ffffffffffffffffffffffffffffffffffff", + "02ffff1690602001909190803590602001909190505061065d565b6040518082", + "0015151515815260200191505060405180910390f35b341561024957600080fd", + "005b610275600480803573ffffffffffffffffffffffffffffffffffffffff16", + "009060200190919050506108f7565b6040518082815260200191505060405180", + "00910390f35b341561029657600080fd5b61029e61090f565b604051808260ff", + "001660ff16815260200191505060405180910390f35b34156102c557600080fd", + "005b610310600480803573ffffffffffffffffffffffffffffffffffffffff16", + "0090602001909190803573ffffffffffffffffffffffffffffffffffffffff16", + "00906020019091905050610922565b6040518082815260200191505060405180", + "00910390f35b341561033157600080fd5b61035d600480803573ffffffffffff", + "0effffffffffffffffffffffffffff16906020019091905050610947565b6040", + "00518082815260200191505060405180910390f35b341561037e57600080fd5b", + "00610386610990565b6040518080602001828103825283818151815260200191", + "00508051906020019080838360005b838110156103c657808201518184015260", + "0120810190506103ab565b50505050905090810190601f1680156103f3578082", + "000380516001836020036101000a031916815260200191505b50925050506040", + "005180910390f35b341561040c57600080fd5b610441600480803573ffffffff", + "10ffffffffffffffffffffffffffffffff169060200190919080359060200190", + "0091905050610a2e565b60405180821515151581526020019150506040518091", + "000390f35b341561046657600080fd5b6104b1600480803573ffffffffffffff", + "0dffffffffffffffffffffffffff1690602001909190803573ffffffffffffff", + "0dffffffffffffffffffffffffff16906020019091905050610b87565b604051", + "008082815260200191505060405180910390f35b600380546001816001161561", + "0201000203166002900480601f01602080910402602001604051908101604052", + "0080929190818152602001828054600181600116156101000203166002900480", + "001561055d5780601f106105325761010080835404028352916020019161055d", + "00565b820191906000526020600020905b815481529060010190602001808311", + "0061054057829003601f168201915b505050505081565b600081600260003373", + "14ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffff", + "0bffffffffffffffffffffff16815260200190815260200160002060008573ff", + "13ffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffff", + "0affffffffffffffffffff168152602001908152602001600020819055508273", + "14ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffff", + "0cffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd", + "0f0314c0f7b2291e5b200ac8c7c3b92584604051808281526020019150506040", + "005180910390a36001905092915050565b60005481565b600080600260008673", + "14ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffff", + "0bffffffffffffffffffffff16815260200190815260200160002060003373ff", + "13ffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffff", + "0affffffffffffffffffff168152602001908152602001600020549050826001", + "0060008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffff", + "0fffffffffffffffffffffffffffffff16815260200190815260200160002054", + "001015801561072e5750828110155b151561073957600080fd5b826001600086", + "0073ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffff", + "0cffffffffffffffffffffffff16815260200190815260200160002060008282", + "0054019250508190555082600160008773ffffffffffffffffffffffffffffff", + "05ffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260", + "012001908152602001600020600082825403925050819055507fffffffffffff", + "1affffffffffffffffffffffffffffffffffffffffffffffffffff8110156108", + "01865782600260008773ffffffffffffffffffffffffffffffffffffffff1673", + "14ffffffffffffffffffffffffffffffffffffffff1681526020019081526020", + "000160002060003373ffffffffffffffffffffffffffffffffffffffff1673ff", + "13ffffffffffffffffffffffffffffffffffffff168152602001908152602001", + "00600020600082825403925050819055505b8373ffffffffffffffffffffffff", + "08ffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff", + "00167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df5", + "0323b3ef856040518082815260200191505060405180910390a3600191505093", + "0092505050565b60016020528060005260406000206000915090505481565b60", + "010460009054906101000a900460ff1681565b60026020528160005260406000", + "0020602052806000526040600020600091509150505481565b60006001600083", + "0073ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffff", + "0cffffffffffffffffffffffff16815260200190815260200160002054905091", + "009050565b60058054600181600116156101000203166002900480601f016020", + "0080910402602001604051908101604052809291908181526020018280546001", + "008160011615610100020316600290048015610a265780601f106109fb576101", + "0100808354040283529160200191610a26565b82019190600052602060002090", + "005b815481529060010190602001808311610a0957829003601f168201915b50", + "005050505081565b600081600160003373ffffffffffffffffffffffffffffff", + "05ffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260", + "0120019081526020016000205410151515610a7e57600080fd5b816001600033", + "0073ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffff", + "0cffffffffffffffffffffffff16815260200190815260200160002060008282", + "0054039250508190555081600160008573ffffffffffffffffffffffffffffff", + "05ffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260", + "012001908152602001600020600082825401925050819055508273ffffffffff", + "0fffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffff", + "07ffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4", + "0aa11628f55a4df523b3ef846040518082815260200191505060405180910390", + "00a36001905092915050565b6000600260008473ffffffffffffffffffffffff", + "08ffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16", + "00815260200190815260200160002060008373ffffffffffffffffffffffffff", + "07ffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681", + "0052602001908152602001600020549050929150505600a165627a7a72305820", + "00df254047bc8f2904ad3e966b6db116d703bebd40efadadb5e738c836ffc8f5", + "008a002900000000000000000000000000000000000000000000000000000000" + ] +} +] \ No newline at end of file