diff --git a/packages/stargate/package.json b/packages/stargate/package.json index 5b69f12a..58f477c7 100644 --- a/packages/stargate/package.json +++ b/packages/stargate/package.json @@ -45,6 +45,7 @@ "postdefine-proto": "prettier --write \"src/codec/generated/codecimpl.*\"" }, "dependencies": { + "@confio/ics23": "^0.6.0", "@cosmjs/encoding": "^0.22.2", "@cosmjs/launchpad": "^0.22.2", "@cosmjs/math": "^0.22.2", diff --git a/packages/stargate/src/queries/queryclient.ts b/packages/stargate/src/queries/queryclient.ts index 460e2221..8f8759f7 100644 --- a/packages/stargate/src/queries/queryclient.ts +++ b/packages/stargate/src/queries/queryclient.ts @@ -1,8 +1,44 @@ /* eslint-disable no-dupe-class-members, @typescript-eslint/ban-types, @typescript-eslint/naming-convention */ -import { toHex, fromAscii } from "@cosmjs/encoding"; +import { ics23, verifyExistence } from "@confio/ics23"; +import { fromAscii, toHex } from "@cosmjs/encoding"; import { Client as TendermintClient } from "@cosmjs/tendermint-rpc"; import { arrayContentEquals, assert, isNonNullObject } from "@cosmjs/utils"; +// TODO: import these from @confio/ics23 when exported +export const IavlSpec: ics23.IProofSpec = { + leafSpec: { + prefix: Uint8Array.from([0]), + hash: ics23.HashOp.SHA256, + prehashValue: ics23.HashOp.SHA256, + prehashKey: ics23.HashOp.NO_HASH, + length: ics23.LengthOp.VAR_PROTO, + }, + innerSpec: { + childOrder: [0, 1], + minPrefixLength: 4, + maxPrefixLength: 12, + childSize: 33, + hash: ics23.HashOp.SHA256, + }, +}; + +export const TendermintSpec: ics23.IProofSpec = { + leafSpec: { + prefix: Uint8Array.from([0]), + hash: ics23.HashOp.SHA256, + prehashValue: ics23.HashOp.SHA256, + prehashKey: ics23.HashOp.NO_HASH, + length: ics23.LengthOp.VAR_PROTO, + }, + innerSpec: { + childOrder: [0, 1], + minPrefixLength: 1, + maxPrefixLength: 1, + childSize: 32, + hash: ics23.HashOp.SHA256, + }, +}; + type QueryExtensionSetup
= (base: QueryClient) => P; export class QueryClient { @@ -166,25 +202,46 @@ export class QueryClient { throw new Error(`Expected 2 proof ops, got ${response.proof.ops.length}. Are you using stargate?`); } - const subProof = response.proof.ops[0]; - if (subProof.type !== "ics23:iavl") { - throw new Error(`Sub-proof expected to be ics23:iavl, got "${subProof.type}`); + const subOp = response.proof.ops[0]; + if (subOp.type !== "ics23:iavl") { + throw new Error(`Sub-proof expected to be ics23:iavl, got "${subOp.type}`); } - if (!arrayContentEquals(key, subProof.key)) { - throw new Error(`Proven key different than queried key.\nQuery: ${toHex(key)}\nProven: ${toHex(subProof.key)}`); - } - console.log(`Proof: ${toHex(subProof.data)}`); - - const storeProof = response.proof.ops[1]; - if (storeProof.type !== "ics23:simple") { - throw new Error(`Store-proof expected to be ics23:simple, got "${storeProof.type}`); - } - if (store !== fromAscii(storeProof.key)) { - throw new Error(`Proven store "${store}" different than queried store ${fromAscii(storeProof.key)}`); + if (!arrayContentEquals(key, subOp.key)) { + throw new Error( + `Proven key different than queried key.\nQuery: ${toHex(key)}\nProven: ${toHex(subOp.key)}`, + ); } - // TODO: implement proof verification - // https://github.com/CosmWasm/cosmjs/issues/347 + const storeOp = response.proof.ops[1]; + if (storeOp.type !== "ics23:simple") { + throw new Error(`Store-proof expected to be ics23:simple, got "${storeOp.type}`); + } + if (store !== fromAscii(storeOp.key)) { + throw new Error(`Proven store "${store}" different than queried store ${fromAscii(storeOp.key)}`); + } + + assert(response.height); + assert(response.height > 0); + + // parse proofs + const subProof = ics23.CommitmentProof.decode(subOp.data); + assert(subProof.exist); + assert(subProof.exist.value); + + const storeProof = ics23.CommitmentProof.decode(storeOp.data); + assert(storeProof.exist); + assert(storeProof.exist.value); + + // the subproof must map the desired key to the "value" of the storeProof + verifyExistence(subProof.exist, IavlSpec, storeProof.exist.value, key, response.value); + + // the storeproof must may it's declared value (root of subProof) to the appHash + // TODO + // // get the header for height + 1 + // await sleep(5000); // TODO: we can do better + // // TODO: why don't we expose the header query, just height? + // const header = await this.tmClient.block(response.height+1); + // verifyExistence(storeProof.exist, TendermintSpec, header.block.header.appHash, toAscii(store), storeProof.exist.value!); return response.value; } diff --git a/packages/stargate/types/queries/queryclient.d.ts b/packages/stargate/types/queries/queryclient.d.ts index c00db57b..a0518931 100644 --- a/packages/stargate/types/queries/queryclient.d.ts +++ b/packages/stargate/types/queries/queryclient.d.ts @@ -1,3 +1,4 @@ +import { ics23 } from "@confio/ics23"; import { Client as TendermintClient } from "@cosmjs/tendermint-rpc"; declare type QueryExtensionSetup
= (base: QueryClient) => P;
export declare class QueryClient {
@@ -106,4 +107,6 @@ export declare class QueryClient {
queryVerified(store: string, key: Uint8Array): Promise