From 125fc1540cfbc0a4afdba5aabac0884c750e58c1 Mon Sep 17 00:00:00 2001 From: Steven Luscher Date: Wed, 20 Mar 2024 21:29:00 -0700 Subject: [PATCH] Convert `SubtleCrypto` assertion methods to non-async (#2352) # Summary `assertKeyGenerationIsAvailable()` definitely needs to be async because it feature-detects `Ed25519`. These other assertions don't seem to need to be async though; they just test for the _presence_ of a function. Unless there's a good reason to yield the loop here, we should probably just make these checks sync for performance. # Test Plan ``` pnpm turbo test:typecheck test:unit:node test:unit:browser ``` --- .changeset/wild-lobsters-kick.md | 7 ++ .../addresses/src/program-derived-address.ts | 2 +- packages/addresses/src/public-key.ts | 2 +- .../src/__tests__/subtle-crypto-test.ts | 82 ++++++++++--------- packages/assertions/src/subtle-crypto.ts | 8 +- packages/keys/src/signatures.ts | 4 +- 6 files changed, 60 insertions(+), 45 deletions(-) create mode 100644 .changeset/wild-lobsters-kick.md diff --git a/.changeset/wild-lobsters-kick.md b/.changeset/wild-lobsters-kick.md new file mode 100644 index 000000000000..6aa14f789a6e --- /dev/null +++ b/.changeset/wild-lobsters-kick.md @@ -0,0 +1,7 @@ +--- +"@solana/addresses": patch +"@solana/assertions": patch +"@solana/keys": patch +--- + +`SubtleCrypto` assertion methods that can make their assertions synchronously are now synchronous, for performance. diff --git a/packages/addresses/src/program-derived-address.ts b/packages/addresses/src/program-derived-address.ts index 73196391f97f..cc0f8e324f84 100644 --- a/packages/addresses/src/program-derived-address.ts +++ b/packages/addresses/src/program-derived-address.ts @@ -87,7 +87,7 @@ const PDA_MARKER_BYTES = [ ] as const; async function createProgramDerivedAddress({ programAddress, seeds }: ProgramDerivedAddressInput): Promise
{ - await assertDigestCapabilityIsAvailable(); + assertDigestCapabilityIsAvailable(); if (seeds.length > MAX_SEEDS) { throw new SolanaError(SOLANA_ERROR__ADDRESSES__MAX_NUMBER_OF_PDA_SEEDS_EXCEEDED, { actual: seeds.length, diff --git a/packages/addresses/src/public-key.ts b/packages/addresses/src/public-key.ts index 994240785653..717293101bfd 100644 --- a/packages/addresses/src/public-key.ts +++ b/packages/addresses/src/public-key.ts @@ -4,7 +4,7 @@ import { SOLANA_ERROR__ADDRESSES__INVALID_ED25519_PUBLIC_KEY, SolanaError } from import { Address, getAddressDecoder } from './address'; export async function getAddressFromPublicKey(publicKey: CryptoKey): Promise
{ - await assertKeyExporterIsAvailable(); + assertKeyExporterIsAvailable(); if (publicKey.type !== 'public' || publicKey.algorithm.name !== 'Ed25519') { throw new SolanaError(SOLANA_ERROR__ADDRESSES__INVALID_ED25519_PUBLIC_KEY); } diff --git a/packages/assertions/src/__tests__/subtle-crypto-test.ts b/packages/assertions/src/__tests__/subtle-crypto-test.ts index e62e61eb782e..d3914f680ca0 100644 --- a/packages/assertions/src/__tests__/subtle-crypto-test.ts +++ b/packages/assertions/src/__tests__/subtle-crypto-test.ts @@ -17,18 +17,21 @@ import { } from '../subtle-crypto'; describe('assertDigestCapabilityIsAvailable()', () => { - it('resolves to `undefined` without throwing', async () => { - expect.assertions(1); - await expect(assertDigestCapabilityIsAvailable()).resolves.toBeUndefined(); + describe('when `SubtleCrypto::digest` is available', () => { + it('does not throw', () => { + expect(assertDigestCapabilityIsAvailable).not.toThrow(); + }); + it('returns `undefined`', () => { + expect(assertDigestCapabilityIsAvailable()).toBeUndefined(); + }); }); if (__BROWSER__) { describe('when in an insecure browser context', () => { beforeEach(() => { globalThis.isSecureContext = false; }); - it('rejects', async () => { - expect.assertions(1); - await expect(() => assertDigestCapabilityIsAvailable()).rejects.toThrow( + it('throws', () => { + expect(assertDigestCapabilityIsAvailable).toThrow( new SolanaError(SOLANA_ERROR__SUBTLE_CRYPTO__DISALLOWED_IN_INSECURE_CONTEXT), ); }); @@ -45,9 +48,8 @@ describe('assertDigestCapabilityIsAvailable()', () => { afterEach(() => { globalThis.crypto.subtle.digest = oldDigest; }); - it('rejects', async () => { - expect.assertions(1); - await expect(assertDigestCapabilityIsAvailable()).rejects.toThrow( + it('throws', () => { + expect(assertDigestCapabilityIsAvailable).toThrow( new SolanaError(SOLANA_ERROR__SUBTLE_CRYPTO__DIGEST_UNIMPLEMENTED), ); }); @@ -55,18 +57,21 @@ describe('assertDigestCapabilityIsAvailable()', () => { }); describe('assertKeyExporterIsAvailable()', () => { - it('resolves to `undefined` without throwing', async () => { - expect.assertions(1); - await expect(assertKeyExporterIsAvailable()).resolves.toBeUndefined(); + describe('when `SubtleCrypto::exportKey` is available', () => { + it('does not throw', () => { + expect(assertKeyExporterIsAvailable).not.toThrow(); + }); + it('returns `undefined`', () => { + expect(assertKeyExporterIsAvailable()).toBeUndefined(); + }); }); if (__BROWSER__) { describe('when in an insecure browser context', () => { beforeEach(() => { globalThis.isSecureContext = false; }); - it('rejects', async () => { - expect.assertions(1); - await expect(() => assertKeyExporterIsAvailable()).rejects.toThrow( + it('throws', () => { + expect(assertKeyExporterIsAvailable).toThrow( new SolanaError(SOLANA_ERROR__SUBTLE_CRYPTO__DISALLOWED_IN_INSECURE_CONTEXT), ); }); @@ -83,9 +88,8 @@ describe('assertKeyExporterIsAvailable()', () => { afterEach(() => { globalThis.crypto.subtle.exportKey = oldExportKey; }); - it('rejects', async () => { - expect.assertions(1); - await expect(assertKeyExporterIsAvailable()).rejects.toThrow( + it('throws', () => { + expect(assertKeyExporterIsAvailable).toThrow( new SolanaError(SOLANA_ERROR__SUBTLE_CRYPTO__EXPORT_FUNCTION_UNIMPLEMENTED), ); }); @@ -114,7 +118,7 @@ describe('assertKeyGenerationIsAvailable()', () => { }); it('rejects', async () => { expect.assertions(1); - await expect(() => assertKeyGenerationIsAvailable()).rejects.toThrow( + await expect(assertKeyGenerationIsAvailable()).rejects.toThrow( new SolanaError(SOLANA_ERROR__SUBTLE_CRYPTO__DISALLOWED_IN_INSECURE_CONTEXT), ); }); @@ -177,18 +181,21 @@ describe('assertKeyGenerationIsAvailable()', () => { }); describe('assertSigningCapabilityIsAvailable()', () => { - it('resolves to `undefined` without throwing', async () => { - expect.assertions(1); - await expect(assertSigningCapabilityIsAvailable()).resolves.toBeUndefined(); + describe('when `SubtleCrypto::sign` is available', () => { + it('does not throw', () => { + expect(assertSigningCapabilityIsAvailable).not.toThrow(); + }); + it('returns `undefined`', () => { + expect(assertSigningCapabilityIsAvailable()).toBeUndefined(); + }); }); if (__BROWSER__) { describe('when in an insecure browser context', () => { beforeEach(() => { globalThis.isSecureContext = false; }); - it('rejects', async () => { - expect.assertions(1); - await expect(() => assertSigningCapabilityIsAvailable()).rejects.toThrow( + it('throws', () => { + expect(assertSigningCapabilityIsAvailable).toThrow( new SolanaError(SOLANA_ERROR__SUBTLE_CRYPTO__DISALLOWED_IN_INSECURE_CONTEXT), ); }); @@ -205,9 +212,8 @@ describe('assertSigningCapabilityIsAvailable()', () => { afterEach(() => { globalThis.crypto.subtle.sign = oldSign; }); - it('rejects', async () => { - expect.assertions(1); - await expect(assertSigningCapabilityIsAvailable()).rejects.toThrow( + it('throws', () => { + expect(assertSigningCapabilityIsAvailable).toThrow( new SolanaError(SOLANA_ERROR__SUBTLE_CRYPTO__SIGN_FUNCTION_UNIMPLEMENTED), ); }); @@ -215,18 +221,21 @@ describe('assertSigningCapabilityIsAvailable()', () => { }); describe('assertVerificationCapabilityIsAvailable()', () => { - it('resolves to `undefined` without throwing', async () => { - expect.assertions(1); - await expect(assertVerificationCapabilityIsAvailable()).resolves.toBeUndefined(); + describe('when `SubtleCrypto::verify` is available', () => { + it('does not throw', () => { + expect(assertVerificationCapabilityIsAvailable).not.toThrow(); + }); + it('returns `undefined`', () => { + expect(assertVerificationCapabilityIsAvailable()).toBeUndefined(); + }); }); if (__BROWSER__) { describe('when in an insecure browser context', () => { beforeEach(() => { globalThis.isSecureContext = false; }); - it('rejects', async () => { - expect.assertions(1); - await expect(() => assertVerificationCapabilityIsAvailable()).rejects.toThrow( + it('throws', () => { + expect(assertVerificationCapabilityIsAvailable).toThrow( new SolanaError(SOLANA_ERROR__SUBTLE_CRYPTO__DISALLOWED_IN_INSECURE_CONTEXT), ); }); @@ -243,9 +252,8 @@ describe('assertVerificationCapabilityIsAvailable()', () => { afterEach(() => { globalThis.crypto.subtle.verify = oldVerify; }); - it('rejects', async () => { - expect.assertions(1); - await expect(assertVerificationCapabilityIsAvailable()).rejects.toThrow( + it('throws', () => { + expect(assertVerificationCapabilityIsAvailable).toThrow( new SolanaError(SOLANA_ERROR__SUBTLE_CRYPTO__VERIFY_FUNCTION_UNIMPLEMENTED), ); }); diff --git a/packages/assertions/src/subtle-crypto.ts b/packages/assertions/src/subtle-crypto.ts index f8c1fa6c55fd..093207e02a2e 100644 --- a/packages/assertions/src/subtle-crypto.ts +++ b/packages/assertions/src/subtle-crypto.ts @@ -36,7 +36,7 @@ async function isEd25519CurveSupported(subtle: SubtleCrypto): Promise { } } -export async function assertDigestCapabilityIsAvailable() { +export function assertDigestCapabilityIsAvailable() { assertIsSecureContext(); if (typeof globalThis.crypto === 'undefined' || typeof globalThis.crypto.subtle?.digest !== 'function') { throw new SolanaError(SOLANA_ERROR__SUBTLE_CRYPTO__DIGEST_UNIMPLEMENTED); @@ -53,21 +53,21 @@ export async function assertKeyGenerationIsAvailable() { } } -export async function assertKeyExporterIsAvailable() { +export function assertKeyExporterIsAvailable() { assertIsSecureContext(); if (typeof globalThis.crypto === 'undefined' || typeof globalThis.crypto.subtle?.exportKey !== 'function') { throw new SolanaError(SOLANA_ERROR__SUBTLE_CRYPTO__EXPORT_FUNCTION_UNIMPLEMENTED); } } -export async function assertSigningCapabilityIsAvailable() { +export function assertSigningCapabilityIsAvailable() { assertIsSecureContext(); if (typeof globalThis.crypto === 'undefined' || typeof globalThis.crypto.subtle?.sign !== 'function') { throw new SolanaError(SOLANA_ERROR__SUBTLE_CRYPTO__SIGN_FUNCTION_UNIMPLEMENTED); } } -export async function assertVerificationCapabilityIsAvailable() { +export function assertVerificationCapabilityIsAvailable() { assertIsSecureContext(); if (typeof globalThis.crypto === 'undefined' || typeof globalThis.crypto.subtle?.verify !== 'function') { throw new SolanaError(SOLANA_ERROR__SUBTLE_CRYPTO__VERIFY_FUNCTION_UNIMPLEMENTED); diff --git a/packages/keys/src/signatures.ts b/packages/keys/src/signatures.ts index 8e9240a42cca..b82c873dd32e 100644 --- a/packages/keys/src/signatures.ts +++ b/packages/keys/src/signatures.ts @@ -57,7 +57,7 @@ export function isSignature(putativeSignature: string): putativeSignature is Sig } export async function signBytes(key: CryptoKey, data: Uint8Array): Promise { - await assertSigningCapabilityIsAvailable(); + assertSigningCapabilityIsAvailable(); const signedData = await crypto.subtle.sign('Ed25519', key, data); return new Uint8Array(signedData) as SignatureBytes; } @@ -68,6 +68,6 @@ export function signature(putativeSignature: string): Signature { } export async function verifySignature(key: CryptoKey, signature: SignatureBytes, data: Uint8Array): Promise { - await assertVerificationCapabilityIsAvailable(); + assertVerificationCapabilityIsAvailable(); return await crypto.subtle.verify('Ed25519', key, signature, data); }