From d233f88c626a1aae2a1e44fc05399b23bd00033b Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Tue, 6 Sep 2022 12:53:47 -0700 Subject: [PATCH 1/6] feat: support for polyfill of blsVerify --- packages/agent/src/certificate.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/agent/src/certificate.ts b/packages/agent/src/certificate.ts index a92824cd1..df47dc829 100644 --- a/packages/agent/src/certificate.ts +++ b/packages/agent/src/certificate.ts @@ -1,9 +1,9 @@ import * as cbor from './cbor'; import { AgentError } from './errors'; import { hash } from './request_id'; -import { blsVerify } from './utils/bls'; import { concat, fromHex, toHex } from './utils/buffer'; import { Principal } from '@dfinity/principal'; +import { blsVerify } from './utils/bls'; /** * A certificate may fail verification with respect to the provided public key @@ -113,6 +113,10 @@ export interface CreateCertificateOptions { * the signing canister ID when verifying a certified variable. */ canisterId: Principal; + /** + * BLS Verification strategy. Default strategy uses wasm for performance, but that may not be available in all contexts. + */ + blsVerify?: (pk: Uint8Array, sig: Uint8Array, msg: Uint8Array) => Promise; } export class Certificate { @@ -130,7 +134,16 @@ export class Certificate { * @throws {CertificateVerificationError} */ public static async create(options: CreateCertificateOptions): Promise { - const cert = new Certificate(options.certificate, options.rootKey, options.canisterId); + let blsVerify = options.blsVerify; + if (!blsVerify) { + blsVerify = await (await import('./utils/bls')).blsVerify; + } + const cert = new Certificate( + options.certificate, + options.rootKey, + options.canisterId, + blsVerify, + ); await cert.verify(); return cert; } @@ -139,6 +152,7 @@ export class Certificate { certificate: ArrayBuffer, private _rootKey: ArrayBuffer, private _canisterId: Principal, + private _blsVerify: CreateCertificateOptions['blsVerify'], ) { this.cert = cbor.decode(new Uint8Array(certificate)); } From d1a0597eef1fdf39c7ffa8171d69990e5461d409 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Tue, 6 Sep 2022 13:12:34 -0700 Subject: [PATCH 2/6] extends blsVerify support to ActorConfig and CanisterStatus --- docs/generated/changelog.html | 4 ++++ packages/agent/src/actor.ts | 17 ++++++++++++++--- packages/agent/src/canisterStatus/index.ts | 2 ++ packages/agent/src/polling/index.ts | 4 +++- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/docs/generated/changelog.html b/docs/generated/changelog.html index e7b6bd536..cd367daa8 100644 --- a/docs/generated/changelog.html +++ b/docs/generated/changelog.html @@ -10,6 +10,10 @@

Agent-JS Changelog

+

Version 0.13.3

+
    +
  • adds ability to polyfill bls verification in Certificate
  • +

Version 0.13.2

  • auth-client avoids localstorage global and can be used in a web worker or nodejs
  • diff --git a/packages/agent/src/actor.ts b/packages/agent/src/actor.ts index 270455679..5e6dfc5ec 100644 --- a/packages/agent/src/actor.ts +++ b/packages/agent/src/actor.ts @@ -14,6 +14,7 @@ import { pollForResponse, PollStrategyFactory, strategy } from './polling'; import { Principal } from '@dfinity/principal'; import { RequestId } from './request_id'; import { toHex } from './utils/buffer'; +import { CreateCertificateOptions } from './certificate'; export class ActorCallError extends AgentError { constructor( @@ -115,6 +116,11 @@ export interface ActorConfig extends CallConfig { args: unknown[], callConfig: CallConfig, ): Partial | void; + + /** + * Polyfill for BLS Certificate verification in case wasm is not supported + */ + blsVerify?: CreateCertificateOptions['blsVerify']; } // TODO: move this to proper typing when Candid support TypeScript. @@ -254,7 +260,7 @@ export class Actor { }); for (const [methodName, func] of service._fields) { - this[methodName] = _createActorMethod(this, methodName, func); + this[methodName] = _createActorMethod(this, methodName, func, config.blsVerify); } } } @@ -299,7 +305,12 @@ const DEFAULT_ACTOR_CONFIG = { export type ActorConstructor = new (config: ActorConfig) => ActorSubclass; -function _createActorMethod(actor: Actor, methodName: string, func: IDL.FuncClass): ActorMethod { +function _createActorMethod( + actor: Actor, + methodName: string, + func: IDL.FuncClass, + blsVerify?: CreateCertificateOptions['blsVerify'], +): ActorMethod { let caller: (options: CallConfig, ...args: unknown[]) => Promise; if (func.annotations.includes('query')) { caller = async (options, ...args) => { @@ -357,7 +368,7 @@ function _createActorMethod(actor: Actor, methodName: string, func: IDL.FuncClas } const pollStrategy = pollingStrategyFactory(); - const responseBytes = await pollForResponse(agent, ecid, requestId, pollStrategy); + const responseBytes = await pollForResponse(agent, ecid, requestId, pollStrategy, blsVerify); if (responseBytes !== undefined) { return decodeReturnValue(func.retTypes, responseBytes); diff --git a/packages/agent/src/canisterStatus/index.ts b/packages/agent/src/canisterStatus/index.ts index d4c151cce..3c31ae80b 100644 --- a/packages/agent/src/canisterStatus/index.ts +++ b/packages/agent/src/canisterStatus/index.ts @@ -4,6 +4,7 @@ import { lebDecode, PipeArrayBuffer } from '@dfinity/candid'; import { Principal } from '@dfinity/principal'; import { AgentError } from '../errors'; import { HttpAgent, Cbor, Certificate, toHex } from '..'; +import type { CreateCertificateOptions } from '..'; /** * Types of an entry on the canisterStatus map. @@ -50,6 +51,7 @@ export type CanisterStatusOptions = { canisterId: Principal; agent: HttpAgent; paths?: Path[] | Set; + blsVerify?: CreateCertificateOptions['blsVerify']; }; /** diff --git a/packages/agent/src/polling/index.ts b/packages/agent/src/polling/index.ts index 788550583..09c1e445d 100644 --- a/packages/agent/src/polling/index.ts +++ b/packages/agent/src/polling/index.ts @@ -1,6 +1,6 @@ import { Principal } from '@dfinity/principal'; import { Agent, RequestStatusResponseStatus } from '../agent'; -import { Certificate } from '../certificate'; +import { Certificate, CreateCertificateOptions } from '../certificate'; import { RequestId } from '../request_id'; import { toHex } from '../utils/buffer'; @@ -29,6 +29,7 @@ export async function pollForResponse( strategy: PollStrategy, // eslint-disable-next-line request?: any, + blsVerify?: CreateCertificateOptions['blsVerify'], ): Promise { const path = [new TextEncoder().encode('request_status'), requestId]; const currentRequest = request ?? (await agent.createReadStateRequest?.({ paths: [path] })); @@ -38,6 +39,7 @@ export async function pollForResponse( certificate: state.certificate, rootKey: agent.rootKey, canisterId: canisterId, + blsVerify, }); const maybeBuf = cert.lookup([...path, new TextEncoder().encode('status')]); let status; From ffe4434cf62b6ceaeb1d4bd4231449262351d59d Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Tue, 6 Sep 2022 13:35:13 -0700 Subject: [PATCH 3/6] fake timers for tests --- e2e/node/basic/canisterStatus.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/e2e/node/basic/canisterStatus.test.ts b/e2e/node/basic/canisterStatus.test.ts index 5b7e31cf9..57d15c2be 100644 --- a/e2e/node/basic/canisterStatus.test.ts +++ b/e2e/node/basic/canisterStatus.test.ts @@ -4,6 +4,7 @@ import counter from '../canisters/counter'; jest.setTimeout(30_000); describe.only('canister status', () => { + jest.useFakeTimers(); it('should fetch successfully', async () => { const counterObj = await (await counter)(); const agent = new HttpAgent({ host: `http://localhost:${process.env.REPLICA_PORT}` }); From 80552808f6f2189da92516661dd72b12ecaa8149 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Tue, 6 Sep 2022 14:17:46 -0700 Subject: [PATCH 4/6] promise.resolve strategy for cleanup --- dfx.json | 6 ++++++ e2e/node/basic/canisterStatus.test.ts | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/dfx.json b/dfx.json index 0995803d2..c23a076d3 100644 --- a/dfx.json +++ b/dfx.json @@ -4,5 +4,11 @@ "type": "assets", "source": ["docs/generated"] } + }, + "networks": { + "local": { + "bind": "127.0.0.1:8000", + "type": "ephemeral" + } } } diff --git a/e2e/node/basic/canisterStatus.test.ts b/e2e/node/basic/canisterStatus.test.ts index 57d15c2be..dff687889 100644 --- a/e2e/node/basic/canisterStatus.test.ts +++ b/e2e/node/basic/canisterStatus.test.ts @@ -3,8 +3,10 @@ import { Principal } from '@dfinity/principal'; import counter from '../canisters/counter'; jest.setTimeout(30_000); +afterEach(async () => { + await Promise.resolve(); +}); describe.only('canister status', () => { - jest.useFakeTimers(); it('should fetch successfully', async () => { const counterObj = await (await counter)(); const agent = new HttpAgent({ host: `http://localhost:${process.env.REPLICA_PORT}` }); From 44ddb537b0cc88e89b54dabd565d32b6fd3aa21e Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Tue, 6 Sep 2022 14:45:53 -0700 Subject: [PATCH 5/6] re-phrasing conditional import --- dfx.json | 6 ------ packages/agent/src/certificate.ts | 3 ++- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/dfx.json b/dfx.json index c23a076d3..0995803d2 100644 --- a/dfx.json +++ b/dfx.json @@ -4,11 +4,5 @@ "type": "assets", "source": ["docs/generated"] } - }, - "networks": { - "local": { - "bind": "127.0.0.1:8000", - "type": "ephemeral" - } } } diff --git a/packages/agent/src/certificate.ts b/packages/agent/src/certificate.ts index df47dc829..b2ded95ff 100644 --- a/packages/agent/src/certificate.ts +++ b/packages/agent/src/certificate.ts @@ -136,7 +136,8 @@ export class Certificate { public static async create(options: CreateCertificateOptions): Promise { let blsVerify = options.blsVerify; if (!blsVerify) { - blsVerify = await (await import('./utils/bls')).blsVerify; + const bls = await import('./utils/bls'); + blsVerify = bls.blsVerify; } const cert = new Certificate( options.certificate, From e07cdd1fb24bd93eb29ac818982e1ede2eb2a127 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Tue, 6 Sep 2022 14:58:22 -0700 Subject: [PATCH 6/6] removes conditional import --- packages/agent/src/certificate.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/agent/src/certificate.ts b/packages/agent/src/certificate.ts index b2ded95ff..16262e37e 100644 --- a/packages/agent/src/certificate.ts +++ b/packages/agent/src/certificate.ts @@ -3,7 +3,7 @@ import { AgentError } from './errors'; import { hash } from './request_id'; import { concat, fromHex, toHex } from './utils/buffer'; import { Principal } from '@dfinity/principal'; -import { blsVerify } from './utils/bls'; +import * as bls from './utils/bls'; /** * A certificate may fail verification with respect to the provided public key @@ -98,6 +98,8 @@ function isBufferEqual(a: ArrayBuffer, b: ArrayBuffer): boolean { return true; } +type VerifyFunc = (pk: Uint8Array, sig: Uint8Array, msg: Uint8Array) => Promise; + export interface CreateCertificateOptions { /** * The bytes encoding the certificate to be verified @@ -116,7 +118,7 @@ export interface CreateCertificateOptions { /** * BLS Verification strategy. Default strategy uses wasm for performance, but that may not be available in all contexts. */ - blsVerify?: (pk: Uint8Array, sig: Uint8Array, msg: Uint8Array) => Promise; + blsVerify?: VerifyFunc; } export class Certificate { @@ -136,7 +138,6 @@ export class Certificate { public static async create(options: CreateCertificateOptions): Promise { let blsVerify = options.blsVerify; if (!blsVerify) { - const bls = await import('./utils/bls'); blsVerify = bls.blsVerify; } const cert = new Certificate( @@ -153,7 +154,7 @@ export class Certificate { certificate: ArrayBuffer, private _rootKey: ArrayBuffer, private _canisterId: Principal, - private _blsVerify: CreateCertificateOptions['blsVerify'], + private _blsVerify: VerifyFunc, ) { this.cert = cbor.decode(new Uint8Array(certificate)); } @@ -170,7 +171,7 @@ export class Certificate { const msg = concat(domain_sep('ic-state-root'), rootHash); let sigVer = false; try { - sigVer = await blsVerify(new Uint8Array(key), new Uint8Array(sig), new Uint8Array(msg)); + sigVer = await this._blsVerify(new Uint8Array(key), new Uint8Array(sig), new Uint8Array(msg)); } catch (err) { sigVer = false; }