From f56c008f91b43d95fe408a699c01862232967dcc Mon Sep 17 00:00:00 2001 From: Thomas Zamojski Date: Thu, 20 Feb 2025 16:48:32 +0100 Subject: [PATCH] ADD: PartialTrieView reconstruction from ExecutionWitness Signed-off-by: Thomas Zamojski --- .../besu/ethereum/trie/verkle/node/Node.java | 24 ++- .../ethereum/trie/verkle/node/StemNode.java | 4 +- .../trie/verkle/proof/ExecutionWitness.java | 63 +++++++ .../trie/verkle/proof/PartialTrieView.java | 158 ++++++++++++++++++ .../trie/verkle/proof/ProofVerifier.java | 70 -------- .../verkle/proof/PartialTrieViewTest.java | 62 +++++++ 6 files changed, 305 insertions(+), 76 deletions(-) create mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/proof/ExecutionWitness.java create mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/proof/PartialTrieView.java delete mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/proof/ProofVerifier.java create mode 100644 src/test/java/org/hyperledger/besu/ethereum/trie/verkle/proof/PartialTrieViewTest.java diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/Node.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/Node.java index 61099657..e399d42b 100644 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/Node.java +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/Node.java @@ -41,6 +41,9 @@ public abstract class Node { "0x0000000000000000000000000000000000000000000000000000000000000000" + "0100000000000000000000000000000000000000000000000000000000000000"); + /** A constant representing a compressed commitment to NullNodes */ + public static Bytes32 EMPTY_COMMITMENT_COMPRESSED = Bytes32.ZERO; + Optional previous; boolean dirty; boolean persisted; @@ -237,12 +240,14 @@ public String toDot() { * @return The low value. */ public static Bytes32 getLowValue(Optional value) { - // Low values have a flag at bit 128. return value .map( - (v) -> - Bytes32.rightPad( - Bytes.concatenate(Bytes32.rightPad((Bytes) v).slice(0, 16), Bytes.of(1)))) + (v) -> { + byte[] array = new byte[32]; + array[16] = 1; // Low values have a isNotNull flag at bit 128 + System.arraycopy(((Bytes32) v).toArrayUnsafe(), 0, array, 0, 16); + return Bytes32.wrap(array); + }) .orElse(Bytes32.ZERO); } @@ -254,7 +259,16 @@ public static Bytes32 getLowValue(Optional value) { */ public static Bytes32 getHighValue(Optional value) { return value - .map((v) -> Bytes32.rightPad(Bytes32.rightPad((Bytes) v).slice(16, 16))) + .map(Bytes.class::cast) + .map( + (v) -> { + final byte[] array = new byte[32]; + final int size = v.size(); + if (size > 16) { + System.arraycopy(((Bytes32) v).toArrayUnsafe(), 16, array, 0, size - 16); + } + return Bytes32.wrap(array); + }) .orElse(Bytes32.ZERO); } diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/StemNode.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/StemNode.java index 946d1002..837c908f 100644 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/StemNode.java +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/StemNode.java @@ -35,6 +35,9 @@ */ public class StemNode extends BranchNode { + /** A constant representing a StemNode's extension Marker */ + public static Bytes32 MARKER = Bytes32.fromHexString("0x01"); + private final Bytes stem; private Optional leftHash; private Optional leftCommitment; @@ -212,7 +215,6 @@ public Optional getRightCommitment() { * @return The updated Node */ @Override - @SuppressWarnings("unchecked") public StemNode replaceLocation(Bytes newLocation) { List> newChildren = new ArrayList<>(maxChild()); for (int i = 0; i < maxChild(); i++) { diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/proof/ExecutionWitness.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/proof/ExecutionWitness.java new file mode 100644 index 00000000..766e3ff2 --- /dev/null +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/proof/ExecutionWitness.java @@ -0,0 +1,63 @@ +/* + * Copyright Hyperledger Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ +package org.hyperledger.besu.ethereum.trie.verkle.proof; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +public record ExecutionWitness( + Bytes32 previousStateRoot, + List stemInfos, + VerificationHint verificationHint, + IPAMultiProof proof) { + + public record SuffixDiff(int suffix, Bytes value) {} + ; + + public record StemInfo(Bytes stem, int depth, int extensionType, List suffixDiffs) {} + ; + + public record VerificationHint(List commitments, List otherStems) {} + ; + + public record IPAMultiProof( + Bytes32 multiCommitment, + List leftCommitments, + List rightCommitments, + Bytes32 finalEvaluation) { + + public int getIPADepth() { + return leftCommitments.size(); + } + + public Bytes toBytes() { + List allBytes = new ArrayList(getIPADepth() * 2 + 1); + allBytes.addAll(leftCommitments); + allBytes.addAll(rightCommitments); + allBytes.add(finalEvaluation); + return Bytes.concatenate(allBytes); + } + } + ; + + // public static ExecutionWitness fromParameters(ExecutionWitnessParameters + // params) { + // Logic to validate an executionWitness from json parameters. + // } +} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/proof/PartialTrieView.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/proof/PartialTrieView.java new file mode 100644 index 00000000..d688f5f3 --- /dev/null +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/proof/PartialTrieView.java @@ -0,0 +1,158 @@ +/* + * Copyright Hyperledger Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ +package org.hyperledger.besu.ethereum.trie.verkle.proof; + +import org.hyperledger.besu.ethereum.trie.verkle.node.Node; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +public class PartialTrieView { + private final Map nodeViews; + private final Map stemViews; + + public PartialTrieView(Map nodeViews, Map stemViews) { + this.nodeViews = nodeViews; + this.stemViews = stemViews; + } + + public Map getNodeViews() { + return nodeViews; + } + + public Map getStemViews() { + return stemViews; + } + + public static PartialTrieView fromExecutionWitness(ExecutionWitness executionWitness) + throws IllegalArgumentException { + Map nodeViews = new HashMap<>(); + Map stemViews = new HashMap<>(); + Iterator iterCommitments = + executionWitness.verificationHint().commitments().iterator(); + Iterator iterOtherStems = executionWitness.verificationHint().otherStems().iterator(); + + // Process root Node + Bytes stem; + Bytes path = Bytes.EMPTY; + Bytes32 commitment = executionWitness.previousStateRoot(); + nodeViews.put(path, NodeView.fromCommitment(commitment)); + + // Process Stems + for (ExecutionWitness.StemInfo stemInfo : executionWitness.stemInfos()) { + // Process Internal Nodes + for (int i = 1; i < stemInfo.depth(); i++) { + path = stemInfo.stem().slice(0, i); + NodeView current = nodeViews.get(path); + if (current == null) { + if (!iterCommitments.hasNext()) { + throw new IllegalArgumentException("Not enough Commitments"); + } + commitment = iterCommitments.next(); + nodeViews.put(path, NodeView.fromCommitment(commitment)); + } + } + + // Process Last Node + // 0: Internal Node with Emtpy value + // 1: Stem Node with different stem + // 2. Stem Node with value + path = stemInfo.stem().slice(0, stemInfo.depth()); + switch (stemInfo.extensionType()) { + case 0: // Empty Internal Node + nodeViews.put(path, NodeView.fromCommitment(Node.EMPTY_COMMITMENT_COMPRESSED)); + break; + case 1: // Other Stem + if (!iterCommitments.hasNext()) { + throw new IllegalArgumentException("Not enough Commitments"); + } + commitment = iterCommitments.next(); + if (!iterOtherStems.hasNext()) { + throw new IllegalArgumentException("Not enough OtherStems"); + } + stem = iterOtherStems.next(); + if (stem.commonPrefix(path) != path) { + throw new IllegalArgumentException("OtherStem does not match path prefix"); + } + StemView stemView = stemViews.get(stem); + if (stemView == null) { + stemViews.put(stem, StemView.fromEmpty()); + } + break; + case 2: // Stem is Present + stem = stemInfo.stem(); + if (!iterCommitments.hasNext()) { + throw new IllegalArgumentException("Not enough Commitments"); + } + commitment = iterCommitments.next(); + nodeViews.put(path, new NodeView(commitment, stem)); + + List diffs = stemInfo.suffixDiffs(); + Optional leftCommitment = Optional.empty(); + Optional rightCommitment = Optional.empty(); + if (diffs.get(0).suffix() < 128) { + if (!iterCommitments.hasNext()) { + throw new IllegalArgumentException(); // Missing commitments to fill the tree + } + leftCommitment = Optional.of(iterCommitments.next()); + } + if (diffs.getLast().suffix() >= 128) { + if (!iterCommitments.hasNext()) { + throw new IllegalArgumentException(); // Missing commitments to fill the tree + } + rightCommitment = Optional.of(iterCommitments.next()); + } + stemViews.put(stem, new StemView(leftCommitment, rightCommitment, diffs)); + break; + default: + throw new IllegalArgumentException("Illegal ExtensionType"); + } + } // End for each stemInfo + + if (iterCommitments.hasNext()) { + throw new IllegalArgumentException("Too many commitments"); + } + if (iterOtherStems.hasNext()) { + throw new IllegalArgumentException("Too many otherStems"); + } + return new PartialTrieView(nodeViews, stemViews); + } + + public record NodeView(Bytes32 commitment, Bytes stem) { + public static NodeView fromCommitment(Bytes32 commitment) { + return new NodeView(commitment, Bytes.EMPTY); + } + } + ; + + public record StemView( + Optional leftCommiment, + Optional rightCommitment, + List suffixDiffs) { + public static StemView fromEmpty() { + return new StemView( + Optional.empty(), Optional.empty(), new ArrayList()); + } + } + ; +} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/proof/ProofVerifier.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/proof/ProofVerifier.java deleted file mode 100644 index aeead070..00000000 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/proof/ProofVerifier.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * - */ -package org.hyperledger.besu.ethereum.trie.verkle.proof; - -import java.util.List; -import java.util.function.Function; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import verkle.cryptography.LibIpaMultipoint; - -/** - * This class validates the Verkle proof against the specified pre-state root. - * - *

This method checks if the provided execution witness data correctly corresponds to the given - * pre-state root. - */ -public class ProofVerifier { - - public boolean verifyVerkleProof( - final List keys, - final List currentValues, - final List commitmentsByPath, - final List cl, - final List cr, - final List otherStems, - final Bytes d, - final Bytes depthsExtensionPresentStems, - final Bytes finalEvaluation, - final Bytes prestateRoot) { - return LibIpaMultipoint.verifyPreStateRoot( - toArray(keys), - toArray(currentValues, unused -> Bytes.EMPTY.toArrayUnsafe()), - toArray(commitmentsByPath), - toArray(cl), - toArray(cr), - toArray(otherStems), - d.toArrayUnsafe(), - depthsExtensionPresentStems.toArrayUnsafe(), - finalEvaluation.toArrayUnsafe(), - prestateRoot.toArrayUnsafe()); - } - - private byte[][] toArray(final List elts) { - return toArray(elts, unused -> null); - } - - private byte[][] toArray( - final List elts, final Function defaultValue) { - final byte[][] elements = new byte[elts.size()][]; - for (int i = 0; i < elts.size(); i++) { - elements[i] = - ((elts.get(i) == null) ? defaultValue.apply(null) : elts.get(i).toArrayUnsafe()); - } - return elements; - } -} diff --git a/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/proof/PartialTrieViewTest.java b/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/proof/PartialTrieViewTest.java new file mode 100644 index 00000000..db4c0bdb --- /dev/null +++ b/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/proof/PartialTrieViewTest.java @@ -0,0 +1,62 @@ +/* + * Copyright Hyperledger Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ +package org.hyperledger.besu.ethereum.trie.verkle.proof; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.Test; + +public class PartialTrieViewTest { + + @Test + public void test0x62() { + List diffs = new ArrayList<>(); + diffs.add(new ExecutionWitness.SuffixDiff(97, null)); + List stemInfos = new ArrayList<>(); + stemInfos.add( + new ExecutionWitness.StemInfo( + Bytes.fromHexString("0xab8fbede899caa6a95ece66789421c7777983761db3cfb33b5e47ba10f413b"), + 2, + 2, + diffs)); + List otherStems = new ArrayList<>(); + List commitments = new ArrayList<>(); + commitments.add( + Bytes32.fromHexString( + "0x4900c9eda0b8f9a4ef9a2181ced149c9431b627797ab747ee9747b229579b583")); + commitments.add( + Bytes32.fromHexString( + "0x491dff71f13c89dac9aea22355478f5cfcf0af841b68e379a90aa77b8894c00e")); + commitments.add( + Bytes32.fromHexString( + "0x525d67511657d9220031586db9d41663ad592bbafc89bc763273a3c2eb0b19dc")); + ExecutionWitness witness = + new ExecutionWitness( + Bytes32.fromHexString( + "0x2cf2ab8fed2dcfe2fa77da044ab16393dbdabbc65deea5fdf272107a039f2c60"), + stemInfos, + new ExecutionWitness.VerificationHint(commitments, otherStems), + null); + + PartialTrieView view = PartialTrieView.fromExecutionWitness(witness); + assertThat(view != null).isTrue(); + } +}