From f43870f8183c2b24d0bf7c19f3d99b027c507560 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 14 Aug 2020 16:22:15 +0200 Subject: [PATCH] Parse ics23 proofs and do initial verification --- packages/stargate/package.json | 1 + packages/stargate/src/queries/queryclient.ts | 91 +++++++++++++++---- .../stargate/types/queries/queryclient.d.ts | 3 + .../tendermint-rpc/src/v0-33/responses.ts | 1 - yarn.lock | 28 ++++++ 5 files changed, 106 insertions(+), 18 deletions(-) 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; queryUnverified(path: string, request: Uint8Array): Promise; } +export declare const IavlSpec: ics23.IProofSpec; +export declare const TendermintSpec: ics23.IProofSpec; export {}; diff --git a/packages/tendermint-rpc/src/v0-33/responses.ts b/packages/tendermint-rpc/src/v0-33/responses.ts index ae08ee0e..3e785ac1 100644 --- a/packages/tendermint-rpc/src/v0-33/responses.ts +++ b/packages/tendermint-rpc/src/v0-33/responses.ts @@ -25,7 +25,6 @@ import * as responses from "../responses"; import { SubscriptionEvent } from "../rpcclients"; import { IpPortString, TxBytes, TxHash, ValidatorPubkey, ValidatorSignature } from "../types"; import { hashTx } from "./hasher"; -import { isArgon2idOptions } from "@cosmjs/crypto"; interface AbciInfoResult { readonly response: RpcAbciInfoResponse; diff --git a/yarn.lock b/yarn.lock index 6b81d06a..3220ddc9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -177,6 +177,15 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" +"@confio/ics23@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@confio/ics23/-/ics23-0.6.0.tgz#8c3e6508159bdbee02b12beeee9b369ea68ef6ba" + integrity sha512-P/NcAO3pz7xXg4cpwaKRUIN4PuOOxwY94n7WD+wod7UG5YlMLzb8Upv6nZo9/brnKXW4gXB2DccLTtV0Um1Wvw== + dependencies: + protobufjs "^6.8.8" + ripemd160 "^2.0.2" + sha.js "^2.4.11" + "@evocateur/libnpmaccess@^3.1.2": version "3.1.2" resolved "https://registry.yarnpkg.com/@evocateur/libnpmaccess/-/libnpmaccess-3.1.2.tgz#ecf7f6ce6b004e9f942b098d92200be4a4b1c845" @@ -6764,6 +6773,25 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= +protobufjs@^6.8.8: + version "6.10.1" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.10.1.tgz#e6a484dd8f04b29629e9053344e3970cccf13cd2" + integrity sha512-pb8kTchL+1Ceg4lFd5XUpK8PdWacbvV5SK2ULH2ebrYtl4GjJmS24m6CKME67jzV53tbJxHlnNOSqQHbTsR9JQ== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/long" "^4.0.1" + "@types/node" "^13.7.0" + long "^4.0.0" + protobufjs@~6.10.0: version "6.10.0" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.10.0.tgz#b0698a2a91fc597e2dc625dcf3539ece9675c8fd"