Skip to content

Commit eca04d7

Browse files
ibc evm implement non membership verification (#702)
closes #699 closes #701 towards #538
2 parents 84fc711 + 4621d73 commit eca04d7

5 files changed

Lines changed: 290 additions & 71 deletions

File tree

evm/contracts/clients/CometblsClient.sol

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,17 @@ contract CometblsClient is ILightClient {
277277
consensusState.timestamp != 0,
278278
"LC: verifyMembership: consensusState does not exist"
279279
);
280+
if (
281+
(delayTimePeriod != 0 || delayBlockPeriod != 0) &&
282+
!validateDelayPeriod(
283+
clientId,
284+
height,
285+
delayTimePeriod,
286+
delayBlockPeriod
287+
)
288+
) {
289+
return false;
290+
}
280291
return
281292
membershipVerifier.verifyMembership(
282293
abi.encodePacked(consensusState.root),
@@ -303,6 +314,17 @@ contract CometblsClient is ILightClient {
303314
consensusState.timestamp != 0,
304315
"LC: verifyNonMembership: consensusState does not exist"
305316
);
317+
if (
318+
(delayTimePeriod != 0 || delayBlockPeriod != 0) &&
319+
!validateDelayPeriod(
320+
clientId,
321+
height,
322+
delayTimePeriod,
323+
delayBlockPeriod
324+
)
325+
) {
326+
return false;
327+
}
306328
return
307329
membershipVerifier.verifyNonMembership(
308330
abi.encodePacked(consensusState.root),
@@ -312,13 +334,40 @@ contract CometblsClient is ILightClient {
312334
);
313335
}
314336

337+
function validateDelayPeriod(
338+
string calldata clientId,
339+
IbcCoreClientV1Height.Data calldata height,
340+
uint64 delayPeriodTime,
341+
uint64 delayPeriodBlocks
342+
) public view returns (bool) {
343+
uint128 heightU128 = height.toUint128();
344+
uint64 currentTime = uint64(block.timestamp * 1e9);
345+
ProcessedMoment memory moment = processedMoments[
346+
stateIndex(clientId, heightU128)
347+
];
348+
uint64 validTime = uint64(moment.timestamp) * 1e9 + delayPeriodTime;
349+
if (currentTime < validTime) {
350+
return false;
351+
}
352+
uint64 currentHeight = uint64(block.number);
353+
uint64 validHeight = uint64(moment.height) + delayPeriodBlocks;
354+
if (currentHeight < validHeight) {
355+
return false;
356+
}
357+
return true;
358+
}
359+
315360
function getClientState(
316361
string calldata clientId
317362
) external view returns (bytes memory, bool) {
363+
bytes memory codeId = codeIds[clientId];
364+
if (codeId.length == 0) {
365+
return (bytes(""), false);
366+
}
318367
return (
319368
clientStates[clientId].marshalToProto(
320369
latestHeights[clientId],
321-
codeIds[clientId]
370+
codeId
322371
),
323372
true
324373
);
@@ -328,11 +377,13 @@ contract CometblsClient is ILightClient {
328377
string calldata clientId,
329378
IbcCoreClientV1Height.Data calldata height
330379
) external view returns (bytes memory, bool) {
331-
return (
332-
consensusStates[stateIndex(clientId, height.toUint128())]
333-
.marshalToProto(),
334-
true
335-
);
380+
OptimizedConsensusState memory consensusState = consensusStates[
381+
stateIndex(clientId, height.toUint128())
382+
];
383+
if (consensusState.timestamp == 0) {
384+
return (bytes(""), false);
385+
}
386+
return (consensusState.marshalToProto(), true);
336387
}
337388

338389
modifier onlyIBC() {

evm/contracts/clients/ICS23MembershipVerifier.sol

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,8 @@ import "../core/IMembershipVerifier.sol";
44
import "../lib/ICS23.sol";
55
import "../proto/cosmos/ics23/v1/proofs.sol";
66
import "../proto/ibc/core/commitment/v1/commitment.sol";
7-
import "solady/utils/LibString.sol";
87

98
contract ICS23MembershipVerifier is IMembershipVerifier {
10-
using LibString for string;
11-
129
function verifyMembership(
1310
bytes memory root,
1411
bytes calldata proof,
@@ -34,6 +31,14 @@ contract ICS23MembershipVerifier is IMembershipVerifier {
3431
bytes calldata prefix,
3532
bytes calldata path
3633
) external view override returns (bool) {
37-
revert("not implemented yet");
34+
bytes[] memory fullPath = new bytes[](2);
35+
fullPath[0] = prefix;
36+
fullPath[1] = path;
37+
return
38+
Ics23.verifyChainedNonMembership(
39+
IbcCoreCommitmentV1MerkleProof.decode(proof),
40+
root,
41+
fullPath
42+
) == Ics23.VerifyChainedNonMembershipError.None;
3843
}
3944
}

evm/contracts/lib/ICS23.sol

Lines changed: 132 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,125 @@ library Ics23 {
6666
});
6767
}
6868

69+
enum VerifyChainedNonMembershipError {
70+
None,
71+
NonExistenceProofIsNil,
72+
ExistenceProofIsNil,
73+
InvalidProofRoot,
74+
KeyMismatch,
75+
ValueMismatch,
76+
InvalidSpec,
77+
InvalidIntermediateProofRoot,
78+
IntermateProofRootMismatch,
79+
RootMismatch,
80+
VerifyLeft,
81+
VerifyRight,
82+
LeftAndRightKeyEmpty,
83+
RightKeyRange,
84+
LeftKeyRange,
85+
RightProofLeftMost,
86+
LeftProofRightMost,
87+
IsLeftNeighbor
88+
}
89+
90+
function verifyChainedNonMembership(
91+
IbcCoreCommitmentV1MerkleProof.Data memory merkleProof,
92+
bytes memory root,
93+
bytes[] memory path
94+
) internal pure returns (VerifyChainedNonMembershipError) {
95+
CosmosIcs23V1ProofSpec.Data memory iavlSpec = getIavlProofSpec();
96+
CosmosIcs23V1ProofSpec.Data
97+
memory tendermintSpec = getTendermintProofSpec();
98+
99+
CosmosIcs23V1CommitmentProof.Data memory proof = merkleProof.proofs[0];
100+
CosmosIcs23V1NonExistenceProof.Data memory nonExistenceProof = proof
101+
.nonexist;
102+
if (CosmosIcs23V1NonExistenceProof.isNil(nonExistenceProof)) {
103+
return VerifyChainedNonMembershipError.NonExistenceProofIsNil;
104+
}
105+
106+
(bytes memory subroot, Proof.CalculateRootError rCode) = Proof
107+
.calculateRoot(proof);
108+
if (rCode != Proof.CalculateRootError.None) {
109+
return VerifyChainedNonMembershipError.InvalidProofRoot;
110+
}
111+
112+
bytes memory key = path[path.length - 1];
113+
Proof.VerifyNonExistenceError vCode = Proof.verify(
114+
nonExistenceProof,
115+
iavlSpec,
116+
subroot,
117+
key
118+
);
119+
120+
// Map non existence error to non membership error
121+
if (vCode != Proof.VerifyNonExistenceError.None) {
122+
if (vCode == Proof.VerifyNonExistenceError.VerifyLeft) {
123+
return VerifyChainedNonMembershipError.VerifyLeft;
124+
} else if (
125+
vCode == Proof.VerifyNonExistenceError.LeftAndRightKeyEmpty
126+
) {
127+
return VerifyChainedNonMembershipError.LeftAndRightKeyEmpty;
128+
} else if (vCode == Proof.VerifyNonExistenceError.RightKeyRange) {
129+
return VerifyChainedNonMembershipError.RightKeyRange;
130+
} else if (vCode == Proof.VerifyNonExistenceError.LeftKeyRange) {
131+
return VerifyChainedNonMembershipError.LeftKeyRange;
132+
} else if (
133+
vCode == Proof.VerifyNonExistenceError.RightProofLeftMost
134+
) {
135+
return VerifyChainedNonMembershipError.RightProofLeftMost;
136+
} else if (
137+
vCode == Proof.VerifyNonExistenceError.LeftProofRightMost
138+
) {
139+
return VerifyChainedNonMembershipError.LeftProofRightMost;
140+
} else if (vCode == Proof.VerifyNonExistenceError.IsLeftNeighbor) {
141+
return VerifyChainedNonMembershipError.IsLeftNeighbor;
142+
}
143+
144+
revert(
145+
"verifyChainedNonMembership: non exhaustive pattern matching on VerifyNonExistenceError"
146+
);
147+
}
148+
149+
VerifyChainedMembershipError mCode = verifyChainedMembershipAt(
150+
merkleProof,
151+
root,
152+
path,
153+
subroot,
154+
1
155+
);
156+
157+
// Map membership error to non membership error
158+
if (mCode != VerifyChainedMembershipError.None) {
159+
if (mCode == VerifyChainedMembershipError.ExistenceProofIsNil) {
160+
return VerifyChainedNonMembershipError.ExistenceProofIsNil;
161+
} else if (mCode == VerifyChainedMembershipError.InvalidProofRoot) {
162+
return VerifyChainedNonMembershipError.InvalidProofRoot;
163+
} else if (mCode == VerifyChainedMembershipError.KeyMismatch) {
164+
return VerifyChainedNonMembershipError.KeyMismatch;
165+
} else if (mCode == VerifyChainedMembershipError.ValueMismatch) {
166+
return VerifyChainedNonMembershipError.ValueMismatch;
167+
} else if (mCode == VerifyChainedMembershipError.InvalidSpec) {
168+
return VerifyChainedNonMembershipError.InvalidSpec;
169+
} else if (
170+
mCode ==
171+
VerifyChainedMembershipError.InvalidIntermediateProofRoot
172+
) {
173+
return
174+
VerifyChainedNonMembershipError
175+
.InvalidIntermediateProofRoot;
176+
} else if (mCode == VerifyChainedMembershipError.RootMismatch) {
177+
return VerifyChainedNonMembershipError.RootMismatch;
178+
}
179+
180+
revert(
181+
"verifyChainedNonMembership: non exhaustive pattern matching on VerifyChainedMembershipError"
182+
);
183+
}
184+
185+
return VerifyChainedNonMembershipError.None;
186+
}
187+
69188
enum VerifyChainedMembershipError {
70189
None,
71190
ExistenceProofIsNil,
@@ -83,12 +202,22 @@ library Ics23 {
83202
bytes memory root,
84203
bytes[] memory path,
85204
bytes memory value
205+
) internal pure returns (VerifyChainedMembershipError) {
206+
return verifyChainedMembershipAt(merkleProof, root, path, value, 0);
207+
}
208+
209+
function verifyChainedMembershipAt(
210+
IbcCoreCommitmentV1MerkleProof.Data memory merkleProof,
211+
bytes memory root,
212+
bytes[] memory path,
213+
bytes memory value,
214+
uint256 index
86215
) internal pure returns (VerifyChainedMembershipError) {
87216
CosmosIcs23V1ProofSpec.Data memory iavlSpec = getIavlProofSpec();
88217
CosmosIcs23V1ProofSpec.Data
89218
memory tendermintSpec = getTendermintProofSpec();
90219
bytes memory subroot = value;
91-
for (uint256 i = 0; i < merkleProof.proofs.length; i++) {
220+
for (uint256 i = index; i < merkleProof.proofs.length; i++) {
92221
CosmosIcs23V1CommitmentProof.Data memory proof = merkleProof.proofs[
93222
i
94223
];
@@ -110,9 +239,10 @@ library Ics23 {
110239
* Path is provided as /a/b/c, we need to pop until reaching the root
111240
*/
112241
bytes memory key = path[path.length - i - 1];
242+
113243
Proof.VerifyExistenceError vCode = Proof.verify(
114244
existenceProof,
115-
i == (path.length - 1) ? tendermintSpec : iavlSpec,
245+
i == 0 ? iavlSpec : tendermintSpec,
116246
subroot,
117247
key,
118248
value

0 commit comments

Comments
 (0)