Skip to content

Commit a532519

Browse files
cherrypick #136 (#199)
1 parent 9d76542 commit a532519

22 files changed

+520
-273
lines changed

go/ics23.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func VerifyMembership(spec *ProofSpec, root CommitmentRoot, proof *CommitmentPro
5252
func VerifyNonMembership(spec *ProofSpec, root CommitmentRoot, proof *CommitmentProof, key []byte) bool {
5353
// decompress it before running code (no-op if not compressed)
5454
proof = Decompress(proof)
55-
np := getNonExistProofForKey(proof, key)
55+
np := getNonExistProofForKey(spec, proof, key)
5656
if np == nil {
5757
return false
5858
}
@@ -148,27 +148,27 @@ func getExistProofForKey(proof *CommitmentProof, key []byte) *ExistenceProof {
148148
return nil
149149
}
150150

151-
func getNonExistProofForKey(proof *CommitmentProof, key []byte) *NonExistenceProof {
151+
func getNonExistProofForKey(spec *ProofSpec, proof *CommitmentProof, key []byte) *NonExistenceProof {
152152
switch p := proof.Proof.(type) {
153153
case *CommitmentProof_Nonexist:
154154
np := p.Nonexist
155-
if isLeft(np.Left, key) && isRight(np.Right, key) {
155+
if isLeft(spec, np.Left, key) && isRight(spec, np.Right, key) {
156156
return np
157157
}
158158
case *CommitmentProof_Batch:
159159
for _, sub := range p.Batch.Entries {
160-
if np := sub.GetNonexist(); np != nil && isLeft(np.Left, key) && isRight(np.Right, key) {
160+
if np := sub.GetNonexist(); np != nil && isLeft(spec, np.Left, key) && isRight(spec, np.Right, key) {
161161
return np
162162
}
163163
}
164164
}
165165
return nil
166166
}
167167

168-
func isLeft(left *ExistenceProof, key []byte) bool {
169-
return left == nil || bytes.Compare(left.Key, key) < 0
168+
func isLeft(spec *ProofSpec, left *ExistenceProof, key []byte) bool {
169+
return left == nil || bytes.Compare(keyForComparison(spec, left.Key), keyForComparison(spec, key)) < 0
170170
}
171171

172-
func isRight(right *ExistenceProof, key []byte) bool {
173-
return right == nil || bytes.Compare(right.Key, key) > 0
172+
func isRight(spec *ProofSpec, right *ExistenceProof, key []byte) bool {
173+
return right == nil || bytes.Compare(keyForComparison(spec, right.Key), keyForComparison(spec, key)) > 0
174174
}

go/proof.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ var TendermintSpec = &ProofSpec{
4747
var SmtSpec = &ProofSpec{
4848
LeafSpec: &LeafOp{
4949
Hash: HashOp_SHA256,
50-
PrehashKey: HashOp_NO_HASH,
50+
PrehashKey: HashOp_SHA256,
5151
PrehashValue: HashOp_SHA256,
5252
Length: LengthOp_NO_PREFIX,
5353
Prefix: []byte{0},
@@ -60,7 +60,8 @@ var SmtSpec = &ProofSpec{
6060
EmptyChild: make([]byte, 32),
6161
Hash: HashOp_SHA256,
6262
},
63-
MaxDepth: 256,
63+
MaxDepth: 256,
64+
PrehashKeyBeforeComparison: true,
6465
}
6566

6667
func encodeVarintProto(l int) []byte {
@@ -204,6 +205,17 @@ func (p *ExistenceProof) CheckAgainstSpec(spec *ProofSpec) error {
204205
return nil
205206
}
206207

208+
// If we should prehash the key before comparison, do so; otherwise, return the key. Prehashing
209+
// changes lexical comparison, so we do so before comparison if the spec sets
210+
// `PrehashKeyBeforeComparison`.
211+
func keyForComparison(spec *ProofSpec, key []byte) []byte {
212+
if !spec.PrehashKeyBeforeComparison {
213+
return key
214+
}
215+
hash, _ := doHashOrNoop(spec.LeafSpec.PrehashKey, key)
216+
return hash
217+
}
218+
207219
// Verify does all checks to ensure the proof has valid non-existence proofs,
208220
// and they ensure the given key is not in the CommitmentState
209221
func (p *NonExistenceProof) Verify(spec *ProofSpec, root CommitmentRoot, key []byte) error {
@@ -229,13 +241,13 @@ func (p *NonExistenceProof) Verify(spec *ProofSpec, root CommitmentRoot, key []b
229241

230242
// Ensure in valid range
231243
if rightKey != nil {
232-
if bytes.Compare(key, rightKey) >= 0 {
244+
if bytes.Compare(keyForComparison(spec, key), keyForComparison(spec, rightKey)) >= 0 {
233245
return errors.New("key is not left of right proof")
234246
}
235247
}
236248

237249
if leftKey != nil {
238-
if bytes.Compare(key, leftKey) <= 0 {
250+
if bytes.Compare(keyForComparison(spec, key), keyForComparison(spec, leftKey)) <= 0 {
239251
return errors.New("key is not right of left proof")
240252
}
241253
}

go/proofs.pb.go

Lines changed: 175 additions & 130 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/src/generated/codecimpl.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,9 @@ export namespace ics23 {
703703

704704
/** ProofSpec minDepth */
705705
minDepth?: number | null;
706+
707+
/** ProofSpec prehashKeyBeforeComparison */
708+
prehashKeyBeforeComparison?: boolean | null;
706709
}
707710

708711
/**
@@ -736,6 +739,9 @@ export namespace ics23 {
736739
/** ProofSpec minDepth. */
737740
public minDepth: number;
738741

742+
/** ProofSpec prehashKeyBeforeComparison. */
743+
public prehashKeyBeforeComparison: boolean;
744+
739745
/**
740746
* Creates a new ProofSpec instance using the specified properties.
741747
* @param [properties] Properties to set

js/src/generated/codecimpl.js

Lines changed: 61 additions & 39 deletions
Large diffs are not rendered by default.

js/src/ics23.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { decompress } from "./compress";
22
import { ics23 } from "./generated/codecimpl";
33
import { CommitmentRoot, verifyExistence, verifyNonExistence } from "./proofs";
4+
import { keyForComparison } from "./proofs";
45
import { bytesBefore, bytesEqual } from "./specs";
5-
66
/*
77
This implements the client side functions as specified in
88
https://github.com/cosmos/ics/tree/master/spec/ics-023-vector-commitments
@@ -59,7 +59,7 @@ export function verifyNonMembership(
5959
key: Uint8Array
6060
): boolean {
6161
const norm = decompress(proof);
62-
const nonexist = getNonExistForKey(norm, key);
62+
const nonexist = getNonExistForKey(spec, norm, key);
6363
if (!nonexist) {
6464
return false;
6565
}
@@ -122,14 +122,23 @@ function getExistForKey(
122122
}
123123

124124
function getNonExistForKey(
125+
spec: ics23.IProofSpec,
125126
proof: ics23.ICommitmentProof,
126127
key: Uint8Array
127128
): ics23.INonExistenceProof | undefined | null {
128129
const match = (p: ics23.INonExistenceProof | null | undefined): boolean => {
129130
return (
130131
!!p &&
131-
(!p.left || bytesBefore(p.left.key!, key)) &&
132-
(!p.right || bytesBefore(key, p.right.key!))
132+
(!p.left ||
133+
bytesBefore(
134+
keyForComparison(spec, p.left.key!),
135+
keyForComparison(spec, key)
136+
)) &&
137+
(!p.right ||
138+
bytesBefore(
139+
keyForComparison(spec, key),
140+
keyForComparison(spec, p.right.key!)
141+
))
133142
);
134143
};
135144
if (match(proof.nonexist)) {

js/src/proofs.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ics23 } from "./generated/codecimpl";
22
import { applyInner, applyLeaf } from "./ops";
3+
import { doHash } from "./ops";
34
import {
45
bytesEqual,
56
ensureBytesBefore,
@@ -45,7 +46,7 @@ export const tendermintSpec: ics23.IProofSpec = {
4546
export const smtSpec: ics23.IProofSpec = {
4647
leafSpec: {
4748
hash: ics23.HashOp.SHA256,
48-
prehashKey: ics23.HashOp.NO_HASH,
49+
prehashKey: ics23.HashOp.SHA256,
4950
prehashValue: ics23.HashOp.SHA256,
5051
length: ics23.LengthOp.NO_PREFIX,
5152
prefix: Uint8Array.from([0]),
@@ -59,10 +60,22 @@ export const smtSpec: ics23.IProofSpec = {
5960
hash: ics23.HashOp.SHA256,
6061
},
6162
maxDepth: 256,
63+
prehashKeyBeforeComparison: true,
6264
};
6365

6466
export type CommitmentRoot = Uint8Array;
6567

68+
export function keyForComparison(
69+
spec: ics23.IProofSpec,
70+
key: Uint8Array
71+
): Uint8Array {
72+
if (!spec.prehashKeyBeforeComparison) {
73+
return key;
74+
}
75+
76+
return doHash(spec.leafSpec!.prehashKey!, key);
77+
}
78+
6679
// verifyExistence will throw an error if the proof doesn't link key, value -> root
6780
// or if it doesn't fulfill the spec
6881
export function verifyExistence(
@@ -111,10 +124,16 @@ export function verifyNonExistence(
111124
}
112125

113126
if (leftKey) {
114-
ensureBytesBefore(leftKey, key);
127+
ensureBytesBefore(
128+
keyForComparison(spec, leftKey),
129+
keyForComparison(spec, key)
130+
);
115131
}
116132
if (rightKey) {
117-
ensureBytesBefore(key, rightKey);
133+
ensureBytesBefore(
134+
keyForComparison(spec, key),
135+
keyForComparison(spec, rightKey)
136+
);
118137
}
119138

120139
if (!spec.innerSpec) {

js/src/testvectors.spec.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
verifyMembership,
99
verifyNonMembership,
1010
} from "./ics23";
11-
import { iavlSpec, tendermintSpec } from "./proofs";
11+
import { iavlSpec, smtSpec, tendermintSpec } from "./proofs";
1212
import { fromHex } from "./testhelpers.spec";
1313

1414
describe("calculateExistenceRoot", () => {
@@ -251,4 +251,28 @@ describe("calculateExistenceRoot", () => {
251251
]);
252252
validateBatch(proof, tendermintSpec, data[3]);
253253
});
254+
255+
it("should validate smt batch exist", () => {
256+
const { proof, data } = loadBatch([
257+
"../testdata/smt/exist_left.json",
258+
"../testdata/smt/exist_right.json",
259+
"../testdata/smt/exist_middle.json",
260+
"../testdata/smt/nonexist_left.json",
261+
"../testdata/smt/nonexist_right.json",
262+
"../testdata/smt/nonexist_middle.json",
263+
]);
264+
validateBatch(proof, smtSpec, data[2]);
265+
});
266+
267+
it("should validate smt batch nonexist", () => {
268+
const { proof, data } = loadBatch([
269+
"../testdata/smt/exist_left.json",
270+
"../testdata/smt/exist_right.json",
271+
"../testdata/smt/exist_middle.json",
272+
"../testdata/smt/nonexist_left.json",
273+
"../testdata/smt/nonexist_right.json",
274+
"../testdata/smt/nonexist_middle.json",
275+
]);
276+
validateBatch(proof, smtSpec, data[3]);
277+
});
254278
});

proofs.proto

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ message ProofSpec {
162162
int32 max_depth = 3;
163163
// min_depth (if > 0) is the minimum number of InnerOps allowed (mainly for fixed-depth tries)
164164
int32 min_depth = 4;
165+
// prehash_key_before_comparison is a flag that indicates whether to use the
166+
// prehash_key specified by LeafOp to compare lexical ordering of keys for
167+
// non-existence proofs.
168+
bool prehash_key_before_comparison = 5;
165169
}
166170

167171
/*

rust/codegen/src/main.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ fn main() {
1313
let out_dir: &str = &format!("{}{}", root, "/rust/src");
1414
let input: &str = &format!("{}{}", root, "/proofs.proto");
1515

16-
let mut cfg = prost_build::Config::new();
17-
cfg.out_dir(&out_dir);
18-
cfg.compile_protos(&[input], &[root]).unwrap();
16+
prost_build::Config::new()
17+
.out_dir(&out_dir)
18+
.format(true)
19+
.compile_protos(&[input], &[root])
20+
.unwrap();
1921
}

0 commit comments

Comments
 (0)