diff --git a/packages/crypto/src/pbkdf2.spec.ts b/packages/crypto/src/pbkdf2.spec.ts index beddae2d..3606811a 100644 --- a/packages/crypto/src/pbkdf2.spec.ts +++ b/packages/crypto/src/pbkdf2.spec.ts @@ -1,7 +1,7 @@ import { fromHex, toAscii, toUtf8 } from "@cosmjs/encoding"; import { assert } from "@cosmjs/utils"; -import { getSubtle, pbkdf2Sha512, pbkdf2Sha512Subtle } from "./pbkdf2"; +import { getCryptoModule, getSubtle, pbkdf2Sha512, pbkdf2Sha512Crypto, pbkdf2Sha512Subtle } from "./pbkdf2"; interface TestVector { secret: Uint8Array; @@ -139,4 +139,23 @@ describe("pbkdf2", () => { } }); }); + + describe("pbkdf2Sha512Crypto", () => { + it("works", async () => { + const crypto = await getCryptoModule(); + assert(crypto); + + { + const { secret, salt, iterations, keylen, expected } = botanTest; + const hash = await pbkdf2Sha512Crypto(crypto, 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 pbkdf2Sha512Crypto(crypto, 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 8a3705da..70dca985 100644 --- a/packages/crypto/src/pbkdf2.ts +++ b/packages/crypto/src/pbkdf2.ts @@ -1,11 +1,20 @@ import { assert } from "@cosmjs/utils"; +export async function getCryptoModule(): Promise { + try { + const crypto = await import("crypto"); + return crypto; + } catch { + return undefined; + } +} + export async function getSubtle(): Promise { const g: any = globalThis; let subtle = g.crypto && g.crypto.subtle; if (!subtle) { - const crypto: any = await import("crypto"); - if (crypto.webcrypto && crypto.webcrypto.subtle) { + const crypto = await getCryptoModule(); + if (crypto && crypto.webcrypto && crypto.webcrypto.subtle) { subtle = crypto.webcrypto.subtle; } } @@ -41,6 +50,29 @@ export async function pbkdf2Sha512Subtle( ); } +export async function pbkdf2Sha512Crypto( + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + crypto: any, + secret: Uint8Array, + salt: Uint8Array, + iterations: number, + keylen: number, +): Promise { + assert(crypto, "Argument crypto is falsy"); + assert(typeof crypto === "object", "Argument crypto is not of type object"); + assert(typeof crypto.pbkdf2 === "function", "crypto.pbkdf2 is not a function"); + + return new Promise((resolve, reject) => { + crypto.pbkdf2(secret, salt, iterations, keylen, "sha512", (error: any, result: any) => { + if (error) { + reject(error); + } else { + resolve(Uint8Array.from(result)); + } + }); + }); +} + /** * A pbkdf2 implementation for BIP39. This is not exported at package level and thus a private API. */ @@ -54,15 +86,14 @@ export async function pbkdf2Sha512( if (subtle) { return pbkdf2Sha512Subtle(subtle, secret, salt, iterations, keylen); } else { - const module = await import("crypto"); - return new Promise((resolve, reject) => { - module.pbkdf2(secret, salt, iterations, keylen, "sha512", (error, result) => { - if (error) { - reject(error); - } else { - resolve(Uint8Array.from(result)); - } - }); - }); + const crypto = await getCryptoModule(); + if (crypto) { + return pbkdf2Sha512Crypto(crypto, secret, salt, iterations, keylen); + } else { + throw new Error( + "Could not find a pbkdf2 implementation in subtle (WebCrypto) or the crypto module. " + + "If you need a pure software implementation, please open an issue at https://github.com/cosmos/cosmjs", + ); + } } }