diff --git a/packages/keys/README.md b/packages/keys/README.md index 10883e60cf0d..d4ad7476ed6f 100644 --- a/packages/keys/README.md +++ b/packages/keys/README.md @@ -50,3 +50,7 @@ function handleSubmit() { } } ``` + +### `generateKeyPair()` + +Generates an Ed25519 public/private keypair for use with other methods in this package that accept `CryptoKey` objects. diff --git a/packages/keys/src/__tests__/keypair-test.ts b/packages/keys/src/__tests__/keypair-test.ts new file mode 100644 index 000000000000..16c6d09e063e --- /dev/null +++ b/packages/keys/src/__tests__/keypair-test.ts @@ -0,0 +1,51 @@ +import { generateKeyPair } from '../keypair'; + +describe('generateKeyPair', () => { + let oldIsSecureContext: boolean; + beforeEach(() => { + if (__BROWSER__) { + // FIXME: JSDOM does not set `isSecureContext` or otherwise allow you to configure it. + // Some discussion: https://github.com/jsdom/jsdom/issues/2751#issuecomment-846613392 + if (globalThis.isSecureContext !== undefined) { + oldIsSecureContext = globalThis.isSecureContext; + } + globalThis.isSecureContext = true; + } + }); + afterEach(() => { + if (oldIsSecureContext !== undefined) { + globalThis.isSecureContext = oldIsSecureContext; + } + }); + it.each(['private', 'public'])('generates an ed25519 %s `CryptoKey`', async type => { + expect.assertions(1); + const keyPair = await generateKeyPair(); + expect(keyPair).toMatchObject({ + [`${type}Key`]: expect.objectContaining({ + [Symbol.toStringTag]: 'CryptoKey', + algorithm: { name: 'Ed25519' }, + type, + }), + }); + }); + it('generates a non-extractable private key', async () => { + expect.assertions(1); + const { privateKey } = await generateKeyPair(); + expect(privateKey).toHaveProperty('extractable', false); + }); + it('generates a private key usable for signing operations', async () => { + expect.assertions(1); + const { privateKey } = await generateKeyPair(); + expect(privateKey).toHaveProperty('usages', ['sign']); + }); + it('generates an extractable public key', async () => { + expect.assertions(1); + const { publicKey } = await generateKeyPair(); + expect(publicKey).toHaveProperty('extractable', true); + }); + it('generates a public key usable for verifying signatures', async () => { + expect.assertions(1); + const { publicKey } = await generateKeyPair(); + expect(publicKey).toHaveProperty('usages', ['verify']); + }); +}); diff --git a/packages/keys/src/index.ts b/packages/keys/src/index.ts index a5c83cfaa57f..281bd9217d03 100644 --- a/packages/keys/src/index.ts +++ b/packages/keys/src/index.ts @@ -1 +1,2 @@ export * from './base58'; +export * from './keypair'; diff --git a/packages/keys/src/keypair.ts b/packages/keys/src/keypair.ts new file mode 100644 index 000000000000..dfd1076e1907 --- /dev/null +++ b/packages/keys/src/keypair.ts @@ -0,0 +1,11 @@ +import { assertKeyGenerationIsAvailable } from './guard'; + +export async function generateKeyPair(): Promise { + await assertKeyGenerationIsAvailable(); + const keyPair = await crypto.subtle.generateKey( + /* algorithm */ 'Ed25519', // Native implementation status: https://github.com/WICG/webcrypto-secure-curves/issues/20 + /* extractable */ false, // Prevents the bytes of the private key from being visible to JS. + /* allowed uses */ ['sign', 'verify'] + ); + return keyPair as CryptoKeyPair; +}