Skip to content

Commit

Permalink
feat: support for bls verification polyfill (#626)
Browse files Browse the repository at this point in the history
* feat: support for polyfill of blsVerify
  • Loading branch information
krpeacock committed Sep 7, 2022
1 parent 4d64065 commit 25307d5
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 7 deletions.
4 changes: 4 additions & 0 deletions docs/generated/changelog.html
Expand Up @@ -10,6 +10,10 @@
<h1>Agent-JS Changelog</h1>

<section>
<h2>Version 0.13.3</h2>
<ul>
<li>adds ability to polyfill bls verification in Certificate</li>
</ul>
<h2>Version 0.13.2</h2>
<ul>
<li>auth-client avoids localstorage global and can be used in a web worker or nodejs</li>
Expand Down
3 changes: 3 additions & 0 deletions e2e/node/basic/canisterStatus.test.ts
Expand Up @@ -3,6 +3,9 @@ import { Principal } from '@dfinity/principal';
import counter from '../canisters/counter';

jest.setTimeout(30_000);
afterEach(async () => {
await Promise.resolve();
});
describe.only('canister status', () => {
it('should fetch successfully', async () => {
const counterObj = await (await counter)();
Expand Down
17 changes: 14 additions & 3 deletions packages/agent/src/actor.ts
Expand Up @@ -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(
Expand Down Expand Up @@ -115,6 +116,11 @@ export interface ActorConfig extends CallConfig {
args: unknown[],
callConfig: CallConfig,
): Partial<CallConfig> | 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.
Expand Down Expand Up @@ -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);
}
}
}
Expand Down Expand Up @@ -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<unknown>;
if (func.annotations.includes('query')) {
caller = async (options, ...args) => {
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions packages/agent/src/canisterStatus/index.ts
Expand Up @@ -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.
Expand Down Expand Up @@ -50,6 +51,7 @@ export type CanisterStatusOptions = {
canisterId: Principal;
agent: HttpAgent;
paths?: Path[] | Set<Path>;
blsVerify?: CreateCertificateOptions['blsVerify'];
};

/**
Expand Down
22 changes: 19 additions & 3 deletions 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 * as bls from './utils/bls';

/**
* A certificate may fail verification with respect to the provided public key
Expand Down Expand Up @@ -98,6 +98,8 @@ function isBufferEqual(a: ArrayBuffer, b: ArrayBuffer): boolean {
return true;
}

type VerifyFunc = (pk: Uint8Array, sig: Uint8Array, msg: Uint8Array) => Promise<boolean>;

export interface CreateCertificateOptions {
/**
* The bytes encoding the certificate to be verified
Expand All @@ -113,6 +115,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?: VerifyFunc;
}

export class Certificate {
Expand All @@ -130,7 +136,16 @@ export class Certificate {
* @throws {CertificateVerificationError}
*/
public static async create(options: CreateCertificateOptions): Promise<Certificate> {
const cert = new Certificate(options.certificate, options.rootKey, options.canisterId);
let blsVerify = options.blsVerify;
if (!blsVerify) {
blsVerify = bls.blsVerify;
}
const cert = new Certificate(
options.certificate,
options.rootKey,
options.canisterId,
blsVerify,
);
await cert.verify();
return cert;
}
Expand All @@ -139,6 +154,7 @@ export class Certificate {
certificate: ArrayBuffer,
private _rootKey: ArrayBuffer,
private _canisterId: Principal,
private _blsVerify: VerifyFunc,
) {
this.cert = cbor.decode(new Uint8Array(certificate));
}
Expand All @@ -155,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;
}
Expand Down
4 changes: 3 additions & 1 deletion 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';

Expand Down Expand Up @@ -29,6 +29,7 @@ export async function pollForResponse(
strategy: PollStrategy,
// eslint-disable-next-line
request?: any,
blsVerify?: CreateCertificateOptions['blsVerify'],
): Promise<ArrayBuffer> {
const path = [new TextEncoder().encode('request_status'), requestId];
const currentRequest = request ?? (await agent.createReadStateRequest?.({ paths: [path] }));
Expand All @@ -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;
Expand Down

0 comments on commit 25307d5

Please sign in to comment.