diff --git a/packages/crypto/src/pbkdf2.spec.ts b/packages/crypto/src/pbkdf2.spec.ts index b67ccdcf..beddae2d 100644 --- a/packages/crypto/src/pbkdf2.spec.ts +++ b/packages/crypto/src/pbkdf2.spec.ts @@ -1,6 +1,7 @@ import { fromHex, toAscii, toUtf8 } from "@cosmjs/encoding"; +import { assert } from "@cosmjs/utils"; -import { pbkdf2Sha512 } from "./pbkdf2"; +import { getSubtle, pbkdf2Sha512, pbkdf2Sha512Subtle } from "./pbkdf2"; interface TestVector { secret: Uint8Array; @@ -115,7 +116,26 @@ describe("pbkdf2", () => { for (const [index, test] of brycxTests.entries()) { const { secret, salt, iterations, keylen, expected } = test; const hash = await pbkdf2Sha512(secret, salt, iterations, keylen); - expect(hash).withContext(`Index ${index}`).toEqual(expected); + expect(hash).withContext(`brycx tests index ${index}`).toEqual(expected); + } + }); + }); + + describe("pbkdf2Sha512Subtle", () => { + it("works", async () => { + const subtle = await getSubtle(); + assert(subtle); + + { + const { secret, salt, iterations, keylen, expected } = botanTest; + const hash = await pbkdf2Sha512Subtle(subtle, secret, salt, iterations, keylen); + expect(hash).toEqual(expected); + } + + for (const [index, test] of brycxTests.entries()) { + const { secret, salt, iterations, keylen, expected } = test; + const hash = await pbkdf2Sha512Subtle(subtle, secret, salt, iterations, keylen); + expect(hash).withContext(`brycx tests index ${index}`).toEqual(expected); } }); }); diff --git a/packages/crypto/src/pbkdf2.ts b/packages/crypto/src/pbkdf2.ts index 5faf56e4..8a3705da 100644 --- a/packages/crypto/src/pbkdf2.ts +++ b/packages/crypto/src/pbkdf2.ts @@ -1,4 +1,6 @@ -async function getSubtle(): Promise { +import { assert } from "@cosmjs/utils"; + +export async function getSubtle(): Promise { const g: any = globalThis; let subtle = g.crypto && g.crypto.subtle; if (!subtle) { @@ -10,6 +12,35 @@ async function getSubtle(): Promise { return subtle; } +export async function pbkdf2Sha512Subtle( + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + subtle: any, + secret: Uint8Array, + salt: Uint8Array, + iterations: number, + keylen: number, +): Promise { + assert(subtle, "Argument subtle is falsy"); + assert(typeof subtle === "object", "Argument subtle is not of type object"); + assert(typeof subtle.importKey === "function", "subtle.importKey is not a function"); + assert(typeof subtle.deriveBits === "function", "subtle.deriveBits is not a function"); + + return subtle.importKey("raw", secret, { name: "PBKDF2" }, false, ["deriveBits"]).then((key: Uint8Array) => + subtle + .deriveBits( + { + name: "PBKDF2", + salt: salt, + iterations: iterations, + hash: { name: "SHA-512" }, + }, + key, + keylen * 8, + ) + .then((buffer: ArrayBuffer) => new Uint8Array(buffer)), + ); +} + /** * A pbkdf2 implementation for BIP39. This is not exported at package level and thus a private API. */ @@ -21,22 +52,7 @@ export async function pbkdf2Sha512( ): Promise { const subtle = await getSubtle(); if (subtle) { - return subtle - .importKey("raw", secret, { name: "PBKDF2" }, false, ["deriveBits"]) - .then((key: Uint8Array) => - subtle - .deriveBits( - { - name: "PBKDF2", - salt: salt, - iterations: iterations, - hash: { name: "SHA-512" }, - }, - key, - keylen * 8, - ) - .then((buffer: ArrayBuffer) => new Uint8Array(buffer)), - ); + return pbkdf2Sha512Subtle(subtle, secret, salt, iterations, keylen); } else { const module = await import("crypto"); return new Promise((resolve, reject) => {