Skip to content

Commit f587b88

Browse files
Verify proof with hashed key
1 parent eaf3527 commit f587b88

File tree

2 files changed

+31
-13
lines changed

2 files changed

+31
-13
lines changed

contracts/utils/cryptography/TrieProof.sol

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ library TrieProof {
3535
INVALID_INTERNAL_NODE_HASH, // Internal node hash doesn't match expected value
3636
EMPTY_VALUE, // The value to verify is empty
3737
INVALID_EXTRA_PROOF_ELEMENT, // Proof contains unexpected additional elements
38+
MISMATCH_LEAF_PATH_KEY_REMAINDERS, // Leaf path remainder doesn't match key remainder
3839
INVALID_PATH_REMAINDER, // Path remainder doesn't match expected value
3940
INVALID_KEY_REMAINDER, // Key remainder doesn't match expected value
4041
UNKNOWN_NODE_PREFIX, // Node prefix is not recognized
@@ -129,7 +130,6 @@ library TrieProof {
129130
// Otherwise, continue down the branch specified by the next nibble in the key
130131
uint8 branchKey = uint8(key[keyIndex]);
131132
(nodeId, keyIndex) = (_id(node.decoded[branchKey]), keyIndex + 1);
132-
nodeId = node.decoded[11].readBytes(); // test
133133
} else if (nodeLength == LEAF_OR_EXTENSION_NODE_LENGTH) {
134134
return _processLeafOrExtension(node, trieProof, key, nodeId, keyIndex, i);
135135
}
@@ -172,14 +172,16 @@ library TrieProof {
172172
uint256 i
173173
) private pure returns (bytes memory value, ProofError err) {
174174
bytes memory path = _path(node);
175-
uint8 prefix = uint8(path[0] >> 4);
175+
uint8 prefix = uint8(path[0]);
176176
uint8 offset = 2 - (prefix % 2); // Calculate offset based on even/odd path length
177177
bytes memory pathRemainder = Bytes.slice(path, offset); // Path after the prefix
178178
bytes memory keyRemainder = Bytes.slice(key, keyIndex); // Remaining key to match
179179
if (prefix > uint8(type(Prefix).max)) return ("", ProofError.UNKNOWN_NODE_PREFIX);
180180

181181
// Leaf node (terminal) - return its value if key matches completely
182182
if (Prefix(prefix) == Prefix.LEAF_EVEN || Prefix(prefix) == Prefix.LEAF_ODD) {
183+
if (!string(pathRemainder).equal(string(keyRemainder)))
184+
return ("", ProofError.MISMATCH_LEAF_PATH_KEY_REMAINDERS);
183185
if (keyRemainder.length == 0) return ("", ProofError.INVALID_KEY_REMAINDER);
184186
return _validateLastItem(node.decoded[1], trieProof, i);
185187
}
@@ -227,7 +229,7 @@ library TrieProof {
227229
*/
228230
function _id(Memory.Slice node) private pure returns (bytes memory) {
229231
bytes memory raw = node.readBytes();
230-
return raw.length < 32 ? raw : bytes.concat(keccak256(raw));
232+
return raw.length <= 32 ? raw : bytes.concat(keccak256(raw));
231233
}
232234

233235
/**
@@ -256,7 +258,7 @@ library TrieProof {
256258
uint256 length = value.length;
257259
bytes memory nibbles_ = new bytes(length * 2);
258260
for (uint256 i = 0; i < length; i++) {
259-
(nibbles_[i * 2], nibbles_[i * 2 + 1]) = (value[i] & 0xf0, value[i] & 0x0f);
261+
(nibbles_[i * 2], nibbles_[i * 2 + 1]) = ((value[i] & 0xf0) >> 4, value[i] & 0x0f);
260262
}
261263
return nibbles_;
262264
}

test/utils/cryptography/TrieProof.test.js

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ const ProofError = {
1414
INVALID_INTERNAL_NODE_HASH: 5,
1515
EMPTY_VALUE: 6,
1616
INVALID_EXTRA_PROOF_ELEMENT: 7,
17-
INVALID_PATH_REMAINDER: 8,
18-
INVALID_KEY_REMAINDER: 9,
19-
UNKNOWN_NODE_PREFIX: 10,
20-
UNPARSEABLE_NODE: 11,
21-
INVALID_PROOF: 12,
17+
MISMATCH_LEAF_PATH_KEY_REMAINDERS: 8,
18+
INVALID_PATH_REMAINDER: 9,
19+
INVALID_KEY_REMAINDER: 10,
20+
UNKNOWN_NODE_PREFIX: 11,
21+
UNPARSEABLE_NODE: 12,
22+
INVALID_PROOF: 13,
2223
};
2324

2425
// Method eth_getProof is not supported with default Hardhat Network, so Anvil is used for that.
@@ -80,7 +81,7 @@ describe('TrieProof', function () {
8081
const slot = ethers.ZeroHash;
8182
const tx = await call(this.storage, 'setUint256Slot', [slot, 42]);
8283
const { key, value, proof, storageHash } = await this.getProof(this.storage, slot, tx);
83-
const result = await this.mock.$verify(key, value, proof, storageHash);
84+
const result = await this.mock.$verify(ethers.keccak256(key), value, proof, storageHash);
8485
expect(result).is.true;
8586
});
8687

@@ -90,7 +91,7 @@ describe('TrieProof', function () {
9091
await call(this.storage, 'setUint256Slot', [slot0, 42]);
9192
const tx = await call(this.storage, 'setUint256Slot', [slot1, 43]);
9293
const { key, value, proof, storageHash } = await this.getProof(this.storage, slot1, tx);
93-
const result = await this.mock.$verify(key, value, proof, storageHash);
94+
const result = await this.mock.$verify(ethers.keccak256(key), value, proof, storageHash);
9495
expect(result).is.true;
9596
});
9697

@@ -126,7 +127,7 @@ describe('TrieProof', function () {
126127
it.skip('fails to process proof with invalid internal short node', async function () {}); // TODO: INVALID_INTERNAL_NODE_HASH
127128

128129
it('fails to process proof with empty value', async function () {
129-
const proof = [ethers.encodeRlp(['0x20', '0x'])]; // Corrupt proof to yield empty value
130+
const proof = [ethers.encodeRlp(['0x2000', '0x'])]; // Corrupt proof to yield empty value
130131
const [processedValue, error] = await this.mock.$processProof('0x00', proof, ethers.keccak256(proof[0]));
131132
expect(processedValue).to.equal('0x');
132133
expect(error).to.equal(ProofError.EMPTY_VALUE);
@@ -137,11 +138,26 @@ describe('TrieProof', function () {
137138
const tx = await call(this.storage, 'setUint256Slot', [slot0, 42]);
138139
const { key, proof, storageHash } = await this.getProof(this.storage, slot0, tx);
139140
proof[1] = ethers.encodeRlp([]); // extra proof element
140-
const [processedValue, error] = await this.mock.$processProof(key, proof, storageHash);
141+
const [processedValue, error] = await this.mock.$processProof(ethers.keccak256(key), proof, storageHash);
141142
expect(processedValue).to.equal('0x');
142143
expect(error).to.equal(ProofError.INVALID_EXTRA_PROOF_ELEMENT);
143144
});
144145

146+
it('fails to process proof with mismatched leaf path and key remainders', async function () {
147+
const slot = ethers.ZeroHash;
148+
const key = ethers.keccak256(slot);
149+
const value = '0x2a';
150+
const proof = [
151+
ethers.encodeRlp([
152+
'0x20' + ethers.toBeHex(BigInt(key) + 1n).replace('0x', ''), // corrupt end of leaf path
153+
value,
154+
]),
155+
];
156+
const [processedValue, error] = await this.mock.$processProof(key, proof, ethers.keccak256(proof[0]));
157+
expect(processedValue).to.equal('0x');
158+
expect(error).to.equal(ProofError.MISMATCH_LEAF_PATH_KEY_REMAINDERS);
159+
});
160+
145161
it('fails to process proof with invalid path remainder', async function () {
146162
const proof = [ethers.encodeRlp(['0x0011', '0x'])]; // Corrupt proof to yield invalid path remainder
147163
const [processedValue, error] = await this.mock.$processProof(ethers.ZeroHash, proof, ethers.keccak256(proof[0]));

0 commit comments

Comments
 (0)