Skip to content

Commit

Permalink
Convert SubtleCrypto assertion methods to non-async (#2352)
Browse files Browse the repository at this point in the history
# 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
```
  • Loading branch information
steveluscher committed Mar 21, 2024
1 parent deb7b80 commit 125fc15
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 45 deletions.
7 changes: 7 additions & 0 deletions .changeset/wild-lobsters-kick.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion packages/addresses/src/program-derived-address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const PDA_MARKER_BYTES = [
] as const;

async function createProgramDerivedAddress({ programAddress, seeds }: ProgramDerivedAddressInput): Promise<Address> {
await assertDigestCapabilityIsAvailable();
assertDigestCapabilityIsAvailable();
if (seeds.length > MAX_SEEDS) {
throw new SolanaError(SOLANA_ERROR__ADDRESSES__MAX_NUMBER_OF_PDA_SEEDS_EXCEEDED, {
actual: seeds.length,
Expand Down
2 changes: 1 addition & 1 deletion packages/addresses/src/public-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Address> {
await assertKeyExporterIsAvailable();
assertKeyExporterIsAvailable();
if (publicKey.type !== 'public' || publicKey.algorithm.name !== 'Ed25519') {
throw new SolanaError(SOLANA_ERROR__ADDRESSES__INVALID_ED25519_PUBLIC_KEY);
}
Expand Down
82 changes: 45 additions & 37 deletions packages/assertions/src/__tests__/subtle-crypto-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
);
});
Expand All @@ -45,28 +48,30 @@ 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),
);
});
});
});

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),
);
});
Expand All @@ -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),
);
});
Expand Down Expand Up @@ -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),
);
});
Expand Down Expand Up @@ -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),
);
});
Expand All @@ -205,28 +212,30 @@ 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),
);
});
});
});

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),
);
});
Expand All @@ -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),
);
});
Expand Down
8 changes: 4 additions & 4 deletions packages/assertions/src/subtle-crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ async function isEd25519CurveSupported(subtle: SubtleCrypto): Promise<boolean> {
}
}

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);
Expand All @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions packages/keys/src/signatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function isSignature(putativeSignature: string): putativeSignature is Sig
}

export async function signBytes(key: CryptoKey, data: Uint8Array): Promise<SignatureBytes> {
await assertSigningCapabilityIsAvailable();
assertSigningCapabilityIsAvailable();
const signedData = await crypto.subtle.sign('Ed25519', key, data);
return new Uint8Array(signedData) as SignatureBytes;
}
Expand All @@ -68,6 +68,6 @@ export function signature(putativeSignature: string): Signature {
}

export async function verifySignature(key: CryptoKey, signature: SignatureBytes, data: Uint8Array): Promise<boolean> {
await assertVerificationCapabilityIsAvailable();
assertVerificationCapabilityIsAvailable();
return await crypto.subtle.verify('Ed25519', key, signature, data);
}

0 comments on commit 125fc15

Please sign in to comment.