Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support for bls verification polyfill #626

Merged
merged 6 commits into from Sep 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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