Skip to content

Commit

Permalink
feat: replaces the js-sha256 library with @noble/hashes (#753)
Browse files Browse the repository at this point in the history
* feat: replaces js-sha256 library with @noble/hashes

* adding dep to identity and principal
* adds missing dependency for identity-secp256k1
  • Loading branch information
krpeacock authored Aug 22, 2023
1 parent 83e4d34 commit ac5d163
Show file tree
Hide file tree
Showing 14 changed files with 68 additions and 39 deletions.
4 changes: 4 additions & 0 deletions docs/generated/changelog.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ <h1>Agent-JS Changelog</h1>
<section>
<h2>Version x.x.x</h2>
<ul>
<li>
feat: replaces the `js-sha256` library with `@noble/hashes` due to a breaking bug in
Chrome
</li>
<li>Fix: add `@dfinity/principal` as a peerDependency to `assets` and `candid`.</li>
<li>
Feat: HttpAgent now uses a default address of https://icp-api.io. Users will be warned for
Expand Down
30 changes: 22 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@
"@dfinity/principal": "^0.18.1"
},
"dependencies": {
"@noble/hashes": "^1.3.1",
"base64-arraybuffer": "^0.2.0",
"borc": "^2.1.1",
"js-sha256": "0.9.0",
"simple-cbor": "^0.4.1"
},
"devDependencies": {
Expand Down
6 changes: 3 additions & 3 deletions packages/agent/src/request_id.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { lebEncode } from '@dfinity/candid';
import { Principal } from '@dfinity/principal';
import borc from 'borc';
import { sha256 as jsSha256 } from 'js-sha256';
import { compare, concat } from './utils/buffer';
import { sha256 } from '@noble/hashes/sha256';
import { compare, concat, uint8ToBuf } from './utils/buffer';

export type RequestId = ArrayBuffer & { __requestId__: void };

Expand All @@ -11,7 +11,7 @@ export type RequestId = ArrayBuffer & { __requestId__: void };
* @param data - input to hash function
*/
export function hash(data: ArrayBuffer): ArrayBuffer {
return jsSha256.create().update(new Uint8Array(data)).arrayBuffer();
return uint8ToBuf(sha256.create().update(new Uint8Array(data)).digest());
}

interface ToHashable {
Expand Down
9 changes: 9 additions & 0 deletions packages/agent/src/utils/buffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,12 @@ export function compare(b1: ArrayBuffer, b2: ArrayBuffer): number {
}
return 0;
}

/**
* Returns a true ArrayBuffer from a Uint8Array, as Uint8Array.buffer is unsafe.
* @param {Uint8Array} arr Uint8Array to convert
* @returns ArrayBuffer
*/
export function uint8ToBuf(arr: Uint8Array): ArrayBuffer {
return new DataView(arr.buffer, arr.byteOffset, arr.byteLength).buffer;
}
2 changes: 1 addition & 1 deletion packages/assets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"peerDependencies": {
"@dfinity/agent": "^0.18.1",
"@dfinity/principal": "^0.18.1",
"js-sha256": "0.9.0"
"@noble/hashes": "^1.3.1"
},
"dependencies": {
"base64-arraybuffer": "^1.0.2",
Expand Down
27 changes: 15 additions & 12 deletions packages/assets/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import {
HashTree,
lookup_path,
reconstruct,
uint8ToBuf,
} from '@dfinity/agent';
import { lebDecode } from '@dfinity/candid';
import { PipeArrayBuffer } from '@dfinity/candid/lib/cjs/utils/buffer';
import { AssetsCanisterRecord, getAssetsCanister } from './canisters/assets';
import { Hasher, sha256 as jsSha256 } from 'js-sha256';
import { sha256 } from '@noble/hashes/sha256';
import { BatchOperationKind } from './canisters/assets_service';
import * as base64Arraybuffer from 'base64-arraybuffer';
import { isReadable, Readable } from './readable/readable';
Expand Down Expand Up @@ -196,14 +197,12 @@ export class AssetManager {
await readable.open();
const bytes = await readable.slice(0, readable.length);
await readable.close();
const sha256 =
config?.sha256 ??
new Uint8Array(jsSha256.create().update(new Uint8Array(bytes)).arrayBuffer());
const hash = config?.sha256 ?? sha256.create().update(new Uint8Array(bytes)).digest();
return this._actor.store({
key,
content: bytes,
content_type: readable.contentType,
sha256: [sha256],
sha256: [hash],
content_encoding: config?.contentEncoding ?? 'identity',
});
});
Expand Down Expand Up @@ -268,11 +267,15 @@ export class AssetManager {
}
}

// Required since the sha256 type is not exported
const hasher = sha256.create();
type SHA256TYPE = typeof hasher;

class AssetManagerBatch {
private _scheduledOperations: Array<
(batch_id: bigint, onProgress?: (progress: Progress) => void) => Promise<BatchOperationKind[]>
> = [];
private _sha256: { [key: string]: Hasher } = {};
private _sha256: { [key: string]: SHA256TYPE } = {};
private _progress: { [key: string]: Progress } = {};

constructor(
Expand All @@ -290,7 +293,7 @@ class AssetManagerBatch {
const [, config] = args;
const key = [config?.path ?? '', config?.fileName ?? readable.fileName].join('/');
if (!config?.sha256) {
this._sha256[key] = jsSha256.create();
this._sha256[key] = sha256.create();
}
this._progress[key] = { current: 0, total: readable.length };
config?.onProgress?.(this._progress[key]);
Expand Down Expand Up @@ -330,7 +333,7 @@ class AssetManagerBatch {
{
SetAssetContent: {
key,
sha256: [config?.sha256 ?? new Uint8Array(this._sha256[key].arrayBuffer())],
sha256: [config?.sha256 ?? new Uint8Array(this._sha256[key].digest())],
chunk_ids: chunkIds,
content_encoding: config?.contentEncoding ?? 'identity',
},
Expand Down Expand Up @@ -573,12 +576,12 @@ class Asset {
if (!this.sha256?.buffer) {
return false;
}
const sha256 = jsSha256.create();
const hash = sha256.create();
if (bytes) {
sha256.update(Array.isArray(bytes) ? new Uint8Array(bytes) : bytes);
hash.update(Array.isArray(bytes) ? new Uint8Array(bytes) : bytes);
} else {
await this.getChunks((_, chunk) => sha256.update(chunk), true);
await this.getChunks((_, chunk) => hash.update(chunk), true);
}
return compare(this.sha256.buffer, sha256.arrayBuffer()) === 0;
return compare(uint8ToBuf(this.sha256), uint8ToBuf(hash.digest())) === 0;
}
}
1 change: 1 addition & 0 deletions packages/identity-secp256k1/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
},
"dependencies": {
"@dfinity/agent": "^0.18.1",
"@noble/hashes": "^1.3.1",
"bip39": "^3.0.4",
"bs58check": "^2.1.2",
"secp256k1": "^4.0.3"
Expand Down
2 changes: 1 addition & 1 deletion packages/identity-secp256k1/src/secp256k1.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DerEncodedPublicKey, PublicKey } from '@dfinity/agent';
import { toHexString } from '@dfinity/candid/lib/cjs/utils/buffer';
import { randomBytes } from 'crypto';
import { sha256 } from 'js-sha256';
import { sha256 } from '@noble/hashes/sha256';
import Secp256k1 from 'secp256k1';
import { Secp256k1KeyIdentity, Secp256k1PublicKey } from './secp256k1';

Expand Down
4 changes: 2 additions & 2 deletions packages/identity-secp256k1/src/secp256k1.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable no-underscore-dangle */
import { DerEncodedPublicKey, KeyPair, Signature } from '@dfinity/agent';
import Secp256k1 from 'secp256k1';
import { sha256 } from 'js-sha256';
import { sha256 } from '@noble/hashes/sha256';
import { randomBytes } from 'tweetnacl';
import hdkey from './hdkey';
import { mnemonicToSeedSync } from 'bip39';
Expand Down Expand Up @@ -201,7 +201,7 @@ export class Secp256k1KeyIdentity extends SignIdentity {
*/
public async sign(challenge: ArrayBuffer): Promise<Signature> {
const hash = sha256.create();
hash.update(challenge);
hash.update(new Uint8Array(challenge));
const signature = Secp256k1.ecdsaSign(
new Uint8Array(hash.digest()),
new Uint8Array(this._privateKey),
Expand Down
2 changes: 1 addition & 1 deletion packages/identity/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@
"@peculiar/webcrypto": "^1.4.0"
},
"dependencies": {
"@noble/hashes": "^1.3.1",
"borc": "^2.1.1",
"js-sha256": "^0.9.0",
"tweetnacl": "^1.0.1"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/identity/src/identity/ecdsa.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { sha256 } from 'js-sha256';
import { sha256 } from '@noble/hashes/sha256';
import { ECDSAKeyIdentity } from './ecdsa';
import { Crypto } from '@peculiar/webcrypto';

Expand Down
10 changes: 5 additions & 5 deletions packages/principal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@
"@typescript-eslint/eslint-plugin": "^5.30.5",
"@typescript-eslint/parser": "^5.30.5",
"esbuild": "^0.15.16",
"eslint-plugin-jsdoc": "^39.3.3",
"eslint": "^8.19.0",
"eslint-plugin-jsdoc": "^39.3.3",
"jest": "^28.1.2",
"size-limit": "^8.2.6",
"text-encoding": "^0.7.0",
Expand All @@ -62,14 +62,14 @@
"typescript": "^4.2.3",
"whatwg-fetch": "^3.0.0"
},
"dependencies": {
"js-sha256": "^0.9.0"
},
"size-limit": [
{
"path": "./dist/index.js",
"limit": "100 kB",
"webpack": false
}
]
],
"dependencies": {
"@noble/hashes": "^1.3.1"
}
}
6 changes: 2 additions & 4 deletions packages/principal/src/utils/sha224.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { sha224 as jsSha224 } from 'js-sha256';
import { sha224 as jsSha224 } from '@noble/hashes/sha256';

/**
* Returns the SHA224 hash of the buffer.
* @param data Arraybuffer to encode
*/
export function sha224(data: ArrayBuffer): Uint8Array {
const shaObj = jsSha224.create();
shaObj.update(data);
return new Uint8Array(shaObj.array());
return jsSha224.create().update(new Uint8Array(data)).digest();
}

0 comments on commit ac5d163

Please sign in to comment.